Skip to content

Commit

Permalink
Handle %s strings with length and alignment (#194)
Browse files Browse the repository at this point in the history
* Handle %s strings with length and alignment

%s format strings allow for padding and alignment, but their behaviour
is very different from fstrings.

%20s will pad a string to 20 characters, and right align
%-20s will pad a string to 20 characters, and left align

This behaviour is carried over from the C *printf() functions.

This patch adds the ability to properly convert these to fstrings, using
the correct alignment markers.

The feature is gated behind aggressive mode for now.

The earlier code did already convert %20s, but changed the alignment
(the resulting fstring would be left aligned instead of right), and did
not understand %-20s at all.

* Update src/flynt/transform/percent_transformer.py

---------

Co-authored-by: Ralf Ertzinger <[email protected]>
Co-authored-by: Ilya Kamen <[email protected]>
  • Loading branch information
3 people authored Feb 2, 2025
1 parent 10c2235 commit 30df63b
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/flynt/transform/percent_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
FORMAT_GROUP = f"[hlL]?[{FORMATS}]"
FORMAT_GROUP_MATCH = f"[hlL]?([{FORMATS}])"

PREFIX_GROUP = "[0-9]*[.]?[0-9]*"
PREFIX_GROUP = "[+-]?[0-9]*[.]?[0-9]*"

ANY_DICT = re.compile(r"(?<!%)%\([^)]+?\)")
DICT_PATTERN = re.compile(rf"(%\([^)]+\){PREFIX_GROUP}{FORMAT_GROUP})")
Expand Down Expand Up @@ -68,6 +68,32 @@ def formatted_value(
fmt_prefix = fmt_prefix.replace(".", "0")

if fmt_spec in conversion_methods:
if fmt_spec == "s" and fmt_prefix:
# Strings are right aligned in percent fmt by default, and indicate
# left alignment through a negative prefix.
#
# fstrings left align by default, and separate signs from alignment
#
# Python even accepts float values here, for both percent fmt
# and fstrings
#
# In order to not have to figure out what sort of number we are
# dealing with, just look at the leading - sign (if there is one)
# and remove it for the conversion
if fmt_prefix.startswith("-"):
# Left alignment
return ast_formatted_value(
val,
fmt_str=f"{fmt_prefix[1:]}",
conversion=conversion_methods[fmt_spec],
)
if aggressive and not fmt_prefix.startswith("-"):
# Right alignment
return ast_formatted_value(
val,
fmt_str=f">{fmt_prefix}",
conversion=conversion_methods[fmt_spec],
)
if not aggressive and fmt_prefix:
raise ConversionRefused(
"Default text alignment has changed between percent fmt and fstrings. "
Expand Down
18 changes: 18 additions & 0 deletions test/test_edits.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ def test_call(state: State):

def test_string_specific_len(state: State):
s_in = """'%5s' % CLASS_NAMES[labels[j]]"""
s_expected = """f'{CLASS_NAMES[labels[j]]:>5}'"""

state.aggressive = True
s_out, count = code_editor.fstringify_code_by_line(s_in, state)
assert s_out == s_expected


def test_string_specific_len_right_aligned(state: State):
s_in = """'%5s' % CLASS_NAMES[labels[j]]"""
s_expected = """f'{CLASS_NAMES[labels[j]]:>5}'"""

state.aggressive = True
s_out, count = code_editor.fstringify_code_by_line(s_in, state)
assert s_out == s_expected


def test_string_specific_len_left_aligned(state: State):
s_in = """'%-5s' % CLASS_NAMES[labels[j]]"""
s_expected = """f'{CLASS_NAMES[labels[j]]:5}'"""

state.aggressive = True
Expand Down

0 comments on commit 30df63b

Please sign in to comment.