diff configmix/__init__.py @ 144:7e6ec99d5ff5

Allow comments as keys and filter them by default
author Franz Glasner <hg@dom66.de>
date Fri, 13 Apr 2018 09:51:02 +0200
parents 647782859ae1
children e2e8d21b4122
line wrap: on
line diff
--- a/configmix/__init__.py	Mon Apr 09 09:35:04 2018 +0200
+++ b/configmix/__init__.py	Fri Apr 13 09:51:02 2018 +0200
@@ -20,6 +20,7 @@
 
 import copy
 
+from .compat import u
 from .config import Configuration
 
 
@@ -28,6 +29,15 @@
            "Configuration"]
 
 
+COMMENTS = [u("__comment"),
+            u("__doc"),
+]
+"""Prefixes for comment configuration keys that are to be handled as
+comments
+
+"""
+
+
 def load(*files):
     """Load the given configuration files, merge them in the given order
     and return the resulting configuration dictionary.
@@ -168,7 +178,7 @@
         return result
 
 
-def merge(user, default):
+def merge(user, default, filter_comments=True):
     """Logically merge the configuration in `user` into `default`.
 
     :param ~configmix.config.Configuration user:
@@ -176,73 +186,120 @@
                 into `default`
     :param ~configmix.config.Configuration default:
                 the base configuration where `user` is logically merged into
+    :param bool filter_comments: flag whether to filter comment keys that
+                   start with any of the items in :data:`COMMENTS`
     :returns: `user` with the necessary amendments from `default`.
               If `user` is ``None`` then `default` is returned.
 
-    .. note:: The configuration in `default` is not changed but the
-              configuration given in `user` is changed **inplace**.
+    .. note:: The configuration in `user` is augmented/changed
+              **inplace**.
+
+              The configuration in `default` will be changed **inplace**
+              when filtering out comments (which is the default).
 
     From http://stackoverflow.com/questions/823196/yaml-merge-in-python
 
     """
     if user is None:
+        if filter_comments:
+            _filter_comments(default)
         return default
+    if filter_comments:
+        _filter_comments(user)
     if isinstance(user, dict) and isinstance(default, dict):
         for k, v in default.items():
+            if filter_comments and _is_comment(k):
+                continue
             if k not in user:
                 user[k] = v
             else:
-                user[k] = _merge(user[k], v)
+                user[k] = _merge(user[k], v, filter_comments)
     return user
 
 
-def _merge(user, default):
+def _merge(user, default, filter_comments):
     """Recursion helper for :meth:`merge`
 
     """
     if isinstance(user, dict) and isinstance(default, dict):
         for k, v in default.items():
+            if filter_comments and _is_comment(k):
+                continue
             if k not in user:
                 user[k] = v
             else:
-                user[k] = _merge(user[k], v)
+                user[k] = _merge(user[k], v, filter_comments)
     return user
 
 
-def safe_merge(user, default):
+def safe_merge(user, default, filter_comments=True):
     """A more safe version of :func:`merge` that makes deep copies of
     the returned container objects.
 
-    No given argument is ever changed inplace. Every object from `default`
-    is decoupled from the result -- so changing the `default` configuration
-    lates does not yield into a merged configuration later.
+    Contrary to :func:`merge` no given argument is ever changed
+    inplace. Every object from `default` is decoupled from the result
+    -- so changing the `default` configuration later does not yield
+    into a merged configuration later.
 
     """
     if user is None:
+        if filter_comments:
+            _filter_comments(default)
         return copy.deepcopy(default)
     user = copy.deepcopy(user)
+    if filter_comments:
+        _filter_comments(user)
     if isinstance(user, dict) and isinstance(default, dict):
         for k, v in default.items():
+            if filter_comments and _is_comment(k):
+                continue
             if k not in user:
                 user[k] = copy.deepcopy(v)
             else:
-                user[k] = _safe_merge(user[k], v)
+                user[k] = _safe_merge(user[k], v, filter_comments)
     return user
 
 
-def _safe_merge(user, default):
+def _safe_merge(user, default, filter_comments):
     """Recursion helper for :meth:`safe_merge`
 
     """
     if isinstance(user, dict) and isinstance(default, dict):
         for k, v in default.items():
+            if filter_comments and _is_comment(k):
+                continue
             if k not in user:
                 user[k] = copy.deepcopy(v)
             else:
-                user[k] = _safe_merge(user[k], v)
+                user[k] = _safe_merge(user[k], v, filter_comments)
     return user
 
 
+def _filter_comments(d):
+    """Recursively filter comments keys in the dict `d`.
+
+    Comment keys are keys that start with any of the items in
+    :data:`COMMENTS`.
+
+    """
+    if not isinstance(d, dict):
+        return
+    # use a copy of the keys because we change `d` while iterating
+    for k in list(d.keys()):
+        if _is_comment(k):
+            del d[k]
+        else:
+            if isinstance(d[k], dict):
+                _filter_comments(d[k])
+
+
+def _is_comment(k):
+    for i in COMMENTS:
+        if k.startswith(i):
+            return True
+    return False
+
+
 #
 # Init loader defaults
 #