##// END OF EJS Templates
Merge pull request #2663 from timodonnell/dev/timodonnell/bug/2660...
Bradley M. Froehle -
r8844:591e9b32 merge
parent child Browse files
Show More
@@ -1,511 +1,520 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 log_format = Unicode("[%(name)s] %(message)s", config=True,
136 log_format = Unicode("[%(name)s] %(message)s", config=True,
137 help="The Logging format template",
137 help="The Logging format template",
138 )
138 )
139 log = Instance(logging.Logger)
139 log = Instance(logging.Logger)
140 def _log_default(self):
140 def _log_default(self):
141 """Start logging for this application.
141 """Start logging for this application.
142
142
143 The default is to log to stdout using a StreaHandler. The log level
143 The default is to log to stdout using a StreaHandler. The log level
144 starts at loggin.WARN, but this can be adjusted by setting the
144 starts at loggin.WARN, but this can be adjusted by setting the
145 ``log_level`` attribute.
145 ``log_level`` attribute.
146 """
146 """
147 log = logging.getLogger(self.__class__.__name__)
147 log = logging.getLogger(self.__class__.__name__)
148 log.setLevel(self.log_level)
148 log.setLevel(self.log_level)
149 if sys.executable.endswith('pythonw.exe'):
149 if sys.executable.endswith('pythonw.exe'):
150 # this should really go to a file, but file-logging is only
150 # this should really go to a file, but file-logging is only
151 # hooked up in parallel applications
151 # hooked up in parallel applications
152 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
152 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
153 else:
153 else:
154 _log_handler = logging.StreamHandler()
154 _log_handler = logging.StreamHandler()
155 _log_formatter = logging.Formatter(self.log_format)
155 _log_formatter = logging.Formatter(self.log_format)
156 _log_handler.setFormatter(_log_formatter)
156 _log_handler.setFormatter(_log_formatter)
157 log.addHandler(_log_handler)
157 log.addHandler(_log_handler)
158 return log
158 return log
159
159
160 # the alias map for configurables
160 # the alias map for configurables
161 aliases = Dict({'log-level' : 'Application.log_level'})
161 aliases = Dict({'log-level' : 'Application.log_level'})
162
162
163 # flags for loading Configurables or store_const style flags
163 # flags for loading Configurables or store_const style flags
164 # flags are loaded from this dict by '--key' flags
164 # flags are loaded from this dict by '--key' flags
165 # this must be a dict of two-tuples, the first element being the Config/dict
165 # this must be a dict of two-tuples, the first element being the Config/dict
166 # and the second being the help string for the flag
166 # and the second being the help string for the flag
167 flags = Dict()
167 flags = Dict()
168 def _flags_changed(self, name, old, new):
168 def _flags_changed(self, name, old, new):
169 """ensure flags dict is valid"""
169 """ensure flags dict is valid"""
170 for key,value in new.iteritems():
170 for key,value in new.iteritems():
171 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
171 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
172 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
172 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
173 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
173 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
174
174
175
175
176 # subcommands for launching other applications
176 # subcommands for launching other applications
177 # if this is not empty, this will be a parent Application
177 # if this is not empty, this will be a parent Application
178 # this must be a dict of two-tuples,
178 # this must be a dict of two-tuples,
179 # the first element being the application class/import string
179 # the first element being the application class/import string
180 # and the second being the help string for the subcommand
180 # and the second being the help string for the subcommand
181 subcommands = Dict()
181 subcommands = Dict()
182 # parse_command_line will initialize a subapp, if requested
182 # parse_command_line will initialize a subapp, if requested
183 subapp = Instance('IPython.config.application.Application', allow_none=True)
183 subapp = Instance('IPython.config.application.Application', allow_none=True)
184
184
185 # extra command-line arguments that don't set config values
185 # extra command-line arguments that don't set config values
186 extra_args = List(Unicode)
186 extra_args = List(Unicode)
187
187
188
188
189 def __init__(self, **kwargs):
189 def __init__(self, **kwargs):
190 SingletonConfigurable.__init__(self, **kwargs)
190 SingletonConfigurable.__init__(self, **kwargs)
191 # Ensure my class is in self.classes, so my attributes appear in command line
191 # Ensure my class is in self.classes, so my attributes appear in command line
192 # options and config files.
192 # options and config files.
193 if self.__class__ not in self.classes:
193 if self.__class__ not in self.classes:
194 self.classes.insert(0, self.__class__)
194 self.classes.insert(0, self.__class__)
195
195
196 def _config_changed(self, name, old, new):
196 def _config_changed(self, name, old, new):
197 SingletonConfigurable._config_changed(self, name, old, new)
197 SingletonConfigurable._config_changed(self, name, old, new)
198 self.log.debug('Config changed:')
198 self.log.debug('Config changed:')
199 self.log.debug(repr(new))
199 self.log.debug(repr(new))
200
200
201 @catch_config_error
201 @catch_config_error
202 def initialize(self, argv=None):
202 def initialize(self, argv=None):
203 """Do the basic steps to configure me.
203 """Do the basic steps to configure me.
204
204
205 Override in subclasses.
205 Override in subclasses.
206 """
206 """
207 self.parse_command_line(argv)
207 self.parse_command_line(argv)
208
208
209
209
210 def start(self):
210 def start(self):
211 """Start the app mainloop.
211 """Start the app mainloop.
212
212
213 Override in subclasses.
213 Override in subclasses.
214 """
214 """
215 if self.subapp is not None:
215 if self.subapp is not None:
216 return self.subapp.start()
216 return self.subapp.start()
217
217
218 def print_alias_help(self):
218 def print_alias_help(self):
219 """Print the alias part of the help."""
219 """Print the alias part of the help."""
220 if not self.aliases:
220 if not self.aliases:
221 return
221 return
222
222
223 lines = []
223 lines = []
224 classdict = {}
224 classdict = {}
225 for cls in self.classes:
225 for cls in self.classes:
226 # include all parents (up to, but excluding Configurable) in available names
226 # include all parents (up to, but excluding Configurable) in available names
227 for c in cls.mro()[:-3]:
227 for c in cls.mro()[:-3]:
228 classdict[c.__name__] = c
228 classdict[c.__name__] = c
229
229
230 for alias, longname in self.aliases.iteritems():
230 for alias, longname in self.aliases.iteritems():
231 classname, traitname = longname.split('.',1)
231 classname, traitname = longname.split('.',1)
232 cls = classdict[classname]
232 cls = classdict[classname]
233
233
234 trait = cls.class_traits(config=True)[traitname]
234 trait = cls.class_traits(config=True)[traitname]
235 help = cls.class_get_trait_help(trait).splitlines()
235 help = cls.class_get_trait_help(trait).splitlines()
236 # reformat first line
236 # reformat first line
237 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
237 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
238 if len(alias) == 1:
238 if len(alias) == 1:
239 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
239 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
240 lines.extend(help)
240 lines.extend(help)
241 # lines.append('')
241 # lines.append('')
242 print os.linesep.join(lines)
242 print os.linesep.join(lines)
243
243
244 def print_flag_help(self):
244 def print_flag_help(self):
245 """Print the flag part of the help."""
245 """Print the flag part of the help."""
246 if not self.flags:
246 if not self.flags:
247 return
247 return
248
248
249 lines = []
249 lines = []
250 for m, (cfg,help) in self.flags.iteritems():
250 for m, (cfg,help) in self.flags.iteritems():
251 prefix = '--' if len(m) > 1 else '-'
251 prefix = '--' if len(m) > 1 else '-'
252 lines.append(prefix+m)
252 lines.append(prefix+m)
253 lines.append(indent(dedent(help.strip())))
253 lines.append(indent(dedent(help.strip())))
254 # lines.append('')
254 # lines.append('')
255 print os.linesep.join(lines)
255 print os.linesep.join(lines)
256
256
257 def print_options(self):
257 def print_options(self):
258 if not self.flags and not self.aliases:
258 if not self.flags and not self.aliases:
259 return
259 return
260 lines = ['Options']
260 lines = ['Options']
261 lines.append('-'*len(lines[0]))
261 lines.append('-'*len(lines[0]))
262 lines.append('')
262 lines.append('')
263 for p in wrap_paragraphs(self.option_description):
263 for p in wrap_paragraphs(self.option_description):
264 lines.append(p)
264 lines.append(p)
265 lines.append('')
265 lines.append('')
266 print os.linesep.join(lines)
266 print os.linesep.join(lines)
267 self.print_flag_help()
267 self.print_flag_help()
268 self.print_alias_help()
268 self.print_alias_help()
269 print
269 print
270
270
271 def print_subcommands(self):
271 def print_subcommands(self):
272 """Print the subcommand part of the help."""
272 """Print the subcommand part of the help."""
273 if not self.subcommands:
273 if not self.subcommands:
274 return
274 return
275
275
276 lines = ["Subcommands"]
276 lines = ["Subcommands"]
277 lines.append('-'*len(lines[0]))
277 lines.append('-'*len(lines[0]))
278 lines.append('')
278 lines.append('')
279 for p in wrap_paragraphs(self.subcommand_description):
279 for p in wrap_paragraphs(self.subcommand_description):
280 lines.append(p)
280 lines.append(p)
281 lines.append('')
281 lines.append('')
282 for subc, (cls, help) in self.subcommands.iteritems():
282 for subc, (cls, help) in self.subcommands.iteritems():
283 lines.append(subc)
283 lines.append(subc)
284 if help:
284 if help:
285 lines.append(indent(dedent(help.strip())))
285 lines.append(indent(dedent(help.strip())))
286 lines.append('')
286 lines.append('')
287 print os.linesep.join(lines)
287 print os.linesep.join(lines)
288
288
289 def print_help(self, classes=False):
289 def print_help(self, classes=False):
290 """Print the help for each Configurable class in self.classes.
290 """Print the help for each Configurable class in self.classes.
291
291
292 If classes=False (the default), only flags and aliases are printed.
292 If classes=False (the default), only flags and aliases are printed.
293 """
293 """
294 self.print_subcommands()
294 self.print_subcommands()
295 self.print_options()
295 self.print_options()
296
296
297 if classes:
297 if classes:
298 if self.classes:
298 if self.classes:
299 print "Class parameters"
299 print "Class parameters"
300 print "----------------"
300 print "----------------"
301 print
301 print
302 for p in wrap_paragraphs(self.keyvalue_description):
302 for p in wrap_paragraphs(self.keyvalue_description):
303 print p
303 print p
304 print
304 print
305
305
306 for cls in self.classes:
306 for cls in self.classes:
307 cls.class_print_help()
307 cls.class_print_help()
308 print
308 print
309 else:
309 else:
310 print "To see all available configurables, use `--help-all`"
310 print "To see all available configurables, use `--help-all`"
311 print
311 print
312
312
313 def print_description(self):
313 def print_description(self):
314 """Print the application description."""
314 """Print the application description."""
315 for p in wrap_paragraphs(self.description):
315 for p in wrap_paragraphs(self.description):
316 print p
316 print p
317 print
317 print
318
318
319 def print_examples(self):
319 def print_examples(self):
320 """Print usage and examples.
320 """Print usage and examples.
321
321
322 This usage string goes at the end of the command line help string
322 This usage string goes at the end of the command line help string
323 and should contain examples of the application's usage.
323 and should contain examples of the application's usage.
324 """
324 """
325 if self.examples:
325 if self.examples:
326 print "Examples"
326 print "Examples"
327 print "--------"
327 print "--------"
328 print
328 print
329 print indent(dedent(self.examples.strip()))
329 print indent(dedent(self.examples.strip()))
330 print
330 print
331
331
332 def print_version(self):
332 def print_version(self):
333 """Print the version string."""
333 """Print the version string."""
334 print self.version
334 print self.version
335
335
336 def update_config(self, config):
336 def update_config(self, config):
337 """Fire the traits events when the config is updated."""
337 """Fire the traits events when the config is updated."""
338 # Save a copy of the current config.
338 # Save a copy of the current config.
339 newconfig = deepcopy(self.config)
339 newconfig = deepcopy(self.config)
340 # Merge the new config into the current one.
340 # Merge the new config into the current one.
341 newconfig._merge(config)
341 newconfig._merge(config)
342 # Save the combined config as self.config, which triggers the traits
342 # Save the combined config as self.config, which triggers the traits
343 # events.
343 # events.
344 self.config = newconfig
344 self.config = newconfig
345
345
346 @catch_config_error
346 @catch_config_error
347 def initialize_subcommand(self, subc, argv=None):
347 def initialize_subcommand(self, subc, argv=None):
348 """Initialize a subcommand with argv."""
348 """Initialize a subcommand with argv."""
349 subapp,help = self.subcommands.get(subc)
349 subapp,help = self.subcommands.get(subc)
350
350
351 if isinstance(subapp, basestring):
351 if isinstance(subapp, basestring):
352 subapp = import_item(subapp)
352 subapp = import_item(subapp)
353
353
354 # clear existing instances
354 # clear existing instances
355 self.__class__.clear_instance()
355 self.__class__.clear_instance()
356 # instantiate
356 # instantiate
357 self.subapp = subapp.instance()
357 self.subapp = subapp.instance()
358 # and initialize subapp
358 # and initialize subapp
359 self.subapp.initialize(argv)
359 self.subapp.initialize(argv)
360
360
361 def flatten_flags(self):
361 def flatten_flags(self):
362 """flatten flags and aliases, so cl-args override as expected.
362 """flatten flags and aliases, so cl-args override as expected.
363
363
364 This prevents issues such as an alias pointing to InteractiveShell,
364 This prevents issues such as an alias pointing to InteractiveShell,
365 but a config file setting the same trait in TerminalInteraciveShell
365 but a config file setting the same trait in TerminalInteraciveShell
366 getting inappropriate priority over the command-line arg.
366 getting inappropriate priority over the command-line arg.
367
367
368 Only aliases with exactly one descendent in the class list
368 Only aliases with exactly one descendent in the class list
369 will be promoted.
369 will be promoted.
370
370
371 """
371 """
372 # build a tree of classes in our list that inherit from a particular
372 # build a tree of classes in our list that inherit from a particular
373 # it will be a dict by parent classname of classes in our list
373 # it will be a dict by parent classname of classes in our list
374 # that are descendents
374 # that are descendents
375 mro_tree = defaultdict(list)
375 mro_tree = defaultdict(list)
376 for cls in self.classes:
376 for cls in self.classes:
377 clsname = cls.__name__
377 clsname = cls.__name__
378 for parent in cls.mro()[1:-3]:
378 for parent in cls.mro()[1:-3]:
379 # exclude cls itself and Configurable,HasTraits,object
379 # exclude cls itself and Configurable,HasTraits,object
380 mro_tree[parent.__name__].append(clsname)
380 mro_tree[parent.__name__].append(clsname)
381 # flatten aliases, which have the form:
381 # flatten aliases, which have the form:
382 # { 'alias' : 'Class.trait' }
382 # { 'alias' : 'Class.trait' }
383 aliases = {}
383 aliases = {}
384 for alias, cls_trait in self.aliases.iteritems():
384 for alias, cls_trait in self.aliases.iteritems():
385 cls,trait = cls_trait.split('.',1)
385 cls,trait = cls_trait.split('.',1)
386 children = mro_tree[cls]
386 children = mro_tree[cls]
387 if len(children) == 1:
387 if len(children) == 1:
388 # exactly one descendent, promote alias
388 # exactly one descendent, promote alias
389 cls = children[0]
389 cls = children[0]
390 aliases[alias] = '.'.join([cls,trait])
390 aliases[alias] = '.'.join([cls,trait])
391
391
392 # flatten flags, which are of the form:
392 # flatten flags, which are of the form:
393 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
393 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
394 flags = {}
394 flags = {}
395 for key, (flagdict, help) in self.flags.iteritems():
395 for key, (flagdict, help) in self.flags.iteritems():
396 newflag = {}
396 newflag = {}
397 for cls, subdict in flagdict.iteritems():
397 for cls, subdict in flagdict.iteritems():
398 children = mro_tree[cls]
398 children = mro_tree[cls]
399 # exactly one descendent, promote flag section
399 # exactly one descendent, promote flag section
400 if len(children) == 1:
400 if len(children) == 1:
401 cls = children[0]
401 cls = children[0]
402 newflag[cls] = subdict
402 newflag[cls] = subdict
403 flags[key] = (newflag, help)
403 flags[key] = (newflag, help)
404 return flags, aliases
404 return flags, aliases
405
405
406 @catch_config_error
406 @catch_config_error
407 def parse_command_line(self, argv=None):
407 def parse_command_line(self, argv=None):
408 """Parse the command line arguments."""
408 """Parse the command line arguments."""
409 argv = sys.argv[1:] if argv is None else argv
409 argv = sys.argv[1:] if argv is None else argv
410
410
411 if argv and argv[0] == 'help':
411 if argv and argv[0] == 'help':
412 # turn `ipython help notebook` into `ipython notebook -h`
412 # turn `ipython help notebook` into `ipython notebook -h`
413 argv = argv[1:] + ['-h']
413 argv = argv[1:] + ['-h']
414
414
415 if self.subcommands and len(argv) > 0:
415 if self.subcommands and len(argv) > 0:
416 # we have subcommands, and one may have been specified
416 # we have subcommands, and one may have been specified
417 subc, subargv = argv[0], argv[1:]
417 subc, subargv = argv[0], argv[1:]
418 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
418 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
419 # it's a subcommand, and *not* a flag or class parameter
419 # it's a subcommand, and *not* a flag or class parameter
420 return self.initialize_subcommand(subc, subargv)
420 return self.initialize_subcommand(subc, subargv)
421
421
422 if '-h' in argv or '--help' in argv or '--help-all' in argv:
422 # Arguments after a '--' argument are for the script IPython may be
423 # about to run, not IPython iteslf. For arguments parsed here (help and
424 # version), we want to only search the arguments up to the first
425 # occurrence of '--', which we're calling interpreted_argv.
426 try:
427 interpreted_argv = argv[:argv.index('--')]
428 except ValueError:
429 interpreted_argv = argv
430
431 if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
423 self.print_description()
432 self.print_description()
424 self.print_help('--help-all' in argv)
433 self.print_help('--help-all' in interpreted_argv)
425 self.print_examples()
434 self.print_examples()
426 self.exit(0)
435 self.exit(0)
427
436
428 if '--version' in argv or '-V' in argv:
437 if '--version' in interpreted_argv or '-V' in interpreted_argv:
429 self.print_version()
438 self.print_version()
430 self.exit(0)
439 self.exit(0)
431
440
432 # flatten flags&aliases, so cl-args get appropriate priority:
441 # flatten flags&aliases, so cl-args get appropriate priority:
433 flags,aliases = self.flatten_flags()
442 flags,aliases = self.flatten_flags()
434
443
435 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
444 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
436 flags=flags)
445 flags=flags)
437 config = loader.load_config()
446 config = loader.load_config()
438 self.update_config(config)
447 self.update_config(config)
439 # store unparsed args in extra_args
448 # store unparsed args in extra_args
440 self.extra_args = loader.extra_args
449 self.extra_args = loader.extra_args
441
450
442 @catch_config_error
451 @catch_config_error
443 def load_config_file(self, filename, path=None):
452 def load_config_file(self, filename, path=None):
444 """Load a .py based config file by filename and path."""
453 """Load a .py based config file by filename and path."""
445 loader = PyFileConfigLoader(filename, path=path)
454 loader = PyFileConfigLoader(filename, path=path)
446 try:
455 try:
447 config = loader.load_config()
456 config = loader.load_config()
448 except ConfigFileNotFound:
457 except ConfigFileNotFound:
449 # problem finding the file, raise
458 # problem finding the file, raise
450 raise
459 raise
451 except Exception:
460 except Exception:
452 # try to get the full filename, but it will be empty in the
461 # try to get the full filename, but it will be empty in the
453 # unlikely event that the error raised before filefind finished
462 # unlikely event that the error raised before filefind finished
454 filename = loader.full_filename or filename
463 filename = loader.full_filename or filename
455 # problem while running the file
464 # problem while running the file
456 self.log.error("Exception while loading config file %s",
465 self.log.error("Exception while loading config file %s",
457 filename, exc_info=True)
466 filename, exc_info=True)
458 else:
467 else:
459 self.log.debug("Loaded config file: %s", loader.full_filename)
468 self.log.debug("Loaded config file: %s", loader.full_filename)
460 self.update_config(config)
469 self.update_config(config)
461
470
462 def generate_config_file(self):
471 def generate_config_file(self):
463 """generate default config file from Configurables"""
472 """generate default config file from Configurables"""
464 lines = ["# Configuration file for %s."%self.name]
473 lines = ["# Configuration file for %s."%self.name]
465 lines.append('')
474 lines.append('')
466 lines.append('c = get_config()')
475 lines.append('c = get_config()')
467 lines.append('')
476 lines.append('')
468 for cls in self.classes:
477 for cls in self.classes:
469 lines.append(cls.class_config_section())
478 lines.append(cls.class_config_section())
470 return '\n'.join(lines)
479 return '\n'.join(lines)
471
480
472 def exit(self, exit_status=0):
481 def exit(self, exit_status=0):
473 self.log.debug("Exiting application: %s" % self.name)
482 self.log.debug("Exiting application: %s" % self.name)
474 sys.exit(exit_status)
483 sys.exit(exit_status)
475
484
476 #-----------------------------------------------------------------------------
485 #-----------------------------------------------------------------------------
477 # utility functions, for convenience
486 # utility functions, for convenience
478 #-----------------------------------------------------------------------------
487 #-----------------------------------------------------------------------------
479
488
480 def boolean_flag(name, configurable, set_help='', unset_help=''):
489 def boolean_flag(name, configurable, set_help='', unset_help=''):
481 """Helper for building basic --trait, --no-trait flags.
490 """Helper for building basic --trait, --no-trait flags.
482
491
483 Parameters
492 Parameters
484 ----------
493 ----------
485
494
486 name : str
495 name : str
487 The name of the flag.
496 The name of the flag.
488 configurable : str
497 configurable : str
489 The 'Class.trait' string of the trait to be set/unset with the flag
498 The 'Class.trait' string of the trait to be set/unset with the flag
490 set_help : unicode
499 set_help : unicode
491 help string for --name flag
500 help string for --name flag
492 unset_help : unicode
501 unset_help : unicode
493 help string for --no-name flag
502 help string for --no-name flag
494
503
495 Returns
504 Returns
496 -------
505 -------
497
506
498 cfg : dict
507 cfg : dict
499 A dict with two keys: 'name', and 'no-name', for setting and unsetting
508 A dict with two keys: 'name', and 'no-name', for setting and unsetting
500 the trait, respectively.
509 the trait, respectively.
501 """
510 """
502 # default helpstrings
511 # default helpstrings
503 set_help = set_help or "set %s=True"%configurable
512 set_help = set_help or "set %s=True"%configurable
504 unset_help = unset_help or "set %s=False"%configurable
513 unset_help = unset_help or "set %s=False"%configurable
505
514
506 cls,trait = configurable.split('.')
515 cls,trait = configurable.split('.')
507
516
508 setter = {cls : {trait : True}}
517 setter = {cls : {trait : True}}
509 unsetter = {cls : {trait : False}}
518 unsetter = {cls : {trait : False}}
510 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
519 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
511
520
General Comments 0
You need to be logged in to leave comments. Login now