2024-04-19 17:32:40 +02:00

178 lines
6.5 KiB
Python

import difflib
import json
import os
import re
from configparser import ConfigParser
from pathlib import Path
from typing import Iterator
from ..diagnostic import Diagnostic
from ..replacement import Replacement
from .linter import Linter
FORMULA_NAMES = [
"extruderValue",
"extruderValues",
"anyExtruderWithMaterial",
"anyExtruderNrWithOrDefault",
"resolveOrValue",
"defaultExtruderPosition",
"valueFromContainer",
"extruderValueFromContainer",
"math",
"round",
"max",
"ceil",
"min",
"sqrt",
"log",
"tan",
"cos",
"sin",
"atan",
"acos",
"asin",
"floor",
"sum",
"len",
"radians",
"degrees"
]
DELIMITERS = [r'\+', '-', '=', '/', '\*', r'\(', r'\)', r'\[', r'\]', '{', '}', ' ', '^']
class Formulas(Linter):
"""Finds Typos in the definition files and their formulas."""
def __init__(self, file: Path, settings: dict) -> None:
super().__init__(file, settings)
self._cura_correction_strings = FORMULA_NAMES + list(self.getCuraSettingList())
self._definition = {}
def getCuraSettingList(self) -> list:
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):
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"):
key_incorrect = self.checkValueIncorrect(key)
if key_incorrect:
found = self._appendCorrections(key, key)
value_incorrect = self.checkValueIncorrect(value_dict[value])
if value_incorrect:
found = self._appendCorrections(key, value_dict[value])
if key_incorrect or value_incorrect:
if len(found.group().splitlines()) > 1:
replacements = []
else:
replacements = [Replacement(
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 {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 _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).strip(' ')
return found
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._parseCfg(definition_file)
def _parseCfg(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, formula) -> bool:
if isinstance(formula, str):
self._correct_formula = self._correctTyposInFormula(formula)
return self._correct_formula != formula
else:
return False
def _correctTyposInFormula(self, formula):
pattern = '|'.join(DELIMITERS)
tokens = re.split(pattern, formula)
output = formula
for token in tokens:
if '(' not in token and ')' not in token:
cleaned_token = re.sub(r'[^\w\s]', '', token)
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