mirror of
https://github.com/kennethreitz-archive/parse.git
synced 2026-06-05 23:40:17 +00:00
625 lines
21 KiB
Python
625 lines
21 KiB
Python
#
|
|
# $Id$
|
|
# $HeadURL$
|
|
#
|
|
'''Parse strings using a specification based on the Python format() syntax.
|
|
|
|
parse() is the opposite of format()
|
|
|
|
Basic usage:
|
|
|
|
>>> from parse import * # only exports parse() and compile()
|
|
>>> parse("It's {}, I love it!", "It's spam, I love it!")
|
|
<Result ('spam',) {}>
|
|
>>> p = compile("It's {}, I love it!")
|
|
>>> print p
|
|
<Parser "It's {}, I love it!">
|
|
>>> p.parse("It's spam, I love it!")
|
|
<Result ('spam',) {}>
|
|
|
|
|
|
Format Syntax
|
|
-------------
|
|
|
|
A basic version of the `Format String Syntax`_ is supported with anonymous
|
|
(fixed-position), named and formatted fields::
|
|
|
|
{[field name]:[format spec]}
|
|
|
|
Field names must be a single Python identifier word. No attributes or
|
|
element indexes are supported (as they would make no sense.)
|
|
|
|
Numbered fields are also not supported: the result of parsing will include
|
|
the parsed fields in the order they are parsed.
|
|
|
|
The conversion of fields to types other than strings is done based on the
|
|
type in the format specification, which mirrors the format() behaviour.
|
|
There are no "!" field conversions like format() has.
|
|
|
|
Some simple parse() format string examples:
|
|
|
|
>>> parse("Bring me a {}", "Bring me a shrubbery")
|
|
<Result ('shrubbery',) {}>
|
|
>>> r = parse("The {} who say {}", "The knights who say Ni!")
|
|
>>> print r
|
|
<Result ('knights', 'Ni!') {}>
|
|
>>> print r.fixed
|
|
('knights', 'Ni!')
|
|
>>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade")
|
|
>>> print r
|
|
<Result () {'item': 'hand grenade'}>
|
|
>>> print r.named
|
|
{'item': 'hand grenade'}
|
|
|
|
Format Specification
|
|
--------------------
|
|
|
|
Most of the `Format Specification Mini-Language`_ is supported::
|
|
|
|
[[fill]align][sign][#][0][width][,][.precision][type]
|
|
|
|
The align operators will cause spaces (or specified fill character)
|
|
to be stripped from the value. The alignment character "=" is not yet
|
|
supported.
|
|
|
|
The comma "," separator is not yet supported.
|
|
|
|
The types supported are a slightly different mix to the format() types.
|
|
Some format() types come directly over: d, n, f, b, o, h, x and X.
|
|
In addition some regular expression character group types
|
|
D, w, W, s and S are also available.
|
|
|
|
The format() types %, F, e, E, g and G are not yet supported.
|
|
|
|
===== ========================================== =======
|
|
Type Characters Matched Output
|
|
===== ========================================== =======
|
|
w Letters and underscore str
|
|
W Non-letter and underscore str
|
|
s Whitespace str
|
|
S Non-whitespace str
|
|
d Digits (effectively integer numbers) int
|
|
D Non-digit str
|
|
n Numbers with thousands separators (, or .) int
|
|
f Fixed-point numbers float
|
|
b Binary numbers int
|
|
o Octal numbers int
|
|
h Hexadecimal numbers (lower and upper case) int
|
|
x Lower-case hexadecimal numbers int
|
|
X Upper-case hexadecimal numbers int
|
|
===== ========================================== =======
|
|
|
|
Do remember though that most often a straight type-less {} will suffice
|
|
where a more complex type specification might have been used.
|
|
|
|
So, for example, some typed parsing, and None resulting if the typing
|
|
does not match:
|
|
|
|
>>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...')
|
|
<Result (3, 'weapons') {}>
|
|
>>> parse('Our {:d} {:w} are...', 'Our three weapons are...')
|
|
None
|
|
|
|
And messing about with alignment:
|
|
|
|
>>> parse('with {:>} herring', 'with a herring')
|
|
<Result ('a',) {}>
|
|
>>> parse('spam {:^} spam', 'spam lovely spam')
|
|
<Result ('lovely',) {}>
|
|
|
|
Note that the "center" alignment does not test to make sure the value is
|
|
actually centered. It just strips leading and trailing whitespace.
|
|
|
|
See also the unit tests at the end of the module for some more
|
|
examples. Run the tests with "python -m parse".
|
|
|
|
.. _`Format String Syntax`: http://docs.python.org/library/string.html#format-string-syntax
|
|
.. _`Format Specification Mini-Language`: http://docs.python.org/library/string.html#format-specification-mini-language
|
|
|
|
----
|
|
|
|
**Version history (in brief)**:
|
|
|
|
- 1.1.3 type conversion is automatic based on specified field types. Also added
|
|
"f" and "n" types.
|
|
- 1.1.2 refactored, added compile() and limited ``from parse import *``
|
|
- 1.1.1 documentation improvements
|
|
- 1.1.0 implemented more of the `Format Specification Mini-Language`_
|
|
and removed the restriction on mixing fixed-position and named fields
|
|
- 1.0.0 initial release
|
|
|
|
This code is copyright 2011 eKit.com Inc (http://www.ekit.com/)
|
|
See the end of the source file for the license of use.
|
|
'''
|
|
__version__ = '1.1.3'
|
|
|
|
import re
|
|
import unittest
|
|
|
|
|
|
__all__ = 'parse compile'.split()
|
|
|
|
|
|
# yes, I now have two problems
|
|
PARSE_RE = re.compile('''
|
|
(
|
|
(?P<openbrace>{{)
|
|
|
|
|
(?P<closebrace>}})
|
|
|
|
|
(?P<fixed>{(:[^}]+?)?})
|
|
|
|
|
{(?P<named>\w+(:[^}]+?)?)}
|
|
)''', re.VERBOSE)
|
|
|
|
|
|
# three problems?
|
|
FORMAT_RE = re.compile('''
|
|
(?P<align>(?P<fill>[^}])?[<>^])?
|
|
(?P<sign>[-+ ])?
|
|
(?P<prefix>\#)?
|
|
(?P<width>(?P<zero>0)?[1-9]\d*)?
|
|
(\.(?P<precision>\d+))?
|
|
(?P<type>[nbohxXfwWdDsS])?
|
|
''', re.VERBOSE)
|
|
|
|
|
|
class Parser(object):
|
|
def __init__(self, format):
|
|
self._fixed_args = []
|
|
self._groups = 0
|
|
self._format = format
|
|
self._type_conversions = {}
|
|
self._group_checks = {}
|
|
self._expression = re.compile('^%s$' % PARSE_RE.sub(self.replace, format))
|
|
|
|
def __repr__(self):
|
|
if len(self._format) > 20:
|
|
return '<%s %r>' % (self.__class__.__name__, self._format[:17] + '...')
|
|
return '<%s %r>' % (self.__class__.__name__, self._format)
|
|
|
|
def parse(self, string):
|
|
m = self._expression.match(string)
|
|
if m is None:
|
|
return None
|
|
l = list(m.groups())
|
|
for n in self._fixed_args:
|
|
if n in self._type_conversions:
|
|
l[n] = self._type_conversions[n](l[n])
|
|
named = m.groupdict()
|
|
for k in named:
|
|
if k in self._type_conversions:
|
|
named[k] = self._type_conversions[k](named[k])
|
|
fixed = tuple(l[n] for n in self._fixed_args)
|
|
return Result(fixed, named)
|
|
|
|
def replace(self, match):
|
|
d = match.groupdict()
|
|
if d['openbrace']: return '{'
|
|
if d['closebrace']: return '}'
|
|
|
|
format = ''
|
|
|
|
#print 'PARSE', d
|
|
|
|
if d['fixed']:
|
|
self._fixed_args.append(self._groups)
|
|
wrap = '(%s)'
|
|
if ':' in d['fixed']:
|
|
format = d['fixed'][2:-1]
|
|
group = self._groups
|
|
elif d['named']:
|
|
if ':' in d['named']:
|
|
name, format = d['named'].split(':')
|
|
else:
|
|
name = d['named']
|
|
group = name
|
|
wrap = '(?P<%s>%%s)' % name
|
|
else:
|
|
raise ValueError('format not recognised')
|
|
|
|
self._groups += 1
|
|
|
|
if not format:
|
|
return wrap % '.+?'
|
|
|
|
m = FORMAT_RE.match(format)
|
|
if m is None:
|
|
raise ValueError('format %r not recognised' % format)
|
|
|
|
d = m.groupdict()
|
|
#print 'FORMAT', d
|
|
|
|
if d['type'] == 'n':
|
|
s = '\d{1,3}([,.]\d{3})*'
|
|
self._type_conversions[group] = lambda x: int(x.replace(',', '').replace('.', ''))
|
|
elif d['type'] == 'o':
|
|
s = '[0-7]'
|
|
self._type_conversions[group] = lambda x: int(x, 8)
|
|
elif d['type'] == 'b':
|
|
s = '[01]'
|
|
self._type_conversions[group] = lambda x: int(x, 2)
|
|
elif d['type'] == 'h':
|
|
s = '[0-9a-fA-F]'
|
|
self._type_conversions[group] = lambda x: int(x, 16)
|
|
elif d['type'] == 'x':
|
|
s = '[0-9a-f]'
|
|
self._type_conversions[group] = lambda x: int(x, 16)
|
|
elif d['type'] == 'X':
|
|
s = '[0-9A-F]'
|
|
self._type_conversions[group] = lambda x: int(x, 16)
|
|
elif d['type'] == 'f':
|
|
s = r'\d+\.\d+'
|
|
self._type_conversions[group] = float
|
|
elif d['type'] == 'd':
|
|
s = r'\d'
|
|
self._type_conversions[group] = int
|
|
elif d['type']:
|
|
s = r'\%s' % d['type']
|
|
else:
|
|
s = '.'
|
|
|
|
# TODO: number types still to support:
|
|
# e Exponent notation
|
|
# E Exponent notation with upper-case E
|
|
# g General number format with added nan, inf and -inf
|
|
# G General number format with upper-case E, NAN, INF and -INF
|
|
|
|
if d['type'] and d['type'] in 'nfdobhxX':
|
|
if d['prefix']:
|
|
if d['type'] == 'b':
|
|
s = '0b' + s
|
|
elif d['type'] == 'o':
|
|
s = '0o' + s
|
|
elif d['type'] in 'hxX':
|
|
s = '0x' + s
|
|
else:
|
|
raise ValueError('prefix # not compatible with type %s' %
|
|
d['type'])
|
|
if not d['sign']:
|
|
# default sign handling
|
|
s = r'-?' + s
|
|
elif d['sign'] == '+':
|
|
s = r'[-+]?' + s
|
|
elif d['sign'] == '-':
|
|
s = r'-?' + s
|
|
elif d['sign'] == ' ':
|
|
s = r'[- ]?' + s
|
|
else:
|
|
raise ValueError('sign in format "%s" unrecognised' % d['sign'])
|
|
else:
|
|
if d['prefix']:
|
|
raise ValueError('prefix # in format must accompany numeric type')
|
|
if d['sign']:
|
|
raise ValueError('sign in format must accompany "d" type')
|
|
|
|
if not d['type'] or d['type'] not in 'fn':
|
|
# all other types need some form of character set repetition now
|
|
s = s + '+?'
|
|
|
|
# place into a group now
|
|
s = wrap % s
|
|
|
|
# prefix with zeros or spaces?
|
|
if d['zero']:
|
|
s = '0*' + s
|
|
elif d['width']:
|
|
# all we really care about is that if the format originally
|
|
# specified a width then there will probably be padding - without an
|
|
# explicit alignment that'll mean right alignment with spaces
|
|
# padding
|
|
if not d['align']:
|
|
d['align'] = '>'
|
|
|
|
# we're just going to ignore precision...
|
|
#(\.(?P<precision>\d+))?
|
|
|
|
# TODO support '='
|
|
align = d['align']
|
|
fill = d['fill']
|
|
if fill:
|
|
align = align[1]
|
|
else:
|
|
fill = ' '
|
|
|
|
if fill in '.\+?*[](){}^$':
|
|
fill = '\\' + fill
|
|
if align == '<':
|
|
s = '%s%s*' % (s, fill)
|
|
elif align == '>':
|
|
s = '%s*%s' % (fill, s)
|
|
elif align == '^':
|
|
s = '%s*%s%s*' % (fill, s, fill)
|
|
return s
|
|
|
|
|
|
class Result(object):
|
|
def __init__(self, fixed, named):
|
|
self.fixed = fixed
|
|
self.named = named
|
|
|
|
def __repr__(self):
|
|
return '<%s %r %r>' % (self.__class__.__name__, self.fixed,
|
|
self.named)
|
|
|
|
|
|
def parse(format, string):
|
|
'''Using "format" attempt to pull values from "string".
|
|
|
|
The return value will be an object with two attributes:
|
|
|
|
.fixed - tuple of fixed-position values from the string
|
|
.named - dict of named values from the string
|
|
|
|
If the format is invalid a ValueError will be raised.
|
|
|
|
In the case there is no match parse() will return None.
|
|
'''
|
|
return Parser(format).parse(string)
|
|
|
|
|
|
def compile(format):
|
|
'''Create a Parser instance to parse "format".
|
|
|
|
The resultant Parser has a method .parse(string) which
|
|
behaves in the same manner as parse(format, string).
|
|
|
|
Use this function if you intend to parse many strings
|
|
with the same format.
|
|
'''
|
|
return Parser(format)
|
|
|
|
|
|
# yes, I now unit test both of the problems
|
|
class TestPattern(unittest.TestCase):
|
|
def setUp(self):
|
|
self.p = Parser('')
|
|
|
|
def test_braces(self):
|
|
'pull a simple string out of another string'
|
|
s = PARSE_RE.sub(self.p.replace, '{{ }}')
|
|
self.assertEquals(s, '{ }')
|
|
|
|
def test_fixed(self):
|
|
'pull a simple string out of another string'
|
|
s = PARSE_RE.sub(self.p.replace, '{}')
|
|
self.assertEquals(s, '(.+?)')
|
|
s = PARSE_RE.sub(self.p.replace, '{} {}')
|
|
self.assertEquals(s, '(.+?) (.+?)')
|
|
|
|
def test_typed(self):
|
|
'pull a named string out of another string'
|
|
s = PARSE_RE.sub(self.p.replace, '{:d}')
|
|
self.assertEquals(s, '(-?\d+?)')
|
|
s = PARSE_RE.sub(self.p.replace, '{:d} {:w}')
|
|
self.assertEquals(s, '(-?\d+?) (\w+?)')
|
|
|
|
def test_named(self):
|
|
'pull a named string out of another string'
|
|
s = PARSE_RE.sub(self.p.replace, '{name}')
|
|
self.assertEquals(s, '(?P<name>.+?)')
|
|
s = PARSE_RE.sub(self.p.replace, '{name} {other}')
|
|
self.assertEquals(s, '(?P<name>.+?) (?P<other>.+?)')
|
|
|
|
def test_named_typed(self):
|
|
'pull a named string out of another string'
|
|
s = PARSE_RE.sub(self.p.replace, '{name:d}')
|
|
self.assertEquals(s, '(?P<name>-?\d+?)')
|
|
s = PARSE_RE.sub(self.p.replace, '{name:d} {other:w}')
|
|
self.assertEquals(s, '(?P<name>-?\d+?) (?P<other>\w+?)')
|
|
|
|
def test_beaker(self):
|
|
'skip some trailing whitespace'
|
|
s = PARSE_RE.sub(self.p.replace, '{:<}')
|
|
self.assertEquals(s, '(.+?) *')
|
|
|
|
def test_left_fill(self):
|
|
'skip some trailing periods'
|
|
s = PARSE_RE.sub(self.p.replace, '{:.<}')
|
|
self.assertEquals(s, '(.+?)\.*')
|
|
|
|
def test_bird(self):
|
|
'skip some trailing whitespace'
|
|
s = PARSE_RE.sub(self.p.replace, '{:>}')
|
|
self.assertEquals(s, ' *(.+?)')
|
|
|
|
def test_center(self):
|
|
'skip some surrounding whitespace'
|
|
s = PARSE_RE.sub(self.p.replace, '{:^}')
|
|
self.assertEquals(s, ' *(.+?) *')
|
|
|
|
def test_float(self):
|
|
'skip test float expression generation'
|
|
_ = lambda s: PARSE_RE.sub(self.p.replace, s)
|
|
self.assertEquals(_('{:f}'), '(-?\d+\.\d+)')
|
|
self.assertEquals(_('{:+f}'), '([-+]?\d+\.\d+)')
|
|
|
|
def test_number_commas(self):
|
|
'skip number with commas generation'
|
|
_ = lambda s: PARSE_RE.sub(self.p.replace, s)
|
|
self.assertEquals(_('{:n}'), '(-?\\d{1,3}([,.]\\d{3})*)')
|
|
self.assertEquals(_('{:+n}'), '([-+]?\\d{1,3}([,.]\\d{3})*)')
|
|
|
|
def test_format(self):
|
|
def _(fmt, matches):
|
|
m = FORMAT_RE.match(fmt)
|
|
self.assertNotEquals(m, None,
|
|
'FORMAT_RE failed to parse %r' % fmt)
|
|
d = m.groupdict()
|
|
for k in matches:
|
|
self.assertEquals(d.get(k), matches[k],
|
|
'm["%s"]=%r, expect %r' % (k, d.get(k), matches[k]))
|
|
|
|
for t in 'obhfdDwWsS':
|
|
_(t, dict(type=t))
|
|
_('10'+t, dict(type=t, width='10'))
|
|
_('05d', dict(type='d', width='05', zero='0'))
|
|
_('#d', dict(type='d', prefix='#'))
|
|
_('<', dict(align='<'))
|
|
_('.<', dict(align='.<', fill='.'))
|
|
_('>', dict(align='>'))
|
|
_('.>', dict(align='.>', fill='.'))
|
|
_('^', dict(align='^'))
|
|
_('.^', dict(align='.^', fill='.'))
|
|
_('d', dict(type='d'))
|
|
_('-d', dict(type='d', sign='-'))
|
|
_('+d', dict(type='d', sign='+'))
|
|
_(' d', dict(type='d', sign=' '))
|
|
|
|
_('.^+#010d', dict(type='d', width='010', align='.^', fill='.', prefix='#',
|
|
sign='+', zero='0'))
|
|
|
|
#(\.(?P<precision>\d+))?
|
|
|
|
|
|
class TestParse(unittest.TestCase):
|
|
def test_no_match(self):
|
|
'string does not match format'
|
|
self.assertEquals(parse('{{hello}}', 'hello'), None)
|
|
|
|
def test_nothing(self):
|
|
'do no actual parsing'
|
|
r = parse('{{hello}}', '{hello}')
|
|
self.assertEquals(r.fixed, ())
|
|
self.assertEquals(r.named, {})
|
|
|
|
def test_fixed(self):
|
|
'pull a fixed value out of string'
|
|
r = parse('hello {}', 'hello world')
|
|
self.assertEquals(r.fixed, ('world', ))
|
|
|
|
def test_left(self):
|
|
'pull left-aligned text out of string'
|
|
r = parse('{:<} world', 'hello world')
|
|
self.assertEquals(r.fixed, ('hello', ))
|
|
|
|
def test_right(self):
|
|
'pull right-aligned text out of string'
|
|
r = parse('hello {:>}', 'hello world')
|
|
self.assertEquals(r.fixed, ('world', ))
|
|
|
|
def test_center(self):
|
|
'pull right-aligned text out of string'
|
|
r = parse('hello {:^} world', 'hello there world')
|
|
self.assertEquals(r.fixed, ('there', ))
|
|
|
|
def test_typed(self):
|
|
'pull a named, typed values out of string'
|
|
r = parse('hello {:d} {:w}', 'hello 12 people')
|
|
self.assertEquals(r.fixed, (12, 'people'))
|
|
r = parse('hello {:w} {:w}', 'hello 12 people')
|
|
self.assertEquals(r.fixed, ('12', 'people'))
|
|
|
|
def test_typed_fail(self):
|
|
'pull a named, typed values out of string'
|
|
self.assertEquals(parse('hello {:d} {:w}', 'hello people 12'), None)
|
|
|
|
def test_named(self):
|
|
'pull a named value out of string'
|
|
r = parse('hello {name}', 'hello world')
|
|
self.assertEquals(r.named, {'name': 'world'})
|
|
|
|
def test_mixed(self):
|
|
'pull a fixed and named values out of string'
|
|
r = parse('hello {} {name} {} {spam}', 'hello world and other beings')
|
|
self.assertEquals(r.fixed, ('world', 'other'))
|
|
self.assertEquals(r.named, dict(name='and', spam='beings'))
|
|
|
|
def test_named_typed(self):
|
|
'pull a named, typed values out of string'
|
|
r = parse('hello {number:d} {things}', 'hello 12 people')
|
|
self.assertEquals(r.named, dict(number=12, things='people'))
|
|
r = parse('hello {number:w} {things}', 'hello 12 people')
|
|
self.assertEquals(r.named, dict(number='12', things='people'))
|
|
|
|
def test_named_aligned_typed(self):
|
|
'pull a named, typed values out of string'
|
|
r = parse('hello {number:<d} {things}', 'hello 12 people')
|
|
self.assertEquals(r.named, dict(number=12, things='people'))
|
|
r = parse('hello {number:>d} {things}', 'hello 12 people')
|
|
self.assertEquals(r.named, dict(number=12, things='people'))
|
|
r = parse('hello {number:^d} {things}', 'hello 12 people')
|
|
self.assertEquals(r.named, dict(number=12, things='people'))
|
|
|
|
def test_numbers(self):
|
|
'pull a numbers out of a string'
|
|
def y(fmt, s, e):
|
|
r = parse(fmt, s)
|
|
if r is None: self.fail('%r did not match %r' % (fmt, s))
|
|
self.assertEquals(r.fixed[0], e,
|
|
'%r found %r in %r, not %r' % (fmt, r.fixed[0], s, e))
|
|
def n(fmt, s, e):
|
|
if parse(fmt, s) is not None:
|
|
self.fail('%r matched %r' % (fmt, s))
|
|
y('a {:d} b', 'a 12 b', 12)
|
|
y('a {:5d} b', 'a 12 b', 12)
|
|
y('a {:d} b', 'a -12 b', -12)
|
|
n('a {:d} b', 'a +12 b', None)
|
|
y('a {:-d} b', 'a -12 b', -12)
|
|
n('a {:-d} b', 'a +12 b', None)
|
|
y('a {:+d} b', 'a -12 b', -12)
|
|
y('a {:+d} b', 'a +12 b', 12)
|
|
y('a {: d} b', 'a -12 b', -12)
|
|
y('a {: d} b', 'a 12 b', 12)
|
|
n('a {: d} b', 'a +12 b', None)
|
|
|
|
y('a {:n} b', 'a 100 b', 100)
|
|
y('a {:n} b', 'a 1,000 b', 1000)
|
|
y('a {:n} b', 'a 1.000 b', 1000)
|
|
y('a {:n} b', 'a -1,000 b', -1000)
|
|
y('a {:+n} b', 'a +1,000 b', 1000)
|
|
y('a {:n} b', 'a 10,000 b', 10000)
|
|
y('a {:n} b', 'a 100,000 b', 100000)
|
|
n('a {:n} b', 'a 100,00 b', None)
|
|
|
|
y('a {:f} b', 'a 12.0 b', 12.0)
|
|
y('a {:f} b', 'a -12.1 b', -12.1)
|
|
y('a {:-f} b', 'a -12.1 b', -12.1)
|
|
y('a {:+f} b', 'a +12.1 b', 12.1)
|
|
y('a {: f} b', 'a 12.1 b', 12.1)
|
|
n('a {:f} b', 'a 12 b', None)
|
|
|
|
y('a {:b} b', 'a 101101 b', 0b101101)
|
|
y('a {:#b} b', 'a 0b101101 b', 0b101101)
|
|
y('a {:o} b', 'a 12345670 b', 0o12345670)
|
|
y('a {:#o} b', 'a 0o12345670 b', 0o12345670)
|
|
y('a {:h} b', 'a 1234567890abcdef b', 0x1234567890abcdef)
|
|
y('a {:h} b', 'a 1234567890ABCDEF b', 0x1234567890ABCDEF)
|
|
y('a {:#h} b', 'a 0x1234567890abcdef b', 0x1234567890abcdef)
|
|
y('a {:#h} b', 'a 0x1234567890ABCDEF b', 0x1234567890ABCDEF)
|
|
y('a {:x} b', 'a 1234567890abcdef b', 0x1234567890abcdef)
|
|
y('a {:X} b', 'a 1234567890ABCDEF b', 0x1234567890ABCDEF)
|
|
y('a {:#x} b', 'a 0x1234567890abcdef b', 0x1234567890abcdef)
|
|
y('a {:#X} b', 'a 0x1234567890ABCDEF b', 0x1234567890ABCDEF)
|
|
|
|
y('a {:05d} b', 'a 00001 b', 1)
|
|
|
|
# TODO this should pass
|
|
# y('a {:05d} b', 'a 0000001 b', None)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
|
|
# Copyright (c) 2011 eKit.com Inc (http://www.ekit.com/)
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
# vim: set filetype=python ts=4 sw=4 et si tw=75
|