Mercurial > repos > shellac > guppy_basecaller
diff env/lib/python3.7/site-packages/boltons/formatutils.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/lib/python3.7/site-packages/boltons/formatutils.py Sat May 02 07:14:21 2020 -0400 @@ -0,0 +1,333 @@ +# -*- coding: utf-8 -*- +"""`PEP 3101`_ introduced the :meth:`str.format` method, and what +would later be called "new-style" string formatting. For the sake of +explicit correctness, it is probably best to refer to Python's dual +string formatting capabilities as *bracket-style* and +*percent-style*. There is overlap, but one does not replace the +other. + + * Bracket-style is more pluggable, slower, and uses a method. + * Percent-style is simpler, faster, and uses an operator. + +Bracket-style formatting brought with it a much more powerful toolbox, +but it was far from a full one. :meth:`str.format` uses `more powerful +syntax`_, but `the tools and idioms`_ for working with +that syntax are not well-developed nor well-advertised. + +``formatutils`` adds several functions for working with bracket-style +format strings: + + * :class:`DeferredValue`: Defer fetching or calculating a value + until format time. + * :func:`get_format_args`: Parse the positional and keyword + arguments out of a format string. + * :func:`tokenize_format_str`: Tokenize a format string into + literals and :class:`BaseFormatField` objects. + * :func:`construct_format_field_str`: Assists in progammatic + construction of format strings. + * :func:`infer_positional_format_args`: Converts anonymous + references in 2.7+ format strings to explicit positional arguments + suitable for usage with Python 2.6. + +.. _more powerful syntax: https://docs.python.org/2/library/string.html#format-string-syntax +.. _the tools and idioms: https://docs.python.org/2/library/string.html#string-formatting +.. _PEP 3101: https://www.python.org/dev/peps/pep-3101/ +""" +# TODO: also include percent-formatting utils? +# TODO: include lithoxyl.formatters.Formatter (or some adaptation)? + +from __future__ import print_function + +import re +from string import Formatter + +try: + unicode # Python 2 +except NameError: + unicode = str # Python 3 + +__all__ = ['DeferredValue', 'get_format_args', 'tokenize_format_str', + 'construct_format_field_str', 'infer_positional_format_args', + 'BaseFormatField'] + + +_pos_farg_re = re.compile('({{)|' # escaped open-brace + '(}})|' # escaped close-brace + r'({[:!.\[}])') # anon positional format arg + + +def construct_format_field_str(fname, fspec, conv): + """ + Constructs a format field string from the field name, spec, and + conversion character (``fname``, ``fspec``, ``conv``). See Python + String Formatting for more info. + """ + if fname is None: + return '' + ret = '{' + fname + if conv: + ret += '!' + conv + if fspec: + ret += ':' + fspec + ret += '}' + return ret + + +def split_format_str(fstr): + """Does very basic splitting of a format string, returns a list of + strings. For full tokenization, see :func:`tokenize_format_str`. + + """ + ret = [] + + for lit, fname, fspec, conv in Formatter().parse(fstr): + if fname is None: + ret.append((lit, None)) + continue + field_str = construct_format_field_str(fname, fspec, conv) + ret.append((lit, field_str)) + return ret + + +def infer_positional_format_args(fstr): + """Takes format strings with anonymous positional arguments, (e.g., + "{}" and {:d}), and converts them into numbered ones for explicitness and + compatibility with 2.6. + + Returns a string with the inferred positional arguments. + """ + # TODO: memoize + ret, max_anon = '', 0 + # look for {: or {! or {. or {[ or {} + start, end, prev_end = 0, 0, 0 + for match in _pos_farg_re.finditer(fstr): + start, end, group = match.start(), match.end(), match.group() + if prev_end < start: + ret += fstr[prev_end:start] + prev_end = end + if group == '{{' or group == '}}': + ret += group + continue + ret += '{%s%s' % (max_anon, group[1:]) + max_anon += 1 + ret += fstr[prev_end:] + return ret + + +# This approach is hardly exhaustive but it works for most builtins +_INTCHARS = 'bcdoxXn' +_FLOATCHARS = 'eEfFgGn%' +_TYPE_MAP = dict([(x, int) for x in _INTCHARS] + + [(x, float) for x in _FLOATCHARS]) +_TYPE_MAP['s'] = str + + +def get_format_args(fstr): + """ + Turn a format string into two lists of arguments referenced by the + format string. One is positional arguments, and the other is named + arguments. Each element of the list includes the name and the + nominal type of the field. + + # >>> get_format_args("{noun} is {1:d} years old{punct}") + # ([(1, <type 'int'>)], [('noun', <type 'str'>), ('punct', <type 'str'>)]) + + # XXX: Py3k + >>> get_format_args("{noun} is {1:d} years old{punct}") == \ + ([(1, int)], [('noun', str), ('punct', str)]) + True + """ + # TODO: memoize + formatter = Formatter() + fargs, fkwargs, _dedup = [], [], set() + + def _add_arg(argname, type_char='s'): + if argname not in _dedup: + _dedup.add(argname) + argtype = _TYPE_MAP.get(type_char, str) # TODO: unicode + try: + fargs.append((int(argname), argtype)) + except ValueError: + fkwargs.append((argname, argtype)) + + for lit, fname, fspec, conv in formatter.parse(fstr): + if fname is not None: + type_char = fspec[-1:] + fname_list = re.split('[.[]', fname) + if len(fname_list) > 1: + raise ValueError('encountered compound format arg: %r' % fname) + try: + base_fname = fname_list[0] + assert base_fname + except (IndexError, AssertionError): + raise ValueError('encountered anonymous positional argument') + _add_arg(fname, type_char) + for sublit, subfname, _, _ in formatter.parse(fspec): + # TODO: positional and anon args not allowed here. + if subfname is not None: + _add_arg(subfname) + return fargs, fkwargs + + +def tokenize_format_str(fstr, resolve_pos=True): + """Takes a format string, turns it into a list of alternating string + literals and :class:`BaseFormatField` tokens. By default, also + infers anonymous positional references into explicit, numbered + positional references. To disable this behavior set *resolve_pos* + to ``False``. + """ + ret = [] + if resolve_pos: + fstr = infer_positional_format_args(fstr) + formatter = Formatter() + for lit, fname, fspec, conv in formatter.parse(fstr): + if lit: + ret.append(lit) + if fname is None: + continue + ret.append(BaseFormatField(fname, fspec, conv)) + return ret + + +class BaseFormatField(object): + """A class representing a reference to an argument inside of a + bracket-style format string. For instance, in ``"{greeting}, + world!"``, there is a field named "greeting". + + These fields can have many options applied to them. See the + Python docs on `Format String Syntax`_ for the full details. + + .. _Format String Syntax: https://docs.python.org/2/library/string.html#string-formatting + """ + def __init__(self, fname, fspec='', conv=None): + self.set_fname(fname) + self.set_fspec(fspec) + self.set_conv(conv) + + def set_fname(self, fname): + "Set the field name." + + path_list = re.split('[.[]', fname) # TODO + + self.base_name = path_list[0] + self.fname = fname + self.subpath = path_list[1:] + self.is_positional = not self.base_name or self.base_name.isdigit() + + def set_fspec(self, fspec): + "Set the field spec." + fspec = fspec or '' + subfields = [] + for sublit, subfname, _, _ in Formatter().parse(fspec): + if subfname is not None: + subfields.append(subfname) + self.subfields = subfields + self.fspec = fspec + self.type_char = fspec[-1:] + self.type_func = _TYPE_MAP.get(self.type_char, str) + + def set_conv(self, conv): + """There are only two built-in converters: ``s`` and ``r``. They are + somewhat rare and appearlike ``"{ref!r}"``.""" + # TODO + self.conv = conv + self.conv_func = None # TODO + + @property + def fstr(self): + "The current state of the field in string format." + return construct_format_field_str(self.fname, self.fspec, self.conv) + + def __repr__(self): + cn = self.__class__.__name__ + args = [self.fname] + if self.conv is not None: + args.extend([self.fspec, self.conv]) + elif self.fspec != '': + args.append(self.fspec) + args_repr = ', '.join([repr(a) for a in args]) + return '%s(%s)' % (cn, args_repr) + + def __str__(self): + return self.fstr + + +_UNSET = object() + + +class DeferredValue(object): + """:class:`DeferredValue` is a wrapper type, used to defer computing + values which would otherwise be expensive to stringify and + format. This is most valuable in areas like logging, where one + would not want to waste time formatting a value for a log message + which will subsequently be filtered because the message's log + level was DEBUG and the logger was set to only emit CRITICAL + messages. + + The :class:``DeferredValue`` is initialized with a callable that + takes no arguments and returns the value, which can be of any + type. By default DeferredValue only calls that callable once, and + future references will get a cached value. This behavior can be + disabled by setting *cache_value* to ``False``. + + Args: + + func (function): A callable that takes no arguments and + computes the value being represented. + cache_value (bool): Whether subsequent usages will call *func* + again. Defaults to ``True``. + + >>> import sys + >>> dv = DeferredValue(lambda: len(sys._current_frames())) + >>> output = "works great in all {0} threads!".format(dv) + + PROTIP: To keep lines shorter, use: ``from formatutils import + DeferredValue as DV`` + """ + def __init__(self, func, cache_value=True): + self.func = func + self.cache_value = True + self._value = _UNSET + + def get_value(self): + """Computes, optionally caches, and returns the value of the + *func*. If ``get_value()`` has been called before, a cached + value may be returned depending on the *cache_value* option + passed to the constructor. + """ + if self._value is not _UNSET and self.cache_value: + value = self._value + else: + value = self.func() + if self.cache_value: + self._value = value + return value + + def __int__(self): + return int(self.get_value()) + + def __float__(self): + return float(self.get_value()) + + def __str__(self): + return str(self.get_value()) + + def __unicode__(self): + return unicode(self.get_value()) + + def __repr__(self): + return repr(self.get_value()) + + def __format__(self, fmt): + value = self.get_value() + + pt = fmt[-1:] # presentation type + type_conv = _TYPE_MAP.get(pt, str) + + try: + return value.__format__(fmt) + except (ValueError, TypeError): + # TODO: this may be overkill + return type_conv(value).__format__(fmt) + +# end formatutils.py