# Copyright (c) 2022 David Timber # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import importlib import logging import os import sys from abc import ABC, abstractmethod from getopt import getopt import palhm from palhm.exceptions import InvalidConfigError class ProgConf: conf = "/etc/palhm/palhm.jsonc" cmd = None override_vl = None ctx = None def alloc_ctx (): ProgConf.ctx = palhm.setup_conf(palhm.load_conf(ProgConf.conf)) if not ProgConf.override_vl is None: ProgConf.ctx.l.setLevel(ProgConf.override_vl) def err_unknown_cmd (): sys.stderr.write( "Unknown command. Run '" + sys.executable + " -m palhm help' for usage.\n") exit(2) class Cmd (ABC): @abstractmethod def do_cmd (self): ... class ConfigCmd (Cmd): def __init__ (self, *args, **kwargs): pass def do_cmd (self): ProgConf.alloc_ctx() print(ProgConf.ctx) return 0 def print_help (): print( "Usage: " + sys.executable + " -m palhm config" + ''' Load and parse config. Print the structure to stdout.''') class RunCmd (Cmd): def __init__ (self, optlist, args): self.optlist = optlist self.args = args def do_cmd (self): ProgConf.alloc_ctx() if self.args and self.args[0]: # empty string as "default" task = self.args[0] else: task = palhm.DEFAULT.RUN_TASK.value ProgConf.ctx.task_map[task].run(ProgConf.ctx) return 0 def print_help (): print( "Usage: " + sys.executable + " -m palhm run [TASK]" + ''' Run a task in config. Run the "''' + palhm.DEFAULT.RUN_TASK.value + '''" task if [TASK] is not specified.''') class ModsCmd (Cmd): def __init__ (self, *args, **kwargs): pass def _walk_mods (self, path: str): def is_mod_dir (path: str) -> bool: try: for i in os.scandir(path): if i.name.startswith("__init__.py"): return True except NotADirectoryError: pass return False def is_mod_file (path: str) -> str: if not os.path.isfile(path): return None try: pos = path.rindex(".") if path[pos + 1:].startswith("py"): return os.path.basename(path[:pos]) except ValueError: pass for i in os.scandir(path): if i.name.startswith("_"): continue elif is_mod_dir(i.path): print(i.name) self._walk_mods(i.path) else: name = is_mod_file(i.path) if name: print(name) def do_cmd (self): for i in importlib.util.find_spec("palhm.mod").submodule_search_locations: self._walk_mods(i) return 0 def print_help (): print( "Usage: " + sys.executable + " -m palhm mods" + ''' Prints the available modules to stdout.''') class BootReportCmd (Cmd): def __init__ (self, *args, **kwargs): pass def do_cmd (self): ProgConf.alloc_ctx() if ProgConf.ctx.boot_report is None: raise InvalidConfigError("'boot-report' not configured") return ProgConf.ctx.boot_report.do_send(ProgConf.ctx) def print_help (): print( "Usage: " + sys.executable + " -m palhm boot-report" + ''' Send mail of boot report to recipients configured.''') class HelpCmd (Cmd): def __init__ (self, optlist, args): self.optlist = optlist self.args = args def do_cmd (self): if len(self.args) >= 2: if not args[0] in CmdMap: err_unknown_cmd() else: CmdMap[self.args[0]].print_help() else: HelpCmd.print_help() return 0 def print_help (): print( "Usage: " + sys.executable + " -m palhm [options] CMD [command options ...]" + ''' Options: -q Set the verbosity level to 0(CRITIAL). Overrides config -v Increase the verbosity level by 1. Overrides config -f FILE Load config from FILE instead of the hard-coded default Config: ''' + ProgConf.conf + ''' Commands: run run a task config load config and print the contents help [CMD] print this message and exit normally if [CMD] is not specified. Print usage of [CMD] otherwise mods list available modules boot-report mail boot report''') return 0 CmdMap = { "config": ConfigCmd, "run": RunCmd, "help": HelpCmd, "mods": ModsCmd, "boot-report": BootReportCmd } optlist, args = getopt(sys.argv[1:], "qvf:") optkset = set() for p in optlist: optkset.add(p[0]) if "-v" in optkset and "-q" in optkset: sys.stderr.write("Options -v and -q cannot not used together.\n") exit(2) if not args or not args[0] in CmdMap: err_unknown_cmd() for p in optlist: if p[0] == "-q": ProgConf.override_vl = logging.ERROR elif p[0] == "-v": if ProgConf.override_vl is None: ProgConf.override_vl = palhm.DEFAULT.VL.value - 10 else: ProgConf.override_vl -= 10 elif p[0] == "-f": ProgConf.conf = p[1] logging.basicConfig(format = "%(name)s %(message)s") ProgConf.cmd = CmdMap[args[0]](optlist, args) del args[0] exit(ProgConf.cmd.do_cmd())