From a3fd557914767c2faa52fea2297beb5b88cb7a73 Mon Sep 17 00:00:00 2001 From: "steven.bethard" Date: Sat, 12 Sep 2009 23:35:45 +0000 Subject: Fix bug with long prog= values. --- argparse.py | 97 +++++++++++++++++++++++++++++++------------------- test/test_argparse.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 155 insertions(+), 40 deletions(-) diff --git a/argparse.py b/argparse.py index 0f407e7..75a9043 100644 --- a/argparse.py +++ b/argparse.py @@ -327,7 +327,7 @@ class HelpFormatter(object): # if optionals and positionals are available, calculate usage elif usage is None: - usage = '%(prog)s' % dict(prog=self._prog) + prog = '%(prog)s' % dict(prog=self._prog) # split optionals from positionals optionals = [] @@ -338,44 +338,69 @@ class HelpFormatter(object): else: positionals.append(action) - # determine width of "usage: PROG" and width of text - prefix_width = len(prefix) + len(usage) + 1 - prefix_indent = self._current_indent + prefix_width - text_width = self._width - self._current_indent - - # put them on one line if they're short enough + # build full usage string format = self._format_actions_usage action_usage = format(optionals + positionals, groups) - if prefix_width + len(action_usage) + 1 < text_width: - usage = '%s %s' % (usage, action_usage) + usage = ' '.join([s for s in [prog, action_usage] if s]) - # if they're long, wrap optionals and positionals individually - else: - optional_usage = format(optionals, groups) - positional_usage = format(positionals, groups) - indent = ' ' * prefix_indent - - # usage is made of PROG, optionals and positionals - parts = [usage, ' '] - - # options always get added right after PROG - if optional_usage: - parts.append(_textwrap.fill( - optional_usage, text_width, - initial_indent=indent, - subsequent_indent=indent).lstrip()) - - # if there were options, put arguments on the next line - # otherwise, start them right after PROG - if positional_usage: - part = _textwrap.fill( - positional_usage, text_width, - initial_indent=indent, - subsequent_indent=indent).lstrip() - if optional_usage: - part = '\n' + indent + part - parts.append(part) - usage = ''.join(parts) + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + 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 + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) # prefix with 'usage:' return '%s%s\n\n' % (prefix, usage) diff --git a/test/test_argparse.py b/test/test_argparse.py index 57de0c4..447a4dd 100644 --- a/test/test_argparse.py +++ b/test/test_argparse.py @@ -2095,12 +2095,12 @@ class TestMutuallyExclusiveLong(MEMixin, TestCase): ] usage_when_not_required = '''\ - usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] [--klmno KLMNO | --pqrst - PQRST] + usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] + [--klmno KLMNO | --pqrst PQRST] ''' usage_when_required = '''\ - usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] (--klmno KLMNO | --pqrst - PQRST) + usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] + (--klmno KLMNO | --pqrst PQRST) ''' help = '''\ @@ -2855,6 +2855,96 @@ class TestHelpOnlyUserGroups(HelpTestCase): version = '' +class TestHelpUsageLongProg(HelpTestCase): + """Test usage messages where the prog is long""" + + parser_signature = Sig(prog='P' * 60) + argument_signatures = [ + Sig('-w', metavar='W'), + Sig('-x', metavar='X'), + Sig('a'), + Sig('b'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + [-h] [-w W] [-x X] a b + ''' + help = usage + '''\ + + positional arguments: + a + b + + optional arguments: + -h, --help show this help message and exit + -w W + -x X + ''' + version = '' + + +class TestHelpUsageLongProgOptionsWrap(HelpTestCase): + """Test usage messages where the prog is long and the optionals wrap""" + + parser_signature = Sig(prog='P' * 60) + argument_signatures = [ + Sig('-w', metavar='W' * 25), + Sig('-x', metavar='X' * 25), + Sig('-y', metavar='Y' * 25), + Sig('-z', metavar='Z' * 25), + Sig('a'), + Sig('b'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + [-h] [-w WWWWWWWWWWWWWWWWWWWWWWWWW] \ +[-x XXXXXXXXXXXXXXXXXXXXXXXXX] + [-y YYYYYYYYYYYYYYYYYYYYYYYYY] [-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] + a b + ''' + help = usage + '''\ + + positional arguments: + a + b + + optional arguments: + -h, --help show this help message and exit + -w WWWWWWWWWWWWWWWWWWWWWWWWW + -x XXXXXXXXXXXXXXXXXXXXXXXXX + -y YYYYYYYYYYYYYYYYYYYYYYYYY + -z ZZZZZZZZZZZZZZZZZZZZZZZZZ + ''' + version = '' + + +class TestHelpUsageLongProgPositionalsWrap(HelpTestCase): + """Test usage messages where the prog is long and the positionals wrap""" + + parser_signature = Sig(prog='P' * 60, add_help=False) + argument_signatures = [ + Sig('a' * 25), + Sig('b' * 25), + Sig('c' * 25), + ] + argument_group_signatures = [] + usage = '''\ + usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + help = usage + '''\ + + positional arguments: + aaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + version = '' + + class TestHelpUsageOptionalsWrap(HelpTestCase): """Test usage messages where the optionals wrap""" -- cgit v1.2.1