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