Moved FormattableString to compressor.utils.stringformat.
This commit is contained in:
		@@ -5,7 +5,7 @@ import tempfile
 | 
			
		||||
 | 
			
		||||
from compressor.conf import settings
 | 
			
		||||
from compressor.exceptions import FilterError
 | 
			
		||||
from compressor.utils import cmd_split, FormattableString
 | 
			
		||||
from compressor.utils import cmd_split, stringformat
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger("compressor.filters")
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +56,7 @@ class CompilerFilter(FilterBase):
 | 
			
		||||
                ext = ".%s" % self.type and self.type or ""
 | 
			
		||||
                outfile = tempfile.NamedTemporaryFile(mode='w', suffix=ext)
 | 
			
		||||
                self.options["outfile"] = outfile.name
 | 
			
		||||
            cmd = FormattableString(self.command).format(**self.options)
 | 
			
		||||
            cmd = stringformat.FormattableString(self.command).format(**self.options)
 | 
			
		||||
            proc = subprocess.Popen(cmd_split(cmd),
 | 
			
		||||
                stdout=self.stdout, stdin=self.stdin, stderr=self.stderr)
 | 
			
		||||
            if infile is not None:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
from shlex import split as cmd_split
 | 
			
		||||
 | 
			
		||||
from compressor.exceptions import FilterError
 | 
			
		||||
@@ -59,263 +57,3 @@ def walk(root, topdown=True, onerror=None, followlinks=False):
 | 
			
		||||
                if os.path.islink(p):
 | 
			
		||||
                    for link_dirpath, link_dirnames, link_filenames in walk(p):
 | 
			
		||||
                        yield (link_dirpath, link_dirnames, link_filenames)
 | 
			
		||||
 | 
			
		||||
"""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'((?<!{)(?:{{)+'                       # '{{'
 | 
			
		||||
    r'|(?:}})+(?!})'                        # '}}
 | 
			
		||||
    r'|{(?:[^{](?:[^{}]+|{[^{}]*})*)?})'    # replacement field
 | 
			
		||||
)
 | 
			
		||||
_format_sub_re = re.compile(r'({[^{}]*})')  # nested replacement field
 | 
			
		||||
_format_spec_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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										278
									
								
								compressor/utils/stringformat.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								compressor/utils/stringformat.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,278 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
"""Advanced string formatting for Python >= 2.4.
 | 
			
		||||
 | 
			
		||||
An implementation of the advanced string formatting (PEP 3101).
 | 
			
		||||
 | 
			
		||||
Author: Florent Xicluna
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
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'((?<!{)(?:{{)+'                       # '{{'
 | 
			
		||||
    r'|(?:}})+(?!})'                        # '}}
 | 
			
		||||
    r'|{(?:[^{](?:[^{}]+|{[^{}]*})*)?})'    # replacement field
 | 
			
		||||
)
 | 
			
		||||
_format_sub_re = re.compile(r'({[^{}]*})')  # nested replacement field
 | 
			
		||||
_format_spec_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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def selftest():
 | 
			
		||||
    import datetime
 | 
			
		||||
    F = FormattableString
 | 
			
		||||
 | 
			
		||||
    assert F(u"{0:{width}.{precision}s}").format('hello world',
 | 
			
		||||
             width=8, precision=5) == u'hello   '
 | 
			
		||||
 | 
			
		||||
    d = datetime.date(2010, 9, 7)
 | 
			
		||||
    assert F(u"The year is {0.year}").format(d) == u"The year is 2010"
 | 
			
		||||
    assert F(u"Tested on {0:%Y-%m-%d}").format(d) == u"Tested on 2010-09-07"
 | 
			
		||||
    print 'Test successful'
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    selftest()
 | 
			
		||||
		Reference in New Issue
	
	Block a user