Parse bottle files and constructure a package class which can be used in transactions. Set of valid packages keys is defined. func@_parse_bottle_file: - Module protected function called internally to parse bottle files. - Extract the Description field (this is a little different from other fields given it can be multiline). - Remove any comments which may have been left by a user. - Process the bottle file and extract all key-value pairs. This will also ensure that mandatory fields exist, and that any fields which are repeated are flagged (parser will cause app to crash). - Return a dictionary of key-value pairs. class@_Soybottle: - Class (struct) which represents a bottle file in a structured format. The dictionary is unwrapped after parsing and populates this class using the `.loadPackage()` classmethod. - Defined repr dunder which will return a string representation of the class as a dictionary (note class uses slots). - method@loadPackage(): - Defines a set of mandatory fields required in the bottle file. - Reports and exits if mandatory fields are missing. Warn if unrecognised fields exist. - Separate optional fields from mandatory ones. - Construct new class instance and return the object. class@PackageItem: - Class which stores information regarding a package, including whether it is installed, and the package information from _Soybottle. Class is early development. func@loadPackage: - This function is akin to _Soybottle@loadPackage, but *should* be called instead of the classmethod. To be called when a bottle file is given as argv, not by bottle file in the database. - Given a filename (`item=`), will attempt to open and read the contents of a bottle file. If the file is readable, it will then begin parsing and constructing a new class@PackageItem object. - Function will attempt to permanently store a bottle file to database. If this fails, the app will sys-exit. - Either the class@PackageItem or Exception is returned. This is to allow for exception messages to be given back to the install command.
121 lines
4.4 KiB
Python
121 lines
4.4 KiB
Python
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)
|