diff --git a/.vscode/launch.json b/.vscode/launch.json index a15df60..680073b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -35,7 +35,7 @@ "type": "debugpy", "request": "launch", "program": "icm-run.py", - "args": ["install"], + "args": ["install","iceK"], //"console": "internalConsole", "console": "integratedTerminal", "justMyCode": true, diff --git a/icm/__main__.py b/icm/__main__.py index c6489fb..abb2406 100644 --- a/icm/__main__.py +++ b/icm/__main__.py @@ -48,6 +48,11 @@ def info(): @cli.command() -def install(): +@click.argument("coltag", nargs=1) +@click.option( + "-d", "--dev", is_flag=True, help="Install latest development version" +) +def install(coltag, dev): """Install collections""" - cmd_install.main() + + cmd_install.main(coltag, dev) diff --git a/icm/commands/cmd_install.py b/icm/commands/cmd_install.py index b2cc5fb..ee62763 100644 --- a/icm/commands/cmd_install.py +++ b/icm/commands/cmd_install.py @@ -3,6 +3,7 @@ import zipfile import os import sys +import re from pathlib import Path import requests @@ -29,6 +30,7 @@ class Collection: GITHUB_PREFIX = "/archive/refs/" GITHUB_TYPE_DEV = "heads/" GITHUB_TYPE_VER = "tags/" + PACKAGEJSON = "/raw/main/package.json" def __init__(self, folders: commons.Folders) -> None: self.folders = folders @@ -82,6 +84,20 @@ def url(self, name: str, version="") -> str: # -- Return the url return url + def package_url(self, name: str) -> str: + """Return the url of the package.json file for the given + collection + * name: Collection name (Ex. "iceK") + """ + # -- Example of url for the iceK collection: + # https://github.com/FPGAwars/iceK/raw/main/package.json + + # -- Construct the URL + url = f"{self.GITHUB_FPGAWARS}{name}{self.PACKAGEJSON}" + + # -- Return the URL + return url + def nametag(self, name: str, version=""): """Return the collection name+tag: * version Given: @@ -134,6 +150,23 @@ def download(self, url: str, destfile: Path): # Update pogress bar pbar.update(len(chunk)) + def download_package(self, url: str) -> object: # Or None: + """Download the package.json as an object + url: package.json url + Returns: The package.json as an object + or None if there was an error + """ + # -- Generate an http request + response = requests.get(url, timeout=10) + + # -- Check the status. If not ok, exit! + if response.status_code != 200: + return None + + # -- Return the package object + package = response.json() + return package + # -- Uncompress the collection zip file def uncompress(self, zip_file: Path): """Uncompress the given zip file. The destination folder is @@ -160,26 +193,128 @@ def uncompress(self, zip_file: Path): # -- Update progress bar! pbar.update(1) + def parse_coltag(self, coltag: str) -> dict: # or | None: + """Parse a collection name with optional tag version + Ex: "iceK@0.1.4" + Return: + None: There is an error + result: Dictionary with the calculated values + result['name']: Collection name + result['version']: Collection version (it could be "") + """ + # -- Pattern for parsing strings in the format [@] + pattern = r"^(?P[a-zA-Z0-9]+)(@(?P\d+\.\d+(\.\d+)?))?$" + + # Busca coincidencias en la cadena de entrada + match = re.match(pattern, coltag) + + # -- TODO: Raise an exception! + if match: + return match.groupdict() -def main(): - """ENTRY POINT: Install collections""" + # -- No match. Incorrect collection tag + return None + + +def main(coltag: str, dev: bool) -> None: + """ENTRY POINT: Install collections + * coltag: Nombre de la coleccion + tag opcional + Ex. iceK, iceK@0.1.4 + * dev: Install development version + """ # -- Get context information folders = commons.Folders() collection = Collection(folders) - # -- Install the collection iceK-0.1.4 - install(collection, "iceK", "0.1.4") - install(collection, "iceK") - install(collection, "iceK", "0.1.3") - install(collection, "iceWires") - install(collection, "iceIO") - install(collection, "iceGates", "0.3.1") - install(collection, "iceMux") - install(collection, "iceCoders") - install(collection, "iceFF") - install(collection, "iceRegs") - install(collection, "iceSRegs") + print() + + # -- Install the collection! + install_main(collection, coltag, dev) + + # -- Test + # -- TODO: Move it to a test python file + # install_main(collection, "iceK@0.1.4", True) + # install_main(collection, "iceK", True) + # install_main(collection, "iceK@0.1.3") + # install_main(collection, "iceK") + # install_main(collection, "iceWires") + # install_main(collection, "iceIO") + # install_main(collection, "iceGates") + # install_main(collection, "iceMux") + # install_main(collection, "iceCoders") + # install_main(collection, "iceFF") + # install_main(collection, "iceRegs") + # install_main(collection, "iceSRegs") + + +def install_main( + collection: Collection, coltag: str, dev: bool = False +) -> None: + """Main function for installing collections + * collection: Collection class (context) + * coltag: name + version tag (Ex. iceK@0.1.4) + * dev: Development flag + * True: Install latest version from the repo + * False: Install a stable version + + Main use cases: + 1. If dev flag active: highest priority. Install dev collection + (the version is ignored, if given) + 2. "name@version" is given --> Install the given version of collection + 3. "name" is given --> Install the latest stable version + """ + + # -- Parse the collection + tag + coltag = collection.parse_coltag(coltag) + + if not coltag: + print("---> coleccion incorrecta!") + sys.exit(1) + + # -- Get the collection name and version: + name, version = (coltag["name"], coltag["version"]) + + # -- Analyze all the cases + # -- The --dev flag has the highest priority. If it is set, + # -- it does not matter which version was specified (if any) + if dev: + + # -- Conflict! Both version and --dev are specified + # -- --dev has the highest priority. Warn the user + if version: + click.secho( + f"Warning! Installing dev version instead of {version}", + fg="red", + ) + + # -- Case 1: Install development version + install(collection, name) + return + + # -- Case 2: collection name + version given + if version: + install(collection, name, version) + return + + # -- Case 3: Only collection name given + + # Calculate the url for the collection package.json file + url = collection.package_url(name) + + # -- Download the package.json + package = collection.download_package(url) + + # -- Get the latest version + if package: + version = package["version"] + + # -- Install the collection! + install(collection, name, version) + else: + click.secho(f"Collection: {name}", fg="red") + click.secho("No package.json downloaded", fg="red") + click.secho(f"URL: {url}", fg="red") def install(collection: Collection, name: str, version="") -> None: @@ -193,7 +328,6 @@ def install(collection: Collection, name: str, version="") -> None: # -- Get the name+tag nametag = collection.nametag(name, version) - print() click.secho(f"Installing collection {nametag}", fg="yellow") abs_filename = collection.abs_filename(version)