From f99ad8899877358828f84e060ce89479169e3502 Mon Sep 17 00:00:00 2001 From: Stanislas Daniel Claude Dolcini Date: Wed, 11 Sep 2024 15:26:41 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20most=20ruff=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruff.toml | 2 + scripts/__init__.py | 21 +++--- scripts/copy_files.py | 53 ++++++++------- scripts/entvalidate.py | 52 ++++++++++----- scripts/get_version.py | 19 +++--- scripts/modio.py | 118 +++++++++++++++++++--------------- scripts/scriptlib/__init__.py | 111 ++++++++++++++++---------------- 7 files changed, 208 insertions(+), 168 deletions(-) diff --git a/ruff.toml b/ruff.toml index 9e52f3b..2ea7962 100644 --- a/ruff.toml +++ b/ruff.toml @@ -7,6 +7,7 @@ line-ending = "lf" select = ["ALL"] ignore = [ "ANN", + "BLE001", "C90", "COM812", "D10", @@ -40,6 +41,7 @@ ignore = [ "TRY002", "TRY003", "TRY004", + "TRY301", "UP038", "W505" ] diff --git a/scripts/__init__.py b/scripts/__init__.py index e25c723..e74d855 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -1,16 +1,17 @@ import json from pathlib import Path + MOD_PATH = Path("community-mod") + def check_cwd_is_correct(): - cwd = Path().resolve() - try: - if not Path(".git").exists(): - raise Exception("No .git in current folder.") - with open(MOD_PATH / "mod.json", "r") as f: - mod = json.load(f) - if mod['name'] != "community-mod": - raise Exception("mod.json has incorrect name") - except: - raise Exception("scripts.py must be called from the community mod repository") + try: + if not Path(".git").exists(): + raise Exception("No .git in current folder.") + with open(MOD_PATH / "mod.json") as f: + mod = json.load(f) + if mod["name"] != "community-mod": + raise Exception("mod.json has incorrect name") + except Exception as e: + raise Exception("scripts.py must be called from the community mod repository") from e diff --git a/scripts/copy_files.py b/scripts/copy_files.py index 045fedf..225d047 100644 --- a/scripts/copy_files.py +++ b/scripts/copy_files.py @@ -1,43 +1,42 @@ import argparse -import shutil import json +import shutil from pathlib import Path + from . import MOD_PATH, check_cwd_is_correct + PUBLIC_PATH = Path("binaries/data/mods/public/") -DEFAULT_COPY = [ - "simulation/data", - "simulation/templates" -] +DEFAULT_COPY = ["simulation/data", "simulation/templates"] + def validate_path(path: str): - mod_path = Path(path) / PUBLIC_PATH - try: - with open(mod_path / "mod.json", "r", encoding="utf-8") as f: - mod = json.load(f) - if mod['name'] != "0ad": - raise Exception("mod.json has incorrect name") - except: - raise Exception(f"path '{path}' does not point to a 0 A.D. SVN folder.") - return mod_path + mod_path = Path(path) / PUBLIC_PATH + try: + with open(mod_path / "mod.json", encoding="utf-8") as f: + mod = json.load(f) + if mod["name"] != "0ad": + raise Exception("mod.json has incorrect name") + except Exception as e: + raise Exception(f"path '{path}' does not point to a 0 A.D. SVN folder.") from e + return mod_path -def copy_0ad_files(path_0ad: Path, to_copy = DEFAULT_COPY): - for path in to_copy: - shutil.copytree(path_0ad / path, MOD_PATH / path, dirs_exist_ok=False) +def copy_0ad_files(path_0ad: Path, to_copy=DEFAULT_COPY): + for path in to_copy: + shutil.copytree(path_0ad / path, MOD_PATH / path, dirs_exist_ok=False) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Copy files from 0 A.D. to the community mod.') - parser.add_argument('-0ad', help='Path to the 0 A.D. folder') - parser.add_argument('-p','--path', nargs='*', help='Optionally, a list of paths to copy.') +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Copy files from 0 A.D. to the community mod.") + parser.add_argument("-0ad", help="Path to the 0 A.D. folder") + parser.add_argument("-p", "--path", nargs="*", help="Optionally, a list of paths to copy.") - args = parser.parse_args() + args = parser.parse_args() - check_cwd_is_correct() - path = validate_path(getattr(args, '0ad')) - copy_0ad_files(path, args.folder or DEFAULT_COPY) + check_cwd_is_correct() + path = validate_path(getattr(args, "0ad")) + copy_0ad_files(path, args.folder or DEFAULT_COPY) else: - raise Exception("Must be called directly") - + raise Exception("Must be called directly") diff --git a/scripts/entvalidate.py b/scripts/entvalidate.py index b28b777..7dac18f 100755 --- a/scripts/entvalidate.py +++ b/scripts/entvalidate.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import argparse -from os import chdir -from pathlib import Path -from subprocess import run, CalledProcessError -from sys import exit -from xml.etree import ElementTree -from .scriptlib import SimulTemplateEntity, find_files import logging +from pathlib import Path +from subprocess import CalledProcessError, run +from sys import exit +from xml.etree import ElementTree as ET + +from .scriptlib import SimulTemplateEntity, find_files + logger = logging.getLogger(__name__) @@ -15,37 +16,54 @@ relaxng_schema = root / "scripts" / "entity.rng" vfs_root = root mod = "community-mod" + def main(): if not relaxng_schema.exists(): print(f"""Relax NG schema non existant. Please create the file {relaxng_schema.relative_to(root)} You can do that by running 'pyrogenesis -dumpSchema' in the 'system' directory""") exit(1) - if run(['xmllint', '--version'], capture_output=True).returncode != 0: + if run(["xmllint", "--version"], capture_output=True, check=False).returncode != 0: print("xmllint not found in your PATH, please install it (usually in libxml2 package)") exit(2) - parser = argparse.ArgumentParser(description='Validate templates') - parser.add_argument('-p','--path', nargs='*', help='Optionally, a list of templates to validate.') + parser = argparse.ArgumentParser(description="Validate templates") + parser.add_argument( + "-p", "--path", nargs="*", help="Optionally, a list of templates to validate." + ) args = parser.parse_args() - simul_templates_path = Path('simulation/templates') + simul_templates_path = Path("simulation/templates") simul_template_entity = SimulTemplateEntity(vfs_root, logger) count = 0 failed = 0 - templates = sorted([(Path(p), None) for p in args.path]) if args.path else sorted(find_files(vfs_root, [mod], 'simulation/templates', 'xml')) + + templates = [] + if args.path is not None: + templates = sorted([(Path(p), None) for p in args.path]) + else: + templates = sorted(find_files(vfs_root, [mod], "simulation/templates", "xml")) + for fp, _ in templates: - if fp.stem.startswith('template_'): + if fp.stem.startswith("template_"): continue path = fp.as_posix() - if path.startswith('simulation/templates/mixins/') or path.startswith("simulation/templates/special/"): + if path.startswith(("simulation/templates/mixins/", "simulation/templates/special/")): continue print(f"# {fp}...") count += 1 - entity = simul_template_entity.load_inherited(simul_templates_path, str(fp.relative_to(simul_templates_path)), [mod]) - xmlcontent = ElementTree.tostring(entity, encoding='unicode') + entity = simul_template_entity.load_inherited( + simul_templates_path, str(fp.relative_to(simul_templates_path)), [mod] + ) + xmlcontent = ET.tostring(entity, encoding="unicode") try: - run(['xmllint', '--relaxng', str(relaxng_schema.resolve()), '-'], input=xmlcontent, capture_output=True, text=True, check=True) + run( + ["xmllint", "--relaxng", str(relaxng_schema.resolve()), "-"], + input=xmlcontent, + capture_output=True, + text=True, + check=True, + ) except CalledProcessError as e: failed += 1 print(e.stderr) @@ -53,5 +71,5 @@ You can do that by running 'pyrogenesis -dumpSchema' in the 'system' directory"" print(f"\nTotal: {count}; failed: {failed}") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/scripts/get_version.py b/scripts/get_version.py index d6fe4b5..7f43b60 100644 --- a/scripts/get_version.py +++ b/scripts/get_version.py @@ -1,13 +1,16 @@ import json + from . import MOD_PATH, check_cwd_is_correct -def get_version(): - with open(MOD_PATH / "mod.json", "r") as f: - mod = json.load(f) - print(mod['version']) -if __name__ == '__main__': - check_cwd_is_correct() - get_version() +def get_version(): + with open(MOD_PATH / "mod.json") as f: + mod = json.load(f) + print(mod["version"]) + + +if __name__ == "__main__": + check_cwd_is_correct() + get_version() else: - raise Exception("Must be called directly") + raise Exception("Must be called directly") diff --git a/scripts/modio.py b/scripts/modio.py index 73eebd1..a83ccb3 100644 --- a/scripts/modio.py +++ b/scripts/modio.py @@ -1,36 +1,42 @@ import json import os -import requests -from . import MOD_PATH import subprocess +import requests + +from . import MOD_PATH + + def run_git_command(command): - """ Run a git command and return its output. """ - result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + """Run a git command and return its output.""" + result = subprocess.run(command, capture_output=True, text=True, check=False) if result.returncode != 0: raise Exception(f"Git command failed: {result.stderr}") return result.stdout.strip() + def get_current_commit(): - """ Get the current commit hash. """ - return run_git_command(['git', 'rev-parse', 'HEAD']) + """Get the current commit hash.""" + return run_git_command(["git", "rev-parse", "HEAD"]) + def get_commit_for_tag(tag): - """ Get the commit hash for a tag, or return None if the tag doesn't exist. """ + """Get the commit hash for a tag, or return None if the tag doesn't exist.""" try: - commit_hash = run_git_command(['git', 'rev-list', '-n', '1', tag]) - return commit_hash - except Exception: + return run_git_command(["git", "rev-list", "-n", "1", tag]) + except Exception as e: + print(f"Failed to get commit for tag {tag}: {e}") return None + def get_previous_tag_in_series(tag): - """ Get the previous tag in the same minor version series (e.g., 0.26.X-1). """ + """Get the previous tag in the same minor version series (e.g., 0.26.X-1).""" try: - version_array = tag.split('.') + version_array = tag.split(".") # Extract the major.minor part of the tag (e.g., '0.26' from '0.26.11') - major_minor_version = '.'.join(version_array[:2]) + major_minor_version = ".".join(version_array[:2]) # List all tags, reverse sorted by version number - tags = run_git_command(['git', 'tag', '--sort=-v:refname']).splitlines() + tags = run_git_command(["git", "tag", "--sort=-v:refname"]).splitlines() # Filter tags that match the major.minor version filtered_tags = [t for t in tags if t.startswith(major_minor_version)] if tag in filtered_tags: @@ -39,28 +45,30 @@ def get_previous_tag_in_series(tag): return filtered_tags[current_index + 1] version_array[2] = str(int(version_array[2]) - 1) - manual_tag = '.'.join(version_array) - if manual_tag in filtered_tags: - current_index = filtered_tags.index(manual_tag) - return filtered_tags[current_index] - - return None + manual_tag = ".".join(version_array) + if manual_tag not in filtered_tags: + return None + + current_index = filtered_tags.index(manual_tag) + return filtered_tags[current_index] except Exception as e: - raise Exception(f"Failed to get previous tag in series: {e}") + raise Exception(f"Failed to get previous tag in series: {e}") from e + def get_changelog(current_commit, previous_tag): - """ Get the changelog between the current commit and the previous tag. """ + """Get the changelog between the current commit and the previous tag.""" try: if previous_tag is not None: - changelog = run_git_command(['git', 'log', f'{previous_tag}..{current_commit}', '--oneline']) - else: - # No previous tag, so get the log from the start of the repository - changelog = run_git_command(['git', 'log', current_commit, '--oneline']) - return changelog + return run_git_command( + ["git", "log", f"{previous_tag}..{current_commit}", "--oneline"] + ) + # No previous tag, so get the log from the start of the repository + return run_git_command(["git", "log", current_commit, "--oneline"]) except Exception as e: - raise Exception(f"Failed to generate changelog: {e}") + raise Exception(f"Failed to generate changelog: {e}") from e -api_key = os.getenv('MODIO_API_KEY') + +api_key = os.getenv("MODIO_API_KEY") # This must be generated manually from the website's interface. oauth2_token = os.getenv("MODIO_OAUTH2_TOKEN") @@ -72,9 +80,11 @@ mod_name = os.getenv("MOD_NAME") community_mod_id = 2144305 zeroad_id = 5 -mod_json = json.load(open(MOD_PATH / 'mod.json', 'r')) +mod_json = None +with open(MOD_PATH / "mod.json") as f: + mod_json = json.load(f) -tag = mod_json['version'] +tag = mod_json["version"] commit = get_commit_for_tag(tag) if not commit: print(f"Tag {tag} does not exist. Using current commit as fallback.") @@ -83,31 +93,35 @@ if not commit: previous_tag = get_previous_tag_in_series(tag) changelog = get_changelog(commit, previous_tag) -print (changelog) +print(changelog) -headers = { - 'Authorization': f'Bearer {oauth2_token}', - 'Accept': 'application/json' -} +headers = {"Authorization": f"Bearer {oauth2_token}", "Accept": "application/json"} -r = requests.get(f'https://api.mod.io/v1/me/mods', params={ - 'api_key': api_key -}, headers = headers) +r = requests.get( + "https://api.mod.io/v1/me/mods", timeout=5, params={"api_key": api_key}, headers=headers +) print(r.json()) -files = {'filedata': open(mod_file_path, 'rb')} +with open(mod_file_path, "rb") as f: + files = {"filedata": f} + signature = "" + with open(f"{mod_name}-{mod_version}.zip.minisign", encoding="utf-8") as f: + signature = f.read() -signature = open(f"{mod_name}-{mod_version}.zip.minisign", 'r', encoding="utf-8").read() + rq = requests.post( + f"https://api.mod.io/v1/games/{zeroad_id}/mods/{community_mod_id}/files", + files=files, + headers=headers, + timeout=500, + data={ + "version": mod_version, + "active": True, + "changelog": changelog, + "metadata_blob": json.dumps( + {"dependencies": mod_json["dependencies"], "minisigs": [signature]} + ), + }, + ) -rq = requests.post(f'https://api.mod.io/v1/games/{zeroad_id}/mods/{community_mod_id}/files', files=files, headers=headers, data={ - 'version': mod_version, - 'active': True, - 'changelog': changelog, - 'metadata_blob' : json.dumps({ - 'dependencies': mod_json['dependencies'], - 'minisigs': [signature] - }) -}) - -print(rq.json()) \ No newline at end of file + print(rq.json()) diff --git a/scripts/scriptlib/__init__.py b/scripts/scriptlib/__init__.py index eef5195..fbd07dd 100644 --- a/scripts/scriptlib/__init__.py +++ b/scripts/scriptlib/__init__.py @@ -1,9 +1,9 @@ from collections import Counter from decimal import Decimal -from re import split -from sys import stderr -from xml.etree import ElementTree from os.path import exists +from re import split +from xml.etree import ElementTree as ET + class SimulTemplateEntity: def __init__(self, vfs_root, logger): @@ -12,11 +12,11 @@ class SimulTemplateEntity: def get_file(self, base_path, vfs_path, mod): default_path = self.vfs_root / mod / base_path - file = (default_path/ "special" / "filter" / vfs_path).with_suffix('.xml') + file = (default_path / "special" / "filter" / vfs_path).with_suffix(".xml") if not exists(file): - file = (default_path / "mixins" / vfs_path).with_suffix('.xml') + file = (default_path / "mixins" / vfs_path).with_suffix(".xml") if not exists(file): - file = (default_path / vfs_path).with_suffix('.xml') + file = (default_path / vfs_path).with_suffix(".xml") return file def get_main_mod(self, base_path, vfs_path, mods): @@ -33,107 +33,110 @@ class SimulTemplateEntity: return main_mod def apply_layer(self, base_tag, tag): - """ - apply tag layer to base_tag - """ - if tag.get('datatype') == 'tokens': - base_tokens = split(r'\s+', base_tag.text or '') - tokens = split(r'\s+', tag.text or '') + """Apply tag layer to base_tag.""" + if tag.get("datatype") == "tokens": + base_tokens = split(r"\s+", base_tag.text or "") + tokens = split(r"\s+", tag.text or "") final_tokens = base_tokens.copy() for token in tokens: - if token.startswith('-'): + if token.startswith("-"): token_to_remove = token[1:] if token_to_remove in final_tokens: final_tokens.remove(token_to_remove) elif token not in final_tokens: final_tokens.append(token) - base_tag.text = ' '.join(final_tokens) + base_tag.text = " ".join(final_tokens) base_tag.set("datatype", "tokens") - elif tag.get('op'): - op = tag.get('op') - op1 = Decimal(base_tag.text or '0') - op2 = Decimal(tag.text or '0') + elif tag.get("op"): + op = tag.get("op") + op1 = Decimal(base_tag.text or "0") + op2 = Decimal(tag.text or "0") # Try converting to integers if possible, to pass validation. - if op == 'add': + if op == "add": base_tag.text = str(int(op1 + op2) if int(op1 + op2) == op1 + op2 else op1 + op2) - elif op == 'mul': + elif op == "mul": base_tag.text = str(int(op1 * op2) if int(op1 * op2) == op1 * op2 else op1 * op2) - elif op == 'mul_round': + elif op == "mul_round": base_tag.text = str(round(op1 * op2)) else: raise ValueError(f"Invalid operator '{op}'") else: base_tag.text = tag.text for prop in tag.attrib: - if prop not in ('disable', 'replace', 'parent', 'merge'): + if prop not in ("disable", "replace", "parent", "merge"): base_tag.set(prop, tag.get(prop)) for child in tag: base_child = base_tag.find(child.tag) - if 'disable' in child.attrib: + if "disable" in child.attrib: if base_child is not None: base_tag.remove(base_child) - elif ('merge' not in child.attrib) or (base_child is not None): - if 'replace' in child.attrib and base_child is not None: + elif ("merge" not in child.attrib) or (base_child is not None): + if "replace" in child.attrib and base_child is not None: base_tag.remove(base_child) base_child = None if base_child is None: - base_child = ElementTree.Element(child.tag) + base_child = ET.Element(child.tag) base_tag.append(base_child) self.apply_layer(base_child, child) - if 'replace' in base_child.attrib: - del base_child.attrib['replace'] + if "replace" in base_child.attrib: + del base_child.attrib["replace"] def load_inherited(self, base_path, vfs_path, mods): entity = self._load_inherited(base_path, vfs_path, mods) entity[:] = sorted(entity[:], key=lambda x: x.tag) return entity - def _load_inherited(self, base_path, vfs_path, mods, base = None): - """ - vfs_path should be relative to base_path in a mod - """ - if '|' in vfs_path: + def _load_inherited(self, base_path, vfs_path, mods, base=None): + """vfs_path should be relative to base_path in a mod.""" + if "|" in vfs_path: paths = vfs_path.split("|", 1) - base = self._load_inherited(base_path, paths[1], mods, base); - base = self._load_inherited(base_path, paths[0], mods, base); - return base + self._load_inherited(base_path, paths[1], mods, base) + return self._load_inherited(base_path, paths[0], mods, base) main_mod = self.get_main_mod(base_path, vfs_path, mods) fp = self.get_file(base_path, vfs_path, main_mod) - layer = ElementTree.parse(fp).getroot() + layer = ET.parse(fp).getroot() for el in layer.iter(): children = [x.tag for x in el] duplicates = [x for x, c in Counter(children).items() if c > 1] if duplicates: for dup in duplicates: - self.logger.warning(f"Duplicate child node '{dup}' in tag {el.tag} of {fp}") - if layer.get('parent'): - parent = self._load_inherited(base_path, layer.get('parent'), mods, base) + self.logger.warning( + "Duplicate child node '%s' in tag %s of %s", dup, el.tag, fp + ) + if layer.get("parent"): + parent = self._load_inherited(base_path, layer.get("parent"), mods, base) self.apply_layer(parent, layer) return parent - else: - if not base: - return layer - else: - self.apply_layer(base, layer) - return base + + if not base: + return layer + + self.apply_layer(base, layer) + return base def find_files(vfs_root, mods, vfs_path, *ext_list): + """Return a list of 2-size tuples. + + Each tuple contains: + - Path relative to the mod base + - full Path """ - returns a list of 2-size tuple with: - - Path relative to the mod base - - full Path - """ - full_exts = ['.' + ext for ext in ext_list] + full_exts = ["." + ext for ext in ext_list] def find_recursive(dp, base): - """(relative Path, full Path) generator""" + """(relative Path, full Path) generator.""" if dp.is_dir(): - if dp.name != '.svn' and dp.name != '.git' and not dp.name.endswith('~'): + if dp.name not in (".svn", ".git") and not dp.name.endswith("~"): for fp in dp.iterdir(): yield from find_recursive(fp, base) elif dp.suffix in full_exts: relative_file_path = dp.relative_to(base) yield (relative_file_path, dp.resolve()) - return [(rp, fp) for mod in mods for (rp, fp) in find_recursive(vfs_root / mod / vfs_path, vfs_root / mod)] + + return [ + (rp, fp) + for mod in mods + for (rp, fp) in find_recursive(vfs_root / mod / vfs_path, vfs_root / mod) + ]