Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-37910: argparse usage wrapping should allow whitespace differences caused by metavar #15372

7 changes: 5 additions & 2 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,11 @@ def _format_usage(self, usage, actions, groups, prefix):
pos_usage = format(positionals, groups)
opt_parts = _re.findall(part_regexp, opt_usage)
pos_parts = _re.findall(part_regexp, pos_usage)
assert ' '.join(opt_parts) == opt_usage
assert ' '.join(pos_parts) == pos_usage

# ignore extra whitespace differences
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some tests in test_argparse.py verifying this behavior?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely. It might not be too soon since work's a bit busy at the moment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review, @epicfaace. I added several new tests for a range of special characters.

# can happen if metavar='', '\n', or '\t'
assert ' '.join(opt_parts).split() == opt_usage.split()
assert ' '.join(pos_parts).split() == pos_usage.split()

# helper for wrapping lines
def get_lines(parts, indent, prefix=None):
Expand Down
186 changes: 186 additions & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -5224,6 +5224,192 @@ def test_nargs_3_metavar_length3(self):
self.do_test_no_exception(nargs=3, metavar=("1", "2", "3"))


class TestAddArgumentMetavarWrapNoException(TestCase):
"""Check that certain special character wrap with no exceptions
Based off TestAddArgumentMetavar"""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please combine all of these into a single test method that runs the cases in a loop with unittest subtests.

def do_test_no_exception(self, metavar):
parser = argparse.ArgumentParser(prog='PROG' * 19) # force wrapping
parser.add_argument("--foo", metavar=metavar)
parser.format_usage()

def test_metavar_nil(self):
self.do_test_no_exception(metavar='')

def test_metavar_space(self):
self.do_test_no_exception(metavar=' ')

def test_metavar_Line_Feed(self):
self.do_test_no_exception(metavar='\n')

def test_metavar_Tab(self):
self.do_test_no_exception(metavar='\t')

def test_metavar_Carriage_Return(self):
self.do_test_no_exception(metavar='\r')

def test_metavar_Carriage_Return_and_Line_Feed(self):
self.do_test_no_exception(metavar='\r\n')

# The rest would be unlikely in practice but should not fail

def test_metavar_vLine_Tabulation(self):
self.do_test_no_exception(metavar='\v')

def test_metavar_x0b_Line_Tabulation(self):
self.do_test_no_exception(metavar='\x0b')

def test_metavar_f_Form_Feed(self):
self.do_test_no_exception(metavar='\f')

def test_metavar_x0c_Form_Feed(self):
self.do_test_no_exception(metavar='\x0c')

def test_metavar_File_Separator(self):
self.do_test_no_exception(metavar='\x1c')

def test_metavar_Group_Separator(self):
self.do_test_no_exception(metavar='\x1d')

def test_metavar_Record_Separator(self):
self.do_test_no_exception(metavar='\x1e')

def test_metavar_C1_Control_Code(self):
self.do_test_no_exception(metavar='\x85')

def test_metavar_Line_Separator(self):
self.do_test_no_exception(metavar='\u2028')

def test_metavar_Paragraph_Separator(self):
self.do_test_no_exception(metavar='\u2029')

def test_metavar_backslash(self):
self.do_test_no_exception(metavar='\\')

def test_metavar_single_quote(self):
self.do_test_no_exception(metavar='\'')

def test_metavar_double_quote(self):
self.do_test_no_exception(metavar='\"')

def test_metavar_ASCII_bell(self):
self.do_test_no_exception(metavar='\a')

def test_metavar_ASCII_backspace(self):
self.do_test_no_exception(metavar='\b')

# Unicode whitespaces per wikipedia.org/wiki/Whitespace_character

def test_metavar_unicode_horizontal_tab(self):
self.do_test_no_exception(metavar='\u0009')

def test_metavar_unicode_line_feed(self):
self.do_test_no_exception(metavar='\u000A')

