|
@@ -0,0 +1,129 @@
|
|
|
|
+import configparser
|
|
|
|
+import json
|
|
|
|
+import re
|
|
|
|
+from argparse import ArgumentParser
|
|
|
|
+from collections import OrderedDict
|
|
|
|
+from os import getcwd
|
|
|
|
+from pathlib import Path
|
|
|
|
+
|
|
|
|
+import yaml
|
|
|
|
+
|
|
|
|
+from tidy import create
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def examineFile(file, settings):
|
|
|
|
+ patient = create(file, settings)
|
|
|
|
+ if patient is None:
|
|
|
|
+ return {}
|
|
|
|
+
|
|
|
|
+ full_body_check = {f"{file.as_posix()}": []}
|
|
|
|
+ for diagnostic in patient.check():
|
|
|
|
+ if diagnostic:
|
|
|
|
+ full_body_check[f"{file.as_posix()}"].append(diagnostic.toDict())
|
|
|
|
+
|
|
|
|
+ if len(full_body_check[f"{file.as_posix()}"]) == 0:
|
|
|
|
+ del full_body_check[f"{file.as_posix()}"]
|
|
|
|
+ return full_body_check
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def fixFile(file, settings, full_body_check):
|
|
|
|
+ if not file.exists():
|
|
|
|
+ return
|
|
|
|
+ ext = ".".join(file.name.split(".")[-2:])
|
|
|
|
+
|
|
|
|
+ if ext == "def.json":
|
|
|
|
+ issues = full_body_check[f"{file.as_posix()}"]
|
|
|
|
+ for issue in issues:
|
|
|
|
+ if issue["diagnostic"] == "diagnostic-definition-redundant-override" and settings["fixes"].get(
|
|
|
|
+ "diagnostic-definition-redundant-override", True):
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def formatFile(file: Path, settings):
|
|
|
|
+ if not file.exists():
|
|
|
|
+ return
|
|
|
|
+ ext = ".".join(file.name.split(".")[-2:])
|
|
|
|
+
|
|
|
|
+ if ext == "def.json":
|
|
|
|
+ definition = json.loads(file.read_text())
|
|
|
|
+ content = json.dumps(definition, indent=settings["format"].get("format-definition-indent", 4),
|
|
|
|
+ sort_keys=settings["format"].get("format-definition-sort-keys", True))
|
|
|
|
+
|
|
|
|
+ if settings["format"].get("format-definition-bracket-newline", True):
|
|
|
|
+ newline = re.compile(r"(\B\s+)(\"[\w\"]+)(\:\s\{)")
|
|
|
|
+ content = newline.sub(r"\1\2:\1{", content)
|
|
|
|
+
|
|
|
|
+ if settings["format"].get("format-definition-paired-coordinate-array", True):
|
|
|
|
+ paired_coordinates = re.compile(r"(\[)\s+(-?\d*),\s*(-?\d*)\s*(\])")
|
|
|
|
+ content = paired_coordinates.sub(r"\1 \2, \3 \4", content)
|
|
|
|
+
|
|
|
|
+ file.write_text(content)
|
|
|
|
+
|
|
|
|
+ if ext == "inst.cfg":
|
|
|
|
+ config = configparser.ConfigParser()
|
|
|
|
+ config.read(file)
|
|
|
|
+
|
|
|
|
+ if settings["format"].get("format-profile-sort-keys", True):
|
|
|
|
+ for section in config._sections:
|
|
|
|
+ config._sections[section] = OrderedDict(sorted(config._sections[section].items(), key=lambda t: t[0]))
|
|
|
|
+ config._sections = OrderedDict(sorted(config._sections.items(), key=lambda t: t[0]))
|
|
|
|
+
|
|
|
|
+ with open(file, "w") as f:
|
|
|
|
+ config.write(f, space_around_delimiters=settings["format"].get("format-profile-space-around-delimiters", True))
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def main(files, setting_path, to_format, to_fix, report):
|
|
|
|
+ if not setting_path:
|
|
|
|
+ setting_path = Path(getcwd(), ".printer-linter")
|
|
|
|
+
|
|
|
|
+ if not setting_path.exists():
|
|
|
|
+ print(f"Can't find the settings: {setting_path}")
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+ with open(setting_path, "r") as f:
|
|
|
|
+ settings = yaml.load(f, yaml.FullLoader)
|
|
|
|
+
|
|
|
|
+ full_body_check = {}
|
|
|
|
+ for file in files:
|
|
|
|
+ if file.is_dir():
|
|
|
|
+ for fp in file.rglob("**/*"):
|
|
|
|
+ full_body_check |= examineFile(fp, settings)
|
|
|
|
+ else:
|
|
|
|
+ full_body_check |= examineFile(file, settings)
|
|
|
|
+
|
|
|
|
+ results = yaml.dump(full_body_check, default_flow_style=False, indent=4, width=240)
|
|
|
|
+ if report:
|
|
|
|
+ report.write_text(results)
|
|
|
|
+ else:
|
|
|
|
+ print(results)
|
|
|
|
+
|
|
|
|
+ if to_fix:
|
|
|
|
+ for file in files:
|
|
|
|
+ if file.is_dir():
|
|
|
|
+ for fp in file.rglob("**/*"):
|
|
|
|
+ if f"{file.as_posix()}" in full_body_check:
|
|
|
|
+ fixFile(fp, settings, full_body_check)
|
|
|
|
+ else:
|
|
|
|
+ if f"{file.as_posix()}" in full_body_check:
|
|
|
|
+ fixFile(file, settings, full_body_check)
|
|
|
|
+
|
|
|
|
+ if to_format:
|
|
|
|
+ for file in files:
|
|
|
|
+ if file.is_dir():
|
|
|
|
+ for fp in file.rglob("**/*"):
|
|
|
|
+ formatFile(fp, settings)
|
|
|
|
+ else:
|
|
|
|
+ formatFile(file, settings)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
+ parser = ArgumentParser(
|
|
|
|
+ description="UltiMaker Cura printer linting, static analysis and formatting of Cura printer definitions and other resources")
|
|
|
|
+ parser.add_argument("--setting", required=False, type=Path, help="Path to the `.printer-linter` setting file")
|
|
|
|
+ parser.add_argument("--report", required=False, type=Path, help="Path where the diagnostic report should be stored")
|
|
|
|
+ parser.add_argument("--format", action="store_true", help="Format the files")
|
|
|
|
+ parser.add_argument("--fix", action="store_true", help="Attempt to apply the suggested fixes on the files")
|
|
|
|
+ parser.add_argument("Files", metavar="F", type=Path, nargs="+", help="Files or directories to format")
|
|
|
|
+
|
|
|
|
+ args = parser.parse_args()
|
|
|
|
+ main(args.Files, args.setting, args.format, args.fix, args.report)
|