import string class MyFormatter(string.Formatter): def parse(self, format_string): idx = 0 auto_idx = True while True: # see if there's a { txt, sep, remain = format_string.partition('{') # todo: see if { or } are escaped if not sep: # no more substitution strings yield txt, None, None, None return # look for the terminationg } field, sep, format_string = remain.partition('}') if not sep: raise ValueError('missing matching "}"') # if field is empty, or the first character is # a colon, the field name is missing if len(field) == 0 or field[0] == ':' or field[0] == '!': field_missing = True else: field_missing = False if idx == 0: # first time through, remember which style we're using if field_missing: auto_idx = True else: auto_idx = False else: # make sure we use the same style every time if field_missing != auto_idx: raise ValueError('cannot mix and match auto indexing') if auto_idx: field = str(idx) + field # check for conversion specifier field_name, sep, rest = field.partition('!') if sep: # see if there's a format specifier conversion_specifier, sep, format_spec = rest.partition(':') else: # no conversion specifier, just an optional format_spec field_name, _, format_spec = field.partition(':') conversion_specifier = None yield txt, field_name, format_spec, conversion_specifier idx += 1 if __name__ == '__main__': import unittest import datetime class TestCase(unittest.TestCase): def setUp(self): self.f = MyFormatter() def testInvalidStrings(self): self.assertRaises(ValueError, self.f.format, '{') def testMixAndMatch(self): self.assertRaises(ValueError, self.f.format, '{}{0}', 1, 2) self.assertRaises(ValueError, self.f.format, '{0}{}', 1, 2) def testNormal(self): self.assertEqual(self.f.format('abc'), 'abc') self.assertEqual(self.f.format('{0}', 3), '3') self.assertEqual(self.f.format('{foo}', foo=3), '3') self.assertEqual(self.f.format('{0!r}', 'bar'), "'bar'") self.assertEqual(self.f.format('{0!r:6}', 'bar'), "'bar' ") self.assertEqual(self.f.format('{0!s:^10}', 3), ' 3 ') def testAutoIndex(self): self.assertEqual(self.f.format('a{}b', 10), 'a10b') self.assertEqual(self.f.format('a{}b{}', 10, 20), 'a10b20') self.assertEqual(self.f.format('a{}b{}c', 10, 'bar'), 'a10bbarc') self.assertEqual(self.f.format('a{:.2f}b', 10), 'a10.00b') self.assertEqual(self.f.format('a{:.2f}b{:.3f}c', 10, 20), 'a10.00b20.000c') dt = datetime.datetime(2009, 2, 14) self.assertEqual(self.f.format('{:%Y-%m-%d}', dt), '2009-02-14') self.assertEqual(self.f.format('{!r}', 'bar'), "'bar'") self.assertEqual(self.f.format('{!r:6}', 'bar'), "'bar' ") self.assertEqual(self.f.format('{!s:^10}', 3), ' 3 ') unittest.main()