diff configmix/__init__.py @ 171:1ff11462a5c1

The associations from filename extensions to parsers are "fnmatch" style patterns now. Calling "configmix.set_loader" prepends to the currently defined associations and therefore gets the highest priority.
author Franz Glasner <f.glasner@feldmann-mg.com>
date Thu, 25 Apr 2019 17:00:09 +0200
parents c247a5dc35ed
children 8138d56d7cd3
line wrap: on
line diff
--- a/configmix/__init__.py	Thu Apr 25 16:14:50 2019 +0200
+++ b/configmix/__init__.py	Thu Apr 25 17:00:09 2019 +0200
@@ -19,6 +19,7 @@
 __date__ = "|VCSJustDate|"
 
 
+import fnmatch
 import copy
 
 from .compat import u, u2fs
@@ -26,7 +27,8 @@
 
 
 __all__ = ["load", "safe_load",
-           "set_loader", "default_loaders",
+           "set_loader", "get_loader",
+           "get_default_loader",
            "Configuration"]
 
 
@@ -92,14 +94,17 @@
     return ini.load(filename)
 
 
-default_loaders = {
-    ".yml": _load_yaml,
-    ".yaml": _load_yaml,
-    ".json": _load_json,
-    ".py": _load_py,
-    ".ini": _load_ini
-}
-"""The builtin default associations of extensions with loaders"""
+_default_loaders = [
+    ("*.yml", _load_yaml),
+    ("*.yaml", _load_yaml),
+    ("*.json", _load_json),
+    ("*.py", _load_py),
+    ("*.ini", _load_ini),
+]
+"""The builtin default associations of extensions with loaders -- in that
+order
+
+"""
 
 
 DEFAULT_LOADER = object()
@@ -108,51 +113,80 @@
 To be used in :func:`set_loader`.
 """
 
-_loaders = {}
+def get_default_loader(pattern):
+    """Return the default loader for the :mod:`fnmatch` pattern `pattern`.
+
+    :raises: :class:`KeyError` if the `pattern` is not found.
+
+    """
+    for pat, loader in _default_loaders:
+        if pattern == pat:
+            return loader
+    else:
+        raise KeyError("No loader for pattern %r" % pattern)
+
+
+_loaders = []
 """All configured loader functions"""
 
 
 def clear_loader():
     """Remove all configured loader associations.
 
-    The :data:`default_loaders` are **not** changed.
+    The :data:`_default_loaders` are **not** changed.
 
     """
-    _loaders.clear()
+    del _loaders[:]
 
 
-def set_loader(extension, loader):
-    """Associate a filename trailer `extension` with a callable `loader` that
-    will be called when :func:`load` encounters a file argument that ends
-    with `extension`.
+def get_loader(pattern):
+    """Return the default loader for the :mod:`fnmatch` pattern `pattern`.
+
+    :raises: :class:`KeyError` if the `pattern` is not found.
 
-    :param str extension: the extension to associate a loader with
+    """
+    for pat, loader in _loaders:
+        if pattern == pat:
+            return loader
+    else:
+        raise KeyError("No loader for pattern %r" % pattern)
+
+
+def set_loader(fnpattern, loader):
+    """Associate a :mod:`fnmatch` pattern `fnpattern` with a callable
+    `loader` that will be called when :func:`load` encounters a file
+    argument that matches `fnpattern`.
+
+    :param str fnpattern: the :mod:`fnmatch` pattern to associate a loader with
     :param callable loader: a callable that accepts a `filename` argument and
                             returns a parsed configuration from a given file
 
-    `extension` should be all lowercase because lookup in the loader database
-    is *case-insensitive*.
+    This function prepends to the given pattern to the currently defined
+    associations.
+
+    The OS specific case-sensitivity behaviour of
+    :func:`fnmatch.fnmatch` apply (i.e. :func:`os.path.normpath` will
+    be called for both arguments).
 
     If `loader` is :data:`DEFAULT_LOADER` then the default association
-    from :data:`default_loaders` will be used -- if any.
+    from :data:`_default_loaders` will be used -- if any.
 
     """
     if loader is DEFAULT_LOADER:
-        try:
-            _loaders[extension] = default_loaders[extension]
-        except KeyError:
-            raise ValueError("no DEFAULT loader for extension: %r" % extension)
+        for _p, _l in _default_loaders:
+            if _p == fnpattern:
+                _loaders.insert(0, (fnpattern, _l))
+                break
+        else:
+            raise ValueError("no DEFAULT loader for pattern: %r" % fnpattern)
     else:
-        _loaders[extension] = loader
+        _loaders.insert(0, (fnpattern, loader))
 
 
 def _load_cfg_from_file(filename):
-    fnl = filename.lower()
-    extensions = list(_loaders.keys())
-    extensions.sort(key=lambda x: len(x), reverse=True)
-    for ext in extensions:
-        if fnl.endswith(ext):
-            return _loaders[ext](filename)
+    for _p, _l in _loaders:
+        if fnmatch.fnmatch(filename, _p):
+            return _l(filename)
     else:
         raise ValueError("Unknown configuration file type for filename "
                          "%r" % filename)
@@ -304,5 +338,5 @@
 #
 # Init loader defaults
 #
-for _extension in default_loaders:
-    set_loader(_extension, DEFAULT_LOADER)
+for _pattern, _ld in _default_loaders:
+    set_loader(_pattern, _ld)