Compare commits
15 Commits
11435da41b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b23ab91be | |||
| c20a1fb1c4 | |||
| 353dc3f768 | |||
| 8dc0e10969 | |||
| f312a85353 | |||
| 18634e7794 | |||
| 7407ba1084 | |||
| 4315780777 | |||
| 683e55ea0d | |||
| 851e37538c | |||
| 2bd65d19d4 | |||
| 800e7973c3 | |||
| 82a7224190 | |||
| c8848aec47 | |||
| 63d6c4b31c |
86
README.md
86
README.md
|
|
@ -1,79 +1,15 @@
|
||||||

|

|
||||||
# soypak
|
# soypak
|
||||||
An additional package manager for managing Debian apps on Linux distros.
|
|
||||||
|
|
||||||
This branch is an early prototype to demonstrate the CLI interaction with soypak,
|
An additional package manager for managing Microsoft apps on Linux distros.
|
||||||
and provides a vision of what is to be created. It is not an early prototype which
|
|
||||||
can perform package management. With that, there may be typos and other bugs.
|
|
||||||
|
|
||||||
## Setup & Installation
|
## TODO
|
||||||
This branch can be cloned with the following command.
|
|
||||||
```
|
|
||||||
git clone --single-branch --branch 11435da41b https://git.closedless.xyz/ClosedLess/soypak.git
|
|
||||||
cd soypak/
|
|
||||||
```
|
|
||||||
|
|
||||||
Afterwards, a virtual environment should be created to prevent system-wide pip
|
- [x] CLI Parser
|
||||||
installation.
|
- [x] Logging
|
||||||
```
|
- [ ] Error system
|
||||||
python3 -m venv <virtualenv>
|
- [ ] Translation system (I18N and PO files)
|
||||||
```
|
- [ ] CLI UI
|
||||||
|
- [ ] Package installer
|
||||||
Then the following pip installation commands can be ran
|
- [ ] Dependency resolver
|
||||||
```
|
- [ ] Package Plug-in Manager
|
||||||
pip3 install -e .
|
|
||||||
pip3 install clog-x.x.xxx-py3-none-any.whl # where x.x.xxx is the latest version release
|
|
||||||
```
|
|
||||||
|
|
||||||
You can fetch the latest version of CLog from
|
|
||||||
[here](https://git.closedless.xyz/ClosedLess/clog/releases). Either the `.whl` or `tar.gz` file.
|
|
||||||
|
|
||||||
## Using soypak
|
|
||||||
After setting up soypak, the app can be ran using the Python interpreter
|
|
||||||
```
|
|
||||||
python3 soypak/soypak-cli.py help
|
|
||||||
```
|
|
||||||
|
|
||||||
This will print the help message to the terminal and exit. Here, the `help` command
|
|
||||||
is used explicitly, however the same can be achieved by providing no commands (an
|
|
||||||
additional error message will also be printed at the top in red).
|
|
||||||
|
|
||||||
The following is the output printed
|
|
||||||
```
|
|
||||||
Usage: soypak [options] <command> [arguments]
|
|
||||||
|
|
||||||
A package manager to manage Microsoft apps.
|
|
||||||
|
|
||||||
Where <command> is one of:
|
|
||||||
help Display the help message of the give command(s), or print this message.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
--version show the program's version number and exit
|
|
||||||
|
|
||||||
Author(s): Ethan Smith-Coss.
|
|
||||||
Contacts: ethan.sc@closedless.xyz
|
|
||||||
|
|
||||||
Thanks for using our software 🍪
|
|
||||||
Copyright (C) ClosedLess 2023. Licensed under GNU GPLv3+
|
|
||||||
```
|
|
||||||
|
|
||||||
As stated in the printed message, `help` provides the help message of a given command.
|
|
||||||
With this, we can pass 'help' as an argument to `help`
|
|
||||||
```
|
|
||||||
python3 soypak/soypak-cli.py help help
|
|
||||||
```
|
|
||||||
|
|
||||||
This will produce the following output
|
|
||||||
```
|
|
||||||
help: Display the help message of the give command(s), or print this message.
|
|
||||||
|
|
||||||
Usage: Usage: help [<command1>, <command2>, ..., <commandn>]
|
|
||||||
|
|
||||||
If no arguments are specified, the general help message is displayed.
|
|
||||||
```
|
|
||||||
|
|
||||||
Help messages are structured in the following format:
|
|
||||||
- `<command name>: <synopsis>` - the name of the command and its shortest description
|
|
||||||
- `<usage>` - how to use the command in question
|
|
||||||
- `<summary>` - an extended description of the command
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import clog
|
||||||
import soypak.cli.parser as parser
|
import soypak.cli.parser as parser
|
||||||
|
|
||||||
|
|
||||||
# partial load the logger
|
log = clog.Logger.get("runtime_logger")
|
||||||
log = lambda: clog.Logger.get("runtime_logger")
|
|
||||||
|
|
||||||
|
|
||||||
class CommandHelpStruct:
|
class CommandHelpStruct:
|
||||||
|
|
@ -35,7 +34,7 @@ class Command:
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
# Just get the logger for this one statement (:@Note: if more logging occurs, global variable for logger)
|
# Just get the logger for this one statement (:@Note: if more logging occurs, global variable for logger)
|
||||||
log().error("Unrecognised command: %s" % name).withConsole()
|
log.error("Unrecognised command: %s" % name).withConsole()
|
||||||
Command.die()
|
Command.die()
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
@ -60,13 +59,13 @@ def register_command(_cls=None, *, name: str, alias=None, help: CommandHelpStruc
|
||||||
# check if the command has already been given a registry name (only one should exist)
|
# check if the command has already been given a registry name (only one should exist)
|
||||||
if hasattr(cls, 'name'):
|
if hasattr(cls, 'name'):
|
||||||
msg = f"Command {cls.__name__!r} has already been registered with the given name: {getattr(cls, 'name')!r}"
|
msg = f"Command {cls.__name__!r} has already been registered with the given name: {getattr(cls, 'name')!r}"
|
||||||
log().error(msg)
|
log.error(msg)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
||||||
# check if that registry name is already taken
|
# check if that registry name is already taken
|
||||||
if name in Command.registry_names:
|
if name in Command.registry_names:
|
||||||
msg = f"Command {cls.__name__!r} cannot use registry name {name!r} because it is in use."
|
msg = f"Command {cls.__name__!r} cannot use registry name {name!r} because it is in use."
|
||||||
log().error(msg)
|
log.error(msg)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
||||||
# set the name of the command as given
|
# set the name of the command as given
|
||||||
|
|
@ -74,13 +73,13 @@ def register_command(_cls=None, *, name: str, alias=None, help: CommandHelpStruc
|
||||||
|
|
||||||
# This is deliberate to make aware that `alias` isn't being used so pointless to pass now.
|
# This is deliberate to make aware that `alias` isn't being used so pointless to pass now.
|
||||||
if alias is not None:
|
if alias is not None:
|
||||||
log().warn(f"'alias' argument was passed as something other than None. This is intended for future use.")
|
log.warn(f"'alias' argument was passed as something other than None. This is intended for future use.")
|
||||||
raise Warning("The 'alias' argument is not intended for use. Please exclude for now.")
|
raise Warning("The 'alias' argument is not intended for use. Please exclude for now.")
|
||||||
|
|
||||||
# ensure that the `help` given is of the correct type
|
# ensure that the `help` given is of the correct type
|
||||||
if not isinstance(help, CommandHelpStruct):
|
if not isinstance(help, CommandHelpStruct):
|
||||||
msg = f"The argument 'help' must be of type CommandHelpStruct, not {type(help)!r}"
|
msg = f"The argument 'help' must be of type CommandHelpStruct, not {type(help)!r}"
|
||||||
log().error(msg)
|
log.error(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# construct the help message
|
# construct the help message
|
||||||
|
|
|
||||||
76
soypak/cli/commands/install.py
Normal file
76
soypak/cli/commands/install.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import sys
|
||||||
|
import clog
|
||||||
|
import pathlib
|
||||||
|
import argparse
|
||||||
|
import soypak.cli.command as command
|
||||||
|
import soypak.deps.transaction as transaction
|
||||||
|
import soypak.deps.bottler as bottler
|
||||||
|
|
||||||
|
|
||||||
|
_log = clog.Logger.get("runtime_logger")
|
||||||
|
|
||||||
|
|
||||||
|
@command.register_command(name="install", help=command.CommandHelpStruct(
|
||||||
|
synopsis="Install debian binary packages.",
|
||||||
|
usage="install <package1> [<package2>, ..., <packagen>]",
|
||||||
|
summary=("Install one or multiple available packages via soypak."))
|
||||||
|
)
|
||||||
|
class Install(command.Command):
|
||||||
|
def __init__(self, *, data: argparse.Namespace, args: list[str] = []) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.data = data
|
||||||
|
self.rargs = args
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
candidates: list[bottler.PackageItem] = []
|
||||||
|
# any preinit before installing should occur here
|
||||||
|
for item in self.rargs:
|
||||||
|
# has a bottle file been directly passed?
|
||||||
|
if item.endswith(".bottle"):
|
||||||
|
_log.debug("Detected a bottle file. Attempting to sideload...", end="").withConsole()
|
||||||
|
_log.writeLog(header=False)
|
||||||
|
|
||||||
|
# check if the file actually exists on the system
|
||||||
|
if not pathlib.Path(item).resolve().exists():
|
||||||
|
_log.printLog("failed.", level=4)
|
||||||
|
_log.warn(f"Could not find bottle file {item!r}. Skipping package.").withConsole()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# let's structure the file to be processed.
|
||||||
|
pkg = bottler.loadPackage(item)
|
||||||
|
if isinstance(pkg, Exception):
|
||||||
|
_log.printLog("failed.", level=4)
|
||||||
|
# :@Ethan: maybe the loadPackage could give us some information.
|
||||||
|
_log.error(f"Unable to sideload bottle file {item!r}. The following error was raised: {str(pkg)}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
_log.printLog("ok.", level=1)
|
||||||
|
_log.debug(f"Sideloaded: {item}")
|
||||||
|
candidates.append(pkg)
|
||||||
|
|
||||||
|
|
||||||
|
continue
|
||||||
|
# let's check the database for a package by the given name
|
||||||
|
...
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
_log.info("Nothing to install.").withConsole()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
tx = transaction.Transaction(transaction.PkgGoal.PKG_INSTALL)
|
||||||
|
depends = tx.compute(candidates)
|
||||||
|
|
||||||
|
if len(depends) > 0:
|
||||||
|
_log.debug("Computed system dependencies required by the packages selected.\n", *depends)
|
||||||
|
_log.printLog("The following system dependencies have been identified:\n",
|
||||||
|
*depends, "", level=3, sep=" | ")
|
||||||
|
|
||||||
|
tx.apply()
|
||||||
|
if not tx.problems():
|
||||||
|
...
|
||||||
|
# :@TODO: handle all the problems
|
||||||
|
|
||||||
|
# commit the transaction to record
|
||||||
|
tx.commit()
|
||||||
|
|
@ -4,7 +4,8 @@ import clog
|
||||||
import soypak.cli.command as command
|
import soypak.cli.command as command
|
||||||
# import all the commands to auto-register
|
# import all the commands to auto-register
|
||||||
from soypak.cli.commands import (
|
from soypak.cli.commands import (
|
||||||
help
|
help,
|
||||||
|
install,
|
||||||
)
|
)
|
||||||
|
|
||||||
import soypak.cli.parser as parser
|
import soypak.cli.parser as parser
|
||||||
|
|
|
||||||
1
soypak/db/__init__.py
Normal file
1
soypak/db/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
rack_db_dir = "/var/db/soypak/rack"
|
||||||
120
soypak/deps/bottler.py
Normal file
120
soypak/deps/bottler.py
Normal file
|
|
@ -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)
|
||||||
13
soypak/deps/operations.py
Normal file
13
soypak/deps/operations.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import clog
|
||||||
|
import soypak.deps.bottler as bottler
|
||||||
|
|
||||||
|
|
||||||
|
_log = clog.Logger.get("runtime_logger")
|
||||||
|
|
||||||
|
|
||||||
|
def install_packages(tx, pkgs: list[bottler.PackageItem]):
|
||||||
|
_log.debug(f"The transaction goal is {tx.pkg_goal=}. Attempting to perform installation.")
|
||||||
|
|
||||||
|
# :@TODO: install the damned things!
|
||||||
|
|
||||||
|
_log.debug("Installation of packages is complete. Ensure to resolve problems within the transaction.")
|
||||||
54
soypak/deps/transaction.py
Normal file
54
soypak/deps/transaction.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import enum
|
||||||
|
import sys
|
||||||
|
import clog
|
||||||
|
import soypak.deps.bottler as bottler
|
||||||
|
import soypak.deps.operations as ops
|
||||||
|
|
||||||
|
|
||||||
|
_log = clog.Logger.get("runtime_logger")
|
||||||
|
|
||||||
|
|
||||||
|
class PkgGoal(enum.Enum):
|
||||||
|
PKG_INSTALL = 1
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction:
|
||||||
|
def __init__(self, goal: PkgGoal) -> None:
|
||||||
|
"""Setup a new transaction."""
|
||||||
|
if not isinstance(goal, PkgGoal):
|
||||||
|
_log.error(f"TypeError: {goal=}, expected PkgGoal.")
|
||||||
|
_log.printLog("There was an issue when operating on the transaction.", level=4)
|
||||||
|
sys.exit(1)
|
||||||
|
self.pkg_goal: PkgGoal = goal
|
||||||
|
|
||||||
|
|
||||||
|
def compute(self, pkgs: list[bottler.PackageItem]) -> tuple:
|
||||||
|
"""Compute dependencies and conflicts of packages. Returns a list of total packages to install."""
|
||||||
|
self._final_state = []
|
||||||
|
sys_depends = []
|
||||||
|
for pkg in pkgs:
|
||||||
|
# add the package itself to the final state since it can be installed
|
||||||
|
self._final_state.append(pkg)
|
||||||
|
# :@TODO: compute the dependencies
|
||||||
|
|
||||||
|
return tuple(sys_depends)
|
||||||
|
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
if self.pkg_goal == PkgGoal.PKG_INSTALL:
|
||||||
|
ops.install_packages(self, self._final_state)
|
||||||
|
|
||||||
|
|
||||||
|
def problems(self) -> tuple:
|
||||||
|
"""Provide a report of conflicts and packages which cannot be resolved (dependencies not installable)."""
|
||||||
|
return ()
|
||||||
|
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
"""Commit the transaction to record for what happened."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def finalised(self):
|
||||||
|
return self._final_state
|
||||||
85
soypak/util.py
Normal file
85
soypak/util.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import clog
|
||||||
|
import errno
|
||||||
|
import soypak.db
|
||||||
|
from io import TextIOWrapper
|
||||||
|
|
||||||
|
|
||||||
|
_log = clog.Logger.get("runtime_logger")
|
||||||
|
|
||||||
|
|
||||||
|
re_comments = re.compile(r"(--.*$|\*--(\n.*?)+--\*)", re.MULTILINE)
|
||||||
|
re_desc_tag = re.compile(r"^Description:(?: .*\n)+", re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
# from DNF util module (https://github.com/rpm-software-management/dnf/blob/master/dnf/util.py#L142)
|
||||||
|
def ensure_dir(dname) -> tuple[int, str]:
|
||||||
|
try:
|
||||||
|
os.makedirs(dname, mode=0o755)
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno != errno.EEXIST or not os.path.isdir(dname):
|
||||||
|
return err.errno, str(err)
|
||||||
|
|
||||||
|
return 0, ""
|
||||||
|
|
||||||
|
|
||||||
|
# from eopkg util module (https://github.com/solus-project/package-management/blob/master/pisi/util.py#L293)
|
||||||
|
def join_path(a, *p) -> str:
|
||||||
|
"""Join two or more pathname components.
|
||||||
|
Python os.path.join cannot handle '/' at the start of latter components.
|
||||||
|
"""
|
||||||
|
for b in p:
|
||||||
|
b = b.lstrip('/')
|
||||||
|
if a == '' or a.endswith('/'):
|
||||||
|
a += b
|
||||||
|
continue
|
||||||
|
a += '/' + b
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
def rack_bottle(fp: TextIOWrapper | str, f_name: str) -> bool:
|
||||||
|
"""Store a bottle file in the database and update package cache."""
|
||||||
|
if isinstance(fp, TextIOWrapper):
|
||||||
|
_log.debug(f"{fp=}, ensuring file is read from beginning...", end="")
|
||||||
|
try:
|
||||||
|
# ensure we are at the start of the file before reading
|
||||||
|
fp.seek(0)
|
||||||
|
c = fp.read()
|
||||||
|
except IOError as err:
|
||||||
|
_log.writeLog("failed.", header=False)
|
||||||
|
_log.error(f"Couldn't open file for reading: {str(err)}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
_log.writeLog("ok.", header=False)
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
f_name = fp.name.split('/')[-1]
|
||||||
|
else:
|
||||||
|
_log.debug("fp is a string. The contents can be directly used.")
|
||||||
|
c = fp
|
||||||
|
|
||||||
|
# clean the file of any comments (specific to storing from user input)
|
||||||
|
if re.search(re_desc_tag, c):
|
||||||
|
_log.debug("Scrubbing bottle comments from string content.")
|
||||||
|
c = re.sub(re_desc_tag, "", c)
|
||||||
|
|
||||||
|
result = ensure_dir(soypak.db.rack_db_dir)
|
||||||
|
if result[0] != 0:
|
||||||
|
_log.error(f"There was an issue when ensuring directory (exit code: {result[0]}): {result[1]}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
new_file = join_path(soypak.db.rack_db_dir, f_name)
|
||||||
|
_log.debug(f"Opening new file to write bottle file to permanently: {new_file!r}")
|
||||||
|
try:
|
||||||
|
fp = open(new_file, 'w')
|
||||||
|
except IOError as err:
|
||||||
|
_log.error(f"There was an issue attempting to write to {new_file!r}: {str(err)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
fp.write(c)
|
||||||
|
s = fp.tell()
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
_log.debug("Successfully wrote %s bytes to file." % s)
|
||||||
|
return True
|
||||||
35
spec.bottle
Normal file
35
spec.bottle
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
-- Mandatory
|
||||||
|
Package: foo
|
||||||
|
Version: 0.1
|
||||||
|
Maintainer: Joe Bloggs
|
||||||
|
Filename: [uri|path-spec|ask|stdin]
|
||||||
|
Package-Repo: [url|local]
|
||||||
|
Description: <short description>
|
||||||
|
<long description>
|
||||||
|
|
||||||
|
-- Optional
|
||||||
|
Section: <section>
|
||||||
|
Priority: <priority>
|
||||||
|
*--
|
||||||
|
In Debian, the Section and Priority fields have a defined set of accepted values based on the Policy Manual. A list of
|
||||||
|
these values can be obtained from the latest version of the debian-policy package.
|
||||||
|
--*
|
||||||
|
Essential: <yes|no>
|
||||||
|
Architecture: <arch|all>
|
||||||
|
Origin: <name>
|
||||||
|
Bugs: <url>
|
||||||
|
Homepage: <url>
|
||||||
|
Tag: <tag list>
|
||||||
|
Depends: <package list>
|
||||||
|
Pre-Depends: <package list>
|
||||||
|
Recommends: <package list>
|
||||||
|
Suggests: <package list>
|
||||||
|
Breaks: <package list>
|
||||||
|
Conflicts: <package list>
|
||||||
|
Replaces: <package list>
|
||||||
|
Provides: <package list>
|
||||||
|
|
||||||
|
*--
|
||||||
|
Multiline comments.
|
||||||
|
ClosedLess (c) 2021-2024
|
||||||
|
--*
|
||||||
Loading…
Reference in New Issue
Block a user