4

Is there a good way to parse a printf-style format string and get the various bits (literal text & format points with processable options) as part of the standard library, or failing that as a third-party library? I could not find one in the stdlib, and a web search does not seem to turn up anything useful.

Python 2.6 added string.Formatter which allows processing and customising format-style strings, but I did not manage to find a similar tool for printf-style format strings.

I'm looking for it to do things like validate translations, and possibly convert printf-style format strings to format-style ones (or even f-strings).

2
  • So, you are asking if there is a printf-style formatting for stdlib or I am lost in the question? Commented Jul 11, 2018 at 8:36
  • Yeah I kinda forgot putting the question in the clear, I've added it and reorganised the post: as the title hints I'm looking for a parser of printf-style string. Commented Jul 11, 2018 at 9:21

1 Answer 1

2

Not hard to slap one together with pyparsing, after looking at the docs:

import pyparsing as pp

# from https://docs.python.org/3/library/stdtypes.html?highlight=string%20interpolation#printf-style-string-formatting
PCT,LPAREN,RPAREN,DOT = map(pp.Literal, '%().')
conversion_flag_expr = pp.oneOf(list("#0- +"))
conversion_type_expr = pp.oneOf(list("diouxXeEfFgGcrsa%"))
length_mod_expr = pp.oneOf(list("hlL"))
interp_expr = (PCT
               + pp.Optional(LPAREN + pp.Word(pp.printables, excludeChars=")")("mapping_key") + RPAREN)
               + pp.Optional(conversion_flag_expr("conversion_flag"))
               + pp.Optional(('*' | pp.pyparsing_common.integer)("min_width"))
               + pp.Optional(DOT + pp.pyparsing_common.integer("max_width"))
               + pp.Optional(length_mod_expr("length_modifier"))
               + conversion_type_expr("conversion_type")
               )

tests = """
    Now is the winter of our %s made %(adjective)s %(season)s by this %(relative)s of %(city)s
    %(name)s, %(name)s, %(name)s, a %4.8d deaths were not enough for %(name)s 
""".splitlines()

for t in tests:
    print(t)
    for t,s,e in interp_expr.scanString(t):
        print("start: {} end: {}\n{}".format(s, e, t.dump()))
    print()

Prints:

Now is the winter of our %s made %(adjective)s %(season)s by this %(relative)s of %(city)s
start: 29 end: 31
['%', 's']
- conversion_type: 's'
start: 37 end: 50
['%', '(', 'adjective', ')', 's']
- conversion_type: 's'
- mapping_key: 'adjective'
start: 51 end: 61
['%', '(', 'season', ')', 's']
- conversion_type: 's'
- mapping_key: 'season'
start: 70 end: 82
['%', '(', 'relative', ')', 's']
- conversion_type: 's'
- mapping_key: 'relative'
start: 86 end: 94
['%', '(', 'city', ')', 's']
- conversion_type: 's'
- mapping_key: 'city'

%(name)s, %(name)s, %(name)s, a %4.8d deaths were not enough for %(name)s 
start: 4 end: 12
['%', '(', 'name', ')', 's']
- conversion_type: 's'
- mapping_key: 'name'
start: 14 end: 22
['%', '(', 'name', ')', 's']
- conversion_type: 's'
- mapping_key: 'name'
start: 24 end: 32
['%', '(', 'name', ')', 's']
- conversion_type: 's'
- mapping_key: 'name'
start: 36 end: 41
['%', 4, '.', 8, 'd']
- conversion_type: 'd'
- max_width: 8
- min_width: 4
start: 68 end: 76
['%', '(', 'name', ')', 's']
- conversion_type: 's'
- mapping_key: 'name'

If you prefer regex, it should not be hard to convert this prototype to something workable.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.