1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import copy
34 import getopt
35 import qm
36 import string
37 import structured_text
38 import sys
39
40
41
42
43
47
48
49
51 """Class for the functionality that parses the command line.
52
53 The command parser is used to easily specify a list of command line
54 options and commands to be parsed from an argument list."""
55
56 - def __init__(self, name, options, commands, conflicting_options=()):
57 """Create a new command parser.
58
59 'name' -- The name of the executable that we are currently
60 using. This will normally be argv[0].
61
62 'options' -- A list of 4-tuples specifying options that you wish
63 this parser to accept. The 4-tuple has the following form:
64 (short_form, long_form, options, description). 'short_form'
65 must be exactly one character. 'long_form' must be specified
66 for every option in the list. 'arg_name' is a string
67 representing the name of the argument that is passed to this
68 option. If it is 'None,' then this option doesn't take an
69 argument. 'description' is a string describing the option.
70
71 'commands' -- A list of 5-tuples specifying commands to be
72 accepted after the command line options. The 5-tuple has the
73 form '(name, short_description, args_string, long_description,
74 options)'.
75
76 'name' -- The string for the command.
77
78 'short_description' -- A short description of the command to
79 be printed out in general help.
80
81 'args_string' -- The string that will be printed after the
82 command in the command specific help.
83
84 'long_description' -- The long description to be printed out
85 in the command specfic help.
86
87 'options' -- A list of 4-tuples of the same form as the
88 'options' described above.
89
90 'conflicting_options' -- A sequence of sets of conflicting
91 options. Each element is a sequence of option specifiers in the
92 same form as 'options', above."""
93
94 self.__name = name
95
96
97 self.CheckOptions(options)
98 self.__options = copy.deepcopy(options)
99
100 self.__option_to_long = {}
101 for option in self.__options:
102 if option[0]:
103
104 assert not self.__option_to_long.has_key('-' + option[0])
105 self.__option_to_long['-' + option[0]] = option[1]
106
107 assert not self.__option_to_long.has_key('--' + option[1])
108 self.__option_to_long['--' + option[1]] = option[1]
109
110
111 for command in commands:
112 self.CheckOptions(command[4])
113
114 self.__commands = copy.deepcopy(commands)
115 for i in range(0, len(self.__commands)):
116 command = self.__commands[i]
117 map = {}
118 for option in command[4]:
119 if option[0] is not None:
120
121 if map.has_key('-' + option[0]):
122 raise ValueError, \
123 "duplicate short command option -%s" \
124 % option[0]
125 map['-' + option[0]] = option[1]
126
127 if map.has_key('--' + option[1]):
128 raise ValueError, \
129 "duplicate long command option --%s" % option[1]
130 map['--' + option[1]] = option[1]
131 command = command + (map,)
132 self.__commands[i] = command
133
134
135 self.__getopt_options = self.BuildGetoptString(self.__options)
136
137
138
139 for conflict_set in conflicting_options:
140
141 for option_spec in conflict_set:
142 found = 0
143
144 if option_spec in options:
145 found = 1
146 break
147 if not found:
148
149 for command in commands:
150 if option in command[4]:
151 found = 1
152 break
153 if not found:
154
155 raise ValueError, \
156 "unknown option --%s in conflict set", option[1]
157
158 self.__conflicting_options = conflicting_options
159
160
162 """Check that a list of options 4-tuples is correct.
163
164 'options' -- A list of 4-tuples as described above.
165
166 returns -- 1 if the options are all valid, 0 otherwise."""
167
168 for short_option, long_option, options, descripton in options:
169
170 if short_option != None and len(short_option) != 1:
171 raise ValueError, "short option must have exactly 1 character"
172
173 if long_option == None or len(long_option) == 0:
174 raise ValueError, \
175 "long option must be specified for -%s" % short_option
176
177 return 1
178
179
181 """Build a getopt list for the long options.
182
183 'options' -- A list of 4-tuples as described above.
184
185 returns -- A list to be passed to getopt to parse long options."""
186
187
188 getopt_list = []
189
190 for option in options:
191
192 if option[2] != None:
193 getopt_list.append(option[1] + '=')
194 else:
195 getopt_list.append(option[1])
196
197 return getopt_list
198
199
201 """Build a getopt string for the options passed in.
202
203 'options' -- A list of 4-tuples as described above.
204
205 returns -- A string to be passed to getopt to parse the
206 options."""
207
208
209 getopt_string = ''
210
211 for option in options:
212 if option[0] is not None:
213 getopt_string = getopt_string + option[0]
214
215 if option[2] != None:
216 getopt_string = getopt_string + ':'
217
218 return getopt_string
219
220
222 """Return a string that is the basic help for options.
223
224 options -- A list of options to get the help string for.
225
226 returns -- A string to be printed for the options."""
227
228 help_string = ""
229
230
231 for option in options:
232
233 if option[0] is None:
234 short_form = " "
235 else:
236 short_form = "-%s," % option[0]
237
238
239 if option[2] is None:
240 long_form = "--%-24s" % option[1]
241 else:
242 long_form = "--%-24s" % (option[1] + " " + option[2])
243
244 help_string = help_string \
245 + " %s %s: %s\n" \
246 % (short_form, long_form, option[3])
247
248 return help_string
249
250
252 """Return a string that is the basic help for the commands.
253
254 returns -- A string to be printed with basic functionality of
255 arguments and commands."""
256
257 help_string = "Usage: %s " % self.__name
258 help_string = help_string + "[ OPTION... ] COMMAND " \
259 "[ COMMAND-OPTION... ] [ ARGUMENT... ]\n\n"
260 help_string = help_string + "Options:\n"
261 help_string = help_string + self.GetOptionsHelp(self.__options)
262 help_string = help_string + "\nCommands:\n"
263
264 for command in self.__commands:
265 help_add = "%-30s: %s"%(command[0], command[1])
266 help_string = help_string + " %s\n"%(help_add)
267 help_string = help_string \
268 + "\nInvoke\n %s COMMAND --help\n" \
269 "for information about " \
270 "COMMAND-OPTIONS and ARGUMENTS.\n\n" % self.__name
271
272 return help_string
273
274
276 """Return a string that is the help for a specific command.
277
278 command -- A string of the command that you want help for.
279
280 returns -- A string of help for a given command."""
281
282 help_string = "Usage: %s %s [ OPTIONS ] "%(self.__name, command)
283 for command_item in self.__commands:
284 if command_item[0] == command:
285 help_string = help_string + command_item[2] + "\n\n"
286 help_string = help_string + "Options:\n"
287 help_string = help_string \
288 + self.GetOptionsHelp(command_item[4])
289 help_string = help_string + "\n"
290 help_string = help_string \
291 + structured_text.to_text(command_item[3])
292 return help_string
293
294 return "Command not found"
295
296
298 """Parse a command line.
299
300 'argv' -- A string containing the command line starting with
301 argv[1]. It should not contain the name of the executed program.
302
303 returns -- A 4-tuple of the options given, the command given,
304 the command options, and the command arguments. Its form is
305 this: (options, command, command_options, command_args).
306 'options' is a list of 2-tuples indicating each option specified
307 and the argument given to that option (if applicable).
308 'command' is the command given. 'command_options' is a list of
309 2-tuples indicating each option given to the command and its
310 possible argument. 'command_args' is a list of arguments as
311 given to the command. If no command is given, then the function
312 will return '' for the command, [] for the arguments, and [] for
313 the command options.
314
315 raises -- 'CommandError' if the command is invalid."""
316
317
318 getopt_list = self.BuildGetoptList(self.__options)
319
320 try:
321 options, args = getopt.getopt(argv, self.__getopt_options,
322 getopt_list)
323 except getopt.error, msg:
324 raise CommandError, msg
325
326 for i in range(0, len(options)):
327 option = options[i]
328 new_option = (self.__option_to_long[option[0]], option[1])
329 options[i] = new_option
330
331
332 if args == []:
333 return (options, '', [], [])
334
335
336 command = args[0]
337
338
339
340
341 found = 0
342 for command_item in self.__commands:
343 if command == command_item[0]:
344 found = 1
345
346 if found == 0:
347
348
349 raise CommandError, \
350 qm.error("unrecognized command", command=command)
351
352
353 command_options = []
354
355 for command_item in self.__commands:
356 if command_item[0] == command:
357 command_options = command_item[4]
358 break
359 getopt_string = self.BuildGetoptString(command_options)
360 getopt_list = self.BuildGetoptList(command_options)
361 try:
362 command_options, command_args = getopt.getopt(args[1:],
363 getopt_string,
364 getopt_list)
365 except getopt.error, msg:
366 raise CommandError, "%s: %s" % (command, msg)
367
368 for i in range(0, len(command_options)):
369 option = command_options[i]
370 new_option = (command_item[5][option[0]], option[1])
371 command_options[i] = new_option
372
373
374
375
376 all_options = map(lambda option: option[0],
377 options + command_options)
378
379 for conflict_set in self.__conflicting_options:
380
381 conflict_names = map(lambda opt_spec: opt_spec[1], conflict_set)
382
383
384 conflict_filter = lambda option, conflict_names=conflict_names: \
385 option in conflict_names and option
386 matches = filter(conflict_filter, all_options)
387
388 if len(matches) > 1:
389
390 raise qm.cmdline.CommandError, \
391 qm.error("conflicting options",
392 option1=matches[0],
393 option2=matches[1])
394
395 return (options, command, command_options, command_args)
396