def test_metavar_unicode_vertical_tab(self):
self.do_test_no_exception(metavar='\u000B')

def test_metavar_unicode_form_feed(self):
self.do_test_no_exception(metavar='\u000C')

def test_metavar_unicode_carriage_return(self):
self.do_test_no_exception(metavar='\u000D')

def test_metavar_unicode_space(self):
self.do_test_no_exception(metavar='\u0020')

def test_metavar_unicode_next_line(self):
self.do_test_no_exception(metavar='\u0085')

def test_metavar_unicode_non_breaking_space(self):
self.do_test_no_exception(metavar='\u00A0')

def test_metavar_unicode_ogham_space_mark(self):
self.do_test_no_exception(metavar='\u1680')

def test_metavar_unicode_en_quad(self):
self.do_test_no_exception(metavar='\u2000')

def test_metavar_unicode_em_quad(self):
self.do_test_no_exception(metavar='\u2001')

def test_metavar_unicode_en_space(self):
self.do_test_no_exception(metavar='\u2002')

def test_metavar_unicode_em_space(self):
self.do_test_no_exception(metavar='\u2003')

def test_metavar_unicode_three_per_em_space(self):
self.do_test_no_exception(metavar='\u2004')

def test_metavar_unicode_four_per_em_space(self):
self.do_test_no_exception(metavar='\u2005')

def test_metavar_unicode_six_per_em_space(self):
self.do_test_no_exception(metavar='\u2006')

def test_metavar_unicode_figure_space(self):
self.do_test_no_exception(metavar='\u2007')

def test_metavar_unicode_puctuation_space(self):
self.do_test_no_exception(metavar='\u2008')

def test_metavar_unicode_thin_space(self):
self.do_test_no_exception(metavar='\u2009')

def test_metavar_unicode_hair_space(self):
self.do_test_no_exception(metavar='\u200A')

def test_metavar_unicode_line_separator(self):
self.do_test_no_exception(metavar='\u2028')

def test_metavar_unicode_paragraph_separator(self):
self.do_test_no_exception(metavar='\u2029')

def test_metavar_unicode_narrow_no_break_space(self):
self.do_test_no_exception(metavar='\u202F')

def test_metavar_unicode_medium_mathematical_space(self):
self.do_test_no_exception(metavar='\u205F')

def test_metavar_unicode_ideographic_space(self):
self.do_test_no_exception(metavar='\u3000')

def test_metavar_unicode_mongolian_vowel_separator(self):
self.do_test_no_exception(metavar='\u180E')

def test_metavar_unicode_zero_width_space(self):
self.do_test_no_exception(metavar='\u200B')

def test_metavar_unicode_zero_width_non_joiner(self):
self.do_test_no_exception(metavar='\u200C')

def test_metavar_unicode_zero_width_joiner(self):
self.do_test_no_exception(metavar='\u200D')

def test_metavar_unicode_word_joiner(self):
self.do_test_no_exception(metavar='\u2060')

def test_metavar_unicode_zero_width_non_breaking_space(self):
self.do_test_no_exception(metavar='\uFEFF')

# visible 'whitespace' characters, mainly with typesetting usages.
def test_metavar_unicode_middle_dot(self):
self.do_test_no_exception(metavar='\u00B7')

def test_metavar_unicode_shouldered_open_box(self):
self.do_test_no_exception(metavar='\u237D')

def test_metavar_unicode_symbol_for_space(self):
self.do_test_no_exception(metavar='\u2420')

def test_metavar_unicode_blank_symbol(self):
self.do_test_no_exception(metavar='\u2422')

def test_metavar_unicode_open_box(self):
self.do_test_no_exception(metavar='\u2423')


class TestInvalidNargs(TestCase):

EXPECTED_INVALID_MESSAGE = "invalid nargs value"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
argparse.py now allows metavar to be certain whitespace characters, such as
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls use :mod:argparse in here.

'' and '\t'. This fixes a bug where wrapping usage across multiple lines
would raise an assertion error.