Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/boltons/formatutils.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:18:57 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d30785e31577 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 """`PEP 3101`_ introduced the :meth:`str.format` method, and what | |
| 3 would later be called "new-style" string formatting. For the sake of | |
| 4 explicit correctness, it is probably best to refer to Python's dual | |
| 5 string formatting capabilities as *bracket-style* and | |
| 6 *percent-style*. There is overlap, but one does not replace the | |
| 7 other. | |
| 8 | |
| 9 * Bracket-style is more pluggable, slower, and uses a method. | |
| 10 * Percent-style is simpler, faster, and uses an operator. | |
| 11 | |
| 12 Bracket-style formatting brought with it a much more powerful toolbox, | |
| 13 but it was far from a full one. :meth:`str.format` uses `more powerful | |
| 14 syntax`_, but `the tools and idioms`_ for working with | |
| 15 that syntax are not well-developed nor well-advertised. | |
| 16 | |
| 17 ``formatutils`` adds several functions for working with bracket-style | |
| 18 format strings: | |
| 19 | |
| 20 * :class:`DeferredValue`: Defer fetching or calculating a value | |
| 21 until format time. | |
| 22 * :func:`get_format_args`: Parse the positional and keyword | |
| 23 arguments out of a format string. | |
| 24 * :func:`tokenize_format_str`: Tokenize a format string into | |
| 25 literals and :class:`BaseFormatField` objects. | |
| 26 * :func:`construct_format_field_str`: Assists in progammatic | |
| 27 construction of format strings. | |
| 28 * :func:`infer_positional_format_args`: Converts anonymous | |
| 29 references in 2.7+ format strings to explicit positional arguments | |
| 30 suitable for usage with Python 2.6. | |
| 31 | |
| 32 .. _more powerful syntax: https://docs.python.org/2/library/string.html#format-string-syntax | |
| 33 .. _the tools and idioms: https://docs.python.org/2/library/string.html#string-formatting | |
| 34 .. _PEP 3101: https://www.python.org/dev/peps/pep-3101/ | |
| 35 """ | |
| 36 # TODO: also include percent-formatting utils? | |
| 37 # TODO: include lithoxyl.formatters.Formatter (or some adaptation)? | |
| 38 | |
| 39 from __future__ import print_function | |
| 40 | |
| 41 import re | |
| 42 from string import Formatter | |
| 43 | |
| 44 try: | |
| 45 unicode # Python 2 | |
| 46 except NameError: | |
| 47 unicode = str # Python 3 | |
| 48 | |
| 49 __all__ = ['DeferredValue', 'get_format_args', 'tokenize_format_str', | |
| 50 'construct_format_field_str', 'infer_positional_format_args', | |
| 51 'BaseFormatField'] | |
| 52 | |
| 53 | |
| 54 _pos_farg_re = re.compile('({{)|' # escaped open-brace | |
| 55 '(}})|' # escaped close-brace | |
| 56 r'({[:!.\[}])') # anon positional format arg | |
| 57 | |
| 58 | |
| 59 def construct_format_field_str(fname, fspec, conv): | |
| 60 """ | |
| 61 Constructs a format field string from the field name, spec, and | |
| 62 conversion character (``fname``, ``fspec``, ``conv``). See Python | |
| 63 String Formatting for more info. | |
| 64 """ | |
| 65 if fname is None: | |
| 66 return '' | |
| 67 ret = '{' + fname | |
| 68 if conv: | |
| 69 ret += '!' + conv | |
| 70 if fspec: | |
| 71 ret += ':' + fspec | |
| 72 ret += '}' | |
| 73 return ret | |
| 74 | |
| 75 | |
| 76 def split_format_str(fstr): | |
| 77 """Does very basic splitting of a format string, returns a list of | |
| 78 strings. For full tokenization, see :func:`tokenize_format_str`. | |
| 79 | |
| 80 """ | |
| 81 ret = [] | |
| 82 | |
| 83 for lit, fname, fspec, conv in Formatter().parse(fstr): | |
| 84 if fname is None: | |
| 85 ret.append((lit, None)) | |
| 86 continue | |
| 87 field_str = construct_format_field_str(fname, fspec, conv) | |
| 88 ret.append((lit, field_str)) | |
| 89 return ret | |
| 90 | |
| 91 | |
| 92 def infer_positional_format_args(fstr): | |
| 93 """Takes format strings with anonymous positional arguments, (e.g., | |
| 94 "{}" and {:d}), and converts them into numbered ones for explicitness and | |
| 95 compatibility with 2.6. | |
| 96 | |
| 97 Returns a string with the inferred positional arguments. | |
| 98 """ | |
| 99 # TODO: memoize | |
| 100 ret, max_anon = '', 0 | |
| 101 # look for {: or {! or {. or {[ or {} | |
| 102 start, end, prev_end = 0, 0, 0 | |
| 103 for match in _pos_farg_re.finditer(fstr): | |
| 104 start, end, group = match.start(), match.end(), match.group() | |
| 105 if prev_end < start: | |
| 106 ret += fstr[prev_end:start] | |
| 107 prev_end = end | |
| 108 if group == '{{' or group == '}}': | |
| 109 ret += group | |
| 110 continue | |
| 111 ret += '{%s%s' % (max_anon, group[1:]) | |
| 112 max_anon += 1 | |
| 113 ret += fstr[prev_end:] | |
| 114 return ret | |
| 115 | |
| 116 | |
| 117 # This approach is hardly exhaustive but it works for most builtins | |
| 118 _INTCHARS = 'bcdoxXn' | |
| 119 _FLOATCHARS = 'eEfFgGn%' | |
| 120 _TYPE_MAP = dict([(x, int) for x in _INTCHARS] + | |
| 121 [(x, float) for x in _FLOATCHARS]) | |
| 122 _TYPE_MAP['s'] = str | |
| 123 | |
| 124 | |
| 125 def get_format_args(fstr): | |
| 126 """ | |
| 127 Turn a format string into two lists of arguments referenced by the | |
| 128 format string. One is positional arguments, and the other is named | |
| 129 arguments. Each element of the list includes the name and the | |
| 130 nominal type of the field. | |
| 131 | |
| 132 # >>> get_format_args("{noun} is {1:d} years old{punct}") | |
| 133 # ([(1, <type 'int'>)], [('noun', <type 'str'>), ('punct', <type 'str'>)]) | |
| 134 | |
| 135 # XXX: Py3k | |
| 136 >>> get_format_args("{noun} is {1:d} years old{punct}") == \ | |
| 137 ([(1, int)], [('noun', str), ('punct', str)]) | |
| 138 True | |
| 139 """ | |
| 140 # TODO: memoize | |
| 141 formatter = Formatter() | |
| 142 fargs, fkwargs, _dedup = [], [], set() | |
| 143 | |
| 144 def _add_arg(argname, type_char='s'): | |
| 145 if argname not in _dedup: | |
| 146 _dedup.add(argname) | |
| 147 argtype = _TYPE_MAP.get(type_char, str) # TODO: unicode | |
| 148 try: | |
| 149 fargs.append((int(argname), argtype)) | |
| 150 except ValueError: | |
| 151 fkwargs.append((argname, argtype)) | |
| 152 | |
| 153 for lit, fname, fspec, conv in formatter.parse(fstr): | |
| 154 if fname is not None: | |
| 155 type_char = fspec[-1:] | |
| 156 fname_list = re.split('[.[]', fname) | |
| 157 if len(fname_list) > 1: | |
| 158 raise ValueError('encountered compound format arg: %r' % fname) | |
| 159 try: | |
| 160 base_fname = fname_list[0] | |
| 161 assert base_fname | |
| 162 except (IndexError, AssertionError): | |
| 163 raise ValueError('encountered anonymous positional argument') | |
| 164 _add_arg(fname, type_char) | |
| 165 for sublit, subfname, _, _ in formatter.parse(fspec): | |
| 166 # TODO: positional and anon args not allowed here. | |
| 167 if subfname is not None: | |
| 168 _add_arg(subfname) | |
| 169 return fargs, fkwargs | |
| 170 | |
| 171 | |
| 172 def tokenize_format_str(fstr, resolve_pos=True): | |
| 173 """Takes a format string, turns it into a list of alternating string | |
| 174 literals and :class:`BaseFormatField` tokens. By default, also | |
| 175 infers anonymous positional references into explicit, numbered | |
| 176 positional references. To disable this behavior set *resolve_pos* | |
| 177 to ``False``. | |
| 178 """ | |
| 179 ret = [] | |
| 180 if resolve_pos: | |
| 181 fstr = infer_positional_format_args(fstr) | |
| 182 formatter = Formatter() | |
| 183 for lit, fname, fspec, conv in formatter.parse(fstr): | |
| 184 if lit: | |
| 185 ret.append(lit) | |
| 186 if fname is None: | |
| 187 continue | |
| 188 ret.append(BaseFormatField(fname, fspec, conv)) | |
| 189 return ret | |
| 190 | |
| 191 | |
| 192 class BaseFormatField(object): | |
| 193 """A class representing a reference to an argument inside of a | |
| 194 bracket-style format string. For instance, in ``"{greeting}, | |
| 195 world!"``, there is a field named "greeting". | |
| 196 | |
| 197 These fields can have many options applied to them. See the | |
| 198 Python docs on `Format String Syntax`_ for the full details. | |
| 199 | |
| 200 .. _Format String Syntax: https://docs.python.org/2/library/string.html#string-formatting | |
| 201 """ | |
| 202 def __init__(self, fname, fspec='', conv=None): | |
| 203 self.set_fname(fname) | |
| 204 self.set_fspec(fspec) | |
| 205 self.set_conv(conv) | |
| 206 | |
| 207 def set_fname(self, fname): | |
| 208 "Set the field name." | |
| 209 | |
| 210 path_list = re.split('[.[]', fname) # TODO | |
| 211 | |
| 212 self.base_name = path_list[0] | |
| 213 self.fname = fname | |
| 214 self.subpath = path_list[1:] | |
| 215 self.is_positional = not self.base_name or self.base_name.isdigit() | |
| 216 | |
| 217 def set_fspec(self, fspec): | |
| 218 "Set the field spec." | |
| 219 fspec = fspec or '' | |
| 220 subfields = [] | |
| 221 for sublit, subfname, _, _ in Formatter().parse(fspec): | |
| 222 if subfname is not None: | |
| 223 subfields.append(subfname) | |
| 224 self.subfields = subfields | |
| 225 self.fspec = fspec | |
| 226 self.type_char = fspec[-1:] | |
| 227 self.type_func = _TYPE_MAP.get(self.type_char, str) | |
| 228 | |
| 229 def set_conv(self, conv): | |
| 230 """There are only two built-in converters: ``s`` and ``r``. They are | |
| 231 somewhat rare and appearlike ``"{ref!r}"``.""" | |
| 232 # TODO | |
| 233 self.conv = conv | |
| 234 self.conv_func = None # TODO | |
| 235 | |
| 236 @property | |
| 237 def fstr(self): | |
| 238 "The current state of the field in string format." | |
| 239 return construct_format_field_str(self.fname, self.fspec, self.conv) | |
| 240 | |
| 241 def __repr__(self): | |
| 242 cn = self.__class__.__name__ | |
| 243 args = [self.fname] | |
| 244 if self.conv is not None: | |
| 245 args.extend([self.fspec, self.conv]) | |
| 246 elif self.fspec != '': | |
| 247 args.append(self.fspec) | |
| 248 args_repr = ', '.join([repr(a) for a in args]) | |
| 249 return '%s(%s)' % (cn, args_repr) | |
| 250 | |
| 251 def __str__(self): | |
| 252 return self.fstr | |
| 253 | |
| 254 | |
| 255 _UNSET = object() | |
| 256 | |
| 257 | |
| 258 class DeferredValue(object): | |
| 259 """:class:`DeferredValue` is a wrapper type, used to defer computing | |
| 260 values which would otherwise be expensive to stringify and | |
| 261 format. This is most valuable in areas like logging, where one | |
| 262 would not want to waste time formatting a value for a log message | |
| 263 which will subsequently be filtered because the message's log | |
| 264 level was DEBUG and the logger was set to only emit CRITICAL | |
| 265 messages. | |
| 266 | |
| 267 The :class:``DeferredValue`` is initialized with a callable that | |
| 268 takes no arguments and returns the value, which can be of any | |
| 269 type. By default DeferredValue only calls that callable once, and | |
| 270 future references will get a cached value. This behavior can be | |
| 271 disabled by setting *cache_value* to ``False``. | |
| 272 | |
| 273 Args: | |
| 274 | |
| 275 func (function): A callable that takes no arguments and | |
| 276 computes the value being represented. | |
| 277 cache_value (bool): Whether subsequent usages will call *func* | |
| 278 again. Defaults to ``True``. | |
| 279 | |
| 280 >>> import sys | |
| 281 >>> dv = DeferredValue(lambda: len(sys._current_frames())) | |
| 282 >>> output = "works great in all {0} threads!".format(dv) | |
| 283 | |
| 284 PROTIP: To keep lines shorter, use: ``from formatutils import | |
| 285 DeferredValue as DV`` | |
| 286 """ | |
| 287 def __init__(self, func, cache_value=True): | |
| 288 self.func = func | |
| 289 self.cache_value = True | |
| 290 self._value = _UNSET | |
| 291 | |
| 292 def get_value(self): | |
| 293 """Computes, optionally caches, and returns the value of the | |
| 294 *func*. If ``get_value()`` has been called before, a cached | |
| 295 value may be returned depending on the *cache_value* option | |
| 296 passed to the constructor. | |
| 297 """ | |
| 298 if self._value is not _UNSET and self.cache_value: | |
| 299 value = self._value | |
| 300 else: | |
| 301 value = self.func() | |
| 302 if self.cache_value: | |
| 303 self._value = value | |
| 304 return value | |
| 305 | |
| 306 def __int__(self): | |
| 307 return int(self.get_value()) | |
| 308 | |
| 309 def __float__(self): | |
| 310 return float(self.get_value()) | |
| 311 | |
| 312 def __str__(self): | |
| 313 return str(self.get_value()) | |
| 314 | |
| 315 def __unicode__(self): | |
| 316 return unicode(self.get_value()) | |
| 317 | |
| 318 def __repr__(self): | |
| 319 return repr(self.get_value()) | |
| 320 | |
| 321 def __format__(self, fmt): | |
| 322 value = self.get_value() | |
| 323 | |
| 324 pt = fmt[-1:] # presentation type | |
| 325 type_conv = _TYPE_MAP.get(pt, str) | |
| 326 | |
| 327 try: | |
| 328 return value.__format__(fmt) | |
| 329 except (ValueError, TypeError): | |
| 330 # TODO: this may be overkill | |
| 331 return type_conv(value).__format__(fmt) | |
| 332 | |
| 333 # end formatutils.py |
