commit aacf8f1825fb8b7f74c2551001577e1aa5f573cf
parent 162a0b6d4f0772e425dbe142e55575631e306512
Author: nolash <dev@holbrook.no>
Date: Tue, 2 Nov 2021 07:31:17 +0100
Add config env parser, doc parser, doc addition in exports
Diffstat:
13 files changed, 213 insertions(+), 18 deletions(-)
diff --git a/confini/common.py b/confini/common.py
@@ -0,0 +1,2 @@
+def to_constant_name(directive, section):
+ return '{}_{}'.format(section.upper(), directive.upper())
diff --git a/confini/config.py b/confini/config.py
@@ -9,6 +9,9 @@ import re
# external imports
import gnupg
+# local imports
+from confini.common import to_constant_name
+
logg = logging.getLogger('confini')
current_config = None
@@ -89,7 +92,7 @@ class Config:
def censor(self, identifier, section=None):
constant_name = ''
if section != None:
- constant_name = Config.to_constant_name(identifier, section)
+ constant_name = to_constant_name(identifier, section)
else:
constant_name = identifier
self.censored[constant_name] = True
@@ -111,15 +114,10 @@ class Config:
return True
- @staticmethod
- def to_constant_name(directive, section):
- return '{}_{}'.format(section.upper(), directive.upper())
-
-
def _sections_override(self, dct, dct_description):
for s in self.parser.sections():
for k in self.parser[s]:
- cn = Config.to_constant_name(k, s)
+ cn = to_constant_name(k, s)
self.override(cn, self.parser[s][k], dct, dct_description)
@@ -190,7 +188,7 @@ class Config:
self.parser.read(tmp_config_file_path)
for s in self.parser.sections():
for so in self.parser.options(s):
- k = self.to_constant_name(so, s)
+ k = to_constant_name(so, s)
v = self.parser.get(s, so)
logg.debug('default config set {}'.format(k))
self.add(v, k, exists_ok=True)
@@ -201,7 +199,7 @@ class Config:
local_parser.read(tmp_config_file_path)
for s in local_parser.sections():
for so in local_parser.options(s):
- k = self.to_constant_name(so, s)
+ k = to_constant_name(so, s)
if not self.have(k):
raise KeyError('config overrides in {} defines key {} not present in default config {}'.format(self.dirs[i], k, self.dirs[0]))
v = local_parser.get(s, so)
diff --git a/confini/doc.py b/confini/doc.py
@@ -0,0 +1,62 @@
+# standard imports
+import os
+import configparser
+import logging
+
+# local imports
+from confini.common import to_constant_name
+
+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))
+
+ self.src = fp
+ self.docs = {}
+ self.docs_flat = {}
+
+ try:
+ self.process_as_ini()
+ except Exception:
+ self.process_as_env()
+
+
+ def process_as_ini(self):
+ p = configparser.ConfigParser()
+ p.read_file(self.src)
+ return self.process_parser(p)
+
+
+ def process_as_env(self):
+ from confini.env import ConfigEnvParser
+ c = ConfigEnvParser()
+ p = c.from_file(self.src)
+ return self.process_parser(p)
+
+
+ def process_parser(self, p):
+ for ks in p.sections():
+ if self.docs.get(ks) == None:
+ self.docs[ks] = {}
+ for ko in p.options(ks):
+ v = p.get(ks, ko)
+ self.docs[ks][ko] = v
+ c = to_constant_name(ko, ks)
+ self.docs_flat[c] = v
+ logg.debug('docs {} -> {} = {}'.format(ks, ko, v))
+
+
+ def get(self, k, o=None):
+ if o == None:
+ return self.docs_flat[k]
+ else:
+ return self.docs[k][o]
+
+
+ @staticmethod
+ def from_config(config):
+ return ConfigDoc(config.dirs[0])
diff --git a/confini/env.py b/confini/env.py
@@ -0,0 +1,40 @@
+# standard imports
+import configparser
+import io
+import logging
+
+logg = logging.getLogger(__name__)
+
+
+class ConfigEnvParser:
+
+ def __init__(self):
+ self.parser = configparser.ConfigParser()
+
+
+ def from_file(self, fp):
+ f = open(fp, 'r')
+ r = self.from_handle(f)
+ f.close()
+ return r
+
+
+ def from_string(self, s):
+ fh = io.StringIO(s)
+ return self.from_handle(fh)
+
+
+ def from_handle(self, fh):
+ while True:
+ l = fh.readline()
+ if len(l) == 0:
+ break
+ (k, v) = l.split('=')
+ (ks, ko) = k.split('_', maxsplit=1)
+ ks = ks.lower()
+ ko = ko.lower()
+ v = v.rstrip()
+ if not self.parser.has_section(ks):
+ self.parser.add_section(ks)
+ self.parser.set(ks, ko, v)
+ return self.parser
diff --git a/confini/export.py b/confini/export.py
@@ -18,12 +18,14 @@ class ConfigExporterTarget(enum.Enum):
class ConfigExporter:
- def __init__(self, config, target=None, split=False):
+ def __init__(self, config, target=None, split=False, doc=False):
self.config = config
self.sections = {}
self.target_split = split
self.target_typ = ConfigExporterTarget.HANDLE
self.target = None
+ self.make_doc = doc
+ self.doc = None
if isinstance(target, io.IOBase):
self.target = target
else:
@@ -36,6 +38,10 @@ class ConfigExporter:
d = os.getcwd()
self.target = os.path.join(d, target)
+ if self.make_doc:
+ from confini.doc import ConfigDoc
+ self.doc = ConfigDoc.from_config(config)
+
def scan(self):
for k in self.config.all():
@@ -47,12 +53,18 @@ class ConfigExporter:
self.sections[s][v] = self.config.get(k)
- def export_section(self, k, w):
- s = {}
- s[k] = self.sections[k]
- p = configparser.ConfigParser()
- p.read_dict(s)
- p.write(w)
+ def export_section(self, ks, w):
+ w.write("[" + ks + "]\n")
+ for ko in self.sections[ks].keys():
+ if self.make_doc:
+ try:
+ v = self.doc.get(ks, ko)
+ w.write("# " + v + "\n")
+ except KeyError:
+ logg.warning('doc missing for section {} option {}'.format(ks, ko))
+ pass
+ w.write(ko + " = " + self.sections[ks][ko] + "\n")
+ w.write("\n")
def export(self):
diff --git a/tests/files/.confini b/tests/files/.confini
@@ -0,0 +1,2 @@
+FOO_BAR=bar of foo
+XYZZY_BERT=bert of xyzzy
diff --git a/tests/files/doc/ok/.confini b/tests/files/doc/ok/.confini
@@ -0,0 +1,2 @@
+FOO_BAR=foo of bar
+BAR_XYZZY=bar of xyzzy
diff --git a/tests/files/doc/ok/bar.ini b/tests/files/doc/ok/bar.ini
@@ -0,0 +1,2 @@
+[bar]
+xyzzy = 666
diff --git a/tests/files/doc/ok/foo.ini b/tests/files/doc/ok/foo.ini
@@ -0,0 +1,3 @@
+[foo]
+bar = 42
+baz = 13
diff --git a/tests/files/env/env.txt b/tests/files/env/env.txt
@@ -0,0 +1,3 @@
+FOO_BAR=13
+FOO_BAZ=42
+BAR_XYZZY=666
diff --git a/tests/test_doc.py b/tests/test_doc.py
@@ -0,0 +1,28 @@
+# standard imports
+import unittest
+import os
+import logging
+
+# local imports
+from confini import Config
+from confini.doc import ConfigDoc
+
+logging.basicConfig(level=logging.DEBUG)
+
+
+class TestDoc(unittest.TestCase):
+
+ wd = os.path.dirname(__file__)
+
+ def test_from_config(self):
+ inidir = os.path.join(self.wd, 'files/doc/ok')
+ c = Config(inidir)
+ c.process()
+
+ doc = ConfigDoc.from_config(c)
+ self.assertEqual(doc.get('FOO_BAR'), 'foo of bar')
+ self.assertEqual(doc.get('BAR_XYZZY'), 'bar of xyzzy')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_env.py b/tests/test_env.py
@@ -1,9 +1,10 @@
-#!/usr/bin/python
-
+# standard imports
import os
import unittest
import logging
+# local imports
+from confini.env import ConfigEnvParser
from confini import Config
logging.basicConfig(level=logging.DEBUG)
@@ -84,5 +85,15 @@ class TestEnv(unittest.TestCase):
}
self.assertDictEqual(expect, c.store)
+
+ def test_env_parser(self):
+ envpath = os.path.join(self.wd, 'files', 'env', 'env.txt')
+ c = ConfigEnvParser()
+ p = c.from_file(envpath)
+ self.assertTrue(p.has_section('foo'))
+ self.assertTrue(p.has_section('bar'))
+ self.assertTrue(p.has_option('foo', 'bar'))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_export.py b/tests/test_export.py
@@ -5,10 +5,12 @@ import unittest
import logging
import configparser
import tempfile
+import re
# local imports
from confini import Config
from confini.export import ConfigExporter
+from confini.doc import ConfigDoc
logging.basicConfig(level=logging.DEBUG)
@@ -99,5 +101,33 @@ class TestExport(unittest.TestCase):
self.assertEqual(b.get(s, o), a.get(s.lower(), o.lower()))
+
+ def test_doc(self):
+ w = io.StringIO()
+ e = ConfigExporter(self.config, target=w, doc=True)
+ e.export()
+
+ w.seek(0)
+ s = w.read()
+
+ re_c = re.compile('^# ', re.MULTILINE)
+ m = re_c.finditer(s)
+ next(m)
+ next(m)
+ with self.assertRaises(StopIteration):
+ next(m)
+
+ a = configparser.ConfigParser()
+ a.read_string(s)
+
+ b = configparser.ConfigParser()
+ b.read(os.path.join(self.inidir, 'foo.ini'))
+ b.read(os.path.join(self.inidir, 'bar.ini'))
+
+ for s in b.sections():
+ for o in b.options(s):
+ self.assertEqual(b.get(s, o), a.get(s.lower(), o.lower()))
+
+
if __name__ == '__main__':
unittest.main()