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):
|
def set_verbosity(logger, handler, verbosity):
|
||||||
|
"""set logging default verbosity level and format"""
|
||||||
|
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
log_level = logging.DEBUG
|
log_level = logging.DEBUG
|
||||||
log_format = "[%(asctime)s] %(levelname)s @ %(filename)s:%(funcName)s:%(lineno)d: %(message)s"
|
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():
|
def main():
|
||||||
|
# add a new handler (needed to set the level)
|
||||||
log_handler = logging.StreamHandler()
|
log_handler = logging.StreamHandler()
|
||||||
logging.getLogger().addHandler(log_handler)
|
logging.getLogger().addHandler(log_handler)
|
||||||
set_verbosity(logging.getLogger(), log_handler, kiwi.verbosity())
|
set_verbosity(logging.getLogger(), log_handler, kiwi.verbosity())
|
||||||
|
|
||||||
|
# run the app
|
||||||
kiwi.run()
|
kiwi.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,13 @@ from .runner import Runner
|
||||||
|
|
||||||
|
|
||||||
def verbosity():
|
def verbosity():
|
||||||
|
# ensure singleton is instantiated: runs subcommand setup routines
|
||||||
_ = Runner()
|
_ = Runner()
|
||||||
return Parser().get_args().verbosity
|
return Parser().get_args().verbosity
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
# pass down
|
||||||
Runner().run()
|
Runner().run()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# system
|
# system
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# location of "src" directory to use
|
||||||
KIWI_ROOT = os.getenv('KIWI_ROOT', ".")
|
KIWI_ROOT = os.getenv('KIWI_ROOT', ".")
|
||||||
|
# default name of kiwi-config file
|
||||||
KIWI_CONF_NAME = os.getenv('KIWI_CONF_NAME', "kiwi.yml")
|
KIWI_CONF_NAME = os.getenv('KIWI_CONF_NAME', "kiwi.yml")
|
||||||
|
|
|
@ -11,15 +11,25 @@ from ._constants import KIWI_ROOT, KIWI_CONF_NAME
|
||||||
###########
|
###########
|
||||||
# CONSTANTS
|
# CONSTANTS
|
||||||
|
|
||||||
|
# text files inside kiwi-config "src" directory
|
||||||
HEADER_KIWI_CONF_NAME = f"{KIWI_ROOT}/kiwi_header.yml"
|
HEADER_KIWI_CONF_NAME = f"{KIWI_ROOT}/kiwi_header.yml"
|
||||||
DEFAULT_KIWI_CONF_NAME = f"{KIWI_ROOT}/kiwi_default.yml"
|
DEFAULT_KIWI_CONF_NAME = f"{KIWI_ROOT}/kiwi_default.yml"
|
||||||
VERSION_TAG_NAME = f"{KIWI_ROOT}/version-tag"
|
VERSION_TAG_NAME = f"{KIWI_ROOT}/version-tag"
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
"""represents a kiwi.yml"""
|
||||||
|
|
||||||
__yml_content = {}
|
__yml_content = {}
|
||||||
|
|
||||||
def __key_resolve(self, key):
|
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'
|
# "a:b:c" => path = ['a', 'b'], key = 'c'
|
||||||
path = key.split(':')
|
path = key.split(':')
|
||||||
path, key = path[:-1], path[-1]
|
path, key = path[:-1], path[-1]
|
||||||
|
@ -32,14 +42,20 @@ class Config:
|
||||||
return container, key
|
return container, key
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
"""array-like read access to __yml_content"""
|
||||||
|
|
||||||
container, key = self.__key_resolve(key)
|
container, key = self.__key_resolve(key)
|
||||||
return container[key]
|
return container[key]
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
|
"""array-like write access to __yml_content"""
|
||||||
|
|
||||||
container, key = self.__key_resolve(key)
|
container, key = self.__key_resolve(key)
|
||||||
container[key] = value
|
container[key] = value
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
"""dump into textual representation"""
|
||||||
|
|
||||||
# dump yml content
|
# dump yml content
|
||||||
yml_string = yaml.dump(self.__yml_content, default_flow_style=False, sort_keys=False)
|
yml_string = yaml.dump(self.__yml_content, default_flow_style=False, sort_keys=False)
|
||||||
|
|
||||||
|
@ -53,44 +69,68 @@ class Config:
|
||||||
return yml_string
|
return yml_string
|
||||||
|
|
||||||
def _update_from_file(self, filename):
|
def _update_from_file(self, filename):
|
||||||
|
"""return a copy updated using a kiwi.yml file"""
|
||||||
|
|
||||||
with open(filename, 'r') as stream:
|
with open(filename, 'r') as stream:
|
||||||
try:
|
try:
|
||||||
self.__yml_content.update(yaml.safe_load(stream))
|
# create copy
|
||||||
except yaml.YAMLError as exc:
|
|
||||||
logging.error(exc)
|
|
||||||
|
|
||||||
def clone(self):
|
|
||||||
result = Config()
|
result = Config()
|
||||||
result.__yml_content = copy.deepcopy(self.__yml_content)
|
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
|
return result
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
logging.error(exc)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
"""save current yml representation in current directory's kiwi.yml"""
|
||||||
|
|
||||||
with open(KIWI_CONF_NAME, 'w') as stream:
|
with open(KIWI_CONF_NAME, 'w') as stream:
|
||||||
stream.write(str(self))
|
stream.write(str(self))
|
||||||
|
|
||||||
|
|
||||||
class DefaultConfig(Config):
|
class DefaultConfig(Config):
|
||||||
|
"""Singleton: The default kiwi.yml file"""
|
||||||
|
|
||||||
__instance = None
|
__instance = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls):
|
def get(cls):
|
||||||
if cls.__instance is None:
|
if cls.__instance is None:
|
||||||
cls.__instance = cls()
|
# create singleton
|
||||||
cls.__instance._update_from_file(DEFAULT_KIWI_CONF_NAME)
|
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:
|
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
|
return cls.__instance
|
||||||
|
|
||||||
|
|
||||||
class LoadedConfig(Config):
|
class LoadedConfig(Config):
|
||||||
|
"""Singleton collection: kiwi.yml files by path"""
|
||||||
|
|
||||||
|
__instances = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls):
|
def get(cls):
|
||||||
result = DefaultConfig.get().clone()
|
cwd = os.getcwd()
|
||||||
|
|
||||||
if os.path.isfile(KIWI_CONF_NAME):
|
if cwd not in LoadedConfig.__instances:
|
||||||
result._update_from_file(KIWI_CONF_NAME)
|
# 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:
|
class Parser:
|
||||||
|
"""Singleton: Main CLI arguments parser"""
|
||||||
|
|
||||||
class __Parser:
|
class __Parser:
|
||||||
|
"""Singleton type"""
|
||||||
|
|
||||||
|
# argparse objects
|
||||||
__parser = None
|
__parser = None
|
||||||
__subparsers = None
|
__subparsers = None
|
||||||
__args = None
|
__args = None
|
||||||
|
|
||||||
def __init__(self):
|
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(
|
self.__parser.add_argument(
|
||||||
'-v', '--verbosity',
|
'-v', '--verbosity',
|
||||||
action='count', default=0
|
action='count', default=0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# attach subparsers
|
||||||
self.__subparsers = self.__parser.add_subparsers()
|
self.__subparsers = self.__parser.add_subparsers()
|
||||||
self.__subparsers.required = True
|
self.__subparsers.required = True
|
||||||
self.__subparsers.dest = 'command'
|
self.__subparsers.dest = 'command'
|
||||||
|
|
||||||
def get_parser(self):
|
|
||||||
return self.__parser
|
|
||||||
|
|
||||||
def get_subparsers(self):
|
def get_subparsers(self):
|
||||||
return self.__subparsers
|
return self.__subparsers
|
||||||
|
|
||||||
def get_args(self):
|
def get_args(self):
|
||||||
if self.__args is None:
|
if self.__args is None:
|
||||||
|
# parse args if needed
|
||||||
self.__args = self.__parser.parse_args()
|
self.__args = self.__parser.parse_args()
|
||||||
|
|
||||||
return self.__args
|
return self.__args
|
||||||
|
@ -36,7 +44,9 @@ class Parser:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if Parser.__instance is None:
|
if Parser.__instance is None:
|
||||||
|
# create singleton
|
||||||
Parser.__instance = Parser.__Parser()
|
Parser.__instance = Parser.__Parser()
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
|
"""Inner singleton direct access"""
|
||||||
return getattr(self.__instance, item)
|
return getattr(self.__instance, item)
|
|
@ -9,6 +9,7 @@ from .subcommands import *
|
||||||
###########
|
###########
|
||||||
# CONSTANTS
|
# CONSTANTS
|
||||||
|
|
||||||
|
# all available subcommands
|
||||||
SUBCOMMANDS = [
|
SUBCOMMANDS = [
|
||||||
InitCommand,
|
InitCommand,
|
||||||
ShowCommand,
|
ShowCommand,
|
||||||
|
@ -17,30 +18,43 @@ SUBCOMMANDS = [
|
||||||
|
|
||||||
|
|
||||||
class Runner:
|
class Runner:
|
||||||
|
"""Singleton: Subcommands setup and run"""
|
||||||
|
|
||||||
class __Runner:
|
class __Runner:
|
||||||
|
"""Singleton type"""
|
||||||
|
|
||||||
|
__parser = None
|
||||||
__commands = []
|
__commands = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# setup all subcommands
|
||||||
for cmd in SUBCOMMANDS:
|
for cmd in SUBCOMMANDS:
|
||||||
self.__commands.append(cmd())
|
self.__commands.append(cmd())
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
config = LoadedConfig.get()
|
"""run the desired subcommand"""
|
||||||
|
|
||||||
args = Parser().get_args()
|
args = Parser().get_args()
|
||||||
|
|
||||||
for cmd in self.__commands:
|
for cmd in self.__commands:
|
||||||
if str(cmd) == args.command:
|
if str(cmd) == args.command:
|
||||||
|
# command found
|
||||||
logging.debug(f"Running '{cmd}' with args: {args}")
|
logging.debug(f"Running '{cmd}' with args: {args}")
|
||||||
cmd.run(config, args)
|
cmd.run(LoadedConfig.get(), args)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# command not found
|
||||||
|
logging.error(f"kiwi command '{args.command}' unknown")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
__instance = None
|
__instance = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if Runner.__instance is None:
|
if Runner.__instance is None:
|
||||||
|
# create singleton
|
||||||
Runner.__instance = Runner.__Runner()
|
Runner.__instance = Runner.__Runner()
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
|
"""Inner singleton direct access"""
|
||||||
|
|
||||||
return getattr(self.__instance, item)
|
return getattr(self.__instance, item)
|
||||||
|
|
|
@ -3,15 +3,23 @@ from ..parser import Parser
|
||||||
|
|
||||||
|
|
||||||
class SubCommand:
|
class SubCommand:
|
||||||
|
"""represents kiwi [anything] command"""
|
||||||
|
|
||||||
|
# actual command string
|
||||||
__name = None
|
__name = None
|
||||||
|
# command parser
|
||||||
_sub_parser = None
|
_sub_parser = None
|
||||||
|
|
||||||
def __init__(self, name, **kwargs):
|
def __init__(self, name, **kwargs):
|
||||||
self.__name = name
|
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):
|
def __str__(self):
|
||||||
return self.__name
|
return self.__name
|
||||||
|
|
||||||
def run(self, config, args):
|
def run(self, config, args):
|
||||||
|
"""actually run command with this dir's config and parsed CLI args"""
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -10,6 +10,8 @@ from ._subcommand import SubCommand
|
||||||
|
|
||||||
|
|
||||||
def user_input(config, key, prompt):
|
def user_input(config, key, prompt):
|
||||||
|
"""query user for new config value"""
|
||||||
|
|
||||||
# prompt user as per argument
|
# prompt user as per argument
|
||||||
try:
|
try:
|
||||||
result = input("{} [{}] ".format(prompt, config[key])).strip()
|
result = input("{} [{}] ".format(prompt, config[key])).strip()
|
||||||
|
@ -23,12 +25,15 @@ def user_input(config, key, prompt):
|
||||||
|
|
||||||
|
|
||||||
class InitCommand(SubCommand):
|
class InitCommand(SubCommand):
|
||||||
|
"""kiwi init"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'init',
|
'init',
|
||||||
description="Create a new kiwi-config instance"
|
description="Create a new kiwi-config instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -f switch: Initialize with default config
|
||||||
self._sub_parser.add_argument(
|
self._sub_parser.add_argument(
|
||||||
'-f', '--force',
|
'-f', '--force',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
|
@ -36,7 +41,7 @@ class InitCommand(SubCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, config, args):
|
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):
|
if args.force and os.path.isfile(KIWI_CONF_NAME):
|
||||||
from ..config import DefaultConfig
|
from ..config import DefaultConfig
|
||||||
|
|
Loading…
Reference in a new issue