some Python 2.5 and 3.2 compatibility tweaks; remove some "#" turds

This commit is contained in:
Richard Jones
2011-11-22 08:37:02 +11:00
parent 667ecef322
commit 3f07130d1f
3 changed files with 85 additions and 78 deletions
+17 -7
View File
@@ -55,17 +55,24 @@ where a more complex format specification might have been used.
Most of the `Format Specification Mini-Language`_ is supported::
[[fill]align][sign][#][0][width][,][.precision][type]
[[fill]align][sign][0][width][type]
The align operators will cause spaces (or specified fill character)
to be stripped from the value.
to be stripped from the value. Similarly width is not enforced; it
just indicates there may be whitespace to strip.
The "#" format character is handled automatically by b, o and x - that
is: if there is a "0b", "0o" or "0x" prefix respectively, it's ignored.
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.
Some format() types come directly over: d, n, f, e, b, o 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.
The "e" and "g" types are case-insensitive so there is not need for
the "E" or "G" types.
The format() type % is not yet supported.
===== =========================================== ========
Type Characters Matched Output
@@ -78,11 +85,12 @@ Type Characters Matched Output
D Non-digit str
n Numbers with thousands separators (, or .) int
f Fixed-point numbers float
e Floating-point numbers with exponent float
e.g. 1.1e-10, NAN (all case insensitive)
g General number format (either d, f or e) 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
x Hexadecimal numbers (lower and upper case) int
ti ISO 8601 format date/time datetime
e.g. 1972-01-20T10:21:36Z
te RFC2822 e-mail format date/time datetime
@@ -164,6 +172,8 @@ spans
**Version history (in brief)**:
- 1.1.6 add "e" and "g" field types; removed redundant "h" and "X";
removed need for explicit "#".
- 1.1.5 accept textual dates in more places; Result now holds match span
positions.
- 1.1.4 fixes to some int type conversion; implemented "=" alignment; added
+66 -71
View File
@@ -176,6 +176,7 @@ spans
**Version history (in brief)**:
- 1.1.7 Python 3 compatibility tweaks (2.5 to 2.7 and 3.2 are supported).
- 1.1.6 add "e" and "g" field types; removed redundant "h" and "X";
removed need for explicit "#".
- 1.1.5 accept textual dates in more places; Result now holds match span
@@ -193,7 +194,7 @@ spans
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.6'
__version__ = '1.1.7'
import re
import unittest
@@ -219,7 +220,6 @@ PARSE_RE = re.compile('''
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>([nboxfegwWdDsS]|t[ieahgct]))?
@@ -529,8 +529,6 @@ class Parser(object):
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')
@@ -605,83 +603,82 @@ class TestPattern(unittest.TestCase):
def test_braces(self):
'pull a simple string out of another string'
s = PARSE_RE.sub(self.p.replace, '{{ }}')
self.assertEquals(s, '{ }')
self.assertEqual(s, '{ }')
def test_fixed(self):
'pull a simple string out of another string'
s = PARSE_RE.sub(self.p.replace, '{}')
self.assertEquals(s, '(.+?)')
self.assertEqual(s, '(.+?)')
s = PARSE_RE.sub(self.p.replace, '{} {}')
self.assertEquals(s, '(.+?) (.+?)')
self.assertEqual(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+)')
self.assertEqual(s, '(-?\d+)')
s = PARSE_RE.sub(self.p.replace, '{:d} {:w}')
self.assertEquals(s, '(-?\d+) (\w+)')
self.assertEqual(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>.+?)')
self.assertEqual(s, '(?P<name>.+?)')
s = PARSE_RE.sub(self.p.replace, '{name} {other}')
self.assertEquals(s, '(?P<name>.+?) (?P<other>.+?)')
self.assertEqual(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+)')
self.assertEqual(s, '(?P<name>-?\d+)')
s = PARSE_RE.sub(self.p.replace, '{name:d} {other:w}')
self.assertEquals(s, '(?P<name>-?\d+) (?P<other>\w+)')
self.assertEqual(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, '(.+?) *')
self.assertEqual(s, '(.+?) *')
def test_left_fill(self):
'skip some trailing periods'
s = PARSE_RE.sub(self.p.replace, '{:.<}')
self.assertEquals(s, '(.+?)\.*')
self.assertEqual(s, '(.+?)\.*')
def test_bird(self):
'skip some trailing whitespace'
s = PARSE_RE.sub(self.p.replace, '{:>}')
self.assertEquals(s, ' *(.+?)')
self.assertEqual(s, ' *(.+?)')
def test_center(self):
'skip some surrounding whitespace'
s = PARSE_RE.sub(self.p.replace, '{:^}')
self.assertEquals(s, ' *(.+?) *')
self.assertEqual(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+)')
self.assertEqual(_('{:f}'), '(-?\d+\.\d+)')
self.assertEqual(_('{:+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})*)')
self.assertEqual(_('{:n}'), '(-?\\d{1,3}([,.]\\d{3})*)')
self.assertEqual(_('{:+n}'), '([-+]?\\d{1,3}([,.]\\d{3})*)')
def test_format(self):
def _(fmt, matches):
m = FORMAT_RE.match(fmt)
self.assertNotEquals(m, None,
self.assertNotEqual(m, None,
'FORMAT_RE failed to parse %r' % fmt)
d = m.groupdict()
for k in matches:
self.assertEquals(d.get(k), matches[k],
self.assertEqual(d.get(k), matches[k],
'm["%s"]=%r, expect %r' % (k, d.get(k), matches[k]))
for t in 'obxegfdDwWsS':
_(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='>'))
@@ -695,104 +692,102 @@ class TestPattern(unittest.TestCase):
_(' d', dict(type='d', sign=' '))
_('ti', dict(type='ti'))
_('.^+#010d', dict(type='d', width='010', align='.^', fill='.', prefix='#',
_('.^+010d', dict(type='d', width='010', align='.^', fill='.',
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)
self.assertEqual(parse('{{hello}}', 'hello'), None)
def test_nothing(self):
'do no actual parsing'
r = parse('{{hello}}', '{hello}')
self.assertEquals(r.fixed, ())
self.assertEquals(r.named, {})
self.assertEqual(r.fixed, ())
self.assertEqual(r.named, {})
def test_fixed(self):
'pull a fixed value out of string'
r = parse('hello {}', 'hello world')
self.assertEquals(r.fixed, ('world', ))
self.assertEqual(r.fixed, ('world', ))
def test_left(self):
'pull left-aligned text out of string'
r = parse('{:<} world', 'hello world')
self.assertEquals(r.fixed, ('hello', ))
self.assertEqual(r.fixed, ('hello', ))
def test_right(self):
'pull right-aligned text out of string'
r = parse('hello {:>}', 'hello world')
self.assertEquals(r.fixed, ('world', ))
self.assertEqual(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', ))
self.assertEqual(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'))
self.assertEqual(r.fixed, (12, 'people'))
r = parse('hello {:w} {:w}', 'hello 12 people')
self.assertEquals(r.fixed, ('12', 'people'))
self.assertEqual(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)
self.assertEqual(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'})
self.assertEqual(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'))
self.assertEqual(r.fixed, ('world', 'other'))
self.assertEqual(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'))
self.assertEqual(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'))
self.assertEqual(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'))
self.assertEqual(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'))
self.assertEqual(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'))
self.assertEqual(r.named, dict(number=12, things='people'))
def test_spans(self):
'test the string sections our fields come from'
string = 'hello world'
r = parse('hello {}', string)
self.assertEquals(r.spans, {0: (6,11)})
self.assertEqual(r.spans, {0: (6,11)})
start, end = r.spans[0]
self.assertEquals(string[start:end], r.fixed[0])
self.assertEqual(string[start:end], r.fixed[0])
string = 'hello world'
r = parse('hello {:>}', string)
self.assertEquals(r.spans, {0: (10,15)})
self.assertEqual(r.spans, {0: (10,15)})
start, end = r.spans[0]
self.assertEquals(string[start:end], r.fixed[0])
self.assertEqual(string[start:end], r.fixed[0])
string = 'hello 0x12 world'
r = parse('hello {val:#x} world', string)
self.assertEquals(r.spans, {'val': (6,10)})
r = parse('hello {val:x} world', string)
self.assertEqual(r.spans, {'val': (6,10)})
start, end = r.spans['val']
self.assertEquals(string[start:end], '0x%x' % r.named['val'])
self.assertEqual(string[start:end], '0x%x' % r.named['val'])
string = 'hello world and other beings'
r = parse('hello {} {name} {} {spam}', string)
self.assertEquals(r.spans, {0: (6, 11), 'name': (12, 15),
self.assertEqual(r.spans, {0: (6, 11), 'name': (12, 15),
1: (16, 21), 'spam': (22, 28)})
def test_numbers(self):
@@ -804,10 +799,10 @@ class TestParse(unittest.TestCase):
self.fail('%r (%r) did not match %r' % (fmt, p._expression, s))
r = r.fixed[0]
if str_equals:
self.assertEquals(str(r), str(e),
self.assertEqual(str(r), str(e),
'%r found %r in %r, not %r' % (fmt, r, s, e))
else:
self.assertEquals(r, e,
self.assertEqual(r, e,
'%r found %r in %r, not %r' % (fmt, r, s, e))
def n(fmt, s, e):
if parse(fmt, s) is not None:
@@ -861,10 +856,10 @@ class TestParse(unittest.TestCase):
y('a {:g} b', 'a 1.0e10 b', 1.0e10)
y('a {:g} b', 'a 1.0E10 b', 1.0e10)
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 {:b} b', 'a 1000 b', 8)
y('a {:b} b', 'a 0b1000 b', 8)
y('a {:o} b', 'a 12345670 b', int('12345670', 8))
y('a {:o} b', 'a 0o12345670 b', int('12345670', 8))
y('a {:x} b', 'a 1234567890abcdef b', 0x1234567890abcdef)
y('a {:x} b', 'a 1234567890ABCDEF b', 0x1234567890ABCDEF)
y('a {:x} b', 'a 0x1234567890abcdef b', 0x1234567890abcdef)
@@ -888,10 +883,10 @@ class TestParse(unittest.TestCase):
if r is None:
self.fail('%r (%r) did not match %r' % (fmt, p._expression, s))
r = r.fixed[0]
self.assertEquals(r, e,
self.assertEqual(r, e,
'%r found %r in %r, not %r' % (fmt, r, s, e))
if tz is not None:
self.assertEquals(r.tzinfo, tz,
self.assertEqual(r.tzinfo, tz,
'%r found TZ %r in %r, not %r' % (fmt, r.tzinfo, s, e))
def n(fmt, s, e):
if parse(fmt, s) is not None:
@@ -902,28 +897,28 @@ class TestParse(unittest.TestCase):
# ISO 8660 variants
# YYYY-MM-DD (eg 1997-07-16)
y('a {:ti} b', 'a 1997-07-16 b', datetime(1997, 07, 16))
y('a {:ti} b', 'a 1997-07-16 b', datetime(1997, 7, 16))
# YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
y('a {:ti} b', 'a 1997-07-16T19:20 b', datetime(1997, 07, 16, 19, 20, 0))
y('a {:ti} b', 'a 1997-07-16T19:20 b', datetime(1997, 7, 16, 19, 20, 0))
y('a {:ti} b', 'a 1997-07-16T19:20Z b',
datetime(1997, 07, 16, 19, 20, tzinfo=utc))
datetime(1997, 7, 16, 19, 20, tzinfo=utc))
y('a {:ti} b', 'a 1997-07-16T19:20+01:00 b',
datetime(1997, 07, 16, 19, 20, tzinfo=FixedTzOffset(60, '+01:00')))
datetime(1997, 7, 16, 19, 20, tzinfo=FixedTzOffset(60, '+01:00')))
# YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
y('a {:ti} b', 'a 1997-07-16T19:20:30 b', datetime(1997, 07, 16, 19, 20, 30))
y('a {:ti} b', 'a 1997-07-16T19:20:30 b', datetime(1997, 7, 16, 19, 20, 30))
y('a {:ti} b', 'a 1997-07-16T19:20:30Z b',
datetime(1997, 07, 16, 19, 20, 30, tzinfo=utc))
datetime(1997, 7, 16, 19, 20, 30, tzinfo=utc))
y('a {:ti} b', 'a 1997-07-16T19:20:30+01:00 b',
datetime(1997, 07, 16, 19, 20, 30, tzinfo= FixedTzOffset(60, '+01:00')))
datetime(1997, 7, 16, 19, 20, 30, tzinfo= FixedTzOffset(60, '+01:00')))
# YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
y('a {:ti} b', 'a 1997-07-16T19:20:30.500000 b', datetime(1997, 07, 16, 19, 20, 30, 500000))
y('a {:ti} b', 'a 1997-07-16T19:20:30.500000 b', datetime(1997, 7, 16, 19, 20, 30, 500000))
y('a {:ti} b', 'a 1997-07-16T19:20:30.5Z b',
datetime(1997, 07, 16, 19, 20, 30, 500000, tzinfo=utc))
datetime(1997, 7, 16, 19, 20, 30, 500000, tzinfo=utc))
y('a {:ti} b', 'a 1997-07-16T19:20:30.5+01:00 b',
datetime(1997, 07, 16, 19, 20, 30, 500000, tzinfo=FixedTzOffset(60, '+01:00')))
datetime(1997, 7, 16, 19, 20, 30, 500000, tzinfo=FixedTzOffset(60, '+01:00')))
aest_d = datetime(2011, 11, 21, 10, 21, 36, tzinfo=aest)
dt = datetime(2011, 11, 21, 10, 21, 36)
+2
View File
@@ -22,6 +22,8 @@ setup(
'Intended Audience :: Developers',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Topic :: Software Development :: Code Generators',
'Topic :: Software Development :: Libraries :: Python Modules',
'License :: OSI Approved :: BSD License',