root/masterdriverz/use-expand/dev-notes/commandline.rst @ marienz%2540gentoo.org-20061109120338-8e9edd06a397bb65

Revision marienz%2540gentoo.org-20061109120338-8e9edd06a397bb65, 7.4 kB (checked in by Marien Zwart <marienz@…>, 2 years ago)

Documentation.

Line 
1=======================
2 Commandline framework
3=======================
4
5Overview
6========
7
8pkgcore's own commandline tools and ideally also most external tools
9use a couple of utilities from pkgcore.util.commandline to enforce a
10consistent interface and reduce boilerplate. There are also some
11helpers for writing tests for scripts using the utilities. Finally,
12pkgcore's own scripts are started through a single wrapper (just to
13reduce boilerplate).
14
15Writing a script
16================
17
18Whether your script is intended for inclusion with pkgcore itself or
19not, the first things you should write are a commandline.OptionParser
20subclass (unless your script takes no commandline arguments) and a
21main function. The OptionParser is a lightly customized
22optparse.OptionParser, so the standard `optparse documentation`_
23applies. Differences include:
24
25* A couple of standard options and defaults are added. Some of this
26  uses __init__.py, so if you override that (which you will) remember
27  to call the base class (with any keyword arguments you received).
28* The "Values" object used is a subclass, with a "config" property
29  that autoloads the user's configuration. You should access this as
30  late as possible for a more responsive ui.
31* check_values applies some minor cleanups, see the module for
32  details. Remember to call the base method (you will usually want to
33  do some things here).
34
35The "main" function takes an optparse "values" object generated by
36your option parser and two pkgcore.util.formatters.Formatter
37instances, one for stdout and one for stderr. This one should do the
38actual work your script does.
39
40The return value of the main function is your script's exit status.
41Returning None is the same thing as returning 0 (success).
42
43If you have used optparse before you might wonder why main only
44receives an optparse values object, not the remaining arguments. This
45is handled a bit differently in pkgcore: if you handle arguments you
46should sanity-check them in check_values and store them on the values
47object. check_values should always return an empty tuple as second
48argument, either because no arguments were passed or because they were
49all accepted by check_values. We believe this makes more sense, since
50it stores everything learned from the commandline on a single object.
51
52All output *has* to go through the formatter. If you use "print"
53directly the formatter will lose track of where it is in the line,
54which will cause weird output if you use the "wrap" option of the
55formatter. The test helpers also rely on all output going through the
56formatters.
57
58To actually run your script you call pkgcore.util.commandline.main
59(do not confuse this with your own script's main function, the two are
60quite different). The simplest (and most common) call is
61``commandline.main({None: (yourscript.OptionParser, yourscript.main)})``.
62The weird dict is used for subcommands_. The recommended place to put
63this call is in a tiny script that just imports your actual script
64module and calls commandline.main. Making your script an actual module
65you can import means it can be tested (and it can be useful in
66interactive python or for quick hacky scripts).
67
68commandline.main takes care of a couple of things, including setting
69up a reporter for the standard library's logging_ package and
70swallowing exceptions from the configuration system. It does *not*
71swallow any other exceptions your script might raise (although this might
72become an option in the future).
73
74check_values and main: what goes where
75--------------------------------------
76
77The idea (as you can guess from the names) is that check_values makes
78sure everything passed on the commandline makes sense, but no more
79than that.
80
81* The best way to report incorrect commandline parameters is by
82  calling ``error("error message goes here")`` on the option parser.
83  You cannot do this from main, since it has no access to the option
84  parser. Please do not try to print something similar through the
85  ``err`` formatter here, shift the code to check_values.
86* check_values does not have access to the out or err formatter. The
87  only way it should "communicate" is through the error (or possibly
88  exit) methods. If you want to produce different kinds of output, do
89  it in main. (it is possible the option parser will grow a
90  ``warning`` method at some point, if this would be useful let us
91  know (file a trac ticket).
92* Use common sense. If it is part of your script's main task it should
93  be in main. If it changes the filesystem it should definitely be in
94  main.
95
96Subcommands
97===========
98
99The main function recently gained some support for subcommands (which
100you probably know from most version control systems). If you find
101yourself trying to reimplement this kind of interface with optparse,
102or one similar to emerge with a couple of mutually exclusive switches
103selecting a mode (--depclean, --sync etc.) then you should try using
104this subcommand system instead.
105
106To use it, simply define a separate OptionParser and main function for
107every subcommand and use the subcommand name as the key in the dict
108passed to commandline.main. The key ``None`` used for "no subcommand"
109can still be used too, but this is probably not a good idea.
110
111If there is no parser/main pair with the key ``None`` and an
112unrecognized subcommand is passed (including ``--help``) an overview
113of subcommands is printed. This uses the docstring of the __main__
114function, so put something useful there. If there is a ``None`` parser
115you should include the valid subcommands in its ``--help`` output,
116since there is no way to get at commandline.main's autogenerated
117subcommand help if a ``None`` parser is present.
118
119pwrapper
120========
121
122Because having a dozen of different scripts each just calling
123commandline.main would be silly pkgcore's own scripts are all symlinks
124to a single wrapper which imports the right actual script based on the
125``sys.argv[0]`` it is called with and runs it. The script module needs
126to define either a commandline_commands dict (for a script with
127subcommands) or a class called OptionParser and function called main
128for this to work.
129
130The script used in the source tree also takes care of inserting the
131right pkgcore package on sys.path. Installed pkgcore uses a different
132wrapper without this magic.
133
134If you write a new script that should go into pkgcore itself, use the
135wrapper. If you maintain it externally and do not have a lot of
136scripts, don't bother duplicating this wrapper system. Don't bother
137duplicating the path manipulation either: if you put your script in
138the same directory your actual package or module is in (no separate
139"bin" directory) and don't run it as root no path manipulation is
140required.
141
142Tests
143=====
144
145Because additions to the default options pkgcore uses can make your
146script unrunnable it is *critical* to have at least rudimentary tests
147that just instantiate your parser. Because optparse defaults to
148calling sys.exit for a parse failure and the pkgcore version also
149likes to load the user's configuration files, writing those tests is
150slightly tricky. ``pkgcore.test.scripts.helpers`` tries to make it
151easier. It contains a mangle_parser function that takes an
152OptionParser instance and makes it raise exceptions instead of
153exiting. It also contains a mixin with some extra assert methods that
154check if your option parser and main function have the desired effect
155on various arguments and configurations. See the docstrings for more
156information.
157
158.. _`optparse documentation`:
159     http://docs.python.org/lib/module-optparse.html
160.. _logging: http://docs.python.org/lib/module-logging.html
Note: See TracBrowser for help on using the browser.