From fe0120ef64a924268095ccdffe319c5c59579df3 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 5 Apr 2024 18:18:40 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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 +