doc
This commit is contained in:
parent
1bd547587b
commit
0b2b9a0a75
8 changed files with 108 additions and 23 deletions
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
"""Inner singleton direct access"""
|
||||
return getattr(self.__instance, item)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue