diff --git a/soypak/deps/bottler.py b/soypak/deps/bottler.py new file mode 100644 index 0000000..081688b --- /dev/null +++ b/soypak/deps/bottler.py @@ -0,0 +1,120 @@ +import re +import sys +import clog +import soypak.util as util +from io import TextIOWrapper + + +_log = clog.Logger.get("runtime_logger") + + +_PACKAGE_KEYS = {"Package", "Version", "Maintainer", "Filename", "Package-Repo", "Description", "Section", + "Priority", "Essential", "Architecture", "Origin", "Bugs", "Homepage", "Tag", "Depends", + "Pre-Depends", "Recommends", "Suggests", "Breaks", "Conflicts", "Replaces", "Provides"} + + +def _parse_bottle_file(item: TextIOWrapper) -> dict[str, str]: + # read the contents directly into memory + contents = item.read() + + # first grab the description (if exists), then remove it + desc = list(re.finditer(util.re_desc_tag, contents)) + contents = re.sub(util.re_desc_tag, "", contents) + # clean out the comments, strip leading/trailing whitespace, and split the lines + contents = re.sub(util.re_comments, "", contents).strip().splitlines() + parameters = {} + + # handle for description + if not desc: + _log.error("Missing field from bottle file, 'Description'.").withConsole() + sys.exit(-1) + # format, clean and add to parameters dictionary + desc = str(desc[0].group()).split("\n") + parameters['Description'] = (desc[0].lstrip("Description:").strip(), "\n".join(desc[1:])) + + for param in contents: + # handle for when newlines were split (empty string) + if not param: + continue + + # split the key-value and strip it + try: + key, val = map(lambda _: _.strip(), param.split(':')) + except ValueError: + # we should stop processing the file + _log.error("The bottle file contains broken fields.").withConsole() + sys.exit(-1) + + if key in parameters: + # The bottle file has a parameter repeated multiple times. This is parsing error + _log.error(f"Parameter {key!r} occurs more than once in bottle file.").withConsole() + sys.exit(-1) + + if key not in _PACKAGE_KEYS: + _log.error(f"Parameter {key!r} is not a valid field for bottle files.").withConsole() + sys.exit(-1) + + # now we can safely add the key to the dictionary + parameters[key] = val + + return parameters + + +class _Soybottle: + __slots__ = "package", "version", "maintainer", "desc", "package_repo", "filename", "optional" + def __init__(self, *, pkg, version, maintainer, desc, pkg_repo, filename, optionals={}) -> None: + self.package = pkg + self.version = version + self.maintainer = maintainer + self.desc = desc + self.package_repo = pkg_repo + self.filename = filename + self.optional = optionals + + + def __repr__(self) -> str: + return '{' + ", ".join("{0!r}: {1!r}".format(k, getattr(self, k)) for k in type(self).__slots__) + '}' + + + @classmethod + def loadPackage(cls, *, fields: dict[str, str]): + REQUIRED = {"Package", "Version", "Maintainer", "Description", "Package-Repo", "Filename"} + try: + for k in REQUIRED: + fields[k] + except KeyError as err: + _log.error("Missing mandatory key from bottle file %s" % str(err)).withConsole() + sys.exit(-1) + + # grab all the optional fields from the bottle file + optionals = dict(map(lambda k: (k, fields.pop(k)), REQUIRED ^ _PACKAGE_KEYS)) + if len(fields) > 6: + # just log a warning that the bottle file as some extra values unrecognised (not really critical) + _log.warn("Identified additional keys in bottle file that are unrecognised.") + + return cls(pkg=fields['Package'], version=fields['Version'], maintainer=['Maintainer'], desc=['Description'], + pkg_repo=fields['Package-Repo'], filename=fields['Filename'], optionals=optionals) + + +class PackageItem: + def __init__(self, info: _Soybottle) -> None: + self.installed = False + self.pkg_info = info + + +def loadPackage(item: str) -> PackageItem | Exception: + try: + fp = open(item, 'r') + except (IOError, FileNotFoundError) as err: + return err + + _log.debug(f"Obtained file point {fp!r}") + res = _Soybottle.loadPackage(fields=_parse_bottle_file(fp)) + + # permanently store the bottle file to the database + result = util.rack_bottle(fp, "") + if not result: + _log.error("The bottle file could not be permanently saved to soypak.").withConsole() + sys.exit(-1) + + return PackageItem(res)