##// END OF EJS Templates
Fix applications displaying subcommands
Thomas Kluyver -
Show More
@@ -1,586 +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 """
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.format(
313 for p in wrap_paragraphs(self.subcommand_description.format(
314 app=os.path.basename(self.argv[0]))):
314 app=self.name)):
315 lines.append(p)
315 lines.append(p)
316 lines.append('')
316 lines.append('')
317 for subc, (cls, help) in iteritems(self.subcommands):
317 for subc, (cls, help) in iteritems(self.subcommands):
318 lines.append(subc)
318 lines.append(subc)
319 if help:
319 if help:
320 lines.append(indent(dedent(help.strip())))
320 lines.append(indent(dedent(help.strip())))
321 lines.append('')
321 lines.append('')
322 print(os.linesep.join(lines))
322 print(os.linesep.join(lines))
323
323
324 def print_help(self, classes=False):
324 def print_help(self, classes=False):
325 """Print the help for each Configurable class in self.classes.
325 """Print the help for each Configurable class in self.classes.
326
326
327 If classes=False (the default), only flags and aliases are printed.
327 If classes=False (the default), only flags and aliases are printed.
328 """
328 """
329 self.print_description()
329 self.print_description()
330 self.print_subcommands()
330 self.print_subcommands()
331 self.print_options()
331 self.print_options()
332
332
333 if classes:
333 if classes:
334 if self.classes:
334 if self.classes:
335 print("Class parameters")
335 print("Class parameters")
336 print("----------------")
336 print("----------------")
337 print()
337 print()
338 for p in wrap_paragraphs(self.keyvalue_description):
338 for p in wrap_paragraphs(self.keyvalue_description):
339 print(p)
339 print(p)
340 print()
340 print()
341
341
342 for cls in self.classes:
342 for cls in self.classes:
343 cls.class_print_help()
343 cls.class_print_help()
344 print()
344 print()
345 else:
345 else:
346 print("To see all available configurables, use `--help-all`")
346 print("To see all available configurables, use `--help-all`")
347 print()
347 print()
348
348
349 self.print_examples()
349 self.print_examples()
350
350
351
351
352 def print_description(self):
352 def print_description(self):
353 """Print the application description."""
353 """Print the application description."""
354 for p in wrap_paragraphs(self.description):
354 for p in wrap_paragraphs(self.description):
355 print(p)
355 print(p)
356 print()
356 print()
357
357
358 def print_examples(self):
358 def print_examples(self):
359 """Print usage and examples.
359 """Print usage and examples.
360
360
361 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
362 and should contain examples of the application's usage.
362 and should contain examples of the application's usage.
363 """
363 """
364 if self.examples:
364 if self.examples:
365 print("Examples")
365 print("Examples")
366 print("--------")
366 print("--------")
367 print()
367 print()
368 print(indent(dedent(self.examples.strip())))
368 print(indent(dedent(self.examples.strip())))
369 print()
369 print()
370
370
371 def print_version(self):
371 def print_version(self):
372 """Print the version string."""
372 """Print the version string."""
373 print(self.version)
373 print(self.version)
374
374
375 def update_config(self, config):
375 def update_config(self, config):
376 """Fire the traits events when the config is updated."""
376 """Fire the traits events when the config is updated."""
377 # Save a copy of the current config.
377 # Save a copy of the current config.
378 newconfig = deepcopy(self.config)
378 newconfig = deepcopy(self.config)
379 # Merge the new config into the current one.
379 # Merge the new config into the current one.
380 newconfig.merge(config)
380 newconfig.merge(config)
381 # Save the combined config as self.config, which triggers the traits
381 # Save the combined config as self.config, which triggers the traits
382 # events.
382 # events.
383 self.config = newconfig
383 self.config = newconfig
384
384
385 @catch_config_error
385 @catch_config_error
386 def initialize_subcommand(self, subc, argv=None):
386 def initialize_subcommand(self, subc, argv=None):
387 """Initialize a subcommand with argv."""
387 """Initialize a subcommand with argv."""
388 subapp,help = self.subcommands.get(subc)
388 subapp,help = self.subcommands.get(subc)
389
389
390 if isinstance(subapp, string_types):
390 if isinstance(subapp, string_types):
391 subapp = import_item(subapp)
391 subapp = import_item(subapp)
392
392
393 # clear existing instances
393 # clear existing instances
394 self.__class__.clear_instance()
394 self.__class__.clear_instance()
395 # instantiate
395 # instantiate
396 self.subapp = subapp.instance(config=self.config)
396 self.subapp = subapp.instance(config=self.config)
397 # and initialize subapp
397 # and initialize subapp
398 self.subapp.initialize(argv)
398 self.subapp.initialize(argv)
399
399
400 def flatten_flags(self):
400 def flatten_flags(self):
401 """flatten flags and aliases, so cl-args override as expected.
401 """flatten flags and aliases, so cl-args override as expected.
402
402
403 This prevents issues such as an alias pointing to InteractiveShell,
403 This prevents issues such as an alias pointing to InteractiveShell,
404 but a config file setting the same trait in TerminalInteraciveShell
404 but a config file setting the same trait in TerminalInteraciveShell
405 getting inappropriate priority over the command-line arg.
405 getting inappropriate priority over the command-line arg.
406
406
407 Only aliases with exactly one descendent in the class list
407 Only aliases with exactly one descendent in the class list
408 will be promoted.
408 will be promoted.
409
409
410 """
410 """
411 # 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
412 # 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
413 # that are descendents
413 # that are descendents
414 mro_tree = defaultdict(list)
414 mro_tree = defaultdict(list)
415 for cls in self.classes:
415 for cls in self.classes:
416 clsname = cls.__name__
416 clsname = cls.__name__
417 for parent in cls.mro()[1:-3]:
417 for parent in cls.mro()[1:-3]:
418 # exclude cls itself and Configurable,HasTraits,object
418 # exclude cls itself and Configurable,HasTraits,object
419 mro_tree[parent.__name__].append(clsname)
419 mro_tree[parent.__name__].append(clsname)
420 # flatten aliases, which have the form:
420 # flatten aliases, which have the form:
421 # { 'alias' : 'Class.trait' }
421 # { 'alias' : 'Class.trait' }
422 aliases = {}
422 aliases = {}
423 for alias, cls_trait in iteritems(self.aliases):
423 for alias, cls_trait in iteritems(self.aliases):
424 cls,trait = cls_trait.split('.',1)
424 cls,trait = cls_trait.split('.',1)
425 children = mro_tree[cls]
425 children = mro_tree[cls]
426 if len(children) == 1:
426 if len(children) == 1:
427 # exactly one descendent, promote alias
427 # exactly one descendent, promote alias
428 cls = children[0]
428 cls = children[0]
429 aliases[alias] = '.'.join([cls,trait])
429 aliases[alias] = '.'.join([cls,trait])
430
430
431 # flatten flags, which are of the form:
431 # flatten flags, which are of the form:
432 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
432 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
433 flags = {}
433 flags = {}
434 for key, (flagdict, help) in iteritems(self.flags):
434 for key, (flagdict, help) in iteritems(self.flags):
435 newflag = {}
435 newflag = {}
436 for cls, subdict in iteritems(flagdict):
436 for cls, subdict in iteritems(flagdict):
437 children = mro_tree[cls]
437 children = mro_tree[cls]
438 # exactly one descendent, promote flag section
438 # exactly one descendent, promote flag section
439 if len(children) == 1:
439 if len(children) == 1:
440 cls = children[0]
440 cls = children[0]
441 newflag[cls] = subdict
441 newflag[cls] = subdict
442 flags[key] = (newflag, help)
442 flags[key] = (newflag, help)
443 return flags, aliases
443 return flags, aliases
444
444
445 @catch_config_error
445 @catch_config_error
446 def parse_command_line(self, argv=None):
446 def parse_command_line(self, argv=None):
447 """Parse the command line arguments."""
447 """Parse the command line arguments."""
448 argv = sys.argv[1:] if argv is None else argv
448 argv = sys.argv[1:] if argv is None else argv
449 self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
449 self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
450
450
451 if argv and argv[0] == 'help':
451 if argv and argv[0] == 'help':
452 # turn `ipython help notebook` into `ipython notebook -h`
452 # turn `ipython help notebook` into `ipython notebook -h`
453 argv = argv[1:] + ['-h']
453 argv = argv[1:] + ['-h']
454
454
455 if self.subcommands and len(argv) > 0:
455 if self.subcommands and len(argv) > 0:
456 # we have subcommands, and one may have been specified
456 # we have subcommands, and one may have been specified
457 subc, subargv = argv[0], argv[1:]
457 subc, subargv = argv[0], argv[1:]
458 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:
459 # it's a subcommand, and *not* a flag or class parameter
459 # it's a subcommand, and *not* a flag or class parameter
460 return self.initialize_subcommand(subc, subargv)
460 return self.initialize_subcommand(subc, subargv)
461
461
462 # Arguments after a '--' argument are for the script IPython may be
462 # Arguments after a '--' argument are for the script IPython may be
463 # 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
464 # 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
465 # occurrence of '--', which we're calling interpreted_argv.
465 # occurrence of '--', which we're calling interpreted_argv.
466 try:
466 try:
467 interpreted_argv = argv[:argv.index('--')]
467 interpreted_argv = argv[:argv.index('--')]
468 except ValueError:
468 except ValueError:
469 interpreted_argv = argv
469 interpreted_argv = argv
470
470
471 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')):
472 self.print_help('--help-all' in interpreted_argv)
472 self.print_help('--help-all' in interpreted_argv)
473 self.exit(0)
473 self.exit(0)
474
474
475 if '--version' in interpreted_argv or '-V' in interpreted_argv:
475 if '--version' in interpreted_argv or '-V' in interpreted_argv:
476 self.print_version()
476 self.print_version()
477 self.exit(0)
477 self.exit(0)
478
478
479 # flatten flags&aliases, so cl-args get appropriate priority:
479 # flatten flags&aliases, so cl-args get appropriate priority:
480 flags,aliases = self.flatten_flags()
480 flags,aliases = self.flatten_flags()
481 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
481 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
482 flags=flags, log=self.log)
482 flags=flags, log=self.log)
483 config = loader.load_config()
483 config = loader.load_config()
484 self.update_config(config)
484 self.update_config(config)
485 # store unparsed args in extra_args
485 # store unparsed args in extra_args
486 self.extra_args = loader.extra_args
486 self.extra_args = loader.extra_args
487
487
488 @classmethod
488 @classmethod
489 def _load_config_files(cls, basefilename, path=None, log=None):
489 def _load_config_files(cls, basefilename, path=None, log=None):
490 """Load config files (py,json) by filename and path.
490 """Load config files (py,json) by filename and path.
491
491
492 yield each config object in turn.
492 yield each config object in turn.
493 """
493 """
494 pyloader = PyFileConfigLoader(basefilename+'.py', path=path, log=log)
494 pyloader = PyFileConfigLoader(basefilename+'.py', path=path, log=log)
495 jsonloader = JSONFileConfigLoader(basefilename+'.json', path=path, log=log)
495 jsonloader = JSONFileConfigLoader(basefilename+'.json', path=path, log=log)
496 config = None
496 config = None
497 for loader in [pyloader, jsonloader]:
497 for loader in [pyloader, jsonloader]:
498 try:
498 try:
499 config = loader.load_config()
499 config = loader.load_config()
500 except ConfigFileNotFound:
500 except ConfigFileNotFound:
501 pass
501 pass
502 except Exception:
502 except Exception:
503 # 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
504 # unlikely event that the error raised before filefind finished
504 # unlikely event that the error raised before filefind finished
505 filename = loader.full_filename or basefilename
505 filename = loader.full_filename or basefilename
506 # problem while running the file
506 # problem while running the file
507 if log:
507 if log:
508 log.error("Exception while loading config file %s",
508 log.error("Exception while loading config file %s",
509 filename, exc_info=True)
509 filename, exc_info=True)
510 else:
510 else:
511 if log:
511 if log:
512 log.debug("Loaded config file: %s", loader.full_filename)
512 log.debug("Loaded config file: %s", loader.full_filename)
513 if config:
513 if config:
514 yield config
514 yield config
515
515
516 raise StopIteration
516 raise StopIteration
517
517
518
518
519 @catch_config_error
519 @catch_config_error
520 def load_config_file(self, filename, path=None):
520 def load_config_file(self, filename, path=None):
521 """Load config files by filename and path."""
521 """Load config files by filename and path."""
522 filename, ext = os.path.splitext(filename)
522 filename, ext = os.path.splitext(filename)
523 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):
524 self.update_config(config)
524 self.update_config(config)
525
525
526
526
527 def generate_config_file(self):
527 def generate_config_file(self):
528 """generate default config file from Configurables"""
528 """generate default config file from Configurables"""
529 lines = ["# Configuration file for %s."%self.name]
529 lines = ["# Configuration file for %s."%self.name]
530 lines.append('')
530 lines.append('')
531 lines.append('c = get_config()')
531 lines.append('c = get_config()')
532 lines.append('')
532 lines.append('')
533 for cls in self.classes:
533 for cls in self.classes:
534 lines.append(cls.class_config_section())
534 lines.append(cls.class_config_section())
535 return '\n'.join(lines)
535 return '\n'.join(lines)
536
536
537 def exit(self, exit_status=0):
537 def exit(self, exit_status=0):
538 self.log.debug("Exiting application: %s" % self.name)
538 self.log.debug("Exiting application: %s" % self.name)
539 sys.exit(exit_status)
539 sys.exit(exit_status)
540
540
541 @classmethod
541 @classmethod
542 def launch_instance(cls, argv=None, **kwargs):
542 def launch_instance(cls, argv=None, **kwargs):
543 """Launch a global instance of this Application
543 """Launch a global instance of this Application
544
544
545 If a global instance already exists, this reinitializes and starts it
545 If a global instance already exists, this reinitializes and starts it
546 """
546 """
547 app = cls.instance(**kwargs)
547 app = cls.instance(**kwargs)
548 app.initialize(argv)
548 app.initialize(argv)
549 app.start()
549 app.start()
550
550
551 #-----------------------------------------------------------------------------
551 #-----------------------------------------------------------------------------
552 # utility functions, for convenience
552 # utility functions, for convenience
553 #-----------------------------------------------------------------------------
553 #-----------------------------------------------------------------------------
554
554
555 def boolean_flag(name, configurable, set_help='', unset_help=''):
555 def boolean_flag(name, configurable, set_help='', unset_help=''):
556 """Helper for building basic --trait, --no-trait flags.
556 """Helper for building basic --trait, --no-trait flags.
557
557
558 Parameters
558 Parameters
559 ----------
559 ----------
560
560
561 name : str
561 name : str
562 The name of the flag.
562 The name of the flag.
563 configurable : str
563 configurable : str
564 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
565 set_help : unicode
565 set_help : unicode
566 help string for --name flag
566 help string for --name flag
567 unset_help : unicode
567 unset_help : unicode
568 help string for --no-name flag
568 help string for --no-name flag
569
569
570 Returns
570 Returns
571 -------
571 -------
572
572
573 cfg : dict
573 cfg : dict
574 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
575 the trait, respectively.
575 the trait, respectively.
576 """
576 """
577 # default helpstrings
577 # default helpstrings
578 set_help = set_help or "set %s=True"%configurable
578 set_help = set_help or "set %s=True"%configurable
579 unset_help = unset_help or "set %s=False"%configurable
579 unset_help = unset_help or "set %s=False"%configurable
580
580
581 cls,trait = configurable.split('.')
581 cls,trait = configurable.split('.')
582
582
583 setter = {cls : {trait : True}}
583 setter = {cls : {trait : True}}
584 unsetter = {cls : {trait : False}}
584 unsetter = {cls : {trait : False}}
585 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
585 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
586
586
@@ -1,315 +1,315 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for managing IPython profiles.
3 An application for managing IPython profiles.
4
4
5 To be invoked as the `ipython profile` subcommand.
5 To be invoked as the `ipython profile` subcommand.
6
6
7 Authors:
7 Authors:
8
8
9 * Min RK
9 * Min RK
10
10
11 """
11 """
12 from __future__ import print_function
12 from __future__ import print_function
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 import os
25 import os
26
26
27 from IPython.config.application import Application
27 from IPython.config.application import Application
28 from IPython.core.application import (
28 from IPython.core.application import (
29 BaseIPythonApplication, base_flags
29 BaseIPythonApplication, base_flags
30 )
30 )
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.importstring import import_item
32 from IPython.utils.importstring import import_item
33 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
33 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
34 from IPython.utils import py3compat
34 from IPython.utils import py3compat
35 from IPython.utils.traitlets import Unicode, Bool, Dict
35 from IPython.utils.traitlets import Unicode, Bool, Dict
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Constants
38 # Constants
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 create_help = """Create an IPython profile by name
41 create_help = """Create an IPython profile by name
42
42
43 Create an ipython profile directory by its name or
43 Create an ipython profile directory by its name or
44 profile directory path. Profile directories contain
44 profile directory path. Profile directories contain
45 configuration, log and security related files and are named
45 configuration, log and security related files and are named
46 using the convention 'profile_<name>'. By default they are
46 using the convention 'profile_<name>'. By default they are
47 located in your ipython directory. Once created, you will
47 located in your ipython directory. Once created, you will
48 can edit the configuration files in the profile
48 can edit the configuration files in the profile
49 directory to configure IPython. Most users will create a
49 directory to configure IPython. Most users will create a
50 profile directory by name,
50 profile directory by name,
51 `ipython profile create myprofile`, which will put the directory
51 `ipython profile create myprofile`, which will put the directory
52 in `<ipython_dir>/profile_myprofile`.
52 in `<ipython_dir>/profile_myprofile`.
53 """
53 """
54 list_help = """List available IPython profiles
54 list_help = """List available IPython profiles
55
55
56 List all available profiles, by profile location, that can
56 List all available profiles, by profile location, that can
57 be found in the current working directly or in the ipython
57 be found in the current working directly or in the ipython
58 directory. Profile directories are named using the convention
58 directory. Profile directories are named using the convention
59 'profile_<profile>'.
59 'profile_<profile>'.
60 """
60 """
61 profile_help = """Manage IPython profiles
61 profile_help = """Manage IPython profiles
62
62
63 Profile directories contain
63 Profile directories contain
64 configuration, log and security related files and are named
64 configuration, log and security related files and are named
65 using the convention 'profile_<name>'. By default they are
65 using the convention 'profile_<name>'. By default they are
66 located in your ipython directory. You can create profiles
66 located in your ipython directory. You can create profiles
67 with `ipython profile create <name>`, or see the profiles you
67 with `ipython profile create <name>`, or see the profiles you
68 already have with `ipython profile list`
68 already have with `ipython profile list`
69
69
70 To get started configuring IPython, simply do:
70 To get started configuring IPython, simply do:
71
71
72 $> ipython profile create
72 $> ipython profile create
73
73
74 and IPython will create the default profile in <ipython_dir>/profile_default,
74 and IPython will create the default profile in <ipython_dir>/profile_default,
75 where you can edit ipython_config.py to start configuring IPython.
75 where you can edit ipython_config.py to start configuring IPython.
76
76
77 """
77 """
78
78
79 _list_examples = "ipython profile list # list all profiles"
79 _list_examples = "ipython profile list # list all profiles"
80
80
81 _create_examples = """
81 _create_examples = """
82 ipython profile create foo # create profile foo w/ default config files
82 ipython profile create foo # create profile foo w/ default config files
83 ipython profile create foo --reset # restage default config files over current
83 ipython profile create foo --reset # restage default config files over current
84 ipython profile create foo --parallel # also stage parallel config files
84 ipython profile create foo --parallel # also stage parallel config files
85 """
85 """
86
86
87 _main_examples = """
87 _main_examples = """
88 ipython profile create -h # show the help string for the create subcommand
88 ipython profile create -h # show the help string for the create subcommand
89 ipython profile list -h # show the help string for the list subcommand
89 ipython profile list -h # show the help string for the list subcommand
90
90
91 ipython locate profile foo # print the path to the directory for profile 'foo'
91 ipython locate profile foo # print the path to the directory for profile 'foo'
92 """
92 """
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Profile Application Class (for `ipython profile` subcommand)
95 # Profile Application Class (for `ipython profile` subcommand)
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97
97
98
98
99 def list_profiles_in(path):
99 def list_profiles_in(path):
100 """list profiles in a given root directory"""
100 """list profiles in a given root directory"""
101 files = os.listdir(path)
101 files = os.listdir(path)
102 profiles = []
102 profiles = []
103 for f in files:
103 for f in files:
104 try:
104 try:
105 full_path = os.path.join(path, f)
105 full_path = os.path.join(path, f)
106 except UnicodeError:
106 except UnicodeError:
107 continue
107 continue
108 if os.path.isdir(full_path) and f.startswith('profile_'):
108 if os.path.isdir(full_path) and f.startswith('profile_'):
109 profiles.append(f.split('_',1)[-1])
109 profiles.append(f.split('_',1)[-1])
110 return profiles
110 return profiles
111
111
112
112
113 def list_bundled_profiles():
113 def list_bundled_profiles():
114 """list profiles that are bundled with IPython."""
114 """list profiles that are bundled with IPython."""
115 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
115 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
116 files = os.listdir(path)
116 files = os.listdir(path)
117 profiles = []
117 profiles = []
118 for profile in files:
118 for profile in files:
119 full_path = os.path.join(path, profile)
119 full_path = os.path.join(path, profile)
120 if os.path.isdir(full_path) and profile != "__pycache__":
120 if os.path.isdir(full_path) and profile != "__pycache__":
121 profiles.append(profile)
121 profiles.append(profile)
122 return profiles
122 return profiles
123
123
124
124
125 class ProfileLocate(BaseIPythonApplication):
125 class ProfileLocate(BaseIPythonApplication):
126 description = """print the path to an IPython profile dir"""
126 description = """print the path to an IPython profile dir"""
127
127
128 def parse_command_line(self, argv=None):
128 def parse_command_line(self, argv=None):
129 super(ProfileLocate, self).parse_command_line(argv)
129 super(ProfileLocate, self).parse_command_line(argv)
130 if self.extra_args:
130 if self.extra_args:
131 self.profile = self.extra_args[0]
131 self.profile = self.extra_args[0]
132
132
133 def start(self):
133 def start(self):
134 print(self.profile_dir.location)
134 print(self.profile_dir.location)
135
135
136
136
137 class ProfileList(Application):
137 class ProfileList(Application):
138 name = u'ipython-profile'
138 name = u'ipython-profile'
139 description = list_help
139 description = list_help
140 examples = _list_examples
140 examples = _list_examples
141
141
142 aliases = Dict({
142 aliases = Dict({
143 'ipython-dir' : 'ProfileList.ipython_dir',
143 'ipython-dir' : 'ProfileList.ipython_dir',
144 'log-level' : 'Application.log_level',
144 'log-level' : 'Application.log_level',
145 })
145 })
146 flags = Dict(dict(
146 flags = Dict(dict(
147 debug = ({'Application' : {'log_level' : 0}},
147 debug = ({'Application' : {'log_level' : 0}},
148 "Set Application.log_level to 0, maximizing log output."
148 "Set Application.log_level to 0, maximizing log output."
149 )
149 )
150 ))
150 ))
151
151
152 ipython_dir = Unicode(get_ipython_dir(), config=True,
152 ipython_dir = Unicode(get_ipython_dir(), config=True,
153 help="""
153 help="""
154 The name of the IPython directory. This directory is used for logging
154 The name of the IPython directory. This directory is used for logging
155 configuration (through profiles), history storage, etc. The default
155 configuration (through profiles), history storage, etc. The default
156 is usually $HOME/.ipython. This options can also be specified through
156 is usually $HOME/.ipython. This options can also be specified through
157 the environment variable IPYTHONDIR.
157 the environment variable IPYTHONDIR.
158 """
158 """
159 )
159 )
160
160
161
161
162 def _print_profiles(self, profiles):
162 def _print_profiles(self, profiles):
163 """print list of profiles, indented."""
163 """print list of profiles, indented."""
164 for profile in profiles:
164 for profile in profiles:
165 print(' %s' % profile)
165 print(' %s' % profile)
166
166
167 def list_profile_dirs(self):
167 def list_profile_dirs(self):
168 profiles = list_bundled_profiles()
168 profiles = list_bundled_profiles()
169 if profiles:
169 if profiles:
170 print()
170 print()
171 print("Available profiles in IPython:")
171 print("Available profiles in IPython:")
172 self._print_profiles(profiles)
172 self._print_profiles(profiles)
173 print()
173 print()
174 print(" The first request for a bundled profile will copy it")
174 print(" The first request for a bundled profile will copy it")
175 print(" into your IPython directory (%s)," % self.ipython_dir)
175 print(" into your IPython directory (%s)," % self.ipython_dir)
176 print(" where you can customize it.")
176 print(" where you can customize it.")
177
177
178 profiles = list_profiles_in(self.ipython_dir)
178 profiles = list_profiles_in(self.ipython_dir)
179 if profiles:
179 if profiles:
180 print()
180 print()
181 print("Available profiles in %s:" % self.ipython_dir)
181 print("Available profiles in %s:" % self.ipython_dir)
182 self._print_profiles(profiles)
182 self._print_profiles(profiles)
183
183
184 profiles = list_profiles_in(py3compat.getcwd())
184 profiles = list_profiles_in(py3compat.getcwd())
185 if profiles:
185 if profiles:
186 print()
186 print()
187 print("Available profiles in current directory (%s):" % py3compat.getcwd())
187 print("Available profiles in current directory (%s):" % py3compat.getcwd())
188 self._print_profiles(profiles)
188 self._print_profiles(profiles)
189
189
190 print()
190 print()
191 print("To use any of the above profiles, start IPython with:")
191 print("To use any of the above profiles, start IPython with:")
192 print(" ipython --profile=<name>")
192 print(" ipython --profile=<name>")
193 print()
193 print()
194
194
195 def start(self):
195 def start(self):
196 self.list_profile_dirs()
196 self.list_profile_dirs()
197
197
198
198
199 create_flags = {}
199 create_flags = {}
200 create_flags.update(base_flags)
200 create_flags.update(base_flags)
201 # don't include '--init' flag, which implies running profile create in other apps
201 # don't include '--init' flag, which implies running profile create in other apps
202 create_flags.pop('init')
202 create_flags.pop('init')
203 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
203 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
204 "reset config files in this profile to the defaults.")
204 "reset config files in this profile to the defaults.")
205 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
205 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
206 "Include the config files for parallel "
206 "Include the config files for parallel "
207 "computing apps (ipengine, ipcontroller, etc.)")
207 "computing apps (ipengine, ipcontroller, etc.)")
208
208
209
209
210 class ProfileCreate(BaseIPythonApplication):
210 class ProfileCreate(BaseIPythonApplication):
211 name = u'ipython-profile'
211 name = u'ipython-profile'
212 description = create_help
212 description = create_help
213 examples = _create_examples
213 examples = _create_examples
214 auto_create = Bool(True, config=False)
214 auto_create = Bool(True, config=False)
215 def _log_format_default(self):
215 def _log_format_default(self):
216 return "[%(name)s] %(message)s"
216 return "[%(name)s] %(message)s"
217
217
218 def _copy_config_files_default(self):
218 def _copy_config_files_default(self):
219 return True
219 return True
220
220
221 parallel = Bool(False, config=True,
221 parallel = Bool(False, config=True,
222 help="whether to include parallel computing config files")
222 help="whether to include parallel computing config files")
223 def _parallel_changed(self, name, old, new):
223 def _parallel_changed(self, name, old, new):
224 parallel_files = [ 'ipcontroller_config.py',
224 parallel_files = [ 'ipcontroller_config.py',
225 'ipengine_config.py',
225 'ipengine_config.py',
226 'ipcluster_config.py'
226 'ipcluster_config.py'
227 ]
227 ]
228 if new:
228 if new:
229 for cf in parallel_files:
229 for cf in parallel_files:
230 self.config_files.append(cf)
230 self.config_files.append(cf)
231 else:
231 else:
232 for cf in parallel_files:
232 for cf in parallel_files:
233 if cf in self.config_files:
233 if cf in self.config_files:
234 self.config_files.remove(cf)
234 self.config_files.remove(cf)
235
235
236 def parse_command_line(self, argv):
236 def parse_command_line(self, argv):
237 super(ProfileCreate, self).parse_command_line(argv)
237 super(ProfileCreate, self).parse_command_line(argv)
238 # accept positional arg as profile name
238 # accept positional arg as profile name
239 if self.extra_args:
239 if self.extra_args:
240 self.profile = self.extra_args[0]
240 self.profile = self.extra_args[0]
241
241
242 flags = Dict(create_flags)
242 flags = Dict(create_flags)
243
243
244 classes = [ProfileDir]
244 classes = [ProfileDir]
245
245
246 def _import_app(self, app_path):
246 def _import_app(self, app_path):
247 """import an app class"""
247 """import an app class"""
248 app = None
248 app = None
249 name = app_path.rsplit('.', 1)[-1]
249 name = app_path.rsplit('.', 1)[-1]
250 try:
250 try:
251 app = import_item(app_path)
251 app = import_item(app_path)
252 except ImportError:
252 except ImportError:
253 self.log.info("Couldn't import %s, config file will be excluded", name)
253 self.log.info("Couldn't import %s, config file will be excluded", name)
254 except Exception:
254 except Exception:
255 self.log.warn('Unexpected error importing %s', name, exc_info=True)
255 self.log.warn('Unexpected error importing %s', name, exc_info=True)
256 return app
256 return app
257
257
258 def init_config_files(self):
258 def init_config_files(self):
259 super(ProfileCreate, self).init_config_files()
259 super(ProfileCreate, self).init_config_files()
260 # use local imports, since these classes may import from here
260 # use local imports, since these classes may import from here
261 from IPython.terminal.ipapp import TerminalIPythonApp
261 from IPython.terminal.ipapp import TerminalIPythonApp
262 apps = [TerminalIPythonApp]
262 apps = [TerminalIPythonApp]
263 for app_path in (
263 for app_path in (
264 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
264 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
265 'IPython.html.notebookapp.NotebookApp',
265 'IPython.html.notebookapp.NotebookApp',
266 'IPython.nbconvert.nbconvertapp.NbConvertApp',
266 'IPython.nbconvert.nbconvertapp.NbConvertApp',
267 ):
267 ):
268 app = self._import_app(app_path)
268 app = self._import_app(app_path)
269 if app is not None:
269 if app is not None:
270 apps.append(app)
270 apps.append(app)
271 if self.parallel:
271 if self.parallel:
272 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
272 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
273 from IPython.parallel.apps.ipengineapp import IPEngineApp
273 from IPython.parallel.apps.ipengineapp import IPEngineApp
274 from IPython.parallel.apps.ipclusterapp import IPClusterStart
274 from IPython.parallel.apps.ipclusterapp import IPClusterStart
275 from IPython.parallel.apps.iploggerapp import IPLoggerApp
275 from IPython.parallel.apps.iploggerapp import IPLoggerApp
276 apps.extend([
276 apps.extend([
277 IPControllerApp,
277 IPControllerApp,
278 IPEngineApp,
278 IPEngineApp,
279 IPClusterStart,
279 IPClusterStart,
280 IPLoggerApp,
280 IPLoggerApp,
281 ])
281 ])
282 for App in apps:
282 for App in apps:
283 app = App()
283 app = App()
284 app.config.update(self.config)
284 app.config.update(self.config)
285 app.log = self.log
285 app.log = self.log
286 app.overwrite = self.overwrite
286 app.overwrite = self.overwrite
287 app.copy_config_files=True
287 app.copy_config_files=True
288 app.ipython_dir=self.ipython_dir
288 app.ipython_dir=self.ipython_dir
289 app.profile_dir=self.profile_dir
289 app.profile_dir=self.profile_dir
290 app.init_config_files()
290 app.init_config_files()
291
291
292 def stage_default_config_file(self):
292 def stage_default_config_file(self):
293 pass
293 pass
294
294
295
295
296 class ProfileApp(Application):
296 class ProfileApp(Application):
297 name = u'ipython-profile'
297 name = u'ipython profile'
298 description = profile_help
298 description = profile_help
299 examples = _main_examples
299 examples = _main_examples
300
300
301 subcommands = Dict(dict(
301 subcommands = Dict(dict(
302 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
302 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
303 list = (ProfileList, ProfileList.description.splitlines()[0]),
303 list = (ProfileList, ProfileList.description.splitlines()[0]),
304 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
304 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
305 ))
305 ))
306
306
307 def start(self):
307 def start(self):
308 if self.subapp is None:
308 if self.subapp is None:
309 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
309 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
310 print()
310 print()
311 self.print_description()
311 self.print_description()
312 self.print_subcommands()
312 self.print_subcommands()
313 self.exit(1)
313 self.exit(1)
314 else:
314 else:
315 return self.subapp.start()
315 return self.subapp.start()
@@ -1,109 +1,109 b''
1
1
2 # Copyright (c) IPython Development Team.
2 # Copyright (c) IPython Development Team.
3 # Distributed under the terms of the Modified BSD License.
3 # Distributed under the terms of the Modified BSD License.
4
4
5 import errno
5 import errno
6 import os.path
6 import os.path
7
7
8 from IPython.config.application import Application
8 from IPython.config.application import Application
9 from IPython.core.application import BaseIPythonApplication, base_flags
9 from IPython.core.application import BaseIPythonApplication, base_flags
10 from IPython.utils.traitlets import Instance, Dict, Unicode, Bool
10 from IPython.utils.traitlets import Instance, Dict, Unicode, Bool
11
11
12 from .kernelspec import KernelSpecManager
12 from .kernelspec import KernelSpecManager
13
13
14 def _pythonfirst(s):
14 def _pythonfirst(s):
15 "Sort key function that will put strings starting with 'python' first."
15 "Sort key function that will put strings starting with 'python' first."
16 if s.startswith('python'):
16 if s.startswith('python'):
17 return ''
17 return ''
18 return s
18 return s
19
19
20 class ListKernelSpecs(BaseIPythonApplication):
20 class ListKernelSpecs(BaseIPythonApplication):
21 description = """List installed kernel specifications."""
21 description = """List installed kernel specifications."""
22 kernel_spec_manager = Instance(KernelSpecManager)
22 kernel_spec_manager = Instance(KernelSpecManager)
23
23
24 def _kernel_spec_manager_default(self):
24 def _kernel_spec_manager_default(self):
25 return KernelSpecManager(ipython_dir=self.ipython_dir)
25 return KernelSpecManager(ipython_dir=self.ipython_dir)
26
26
27 def start(self):
27 def start(self):
28 print("Available kernels:")
28 print("Available kernels:")
29 for kernelname in sorted(self.kernel_spec_manager.find_kernel_specs(),
29 for kernelname in sorted(self.kernel_spec_manager.find_kernel_specs(),
30 key=_pythonfirst):
30 key=_pythonfirst):
31 print(" %s" % kernelname)
31 print(" %s" % kernelname)
32
32
33
33
34 class InstallKernelSpec(BaseIPythonApplication):
34 class InstallKernelSpec(BaseIPythonApplication):
35 description = """Install a kernel specification directory."""
35 description = """Install a kernel specification directory."""
36 kernel_spec_manager = Instance(KernelSpecManager)
36 kernel_spec_manager = Instance(KernelSpecManager)
37
37
38 def _kernel_spec_manager_default(self):
38 def _kernel_spec_manager_default(self):
39 return KernelSpecManager(ipython_dir=self.ipython_dir)
39 return KernelSpecManager(ipython_dir=self.ipython_dir)
40
40
41 sourcedir = Unicode()
41 sourcedir = Unicode()
42 kernel_name = Unicode("", config=True,
42 kernel_name = Unicode("", config=True,
43 help="Install the kernel spec with this name"
43 help="Install the kernel spec with this name"
44 )
44 )
45 def _kernel_name_default(self):
45 def _kernel_name_default(self):
46 return os.path.basename(self.sourcedir)
46 return os.path.basename(self.sourcedir)
47
47
48 system = Bool(False, config=True,
48 system = Bool(False, config=True,
49 help="""
49 help="""
50 Try to install the kernel spec to the systemwide directory instead of
50 Try to install the kernel spec to the systemwide directory instead of
51 the per-user directory.
51 the per-user directory.
52 """
52 """
53 )
53 )
54 replace = Bool(False, config=True,
54 replace = Bool(False, config=True,
55 help="Replace any existing kernel spec with this name."
55 help="Replace any existing kernel spec with this name."
56 )
56 )
57
57
58 aliases = {'name': 'InstallKernelSpec.kernel_name'}
58 aliases = {'name': 'InstallKernelSpec.kernel_name'}
59
59
60 flags = {'system': ({'InstallKernelSpec': {'system': True}},
60 flags = {'system': ({'InstallKernelSpec': {'system': True}},
61 "Install to the systemwide kernel registry"),
61 "Install to the systemwide kernel registry"),
62 'replace': ({'InstallKernelSpec': {'replace': True}},
62 'replace': ({'InstallKernelSpec': {'replace': True}},
63 "Replace any existing kernel spec with this name."),
63 "Replace any existing kernel spec with this name."),
64 }
64 }
65 flags.update(base_flags)
65 flags.update(base_flags)
66
66
67 def parse_command_line(self, argv):
67 def parse_command_line(self, argv):
68 super(InstallKernelSpec, self).parse_command_line(argv)
68 super(InstallKernelSpec, self).parse_command_line(argv)
69 # accept positional arg as profile name
69 # accept positional arg as profile name
70 if self.extra_args:
70 if self.extra_args:
71 self.sourcedir = self.extra_args[0]
71 self.sourcedir = self.extra_args[0]
72 else:
72 else:
73 print("No source directory specified.")
73 print("No source directory specified.")
74 self.exit(1)
74 self.exit(1)
75
75
76 def start(self):
76 def start(self):
77 try:
77 try:
78 self.kernel_spec_manager.install_kernel_spec(self.sourcedir,
78 self.kernel_spec_manager.install_kernel_spec(self.sourcedir,
79 kernel_name=self.kernel_name,
79 kernel_name=self.kernel_name,
80 system=self.system,
80 system=self.system,
81 replace=self.replace,
81 replace=self.replace,
82 )
82 )
83 except OSError as e:
83 except OSError as e:
84 if e.errno == errno.EACCES:
84 if e.errno == errno.EACCES:
85 print("Permission denied")
85 print("Permission denied")
86 self.exit(1)
86 self.exit(1)
87 elif e.errno == errno.EEXIST:
87 elif e.errno == errno.EEXIST:
88 print("A kernel spec named %r is already present" % self.kernel_name)
88 print("A kernel spec named %r is already present" % self.kernel_name)
89 self.exit(1)
89 self.exit(1)
90 raise
90 raise
91
91
92 class KernelSpecApp(Application):
92 class KernelSpecApp(Application):
93 name = "ipython-kernelspec"
93 name = "ipython kernelspec"
94 description = """Manage IPython kernel specifications."""
94 description = """Manage IPython kernel specifications."""
95
95
96 subcommands = Dict(dict(
96 subcommands = Dict(dict(
97 list = (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
97 list = (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
98 install = (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0])
98 install = (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0])
99 ))
99 ))
100
100
101 def start(self):
101 def start(self):
102 if self.subapp is None:
102 if self.subapp is None:
103 print("No subcommand specified. Must specify one of: %s"% list(self.subcommands))
103 print("No subcommand specified. Must specify one of: %s"% list(self.subcommands))
104 print()
104 print()
105 self.print_description()
105 self.print_description()
106 self.print_subcommands()
106 self.print_subcommands()
107 self.exit(1)
107 self.exit(1)
108 else:
108 else:
109 return self.subapp.start()
109 return self.subapp.start()
General Comments 0
You need to be logged in to leave comments. Login now