changeset 97:1b4d95f60650

Build a tree-ish configuration from an INI style configuration file
author Franz Glasner <hg@dom66.de>
date Sun, 18 Mar 2018 19:15:01 +0100
parents 778c3bb1fb41
children d6ba53ce2091
files CHANGES.txt configmix/ini.py doc/introduction.rst tests/data/conf10.ini tests/data/conf10.py tests/data/conf10.yml tests/test.py
diffstat 7 files changed, 114 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Sun Mar 18 19:13:35 2018 +0100
+++ b/CHANGES.txt	Sun Mar 18 19:15:01 2018 +0100
@@ -1,4 +1,4 @@
-.. -*- coding: utf-8; mode: rst; -*-
+.. -*- coding: utf-8; mode: rst; indent-tags-mode: nil; -*-
 
 ..
 .. Valid tags: doc, feature, bugfix, test
@@ -24,9 +24,14 @@
 
       Begin formal unittests
 
+   .. change::
+      :tags: feature
+
+      Build a tree of configuration settings from INI files
+
 .. changelog::
    :version: 0.5
-   :released: 2016-04-19	     
+   :released: 2016-04-19             
 
    .. change::
       :tags: feature
--- a/configmix/ini.py	Sun Mar 18 19:13:35 2018 +0100
+++ b/configmix/ini.py	Sun Mar 18 19:15:01 2018 +0100
@@ -69,7 +69,7 @@
     def readfp(self, fp, filename):
         """Read from a file-like object `fp`.
 
-        The `fp` argument must have a `readline()` method. 
+        The `fp` argument must have a `readline()` method.
 
         """
         if hasattr(self, "filename"):
@@ -149,6 +149,9 @@
 
     Flattens the given sections into the resulting dictionary.
 
+    Then build a tree out of sections which start with any of the `extract`
+    content value and a point ``.``.
+
     """
     conf = DictImpl()
     ini = INIConfigParser(filename)
@@ -161,4 +164,17 @@
             for option in cfg:
                 value = ini.getx(sect, option)
                 conf[option] = value
+    # try to read "<extract>.xxx" sections as tree
+    for treemarker in [ e + '.' for e in extract ]:
+        sections = list(ini.sections())
+        sections.sort()
+        for section in sections:
+            cur_cfg = conf
+            if section.startswith(treemarker):
+                treestr = section[len(treemarker):]
+                for treepart in treestr.split('.'):
+                    cur_cfg = cur_cfg.setdefault(treepart, DictImpl())
+                for option in ini.options(section):
+                    value = ini.getx(section, option)
+                    cur_cfg[option] = value
     return conf
--- a/doc/introduction.rst	Sun Mar 18 19:13:35 2018 +0100
+++ b/doc/introduction.rst	Sun Mar 18 19:15:01 2018 +0100
@@ -28,6 +28,12 @@
 .. note:: All strings are returned as Unicode text strings.
 
 
+An example is:
+
+.. literalinclude:: ../tests/data/conf10.yml
+   :language: yaml
+
+
 .. _ini-files:
 
 INI Files
@@ -63,9 +69,20 @@
 .. note:: Contrary to the behaviour of the standard Python :mod:`configparser`
           module the INI file reader is *case-sensitive*.
 
-.. todo:: Implement tree-ish interpretation of INI files
+.. todo:: Document the build of tree-ish configuration settings out of
+          INI files.
+
+The example INI style configuration below yields an equivalent
+configuration to the YAML configuration above:
 
-   Currently there is no tree.
+.. literalinclude:: ../tests/data/conf10.ini
+   :language: ini
+
+As can be seen in thie example -- INI file internal value interpolation
+is done as in Python's standard :mod:`configparser` module.
+
+This example also illustrates how INI sections are used to build a
+tree-ish configuration dictionary.
 
 
 .. _executable-python-scripts:
@@ -90,3 +107,9 @@
 
 .. note:: The Python configuration files are evaluated with ``exec`` and not
           imported.
+
+The example configuration by Python script below yields an equivalent
+configuration to the YAML configuration above:
+
+.. literalinclude:: ../tests/data/conf10.py
+   :language: python
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/conf10.ini	Sun Mar 18 19:15:01 2018 +0100
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+[DEFAULT]
+replvalue1 = in the root namespace -- too
+
+[config]
+key1 = in the root namespace
+key2 = %(replvalue1)s
+
+[config.tree1]
+key3 = :int:0x20
+
+[config.tree1.tree2]
+key4 = get this as `tree1.tree2.key4'
+key5 = :bool:TRUE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/conf10.py	Sun Mar 18 19:15:01 2018 +0100
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+key1 = u"in the root namespace"
+key2 = u"in the root namespace -- too"
+
+tree1 = {
+    'key3': 0x20,
+
+    'tree2': {
+        'key4': u"get this as `tree1.tree2.key4'",
+        'key5': True
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/conf10.yml	Sun Mar 18 19:15:01 2018 +0100
@@ -0,0 +1,12 @@
+# -*- coding: utf-8; mode: yaml; indent-tabs-mode: nil; -*-
+%YAML 1.1
+---
+
+key1: in the root namespace
+key2: in the root namespace -- too
+
+tree1:
+  key3: 0x20
+  tree2:
+    key4: get this as `tree1.tree2.key4'
+    key5: true
--- a/tests/test.py	Sun Mar 18 19:13:35 2018 +0100
+++ b/tests/test.py	Sun Mar 18 19:15:01 2018 +0100
@@ -38,6 +38,17 @@
         self.assertTrue(isinstance(cfg.get("key5"), bool))
         self.assertEqual(255, cfg.get("key6"))
 
+    def __check_tree(self, cfg):
+        self.assertEqual(u("in the root namespace"),
+                         cfg.get("key1"))
+        self.assertEqual(u("in the root namespace -- too"),
+                         cfg.get("key2"))
+        self.assertEqual(32,
+                         cfg["tree1"]["key3"])
+        self.assertEqual(u("get this as `tree1.tree2.key4'"),
+                         cfg["tree1"]["tree2"]["key4"])
+        self.assertTrue(cfg["tree1"]["tree2"]["key5"])
+
     def test01_ini_types(self):
         cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf1.ini"))
         self.__check_types(cfg)
@@ -65,6 +76,19 @@
         self.assertTrue(isinstance(cfg.get("key1"), type(u(''))))
         self.assertTrue(cfg.get("_key2") is None)
 
+    def test06_ini_tree(self):
+        cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf10.ini"))
+        self.__check_tree(cfg)
+
+    def test07_py_tree(self):
+        cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf10.py"))
+        self.__check_tree(cfg)
+
+    def test08_yaml_tree(self):
+        with open(os.path.join(TESTDATADIR, "conf10.yml"), "rt") as f:
+            cfg = configmix.yaml.safe_load(f)
+            self.__check_tree(cfg)
+
 
 if __name__ == "__main__":
     unittest.main()