From 68a3576babc7e2c687a4c0231d077e65c8da9f37 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 7 Apr 2011 22:15:27 +0200 Subject: [PATCH] Added FormattableString from the stringformat package. --- LICENSE | 35 ++++++ compressor/utils.py | 263 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) diff --git a/LICENSE b/LICENSE index 02c5247..603a703 100644 --- a/LICENSE +++ b/LICENSE @@ -105,3 +105,38 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +utils.FormattableString +----------------------- +Copyright (c) 2010 by Florent Xicluna. + +Some rights reserved. + +Redistribution and use in source and binary forms of the software as well +as documentation, with or without modification, are permitted provided +that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +* The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/compressor/utils.py b/compressor/utils.py index 539260e..e81c980 100644 --- a/compressor/utils.py +++ b/compressor/utils.py @@ -1,4 +1,6 @@ +# -*- coding: utf-8 -*- import os +import re import sys from inspect import getmembers from shlex import split as cmd_split @@ -268,3 +270,264 @@ class cached_property(object): def deleter(self, fdel): return self.__class__(self.__get, self.__set, fdel) + + +"""Advanced string formatting for Python >= 2.4. + +An implementation of the advanced string formatting (PEP 3101). + +Author: Florent Xicluna +""" + +if hasattr(str, 'partition'): + def partition(s, sep): + return s.partition(sep) +else: # Python 2.4 + def partition(s, sep): + try: + left, right = s.split(sep, 1) + except ValueError: + return s, '', '' + return left, sep, right + +_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 +) + +if hasattr(re, '__version__'): + _format_str_sub = _format_str_re.sub +else: + # Python 2.4 fails to preserve the Unicode type + def _format_str_sub(repl, s): + if isinstance(s, unicode): + return unicode(_format_str_re.sub(repl, s)) + return _format_str_re.sub(repl, s) + +if hasattr(int, '__index__'): + def _is_integer(value): + return hasattr(value, '__index__') +else: # Python 2.4 + def _is_integer(value): + return isinstance(value, (int, long)) + + +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, unicode): + 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(u'{a:5}').format(a=42) + ... # Same as u'{a:5}'.format(a=42) + u' 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 = partition(repl, ':') + literal, sep, conversion = partition(field, '!') + 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