##// END OF EJS Templates
merge flags&aliases help output into just 'options'
MinRK -
Show More
@@ -1,397 +1,404
1 1 # encoding: utf-8
2 2 """
3 3 A base class for a configurable application.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Min RK
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import logging
23 23 import os
24 24 import re
25 25 import sys
26 26 from copy import deepcopy
27 27
28 28 from IPython.config.configurable import SingletonConfigurable
29 29 from IPython.config.loader import (
30 30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
31 31 )
32 32
33 33 from IPython.utils.traitlets import (
34 34 Unicode, List, Int, Enum, Dict, Instance, TraitError
35 35 )
36 36 from IPython.utils.importstring import import_item
37 37 from IPython.utils.text import indent, wrap_paragraphs, dedent
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # function for re-wrapping a helpstring
41 41 #-----------------------------------------------------------------------------
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Descriptions for the various sections
45 45 #-----------------------------------------------------------------------------
46 46
47 flag_description = """
48 Flags are command-line arguments passed as '--<flag>'.
49 These take no parameters, unlike regular key-value arguments.
50 They are typically used for setting boolean flags, or enabling
51 modes that involve setting multiple options together.
52 """.strip() # trim newlines of front and back
47 # merge flags&aliases into options
48 option_description = """
49 IPython command-line arguments are passed as '--<flag>', or '--<name>=<value>'.
53 50
54 alias_description = """
55 These are commonly set parameters, given abbreviated aliases for convenience.
56 They are set in the same `--name=value` way as class parameters, where
57 <name> is replaced by the real parameter for which it is an alias.
51 Arguments that take values are actually aliases to full Configurables, whose
52 aliases are listed on the help line. For more information on full
53 configurables, see '--help-all'.
58 54 """.strip() # trim newlines of front and back
59 55
60 56 keyvalue_description = """
61 57 Parameters are set from command-line arguments of the form:
62 58 `--Class.trait=value`.
63 This line is evaluated in Python, so simple expressions are allowed, e.g.
64 `--C.a='range(3)'` For setting C.a=[0,1,2]
59 This line is evaluated in Python, so simple expressions are allowed, e.g.::
60 `--C.a='range(3)'` For setting C.a=[0,1,2].
65 61 """.strip() # trim newlines of front and back
66 62
63 subcommand_description = """
64 Subcommands are launched as `{app} cmd [args]`. For information on using
65 subcommand 'cmd', do: `{app} cmd -h`.
66 """.strip().format(app=os.path.basename(sys.argv[0]))
67 # get running program name
68
67 69 #-----------------------------------------------------------------------------
68 70 # Application class
69 71 #-----------------------------------------------------------------------------
70 72
71 73
72 74 class ApplicationError(Exception):
73 75 pass
74 76
75 77
76 78 class Application(SingletonConfigurable):
77 79 """A singleton application with full configuration support."""
78 80
79 81 # The name of the application, will usually match the name of the command
80 82 # line application
81 83 name = Unicode(u'application')
82 84
83 85 # The description of the application that is printed at the beginning
84 86 # of the help.
85 87 description = Unicode(u'This is an application.')
86 88 # default section descriptions
87 flag_description = Unicode(flag_description)
88 alias_description = Unicode(alias_description)
89 option_description = Unicode(option_description)
89 90 keyvalue_description = Unicode(keyvalue_description)
91 subcommand_description = Unicode(subcommand_description)
90 92
91 93
92 94 # A sequence of Configurable subclasses whose config=True attributes will
93 95 # be exposed at the command line.
94 96 classes = List([])
95 97
96 98 # The version string of this application.
97 99 version = Unicode(u'0.0')
98 100
99 101 # The log level for the application
100 102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
101 103 default_value=logging.WARN,
102 104 config=True,
103 105 help="Set the log level by value or name.")
104 106 def _log_level_changed(self, name, old, new):
105 107 """Adjust the log level when log_level is set."""
106 108 if isinstance(new, basestring):
107 109 new = getattr(logging, new)
108 110 self.log_level = new
109 111 self.log.setLevel(new)
110 112
111 113 # the alias map for configurables
112 114 aliases = Dict(dict(log_level='Application.log_level'))
113 115
114 116 # flags for loading Configurables or store_const style flags
115 117 # flags are loaded from this dict by '--key' flags
116 118 # this must be a dict of two-tuples, the first element being the Config/dict
117 119 # and the second being the help string for the flag
118 120 flags = Dict()
119 121 def _flags_changed(self, name, old, new):
120 122 """ensure flags dict is valid"""
121 123 for key,value in new.iteritems():
122 124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
123 125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
124 126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
125 127
126 128
127 129 # subcommands for launching other applications
128 130 # if this is not empty, this will be a parent Application
129 131 # this must be a dict of two-tuples,
130 132 # the first element being the application class/import string
131 133 # and the second being the help string for the subcommand
132 134 subcommands = Dict()
133 135 # parse_command_line will initialize a subapp, if requested
134 136 subapp = Instance('IPython.config.application.Application', allow_none=True)
135 137
136 138 # extra command-line arguments that don't set config values
137 139 extra_args = List(Unicode)
138 140
139 141
140 142 def __init__(self, **kwargs):
141 143 SingletonConfigurable.__init__(self, **kwargs)
142 144 # Add my class to self.classes so my attributes appear in command line
143 145 # options.
144 146 self.classes.insert(0, self.__class__)
145 147
146 148 self.init_logging()
147 149
148 150 def _config_changed(self, name, old, new):
149 151 SingletonConfigurable._config_changed(self, name, old, new)
150 152 self.log.debug('Config changed:')
151 153 self.log.debug(repr(new))
152 154
153 155 def init_logging(self):
154 156 """Start logging for this application.
155 157
156 158 The default is to log to stdout using a StreaHandler. The log level
157 159 starts at loggin.WARN, but this can be adjusted by setting the
158 160 ``log_level`` attribute.
159 161 """
160 162 self.log = logging.getLogger(self.__class__.__name__)
161 163 self.log.setLevel(self.log_level)
162 164 if sys.executable.endswith('pythonw.exe'):
163 165 # this should really go to a file, but file-logging is only
164 166 # hooked up in parallel applications
165 167 self._log_handler = logging.StreamHandler(open(os.devnull, 'w'))
166 168 else:
167 169 self._log_handler = logging.StreamHandler()
168 170 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
169 171 self._log_handler.setFormatter(self._log_formatter)
170 172 self.log.addHandler(self._log_handler)
171 173
172 174 def initialize(self, argv=None):
173 175 """Do the basic steps to configure me.
174 176
175 177 Override in subclasses.
176 178 """
177 179 self.parse_command_line(argv)
178 180
179 181
180 182 def start(self):
181 183 """Start the app mainloop.
182 184
183 185 Override in subclasses.
184 186 """
185 187 if self.subapp is not None:
186 188 return self.subapp.start()
187 189
188 190 def print_alias_help(self):
189 191 """Print the alias part of the help."""
190 192 if not self.aliases:
191 193 return
192 194
193 lines = ['Aliases']
194 lines.append('-'*len(lines[0]))
195 lines.append('')
196 for p in wrap_paragraphs(self.alias_description):
197 lines.append(p)
198 lines.append('')
199
195 lines = []
200 196 classdict = {}
201 197 for cls in self.classes:
202 198 # include all parents (up to, but excluding Configurable) in available names
203 199 for c in cls.mro()[:-3]:
204 200 classdict[c.__name__] = c
205 201
206 202 for alias, longname in self.aliases.iteritems():
207 203 classname, traitname = longname.split('.',1)
208 204 cls = classdict[classname]
209 205
210 206 trait = cls.class_traits(config=True)[traitname]
211 207 help = cls.class_get_trait_help(trait).splitlines()
212 208 # reformat first line
213 209 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
214 210 lines.extend(help)
215 lines.append('')
211 # lines.append('')
216 212 print os.linesep.join(lines)
217 213
218 214 def print_flag_help(self):
219 215 """Print the flag part of the help."""
220 216 if not self.flags:
221 217 return
222 218
223 lines = ['Flags']
224 lines.append('-'*len(lines[0]))
225 lines.append('')
226 for p in wrap_paragraphs(self.flag_description):
227 lines.append(p)
228 lines.append('')
229
219 lines = []
230 220 for m, (cfg,help) in self.flags.iteritems():
231 221 lines.append('--'+m)
232 222 lines.append(indent(dedent(help.strip())))
223 # lines.append('')
224 print os.linesep.join(lines)
225
226 def print_options(self):
227 if not self.flags and not self.aliases:
228 return
229 lines = ['Options']
230 lines.append('-'*len(lines[0]))
233 231 lines.append('')
234 print '\n'.join(lines)
232 for p in wrap_paragraphs(self.option_description):
233 lines.append(p)
234 lines.append('')
235 print os.linesep.join(lines)
236 self.print_flag_help()
237 self.print_alias_help()
238 print
235 239
236 240 def print_subcommands(self):
237 241 """Print the subcommand part of the help."""
238 242 if not self.subcommands:
239 243 return
240 244
241 245 lines = ["Subcommands"]
242 246 lines.append('-'*len(lines[0]))
247 lines.append('')
248 for p in wrap_paragraphs(self.subcommand_description):
249 lines.append(p)
250 lines.append('')
243 251 for subc, (cls,help) in self.subcommands.iteritems():
244 252 lines.append("%s : %s"%(subc, cls))
245 253 if help:
246 254 lines.append(indent(dedent(help.strip())))
247 255 lines.append('')
248 print '\n'.join(lines)
256 print os.linesep.join(lines)
249 257
250 258 def print_help(self, classes=False):
251 259 """Print the help for each Configurable class in self.classes.
252 260
253 261 If classes=False (the default), only flags and aliases are printed.
254 262 """
255 263 self.print_subcommands()
256 self.print_flag_help()
257 self.print_alias_help()
264 self.print_options()
258 265
259 266 if classes:
260 267 if self.classes:
261 268 print "Class parameters"
262 269 print "----------------"
263 270 print
264 271 for p in wrap_paragraphs(self.keyvalue_description):
265 272 print p
266 273 print
267 274
268 275 for cls in self.classes:
269 276 cls.class_print_help()
270 277 print
271 278 else:
272 279 print "To see all available configurables, use `--help-all`"
273 280 print
274 281
275 282 def print_description(self):
276 283 """Print the application description."""
277 284 for p in wrap_paragraphs(self.description):
278 285 print p
279 286 print
280 287
281 288 def print_version(self):
282 289 """Print the version string."""
283 290 print self.version
284 291
285 292 def update_config(self, config):
286 293 """Fire the traits events when the config is updated."""
287 294 # Save a copy of the current config.
288 295 newconfig = deepcopy(self.config)
289 296 # Merge the new config into the current one.
290 297 newconfig._merge(config)
291 298 # Save the combined config as self.config, which triggers the traits
292 299 # events.
293 300 self.config = newconfig
294 301
295 302 def initialize_subcommand(self, subc, argv=None):
296 303 """Initialize a subcommand with argv."""
297 304 subapp,help = self.subcommands.get(subc)
298 305
299 306 if isinstance(subapp, basestring):
300 307 subapp = import_item(subapp)
301 308
302 309 # clear existing instances
303 310 self.__class__.clear_instance()
304 311 # instantiate
305 312 self.subapp = subapp.instance()
306 313 # and initialize subapp
307 314 self.subapp.initialize(argv)
308 315
309 316 def parse_command_line(self, argv=None):
310 317 """Parse the command line arguments."""
311 318 argv = sys.argv[1:] if argv is None else argv
312 319
313 320 if self.subcommands and len(argv) > 0:
314 321 # we have subcommands, and one may have been specified
315 322 subc, subargv = argv[0], argv[1:]
316 323 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
317 324 # it's a subcommand, and *not* a flag or class parameter
318 325 return self.initialize_subcommand(subc, subargv)
319 326
320 327 if '-h' in argv or '--help' in argv or '--help-all' in argv:
321 328 self.print_description()
322 329 self.print_help('--help-all' in argv)
323 330 self.exit(0)
324 331
325 332 if '--version' in argv:
326 333 self.print_version()
327 334 self.exit(0)
328 335
329 336 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
330 337 flags=self.flags)
331 338 try:
332 339 config = loader.load_config()
333 340 self.update_config(config)
334 341 except (TraitError, ArgumentError) as e:
335 342 self.print_description()
336 343 self.print_help()
337 344 self.log.fatal(str(e))
338 345 self.exit(1)
339 346 # store unparsed args in extra_args
340 347 self.extra_args = loader.extra_args
341 348
342 349 def load_config_file(self, filename, path=None):
343 350 """Load a .py based config file by filename and path."""
344 351 loader = PyFileConfigLoader(filename, path=path)
345 352 config = loader.load_config()
346 353 self.update_config(config)
347 354
348 355 def generate_config_file(self):
349 356 """generate default config file from Configurables"""
350 357 lines = ["# Configuration file for %s."%self.name]
351 358 lines.append('')
352 359 lines.append('c = get_config()')
353 360 lines.append('')
354 361 for cls in self.classes:
355 362 lines.append(cls.class_config_section())
356 363 return '\n'.join(lines)
357 364
358 365 def exit(self, exit_status=0):
359 366 self.log.debug("Exiting application: %s" % self.name)
360 367 sys.exit(exit_status)
361 368
362 369 #-----------------------------------------------------------------------------
363 370 # utility functions, for convenience
364 371 #-----------------------------------------------------------------------------
365 372
366 373 def boolean_flag(name, configurable, set_help='', unset_help=''):
367 374 """Helper for building basic --trait, --no-trait flags.
368 375
369 376 Parameters
370 377 ----------
371 378
372 379 name : str
373 380 The name of the flag.
374 381 configurable : str
375 382 The 'Class.trait' string of the trait to be set/unset with the flag
376 383 set_help : unicode
377 384 help string for --name flag
378 385 unset_help : unicode
379 386 help string for --no-name flag
380 387
381 388 Returns
382 389 -------
383 390
384 391 cfg : dict
385 392 A dict with two keys: 'name', and 'no-name', for setting and unsetting
386 393 the trait, respectively.
387 394 """
388 395 # default helpstrings
389 396 set_help = set_help or "set %s=True"%configurable
390 397 unset_help = unset_help or "set %s=False"%configurable
391 398
392 399 cls,trait = configurable.split('.')
393 400
394 401 setter = {cls : {trait : True}}
395 402 unsetter = {cls : {trait : False}}
396 403 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
397 404
General Comments 0
You need to be logged in to leave comments. Login now