comparison configmix/config.py @ 502:4f90e1eb7af8

Make quote() and unquote() module globals and also export from configmix
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 18 Dec 2021 01:26:41 +0100
parents 34cd4f111134
children a56f1d97a3f3
comparison
equal deleted inserted replaced
501:1c83389fb8dd 502:4f90e1eb7af8
249 """Mostly used configuration key characters that do not need any quoting 249 """Mostly used configuration key characters that do not need any quoting
250 250
251 """ 251 """
252 252
253 253
254 def quote(s):
255 """Replace important special characters in string `s` by replacing
256 them with ``%xNN`` where `NN` are the two hexadecimal digits of the
257 characters unicode codepoint value.
258
259 Handled are the important special chars: ``%``, ``.``, ``:``,
260 ``#``; ``'``, ``"``, ``|``, ``{``, ``}``, ``[`` and ``]``.
261
262 See also the :ref:`quoting` section.
263
264 """
265 # Quick check whether all of the chars are in _QUOTE_SAFE
266 if not s.rstrip(_QUOTE_SAFE):
267 return s
268
269 # Slow path
270 re_encode = False
271 if PY2:
272 # Use the Unicode translation variant in PY2
273 if isinstance(s, str):
274 s = s.decode("latin1")
275 re_encode = True
276 s = s.translate(_QUOTE_MAP)
277 if re_encode:
278 return s.encode("latin1")
279 else:
280 return s
281
282
283 def unquote(s):
284 """Unquote the content of `s`: handle all patterns ``%xNN``,
285 ``%uNNNN`` or ``%UNNNNNNNN``.
286
287 This is the inverse of :func:`.quote`.
288
289 """
290 if _QUOTE not in s:
291 return s
292 parts = s.split(_QUOTE)
293 res = [parts[0]]
294 res_append = res.append
295 for p in parts[1:]:
296 try:
297 qc = p[0]
298 except IndexError:
299 raise ValueError("unknown quote syntax string: {}".format(s))
300 if qc == _QUOTE_x:
301 if len(p) < 3:
302 raise ValueError("quote syntax: length too small")
303 res_append(uchr(int(p[1:3], 16)))
304 res_append(p[3:])
305 elif qc == _QUOTE_u:
306 if len(p) < 5:
307 raise ValueError("quote syntax: length too small")
308 res_append(uchr(int(p[1:5], 16)))
309 res_append(p[5:])
310 elif qc == _QUOTE_U:
311 if len(p) < 9:
312 raise ValueError("quote syntax: length too small")
313 res_append(uchr(int(p[1:9], 16)))
314 res_append(p[9:])
315 else:
316 raise ValueError("unknown quote syntax string: {}".format(s))
317 return _EMPTY_STR.join(res)
318
319
254 class Configuration(CoercingMethodsMixin, _AttributeDict): 320 class Configuration(CoercingMethodsMixin, _AttributeDict):
255 321
256 """The configuration dictionary with attribute support or 322 """The configuration dictionary with attribute support or
257 variable substitution. 323 variable substitution.
258 324
408 No variable interpolation is done and no filters are applied. 474 No variable interpolation is done and no filters are applied.
409 475
410 Special characters (e.g. ``:`` and ``.``) must be quoted when using 476 Special characters (e.g. ``:`` and ``.``) must be quoted when using
411 the default namespace. 477 the default namespace.
412 478
413 See also :meth:`~.quote`. 479 See also :func:`.quote`.
414 480
415 """ 481 """
416 varns, varname = self._split_ns(varname) 482 varns, varname = self._split_ns(varname)
417 if not varns: 483 if not varns:
418 if varname: 484 if varname:
419 varnameparts = [ 485 varnameparts = [
420 self.unquote(vp) 486 unquote(vp)
421 for vp in varname.split(_HIER_SEPARATOR) 487 for vp in varname.split(_HIER_SEPARATOR)
422 ] 488 ]
423 else: 489 else:
424 varnameparts = tuple() 490 varnameparts = tuple()
425 else: 491 else:
562 return default 628 return default
563 629
564 def _split_ns(self, s): 630 def _split_ns(self, s):
565 ns, sep, rest = s.partition(_NS_SEPARATOR) 631 ns, sep, rest = s.partition(_NS_SEPARATOR)
566 if sep: 632 if sep:
567 return (self.unquote(ns), rest) 633 return (unquote(ns), rest)
568 else: 634 else:
569 return (None, ns) 635 return (None, ns)
570 636
571 def _split_filters(self, s): 637 def _split_filters(self, s):
572 name, sep, filters = s.partition(_FILTER_SEPARATOR) 638 name, sep, filters = s.partition(_FILTER_SEPARATOR)
735 raise NameError("Filter %r not found" % name) 801 raise NameError("Filter %r not found" % name)
736 else: 802 else:
737 value = filterfn(self, value) 803 value = filterfn(self, value)
738 return value 804 return value
739 805
740 @staticmethod
741 def quote(s):
742 """Replace important special characters in string `s` by replacing
743 them with ``%xNN`` where `NN` are the two hexadecimal digits of the
744 characters unicode codepoint value.
745
746 Handled are the important special chars: ``%``, ``.``, ``:``,
747 ``#``; ``'``, ``"``, ``|``, ``{``, ``}``, ``[`` and ``]``.
748
749 See also the :ref:`quoting` section.
750
751 """
752 # Quick check whether all of the chars are in _QUOTE_SAFE
753 if not s.rstrip(_QUOTE_SAFE):
754 return s
755
756 # Slow path
757 re_encode = False
758 if PY2:
759 # Use the Unicode translation variant in PY2
760 if isinstance(s, str):
761 s = s.decode("latin1")
762 re_encode = True
763 s = s.translate(_QUOTE_MAP)
764 if re_encode:
765 return s.encode("latin1")
766 else:
767 return s
768
769 @staticmethod
770 def unquote(s):
771 """Unquote the content of `s`: handle all patterns ``%xNN``,
772 ``%uNNNN`` or ``%UNNNNNNNN``.
773
774 This is the inverse of :meth:`~.quote`.
775
776 """
777 if _QUOTE not in s:
778 return s
779 parts = s.split(_QUOTE)
780 res = [parts[0]]
781 res_append = res.append
782 for p in parts[1:]:
783 try:
784 qc = p[0]
785 except IndexError:
786 raise ValueError("unknown quote syntax string: {}".format(s))
787 if qc == _QUOTE_x:
788 if len(p) < 3:
789 raise ValueError("quote syntax: length too small")
790 res_append(uchr(int(p[1:3], 16)))
791 res_append(p[3:])
792 elif qc == _QUOTE_u:
793 if len(p) < 5:
794 raise ValueError("quote syntax: length too small")
795 res_append(uchr(int(p[1:5], 16)))
796 res_append(p[5:])
797 elif qc == _QUOTE_U:
798 if len(p) < 9:
799 raise ValueError("quote syntax: length too small")
800 res_append(uchr(int(p[1:9], 16)))
801 res_append(p[9:])
802 else:
803 raise ValueError("unknown quote syntax string: {}".format(s))
804 return _EMPTY_STR.join(res)
805
806 def jailed(self, rootpath=None, root=None, bind_root=True): 806 def jailed(self, rootpath=None, root=None, bind_root=True):
807 """Return a "jailed" configuration of the current configuration. 807 """Return a "jailed" configuration of the current configuration.
808 808
809 :param rootpath: a sequence of strings that shall emcompass 809 :param rootpath: a sequence of strings that shall emcompass
810 the chroot-like jail of the returned 810 the chroot-like jail of the returned
834 if varns: 834 if varns:
835 raise ValueError( 835 raise ValueError(
836 "jailed configurations do not support namespaces") 836 "jailed configurations do not support namespaces")
837 if varname: 837 if varname:
838 rootpath = [ 838 rootpath = [
839 self.unquote(p) for p in root.split( 839 unquote(p) for p in root.split(_HIER_SEPARATOR)
840 _HIER_SEPARATOR)
841 ] 840 ]
842 else: 841 else:
843 rootpath = tuple() 842 rootpath = tuple()
844 jc = _JailedConfiguration(*rootpath) 843 jc = _JailedConfiguration(*rootpath)
845 if bind_root: 844 if bind_root:
878 super(_JailedConfiguration, self).__init__() 877 super(_JailedConfiguration, self).__init__()
879 self._path = path 878 self._path = path
880 if path: 879 if path:
881 self._pathstr = \ 880 self._pathstr = \
882 _HIER_SEPARATOR.join( 881 _HIER_SEPARATOR.join(
883 [Configuration.quote(p) for p in path]) \ 882 [quote(p) for p in path]) \
884 + _HIER_SEPARATOR 883 + _HIER_SEPARATOR
885 else: 884 else:
886 self._pathstr = _EMPTY_STR 885 self._pathstr = _EMPTY_STR
887 886
888 @property 887 @property
1052 if varns: 1051 if varns:
1053 raise ValueError( 1052 raise ValueError(
1054 "sub-jails do not support namespaces") 1053 "sub-jails do not support namespaces")
1055 if varname: 1054 if varname:
1056 rootpath = [ 1055 rootpath = [
1057 self._base.unquote(p) for p in varname.split( 1056 unquote(p) for p in varname.split(_HIER_SEPARATOR)
1058 _HIER_SEPARATOR)
1059 ] 1057 ]
1060 else: 1058 else:
1061 rootpath = tuple() 1059 rootpath = tuple()
1062 if self._path: 1060 if self._path:
1063 new_rootpath = self._path + tuple(rootpath) 1061 new_rootpath = self._path + tuple(rootpath)