diff --git a/Makefile b/Makefile index 53b2b33..c342825 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ runtests: coverage run --branch --source=compressor `which django-admin.py` test --settings=compressor.test_settings compressor coveragereport: - coverage report --omit=compressor/test*,compressor/filters/jsmin/rjsmin*,compressor/filters/cssmin/cssmin*,compressor/utils/stringformat* + coverage report --omit=compressor/test*,compressor/filters/jsmin/rjsmin*,compressor/filters/cssmin/cssmin* test: flake8 runtests coveragereport diff --git a/compressor/utils/stringformat.py b/compressor/utils/stringformat.py deleted file mode 100644 index 9311e78..0000000 --- a/compressor/utils/stringformat.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -"""Advanced string formatting for Python >= 2.4. - -An implementation of the advanced string formatting (PEP 3101). - -Author: Florent Xicluna -""" - -from __future__ import unicode_literals - -import re - -from django.utils import six - -_format_str_re = re.compile( - r'((?=^])?)' # alignment - r'([-+ ]?)' # sign - r'(#?)' r'(\d*)' r'(,?)' # base prefix, minimal width, thousands sep - r'((?:\.\d+)?)' # precision - r'(.?)$' # type -) -_field_part_re = re.compile( - r'(?:(\[)|\.|^)' # start or '.' or '[' - r'((?(1)[^]]*|[^.[]*))' # part - r'(?(1)(?:\]|$)([^.[]+)?)' # ']' and invalid tail -) - -_format_str_sub = _format_str_re.sub - - -def _is_integer(value): - return hasattr(value, '__index__') - - -def _strformat(value, format_spec=""): - """Internal string formatter. - - It implements the Format Specification Mini-Language. - """ - m = _format_spec_re.match(str(format_spec)) - if not m: - raise ValueError('Invalid conversion specification') - align, sign, prefix, width, comma, precision, conversion = m.groups() - is_numeric = hasattr(value, '__float__') - is_integer = is_numeric and _is_integer(value) - if prefix and not is_integer: - raise ValueError('Alternate form (#) not allowed in %s format ' - 'specifier' % (is_numeric and 'float' or 'string')) - if is_numeric and conversion == 'n': - # Default to 'd' for ints and 'g' for floats - conversion = is_integer and 'd' or 'g' - elif sign: - if not is_numeric: - raise ValueError("Sign not allowed in string format specifier") - if conversion == 'c': - raise ValueError("Sign not allowed with integer " - "format specifier 'c'") - if comma: - # TODO: thousand separator - pass - try: - if ((is_numeric and conversion == 's') or (not is_integer and conversion in set('cdoxX'))): - raise ValueError - if conversion == 'c': - conversion = 's' - value = chr(value % 256) - rv = ('%' + prefix + precision + (conversion or 's')) % (value,) - except ValueError: - raise ValueError("Unknown format code %r for object of type %r" % - (conversion, value.__class__.__name__)) - if sign not in '-' and value >= 0: - # sign in (' ', '+') - rv = sign + rv - if width: - zero = (width[0] == '0') - width = int(width) - else: - zero = False - width = 0 - # Fastpath when alignment is not required - if width <= len(rv): - if not is_numeric and (align == '=' or (zero and not align)): - raise ValueError("'=' alignment not allowed in string format " - "specifier") - return rv - fill, align = align[:-1], align[-1:] - if not fill: - fill = zero and '0' or ' ' - if align == '^': - padding = width - len(rv) - # tweak the formatting if the padding is odd - if padding % 2: - rv += fill - rv = rv.center(width, fill) - elif align == '=' or (zero and not align): - if not is_numeric: - raise ValueError("'=' alignment not allowed in string format " - "specifier") - if value < 0 or sign not in '-': - rv = rv[0] + rv[1:].rjust(width - 1, fill) - else: - rv = rv.rjust(width, fill) - elif align in ('>', '=') or (is_numeric and not align): - # numeric value right aligned by default - rv = rv.rjust(width, fill) - else: - rv = rv.ljust(width, fill) - return rv - - -def _format_field(value, parts, conv, spec, want_bytes=False): - """Format a replacement field.""" - for k, part, _ in parts: - if k: - if part.isdigit(): - value = value[int(part)] - else: - value = value[part] - else: - value = getattr(value, part) - if conv: - value = ((conv == 'r') and '%r' or '%s') % (value,) - if hasattr(value, '__format__'): - value = value.__format__(spec) - elif hasattr(value, 'strftime') and spec: - value = value.strftime(str(spec)) - else: - value = _strformat(value, spec) - if want_bytes and isinstance(value, six.text_type): - return str(value) - return value - - -class FormattableString(object): - """Class which implements method format(). - - The method format() behaves like str.format() in python 2.6+. - - >>> FormattableString('{a:5}').format(a=42) - ... # Same as '{a:5}'.format(a=42) - ' 42' - - """ - - __slots__ = '_index', '_kwords', '_nested', '_string', 'format_string' - - def __init__(self, format_string): - self._index = 0 - self._kwords = {} - self._nested = {} - - self.format_string = format_string - self._string = _format_str_sub(self._prepare, format_string) - - def __eq__(self, other): - if isinstance(other, FormattableString): - return self.format_string == other.format_string - # Compare equal with the original string. - return self.format_string == other - - def _prepare(self, match): - # Called for each replacement field. - part = match.group(0) - if part[0] == part[-1]: - # '{{' or '}}' - assert part == part[0] * len(part) - return part[:len(part) // 2] - repl = part[1:-1] - field, _, format_spec = repl.partition(':') - literal, sep, conversion = field.partition('!') - if sep and not conversion: - raise ValueError("end of format while looking for " - "conversion specifier") - if len(conversion) > 1: - raise ValueError("expected ':' after format specifier") - if conversion not in 'rsa': - raise ValueError("Unknown conversion specifier %s" % - str(conversion)) - name_parts = _field_part_re.findall(literal) - if literal[:1] in '.[': - # Auto-numbering - if self._index is None: - raise ValueError("cannot switch from manual field " - "specification to automatic field numbering") - name = str(self._index) - self._index += 1 - if not literal: - del name_parts[0] - else: - name = name_parts.pop(0)[1] - if name.isdigit() and self._index is not None: - # Manual specification - if self._index: - raise ValueError("cannot switch from automatic field " - "numbering to manual field specification") - self._index = None - empty_attribute = False - for k, v, tail in name_parts: - if not v: - empty_attribute = True - if tail: - raise ValueError("Only '.' or '[' may follow ']' " - "in format field specifier") - if name_parts and k == '[' and not literal[-1] == ']': - raise ValueError("Missing ']' in format string") - if empty_attribute: - raise ValueError("Empty attribute in format string") - if '{' in format_spec: - format_spec = _format_sub_re.sub(self._prepare, format_spec) - rv = (name_parts, conversion, format_spec) - self._nested.setdefault(name, []).append(rv) - else: - rv = (name_parts, conversion, format_spec) - self._kwords.setdefault(name, []).append(rv) - return r'%%(%s)s' % id(rv) - - def format(self, *args, **kwargs): - """Same as str.format() and unicode.format() in Python 2.6+.""" - if args: - kwargs.update(dict((str(i), value) - for (i, value) in enumerate(args))) - # Encode arguments to ASCII, if format string is bytes - want_bytes = isinstance(self._string, str) - params = {} - for name, items in self._kwords.items(): - value = kwargs[name] - for item in items: - parts, conv, spec = item - params[str(id(item))] = _format_field(value, parts, conv, spec, - want_bytes) - for name, items in self._nested.items(): - value = kwargs[name] - for item in items: - parts, conv, spec = item - spec = spec % params - params[str(id(item))] = _format_field(value, parts, conv, spec, - want_bytes) - return self._string % params - - -def selftest(): - import datetime - F = FormattableString - - assert F("{0:{width}.{precision}s}").format('hello world', - width=8, precision=5) == 'hello ' - - d = datetime.date(2010, 9, 7) - assert F("The year is {0.year}").format(d) == "The year is 2010" - assert F("Tested on {0:%Y-%m-%d}").format(d) == "Tested on 2010-09-07" - print('Test successful') - -if __name__ == '__main__': - selftest()