From 27755e260a1241dfffe2009a0b0a01ba321c38cb Mon Sep 17 00:00:00 2001 From: Grant Gurvis Date: Wed, 29 May 2024 12:31:58 -0700 Subject: [PATCH] Use rye to manage project --- .DS_Store | Bin 0 -> 6148 bytes .github/workflows/build.yml | 16 +++ .gitignore | 6 +- .python-version | 1 + fig.py | 201 ---------------------------- pyproject.toml | 27 ++++ requirements-dev.lock | 36 +++++ requirements.lock | 36 +++++ setup.sh | 3 + src/aws_cli_plugin/__init__.py | 234 +++++++++++++++++++++++++++++++++ 10 files changed, 357 insertions(+), 203 deletions(-) create mode 100644 .DS_Store create mode 100644 .github/workflows/build.yml create mode 100644 .python-version delete mode 100644 fig.py create mode 100644 pyproject.toml create mode 100644 requirements-dev.lock create mode 100644 requirements.lock mode change 100644 => 100755 setup.sh create mode 100644 src/aws_cli_plugin/__init__.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c88a062b05be4fd1d362b3e4c6a7481e718b69d0 GIT binary patch literal 6148 zcmeH~O$x$5422WzLU7Zi%h`AUZ!n0Spcima5J4*Vx1OW>k_m#k8KJGkN^pg011%5 z4-v3?8#bF)Wh4O-Ab}?V`#vPNX$~z_{nLTqBLK8P*$r!-C7{U)&>UK-q5{*H9yD6j z#}KP~J2b_)99pW@cF`C;crrWIXQgOGwy`I%~QMGk}L;X0y%TE9jyNVZZH|!@{KyzrRiVBQB0*--!1inh( E0rZ6u#{d8T literal 0 HcmV?d00001 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c2a5c63 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,16 @@ +on: + push: + branches: + - main + +jobs: + label_issue: + runs-on: ubuntu-latest + steps: + - name: Setup Rye + run: curl -sSf https://rye.astral.sh/get | RYE_INSTALL_OPTION="--yes" bash + - run: rye sync + - run: | + mkdir -p ./export/js/aws + mkdir -p ./export/ts/aws + - run: rye run aws diff --git a/.gitignore b/.gitignore index 1398f7a..2b071d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -export/ +.venv +.env __pycache__ -out.json \ No newline at end of file +export +dist diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..871f80a --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.3 diff --git a/fig.py b/fig.py deleted file mode 100644 index d83321d..0000000 --- a/fig.py +++ /dev/null @@ -1,201 +0,0 @@ -import json -import re -import os -from awscli.customizations.commands import BasicCommand -from awscli.clidriver import ( - CLIDriver, ServiceCommand, ServiceOperation, CLICommand) - -exportDirectory = '/Users/mschrage/fig/research/aws-cli-plugin/' -exportTypescript = True - -def awscli_initialize(cli): - cli.register("building-command-table", read_commands) - # cli.register("building-command-table.s3", read_commands) - - # cli.register("building-command-table", read_commands) - -def print_args(args): - for argName in args: - arg = args[argName] - argJSON = { "name": arg.cli_name, "type": arg.cli_type_name, "nargs": arg.nargs, "required": arg.required, "documentation": arg.documentation, "suggestions": arg.choices} - - # print(arg.cli_name, arg.cli_type_name, arg.nargs, arg.required, arg.documentation, arg.choices) - -def stripHTML(text): - return re.sub('<[^<]+?>', '', text).strip() - -def argumentsDictionary(args): - flags = [] - positional = [] - for argName in args: - arg = args[argName] - choices = arg.choices if arg.choices == None or isinstance(arg.choices, list) else list(arg.choices) - description = stripHTML(arg.documentation) - variadic = arg.nargs != None and arg.nargs == "+" - # print(arg.cli_name, arg.nargs, arg.required) - # js = { "name": arg.cli_name, "type": arg.cli_type_name, "nargs": arg.nargs, "required": arg.required, "documentation": arg.documentation, "suggestions": arg.choices} - raw = {"name": arg.cli_name } - - if description != None and len(description) > 0: - raw["description"] = description - - if arg.positional_arg: - if choices != None and len(choices) > 0: - raw["suggestions"] = choices - # raw["isOptional"] = not arg.required - if variadic: - raw["variadic"] = variadic - - positional.append(raw) - elif arg.cli_type_name == "boolean": - flags.append(raw) - else: - raw["args"] = { "name": arg.cli_type_name } - if choices != None and len(choices) > 0: - raw["args"]["suggestions"] = choices - # raw["isOptional"] = not arg.required - - if variadic: - raw["args"]["variadic"] = variadic - flags.append(raw) - - return (flags, positional) - - -def generateCompletionSpecSkeleton(name, command): - - - command_table = command._create_command_table() - subcommands = [] - for operation_name in command_table: - # print(operation_name) - operation = command_table[operation_name] - if isinstance(operation, ServiceOperation): - (flags, args) = argumentsDictionary(operation.arg_table) - description = stripHTML(operation._operation_model.documentation) - - subcommand = { "name": operation_name} - - if description != None and len(description) > 0: - subcommand["description"] = description - - if flags != None and len(flags) > 0: - subcommand["options"] = flags - - if args != None and len(args) > 0: - subcommand["args"] = args - - subcommands.append(subcommand) - - elif isinstance(operation, BasicCommand): - print("Basic Command:", operation_name) - subcommands.append(parseBasicCommand(operation)) - - else: - print("Unknown command!") - - spec = { "name": name, "description": stripHTML(command.service_model.documentation), "subcommands": subcommands} - - # write spec to file - return spec - -def getDescription(name, description): - value = description - if isinstance(value, BasicCommand.FROM_FILE): - if value.filename is not None: - trailing_path = value.filename - else: - trailing_path = os.path.join(name, "_description" + '.rst') - root_module = value.root_module - doc_path = os.path.join( - os.path.abspath(os.path.dirname(root_module.__file__)), - 'examples', trailing_path) - with open(doc_path) as f: - return f.read() - else: - return value - -def parseBasicCommand(command): - print("Parsing BasicCommand: ", command.name) - # print(command.DESCRIPTION) - subcommands = [] - (flags, args) = argumentsDictionary(command.arg_table) - - raw_subcommands = [] - try: - raw_subcommands = command._build_subcommand_table() - except: - print("Error creating subcommand table!", command.name) - for subcommand_name in raw_subcommands: - subcommand = raw_subcommands[subcommand_name] - # invariant: subcommands must conform to BasicCommand - subcommands.append(parseBasicCommand(subcommand)) - - - description = getDescription(command.name, command.DESCRIPTION) - - spec = { "name": command.name } - - if description != None and len(description) > 0: - spec["description"] = description - - if flags != None and len(flags) > 0: - spec["options"] = flags - - if args != None and len(args) > 0: - spec["args"] = args - - if subcommands != None and len(subcommands) > 0: - spec["subcommands"] = subcommands - return spec - - -def saveJsonAsSpec(d, path): - prefix = 'export const completionSpec: Fig.Spec =' if exportTypescript else 'var completionSpec =' - extension = '.ts' if exportTypescript else '.js' - directory = "{}/export/{}/".format(exportDirectory, 'ts' if exportTypescript else 'js') - final = "{} {}".format(prefix, json.dumps(d, indent=4)) - - f = open(directory + path + extension, "w") - f.write(final) - f.close() - -root = { "name": "aws", "subcommands": []} - -def read_commands(command_table, session, **kwargs): - # Confusingly, we need to subcribe to all `building-command-table` events - # in order to get full listed of services (included customized ones like S3) - # But we only want to actually handle "building-command-table.main" - if kwargs["event_name"] != "building-command-table.main": - return - - for command_name in command_table: - command = command_table[command_name] - if isinstance(command, ServiceCommand): - print("ServiceCommand:", command_name) - - spec = generateCompletionSpecSkeleton(command_name, command) - path = "aws/{}".format(spec["name"]) - saveJsonAsSpec(spec, path) - - root["subcommands"].append({ "name": spec["name"], "description": spec["description"], "loadSpec": path}) - - elif isinstance(command, ServiceOperation): - print("ServiceOperation:", command._parent_name, command_name) - elif isinstance(command, BasicCommand): - spec = parseBasicCommand(command) - path = "aws/{}".format(spec["name"]) - # save spec file to disk - saveJsonAsSpec(spec, path) - - root["subcommands"].append({ "name": spec["name"], "description": spec["description"], "loadSpec": path}) - - else: - print(type(command), command_name) - - print("Done!") - saveJsonAsSpec(root, 'aws') - - - - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f059169 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "aws-cli-plugin" +version = "0.1.0" +description = "Add your description here" +authors = [{ name = "Grant Gurvis", email = "grangurv@amazon.com" }] +dependencies = [ + "awscli>=1.32.114", +] +readme = "README.md" +requires-python = ">= 3.8" + +[project.scripts] +aws_cli_plugin = 'aws_cli_plugin:main' + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.rye] +managed = true +dev-dependencies = [] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["src/aws_cli_plugin"] diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 0000000..8703f28 --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,36 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false + +-e file:. +awscli==1.32.114 + # via aws-cli-plugin +botocore==1.34.114 + # via awscli + # via s3transfer +colorama==0.4.6 + # via awscli +docutils==0.16 + # via awscli +jmespath==1.0.1 + # via botocore +pyasn1==0.6.0 + # via rsa +python-dateutil==2.9.0.post0 + # via botocore +pyyaml==6.0.1 + # via awscli +rsa==4.7.2 + # via awscli +s3transfer==0.10.1 + # via awscli +six==1.16.0 + # via python-dateutil +urllib3==2.2.1 + # via botocore diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..8703f28 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,36 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false + +-e file:. +awscli==1.32.114 + # via aws-cli-plugin +botocore==1.34.114 + # via awscli + # via s3transfer +colorama==0.4.6 + # via awscli +docutils==0.16 + # via awscli +jmespath==1.0.1 + # via botocore +pyasn1==0.6.0 + # via rsa +python-dateutil==2.9.0.post0 + # via botocore +pyyaml==6.0.1 + # via awscli +rsa==4.7.2 + # via awscli +s3transfer==0.10.1 + # via awscli +six==1.16.0 + # via python-dateutil +urllib3==2.2.1 + # via botocore diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 index 8c3aae1..20659b4 --- a/setup.sh +++ b/setup.sh @@ -4,3 +4,6 @@ mkdir -p ./export/ts/aws aws configure set plugins.fig fig export PYTHONPATH=$(pwd):$PYTHONPATH + + +aws configure set plugins.aws_cli_plugin aws_cli_plugin diff --git a/src/aws_cli_plugin/__init__.py b/src/aws_cli_plugin/__init__.py new file mode 100644 index 0000000..0b5369d --- /dev/null +++ b/src/aws_cli_plugin/__init__.py @@ -0,0 +1,234 @@ +import json +import re +import os +from os.path import dirname, realpath +from awscli.customizations.commands import BasicCommand +from awscli.clidriver import ServiceCommand, ServiceOperation + +exportDirectory = dirname(dirname(dirname(realpath(__file__)))) +exportTypescript = True + + +def awscli_initialize(cli): + cli.register("building-command-table", read_commands) + + +def print_args(args): + for argName in args: + arg = args[argName] + argJSON = { + "name": arg.cli_name, + "type": arg.cli_type_name, + "nargs": arg.nargs, + "required": arg.required, + "documentation": arg.documentation, + "suggestions": arg.choices, + } + + # print(arg.cli_name, arg.cli_type_name, arg.nargs, arg.required, arg.documentation, arg.choices) + + +def stripHTML(text): + return re.sub("<[^<]+?>", "", text).strip() + + +def argumentsDictionary(args): + flags = [] + positional = [] + for argName in args: + arg = args[argName] + choices = ( + arg.choices + if arg.choices is None or isinstance(arg.choices, list) + else list(arg.choices) + ) + description = stripHTML(arg.documentation) + variadic = arg.nargs is not None and arg.nargs == "+" + # print(arg.cli_name, arg.nargs, arg.required) + # js = { "name": arg.cli_name, "type": arg.cli_type_name, "nargs": arg.nargs, "required": arg.required, "documentation": arg.documentation, "suggestions": arg.choices} + raw = {"name": arg.cli_name} + + if description is not None and len(description) > 0: + raw["description"] = description + + if arg.positional_arg: + if choices is not None and len(choices) > 0: + raw["suggestions"] = choices + # raw["isOptional"] = not arg.required + if variadic: + raw["variadic"] = variadic + + positional.append(raw) + elif arg.cli_type_name == "boolean": + flags.append(raw) + else: + raw["args"] = {"name": arg.cli_type_name} + if choices is not None and len(choices) > 0: + raw["args"]["suggestions"] = choices + # raw["isOptional"] = not arg.required + + if variadic: + raw["args"]["variadic"] = variadic + flags.append(raw) + + return (flags, positional) + + +def generateCompletionSpecSkeleton(name, command): + command_table = command._create_command_table() + subcommands = [] + for operation_name in command_table: + # print(operation_name) + operation = command_table[operation_name] + if isinstance(operation, ServiceOperation): + (flags, args) = argumentsDictionary(operation.arg_table) + description = stripHTML(operation._operation_model.documentation) + + subcommand = {"name": operation_name} + + if description is not None and len(description) > 0: + subcommand["description"] = description + + if flags is not None and len(flags) > 0: + subcommand["options"] = flags + + if args is not None and len(args) > 0: + subcommand["args"] = args + + subcommands.append(subcommand) + + elif isinstance(operation, BasicCommand): + print("Basic Command:", operation_name) + subcommands.append(parseBasicCommand(operation)) + + else: + print("Unknown command!") + + spec = { + "name": name, + "description": stripHTML(command.service_model.documentation), + "subcommands": subcommands, + } + + # write spec to file + return spec + + +def getDescription(name, description): + value = description + if isinstance(value, BasicCommand.FROM_FILE): + if value.filename is not None: + trailing_path = value.filename + else: + trailing_path = os.path.join(name, "_description" + ".rst") + root_module = value.root_module + doc_path = os.path.join( + os.path.abspath(os.path.dirname(root_module.__file__)), + "examples", + trailing_path, + ) + with open(doc_path) as f: + return f.read() + else: + return value + + +def parseBasicCommand(command): + print("Parsing BasicCommand: ", command.name) + # print(command.DESCRIPTION) + subcommands = [] + (flags, args) = argumentsDictionary(command.arg_table) + + raw_subcommands = [] + try: + raw_subcommands = command._build_subcommand_table() + except: # noqa: E722 + print("Error creating subcommand table!", command.name) + for subcommand_name in raw_subcommands: + subcommand = raw_subcommands[subcommand_name] + # invariant: subcommands must conform to BasicCommand + subcommands.append(parseBasicCommand(subcommand)) + + description = getDescription(command.name, command.DESCRIPTION) + + spec = {"name": command.name} + + if description is not None and len(description) > 0: + spec["description"] = description + + if flags is not None and len(flags) > 0: + spec["options"] = flags + + if args is not None and len(args) > 0: + spec["args"] = args + + if subcommands is not None and len(subcommands) > 0: + spec["subcommands"] = subcommands + return spec + + +def saveJsonAsSpec(d, path): + prefix = ( + "export const completionSpec: Fig.Spec =" + if exportTypescript + else "var completionSpec =" + ) + extension = ".ts" if exportTypescript else ".js" + directory = "{}/export/{}/".format( + exportDirectory, "ts" if exportTypescript else "js" + ) + final = "{} {}".format(prefix, json.dumps(d, indent=4)) + + f = open(directory + path + extension, "w") + f.write(final) + f.close() + + +root = {"name": "aws", "subcommands": []} + + +def read_commands(command_table, session, **kwargs): + # Confusingly, we need to subcribe to all `building-command-table` events + # in order to get full listed of services (included customized ones like S3) + # But we only want to actually handle "building-command-table.main" + if kwargs["event_name"] != "building-command-table.main": + return + + for command_name in command_table: + command = command_table[command_name] + if isinstance(command, ServiceCommand): + print("ServiceCommand:", command_name) + + spec = generateCompletionSpecSkeleton(command_name, command) + path = "aws/{}".format(spec["name"]) + saveJsonAsSpec(spec, path) + + root["subcommands"].append( + { + "name": spec["name"], + "description": spec["description"], + "loadSpec": path, + } + ) + + elif isinstance(command, ServiceOperation): + print("ServiceOperation:", command._parent_name, command_name) + elif isinstance(command, BasicCommand): + spec = parseBasicCommand(command) + path = "aws/{}".format(spec["name"]) + # save spec file to disk + saveJsonAsSpec(spec, path) + + root["subcommands"].append( + { + "name": spec["name"], + "description": spec["description"], + "loadSpec": path, + } + ) + + else: + print(type(command), command_name) + + print("Done!") + saveJsonAsSpec(root, "aws")