Skip to content

Commit

Permalink
Handle %s strings with length and alignment
Browse files Browse the repository at this point in the history
%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.
  • Loading branch information
Ralf Ertzinger committed Nov 16, 2023
1 parent 651c822 commit 4acbc3d
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 @@ -53,6 +53,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 aggressive and 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 4acbc3d

Please sign in to comment.