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