1 # -*- Mode: Python; test-case-name: test_command -*-
2 # vi:si:et:sw=4:sts=4:ts=4
4 # This file is released under the standard PSF license.
6 # from MOAP - https://thomas.apestaart.org/moap/trac
7 # written by Thomas Vander Stichele (thomas at apestaart dot org)
17 from func
.config
import read_config
, CONFIG_FILE
18 from func
.commonconfig
import CMConfig
20 class CommandHelpFormatter(optparse
.IndentedHelpFormatter
):
22 I format the description as usual, but add an overview of commands
23 after it if there are any, formatted like the options.
27 def addCommand(self
, name
, description
):
28 if self
._commands
is None:
30 self
._commands
[name
] = description
32 ### override parent method
33 def format_description(self
, description
):
34 # textwrap doesn't allow for a way to preserve double newlines
35 # to separate paragraphs, so we do it here.
36 blocks
= description
.split('\n\n')
40 rets
.append(optparse
.IndentedHelpFormatter
.format_description(self
,
45 commandDesc
.append("commands:")
46 keys
= self
._commands
.keys()
53 format
= " %-" + "%d" % length
+ "s %s"
54 commandDesc
.append(format
% (name
, self
._commands
[name
]))
55 ret
+= "\n" + "\n".join(commandDesc
) + "\n"
58 class CommandOptionParser(optparse
.OptionParser
):
60 I parse options as usual, but I explicitly allow setting stdout
61 so that our print_help() method (invoked by default with -h/--help)
62 defaults to writing there.
66 def set_stdout(self
, stdout
):
69 # we're overriding the built-in file, but we need to since this is
70 # the signature from the base class
71 __pychecker__
= 'no-shadowbuiltin'
72 def print_help(self
, file=None):
73 # we are overriding a parent method so we can't do anything about file
74 __pychecker__
= 'no-shadowbuiltin'
77 file.write(self
.format_help())
81 I am a class that handles a command for a program.
82 Commands can be nested underneath a command for further processing.
84 @cvar name: name of the command, lowercase
85 @cvar aliases: list of alternative lowercase names recognized
86 @type aliases: list of str
87 @cvar usage: short one-line usage string;
88 %command gets expanded to a sub-command or [commands]
90 @cvar summary: short one-line summary of the command
91 @cvar description: longer paragraph explaining the command
92 @cvar subCommands: dict of name -> commands below this command
93 @type subCommands: dict of str -> L{Command}
102 subCommandClasses
= None
103 aliasedSubCommands
= None
105 def __init__(self
, parentCommand
=None, stdout
=sys
.stdout
,
108 Create a new command instance, with the given parent.
109 Allows for redirecting stdout and stderr if needed.
110 This redirection will be passed on to child commands.
113 self
.name
= str(self
.__class
__).split('.')[-1].lower()
116 self
.parentCommand
= parentCommand
118 self
.config
= read_config(CONFIG_FILE
, CMConfig
)
120 # create subcommands if we have them
121 self
.subCommands
= {}
122 self
.aliasedSubCommands
= {}
123 if self
.subCommandClasses
:
124 for C
in self
.subCommandClasses
:
125 c
= C(self
, stdout
=stdout
, stderr
=stderr
)
126 self
.subCommands
[c
.name
] = c
128 for alias
in c
.aliases
:
129 self
.aliasedSubCommands
[alias
] = c
131 # create our formatter and add subcommands if we have them
132 formatter
= CommandHelpFormatter()
134 for name
, command
in self
.subCommands
.items():
135 formatter
.addCommand(name
, command
.summary
or
138 # expand %command for the bottom usage
139 usage
= self
.usage
or self
.name
140 if usage
.find("%command") > -1:
141 usage
= usage
.split("%command")[0] + '[command]'
144 # FIXME: abstract this into getUsage that takes an optional
145 # parentCommand on where to stop recursing up
146 # useful for implementing subshells
148 # walk the tree up for our usage
149 c
= self
.parentCommand
151 usage
= c
.usage
or c
.name
152 if usage
.find(" %command") > -1:
153 usage
= usage
.split(" %command")[0]
157 usage
= " ".join(usages
)
160 description
= self
.description
or self
.summary
161 self
.parser
= CommandOptionParser(
162 usage
=usage
, description
=description
,
164 self
.parser
.set_stdout(self
.stdout
)
165 self
.parser
.disable_interspersed_args()
167 # allow subclasses to add options
170 def addOptions(self
):
172 Override me to add options to the parser.
178 Override me to implement the functionality of the command.
182 def parse(self
, argv
):
184 Parse the given arguments and act on them.
187 @returns: an exit code
189 self
.options
, args
= self
.parser
.parse_args(argv
)
191 # FIXME: make handleOptions not take options, since we store it
192 # in self.options now
193 ret
= self
.handleOptions(self
.options
)
197 # handle pleas for help
198 if args
and args
[0] == 'help':
199 self
.debug('Asked for help, args %r' % args
)
201 # give help on current command if only 'help' is passed
206 # complain if we were asked for help on a subcommand, but we don't
208 if not self
.subCommands
:
209 self
.stderr
.write('No subcommands defined.')
210 self
.parser
.print_usage(file=self
.stderr
)
212 "Use --help to get more information about this command.\n")
215 # rewrite the args the other way around;
216 # help doap becomes doap help so it gets deferred to the doap
218 args
= [args
[1], args
[0]]
221 # if we have args that we need to deal with, do it now
222 # before we start looking for subcommands
223 self
.handleArguments(args
)
225 # if we don't have subcommands, defer to our do() method
226 if not self
.subCommands
:
229 # if everything's fine, we return 0
236 # if we do have subcommands, defer to them
240 self
.parser
.print_usage(file=self
.stderr
)
242 "Use --help to get a list of commands.\n")
245 if command
in self
.subCommands
.keys():
246 return self
.subCommands
[command
].parse(args
[1:])
248 if self
.aliasedSubCommands
:
249 if command
in self
.aliasedSubCommands
.keys():
250 return self
.aliasedSubCommands
[command
].parse(args
[1:])
252 self
.stderr
.write("Unknown command '%s'.\n" % command
)
255 def outputHelp(self
):
257 Output help information.
259 self
.parser
.print_help(file=self
.stderr
)
261 def outputUsage(self
):
263 Output usage information.
264 Used when the options or arguments were missing or wrong.
266 self
.parser
.print_usage(file=self
.stderr
)
268 def handleOptions(self
, options
):
270 Handle the parsed options.
274 def handleArguments(self
, arguments
):
276 Handle the parsed arguments.
280 def getRootCommand(self
):
282 Return the top-level command, which is typically the program.
285 while c
.parentCommand
: