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] == ':': 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 field_name, _, format_spec = field.partition(':') # todo: see if format_spec contains a conversion specifier yield txt, field_name, format_spec, None 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') 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') unittest.main()