From db0e46b252115bf899a8ceebce648d6ed8a40354 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 10 Apr 2024 10:42:17 +0200 Subject: [PATCH] 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