##// END OF EJS Templates
allow `ipython help subcommand` syntax...
MinRK -
Show More
@@ -1,504 +1,508 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Min RK
8 * Min RK
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import logging
22 import logging
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 from copy import deepcopy
26 from copy import deepcopy
27 from collections import defaultdict
27 from collections import defaultdict
28
28
29 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
30
30
31 from IPython.config.configurable import SingletonConfigurable
31 from IPython.config.configurable import SingletonConfigurable
32 from IPython.config.loader import (
32 from IPython.config.loader import (
33 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound,
33 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound,
34 )
34 )
35
35
36 from IPython.utils.traitlets import (
36 from IPython.utils.traitlets import (
37 Unicode, List, Enum, Dict, Instance, TraitError
37 Unicode, List, Enum, Dict, Instance, TraitError
38 )
38 )
39 from IPython.utils.importstring import import_item
39 from IPython.utils.importstring import import_item
40 from IPython.utils.text import indent, wrap_paragraphs, dedent
40 from IPython.utils.text import indent, wrap_paragraphs, dedent
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # function for re-wrapping a helpstring
43 # function for re-wrapping a helpstring
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Descriptions for the various sections
47 # Descriptions for the various sections
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 # merge flags&aliases into options
50 # merge flags&aliases into options
51 option_description = """
51 option_description = """
52 Arguments that take values are actually convenience aliases to full
52 Arguments that take values are actually convenience aliases to full
53 Configurables, whose aliases are listed on the help line. For more information
53 Configurables, whose aliases are listed on the help line. For more information
54 on full configurables, see '--help-all'.
54 on full configurables, see '--help-all'.
55 """.strip() # trim newlines of front and back
55 """.strip() # trim newlines of front and back
56
56
57 keyvalue_description = """
57 keyvalue_description = """
58 Parameters are set from command-line arguments of the form:
58 Parameters are set from command-line arguments of the form:
59 `--Class.trait=value`.
59 `--Class.trait=value`.
60 This line is evaluated in Python, so simple expressions are allowed, e.g.::
60 This line is evaluated in Python, so simple expressions are allowed, e.g.::
61 `--C.a='range(3)'` For setting C.a=[0,1,2].
61 `--C.a='range(3)'` For setting C.a=[0,1,2].
62 """.strip() # trim newlines of front and back
62 """.strip() # trim newlines of front and back
63
63
64 subcommand_description = """
64 subcommand_description = """
65 Subcommands are launched as `{app} cmd [args]`. For information on using
65 Subcommands are launched as `{app} cmd [args]`. For information on using
66 subcommand 'cmd', do: `{app} cmd -h`.
66 subcommand 'cmd', do: `{app} cmd -h`.
67 """.strip().format(app=os.path.basename(sys.argv[0]))
67 """.strip().format(app=os.path.basename(sys.argv[0]))
68 # get running program name
68 # get running program name
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # Application class
71 # Application class
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74 @decorator
74 @decorator
75 def catch_config_error(method, app, *args, **kwargs):
75 def catch_config_error(method, app, *args, **kwargs):
76 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
76 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
77
77
78 On a TraitError (generally caused by bad config), this will print the trait's
78 On a TraitError (generally caused by bad config), this will print the trait's
79 message, and exit the app.
79 message, and exit the app.
80
80
81 For use on init methods, to prevent invoking excepthook on invalid input.
81 For use on init methods, to prevent invoking excepthook on invalid input.
82 """
82 """
83 try:
83 try:
84 return method(app, *args, **kwargs)
84 return method(app, *args, **kwargs)
85 except (TraitError, ArgumentError) as e:
85 except (TraitError, ArgumentError) as e:
86 app.print_description()
86 app.print_description()
87 app.print_help()
87 app.print_help()
88 app.print_examples()
88 app.print_examples()
89 app.log.fatal("Bad config encountered during initialization:")
89 app.log.fatal("Bad config encountered during initialization:")
90 app.log.fatal(str(e))
90 app.log.fatal(str(e))
91 app.log.debug("Config at the time: %s", app.config)
91 app.log.debug("Config at the time: %s", app.config)
92 app.exit(1)
92 app.exit(1)
93
93
94
94
95 class ApplicationError(Exception):
95 class ApplicationError(Exception):
96 pass
96 pass
97
97
98
98
99 class Application(SingletonConfigurable):
99 class Application(SingletonConfigurable):
100 """A singleton application with full configuration support."""
100 """A singleton application with full configuration support."""
101
101
102 # The name of the application, will usually match the name of the command
102 # The name of the application, will usually match the name of the command
103 # line application
103 # line application
104 name = Unicode(u'application')
104 name = Unicode(u'application')
105
105
106 # The description of the application that is printed at the beginning
106 # The description of the application that is printed at the beginning
107 # of the help.
107 # of the help.
108 description = Unicode(u'This is an application.')
108 description = Unicode(u'This is an application.')
109 # default section descriptions
109 # default section descriptions
110 option_description = Unicode(option_description)
110 option_description = Unicode(option_description)
111 keyvalue_description = Unicode(keyvalue_description)
111 keyvalue_description = Unicode(keyvalue_description)
112 subcommand_description = Unicode(subcommand_description)
112 subcommand_description = Unicode(subcommand_description)
113
113
114 # The usage and example string that goes at the end of the help string.
114 # The usage and example string that goes at the end of the help string.
115 examples = Unicode()
115 examples = Unicode()
116
116
117 # A sequence of Configurable subclasses whose config=True attributes will
117 # A sequence of Configurable subclasses whose config=True attributes will
118 # be exposed at the command line.
118 # be exposed at the command line.
119 classes = List([])
119 classes = List([])
120
120
121 # The version string of this application.
121 # The version string of this application.
122 version = Unicode(u'0.0')
122 version = Unicode(u'0.0')
123
123
124 # The log level for the application
124 # The log level for the application
125 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
125 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
126 default_value=logging.WARN,
126 default_value=logging.WARN,
127 config=True,
127 config=True,
128 help="Set the log level by value or name.")
128 help="Set the log level by value or name.")
129 def _log_level_changed(self, name, old, new):
129 def _log_level_changed(self, name, old, new):
130 """Adjust the log level when log_level is set."""
130 """Adjust the log level when log_level is set."""
131 if isinstance(new, basestring):
131 if isinstance(new, basestring):
132 new = getattr(logging, new)
132 new = getattr(logging, new)
133 self.log_level = new
133 self.log_level = new
134 self.log.setLevel(new)
134 self.log.setLevel(new)
135
135
136 # the alias map for configurables
136 # the alias map for configurables
137 aliases = Dict({'log-level' : 'Application.log_level'})
137 aliases = Dict({'log-level' : 'Application.log_level'})
138
138
139 # flags for loading Configurables or store_const style flags
139 # flags for loading Configurables or store_const style flags
140 # flags are loaded from this dict by '--key' flags
140 # flags are loaded from this dict by '--key' flags
141 # this must be a dict of two-tuples, the first element being the Config/dict
141 # this must be a dict of two-tuples, the first element being the Config/dict
142 # and the second being the help string for the flag
142 # and the second being the help string for the flag
143 flags = Dict()
143 flags = Dict()
144 def _flags_changed(self, name, old, new):
144 def _flags_changed(self, name, old, new):
145 """ensure flags dict is valid"""
145 """ensure flags dict is valid"""
146 for key,value in new.iteritems():
146 for key,value in new.iteritems():
147 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
147 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
148 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
148 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
149 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
149 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
150
150
151
151
152 # subcommands for launching other applications
152 # subcommands for launching other applications
153 # if this is not empty, this will be a parent Application
153 # if this is not empty, this will be a parent Application
154 # this must be a dict of two-tuples,
154 # this must be a dict of two-tuples,
155 # the first element being the application class/import string
155 # the first element being the application class/import string
156 # and the second being the help string for the subcommand
156 # and the second being the help string for the subcommand
157 subcommands = Dict()
157 subcommands = Dict()
158 # parse_command_line will initialize a subapp, if requested
158 # parse_command_line will initialize a subapp, if requested
159 subapp = Instance('IPython.config.application.Application', allow_none=True)
159 subapp = Instance('IPython.config.application.Application', allow_none=True)
160
160
161 # extra command-line arguments that don't set config values
161 # extra command-line arguments that don't set config values
162 extra_args = List(Unicode)
162 extra_args = List(Unicode)
163
163
164
164
165 def __init__(self, **kwargs):
165 def __init__(self, **kwargs):
166 SingletonConfigurable.__init__(self, **kwargs)
166 SingletonConfigurable.__init__(self, **kwargs)
167 # Ensure my class is in self.classes, so my attributes appear in command line
167 # Ensure my class is in self.classes, so my attributes appear in command line
168 # options and config files.
168 # options and config files.
169 if self.__class__ not in self.classes:
169 if self.__class__ not in self.classes:
170 self.classes.insert(0, self.__class__)
170 self.classes.insert(0, self.__class__)
171
171
172 self.init_logging()
172 self.init_logging()
173
173
174 def _config_changed(self, name, old, new):
174 def _config_changed(self, name, old, new):
175 SingletonConfigurable._config_changed(self, name, old, new)
175 SingletonConfigurable._config_changed(self, name, old, new)
176 self.log.debug('Config changed:')
176 self.log.debug('Config changed:')
177 self.log.debug(repr(new))
177 self.log.debug(repr(new))
178
178
179 def init_logging(self):
179 def init_logging(self):
180 """Start logging for this application.
180 """Start logging for this application.
181
181
182 The default is to log to stdout using a StreaHandler. The log level
182 The default is to log to stdout using a StreaHandler. The log level
183 starts at loggin.WARN, but this can be adjusted by setting the
183 starts at loggin.WARN, but this can be adjusted by setting the
184 ``log_level`` attribute.
184 ``log_level`` attribute.
185 """
185 """
186 self.log = logging.getLogger(self.__class__.__name__)
186 self.log = logging.getLogger(self.__class__.__name__)
187 self.log.setLevel(self.log_level)
187 self.log.setLevel(self.log_level)
188 if sys.executable.endswith('pythonw.exe'):
188 if sys.executable.endswith('pythonw.exe'):
189 # this should really go to a file, but file-logging is only
189 # this should really go to a file, but file-logging is only
190 # hooked up in parallel applications
190 # hooked up in parallel applications
191 self._log_handler = logging.StreamHandler(open(os.devnull, 'w'))
191 self._log_handler = logging.StreamHandler(open(os.devnull, 'w'))
192 else:
192 else:
193 self._log_handler = logging.StreamHandler()
193 self._log_handler = logging.StreamHandler()
194 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
194 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
195 self._log_handler.setFormatter(self._log_formatter)
195 self._log_handler.setFormatter(self._log_formatter)
196 self.log.addHandler(self._log_handler)
196 self.log.addHandler(self._log_handler)
197
197
198 @catch_config_error
198 @catch_config_error
199 def initialize(self, argv=None):
199 def initialize(self, argv=None):
200 """Do the basic steps to configure me.
200 """Do the basic steps to configure me.
201
201
202 Override in subclasses.
202 Override in subclasses.
203 """
203 """
204 self.parse_command_line(argv)
204 self.parse_command_line(argv)
205
205
206
206
207 def start(self):
207 def start(self):
208 """Start the app mainloop.
208 """Start the app mainloop.
209
209
210 Override in subclasses.
210 Override in subclasses.
211 """
211 """
212 if self.subapp is not None:
212 if self.subapp is not None:
213 return self.subapp.start()
213 return self.subapp.start()
214
214
215 def print_alias_help(self):
215 def print_alias_help(self):
216 """Print the alias part of the help."""
216 """Print the alias part of the help."""
217 if not self.aliases:
217 if not self.aliases:
218 return
218 return
219
219
220 lines = []
220 lines = []
221 classdict = {}
221 classdict = {}
222 for cls in self.classes:
222 for cls in self.classes:
223 # include all parents (up to, but excluding Configurable) in available names
223 # include all parents (up to, but excluding Configurable) in available names
224 for c in cls.mro()[:-3]:
224 for c in cls.mro()[:-3]:
225 classdict[c.__name__] = c
225 classdict[c.__name__] = c
226
226
227 for alias, longname in self.aliases.iteritems():
227 for alias, longname in self.aliases.iteritems():
228 classname, traitname = longname.split('.',1)
228 classname, traitname = longname.split('.',1)
229 cls = classdict[classname]
229 cls = classdict[classname]
230
230
231 trait = cls.class_traits(config=True)[traitname]
231 trait = cls.class_traits(config=True)[traitname]
232 help = cls.class_get_trait_help(trait).splitlines()
232 help = cls.class_get_trait_help(trait).splitlines()
233 # reformat first line
233 # reformat first line
234 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
234 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
235 if len(alias) == 1:
235 if len(alias) == 1:
236 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
236 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
237 lines.extend(help)
237 lines.extend(help)
238 # lines.append('')
238 # lines.append('')
239 print os.linesep.join(lines)
239 print os.linesep.join(lines)
240
240
241 def print_flag_help(self):
241 def print_flag_help(self):
242 """Print the flag part of the help."""
242 """Print the flag part of the help."""
243 if not self.flags:
243 if not self.flags:
244 return
244 return
245
245
246 lines = []
246 lines = []
247 for m, (cfg,help) in self.flags.iteritems():
247 for m, (cfg,help) in self.flags.iteritems():
248 prefix = '--' if len(m) > 1 else '-'
248 prefix = '--' if len(m) > 1 else '-'
249 lines.append(prefix+m)
249 lines.append(prefix+m)
250 lines.append(indent(dedent(help.strip())))
250 lines.append(indent(dedent(help.strip())))
251 # lines.append('')
251 # lines.append('')
252 print os.linesep.join(lines)
252 print os.linesep.join(lines)
253
253
254 def print_options(self):
254 def print_options(self):
255 if not self.flags and not self.aliases:
255 if not self.flags and not self.aliases:
256 return
256 return
257 lines = ['Options']
257 lines = ['Options']
258 lines.append('-'*len(lines[0]))
258 lines.append('-'*len(lines[0]))
259 lines.append('')
259 lines.append('')
260 for p in wrap_paragraphs(self.option_description):
260 for p in wrap_paragraphs(self.option_description):
261 lines.append(p)
261 lines.append(p)
262 lines.append('')
262 lines.append('')
263 print os.linesep.join(lines)
263 print os.linesep.join(lines)
264 self.print_flag_help()
264 self.print_flag_help()
265 self.print_alias_help()
265 self.print_alias_help()
266 print
266 print
267
267
268 def print_subcommands(self):
268 def print_subcommands(self):
269 """Print the subcommand part of the help."""
269 """Print the subcommand part of the help."""
270 if not self.subcommands:
270 if not self.subcommands:
271 return
271 return
272
272
273 lines = ["Subcommands"]
273 lines = ["Subcommands"]
274 lines.append('-'*len(lines[0]))
274 lines.append('-'*len(lines[0]))
275 lines.append('')
275 lines.append('')
276 for p in wrap_paragraphs(self.subcommand_description):
276 for p in wrap_paragraphs(self.subcommand_description):
277 lines.append(p)
277 lines.append(p)
278 lines.append('')
278 lines.append('')
279 for subc, (cls, help) in self.subcommands.iteritems():
279 for subc, (cls, help) in self.subcommands.iteritems():
280 lines.append(subc)
280 lines.append(subc)
281 if help:
281 if help:
282 lines.append(indent(dedent(help.strip())))
282 lines.append(indent(dedent(help.strip())))
283 lines.append('')
283 lines.append('')
284 print os.linesep.join(lines)
284 print os.linesep.join(lines)
285
285
286 def print_help(self, classes=False):
286 def print_help(self, classes=False):
287 """Print the help for each Configurable class in self.classes.
287 """Print the help for each Configurable class in self.classes.
288
288
289 If classes=False (the default), only flags and aliases are printed.
289 If classes=False (the default), only flags and aliases are printed.
290 """
290 """
291 self.print_subcommands()
291 self.print_subcommands()
292 self.print_options()
292 self.print_options()
293
293
294 if classes:
294 if classes:
295 if self.classes:
295 if self.classes:
296 print "Class parameters"
296 print "Class parameters"
297 print "----------------"
297 print "----------------"
298 print
298 print
299 for p in wrap_paragraphs(self.keyvalue_description):
299 for p in wrap_paragraphs(self.keyvalue_description):
300 print p
300 print p
301 print
301 print
302
302
303 for cls in self.classes:
303 for cls in self.classes:
304 cls.class_print_help()
304 cls.class_print_help()
305 print
305 print
306 else:
306 else:
307 print "To see all available configurables, use `--help-all`"
307 print "To see all available configurables, use `--help-all`"
308 print
308 print
309
309
310 def print_description(self):
310 def print_description(self):
311 """Print the application description."""
311 """Print the application description."""
312 for p in wrap_paragraphs(self.description):
312 for p in wrap_paragraphs(self.description):
313 print p
313 print p
314 print
314 print
315
315
316 def print_examples(self):
316 def print_examples(self):
317 """Print usage and examples.
317 """Print usage and examples.
318
318
319 This usage string goes at the end of the command line help string
319 This usage string goes at the end of the command line help string
320 and should contain examples of the application's usage.
320 and should contain examples of the application's usage.
321 """
321 """
322 if self.examples:
322 if self.examples:
323 print "Examples"
323 print "Examples"
324 print "--------"
324 print "--------"
325 print
325 print
326 print indent(dedent(self.examples.strip()))
326 print indent(dedent(self.examples.strip()))
327 print
327 print
328
328
329 def print_version(self):
329 def print_version(self):
330 """Print the version string."""
330 """Print the version string."""
331 print self.version
331 print self.version
332
332
333 def update_config(self, config):
333 def update_config(self, config):
334 """Fire the traits events when the config is updated."""
334 """Fire the traits events when the config is updated."""
335 # Save a copy of the current config.
335 # Save a copy of the current config.
336 newconfig = deepcopy(self.config)
336 newconfig = deepcopy(self.config)
337 # Merge the new config into the current one.
337 # Merge the new config into the current one.
338 newconfig._merge(config)
338 newconfig._merge(config)
339 # Save the combined config as self.config, which triggers the traits
339 # Save the combined config as self.config, which triggers the traits
340 # events.
340 # events.
341 self.config = newconfig
341 self.config = newconfig
342
342
343 @catch_config_error
343 @catch_config_error
344 def initialize_subcommand(self, subc, argv=None):
344 def initialize_subcommand(self, subc, argv=None):
345 """Initialize a subcommand with argv."""
345 """Initialize a subcommand with argv."""
346 subapp,help = self.subcommands.get(subc)
346 subapp,help = self.subcommands.get(subc)
347
347
348 if isinstance(subapp, basestring):
348 if isinstance(subapp, basestring):
349 subapp = import_item(subapp)
349 subapp = import_item(subapp)
350
350
351 # clear existing instances
351 # clear existing instances
352 self.__class__.clear_instance()
352 self.__class__.clear_instance()
353 # instantiate
353 # instantiate
354 self.subapp = subapp.instance()
354 self.subapp = subapp.instance()
355 # and initialize subapp
355 # and initialize subapp
356 self.subapp.initialize(argv)
356 self.subapp.initialize(argv)
357
357
358 def flatten_flags(self):
358 def flatten_flags(self):
359 """flatten flags and aliases, so cl-args override as expected.
359 """flatten flags and aliases, so cl-args override as expected.
360
360
361 This prevents issues such as an alias pointing to InteractiveShell,
361 This prevents issues such as an alias pointing to InteractiveShell,
362 but a config file setting the same trait in TerminalInteraciveShell
362 but a config file setting the same trait in TerminalInteraciveShell
363 getting inappropriate priority over the command-line arg.
363 getting inappropriate priority over the command-line arg.
364
364
365 Only aliases with exactly one descendent in the class list
365 Only aliases with exactly one descendent in the class list
366 will be promoted.
366 will be promoted.
367
367
368 """
368 """
369 # build a tree of classes in our list that inherit from a particular
369 # build a tree of classes in our list that inherit from a particular
370 # it will be a dict by parent classname of classes in our list
370 # it will be a dict by parent classname of classes in our list
371 # that are descendents
371 # that are descendents
372 mro_tree = defaultdict(list)
372 mro_tree = defaultdict(list)
373 for cls in self.classes:
373 for cls in self.classes:
374 clsname = cls.__name__
374 clsname = cls.__name__
375 for parent in cls.mro()[1:-3]:
375 for parent in cls.mro()[1:-3]:
376 # exclude cls itself and Configurable,HasTraits,object
376 # exclude cls itself and Configurable,HasTraits,object
377 mro_tree[parent.__name__].append(clsname)
377 mro_tree[parent.__name__].append(clsname)
378 # flatten aliases, which have the form:
378 # flatten aliases, which have the form:
379 # { 'alias' : 'Class.trait' }
379 # { 'alias' : 'Class.trait' }
380 aliases = {}
380 aliases = {}
381 for alias, cls_trait in self.aliases.iteritems():
381 for alias, cls_trait in self.aliases.iteritems():
382 cls,trait = cls_trait.split('.',1)
382 cls,trait = cls_trait.split('.',1)
383 children = mro_tree[cls]
383 children = mro_tree[cls]
384 if len(children) == 1:
384 if len(children) == 1:
385 # exactly one descendent, promote alias
385 # exactly one descendent, promote alias
386 cls = children[0]
386 cls = children[0]
387 aliases[alias] = '.'.join([cls,trait])
387 aliases[alias] = '.'.join([cls,trait])
388
388
389 # flatten flags, which are of the form:
389 # flatten flags, which are of the form:
390 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
390 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
391 flags = {}
391 flags = {}
392 for key, (flagdict, help) in self.flags.iteritems():
392 for key, (flagdict, help) in self.flags.iteritems():
393 newflag = {}
393 newflag = {}
394 for cls, subdict in flagdict.iteritems():
394 for cls, subdict in flagdict.iteritems():
395 children = mro_tree[cls]
395 children = mro_tree[cls]
396 # exactly one descendent, promote flag section
396 # exactly one descendent, promote flag section
397 if len(children) == 1:
397 if len(children) == 1:
398 cls = children[0]
398 cls = children[0]
399 newflag[cls] = subdict
399 newflag[cls] = subdict
400 flags[key] = (newflag, help)
400 flags[key] = (newflag, help)
401 return flags, aliases
401 return flags, aliases
402
402
403 @catch_config_error
403 @catch_config_error
404 def parse_command_line(self, argv=None):
404 def parse_command_line(self, argv=None):
405 """Parse the command line arguments."""
405 """Parse the command line arguments."""
406 argv = sys.argv[1:] if argv is None else argv
406 argv = sys.argv[1:] if argv is None else argv
407
408 if argv and argv[0] == 'help':
409 # turn `ipython help notebook` into `ipython notebook -h`
410 argv = argv[1:] + ['-h']
407
411
408 if self.subcommands and len(argv) > 0:
412 if self.subcommands and len(argv) > 0:
409 # we have subcommands, and one may have been specified
413 # we have subcommands, and one may have been specified
410 subc, subargv = argv[0], argv[1:]
414 subc, subargv = argv[0], argv[1:]
411 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
415 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
412 # it's a subcommand, and *not* a flag or class parameter
416 # it's a subcommand, and *not* a flag or class parameter
413 return self.initialize_subcommand(subc, subargv)
417 return self.initialize_subcommand(subc, subargv)
414
418
415 if '-h' in argv or '--help' in argv or '--help-all' in argv:
419 if '-h' in argv or '--help' in argv or '--help-all' in argv:
416 self.print_description()
420 self.print_description()
417 self.print_help('--help-all' in argv)
421 self.print_help('--help-all' in argv)
418 self.print_examples()
422 self.print_examples()
419 self.exit(0)
423 self.exit(0)
420
424
421 if '--version' in argv or '-V' in argv:
425 if '--version' in argv or '-V' in argv:
422 self.print_version()
426 self.print_version()
423 self.exit(0)
427 self.exit(0)
424
428
425 # flatten flags&aliases, so cl-args get appropriate priority:
429 # flatten flags&aliases, so cl-args get appropriate priority:
426 flags,aliases = self.flatten_flags()
430 flags,aliases = self.flatten_flags()
427
431
428 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
432 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
429 flags=flags)
433 flags=flags)
430 config = loader.load_config()
434 config = loader.load_config()
431 self.update_config(config)
435 self.update_config(config)
432 # store unparsed args in extra_args
436 # store unparsed args in extra_args
433 self.extra_args = loader.extra_args
437 self.extra_args = loader.extra_args
434
438
435 @catch_config_error
439 @catch_config_error
436 def load_config_file(self, filename, path=None):
440 def load_config_file(self, filename, path=None):
437 """Load a .py based config file by filename and path."""
441 """Load a .py based config file by filename and path."""
438 loader = PyFileConfigLoader(filename, path=path)
442 loader = PyFileConfigLoader(filename, path=path)
439 try:
443 try:
440 config = loader.load_config()
444 config = loader.load_config()
441 except ConfigFileNotFound:
445 except ConfigFileNotFound:
442 # problem finding the file, raise
446 # problem finding the file, raise
443 raise
447 raise
444 except Exception:
448 except Exception:
445 # try to get the full filename, but it will be empty in the
449 # try to get the full filename, but it will be empty in the
446 # unlikely event that the error raised before filefind finished
450 # unlikely event that the error raised before filefind finished
447 filename = loader.full_filename or filename
451 filename = loader.full_filename or filename
448 # problem while running the file
452 # problem while running the file
449 self.log.error("Exception while loading config file %s",
453 self.log.error("Exception while loading config file %s",
450 filename, exc_info=True)
454 filename, exc_info=True)
451 else:
455 else:
452 self.log.debug("Loaded config file: %s", loader.full_filename)
456 self.log.debug("Loaded config file: %s", loader.full_filename)
453 self.update_config(config)
457 self.update_config(config)
454
458
455 def generate_config_file(self):
459 def generate_config_file(self):
456 """generate default config file from Configurables"""
460 """generate default config file from Configurables"""
457 lines = ["# Configuration file for %s."%self.name]
461 lines = ["# Configuration file for %s."%self.name]
458 lines.append('')
462 lines.append('')
459 lines.append('c = get_config()')
463 lines.append('c = get_config()')
460 lines.append('')
464 lines.append('')
461 for cls in self.classes:
465 for cls in self.classes:
462 lines.append(cls.class_config_section())
466 lines.append(cls.class_config_section())
463 return '\n'.join(lines)
467 return '\n'.join(lines)
464
468
465 def exit(self, exit_status=0):
469 def exit(self, exit_status=0):
466 self.log.debug("Exiting application: %s" % self.name)
470 self.log.debug("Exiting application: %s" % self.name)
467 sys.exit(exit_status)
471 sys.exit(exit_status)
468
472
469 #-----------------------------------------------------------------------------
473 #-----------------------------------------------------------------------------
470 # utility functions, for convenience
474 # utility functions, for convenience
471 #-----------------------------------------------------------------------------
475 #-----------------------------------------------------------------------------
472
476
473 def boolean_flag(name, configurable, set_help='', unset_help=''):
477 def boolean_flag(name, configurable, set_help='', unset_help=''):
474 """Helper for building basic --trait, --no-trait flags.
478 """Helper for building basic --trait, --no-trait flags.
475
479
476 Parameters
480 Parameters
477 ----------
481 ----------
478
482
479 name : str
483 name : str
480 The name of the flag.
484 The name of the flag.
481 configurable : str
485 configurable : str
482 The 'Class.trait' string of the trait to be set/unset with the flag
486 The 'Class.trait' string of the trait to be set/unset with the flag
483 set_help : unicode
487 set_help : unicode
484 help string for --name flag
488 help string for --name flag
485 unset_help : unicode
489 unset_help : unicode
486 help string for --no-name flag
490 help string for --no-name flag
487
491
488 Returns
492 Returns
489 -------
493 -------
490
494
491 cfg : dict
495 cfg : dict
492 A dict with two keys: 'name', and 'no-name', for setting and unsetting
496 A dict with two keys: 'name', and 'no-name', for setting and unsetting
493 the trait, respectively.
497 the trait, respectively.
494 """
498 """
495 # default helpstrings
499 # default helpstrings
496 set_help = set_help or "set %s=True"%configurable
500 set_help = set_help or "set %s=True"%configurable
497 unset_help = unset_help or "set %s=False"%configurable
501 unset_help = unset_help or "set %s=False"%configurable
498
502
499 cls,trait = configurable.split('.')
503 cls,trait = configurable.split('.')
500
504
501 setter = {cls : {trait : True}}
505 setter = {cls : {trait : True}}
502 unsetter = {cls : {trait : False}}
506 unsetter = {cls : {trait : False}}
503 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
507 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
504
508
General Comments 0
You need to be logged in to leave comments. Login now