confini

Parse and merge multiple ini files in python3
git clone git://git.defalsify.org/python-confini.git
Log | Files | Refs | README | LICENSE

commit edc8d9e5d761e61c5e4e747f7883bb06c183bd50
parent 346f6c8f15645ed89efc5deca61b138ebca8862d
Author: nolash <dev@holbrook.no>
Date:   Wed,  3 Nov 2021 05:42:21 +0100

Add doc capability to dumper

Diffstat:
Mconfini/config.py | 59++++++++++++++++++++++++++++++++++++++++++++++-------------
Mconfini/doc.py | 31++++++++++++++++++++++---------
Mconfini/env.py | 20++++++++++++++++++++
Mconfini/export.py | 15++++++++++++---
Mconfini/runnable/dump.py | 32++++++++++++++++++++++----------
5 files changed, 122 insertions(+), 35 deletions(-)

diff --git a/confini/config.py b/confini/config.py @@ -27,7 +27,7 @@ class Config: default_censor_string = '***' - def __init__(self, default_dir, env_prefix=None, override_dirs=[]): + def __init__(self, default_dir, env_prefix=None, override_dirs=[], skip_doc=False): self.parser = configparser.ConfigParser(strict=True) self.__target_tmpdir = None if default_dir == None: @@ -44,6 +44,8 @@ class Config: if not os.path.isdir(d): raise OSError('{} is not a directory'.format(override_dirs)) self.dirs.append(os.path.realpath(d)) + self.skip_doc = skip_doc + self.doc = None self.required = {} self.censored = {} self.store = {} @@ -60,9 +62,13 @@ class Config: self.dirs = [self.__target_tmpdir.name] for i, d in enumerate(dirs): for filename_in in os.listdir(d): - if re.match(r'.+\.ini$', filename_in) == None: + filename_out = None + if filename_in == '.confini': + filename_out = filename_in + elif re.match(r'.+\.ini$', filename_in) == None: continue - filename_out = '{}_{}'.format(i, filename_in) + else: + filename_out = '{}_{}'.format(i, filename_in) in_filepath = os.path.join(d, filename_in) out_filepath = os.path.join(self.dirs[0], filename_out) fr = open(in_filepath, 'rb') @@ -114,7 +120,7 @@ class Config: return True - def _sections_override(self, dct, dct_description): + def __sections_override(self, dct, dct_description): for s in self.parser.sections(): for k in self.parser[s]: cn = to_constant_name(k, s) @@ -146,18 +152,27 @@ class Config: self.src_dirs[k] = d - def process(self, set_as_current=False): - """Concatenates all .ini files in the config directory attribute and parses them to memory - """ - tmp_dir = tempfile.mkdtemp() - logg.debug('using tmp processing dir {}'.format(tmp_dir)) + def __process_doc_(self, d): + if self.skip_doc: + return + doc_fp = os.path.join(d, '.confini') + if self.doc == None: + from confini.doc import ConfigDoc + self.doc = ConfigDoc() + try: + self.doc.process(doc_fp) + except FileNotFoundError: + pass + + + def __collect_dir(self, out_dir): for i, d in enumerate(self.dirs): d = os.path.realpath(d) if i == 0: d_label = 'default' else: d_label = 'override #' + str(i) - tmp_out_dir = os.path.join(tmp_dir, str(i)) + tmp_out_dir = os.path.join(out_dir, str(i)) os.makedirs(tmp_out_dir) logg.debug('processing dir {} ({})'.format(d, d_label)) tmp_out = open(os.path.join(tmp_out_dir, 'config.ini'), 'ab') @@ -174,13 +189,18 @@ class Config: tmp_out.write(data) f.close() tmp_out.close() - d = os.listdir(tmp_dir) + + self.__process_doc_(d) + + + def __process_schema_dir(self, in_dir): + d = os.listdir(in_dir) d.sort() c = 0 # TODO: this will fail of sections/options are repeated. should first use individual parser instances to flatten to single file (perhaps in collect_from_dirs already) for i, tmp_config_dir in enumerate(d): - tmp_config_dir = os.path.join(tmp_dir, tmp_config_dir) + tmp_config_dir = os.path.join(in_dir, tmp_config_dir) for tmp_file in os.listdir(os.path.join(tmp_config_dir)): tmp_config_file_path = os.path.join(tmp_config_dir, tmp_file) if c == 0: @@ -209,7 +229,20 @@ class Config: self.add(v, k, exists_ok=True) self.set_dir(k, self.dirs[i]) c += 1 - self._sections_override(os.environ, 'environment variable') + + + def process(self, set_as_current=False): + """Concatenates all .ini files in the config directory attribute and parses them to memory + """ + tmp_dir = tempfile.mkdtemp() + logg.debug('using tmp processing dir {}'.format(tmp_dir)) + + self.__collect_dir(tmp_dir) + + self.__process_schema_dir(tmp_dir) + + self.__sections_override(os.environ, 'environment variable') + if set_as_current: set_current(self, description=self.dir) diff --git a/confini/doc.py b/confini/doc.py @@ -11,30 +11,37 @@ logg = logging.getLogger(__name__) class ConfigDoc: - def __init__(self, src): - fp = os.path.join(src, '.confini') - logg.debug('attempting doc parser with src {}'.format(fp)) + def __init__(self, src=None): + fp = None + if src != None: + fp = os.path.join(src, '.confini') + logg.debug('attempting doc parser with src {}'.format(fp)) self.src = fp self.docs = {} self.docs_flat = {} + if self.src != None: + self.process(self.src) + + + def process(self, src): try: - self.process_as_ini() + self.process_as_ini(src) except Exception: - self.process_as_env() + self.process_as_env(src) - def process_as_ini(self): + def process_as_ini(self, src): p = configparser.ConfigParser() - p.read_file(self.src) + p.read_file(src) return self.process_parser(p) - def process_as_env(self): + def process_as_env(self, src): from confini.env import ConfigEnvParser c = ConfigEnvParser() - p = c.from_file(self.src) + p = c.from_file(src) return self.process_parser(p) @@ -56,7 +63,13 @@ class ConfigDoc: else: return self.docs[k][o] + + def all(self): + return list(self.docs_flat.keys()) + @staticmethod def from_config(config): + if config.doc != None: + return config.doc return ConfigDoc(config.dirs[0]) diff --git a/confini/env.py b/confini/env.py @@ -2,6 +2,7 @@ import configparser import io import logging +import sys logg = logging.getLogger(__name__) @@ -42,3 +43,22 @@ class ConfigEnvParser: self.parser.add_section(ks) self.parser.set(ks, ko, v) return self.parser + + +def export_env(config, prefix=None, empty_all=False, skip_empty=False, doc=False, w=sys.stdout): + for k in config.all(): + v = config.get(k) + if empty_all or v == None: + v = '' + if v == '' and skip_empty: + logg.debug('skipping empty directive {}'.format(k)) + continue + if doc: + try: + doc_s = config.doc.get(k) + w.write('# ' + doc_s + "\n") + except KeyError: + pass + if prefix != None: + w.write(prefix + ' ') + w.write('{}={}\n'.format(k, v)) diff --git a/confini/export.py b/confini/export.py @@ -1,4 +1,4 @@ -# import +# standard imports import sys import configparser import os @@ -26,7 +26,9 @@ class ConfigExporter: self.target = None self.make_doc = doc self.doc = None - if isinstance(target, io.IOBase): + if target == None: + self.target = sys.stdout + elif isinstance(target, io.IOBase): self.target = target else: st = os.stat(target) @@ -40,7 +42,14 @@ class ConfigExporter: if self.make_doc: from confini.doc import ConfigDoc - self.doc = ConfigDoc.from_config(config) + if isinstance(doc, ConfigDoc): + self.doc = doc + else: + try: + self.doc = ConfigDoc.from_config(config) + except FileNotFoundError as e: + logg.warning('doc set but no doc file found: {}'.format(e)) + self.make_doc = False def scan(self): diff --git a/confini/runnable/dump.py b/confini/runnable/dump.py @@ -8,6 +8,8 @@ import stat # local imports from confini import Config +from confini.env import export_env +from confini.export import ConfigExporter logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() @@ -20,6 +22,8 @@ argparser.add_argument('--prefix', type=str, help='Prefix every line with given argparser.add_argument('--skip-empty', action='store_true', dest='skip_empty', help='Skip defined directives that are missing a value') argparser.add_argument('--schema-dir', dest='schema_dir', action='append', type=str, help='Configuation directory to merge with schema definitions') argparser.add_argument('--schema-module', dest='schema_module', action='append', type=str, default=[], help='Module path to merge with schema definitions') +argparser.add_argument('--ini', action='store_true', help='Output as ini file') +argparser.add_argument('--doc', action='store_true', help='Add matching documentation strings to output') argparser.add_argument('config_dir', nargs='*', type=str, help='Configuation directories to parse') args = argparser.parse_args() @@ -53,16 +57,24 @@ def main(): c = Config(schema_dirs, override_dirs=args.config_dir) c.process() - for k in c.store.keys(): - v = c.get(k) - if args.z or v == None: - v = '' - if v == '' and args.skip_empty: - logg.debug('skipping empty directive {}'.format(k)) - continue - if args.prefix != None: - sys.stdout.write(args.prefix + ' ') - sys.stdout.write('{}={}\n'.format(k, v)) + + if args.ini: + e = ConfigExporter(c, doc=args.doc) + e.export() + else: + export_env(c, prefix=args.prefix, empty_all=args.z, skip_empty=args.skip_empty, doc=args.doc) + + +# for k in c.store.keys(): +# v = c.get(k) +# if args.z or v == None: +# v = '' +# if v == '' and args.skip_empty: +# logg.debug('skipping empty directive {}'.format(k)) +# continue +# if args.prefix != None: +# sys.stdout.write(args.prefix + ' ') +# sys.stdout.write('{}={}\n'.format(k, v)) if __name__ == "__main__":