Addon Manager: Refactor Macro parser

This commit is contained in:
Chris Hennes
2023-02-11 13:43:52 -08:00
committed by Chris Hennes
parent 2603cd6b16
commit f83abbab4c
7 changed files with 683 additions and 156 deletions

View File

@@ -33,9 +33,8 @@ from typing import Dict, Tuple, List, Union, Optional
import FreeCAD
import NetworkManager
from PySide import QtCore
from addonmanager_utilities import is_float
from addonmanager_macro_parser import MacroParser
translate = FreeCAD.Qt.translate
@@ -109,7 +108,8 @@ class Macro:
def is_installed(self):
"""Returns True if this macro is currently installed (that is, if it exists in the
user macro directory), or False if it is not. Both the exact filename, as well as
the filename prefixed with "Macro", are considered an installation of this macro."""
the filename prefixed with "Macro", are considered an installation of this macro.
"""
if self.on_git and not self.src_filename:
return False
return os.path.exists(
@@ -125,144 +125,12 @@ class Macro:
self.fill_details_from_code(self.code)
def fill_details_from_code(self, code: str) -> None:
"""Reads in the macro code from the given string and parses it for its metadata."""
# Number of parsed fields of metadata. Overrides anything set previously (the code is
# considered authoritative).
# For now:
# __Comment__
# __Web__
# __Wiki__
# __Version__
# __Files__
# __Author__
# __Date__
# __Icon__
max_lines_to_search = 200
line_counter = 0
string_search_mapping = {
"__comment__": "comment",
"__web__": "url",
"__wiki__": "wiki",
"__version__": "version",
"__files__": "other_files",
"__author__": "author",
"__date__": "date",
"__icon__": "icon",
"__xpm__": "xpm",
}
string_search_regex = re.compile(r"\s*(['\"])(.*)\1")
f = io.StringIO(code)
while f and line_counter < max_lines_to_search:
line = f.readline()
if not line:
break
if QtCore.QThread.currentThread().isInterruptionRequested():
return
line_counter += 1
if not line.startswith("__"):
# Speed things up a bit... this comparison is very cheap
continue
lowercase_line = line.lower()
for key, value in string_search_mapping.items():
if lowercase_line.startswith(key):
_, _, after_equals = line.partition("=")
match = re.match(string_search_regex, after_equals)
# We do NOT support triple-quoted strings, except for the icon XPM data
# Most cases should be caught by this code
if match and '"""' not in after_equals:
self._standard_extraction(value, match.group(2))
string_search_mapping.pop(key)
break
# For cases where either there is no match, or we found a triple quote,
# more processing is needed
# Macro authors are supposed to be providing strings here, but in some
# cases they are not doing so. If this is the "__version__" tag, try
# to apply some special handling to accepts numbers, and "__date__"
if key == "__version__":
self._process_noncompliant_version(after_equals)
string_search_mapping.pop(key)
break
# Icon data can be actual embedded XPM data, inside a triple-quoted string
if key in ("__icon__", "__xpm__"):
self._process_icon(f, key, after_equals)
string_search_mapping.pop(key)
break
FreeCAD.Console.PrintError(
translate(
"AddonsInstaller",
"Syntax error while reading {} from macro {}",
).format(key, self.name)
+ "\n"
)
FreeCAD.Console.PrintError(line + "\n")
# Do some cleanup of the values:
if self.comment:
self.comment = re.sub("<.*?>", "", self.comment) # Strip any HTML tags
# Truncate long comments to speed up searches, and clean up display
if len(self.comment) > 512:
self.comment = self.comment[:511] + ""
# Make sure the icon is not an absolute path, etc.
self.clean_icon()
parser = MacroParser(self.name, code)
for key, value in parser.parse_results.items():
if value:
self.__dict__[key] = value
self.parsed = True
def _standard_extraction(self, value: str, match_group):
"""For most macro metadata values, this extracts the required data"""
if isinstance(self.__dict__[value], str):
self.__dict__[value] = match_group
elif isinstance(self.__dict__[value], list):
self.__dict__[value] = [of.strip() for of in match_group.split(",")]
else:
FreeCAD.Console.PrintError(
"Internal Error: bad type in addonmanager_macro class.\n"
)
def _process_noncompliant_version(self, after_equals):
if "__date__" in after_equals.lower():
self.version = self.date
elif is_float(after_equals):
self.version = str(after_equals).strip()
else:
FreeCAD.Console.PrintLog(
f"Unrecognized value for __version__ in macro {self.name}"
)
self.version = "(Unknown)"
def _process_icon(self, f, key, after_equals):
# If this is an icon, it's possible that the icon was actually directly
# specified in the file as XPM data. This data **must** be between
# triple double quotes in order for the Addon Manager to recognize it.
if '"""' in after_equals:
_, _, xpm_data = after_equals.partition('"""')
while True:
line = f.readline()
if not line:
FreeCAD.Console.PrintError(
translate(
"AddonsInstaller",
"Syntax error while reading {} from macro {}",
).format(key, self.name)
+ "\n"
)
break
if '"""' in line:
last_line, _, _ = line.partition('"""')
xpm_data += last_line
break
xpm_data += line
self.xpm = xpm_data
def fill_details_from_wiki(self, url):
"""For a given URL, download its data and attempt to get the macro's metadata out of
it. If the macro's code is hosted elsewhere, as specified by a "rawcodeurl" found on