diff --git a/breadpack.py b/breadpack.py new file mode 100755 index 0000000..a657c4a --- /dev/null +++ b/breadpack.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +from datetime import datetime, timezone +from pathlib import Path +from typing import Optional +import argparse +import logging +import os +import pwd + +logging.basicConfig( + level=logging.INFO, + format='%(levelname)s: %(message)s', +) +logger = logging.getLogger(__name__) + + +def _get_username(): + return pwd.getpwuid(os.getuid()).pw_name + + +# The recfile descriptor for package requests. +# Used if Breadpack generates the file from scratch. +REQUEST_DESCRIPTOR = """ +%rec: PackageRequest +%type: Date date +%type: Processed bool +%type: User line +%type: Package line +%mandatory: Package +%allowed: Date User Processed Package Comment +%sort: Processed Date +%doc: Package requests made via breadpack +""".strip() + + +def request_subcommand( + requestsfile: Path, + package_name: str, + comment: Optional[str] = None, + **kwargs): + if not requestsfile.exists(): + logger.warning(f'Creating file {requestsfile}') + with requestsfile.open('w') as f: + f.write(REQUEST_DESCRIPTOR) + + data = { + 'Date': datetime.now(timezone.utc).isoformat(), + 'User': _get_username(), + 'Processed': 'no', + 'Package': package_name, + } + if comment: + data['Comment'] = comment + + with requestsfile.open('a') as f: + f.write('\n\n' + '\n'.join([f'{k}: {v}' for k, v in data.items()])) + + logger.info(f'Your request for {package_name!r} has been sent!') + + +def list_subcommand(json: bool = False, upgradable: bool = False, **kwargs): + raise NotImplementedError + + +def lint_subcommand(file, **kwargs): + raise NotImplementedError + + +def check_subcommand(json: bool = False, save: bool = True, **kwargs): + raise NotImplementedError + + +def lock_subcommand(**kwargs): + raise NotImplementedError + + +def main(): + parser = argparse.ArgumentParser( + description='Breadpunk.club meta-package manager' + ) + parser.add_argument( + '--file', + type=Path, + help='Path to the breadpack.json file.', + default=Path('/bread/breadpack.json'), + ) + parser.add_argument( + '--lockfile', + type=Path, + help='Path to the breadpack-lock.json file.', + default=Path('/bread/breadpack-lock.json'), + ) + parser.add_argument( + '--requestsfile', + type=Path, + help='Path to the breadpack-requests.rec file.', + default=Path('/bread/breadpack-requests.rec'), + ) + + def nocommand(**kwargs): + parser.error('A subcommand is required.') + + parser.set_defaults(func=nocommand) + subparsers = parser.add_subparsers( + title='subcommands', + ) + + request_parser = subparsers.add_parser( + 'request', + help='Request for a new package to be installed.', + ) + request_parser.add_argument( + 'package_name', + help='Name of the requested package.', + ) + request_parser.add_argument( + '-c', '--comment', + help='Optional comment to add notes ' + 'for the admin processing your request.', + default=None, + ) + request_parser.set_defaults(func=request_subcommand) + + list_parser = subparsers.add_parser( + 'list', + help='List installed packages.', + ) + list_parser.add_argument( + '--json', + help='Use JSON output.', + action='store_true', + default=False, + ) + list_parser.add_argument( + '--upgradable', + help='Only list packages with available updates.', + action='store_true', + default=False, + ) + list_parser.set_defaults(func=list_subcommand) + + lint_parser = subparsers.add_parser( + 'lint', + help='Check the syntax of the packages file and lockfile.', + ) + lint_parser.set_defaults(func=lint_subcommand) + + lock_parser = subparsers.add_parser( + 'lock', + help='Regenerate the lockfile from scratch.', + ) + lock_parser.set_defaults(func=lock_subcommand) + + check_parser = subparsers.add_parser( + 'check', + help='Check for available updates.', + ) + check_parser.add_argument( + '--json', + help='Use JSON output.', + action='store_true', + default=False, + ) + check_parser.add_argument( + '--save', + help='Update the lockfile. This is the default.', + action='store_true', + default=True, + ) + check_parser.add_argument( + '--no-save', + help='Do not update the lockfile.', + action='store_false', + dest='save', + ) + + args = vars(parser.parse_args()) + args.pop('func', nocommand)(**args) + + +if __name__ == '__main__': + main()