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