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