This commit is contained in:
Jörn-Michael Miehe 2020-08-13 10:48:01 +02:00
parent 1bd547587b
commit 0b2b9a0a75
8 changed files with 108 additions and 23 deletions

View file

@ -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()

View file

@ -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()

View file

@ -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")

View file

@ -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))
except yaml.YAMLError as exc:
logging.error(exc)
def clone(self):
# 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 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]

View file

@ -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):
"""Inner singleton direct access"""
return getattr(self.__instance, item)

View file

@ -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)

View file

@ -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

View file

@ -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