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