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