From fe0120ef64a924268095ccdffe319c5c59579df3 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 5 Apr 2024 18:18:40 +0200 Subject: [PATCH 01/14] Add Formulas linter and integrate with factory A new linter class, Formulas, has been added to check for issues in definition files, particularly with default parameters overrides. It has been integrated into the Linter factory to also check '.inst.cfg' and '.def.json' files for formulas-related issues. Additionally, a new 'diagnostic-incorrect-formula' check has been included in the .printer-linter configuration. CURA-10901 --- .printer-linter | 1 + printer-linter/src/printerlinter/factory.py | 5 +- .../src/printerlinter/linters/formulas.py | 81 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 printer-linter/src/printerlinter/linters/formulas.py diff --git a/.printer-linter b/.printer-linter index 3a42a5c033..4f643ebd95 100644 --- a/.printer-linter +++ b/.printer-linter @@ -3,6 +3,7 @@ checks: diagnostic-mesh-file-size: true diagnostic-definition-redundant-override: true diagnostic-resources-macos-app-directory-name: true + diagnostic-incorrect-formula: true fixes: diagnostic-definition-redundant-override: true format: diff --git a/printer-linter/src/printerlinter/factory.py b/printer-linter/src/printerlinter/factory.py index 4473fb9a4e..20ee19dee4 100644 --- a/printer-linter/src/printerlinter/factory.py +++ b/printer-linter/src/printerlinter/factory.py @@ -6,6 +6,7 @@ from .linters.defintion import Definition from .linters.linter import Linter from .linters.meshes import Meshes from .linters.directory import Directory +from .linters.formulas import Formulas def getLinter(file: Path, settings: dict) -> Optional[List[Linter]]: @@ -14,12 +15,12 @@ def getLinter(file: Path, settings: dict) -> Optional[List[Linter]]: return None if ".inst" in file.suffixes and ".cfg" in file.suffixes: - return [Directory(file, settings), Profile(file, settings)] + return [Directory(file, settings), Profile(file, settings), Formulas(file, settings)] if ".def" in file.suffixes and ".json" in file.suffixes: if file.stem in ("fdmprinter.def", "fdmextruder.def"): return None - return [Directory(file, settings), Definition(file, settings)] + return [Directory(file, settings), Definition(file, settings), Formulas(file, settings)] if file.parent.stem == "meshes": return [Meshes(file, settings)] diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py new file mode 100644 index 0000000000..c4b0b9d69a --- /dev/null +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -0,0 +1,81 @@ +import json +import re +from pathlib import Path +from typing import Iterator + +from ..diagnostic import Diagnostic +from .linter import Linter +from configparser import ConfigParser +from ..replacement import Replacement + + +class Formulas(Linter): + """ Finds issues in definition files, such as overriding default parameters """ + def __init__(self, file: Path, settings: dict) -> None: + super().__init__(file, settings) + self._definition = {} + + + def check(self) -> Iterator[Diagnostic]: + if self._settings["checks"].get("diagnostic-incorrect-formula", False): + for check in self.checkFormulas(): + yield check + + yield + + def checkFormulas(self) -> Iterator[Diagnostic]: + + self._loadDefinitionFiles(self._file) + self._content = self._file.read_text() + definition_name = list(self._definition.keys())[0] + definition = self._definition[definition_name] + if "overrides" in definition: + for key, value_dict in definition["overrides"].items(): + for value in value_dict: + if value in ("enable", "resolve", "value", "minimum_value_warning", "maximum_value_warning", "maximum_value", "minimum_value"): + value_incorrect = self.checkValueIncorrect() + if value_incorrect: + + yield Diagnostic( + file=self._file, + diagnostic_name="diagnostic-incorrect-formula", + message=f"Given formula {value_dict} to calulate {key} of seems incorrect, please correct the formula and try again.", + level="Error", + offset=1 + ) + yield + + def _loadDefinitionFiles(self, definition_file) -> None: + """ Loads definition file contents into self._definition. Also load parent definition if it exists. """ + definition_name = Path(definition_file.stem).stem + + if not definition_file.exists() or definition_name in self._definition: + return + + if definition_file.suffix == ".json": + # Load definition file into dictionary + self._definition[definition_name] = json.loads(definition_file.read_text()) + + if definition_file.suffix == ".cfg": + self._definition[definition_name] = self.parse_cfg(definition_file) + + + def parse_cfg(self, file_path:Path) -> dict: + config = ConfigParser() + config.read([file_path]) + file_data ={} + overrides = {} + + available_sections = ["values"] + for section in available_sections: + options = config.options(section) + for option in options: + values ={} + values["value"] = config.get(section, option) + overrides[option] = values + file_data["overrides"]= overrides# Process the value here + + return file_data + + def checkValueIncorrect(self): + return True From 8e9ebb683247cfa48d20c552ca44ed04db39938c Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 8 Apr 2024 10:25:23 +0200 Subject: [PATCH 02/14] Consistent for naming convention CURA-10901 --- printer-linter/src/printerlinter/linters/formulas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index c4b0b9d69a..05774742cf 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -57,10 +57,10 @@ class Formulas(Linter): self._definition[definition_name] = json.loads(definition_file.read_text()) if definition_file.suffix == ".cfg": - self._definition[definition_name] = self.parse_cfg(definition_file) + self._definition[definition_name] = self._parseCfg(definition_file) - def parse_cfg(self, file_path:Path) -> dict: + def _parseCfg(self, file_path:Path) -> dict: config = ConfigParser() config.read([file_path]) file_data ={} From e15049f2e08cf1c6b947e5f3a8752d1c0dd903da Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 8 Apr 2024 15:26:55 +0200 Subject: [PATCH 03/14] Update formulas handling CURA-10901 --- printer-linter/src/printerlinter/factory.py | 2 +- .../src/printerlinter/linters/formulas.py | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/printer-linter/src/printerlinter/factory.py b/printer-linter/src/printerlinter/factory.py index 20ee19dee4..4a6d4d8e55 100644 --- a/printer-linter/src/printerlinter/factory.py +++ b/printer-linter/src/printerlinter/factory.py @@ -19,7 +19,7 @@ def getLinter(file: Path, settings: dict) -> Optional[List[Linter]]: if ".def" in file.suffixes and ".json" in file.suffixes: if file.stem in ("fdmprinter.def", "fdmextruder.def"): - return None + return [Formulas(file, settings)] return [Directory(file, settings), Definition(file, settings), Formulas(file, settings)] if file.parent.stem == "meshes": diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index 05774742cf..37a61d9f78 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -1,20 +1,31 @@ import json -import re +import os from pathlib import Path from typing import Iterator +from UM.VersionUpgradeManager import VersionUpgradeManager +from unittest.mock import MagicMock from ..diagnostic import Diagnostic from .linter import Linter from configparser import ConfigParser -from ..replacement import Replacement - +from cura.CuraApplication import CuraApplication # To compare against the current SettingVersion. +from UM.Settings.DefinitionContainer import DefinitionContainer class Formulas(Linter): """ Finds issues in definition files, such as overriding default parameters """ def __init__(self, file: Path, settings: dict) -> None: super().__init__(file, settings) + self._all_keys = self.collectAllSettingIds() self._definition = {} + def collectAllSettingIds(self): + VersionUpgradeManager._VersionUpgradeManager__instance = VersionUpgradeManager(MagicMock()) + CuraApplication._initializeSettingDefinitions() + definition_container = DefinitionContainer("whatever") + with open(os.path.join(os.path.dirname(__file__), "..", "..","..","..", "resources", "definitions", "fdmprinter.def.json"), + encoding="utf-8") as data: + definition_container.deserialize(data.read()) + return definition_container.getAllKeys() def check(self) -> Iterator[Diagnostic]: if self._settings["checks"].get("diagnostic-incorrect-formula", False): @@ -33,7 +44,7 @@ class Formulas(Linter): for key, value_dict in definition["overrides"].items(): for value in value_dict: if value in ("enable", "resolve", "value", "minimum_value_warning", "maximum_value_warning", "maximum_value", "minimum_value"): - value_incorrect = self.checkValueIncorrect() + value_incorrect = self.checkValueIncorrect(value_dict[value].strip("=")) if value_incorrect: yield Diagnostic( @@ -77,5 +88,9 @@ class Formulas(Linter): return file_data - def checkValueIncorrect(self): - return True + def checkValueIncorrect(self, formula:str) -> bool: + try: + compiled_formula = compile(formula, "", "eval") + except SyntaxError: + return True + return False From eecf9cdebf0ac070f86da04ad439979e2aaa450a Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 8 Apr 2024 15:26:55 +0200 Subject: [PATCH 04/14] Refactor formulas.py for improved formula handling Imported difflib, re, and CuraFormulaFunctions for more efficient formula handling and error checking. This prevents crashes and improves the reliability of the application by suggesting the correct formula when an incorrect one is entered CURA-10901 --- .../src/printerlinter/linters/formulas.py | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index 37a61d9f78..df251dbcef 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -1,32 +1,24 @@ +import difflib import json -import os +import re from pathlib import Path from typing import Iterator -from UM.VersionUpgradeManager import VersionUpgradeManager -from unittest.mock import MagicMock + +from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions from ..diagnostic import Diagnostic from .linter import Linter from configparser import ConfigParser -from cura.CuraApplication import CuraApplication # To compare against the current SettingVersion. -from UM.Settings.DefinitionContainer import DefinitionContainer class Formulas(Linter): """ Finds issues in definition files, such as overriding default parameters """ def __init__(self, file: Path, settings: dict) -> None: super().__init__(file, settings) - self._all_keys = self.collectAllSettingIds() + self._cura_formula_functions = CuraFormulaFunctions(self) + self._correct_formulas = ["extruderValue", "extruderValues", "anyExtruderWithMaterial", "anyExtruderNrWithOrDefault" + , "resolveOrValue", "defaultExtruderPosition", "valueFromContainer", "extruderValueFromContainer"] self._definition = {} - def collectAllSettingIds(self): - VersionUpgradeManager._VersionUpgradeManager__instance = VersionUpgradeManager(MagicMock()) - CuraApplication._initializeSettingDefinitions() - definition_container = DefinitionContainer("whatever") - with open(os.path.join(os.path.dirname(__file__), "..", "..","..","..", "resources", "definitions", "fdmprinter.def.json"), - encoding="utf-8") as data: - definition_container.deserialize(data.read()) - return definition_container.getAllKeys() - def check(self) -> Iterator[Diagnostic]: if self._settings["checks"].get("diagnostic-incorrect-formula", False): for check in self.checkFormulas(): @@ -46,11 +38,10 @@ class Formulas(Linter): if value in ("enable", "resolve", "value", "minimum_value_warning", "maximum_value_warning", "maximum_value", "minimum_value"): value_incorrect = self.checkValueIncorrect(value_dict[value].strip("=")) if value_incorrect: - yield Diagnostic( file=self._file, diagnostic_name="diagnostic-incorrect-formula", - message=f"Given formula {value_dict} to calulate {key} of seems incorrect, please correct the formula and try again.", + message=f"Given formula {value_dict} to calulate {key} of seems incorrect, Do you mean {self._correct_formula}? please correct the formula and try again.", level="Error", offset=1 ) @@ -89,8 +80,25 @@ class Formulas(Linter): return file_data def checkValueIncorrect(self, formula:str) -> bool: - try: - compiled_formula = compile(formula, "", "eval") - except SyntaxError: + self._correct_formula = self._correctFormula(formula) + if self._correct_formula == formula: + return False + else: return True - return False + + def _correctFormula(self, input_sentence: str) -> str: + # Find all alphanumeric words, '()' and content inside them, and punctuation + chunks = re.split(r'(\(.*?\))', input_sentence) # split input by parentheses + + corrected_chunks = [] + for chunk in chunks: + if chunk.startswith('(') and chunk.endswith(')'): # if chunk is a formula in parentheses + corrected_chunks.append(chunk) # leave it as is + else: # if chunk is outside parentheses + words = re.findall(r'\w+', chunk) # find potential function names + for word in words: + if difflib.get_close_matches(word, self._correct_formulas, n=1,cutoff=0.6): # if there's a close match in correct formulas + chunk = chunk.replace(word, difflib.get_close_matches(word, self._correct_formulas, n=1, cutoff=0.6)[0]) # replace it + corrected_chunks.append(chunk) + + return ''.join(corrected_chunks) # join chunks back together From 9db52e3888d7a4a5ae8ab1a0e2616147e3b0b23a Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Tue, 9 Apr 2024 16:10:27 +0200 Subject: [PATCH 05/14] Adding the replacement text for the maybe corrected formula CURA-10901 --- .../src/printerlinter/linters/formulas.py | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index df251dbcef..2db1280956 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -7,6 +7,7 @@ from typing import Iterator from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions from ..diagnostic import Diagnostic +from ..replacement import Replacement from .linter import Linter from configparser import ConfigParser @@ -21,7 +22,7 @@ class Formulas(Linter): def check(self) -> Iterator[Diagnostic]: if self._settings["checks"].get("diagnostic-incorrect-formula", False): - for check in self.checkFormulas(): + for check in self.checkFormulas: yield check yield @@ -36,17 +37,38 @@ class Formulas(Linter): for key, value_dict in definition["overrides"].items(): for value in value_dict: if value in ("enable", "resolve", "value", "minimum_value_warning", "maximum_value_warning", "maximum_value", "minimum_value"): - value_incorrect = self.checkValueIncorrect(value_dict[value].strip("=")) - if value_incorrect: - yield Diagnostic( + value_incorrect = self.checkValueIncorrect(self._removeLeadingEqual(value_dict[value])) + if value_incorrect: + if self._file.suffix =='.cfg': + key_with_incorrectValue = re.compile(r'(\b' + key + r'\b\s*=\s*[^=\n]+.*)') + else: + key_with_incorrectValue = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?') + found = key_with_incorrectValue.search(self._content) + if len(found.group().splitlines()) > 1: + replacements = [] + else: + replacement_text = found.group().replace(self._removeLeadingEqual(value_dict[value]), self._correct_formula ) + replacements = [Replacement( + file=self._file, + offset=found.span(1)[0], + length=len(found.group()), + replacement_text=replacement_text)] + yield Diagnostic( file=self._file, diagnostic_name="diagnostic-incorrect-formula", message=f"Given formula {value_dict} to calulate {key} of seems incorrect, Do you mean {self._correct_formula}? please correct the formula and try again.", level="Error", - offset=1 - ) + offset=found.span(0)[0], + replacements=replacements + + ) yield + def _removeLeadingEqual(self, input_value): + if isinstance(input_value, str) and input_value.startswith('='): + return input_value[1:] + return input_value + def _loadDefinitionFiles(self, definition_file) -> None: """ Loads definition file contents into self._definition. Also load parent definition if it exists. """ definition_name = Path(definition_file.stem).stem @@ -79,12 +101,14 @@ class Formulas(Linter): return file_data - def checkValueIncorrect(self, formula:str) -> bool: - self._correct_formula = self._correctFormula(formula) - if self._correct_formula == formula: - return False - else: + def checkValueIncorrect(self, formula) -> bool: + if isinstance(formula, str): + self._correct_formula = self._correctFormula(formula) + if self._correct_formula == formula: + return False return True + else: + return False def _correctFormula(self, input_sentence: str) -> str: # Find all alphanumeric words, '()' and content inside them, and punctuation From 01552556d887ff48fc87ac974535ce263124197a Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Tue, 9 Apr 2024 16:19:49 +0200 Subject: [PATCH 06/14] Fix formula check and correct error message CURA-10901 --- printer-linter/src/printerlinter/linters/formulas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index 2db1280956..ba0e1578ee 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -22,7 +22,7 @@ class Formulas(Linter): def check(self) -> Iterator[Diagnostic]: if self._settings["checks"].get("diagnostic-incorrect-formula", False): - for check in self.checkFormulas: + for check in self.checkFormulas(): yield check yield @@ -56,7 +56,7 @@ class Formulas(Linter): yield Diagnostic( file=self._file, diagnostic_name="diagnostic-incorrect-formula", - message=f"Given formula {value_dict} to calulate {key} of seems incorrect, Do you mean {self._correct_formula}? please correct the formula and try again.", + message=f"Given formula {value_dict} to calulate {key} seems incorrect, Do you mean {self._correct_formula}? please correct the formula and try again.", level="Error", offset=found.span(0)[0], replacements=replacements From db0e46b252115bf899a8ceebce648d6ed8a40354 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 10 Apr 2024 10:42:17 +0200 Subject: [PATCH 07/14] Improve formula checks and error messages in printer-linter This update enhances the checking of formula correctness in printer settings and provides clearer error messages when formulas appear incorrect. By getting a list of Cura setting variables and typical formula names, it uses these to match and replace incorrect segments in formulas, if any. Related code for error handling and message reporting are also revised to give more useful feedback to users for necessary corrections. CURA-10901 --- .../src/printerlinter/linters/formulas.py | 107 +++++++++++------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index ba0e1578ee..e10cd0b2b0 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -1,10 +1,14 @@ import difflib import json import re +import os from pathlib import Path from typing import Iterator +from unittest.mock import MagicMock - +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.VersionUpgradeManager import VersionUpgradeManager +from cura.CuraApplication import CuraApplication from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions from ..diagnostic import Diagnostic from ..replacement import Replacement @@ -16,15 +20,24 @@ class Formulas(Linter): def __init__(self, file: Path, settings: dict) -> None: super().__init__(file, settings) self._cura_formula_functions = CuraFormulaFunctions(self) - self._correct_formulas = ["extruderValue", "extruderValues", "anyExtruderWithMaterial", "anyExtruderNrWithOrDefault" + formula_names = ["extruderValue", "extruderValues", "anyExtruderWithMaterial", "anyExtruderNrWithOrDefault" , "resolveOrValue", "defaultExtruderPosition", "valueFromContainer", "extruderValueFromContainer"] + self._cura_settings_list = list(self.getCuraSettingsList()) + formula_names self._definition = {} + def getCuraSettingsList(self) -> list: + if VersionUpgradeManager._VersionUpgradeManager__instance ==None: + VersionUpgradeManager._VersionUpgradeManager__instance = VersionUpgradeManager(MagicMock()) + CuraApplication._initializeSettingDefinitions() + definition_container = DefinitionContainer("whatever") + with open(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "resources", "definitions", "fdmprinter.def.json"), encoding="utf-8") as data: + definition_container.deserialize(data.read()) + return definition_container.getAllKeys() + def check(self) -> Iterator[Diagnostic]: if self._settings["checks"].get("diagnostic-incorrect-formula", False): for check in self.checkFormulas(): yield check - yield def checkFormulas(self) -> Iterator[Diagnostic]: @@ -36,38 +49,48 @@ class Formulas(Linter): if "overrides" in definition: for key, value_dict in definition["overrides"].items(): for value in value_dict: - if value in ("enable", "resolve", "value", "minimum_value_warning", "maximum_value_warning", "maximum_value", "minimum_value"): - value_incorrect = self.checkValueIncorrect(self._removeLeadingEqual(value_dict[value])) + if value in ("enable", "resolve", "value", "minimum_value_warning", "maximum_value_warning", + "maximum_value", "minimum_value"): + key_incorrect = self.checkValueIncorrect(key) + if key_incorrect: + found = self._appendCorrections(key, key) + value_incorrect = self.checkValueIncorrect(value_dict[value]) if value_incorrect: - if self._file.suffix =='.cfg': - key_with_incorrectValue = re.compile(r'(\b' + key + r'\b\s*=\s*[^=\n]+.*)') - else: - key_with_incorrectValue = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?') - found = key_with_incorrectValue.search(self._content) + found = self._appendCorrections(key, value_dict[value]) + if key_incorrect or value_incorrect: + if len(found.group().splitlines()) > 1: replacements = [] else: - replacement_text = found.group().replace(self._removeLeadingEqual(value_dict[value]), self._correct_formula ) replacements = [Replacement( - file=self._file, - offset=found.span(1)[0], - length=len(found.group()), - replacement_text=replacement_text)] + file=self._file, + offset=found.span(1)[0], + length=len(found.group()), + replacement_text=self._replacement_text)] yield Diagnostic( - file=self._file, - diagnostic_name="diagnostic-incorrect-formula", - message=f"Given formula {value_dict} to calulate {key} seems incorrect, Do you mean {self._correct_formula}? please correct the formula and try again.", - level="Error", - offset=found.span(0)[0], - replacements=replacements - + file=self._file, + diagnostic_name="diagnostic-incorrect-formula", + message=f"Given formula {found.group()} seems incorrect, Do you mean {self._correct_formula}? please correct the formula and try again.", + level="Error", + offset=found.span(0)[0], + replacements=replacements ) + yield - def _removeLeadingEqual(self, input_value): - if isinstance(input_value, str) and input_value.startswith('='): - return input_value[1:] - return input_value + def _appendCorrections(self, key, incorrectString): + + if self._file.suffix == '.cfg': + key_with_incorrectValue = re.compile(r'(\b' + key + r'\b\s*=\s*[^=\n]+.*)') + else: + key_with_incorrectValue = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?') + found = key_with_incorrectValue.search(self._content) + if len(found.group().splitlines()) > 1: + self._replacement_text = '' + else: + self._replacement_text = found.group().replace(incorrectString, self._correct_formula) + return found + def _loadDefinitionFiles(self, definition_file) -> None: """ Loads definition file contents into self._definition. Also load parent definition if it exists. """ @@ -103,26 +126,28 @@ class Formulas(Linter): def checkValueIncorrect(self, formula) -> bool: if isinstance(formula, str): - self._correct_formula = self._correctFormula(formula) + self._correct_formula = self._correctTyposInFormula(formula) if self._correct_formula == formula: return False return True else: return False - def _correctFormula(self, input_sentence: str) -> str: - # Find all alphanumeric words, '()' and content inside them, and punctuation - chunks = re.split(r'(\(.*?\))', input_sentence) # split input by parentheses + def _correctTyposInFormula(self, input): + delimiters = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{','}', ' ', '^'] - corrected_chunks = [] - for chunk in chunks: - if chunk.startswith('(') and chunk.endswith(')'): # if chunk is a formula in parentheses - corrected_chunks.append(chunk) # leave it as is - else: # if chunk is outside parentheses - words = re.findall(r'\w+', chunk) # find potential function names - for word in words: - if difflib.get_close_matches(word, self._correct_formulas, n=1,cutoff=0.6): # if there's a close match in correct formulas - chunk = chunk.replace(word, difflib.get_close_matches(word, self._correct_formulas, n=1, cutoff=0.6)[0]) # replace it - corrected_chunks.append(chunk) + # Create pattern + pattern = '|'.join(delimiters) - return ''.join(corrected_chunks) # join chunks back together + # Split string based on pattern + tokens = re.split(pattern, input) + output = input + for token in tokens: + # If the token does not contain a parenthesis, we treat it as a word + if '(' not in token and ')' not in token: + cleaned_token = re.sub(r'[^\w\s]', '', token) + matches = difflib.get_close_matches(cleaned_token, self._cura_settings_list, n=1, cutoff=0.8) + if matches: + output = output.replace(cleaned_token, matches[0]) + + return output From dcd673a6052e84508159ec423148d44d025a484c Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 10 Apr 2024 10:53:17 +0200 Subject: [PATCH 08/14] Refactor and enhance formula linter in printer-linter module The update improves formula linting by refining the identification and correction processes of formula typos. It establishes a list of standard formula names and delimiters for better match and replacement operations. Additionally, it enhances error reporting, providing users with more specific and instructive feedback. The Cura settings list retrieval method has been optimized as well. Related Task: CURA-10901 --- .../src/printerlinter/linters/formulas.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index e10cd0b2b0..53a059f7d2 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -1,7 +1,8 @@ import difflib import json -import re import os +import re +from configparser import ConfigParser from pathlib import Path from typing import Iterator from unittest.mock import MagicMock @@ -10,23 +11,29 @@ from UM.Settings.DefinitionContainer import DefinitionContainer from UM.VersionUpgradeManager import VersionUpgradeManager from cura.CuraApplication import CuraApplication from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions + from ..diagnostic import Diagnostic from ..replacement import Replacement from .linter import Linter -from configparser import ConfigParser + +FORMULA_NAMES = [ + "extruderValue", "extruderValues", "anyExtruderWithMaterial", "anyExtruderNrWithOrDefault", + "resolveOrValue", "defaultExtruderPosition", "valueFromContainer", "extruderValueFromContainer" +] + +DELIMITERS = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{', '}', ' ', '^'] + class Formulas(Linter): - """ Finds issues in definition files, such as overriding default parameters """ + """Finds issues in definition files, such as overriding default parameters.""" + def __init__(self, file: Path, settings: dict) -> None: super().__init__(file, settings) - self._cura_formula_functions = CuraFormulaFunctions(self) - formula_names = ["extruderValue", "extruderValues", "anyExtruderWithMaterial", "anyExtruderNrWithOrDefault" - , "resolveOrValue", "defaultExtruderPosition", "valueFromContainer", "extruderValueFromContainer"] - self._cura_settings_list = list(self.getCuraSettingsList()) + formula_names + self._cura_correction_strings = FORMULA_NAMES + list(self.getCuraSettingList()) self._definition = {} - def getCuraSettingsList(self) -> list: - if VersionUpgradeManager._VersionUpgradeManager__instance ==None: + def getCuraSettingList(self) -> list: + if VersionUpgradeManager._VersionUpgradeManager__instance is None: VersionUpgradeManager._VersionUpgradeManager__instance = VersionUpgradeManager(MagicMock()) CuraApplication._initializeSettingDefinitions() definition_container = DefinitionContainer("whatever") @@ -133,21 +140,16 @@ class Formulas(Linter): else: return False - def _correctTyposInFormula(self, input): - delimiters = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{','}', ' ', '^'] + def _correctTyposInFormula(self, formula): + pattern = '|'.join(DELIMITERS) + tokens = re.split(pattern, formula) - # Create pattern - pattern = '|'.join(delimiters) - - # Split string based on pattern - tokens = re.split(pattern, input) - output = input + output = formula for token in tokens: - # If the token does not contain a parenthesis, we treat it as a word if '(' not in token and ')' not in token: cleaned_token = re.sub(r'[^\w\s]', '', token) - matches = difflib.get_close_matches(cleaned_token, self._cura_settings_list, n=1, cutoff=0.8) - if matches: - output = output.replace(cleaned_token, matches[0]) - + possible_matches = difflib.get_close_matches(cleaned_token, self._cura_correction_strings, n=1, cutoff=0.8) + if possible_matches: + output = output.replace(cleaned_token, possible_matches[0]) return output + From 3987969a2275065ecd8ed533fd8f5e2cc04808ec Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 10 Apr 2024 11:37:19 +0200 Subject: [PATCH 09/14] Simplify and optimize Cura settings list retrieval Related Task: CURA-10901 --- .../src/printerlinter/linters/formulas.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index 53a059f7d2..a7f9f4e31a 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -5,12 +5,6 @@ import re from configparser import ConfigParser from pathlib import Path from typing import Iterator -from unittest.mock import MagicMock - -from UM.Settings.DefinitionContainer import DefinitionContainer -from UM.VersionUpgradeManager import VersionUpgradeManager -from cura.CuraApplication import CuraApplication -from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions from ..diagnostic import Diagnostic from ..replacement import Replacement @@ -33,13 +27,19 @@ class Formulas(Linter): self._definition = {} def getCuraSettingList(self) -> list: - if VersionUpgradeManager._VersionUpgradeManager__instance is None: - VersionUpgradeManager._VersionUpgradeManager__instance = VersionUpgradeManager(MagicMock()) - CuraApplication._initializeSettingDefinitions() - definition_container = DefinitionContainer("whatever") - with open(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "resources", "definitions", "fdmprinter.def.json"), encoding="utf-8") as data: - definition_container.deserialize(data.read()) - return definition_container.getAllKeys() + with open(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "resources", "definitions", "fdmprinter.def.json")) as data: + json_data = json.load(data) + return self.extractKeys(json_data) + + def extractKeys(self, json_obj, parent_key=''): + keys_with_value = [] + for key, values in json_obj.items(): + new_key = key + if isinstance(values, dict): + if 'label' in values: + keys_with_value.append(new_key) + keys_with_value.extend(self.extractKeys(values, new_key)) + return keys_with_value def check(self) -> Iterator[Diagnostic]: if self._settings["checks"].get("diagnostic-incorrect-formula", False): From 84f90eadf8f164e293643b1d0bbac3aa0fb8f984 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 10 Apr 2024 14:53:49 +0200 Subject: [PATCH 10/14] Adding strip as the github actions were not picking up text with extra spaces CURA-10901 --- printer-linter/src/printerlinter/linters/formulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index a7f9f4e31a..c8630332a1 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -95,7 +95,7 @@ class Formulas(Linter): if len(found.group().splitlines()) > 1: self._replacement_text = '' else: - self._replacement_text = found.group().replace(incorrectString, self._correct_formula) + self._replacement_text = found.group().replace(incorrectString, self._correct_formula).strip(' ') return found From fd388b5cb3b099e7d62759c7ce95d1105caaedc9 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 18 Apr 2024 11:53:38 +0200 Subject: [PATCH 11/14] Modify Formulas class description and simplify return statement CURA-10901 --- printer-linter/src/printerlinter/linters/formulas.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index c8630332a1..d5f8c8fb99 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -19,7 +19,7 @@ DELIMITERS = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{', '}', class Formulas(Linter): - """Finds issues in definition files, such as overriding default parameters.""" + """Finds Typos in the definition files and their formulas.""" def __init__(self, file: Path, settings: dict) -> None: super().__init__(file, settings) @@ -134,9 +134,7 @@ class Formulas(Linter): def checkValueIncorrect(self, formula) -> bool: if isinstance(formula, str): self._correct_formula = self._correctTyposInFormula(formula) - if self._correct_formula == formula: - return False - return True + return self._correct_formula != formula else: return False From 1f2e690809b71ae43b81e54de2eed7dcc0ceadd1 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 19 Apr 2024 17:02:58 +0200 Subject: [PATCH 12/14] Extend list of formula names in linter The list of formula names in the printer-linter's formula linter has been expanded. New entries include various mathematical functions (e.g., "max", "min", "sqrt"), string manipulation functions ("lower", "upper", "startswith"), and cryptographic/hash functions ("hashlib", "md5"). --- .../src/printerlinter/linters/formulas.py | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index d5f8c8fb99..ae9998324c 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -11,8 +11,50 @@ from ..replacement import Replacement from .linter import Linter FORMULA_NAMES = [ - "extruderValue", "extruderValues", "anyExtruderWithMaterial", "anyExtruderNrWithOrDefault", - "resolveOrValue", "defaultExtruderPosition", "valueFromContainer", "extruderValueFromContainer" + "extruderValue", + "extruderValues", + "anyExtruderWithMaterial", + "anyExtruderNrWithOrDefault", + "resolveOrValue", + "defaultExtruderPosition", + "valueFromContainer", + "extruderValueFromContainer", + "math", + "round", + "max", + "ceil", + "min", + "sqrt", + "log", + "tan", + "cos", + "sin", + "atan", + "acos", + "asin", + "pi", + "floor", + "debug", + "sum", + "len", + "uuid", + "hashlib", + "base64", + "uuid3", + "NAMESPACE_DNS", + "decode", + "encode", + "b64encode", + "digest", + "md5", + "radians", + "degrees", + "lower", + "upper", + "startswith", + "endswith", + "capitalize", + "index" ] DELIMITERS = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{', '}', ' ', '^'] From 72bf639beac129f6247a026d4c20cc56f2e2f372 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 19 Apr 2024 17:31:08 +0200 Subject: [PATCH 13/14] Remove unnecessary formulas from linter These formulas or modules are not used in definition files , so removing them CURA-10901 --- .../src/printerlinter/linters/formulas.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index ae9998324c..d100d03067 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -32,29 +32,12 @@ FORMULA_NAMES = [ "atan", "acos", "asin", - "pi", "floor", - "debug", "sum", "len", - "uuid", - "hashlib", - "base64", - "uuid3", - "NAMESPACE_DNS", - "decode", - "encode", - "b64encode", - "digest", - "md5", "radians", "degrees", - "lower", "upper", - "startswith", - "endswith", - "capitalize", - "index" ] DELIMITERS = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{', '}', ' ', '^'] From 039b6ed2c2f8c22084ee8b92954766abe87d33a3 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 19 Apr 2024 17:32:40 +0200 Subject: [PATCH 14/14] Remove 'upper' from available formula functions CURA-10901 --- printer-linter/src/printerlinter/linters/formulas.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/printer-linter/src/printerlinter/linters/formulas.py b/printer-linter/src/printerlinter/linters/formulas.py index d100d03067..ad5b7ee943 100644 --- a/printer-linter/src/printerlinter/linters/formulas.py +++ b/printer-linter/src/printerlinter/linters/formulas.py @@ -36,8 +36,7 @@ FORMULA_NAMES = [ "sum", "len", "radians", - "degrees", - "upper", + "degrees" ] DELIMITERS = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{', '}', ' ', '^']