Moved FormattableString to compressor.utils.stringformat.
This commit is contained in:
@@ -5,7 +5,7 @@ import tempfile
|
|||||||
|
|
||||||
from compressor.conf import settings
|
from compressor.conf import settings
|
||||||
from compressor.exceptions import FilterError
|
from compressor.exceptions import FilterError
|
||||||
from compressor.utils import cmd_split, FormattableString
|
from compressor.utils import cmd_split, stringformat
|
||||||
|
|
||||||
logger = logging.getLogger("compressor.filters")
|
logger = logging.getLogger("compressor.filters")
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class CompilerFilter(FilterBase):
|
|||||||
ext = ".%s" % self.type and self.type or ""
|
ext = ".%s" % self.type and self.type or ""
|
||||||
outfile = tempfile.NamedTemporaryFile(mode='w', suffix=ext)
|
outfile = tempfile.NamedTemporaryFile(mode='w', suffix=ext)
|
||||||
self.options["outfile"] = outfile.name
|
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),
|
proc = subprocess.Popen(cmd_split(cmd),
|
||||||
stdout=self.stdout, stdin=self.stdin, stderr=self.stderr)
|
stdout=self.stdout, stdin=self.stdin, stderr=self.stderr)
|
||||||
if infile is not None:
|
if infile is not None:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from shlex import split as cmd_split
|
from shlex import split as cmd_split
|
||||||
|
|
||||||
from compressor.exceptions import FilterError
|
from compressor.exceptions import FilterError
|
||||||
@@ -59,263 +57,3 @@ def walk(root, topdown=True, onerror=None, followlinks=False):
|
|||||||
if os.path.islink(p):
|
if os.path.islink(p):
|
||||||
for link_dirpath, link_dirnames, link_filenames in walk(p):
|
for link_dirpath, link_dirnames, link_filenames in walk(p):
|
||||||
yield (link_dirpath, link_dirnames, link_filenames)
|
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