Merge lp:~frankban/lpsetup/help-fixes into lp:lpsetup

Proposed by Francesco Banconi
Status: Merged
Approved by: Francesco Banconi
Approved revision: 95
Merged at revision: 75
Proposed branch: lp:~frankban/lpsetup/help-fixes
Merge into: lp:lpsetup
Diff against target: 547 lines (+305/-34)
10 files modified
lpsetup/argparser.py (+45/-4)
lpsetup/cli.py (+32/-10)
lpsetup/subcommands/finish_init_target.py (+18/-0)
lpsetup/subcommands/init_target.py (+29/-3)
lpsetup/subcommands/initlxc.py (+42/-6)
lpsetup/subcommands/initrepo.py (+48/-9)
lpsetup/subcommands/install_lxc.py (+22/-0)
lpsetup/subcommands/update.py (+20/-2)
lpsetup/tests/test_argparser.py (+29/-0)
lpsetup/tests/test_cli.py (+20/-0)
To merge this branch: bzr merge lp:~frankban/lpsetup/help-fixes
Reviewer Review Type Date Requested Status
Francesco Banconi (community) Approve
Brad Crittenden (community) code Approve
Review via email: mp+120957@code.launchpad.net

Commit message

Improved help messages.

Description of the change

= Summary =

This branch introduces 2 main changes:

1) Remove step names repetition in subcommands help.

This is achieved easily by overriding the `metavar` while creating the steps actions. By default, argparse generates the metavar (the value displayed as example in the help for a specific argument) using choices where present. Now the metavar is just 'STEP_NAME', and the choices are explicitly listed in the action's help.

2) Make subcommands help more descriptive, adding usage examples.

This is less trivial.

I added a custom argparse HelpFormatter that preserves newlines and tabs. The default one just replaces \s+ with a single space and the uses textwrap.fill() to format the resulting text. Now textwrap.fill() is used on each line, and tabs are preserved: they are used to indent command line examples.

I also introduced the concept of "epilog" in the subcommands protocol, as an optional name. If a subcommand defines an epilog, that value is used when creating the subparser. The epilogs are already supported by the Python argparse library, and represent texts to be added at the end of the auto-generated help messages.

The global help (i.e. `lp-setup help`) epilog is generated collecting subcommands' epilogs. `cli.SUBCOMMANDS` is used to sort all the epilogs. As a consequence, I reordered the subcommands list so that the generated help message makes sense.

Added an epilog for each subcommand: this strongly increased this MP diff.

== Examples ==

Here is the output of `./lp-setup help init-lxc` in trunk:
http://pastebin.ubuntu.com/1162421/

Here is the output of the same command in this branch:
http://pastebin.ubuntu.com/1162422/

== Other changes ==

The help now is wrapped at terminal width. Looking at the argparse code I've seen that HelpFormatter can be instantiated passing a width, representing the number of columns used to wrap the help message. We already have a reliable function that calculates the terminal width, so, I thought this could be a nice improvement.

In the help of optional argument attached to the parser by subcommands we often had the default value explicitly added, e.g. help='the help [DEFAULT={0}]'.format(default_value). I've found that argparse automatically formats the help strings passing a context that already contains the default value. So this branch replaces the line above with: help='the help [DEFAULT=%(default)s]'.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Thanks for the fix. The help is much clearer now.

* typo: exaustive -> exhaustive

* I find this text confusing: See -s, --steps help for a list of available steps.
  It makes it look like it is suggesting you run 'lp-setup update --steps help' as a new command. How about simply 'See the help above for --steps for the list of available steps.' ?

* Using 'lp-setup -h' I see:

usage: lp-setup [-h]

                {init-lxc,init-target,init-repo,update,finish-init-target,install-lxc,version,
help}
                ...

Create and update Launchpad development and testing environments.

optional arguments:
  -h, --help show this help message and exit

subcommands:
  {init-lxc,init-target,init-repo,update,finish-init-target,install-lxc,version,help}

Is it possible to suppress the multiple display of subcommands in a similar way as you did for steps?

* In HelpFormatter docstring, add a comma to become 'text, preserving'.

