adding some logic for repproducing received configurations

This commit is contained in:
Martin A. Brown 2016-02-21 17:48:46 -08:00
parent e7af014a73
commit 372ae734de
1 changed files with 90 additions and 35 deletions

View File

@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function
import os import os
import sys import sys
import functools
from argparse import ArgumentParser, ArgumentError, Namespace from argparse import ArgumentParser, ArgumentError, Namespace
from argparse import _UNRECOGNIZED_ARGS_ATTR from argparse import _UNRECOGNIZED_ARGS_ATTR
@ -13,10 +14,8 @@ import logging
logging.basicConfig(stream=sys.stderr, level=logging.WARNING) logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
logger = logging.getLogger() logger = logging.getLogger()
CFGSEP = '.' ENVSEP = NSSEP = '_' # -- underscore _
ENVSEP = '_' CLISEP = CFGSEP = '-' # -- dash -
CLISEP = '-'
MULTIVALUESEP = ',' MULTIVALUESEP = ','
try: try:
@ -66,8 +65,8 @@ def convert_multivalues(d, multivaluesep=MULTIVALUESEP):
return d return d
def cfg_to_dict(f, base=None, cfgsep=CFGSEP, clisep=CLISEP): def dict_from_cfg(f, base=None, cfgsep=CFGSEP, clisep=CLISEP):
'''read a configuration file; convert to CLI-parseable form '''read a configuration file, normalizing fields to CLI-parseable form
:param: f, a filename or file-like object (readable via :param: f, a filename or file-like object (readable via
ConfigParser.read() [filename] or ConfigParser.fp() [open file] ConfigParser.read() [filename] or ConfigParser.fp() [open file]
@ -89,7 +88,7 @@ def cfg_to_dict(f, base=None, cfgsep=CFGSEP, clisep=CLISEP):
When invoked as: When invoked as:
cfg_to_dict(f) # -- where f is the pathname or open filehandle dict_from_cfg(f) # -- where f is the pathname or open filehandle
This function will return a dict that looks like this: This function will return a dict that looks like this:
@ -116,8 +115,8 @@ def cfg_to_dict(f, base=None, cfgsep=CFGSEP, clisep=CLISEP):
return d return d
def env_to_dict(env=os.environ, base=None, envsep=ENVSEP, clisep=CLISEP): def dict_from_envdict(env=os.environ, base=None, envsep=ENVSEP, clisep=CLISEP):
'''read environment, return keys starting with 'base' '''read environment, normalizing all keys starting with 'base' to CLI form
:param: env, if nothing is supplied, os.environ :param: env, if nothing is supplied, os.environ
:param: base [optional], envar prefix filter selection criterion :param: base [optional], envar prefix filter selection criterion
@ -141,7 +140,7 @@ def env_to_dict(env=os.environ, base=None, envsep=ENVSEP, clisep=CLISEP):
When invoked as: When invoked as:
env_to_dict(os.environ, 'SSH') dict_from_envdict(os.environ, 'SSH')
This function will return a dict that looks like this: This function will return a dict that looks like this:
@ -150,7 +149,7 @@ def env_to_dict(env=os.environ, base=None, envsep=ENVSEP, clisep=CLISEP):
When invoked as: When invoked as:
env_to_dict(os.environ) dict_from_envdict(os.environ)
This function will return a dict that looks like this: This function will return a dict that looks like this:
@ -170,7 +169,15 @@ def env_to_dict(env=os.environ, base=None, envsep=ENVSEP, clisep=CLISEP):
return d return d
def strip_tag_from_key(base, d, clisep=CLISEP): def prepend_tag(base, d, sep=CLISEP):
newd = dict()
tag = ''.join((base, sep))
for k, v in d.items():
newd[''.join((tag, k.upper()))] = v
return newd
def strip_tag(base, d, clisep=CLISEP):
if not base: if not base:
return d return d
newd = dict() newd = dict()
@ -179,8 +186,9 @@ def strip_tag_from_key(base, d, clisep=CLISEP):
if oldk.startswith(tag): if oldk.startswith(tag):
newk = oldk[len(tag):] newk = oldk[len(tag):]
if newk in newd: if newk in newd:
logger.debug("Duplicate key found when stripping %s from %s", logger.debug("Duplicate key found when stripping %s from %s.",
tag, oldk) tag, oldk)
logger.info("strip_tag: returning unchanged dict().")
return d return d
newd[newk] = v newd[newk] = v
else: else:
@ -188,6 +196,17 @@ def strip_tag_from_key(base, d, clisep=CLISEP):
return newd return newd
def dict_from_ns(ns):
return vars(ns)
def ns_from_dict(d):
ns = Namespace()
for k, v in d.items():
setattr(ns, k, v)
return ns
def argv_from_env(args, tag, **kw): def argv_from_env(args, tag, **kw):
'''read a config file and produce argparse-compatible invocation '''read a config file and produce argparse-compatible invocation
@ -214,11 +233,11 @@ def argv_from_env(args, tag, **kw):
'--sourcedir', '/path/faq/docbook/', '--sourcedir', '/path/faq/docbook/',
'--sourcedir', '/path/howto/linuxdoc/'] '--sourcedir', '/path/howto/linuxdoc/']
''' '''
d = env_to_dict(args, base=tag.upper(), **kw) d = dict_from_envdict(args, base=tag.upper(), **kw)
listify_values = kw.get('convert_multivalues', convert_multivalues) listify_values = kw.get('convert_multivalues', convert_multivalues)
if listify_values is not None: if listify_values is not None:
d = listify_values(d) d = listify_values(d)
d = strip_tag_from_key(tag, d) d = strip_tag(tag, d)
d = dict_to_argv_longform(d) d = dict_to_argv_longform(d)
return d return d
@ -258,26 +277,15 @@ def argv_from_cfg(args, tag, **kw):
'--linuxdoc-sgml2html', '/usr/bin/sgml2html', '--linuxdoc-sgml2html', '/usr/bin/sgml2html',
'--docbook-xsltproc', '/usr/bin/xsltproc'] '--docbook-xsltproc', '/usr/bin/xsltproc']
''' '''
d = cfg_to_dict(args, base=tag, **kw) d = dict_from_cfg(args, base=tag, **kw)
listify_values = kw.get('convert_multivalues', convert_multivalues) listify_values = kw.get('convert_multivalues', convert_multivalues)
if listify_values is not None: if listify_values is not None:
d = listify_values(d, **kw) d = listify_values(d, **kw)
d = strip_tag_from_key(tag, d, **kw) d = strip_tag(tag, d, **kw)
d = dict_to_argv_longform(d, **kw) d = dict_to_argv_longform(d, **kw)
return d return d
def dict_from_namespace(ns):
return vars(ns)
def namespace_from_dict(d):
ns = Namespace()
for k, v in d.items():
setattr(ns, k, v)
return ns
class DefaultFreeArgumentParser(ArgumentParser): class DefaultFreeArgumentParser(ArgumentParser):
'''subclass of stock argparse.ArgumentParser; suppress default generation '''subclass of stock argparse.ArgumentParser; suppress default generation
@ -360,8 +368,9 @@ class CascadingConfig(object):
order = ['cli', 'environment', 'userconfig', 'systemconfig', 'defaults'] order = ['cli', 'environment', 'userconfig', 'systemconfig', 'defaults']
''' '''
order = ['cli', 'environment', 'userconfig', 'systemconfig', 'defaults'] order = ['cli', 'environment', 'userconfig', 'systemconfig', 'defaults']
mine = ['--dump_cli', '--dump_env', '--dump_cfg', '--debug_options']
def __init__(self, tag, parser, argv=sys.argv[1:], env=os.environ, def __init__(self, tag, argparser, argv=sys.argv[1:], env=os.environ,
configfile='configfile', order=order): configfile='configfile', order=order):
'''construct a CascadingConfig '''construct a CascadingConfig
@ -384,12 +393,17 @@ class CascadingConfig(object):
# -- a wee-bit hackish; but this is crucial to the proper functioning # -- a wee-bit hackish; but this is crucial to the proper functioning
# of CascadingConfig # of CascadingConfig
# #
assert hasattr(parser, 'parse_known_args_no_defaults') assert hasattr(argparser, 'parse_known_args_no_defaults')
for opt in self.mine:
synonym = opt.replace(NSSEP, CLISEP)
argparser.add_argument(opt, synonym, action='store_true')
self.tag = tag
self.order = order self.order = order
self.parser = parser.parse_known_args_no_defaults self.argparser = argparser
self.parser = argparser.parse_known_args_no_defaults
self.defaults = parser.parse_args([]) # -- "compiled-in" defaults self.defaults = argparser.parse_args([]) # -- "compiled-in" defaults
self.cli, _ = self.parser(argv) self.cli, _ = self.parser(argv)
self.environment, _ = self.parser(argv_from_env(env, tag)) self.environment, _ = self.parser(argv_from_env(env, tag))
@ -421,6 +435,46 @@ class CascadingConfig(object):
self.resolve() self.resolve()
# -- clean up after ourselves (and report in, if asked)
#
diagfunc = False
for opt in self.mine:
opt = opt.lstrip(CLISEP)
if getattr(self.config, opt, False):
diagfunc = getattr(self, opt)
delattr(self.config, opt)
if diagfunc:
sys.exit(diagfunc())
def dump_env(self):
d = dict_from_ns(self.config)
d = prepend_tag(self.tag.upper(), d, sep=ENVSEP)
for k, v in d.items():
if isinstance(v, (list, tuple)):
v = MULTIVALUESEP.join(v)
print('{}={}'.format(k, v))
return 0
def dump_cfg(self):
d = dict_from_ns(self.config)
return 0
def dump_cli(self):
d = dict_from_ns(self.config)
cli = list()
for k, v in d.items():
k = ''.join(('--', k.replace(NSSEP, CLISEP)))
if isinstance(v, (list, tuple)):
for val in v:
cli.extend((k, str(val)))
else:
cli.extend((k, str(v)))
print(' '.join(cli))
return 0
def debug_options(self):
return 0
def resolve(self, order=None): def resolve(self, order=None):
if order is None: if order is None:
order = self.order order = self.order
@ -436,7 +490,7 @@ class CascadingConfig(object):
logger.info("Source %s: replacing %s=%s with %s=%s", logger.info("Source %s: replacing %s=%s with %s=%s",
sourcename, name, oldval, name, newval) sourcename, name, oldval, name, newval)
setattr(config, name, newval) setattr(config, name, newval)
return config self.config = config
def sample(args): def sample(args):
@ -451,9 +505,10 @@ def sample(args):
type=str) type=str)
parser.add_argument('--configfile', '--cfg', '--config-file', type=str, parser.add_argument('--configfile', '--cfg', '--config-file', type=str,
default="/home/mabrown/tmp/ldptool.cfg") default="/home/mabrown/tmp/ldptool.cfg")
uniconf = CascadingConfig(tag, parser, sys.argv[1:]) cc = CascadingConfig(tag, parser, sys.argv[1:])
config = cc.config
import pprint import pprint
pprint.pprint(uniconf.resolve()) pprint.pprint(dict_from_ns(config))
if __name__ == '__main__': if __name__ == '__main__':