From 0b2b9a0a75ca151277c0adf3441f0ecb4d7a59b4 Mon Sep 17 00:00:00 2001 From: ldericher Date: Thu, 13 Aug 2020 10:48:01 +0200 Subject: [PATCH] doc --- src/kiwi-config.py | 4 ++ src/kiwi/__init__.py | 2 + src/kiwi/_constants.py | 2 + src/kiwi/config.py | 68 +++++++++++++++++++++++------ src/kiwi/parser.py | 20 ++++++--- src/kiwi/runner.py | 18 +++++++- src/kiwi/subcommands/_subcommand.py | 10 ++++- src/kiwi/subcommands/init.py | 7 ++- 8 files changed, 108 insertions(+), 23 deletions(-) diff --git a/src/kiwi-config.py b/src/kiwi-config.py index 0b47176..4c5acad 100755 --- a/src/kiwi-config.py +++ b/src/kiwi-config.py @@ -8,6 +8,8 @@ import kiwi def set_verbosity(logger, handler, verbosity): + """set logging default verbosity level and format""" + if verbosity >= 2: log_level = logging.DEBUG log_format = "[%(asctime)s] %(levelname)s @ %(filename)s:%(funcName)s:%(lineno)d: %(message)s" @@ -23,10 +25,12 @@ def set_verbosity(logger, handler, verbosity): def main(): + # add a new handler (needed to set the level) log_handler = logging.StreamHandler() logging.getLogger().addHandler(log_handler) set_verbosity(logging.getLogger(), log_handler, kiwi.verbosity()) + # run the app kiwi.run() diff --git a/src/kiwi/__init__.py b/src/kiwi/__init__.py index f53a9c1..8a4a6db 100644 --- a/src/kiwi/__init__.py +++ b/src/kiwi/__init__.py @@ -4,11 +4,13 @@ from .runner import Runner def verbosity(): + # ensure singleton is instantiated: runs subcommand setup routines _ = Runner() return Parser().get_args().verbosity def run(): + # pass down Runner().run() diff --git a/src/kiwi/_constants.py b/src/kiwi/_constants.py index 8524233..836bedc 100644 --- a/src/kiwi/_constants.py +++ b/src/kiwi/_constants.py @@ -1,5 +1,7 @@ # system import os +# location of "src" directory to use KIWI_ROOT = os.getenv('KIWI_ROOT', ".") +# default name of kiwi-config file KIWI_CONF_NAME = os.getenv('KIWI_CONF_NAME', "kiwi.yml") diff --git a/src/kiwi/config.py b/src/kiwi/config.py index c238766..dcbe94a 100644 --- a/src/kiwi/config.py +++ b/src/kiwi/config.py @@ -11,15 +11,25 @@ from ._constants import KIWI_ROOT, KIWI_CONF_NAME ########### # CONSTANTS +# text files inside kiwi-config "src" directory HEADER_KIWI_CONF_NAME = f"{KIWI_ROOT}/kiwi_header.yml" DEFAULT_KIWI_CONF_NAME = f"{KIWI_ROOT}/kiwi_default.yml" VERSION_TAG_NAME = f"{KIWI_ROOT}/version-tag" class Config: + """represents a kiwi.yml""" + __yml_content = {} def __key_resolve(self, key): + """ + Resolve nested dictionaries + + If __yml_content is {'a': {'b': {'c': "val"}}} and key is 'a:b:c', + this returns a single dict {'c': "val"} and the direct key 'c' + """ + # "a:b:c" => path = ['a', 'b'], key = 'c' path = key.split(':') path, key = path[:-1], path[-1] @@ -32,14 +42,20 @@ class Config: return container, key def __getitem__(self, key): + """array-like read access to __yml_content""" + container, key = self.__key_resolve(key) return container[key] def __setitem__(self, key, value): + """array-like write access to __yml_content""" + container, key = self.__key_resolve(key) container[key] = value def __str__(self): + """dump into textual representation""" + # dump yml content yml_string = yaml.dump(self.__yml_content, default_flow_style=False, sort_keys=False) @@ -53,44 +69,68 @@ class Config: return yml_string def _update_from_file(self, filename): + """return a copy updated using a kiwi.yml file""" + with open(filename, 'r') as stream: try: - self.__yml_content.update(yaml.safe_load(stream)) + # create copy + result = Config() + result.__yml_content = copy.deepcopy(self.__yml_content) + + # read file + logging.debug(f"Reading '{filename}' into '{id(result.__yml_content)}'") + result.__yml_content.update(yaml.safe_load(stream)) + + return result except yaml.YAMLError as exc: logging.error(exc) - def clone(self): - result = Config() - result.__yml_content = copy.deepcopy(self.__yml_content) - - return result - def save(self): + """save current yml representation in current directory's kiwi.yml""" + with open(KIWI_CONF_NAME, 'w') as stream: stream.write(str(self)) class DefaultConfig(Config): + """Singleton: The default kiwi.yml file""" + __instance = None @classmethod def get(cls): if cls.__instance is None: - cls.__instance = cls() - cls.__instance._update_from_file(DEFAULT_KIWI_CONF_NAME) + # create singleton + cls.__instance = cls()._update_from_file(DEFAULT_KIWI_CONF_NAME) + # add version data from separate file (keeps default config cleaner) with open(VERSION_TAG_NAME, 'r') as stream: - cls.__instance["version"] = stream.read().strip() + cls.__instance['version'] = stream.read().strip() + # return singleton return cls.__instance class LoadedConfig(Config): + """Singleton collection: kiwi.yml files by path""" + + __instances = {} + @classmethod def get(cls): - result = DefaultConfig.get().clone() + cwd = os.getcwd() - if os.path.isfile(KIWI_CONF_NAME): - result._update_from_file(KIWI_CONF_NAME) + if cwd not in LoadedConfig.__instances: + # create singleton for new path + result = DefaultConfig.get() - return result + # update with current dir's kiwi.yml + try: + result = result._update_from_file(KIWI_CONF_NAME) + except FileNotFoundError: + logging.info(f"No '{KIWI_CONF_NAME}' found at '{cwd}'. Using defaults.") + + LoadedConfig.__instances[cwd] = result + + # return singleton + return LoadedConfig.__instances[cwd] diff --git a/src/kiwi/parser.py b/src/kiwi/parser.py index 497e520..2814c4c 100644 --- a/src/kiwi/parser.py +++ b/src/kiwi/parser.py @@ -3,31 +3,39 @@ import argparse class Parser: + """Singleton: Main CLI arguments parser""" + class __Parser: + """Singleton type""" + + # argparse objects __parser = None __subparsers = None __args = None def __init__(self): - self.__parser = argparse.ArgumentParser(description='kiwi-config') + # create main parsers + self.__parser = argparse.ArgumentParser( + description='kiwi-config' + ) + # main arguments self.__parser.add_argument( '-v', '--verbosity', action='count', default=0 ) + # attach subparsers self.__subparsers = self.__parser.add_subparsers() self.__subparsers.required = True self.__subparsers.dest = 'command' - def get_parser(self): - return self.__parser - def get_subparsers(self): return self.__subparsers def get_args(self): if self.__args is None: + # parse args if needed self.__args = self.__parser.parse_args() return self.__args @@ -36,7 +44,9 @@ class Parser: def __init__(self): if Parser.__instance is None: + # create singleton Parser.__instance = Parser.__Parser() def __getattr__(self, item): - return getattr(self.__instance, item) \ No newline at end of file + """Inner singleton direct access""" + return getattr(self.__instance, item) diff --git a/src/kiwi/runner.py b/src/kiwi/runner.py index 37b08da..1561c30 100644 --- a/src/kiwi/runner.py +++ b/src/kiwi/runner.py @@ -9,6 +9,7 @@ from .subcommands import * ########### # CONSTANTS +# all available subcommands SUBCOMMANDS = [ InitCommand, ShowCommand, @@ -17,30 +18,43 @@ SUBCOMMANDS = [ class Runner: + """Singleton: Subcommands setup and run""" + class __Runner: + """Singleton type""" + + __parser = None __commands = [] def __init__(self): + # setup all subcommands for cmd in SUBCOMMANDS: self.__commands.append(cmd()) def run(self): - config = LoadedConfig.get() + """run the desired subcommand""" + args = Parser().get_args() for cmd in self.__commands: if str(cmd) == args.command: + # command found logging.debug(f"Running '{cmd}' with args: {args}") - cmd.run(config, args) + cmd.run(LoadedConfig.get(), args) return True + # command not found + logging.error(f"kiwi command '{args.command}' unknown") return False __instance = None def __init__(self): if Runner.__instance is None: + # create singleton Runner.__instance = Runner.__Runner() def __getattr__(self, item): + """Inner singleton direct access""" + return getattr(self.__instance, item) diff --git a/src/kiwi/subcommands/_subcommand.py b/src/kiwi/subcommands/_subcommand.py index f8d6dd7..ef3833f 100644 --- a/src/kiwi/subcommands/_subcommand.py +++ b/src/kiwi/subcommands/_subcommand.py @@ -3,15 +3,23 @@ from ..parser import Parser class SubCommand: + """represents kiwi [anything] command""" + + # actual command string __name = None + # command parser _sub_parser = None def __init__(self, name, **kwargs): self.__name = name - self._sub_parser = Parser().get_subparsers().add_parser(name, **kwargs) + self._sub_parser = Parser().get_subparsers().add_parser( + name, + **kwargs + ) def __str__(self): return self.__name def run(self, config, args): + """actually run command with this dir's config and parsed CLI args""" pass diff --git a/src/kiwi/subcommands/init.py b/src/kiwi/subcommands/init.py index d1545c5..8d2c2b1 100644 --- a/src/kiwi/subcommands/init.py +++ b/src/kiwi/subcommands/init.py @@ -10,6 +10,8 @@ from ._subcommand import SubCommand def user_input(config, key, prompt): + """query user for new config value""" + # prompt user as per argument try: result = input("{} [{}] ".format(prompt, config[key])).strip() @@ -23,12 +25,15 @@ def user_input(config, key, prompt): class InitCommand(SubCommand): + """kiwi init""" + def __init__(self): super().__init__( 'init', description="Create a new kiwi-config instance" ) + # -f switch: Initialize with default config self._sub_parser.add_argument( '-f', '--force', action='store_true', @@ -36,7 +41,7 @@ class InitCommand(SubCommand): ) def run(self, config, args): - logging.info(f"Initializing kiwi-config instance in '{os.getcwd()}'") + logging.info(f"Initializing '{KIWI_CONF_NAME}' in '{os.getcwd()}'") if args.force and os.path.isfile(KIWI_CONF_NAME): from ..config import DefaultConfig