* Similarly, "Return text wrapped preserving new lines and tabs." ->
"Return text wrapped while preserving new lines and tabs."

* In cli.py, SUBCOMMANDS could be a tuple. The proper ordering is great to have.

* I find this hard to read: "Also add the help subcommand and the global epilog generated collecting all subcommands' epilogs."

How about:
"Also add the help subcommand and the global epilog, which is generated by collecting the epilogs from all of the subcommands."

Thanks for the changes. The help is so much more readable now.

review: Approve (code)
lp:~frankban/lpsetup/help-fixes updated
93. By Francesco Banconi

Fixed typos.

94. By Francesco Banconi

SUBCOMMANDS as a tuple.

95. By Francesco Banconi

Suppress multiple display of available subcommands.

Revision history for this message
Francesco Banconi (frankban) wrote :

Thanks Brad, I changed this branch following your suggestions.
Re-approving so that the branch can land.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lpsetup/argparser.py'
2--- lpsetup/argparser.py 2012-08-13 10:30:06 +0000
3+++ lpsetup/argparser.py 2012-08-23 14:01:21 +0000
4@@ -30,6 +30,10 @@
5 - add_arguments(parser): a callable that receives a parser and can
6 add subcommand specific arguments to that parser.
7
8+ - epilog: a string representing text to be added at the end of the
9+ auto-generated help message. The text can contain `%(prog)s` as a
10+ placeholder to current subcommand name, e.g 'lpsetup init-target'.
11+
12 - has_dry_run: True if the subcommand supports dry run execution,
13 False otherwise. If not defined, False is considered.
14
15@@ -156,6 +160,29 @@
16 from lpsetup.utils import get_terminal_width
17
18
19+class HelpFormatter(argparse.HelpFormatter):
20+ """A customized help formatter for `argparse`.
21+
22+ This formatter wraps text, preserving new lines and tabs.
23+ """
24+
25+ def _fill_text(self, text, width, indent):
26+ """Return text wrapped while preserving new lines and tabs."""
27+ lines = []
28+ for line in text.splitlines():
29+ # The default formatter here replaces newlines, multiple spaces
30+ # and tabs with a single space. Newlines are not present here
31+ # (since we already called *splitlines()*) and we want to preserve
32+ # tabs, so we only need to remove multiple whitespaces.
33+ cleaned = ' '.join(filter(None, line.split(' ')))
34+ # Wrap the line at given *width*.
35+ wrapped = textwrap.fill(
36+ cleaned, width,
37+ initial_indent=indent, subsequent_indent=indent)
38+ lines.append(wrapped)
39+ return '\n'.join(lines)
40+
41+
42 class ArgumentParser(argparse.ArgumentParser):
43 """A customized argument parser for `argparse`."""
44
45@@ -175,6 +202,13 @@
46 action = super(ArgumentParser, self).add_argument(*args, **kwargs)
47 self.actions.append(action)
48
49+ def _get_formatter(self):
50+ """Override to use a customized help formatter.
51+
52+ Also create the formatter instance wrapping help at terminal width.
53+ """
54+ return HelpFormatter(prog=self.prog, width=get_terminal_width())
55+
56
57 class BaseSubCommand(object):
58 """Base class defining a subcommand.
59@@ -297,11 +331,18 @@
60 """Add steps related arguments."""
61 step_names = [get_step_name(i[0]) for i in self.steps]
62 parser.add_argument(
63- '-s', '--steps', nargs='+', choices=step_names,
64- help='Call one or more internal functions.')
65+ '-s', '--steps',
66+ choices=step_names, metavar='STEP_NAME', nargs='+',
67+ # argparse internally formats the help string using choices
68+ # if they are present as an attribute of the action.
69+ help='Call one or more internal functions. '
70+ 'Available steps for this subcommand, in the order they '
71+ 'are executed: %(choices)s.')
72 parser.add_argument(
73- '--skip-steps', nargs='+', choices=step_names,
74- help='Skip one or more internal functions.')
75+ '--skip-steps',
76+ choices=step_names, metavar='STEP_NAME', nargs='+',
77+ help='Skip one or more internal functions. See the help above '
78+ 'for --steps for the list of available steps.')
79
80 def get_description(self, namespace):
81 """Collect steps' descriptions and return them as a single string."""
82
83=== modified file 'lpsetup/cli.py'
84--- lpsetup/cli.py 2012-08-15 20:50:45 +0000
85+++ lpsetup/cli.py 2012-08-23 14:01:21 +0000
86@@ -13,6 +13,7 @@
87 ]
88
89 import sys
90+
91 from lpsetup import (
92 __doc__ as description,
93 argparser,
94@@ -30,15 +31,17 @@
95 from lpsetup.utils import confirm
96
97
98-SUBCOMMANDS = [
99- ('finish-init-target', finish_init_target.SubCommand()),
100+# The global help epilog, built collecting subcommands' epilogs,
101+# reflects the order of the following sequence.
102+SUBCOMMANDS = (
103+ ('init-lxc', initlxc.SubCommand()),
104 ('init-target', init_target.SubCommand()),
105- ('init-lxc', initlxc.SubCommand()),
106 ('init-repo', initrepo.SubCommand()),
107+ ('update', update.SubCommand()),
108+ ('finish-init-target', finish_init_target.SubCommand()),
109 ('install-lxc', install_lxc.SubCommand()),
110- ('update', update.SubCommand()),
111 ('version', version.SubCommand()),
112- ]
113+ )
114
115
116 def run(parser, subcommand, namespace):
117@@ -124,19 +127,38 @@
118 def get_parser(subcommands):
119 """Return the argument parser with all subcommands registered.
120
121- Also add the help subcommand.
122+ Also add the help subcommand and the global epilog, which is generated
123+ by collecting the epilogs from all of the subcommands.
124 """
125 parser = argparser.ArgumentParser(description=description)
126 subparsers = parser.add_subparsers(
127- title='subcommands',
128- help='Each subcommand accepts --h or --help to describe it.')
129+ title='positional arguments', metavar='subcommand',
130+ help='One of the following subcommands. Each subcommand accepts '
131+ '-h or --help to describe it.')
132+ epilogs = ['%(prog)s provides several subcommands that can be used to '
133+ 'create and update Launchpad development and testing '
134+ 'environments. Examples of how to use the available '
135+ 'subcommands follow.']
136 for name, subcommand in subcommands:
137+ # Prepare the subcommand help and epilog.
138+ help = subcommand.help
139+ epilog = getattr(subcommand, 'epilog', None)
140+ if epilog is not None:
141+ # Retrieve the program name including base name and subcommand.
142+ prog = '{0} {1}'.format(parser.prog, name)
143+ epilogs.extend([
144+ '=== {0} ==='.format(prog),
145+ epilog % {'prog': prog}
146+ ])
147 # Register the subcommand.
148- help = subcommand.help
149- subparser = subparsers.add_parser(name, description=help, help=help)
150+ subparser = subparsers.add_parser(
151+ name, description=help, help=help, epilog=epilog)
152 # Prepare the subcommand arguments.
153 prepare_parser(subparser, subcommand)
154
155+ # Add the global epilog.
156+ parser.epilog = '\n\n'.join(epilogs)
157+
158 # Add the *help* subcommand.
159 def main(namespace):
160 command = namespace.command
161
162=== modified file 'lpsetup/subcommands/finish_init_target.py'
163--- lpsetup/subcommands/finish_init_target.py 2012-08-15 23:34:34 +0000
164+++ lpsetup/subcommands/finish_init_target.py 2012-08-23 14:01:21 +0000
165@@ -68,6 +68,24 @@
166 )
167 handlers = (handle_user, handle_target_dir)
168
169+ epilog = """`%(prog)s` finalizes the Launchpad environment set up.
170+ This subcommand must be run once the Launchpad code is retrieved.
171+
172+ \tcd mybranch
173+ \t$ %(prog)s
174+
175+ This is completely equivalent to the following command:
176+
177+ \t$ %(prog)s mybranch
178+
179+ A user name is required to set up the Launchpad database, and by \
180+ default the current system user name is used. If instead this \
181+ subcommand is run as root, the user name needs to be explicitly \
182+ specified:
183+
184+ \t$ %(prog)s -u myuser
185+ """
186+
187 def add_arguments(self, parser):
188 super(SubCommand, self).add_arguments(parser)
189 parser.add_argument(
190
191=== modified file 'lpsetup/subcommands/init_target.py'
192--- lpsetup/subcommands/init_target.py 2012-08-15 23:34:34 +0000
193+++ lpsetup/subcommands/init_target.py 2012-08-23 14:01:21 +0000
194@@ -305,6 +305,33 @@
195 handle_ssh_keys,
196 )
197
198+ epilog = """`%(prog)s` prepares a Launchpad environment in the current \
199+ machine, setting up ssh keys if needed and initializing Launchpad \
200+ dependencies. The machine can either be your local system, an LXC \
201+ container or a cloud instance. However, please refer to \
202+ `lp-setup init-lxc` for an more convenient way to set up a Launchpad \
203+ environment inside an LXC.
204+
205+ Prepare a machine to run Launchpad:
206+
207+ \t$ %(prog)s
208+
209+ By default the home of the current user is configured, but if this \
210+ subcommand is run as root, you need to explicitly specify the user:
211+
212+ \t# %(prog)s -u myuser
213+
214+ In this case, if `myuser` does not exist, it will be created.
215+
216+ The current user (or the provided one) should have a correctly \
217+ configured bazaar user id (i.e `bzr whoami`). However, sometimes this \
218+ is not possible (e.g. when the provided user does not exist). In this \
219+ case, you can explicitly pass the user's full name and email and \
220+ the bazaar user id will be set up using provided arguments:
221+
222+ \t# %(prog)s -u myuser -f 'My User' -e myuser@example.com
223+ """
224+
225 def add_arguments(self, parser):
226 super(SubCommand, self).add_arguments(parser)
227 parser.add_argument(
228@@ -328,6 +355,5 @@
229 'user name is used.')
230 parser.add_argument(
231 '-S', '--ssh-key-name', default=SSH_KEY_NAME,
232- help='{0} [DEFAULT={1}]'.format(
233- 'The ssh key name used to connect to Launchpad.',
234- SSH_KEY_NAME))
235+ help='The ssh key name used to connect to Launchpad. '
236+ '[DEFAULT=%(default)s]')
237
238=== modified file 'lpsetup/subcommands/initlxc.py'
239--- lpsetup/subcommands/initlxc.py 2012-08-16 09:59:20 +0000
240+++ lpsetup/subcommands/initlxc.py 2012-08-23 14:01:21 +0000
241@@ -264,22 +264,58 @@
242 help = __doc__
243 root_required = True
244
245+ epilog = """`%(prog)s` creates an LXC container bind mounting the home \
246+ directory of the current user, setting up ssh keys if needed and \
247+ initializing Launchpad dependencies.
248+
249+ Create and initialize an LXC named `mylxc`:
250+
251+ \t$ %(prog)s mylxc
252+
253+ The default LXC name is `{lxc_name}`, so, to create and initialize \
254+ an LXC named `{lxc_name}`, you can avoid passing the name:
255+
256+ \t$ %(prog)s
257+
258+ By default the home of the current user is bind mounted, but if this \
259+ subcommand is run as root, you need to explicitly specify the user:
260+
261+ \t# %(prog)s -u myuser
262+
263+ In this case, if `myuser` does not exist, it will be created.
264+
265+ The current user (or the provided one) should have a correctly \
266+ configured bazaar user id (i.e `bzr whoami`). However, sometimes this \
267+ is not possible (e.g. when the provided user does not exist). In this \
268+ case, you can explicitly pass the user's full name and email and \
269+ the bazaar user id will be set up using provided arguments:
270+
271+ \t# %(prog)s -u myuser -f 'My User' -e myuser@example.com
272+
273+ At the end of the process the newly created LXC instance is running, \
274+ and it is possible to obtain its ip address using:
275+
276+ \t$ sudo lp-lxc-ip -n mylxc
277+
278+ If instead you want `%(prog)s` to stop the instance once it is \
279+ completely configured, you can use the `--stop-lxc` argument:
280+
281+ \t$ %(prog)s --stop-lxc
282+ """.format(lxc_name=LXC_NAME)
283+
284 def add_arguments(self, parser):
285 super(SubCommand, self).add_arguments(parser)
286 # Add LXC related arguments.
287 parser.add_argument(
288 'lxc_name', nargs='?', default=LXC_NAME,
289- help='The LXC container name to setup. '
290- '[DEFAULT={0}]'.format(LXC_NAME))
291+ help='The LXC container name to setup. [DEFAULT=%(default)s]')
292 parser.add_argument(
293 '-A', '--lxc-arch', default=LXC_GUEST_ARCH,
294- help='The LXC container architecture. '
295- '[DEFAULT={0}]'.format(LXC_GUEST_ARCH))
296+ help='The LXC container architecture. [DEFAULT=%(default)s]')
297 parser.add_argument(
298 '-R', '--lxc-os', default=LXC_GUEST_OS,
299 choices=LXC_GUEST_CHOICES,
300- help='The LXC container distro codename. '
301- '[DEFAULT={0}]'.format(LXC_GUEST_OS))
302+ help='The LXC container distro codename. [DEFAULT=%(default)s]')
303 parser.add_argument(
304 '--stop-lxc', action='store_true',
305 help='Shut down the LXC container at the end of the process.')
306
307=== modified file 'lpsetup/subcommands/initrepo.py'
308--- lpsetup/subcommands/initrepo.py 2012-08-09 15:32:08 +0000
309+++ lpsetup/subcommands/initrepo.py 2012-08-23 14:01:21 +0000
310@@ -35,7 +35,9 @@
311 LP_BRANCH_NAME,
312 LP_BZR_LOCATIONS,
313 LP_CHECKOUT_NAME,
314+ LP_HTTP_REPO,
315 LP_REPOSITORY_DIR,
316+ LP_SSH_REPO,
317 )
318 from lpsetup.utils import (
319 call,
320@@ -148,6 +150,44 @@
321 )
322 root_required = False
323
324+ epilog = """Once a Launchpad environment is correctly initialized \
325+ (i.e. using `init-lxc` or `init-target`) `%(prog)s` can be used \
326+ to retrieve the Launchpad code and its dependencies.
327+
328+ By default, the code is retrieved from `{ssh_repo}`, a shared \
329+ repository without trees is created in `{repository}`, containing a \
330+ lightweight checkout named `{checkout}`:
331+
332+ \t$ %(prog)s
333+
334+ However, it is possible to change these parameters, e.g. to create a \
335+ repository in `~/mylaunchpad` containing a lightweight checkout \
336+ `mycheckout` and retrieving code from `lp:~myuser/launchpad/mybranch`:
337+
338+ \t$ %(prog)s --source lp:~myuser/launchpad/mybranch -r ~/mylaunchpad \
339+ --checkout-name mycheckout
340+
341+ As seen above, the default behavior is to get the code using bzr+ssh: \
342+ we assume you set up a valid Launchpad user id. This way you can \
343+ contribute to Launchpad, commit code, fix bugs, etc. Anyway, \
344+ sometimes an http checkout can be more convenient (e.g. when creating \
345+ testing environments). To retrieve Launchpad from `{http_repo}`:
346+
347+ \t$ %(prog)s --use-http
348+
349+ Lightweight checkouts are considered an effective way of working with \
350+ bazaar repositories. On the other hand, developers may prefer other \
351+ approaches (normal branches with trees, co-located branches). It is \
352+ possible to initialize a Launchpad repository with trees as follow:
353+
354+ \t$ %(prog)s --no-checkout --branch-name trunk
355+
356+ If `--branch-name` is not provided `{branch}` is used by default.
357+ """.format(
358+ branch=LP_BRANCH_NAME, checkout=LP_CHECKOUT_NAME,
359+ http_repo=LP_HTTP_REPO, repository=LP_REPOSITORY_DIR,
360+ ssh_repo=LP_SSH_REPO)
361+
362 @staticmethod
363 def add_common_arguments(parser):
364 parser.add_argument(
365@@ -161,23 +201,22 @@
366 '--source is provided. Defaults to off.')
367 parser.add_argument(
368 '--branch-name', default=LP_BRANCH_NAME,
369- help='Name of the directory in which to place the branch. '
370- 'Defaults to {0}.'.format(LP_BRANCH_NAME))
371+ help='Name of the directory in which to place the branch. '
372+ '[DEFAULT=%(default)s]')
373 parser.add_argument(
374 '--checkout-name', default=LP_CHECKOUT_NAME,
375- help='Create a checkout with the given name. '
376- 'Ignored if --no-checkout is specified. '
377- 'Defaults to {0}.'.format(LP_CHECKOUT_NAME))
378+ help='Create a checkout with the given name. Ignored if '
379+ '--no-checkout is specified. [DEFAULT=%(default)s]')
380 parser.add_argument(
381 '-r', '--repository', default=LP_REPOSITORY_DIR,
382 help='The directory of the Launchpad repository to be created. '
383 'The directory must reside under the home directory of the '
384- 'given user (see -u argument). '
385- '[DEFAULT={0}]'.format(LP_REPOSITORY_DIR))
386+ 'given user (see -u argument). [DEFAULT=%(default)s]')
387 parser.add_argument(
388 '--no-checkout', action='store_true', dest='no_checkout',
389- default=False, help='Initialize a bzr repository with trees and '
390- 'do not create a lightweight checkout.')
391+ default=False,
392+ help='Initialize a bzr repository with trees and '
393+ 'do not create a lightweight checkout.')
394
395 def add_arguments(self, parser):
396 super(SubCommand, self).add_arguments(parser)
397
398=== modified file 'lpsetup/subcommands/install_lxc.py'
399--- lpsetup/subcommands/install_lxc.py 2012-08-16 09:59:20 +0000
400+++ lpsetup/subcommands/install_lxc.py 2012-08-23 14:01:21 +0000
401@@ -158,6 +158,28 @@
402 )
403 help = __doc__
404
405+ epilog = """`%(prog)s` sets up a complete Launchpad environment in an LXC \
406+ container, creating the LXC, configuring the instance, retrieving the code.
407+
408+ This is basically the same of running `init-lxc` and then, inside the \
409+ newly created container, `init-repo`, `update` and `finish-init-target`.
410+
411+ Set up Launchpad for the current user:
412+
413+ \t%(prog)s
414+
415+ A more complex example: set up an environment for `myuser`, creating a \
416+ repository in a customized path, using a different ssh key and activating \
417+ the parallel testing tweaks.
418+
419+ \t%(prog)s -u myuser -E myuser@example.com -f 'My User' \
420+ -r ~/my-lp-branches -S launchpad_lxc_id_rsa --testing
421+
422+ %(prog)s inherits its arguments from the subcommands it wraps. \
423+ You can refer to the help of all the other subcommands for a more \
424+ exhaustive explanation.
425+ """
426+
427 def get_handlers(self, namespace):
428 handlers = super(SubCommand, self).get_handlers(namespace)
429 return handlers + (
430
431=== modified file 'lpsetup/subcommands/update.py'
432--- lpsetup/subcommands/update.py 2012-08-09 09:17:34 +0000
433+++ lpsetup/subcommands/update.py 2012-08-23 14:01:21 +0000
434@@ -98,6 +98,25 @@
435 handlers.handle_target_dir,
436 )
437
438+ epilog = """`%(prog)s` updates an existing Launchpad branch, contextually \
439+ retrieving the current external sources and dependencies.
440+
441+ To update a branch in the current working directory:
442+
443+ \tcd ~/launchpad/lp-branches/mybranch
444+ \t%(prog)s
445+
446+ To update a branch specifying its path:
447+
448+ \t%(prog)s ~/launchpad/lp-branches/mybranch
449+
450+ By default this subcommand updates the download cache checking out \
451+ {deps}. To change the source repository for the cache, use \
452+ `--lp-source-deps`:
453+
454+ \t%(prog)s --lp-source-deps my-source-deps
455+ """.format(deps=LP_SOURCE_DEPS)
456+
457 @staticmethod
458 def add_common_arguments(parser):
459 parser.add_argument(
460@@ -107,8 +126,7 @@
461 'working-dir. ')
462 parser.add_argument(
463 '--lp-source-deps', default=LP_SOURCE_DEPS,
464- help='URL of LP source deps branch. '
465- '[DEFAULT={0}]'.format(LP_SOURCE_DEPS))
466+ help='URL of LP source deps branch. [DEFAULT=%(default)s]')
467
468 def add_arguments(self, parser):
469 super(SubCommand, self).add_arguments(parser)
470
471=== modified file 'lpsetup/tests/test_argparser.py'
472--- lpsetup/tests/test_argparser.py 2012-08-13 09:44:02 +0000
473+++ lpsetup/tests/test_argparser.py 2012-08-23 14:01:21 +0000
474@@ -8,6 +8,7 @@
475 from collections import OrderedDict
476 import itertools
477 import sys
478+import textwrap
479 import unittest
480
481 from lpsetup import argparser
482@@ -222,6 +223,34 @@
483 self.assertStartsWith(' ', second_line)
484
485
486+class HelpFormatterTest(unittest.TestCase):
487+
488+ def test_newlines(self):
489+ # Ensure the customized help formatter wraps text preserving new lines.
490+ formatter = argparser.HelpFormatter('prog', width=25)
491+ wrapped = 'This text will be wrapped.'
492+ preserved = '\nNew lines\nare preserved.'
493+ formatter.add_text(wrapped + preserved)
494+ expected = textwrap.fill(wrapped, 25) + preserved + '\n'
495+ self.assertEqual(expected, formatter.format_help())
496+
497+ def test_tabs(self):
498+ # Ensure the customized help formatter wraps text preserving tabs.
499+ formatter = argparser.HelpFormatter('prog', width=80)
500+ text = '\tTabs are\tpreserved.'
501+ formatter.add_text(text)
502+ self.assertEqual(text.expandtabs() + '\n', formatter.format_help())
503+
504+ def test_whitespaces(self):
505+ # Ensure the customized help formatter replaces multiple whitespaces
506+ # with a single whitespace, and drop leading and trailing whitespaces.
507+ formatter = argparser.HelpFormatter('prog', width=80)
508+ text = ' Text with leading, trailing and multiple whitespaces. '
509+ formatter.add_text(text)
510+ expected = ' '.join(text.split()) + '\n'
511+ self.assertEqual(expected, formatter.format_help())
512+
513+
514 class InitNamespaceTest(unittest.TestCase):
515
516 def test_user_info_in_namespace(self):
517
518=== modified file 'lpsetup/tests/test_cli.py'
519--- lpsetup/tests/test_cli.py 2012-08-13 08:42:08 +0000
520+++ lpsetup/tests/test_cli.py 2012-08-23 14:01:21 +0000
521@@ -64,6 +64,26 @@
522 # The help subcommand also prints arguments info.
523 self.assertIn('--foo', output_value)
524
525+ def test_epilog_in_global_help(self):
526+ # The epilog attribute of subcommands is used to generate
527+ # the base command usage message.
528+ subcommand = self.make_subcommand(epilog='epilog message')
529+ parser = cli.get_parser([('foo', subcommand)])
530+ help = parser.format_help()
531+ self.assertIn(subcommand.epilog, help)
532+
533+ def test_subcommand_epilog(self):
534+ # The epilog attribute of subcommands is used to create the parser,
535+ # so that the epilog text is included in the subcommand help.
536+ name = 'foo'
537+ subcommand = self.make_subcommand(epilog='epilog message')
538+ parser = cli.get_parser([(name, subcommand)])
539+ namespace = parser.parse_args(['help', name])
540+ with self.assertRaises(SystemExit):
541+ with capture_output() as output:
542+ namespace.main(namespace)
543+ self.assertIn(subcommand.epilog, output.getvalue())
544+
545 def test_subcommands(self):
546 # Ensure sub parsers are added for each registered subcommand.
547 subcommands = (

Subscribers

People subscribed via source and target branches

to all changes: