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