I have the custom formatter for string substitution, which is supposed to be only different from standard string formatter in replacement field syntax: instead of '{f}'.format(f=123)
I want '$(f)'.format(f=123)
(the latter is actually going to use the custom formatter class, like shell_formatter.format(...)
).
I overrode the string.Formatter.parse
to use new syntax, but the regular expression I'm using in the end doesn't quite satisfy me.
Questions:
- Can I get the regular expression to be simpler / more readable?
- Is there a simpler way to change formatting syntax, that I missed?
Updates
- Simplified the regexp and added new test.
- The purpose is to generate C source code files from a template. Using
'{f}'
syntax clearly will not work here because of C's excessive usage of curlys. - The
string.Template
module doesn't support complex access specifiers (see third test).
The code follows:
#! /usr/bin/env python3
import string
import re
class ShellFormatter(string.Formatter):
def parse(self, format_string):
for m in re.finditer(
r"""(?: \$ \( ( [^)]+ ) \) ) # the field access specifier
| (
(?:
\n | . (?= \$ \( ) # any one single character before the '$('
)
| (?:
\n | . (?! \$ \( ) # any one single character, except the one before the '$('
)*
)""",
format_string,
re.VERBOSE):
if m.group(1):
yield ('', m.group(1), '', None)
if m.group(2):
yield (m.group(2), None, None, None)
def main():
...
def test():
s = 'ashyudiqhw $(field) fwekojnwe'
ss = 'checking helll kpoqjkf3483297 18934417 hnhfnqi^$&*@&2 1748912$&#^$\n467812\n^$ jfimopw279\nashyudiqhw $(field) fwekojnwe\njjhjhj$(notfield)'
sss = 'const int complex_stuff = $(stuff[0][field1][field2]);'
sf = ShellFormatter()
assert sf.format(s, field='zzz') == 'ashyudiqhw zzz fwekojnwe'
assert sf.format(ss, field='zzz', notfield='xxx') == 'checking helll kpoqjkf3483297 18934417 hnhfnqi^$&*@&2 1748912$&#^$\n467812\n^$ jfimopw279\nashyudiqhw zzz fwekojnwe\njjhjhjxxx'
assert sf.format(sss, stuff=[ { 'field1': { 'field2': '0x1234' } } ]) == 'const int complex_stuff = 0x1234;'
if __name__ == '__main__':
test()
main()