diff -r 44f455e6163d Lib/argparse.py --- a/Lib/argparse.py Thu Jun 27 12:23:29 2013 +0200 +++ b/Lib/argparse.py Fri Jun 20 13:51:38 2014 -0700 @@ -89,6 +89,7 @@ import re as _re import sys as _sys import textwrap as _textwrap +from contextlib import contextmanager as _contextmanager from gettext import gettext as _, ngettext @@ -269,6 +270,18 @@ for action in actions: self.add_argument(action) + @_contextmanager + def add_section(self, heading): + self.start_section(heading) + yield + self.end_section() + + @_contextmanager + def new_indent(self): + self._indent() + yield + self._dedent() + # ======================= # Help-formatting methods # ======================= @@ -605,9 +618,10 @@ except AttributeError: pass else: - self._indent() - yield from get_subactions() - self._dedent() + # new: context, 2.7 compatible yield + with self.new_indent(): + for subaction in get_subactions(): + yield subaction def _split_lines(self, text, width): text = self._whitespace_matcher.sub(' ', text).strip() @@ -681,8 +695,6 @@ def _get_default_metavar_for_positional(self, action): return action.type.__name__ - - # ===================== # Options and Arguments # ===================== @@ -2316,11 +2328,11 @@ formatter.add_text(self.description) # positionals, optionals and user-defined groups + # new context for action_group in self._action_groups: - formatter.start_section(action_group.title) - formatter.add_text(action_group.description) - formatter.add_arguments(action_group._group_actions) - formatter.end_section() + with formatter.add_section(action_group.title): + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) # epilog formatter.add_text(self.epilog) @@ -2331,6 +2343,56 @@ def _get_formatter(self): return self.formatter_class(prog=self.prog) + def custom_help(self, template=None, prefix=None): + if template is None: + template = """\ + %(usage)s + + %(description)s + + %(argument_groups)s + + %(epilog)s + """ + template = _textwrap.dedent(template) + # may need to clear beginning blanks + def usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups, prefix) + return formatter.format_help().strip() + def groups(self): + formatter = self._get_formatter() + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + astr = formatter.format_help().rstrip() + return astr + def description(self): + formatter = self._get_formatter() + formatter.add_text(self.description) + return formatter.format_help().rstrip() + def epilog(self): + formatter = self._get_formatter() + formatter.add_text(self.epilog) + return formatter.format_help().rstrip() + dd = dict( + usage=usage(self), + description = description(self), + argument_groups=groups(self), + epilog=epilog(self), + ) + help = template%dd + # from formatter.format_help; getting number blank lines correct + help = _re.sub(r'\n\n\n+', '\n\n', help) + help = help.strip('\n') + '\n' + return help + + def format_help(self): + return self.custom_help() + # ===================== # Help-printing methods # ===================== diff -r 44f455e6163d Lib/test/test_argparse.py --- a/Lib/test/test_argparse.py Thu Jun 27 12:23:29 2013 +0200 +++ b/Lib/test/test_argparse.py Fri Jun 20 13:51:38 2014 -0700 @@ -4904,6 +4904,108 @@ ] self.assertEqual(sorted(items), sorted(argparse.__all__)) +# ======================== +# custom help layout tests +# ======================== + +class TestCustomLayout(TestCase): + # issue 11695 + template = textwrap.dedent("""\ + My Program, version 3.5 + %(usage)s + + Some description of my program + + %(argument_groups)s + + My epilog text + """) + def test_basic_parser(self): + parser = argparse.ArgumentParser(prog='PROG') + parser.add_argument('--foo') + parser.add_argument('bar') + self.assertEqual(parser.format_help(), textwrap.dedent('''\ + usage: PROG [-h] [--foo FOO] bar + + positional arguments: + bar + + optional arguments: + -h, --help show this help message and exit + --foo FOO + ''')) + + self.assertEqual(parser.custom_help(self.template, prefix='Usage: '), + textwrap.dedent('''\ + My Program, version 3.5 + Usage: PROG [-h] [--foo FOO] bar + + Some description of my program + + positional arguments: + bar + + optional arguments: + -h, --help show this help message and exit + --foo FOO + + My epilog text + ''')) + + def test_wrapped_lines(self): + # for now wrapping only works if template items start at left + parser = argparse.ArgumentParser(prog='LONG_PROGRAM_NAME') + parser.add_argument('--long_optionals_argument_name', + help='optionals help line', metavar='LONGISH_METVAR') + parser.add_argument('long_positional_argument_name') + self.assertEqual(parser.format_help(), textwrap.dedent('''\ + usage: LONG_PROGRAM_NAME [-h] [--long_optionals_argument_name LONGISH_METVAR] + long_positional_argument_name + + positional arguments: + long_positional_argument_name + + optional arguments: + -h, --help show this help message and exit + --long_optionals_argument_name LONGISH_METVAR + optionals help line + ''')) + + self.assertEqual(parser.custom_help(self.template, prefix='Usage: '), + textwrap.dedent('''\ + My Program, version 3.5 + Usage: LONG_PROGRAM_NAME [-h] [--long_optionals_argument_name LONGISH_METVAR] + long_positional_argument_name + + Some description of my program + + positional arguments: + long_positional_argument_name + + optional arguments: + -h, --help show this help message and exit + --long_optionals_argument_name LONGISH_METVAR + optionals help line + + My epilog text + ''')) + + def test_bare_parser(self): + parser = argparse.ArgumentParser(prog='PROG', add_help=False) + self.assertEqual(parser.format_help(), textwrap.dedent('''\ + usage: PROG + ''')) + + self.assertEqual(parser.custom_help(self.template, prefix='Usage: '), + textwrap.dedent('''\ + My Program, version 3.5 + Usage: PROG + + Some description of my program + + My epilog text + ''')) + def test_main(): support.run_unittest(__name__) # Remove global references to avoid looking like we have refleaks.