comparison 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
comparison
equal deleted inserted replaced
170:c247a5dc35ed 171:1ff11462a5c1
17 17
18 __revision__ = "|VCSRevision|" 18 __revision__ = "|VCSRevision|"
19 __date__ = "|VCSJustDate|" 19 __date__ = "|VCSJustDate|"
20 20
21 21
22 import fnmatch
22 import copy 23 import copy
23 24
24 from .compat import u, u2fs 25 from .compat import u, u2fs
25 from .config import Configuration 26 from .config import Configuration
26 27
27 28
28 __all__ = ["load", "safe_load", 29 __all__ = ["load", "safe_load",
29 "set_loader", "default_loaders", 30 "set_loader", "get_loader",
31 "get_default_loader",
30 "Configuration"] 32 "Configuration"]
31 33
32 34
33 COMMENTS = [u("__comment"), 35 COMMENTS = [u("__comment"),
34 u("__doc"), 36 u("__doc"),
90 def _load_ini(filename): 92 def _load_ini(filename):
91 from . import ini 93 from . import ini
92 return ini.load(filename) 94 return ini.load(filename)
93 95
94 96
95 default_loaders = { 97 _default_loaders = [
96 ".yml": _load_yaml, 98 ("*.yml", _load_yaml),
97 ".yaml": _load_yaml, 99 ("*.yaml", _load_yaml),
98 ".json": _load_json, 100 ("*.json", _load_json),
99 ".py": _load_py, 101 ("*.py", _load_py),
100 ".ini": _load_ini 102 ("*.ini", _load_ini),
101 } 103 ]
102 """The builtin default associations of extensions with loaders""" 104 """The builtin default associations of extensions with loaders -- in that
105 order
106
107 """
103 108
104 109
105 DEFAULT_LOADER = object() 110 DEFAULT_LOADER = object()
106 """Marker for the default loader for an extension. 111 """Marker for the default loader for an extension.
107 112
108 To be used in :func:`set_loader`. 113 To be used in :func:`set_loader`.
109 """ 114 """
110 115
111 _loaders = {} 116 def get_default_loader(pattern):
117 """Return the default loader for the :mod:`fnmatch` pattern `pattern`.
118
119 :raises: :class:`KeyError` if the `pattern` is not found.
120
121 """
122 for pat, loader in _default_loaders:
123 if pattern == pat:
124 return loader
125 else:
126 raise KeyError("No loader for pattern %r" % pattern)
127
128
129 _loaders = []
112 """All configured loader functions""" 130 """All configured loader functions"""
113 131
114 132
115 def clear_loader(): 133 def clear_loader():
116 """Remove all configured loader associations. 134 """Remove all configured loader associations.
117 135
118 The :data:`default_loaders` are **not** changed. 136 The :data:`_default_loaders` are **not** changed.
119 137
120 """ 138 """
121 _loaders.clear() 139 del _loaders[:]
122 140
123 141
124 def set_loader(extension, loader): 142 def get_loader(pattern):
125 """Associate a filename trailer `extension` with a callable `loader` that 143 """Return the default loader for the :mod:`fnmatch` pattern `pattern`.
126 will be called when :func:`load` encounters a file argument that ends 144
127 with `extension`. 145 :raises: :class:`KeyError` if the `pattern` is not found.
128 146
129 :param str extension: the extension to associate a loader with 147 """
148 for pat, loader in _loaders:
149 if pattern == pat:
150 return loader
151 else:
152 raise KeyError("No loader for pattern %r" % pattern)
153
154
155 def set_loader(fnpattern, loader):
156 """Associate a :mod:`fnmatch` pattern `fnpattern` with a callable
157 `loader` that will be called when :func:`load` encounters a file
158 argument that matches `fnpattern`.
159
160 :param str fnpattern: the :mod:`fnmatch` pattern to associate a loader with
130 :param callable loader: a callable that accepts a `filename` argument and 161 :param callable loader: a callable that accepts a `filename` argument and
131 returns a parsed configuration from a given file 162 returns a parsed configuration from a given file
132 163
133 `extension` should be all lowercase because lookup in the loader database 164 This function prepends to the given pattern to the currently defined
134 is *case-insensitive*. 165 associations.
166
167 The OS specific case-sensitivity behaviour of
168 :func:`fnmatch.fnmatch` apply (i.e. :func:`os.path.normpath` will
169 be called for both arguments).
135 170
136 If `loader` is :data:`DEFAULT_LOADER` then the default association 171 If `loader` is :data:`DEFAULT_LOADER` then the default association
137 from :data:`default_loaders` will be used -- if any. 172 from :data:`_default_loaders` will be used -- if any.
138 173
139 """ 174 """
140 if loader is DEFAULT_LOADER: 175 if loader is DEFAULT_LOADER:
141 try: 176 for _p, _l in _default_loaders:
142 _loaders[extension] = default_loaders[extension] 177 if _p == fnpattern:
143 except KeyError: 178 _loaders.insert(0, (fnpattern, _l))
144 raise ValueError("no DEFAULT loader for extension: %r" % extension) 179 break
145 else: 180 else:
146 _loaders[extension] = loader 181 raise ValueError("no DEFAULT loader for pattern: %r" % fnpattern)
182 else:
183 _loaders.insert(0, (fnpattern, loader))
147 184
148 185
149 def _load_cfg_from_file(filename): 186 def _load_cfg_from_file(filename):
150 fnl = filename.lower() 187 for _p, _l in _loaders:
151 extensions = list(_loaders.keys()) 188 if fnmatch.fnmatch(filename, _p):
152 extensions.sort(key=lambda x: len(x), reverse=True) 189 return _l(filename)
153 for ext in extensions:
154 if fnl.endswith(ext):
155 return _loaders[ext](filename)
156 else: 190 else:
157 raise ValueError("Unknown configuration file type for filename " 191 raise ValueError("Unknown configuration file type for filename "
158 "%r" % filename) 192 "%r" % filename)
159 193
160 194
302 336
303 337
304 # 338 #
305 # Init loader defaults 339 # Init loader defaults
306 # 340 #
307 for _extension in default_loaders: 341 for _pattern, _ld in _default_loaders:
308 set_loader(_extension, DEFAULT_LOADER) 342 set_loader(_pattern, _ld)