diff --git a/.config/dictionary.txt b/.config/dictionary.txt index 5ec2e26fc..b6fd2c8de 100644 --- a/.config/dictionary.txt +++ b/.config/dictionary.txt @@ -58,6 +58,7 @@ bitness bthornto cacheable cfgs +charliermarsh checode chromedriver chronographer diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f915f3a0..d45dedd2e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ exclude: > minimum_pre_commit_version: 2.9.0 # types_or repos: - repo: https://github.com/streetsidesoftware/cspell-cli - rev: v8.14.0 + rev: v8.15.2 hooks: - id: cspell # name: Spell check with cspell @@ -41,6 +41,20 @@ repos: - --color=always - -e - SC1091 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.0 + hooks: + - id: ruff + args: + - --fix + - --exit-non-zero-on-fix + types_or: [python, pyi] + - id: ruff-format # must be after ruff + types_or: [python, pyi] + - repo: https://github.com/psf/black # must be after ruff + rev: 24.10.0 + hooks: + - id: black - repo: local hooks: - id: depcheck diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c5f242281..11746b415 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "DavidAnson.vscode-markdownlint", + "charliermarsh.ruff", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "hbenl.vscode-mocha-test-adapter", diff --git a/examples/dynamic_inventory.py b/examples/dynamic_inventory.py index ed581c072..fa7abaead 100755 --- a/examples/dynamic_inventory.py +++ b/examples/dynamic_inventory.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -''' +""" Example custom dynamic inventory script for Ansible, in Python. -''' +""" import argparse from time import sleep @@ -12,6 +12,7 @@ except ImportError: import simplejson as json + class ExampleInventory(object): def __init__(self): @@ -29,40 +30,37 @@ def __init__(self): else: self.inventory = self.empty_inventory() - print (json.dumps(self.inventory)); + print(json.dumps(self.inventory)) # Example inventory for testing. def example_inventory(self): sleep(1) return { - 'python_hosts': { - 'hosts': ['10.220.21.24', '10.220.21.27'], - 'vars': { - 'ansible_ssh_user': 'projectuser', - } + "python_hosts": { + "hosts": ["10.220.21.24", "10.220.21.27"], + "vars": { + "ansible_ssh_user": "projectuser", + }, }, - '_meta': { - 'hostvars': { - '10.220.21.24': { - 'host_specific_var': 'testhost' - }, - '10.220.21.27': { - 'host_specific_var': 'towerhost' - } + "_meta": { + "hostvars": { + "10.220.21.24": {"host_specific_var": "testhost"}, + "10.220.21.27": {"host_specific_var": "towerhost"}, } - } + }, } # Empty inventory for testing. def empty_inventory(self): - return {'_meta': {'hostvars': {}}} + return {"_meta": {"hostvars": {}}} # Read the command line args passed to the script. def read_cli_args(self): parser = argparse.ArgumentParser() - parser.add_argument('--list', action = 'store_true') - parser.add_argument('--host', action = 'store') + parser.add_argument("--list", action="store_true") + parser.add_argument("--host", action="store") self.args = parser.parse_args() + # Get the inventory. ExampleInventory() diff --git a/packages/ansible-language-server/test/fixtures/completion/dynamic_inventory.py b/packages/ansible-language-server/test/fixtures/completion/dynamic_inventory.py index aef574044..6d1844362 100755 --- a/packages/ansible-language-server/test/fixtures/completion/dynamic_inventory.py +++ b/packages/ansible-language-server/test/fixtures/completion/dynamic_inventory.py @@ -1,19 +1,18 @@ #!/usr/bin/env python3 -''' +""" Example custom dynamic inventory script for Ansible, in Python. -''' +""" import argparse -from time import sleep try: import json except ImportError: import simplejson as json -class ExampleInventory(object): +class ExampleInventory(object): def __init__(self): self.inventory = {} self.read_cli_args() @@ -29,41 +28,38 @@ def __init__(self): else: self.inventory = self.empty_inventory() - print(json.dumps(self.inventory)); + print(json.dumps(self.inventory)) # Example inventory for testing. def example_inventory(self): # sleep is added to create an impression of a complex dynamic inventory file that takes time to fetch hosts # sleep(1) return { - 'python_hosts': { - 'hosts': ['10.220.21.24', '10.220.21.27'], - 'vars': { - 'ansible_ssh_user': 'projectuser', - } + "python_hosts": { + "hosts": ["10.220.21.24", "10.220.21.27"], + "vars": { + "ansible_ssh_user": "projectuser", + }, }, - '_meta': { - 'hostvars': { - '10.220.21.24': { - 'host_specific_var': 'testhost' - }, - '10.220.21.27': { - 'host_specific_var': 'towerhost' - } + "_meta": { + "hostvars": { + "10.220.21.24": {"host_specific_var": "testhost"}, + "10.220.21.27": {"host_specific_var": "towerhost"}, } - } + }, } # Empty inventory for testing. def empty_inventory(self): - return {'_meta': {'hostvars': {}}} + return {"_meta": {"hostvars": {}}} # Read the command line args passed to the script. def read_cli_args(self): parser = argparse.ArgumentParser() - parser.add_argument('--list', action = 'store_true') - parser.add_argument('--host', action = 'store') + parser.add_argument("--list", action="store_true") + parser.add_argument("--host", action="store") self.args = parser.parse_args() + # Get the inventory. ExampleInventory() diff --git a/packages/ansible-language-server/tools/version_sync.py b/packages/ansible-language-server/tools/version_sync.py index 0f3af1425..f94a8f007 100644 --- a/packages/ansible-language-server/tools/version_sync.py +++ b/packages/ansible-language-server/tools/version_sync.py @@ -11,9 +11,7 @@ def sync_als_version_in_vscode_ansible_devel(): with open("../vscode-ansible/package.json") as fp: package_json_vscode_ansible = json.load(fp) - package_json_vscode_ansible["dependencies"][ - "@ansible/ansible-language-server" - ] = version_als + package_json_vscode_ansible["dependencies"]["@ansible/ansible-language-server"] = version_als with open("../vscode-ansible/package.json", "w") as fp: json.dump(package_json_vscode_ansible, fp, indent=4) diff --git a/pyproject.toml b/pyproject.toml index bb9e36d82..03083e53d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ +[tool.black] +enable-unstable-feature = ["string_processing"] +line-length = 100 +preview = true + [tool.codespell] builtin = "clear,rare,code" check-filenames = true @@ -24,3 +29,9 @@ norecursedirs = [ "out", "node_modules", ] + +[tool.ruff] +exclude = ["examples"] +fix = true +line-length = 100 +target-version = "py310" diff --git a/syntaxes/plist2xml.py b/syntaxes/plist2xml.py index 1efb7f635..3ef2396f4 100644 --- a/syntaxes/plist2xml.py +++ b/syntaxes/plist2xml.py @@ -6,21 +6,26 @@ @click.command() -@click.argument('source', required=True) -@click.argument('dest', required=True) +@click.argument("source", required=True) +@click.argument("dest", required=True) def main(source, dest): ast = parse(source) - plist: ET.Element = ET.Element('plist', version="1.0") + plist: ET.Element = ET.Element("plist", version="1.0") generate_xml(plist, ast) - ET.indent(plist, ' ') + ET.indent(plist, " ") xml = ET.tostring( - plist, encoding='UTF-8', xml_declaration=True, - doctype='' + plist, + encoding="UTF-8", + xml_declaration=True, + doctype=( + '' + ), ) - with open(dest, 'wb') as f: + with open(dest, "wb") as f: f.write(xml) @@ -29,60 +34,59 @@ def parse(plistPath): # https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html#//apple_ref/doc/uid/20001012-BBCBDBJE LPAR, RPAR, LBRACE, RBRACE, EQUALS, SEMI, COL = map(pp.Suppress, "(){}=;,") - array = pp.Forward().setName('array') - dictionary = pp.Forward().setName('dict') - string = (pp.QuotedString('"', escChar="\\", multiline=True, convertWhitespaceEscapes=True) | - pp.QuotedString("'", escQuote="''", multiline=True, convertWhitespaceEscapes=False) | - pp.Word(pp.alphanums+'_-'))('string').setName('simple string or quoted string') + array = pp.Forward().setName("array") + dictionary = pp.Forward().setName("dict") + string = ( + pp.QuotedString('"', escChar="\\", multiline=True, convertWhitespaceEscapes=True) + | pp.QuotedString("'", escQuote="''", multiline=True, convertWhitespaceEscapes=False) + | pp.Word(pp.alphanums + "_-") + )("string").setName("simple string or quoted string") # order here is very important when using '-' - element = pp.Group(string | array | dictionary)('value') + element = pp.Group(string | array | dictionary)("value") # that '+' is required for matching the optional colon array_elements = element - pp.ZeroOrMore(COL + element) - pp.Optional(COL) - array << pp.Group(LPAR - pp.Optional(array_elements) - - RPAR)('array') + array << pp.Group(LPAR - pp.Optional(array_elements) - RPAR)("array") - dict_item = pp.Group(string('key') - EQUALS - element - SEMI) - dictionary << pp.Group(LBRACE - - pp.ZeroOrMore(dict_item) - - RBRACE)('dict') + dict_item = pp.Group(string("key") - EQUALS - element - SEMI) + dictionary << pp.Group(LBRACE - pp.ZeroOrMore(dict_item) - RBRACE)("dict") res = dictionary.parseFile(plistPath, parseAll=True) return res def generate_xml(parent: ET.Element, data: pp.ParseResults, level=0, indent=4): - if 'dict' in data: - dictionary = data['dict'] - parent = ET.SubElement(parent, 'dict') + if "dict" in data: + dictionary = data["dict"] + parent = ET.SubElement(parent, "dict") for dict_item in dictionary: - key = dict_item['key'] - x_key = ET.SubElement(parent, 'key') + key = dict_item["key"] + x_key = ET.SubElement(parent, "key") x_key.text = key - value = dict_item['value'] - generate_xml(parent, value, level+1, indent) + value = dict_item["value"] + generate_xml(parent, value, level + 1, indent) pass - elif 'array' in data: - array = data['array'] - parent = ET.SubElement(parent, 'array') + elif "array" in data: + array = data["array"] + parent = ET.SubElement(parent, "array") for array_item in array: - generate_xml(parent, array_item, level+1, indent) - elif 'string' in data: - string: str = data['string'] - lines = string.split('\n') + generate_xml(parent, array_item, level + 1, indent) + elif "string" in data: + string: str = data["string"] + lines = string.split("\n") reindented_lines = [lines[0]] for line in lines[1:]: - m: Match = re.match(f'^[ ]{{{(level-1)*indent}}}', line) + m: Match = re.match(f"^[ ]{{{(level-1)*indent}}}", line) if m: # remove plist indent - line = line[len(m.group(0)):] + line = line[len(m.group(0)) :] # insert XML indent - line = ' '*((level)*indent) + line + line = " " * ((level) * indent) + line reindented_lines.append(line) - x_string = ET.SubElement(parent, 'string') - x_string.text = '\n'.join(reindented_lines) + x_string = ET.SubElement(parent, "string") + x_string.text = "\n".join(reindented_lines) if __name__ == "__main__": diff --git a/syntaxes/xml2plist.py b/syntaxes/xml2plist.py index a9020158a..4f7f24b63 100644 --- a/syntaxes/xml2plist.py +++ b/syntaxes/xml2plist.py @@ -5,17 +5,17 @@ @click.command() -@click.argument('source', required=True) -@click.argument('dest', required=True) +@click.argument("source", required=True) +@click.argument("dest", required=True) def main(source, dest): root = ET.parse(source).getroot() - with open(dest, 'w') as f: + with open(dest, "w") as f: f.write(convert_to_plist(root)) def to_safe_string(text: str): - if re.match(r'^[\w-]+$', text): + if re.match(r"^[\w-]+$", text): return text else: text = text.replace("'", "''") # escape single-quotes @@ -24,63 +24,70 @@ def to_safe_string(text: str): def format(tag, value: Union[list, str], level, indent, context): xml_indent = 4 - indentation = ' '*indent*level - if context == 'mapping': - start_indent = '' + indentation = " " * indent * level + if context == "mapping": + start_indent = "" else: start_indent = indentation if type(value) is list: - if tag == 'dict': - start = '{' - end = '}' - joined_values = '\n'.join(value) - elif tag == 'array': - start = '(' - end = ')' - joined_values = ',\n'.join(value) + if tag == "dict": + start = "{" + end = "}" + joined_values = "\n".join(value) + elif tag == "array": + start = "(" + end = ")" + joined_values = ",\n".join(value) return f"{start_indent}{start}\n{joined_values}\n{indentation}{end}" else: reindented_lines = [] - lines = value.split('\n') + lines = value.split("\n") for line in lines: - m: Match = re.match(r'^[\t ]*', line) + m: Match = re.match(r"^[\t ]*", line) # Normalize tabs to spaces - text_indentation = m.group(0).replace('\t', ' '*xml_indent) + text_indentation = m.group(0).replace("\t", " " * xml_indent) # reindent in case indent does not match xml_indent - text_indentation = ' ' * \ - int(len(text_indentation)/xml_indent * - indent-indent) # working one level lower (plist tag is removed) + text_indentation = " " * int( + len(text_indentation) / xml_indent * indent - indent + ) # working one level lower (plist tag is removed) reindented_lines.append(text_indentation + line.lstrip()) - value = '\n'.join(reindented_lines) + value = "\n".join(reindented_lines) return f"{start_indent}{value}" -def convert_to_plist(element: ET.Element, level=0, indent=4, context: str = ''): - if element.tag == 'dict': +def convert_to_plist(element: ET.Element, level=0, indent=4, context: str = ""): + if element.tag == "dict": # with new level, since indent is appended already here - inner_indentation = ' '*indent*(level+1) + inner_indentation = " " * indent * (level + 1) dict_iter = iter(element) dict_items = [] while True: try: key_element = next(dict_iter) - assert key_element.tag == 'key', f"Got {key_element.tag}({key_element.text}) instead of key" + assert ( + key_element.tag == "key" + ), f"Got {key_element.tag}({key_element.text}) instead of key" value_element = next(dict_iter, None) - assert value_element is not None, f"Got {key_element.tag}({key_element.text}) without value" - item_str = f"{inner_indentation}{to_safe_string(key_element.text)} = {convert_to_plist(value_element, level+1, indent, 'mapping')};" + assert ( + value_element is not None + ), f"Got {key_element.tag}({key_element.text}) without value" + item_str = ( + f"{inner_indentation}{to_safe_string(key_element.text)} =" + f" {convert_to_plist(value_element, level+1, indent, 'mapping')};" + ) dict_items.append(item_str) except StopIteration: break return format(element.tag, dict_items, level, indent, context) - elif element.tag == 'array': + elif element.tag == "array": array_items = [] for item_element in element: - array_items.append(convert_to_plist(item_element, level+1, indent)) + array_items.append(convert_to_plist(item_element, level + 1, indent)) return format(element.tag, array_items, level, indent, context) - elif element.tag == 'string': + elif element.tag == "string": return format(element.tag, to_safe_string(element.text), level, indent, context) - elif element.tag == 'plist': + elif element.tag == "plist": return convert_to_plist(element[0], level, indent) else: raise Exception(f"Unrecognized tag: {element.tag}") diff --git a/test/testFixtures/common/collections/ansible_collections/testorg/collection_1/plugins/lookup/lookup_1.py b/test/testFixtures/common/collections/ansible_collections/testorg/collection_1/plugins/lookup/lookup_1.py index 1e8d021d9..a488076f3 100644 --- a/test/testFixtures/common/collections/ansible_collections/testorg/collection_1/plugins/lookup/lookup_1.py +++ b/test/testFixtures/common/collections/ansible_collections/testorg/collection_1/plugins/lookup/lookup_1.py @@ -1,6 +1,7 @@ """ The dummy lookup plugin """ + from __future__ import absolute_import, division, print_function __metaclass__ = type diff --git a/tools/helper b/tools/helper index 1022ad924..880b28ff3 100755 --- a/tools/helper +++ b/tools/helper @@ -1,12 +1,14 @@ #!/usr/bin/env python3 """Utility that helps with extension building and testing.""" + import argparse import glob import subprocess import logging import sys + def run(cmd: str): """Helper to easy calling subprocess.""" return subprocess.run(cmd, shell=True, check=True) @@ -14,18 +16,30 @@ def run(cmd: str): def cli(): """Main.""" - parser = argparse.ArgumentParser( - prog='helper', - description='Build process helper') - parser.add_argument('--version', action='store_true', help="Retrieve marketplace compatible version number to be used during build.") - parser.add_argument('--publish', action='store_true', help="Publish already built vsix file to marketplaces.") - parser.add_argument('--package', action='store_true', help="Package the extension.") + parser = argparse.ArgumentParser(prog="helper", description="Build process helper") + parser.add_argument( + "--version", + action="store_true", + help="Retrieve marketplace compatible version number to be used during build.", + ) + parser.add_argument( + "--publish", + action="store_true", + help="Publish already built vsix file to marketplaces.", + ) + parser.add_argument("--package", action="store_true", help="Package the extension.") opt, _ = parser.parse_known_args() - logging.basicConfig(level=logging.INFO, format='%(message)s') + logging.basicConfig(level=logging.INFO, format="%(message)s") pre_release = False - result = subprocess.run('git describe --dirty --tags --long --match "v*.*"', shell=True, capture_output=True, check=True, text=True) + result = subprocess.run( + 'git describe --dirty --tags --long --match "v*.*"', + shell=True, + capture_output=True, + check=True, + text=True, + ) git_tag = result.stdout.rstrip() logging.debug('git describe (with --match "v*.*") returned: %s', git_tag) tag, commits_since, suffix = git_tag.split("-", 2) @@ -34,7 +48,13 @@ def cli(): if "-dirty" in suffix or commits_since != "0": pre_release = True # If pre_release = True, we need to calculate the time difference from the first stable release of the month with a "*.0" tag - result = subprocess.run('git describe --dirty --tags --long --match "v*.*.0"', shell=True, capture_output=True, check=True, text=True) + result = subprocess.run( + 'git describe --dirty --tags --long --match "v*.*.0"', + shell=True, + capture_output=True, + check=True, + text=True, + ) git_tag = result.stdout.rstrip() logging.debug('git describe (with --match "v*.*.0") returned: %s', git_tag) tag, commits_since, suffix = git_tag.split("-", 2) @@ -44,7 +64,10 @@ def cli(): if len(version_info) == 2: version_info.append(0) if len(version_info) != 3: - msg = f"Unsupported version tag ({version}) found, we only support MINOR.MAJOR.PATCH pattern." + msg = ( + f"Unsupported version tag ({version}) found, we only support MINOR.MAJOR.PATCH" + " pattern." + ) logging.error(msg) sys.exit(2) if version_info[1] % 2 == 0: @@ -56,34 +79,67 @@ def cli(): else: version_info[1] += 1 else: - msg = f"Last git tag ({tag}) had a MINOR version number ({version_info[1]}) that was odd. Odd numbers are reserved for pre-release versions. Remove the tag and try again." + msg = ( + f"Last git tag ({tag}) had a MINOR version number ({version_info[1]}) that was odd." + " Odd numbers are reserved for pre-release versions. Remove the tag and try again." + ) logging.error(msg) sys.exit(2) # determine the PATCH value, which is the time passed between last tag and last commit - last_tag_timestamp = int(subprocess.run(f"git -P log -1 --format=%ct {tag}", shell=True, capture_output=True, check=True, text=True).stdout.rstrip()) - last_commit_timestamp = int(subprocess.run("git -P show --no-patch --format=%ct HEAD", shell=True, capture_output=True, check=True, text=True).stdout.rstrip()) + last_tag_timestamp = int( + subprocess.run( + f"git -P log -1 --format=%ct {tag}", + shell=True, + capture_output=True, + check=True, + text=True, + ).stdout.rstrip() + ) + last_commit_timestamp = int( + subprocess.run( + "git -P show --no-patch --format=%ct HEAD", + shell=True, + capture_output=True, + check=True, + text=True, + ).stdout.rstrip() + ) version_info[2] = last_commit_timestamp - last_tag_timestamp version = ".".join([str(x) for x in version_info]) - logging.info("Determined version=%s and pre_release=%s base on git describe result: %s", version, pre_release, git_tag) + logging.info( + "Determined version=%s and pre_release=%s base on git describe result: %s", + version, + pre_release, + git_tag, + ) if opt.version: print(version) sys.exit(0) - pre_release_arg="--pre-release" if pre_release else "" + pre_release_arg = "--pre-release" if pre_release else "" if opt.publish: - vsix_files = glob.glob('*.vsix') + vsix_files = glob.glob("*.vsix") if len(vsix_files) != 1: - msg = f"Publish command requires presence of exactly one '.vsix' on disk, found: {vsix_files}" + msg = ( + "Publish command requires presence of exactly one '.vsix' on disk, found:" + f" {vsix_files}" + ) logging.error(msg) sys.exit(2) - run(f"yarn run vsce publish {pre_release_arg} --skip-duplicate --packagePath {vsix_files[0]} --readme-path docs/README.md") + run( + f"yarn run vsce publish {pre_release_arg} --skip-duplicate --packagePath" + f" {vsix_files[0]} --readme-path docs/README.md" + ) run(f"yarn run ovsx publish {pre_release_arg} --skip-duplicate {vsix_files[0]}") sys.exit() if opt.package: run("rm -f ./*.vsix") run("yarn run webpack") # --no-dependencies and --no-yarn needed due to https://github.com/microsoft/vscode-vsce/issues/439 - run(f"yarn run vsce package --no-dependencies --no-git-tag-version --no-update-package-json --readme-path docs/README.md {pre_release_arg} {version}") + run( + "yarn run vsce package --no-dependencies --no-git-tag-version --no-update-package-json" + f" --readme-path docs/README.md {pre_release_arg} {version}" + ) # Using zipinfo instead of `npx vsce ls` due to https://github.com/microsoft/vscode-vsce/issues/517 run("zipinfo -1 ./*.vsix > out/log/package.log") run("tools/dirty.sh") diff --git a/tools/precheck.py b/tools/precheck.py index d15de85aa..615c9d16b 100644 --- a/tools/precheck.py +++ b/tools/precheck.py @@ -1,8 +1,10 @@ """Check that the python3 executable points to Python 3.9 or newer.""" + import sys if sys.version_info < (3, 9): print( "FATAL: python3 executable must point to Python 3.9 or newer for tests to work", - file=sys.stderr) + file=sys.stderr, + ) sys.exit(99)