##// END OF EJS Templates
Import argparse directly from stdlib
Thomas Kluyver -
Show More
@@ -1,718 +1,718 b''
1 1 """A simple configuration system.
2 2
3 3 Inheritance diagram:
4 4
5 5 .. inheritance-diagram:: IPython.config.loader
6 6 :parts: 3
7 7
8 8 Authors
9 9 -------
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min RK
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2011 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 import __builtin__ as builtin_mod
27 import argparse
27 28 import os
28 29 import re
29 30 import sys
30 31
31 from IPython.external import argparse
32 32 from IPython.utils.path import filefind, get_ipython_dir
33 33 from IPython.utils import py3compat, warn
34 34 from IPython.utils.encoding import DEFAULT_ENCODING
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Exceptions
38 38 #-----------------------------------------------------------------------------
39 39
40 40
41 41 class ConfigError(Exception):
42 42 pass
43 43
44 44 class ConfigLoaderError(ConfigError):
45 45 pass
46 46
47 47 class ConfigFileNotFound(ConfigError):
48 48 pass
49 49
50 50 class ArgumentError(ConfigLoaderError):
51 51 pass
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Argparse fix
55 55 #-----------------------------------------------------------------------------
56 56
57 57 # Unfortunately argparse by default prints help messages to stderr instead of
58 58 # stdout. This makes it annoying to capture long help screens at the command
59 59 # line, since one must know how to pipe stderr, which many users don't know how
60 60 # to do. So we override the print_help method with one that defaults to
61 61 # stdout and use our class instead.
62 62
63 63 class ArgumentParser(argparse.ArgumentParser):
64 64 """Simple argparse subclass that prints help to stdout by default."""
65 65
66 66 def print_help(self, file=None):
67 67 if file is None:
68 68 file = sys.stdout
69 69 return super(ArgumentParser, self).print_help(file)
70 70
71 71 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Config class for holding config information
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class Config(dict):
79 79 """An attribute based dict that can do smart merges."""
80 80
81 81 def __init__(self, *args, **kwds):
82 82 dict.__init__(self, *args, **kwds)
83 83 # This sets self.__dict__ = self, but it has to be done this way
84 84 # because we are also overriding __setattr__.
85 85 dict.__setattr__(self, '__dict__', self)
86 86 self._ensure_subconfig()
87 87
88 88 def _ensure_subconfig(self):
89 89 """ensure that sub-dicts that should be Config objects are
90 90
91 91 casts dicts that are under section keys to Config objects,
92 92 which is necessary for constructing Config objects from dict literals.
93 93 """
94 94 for key in self:
95 95 obj = self[key]
96 96 if self._is_section_key(key) \
97 97 and isinstance(obj, dict) \
98 98 and not isinstance(obj, Config):
99 99 dict.__setattr__(self, key, Config(obj))
100 100
101 101 def _merge(self, other):
102 102 """deprecated alias, use Config.merge()"""
103 103 self.merge(other)
104 104
105 105 def merge(self, other):
106 106 """merge another config object into this one"""
107 107 to_update = {}
108 108 for k, v in other.iteritems():
109 109 if k not in self:
110 110 to_update[k] = v
111 111 else: # I have this key
112 112 if isinstance(v, Config) and isinstance(self[k], Config):
113 113 # Recursively merge common sub Configs
114 114 self[k].merge(v)
115 115 else:
116 116 # Plain updates for non-Configs
117 117 to_update[k] = v
118 118
119 119 self.update(to_update)
120 120
121 121 def _is_section_key(self, key):
122 122 if key[0].upper()==key[0] and not key.startswith('_'):
123 123 return True
124 124 else:
125 125 return False
126 126
127 127 def __contains__(self, key):
128 128 if self._is_section_key(key):
129 129 return True
130 130 else:
131 131 return super(Config, self).__contains__(key)
132 132 # .has_key is deprecated for dictionaries.
133 133 has_key = __contains__
134 134
135 135 def _has_section(self, key):
136 136 if self._is_section_key(key):
137 137 if super(Config, self).__contains__(key):
138 138 return True
139 139 return False
140 140
141 141 def copy(self):
142 142 return type(self)(dict.copy(self))
143 143
144 144 def __copy__(self):
145 145 return self.copy()
146 146
147 147 def __deepcopy__(self, memo):
148 148 import copy
149 149 return type(self)(copy.deepcopy(self.items()))
150 150
151 151 def __getitem__(self, key):
152 152 # We cannot use directly self._is_section_key, because it triggers
153 153 # infinite recursion on top of PyPy. Instead, we manually fish the
154 154 # bound method.
155 155 is_section_key = self.__class__._is_section_key.__get__(self)
156 156
157 157 # Because we use this for an exec namespace, we need to delegate
158 158 # the lookup of names in __builtin__ to itself. This means
159 159 # that you can't have section or attribute names that are
160 160 # builtins.
161 161 try:
162 162 return getattr(builtin_mod, key)
163 163 except AttributeError:
164 164 pass
165 165 if is_section_key(key):
166 166 try:
167 167 return dict.__getitem__(self, key)
168 168 except KeyError:
169 169 c = Config()
170 170 dict.__setitem__(self, key, c)
171 171 return c
172 172 else:
173 173 return dict.__getitem__(self, key)
174 174
175 175 def __setitem__(self, key, value):
176 176 if self._is_section_key(key):
177 177 if not isinstance(value, Config):
178 178 raise ValueError('values whose keys begin with an uppercase '
179 179 'char must be Config instances: %r, %r' % (key, value))
180 180 else:
181 181 dict.__setitem__(self, key, value)
182 182
183 183 def __getattr__(self, key):
184 184 try:
185 185 return self.__getitem__(key)
186 186 except KeyError as e:
187 187 raise AttributeError(e)
188 188
189 189 def __setattr__(self, key, value):
190 190 try:
191 191 self.__setitem__(key, value)
192 192 except KeyError as e:
193 193 raise AttributeError(e)
194 194
195 195 def __delattr__(self, key):
196 196 try:
197 197 dict.__delitem__(self, key)
198 198 except KeyError as e:
199 199 raise AttributeError(e)
200 200
201 201
202 202 #-----------------------------------------------------------------------------
203 203 # Config loading classes
204 204 #-----------------------------------------------------------------------------
205 205
206 206
207 207 class ConfigLoader(object):
208 208 """A object for loading configurations from just about anywhere.
209 209
210 210 The resulting configuration is packaged as a :class:`Struct`.
211 211
212 212 Notes
213 213 -----
214 214 A :class:`ConfigLoader` does one thing: load a config from a source
215 215 (file, command line arguments) and returns the data as a :class:`Struct`.
216 216 There are lots of things that :class:`ConfigLoader` does not do. It does
217 217 not implement complex logic for finding config files. It does not handle
218 218 default values or merge multiple configs. These things need to be
219 219 handled elsewhere.
220 220 """
221 221
222 222 def __init__(self):
223 223 """A base class for config loaders.
224 224
225 225 Examples
226 226 --------
227 227
228 228 >>> cl = ConfigLoader()
229 229 >>> config = cl.load_config()
230 230 >>> config
231 231 {}
232 232 """
233 233 self.clear()
234 234
235 235 def clear(self):
236 236 self.config = Config()
237 237
238 238 def load_config(self):
239 239 """Load a config from somewhere, return a :class:`Config` instance.
240 240
241 241 Usually, this will cause self.config to be set and then returned.
242 242 However, in most cases, :meth:`ConfigLoader.clear` should be called
243 243 to erase any previous state.
244 244 """
245 245 self.clear()
246 246 return self.config
247 247
248 248
249 249 class FileConfigLoader(ConfigLoader):
250 250 """A base class for file based configurations.
251 251
252 252 As we add more file based config loaders, the common logic should go
253 253 here.
254 254 """
255 255 pass
256 256
257 257
258 258 class PyFileConfigLoader(FileConfigLoader):
259 259 """A config loader for pure python files.
260 260
261 261 This calls execfile on a plain python file and looks for attributes
262 262 that are all caps. These attribute are added to the config Struct.
263 263 """
264 264
265 265 def __init__(self, filename, path=None):
266 266 """Build a config loader for a filename and path.
267 267
268 268 Parameters
269 269 ----------
270 270 filename : str
271 271 The file name of the config file.
272 272 path : str, list, tuple
273 273 The path to search for the config file on, or a sequence of
274 274 paths to try in order.
275 275 """
276 276 super(PyFileConfigLoader, self).__init__()
277 277 self.filename = filename
278 278 self.path = path
279 279 self.full_filename = ''
280 280 self.data = None
281 281
282 282 def load_config(self):
283 283 """Load the config from a file and return it as a Struct."""
284 284 self.clear()
285 285 try:
286 286 self._find_file()
287 287 except IOError as e:
288 288 raise ConfigFileNotFound(str(e))
289 289 self._read_file_as_dict()
290 290 self._convert_to_config()
291 291 return self.config
292 292
293 293 def _find_file(self):
294 294 """Try to find the file by searching the paths."""
295 295 self.full_filename = filefind(self.filename, self.path)
296 296
297 297 def _read_file_as_dict(self):
298 298 """Load the config file into self.config, with recursive loading."""
299 299 # This closure is made available in the namespace that is used
300 300 # to exec the config file. It allows users to call
301 301 # load_subconfig('myconfig.py') to load config files recursively.
302 302 # It needs to be a closure because it has references to self.path
303 303 # and self.config. The sub-config is loaded with the same path
304 304 # as the parent, but it uses an empty config which is then merged
305 305 # with the parents.
306 306
307 307 # If a profile is specified, the config file will be loaded
308 308 # from that profile
309 309
310 310 def load_subconfig(fname, profile=None):
311 311 # import here to prevent circular imports
312 312 from IPython.core.profiledir import ProfileDir, ProfileDirError
313 313 if profile is not None:
314 314 try:
315 315 profile_dir = ProfileDir.find_profile_dir_by_name(
316 316 get_ipython_dir(),
317 317 profile,
318 318 )
319 319 except ProfileDirError:
320 320 return
321 321 path = profile_dir.location
322 322 else:
323 323 path = self.path
324 324 loader = PyFileConfigLoader(fname, path)
325 325 try:
326 326 sub_config = loader.load_config()
327 327 except ConfigFileNotFound:
328 328 # Pass silently if the sub config is not there. This happens
329 329 # when a user s using a profile, but not the default config.
330 330 pass
331 331 else:
332 332 self.config.merge(sub_config)
333 333
334 334 # Again, this needs to be a closure and should be used in config
335 335 # files to get the config being loaded.
336 336 def get_config():
337 337 return self.config
338 338
339 339 namespace = dict(
340 340 load_subconfig=load_subconfig,
341 341 get_config=get_config,
342 342 __file__=self.full_filename,
343 343 )
344 344 fs_encoding = sys.getfilesystemencoding() or 'ascii'
345 345 conf_filename = self.full_filename.encode(fs_encoding)
346 346 py3compat.execfile(conf_filename, namespace)
347 347
348 348 def _convert_to_config(self):
349 349 if self.data is None:
350 350 ConfigLoaderError('self.data does not exist')
351 351
352 352
353 353 class CommandLineConfigLoader(ConfigLoader):
354 354 """A config loader for command line arguments.
355 355
356 356 As we add more command line based loaders, the common logic should go
357 357 here.
358 358 """
359 359
360 360 def _exec_config_str(self, lhs, rhs):
361 361 """execute self.config.<lhs> = <rhs>
362 362
363 363 * expands ~ with expanduser
364 364 * tries to assign with raw eval, otherwise assigns with just the string,
365 365 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
366 366 equivalent are `--C.a=4` and `--C.a='4'`.
367 367 """
368 368 rhs = os.path.expanduser(rhs)
369 369 try:
370 370 # Try to see if regular Python syntax will work. This
371 371 # won't handle strings as the quote marks are removed
372 372 # by the system shell.
373 373 value = eval(rhs)
374 374 except (NameError, SyntaxError):
375 375 # This case happens if the rhs is a string.
376 376 value = rhs
377 377
378 378 exec u'self.config.%s = value' % lhs
379 379
380 380 def _load_flag(self, cfg):
381 381 """update self.config from a flag, which can be a dict or Config"""
382 382 if isinstance(cfg, (dict, Config)):
383 383 # don't clobber whole config sections, update
384 384 # each section from config:
385 385 for sec,c in cfg.iteritems():
386 386 self.config[sec].update(c)
387 387 else:
388 388 raise TypeError("Invalid flag: %r" % cfg)
389 389
390 390 # raw --identifier=value pattern
391 391 # but *also* accept '-' as wordsep, for aliases
392 392 # accepts: --foo=a
393 393 # --Class.trait=value
394 394 # --alias-name=value
395 395 # rejects: -foo=value
396 396 # --foo
397 397 # --Class.trait
398 398 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
399 399
400 400 # just flags, no assignments, with two *or one* leading '-'
401 401 # accepts: --foo
402 402 # -foo-bar-again
403 403 # rejects: --anything=anything
404 404 # --two.word
405 405
406 406 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
407 407
408 408 class KeyValueConfigLoader(CommandLineConfigLoader):
409 409 """A config loader that loads key value pairs from the command line.
410 410
411 411 This allows command line options to be gives in the following form::
412 412
413 413 ipython --profile="foo" --InteractiveShell.autocall=False
414 414 """
415 415
416 416 def __init__(self, argv=None, aliases=None, flags=None):
417 417 """Create a key value pair config loader.
418 418
419 419 Parameters
420 420 ----------
421 421 argv : list
422 422 A list that has the form of sys.argv[1:] which has unicode
423 423 elements of the form u"key=value". If this is None (default),
424 424 then sys.argv[1:] will be used.
425 425 aliases : dict
426 426 A dict of aliases for configurable traits.
427 427 Keys are the short aliases, Values are the resolved trait.
428 428 Of the form: `{'alias' : 'Configurable.trait'}`
429 429 flags : dict
430 430 A dict of flags, keyed by str name. Vaues can be Config objects,
431 431 dicts, or "key=value" strings. If Config or dict, when the flag
432 432 is triggered, The flag is loaded as `self.config.update(m)`.
433 433
434 434 Returns
435 435 -------
436 436 config : Config
437 437 The resulting Config object.
438 438
439 439 Examples
440 440 --------
441 441
442 442 >>> from IPython.config.loader import KeyValueConfigLoader
443 443 >>> cl = KeyValueConfigLoader()
444 444 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
445 445 >>> sorted(d.items())
446 446 [('A', {'name': 'brian'}), ('B', {'number': 0})]
447 447 """
448 448 self.clear()
449 449 if argv is None:
450 450 argv = sys.argv[1:]
451 451 self.argv = argv
452 452 self.aliases = aliases or {}
453 453 self.flags = flags or {}
454 454
455 455
456 456 def clear(self):
457 457 super(KeyValueConfigLoader, self).clear()
458 458 self.extra_args = []
459 459
460 460
461 461 def _decode_argv(self, argv, enc=None):
462 462 """decode argv if bytes, using stin.encoding, falling back on default enc"""
463 463 uargv = []
464 464 if enc is None:
465 465 enc = DEFAULT_ENCODING
466 466 for arg in argv:
467 467 if not isinstance(arg, unicode):
468 468 # only decode if not already decoded
469 469 arg = arg.decode(enc)
470 470 uargv.append(arg)
471 471 return uargv
472 472
473 473
474 474 def load_config(self, argv=None, aliases=None, flags=None):
475 475 """Parse the configuration and generate the Config object.
476 476
477 477 After loading, any arguments that are not key-value or
478 478 flags will be stored in self.extra_args - a list of
479 479 unparsed command-line arguments. This is used for
480 480 arguments such as input files or subcommands.
481 481
482 482 Parameters
483 483 ----------
484 484 argv : list, optional
485 485 A list that has the form of sys.argv[1:] which has unicode
486 486 elements of the form u"key=value". If this is None (default),
487 487 then self.argv will be used.
488 488 aliases : dict
489 489 A dict of aliases for configurable traits.
490 490 Keys are the short aliases, Values are the resolved trait.
491 491 Of the form: `{'alias' : 'Configurable.trait'}`
492 492 flags : dict
493 493 A dict of flags, keyed by str name. Values can be Config objects
494 494 or dicts. When the flag is triggered, The config is loaded as
495 495 `self.config.update(cfg)`.
496 496 """
497 497 self.clear()
498 498 if argv is None:
499 499 argv = self.argv
500 500 if aliases is None:
501 501 aliases = self.aliases
502 502 if flags is None:
503 503 flags = self.flags
504 504
505 505 # ensure argv is a list of unicode strings:
506 506 uargv = self._decode_argv(argv)
507 507 for idx,raw in enumerate(uargv):
508 508 # strip leading '-'
509 509 item = raw.lstrip('-')
510 510
511 511 if raw == '--':
512 512 # don't parse arguments after '--'
513 513 # this is useful for relaying arguments to scripts, e.g.
514 514 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
515 515 self.extra_args.extend(uargv[idx+1:])
516 516 break
517 517
518 518 if kv_pattern.match(raw):
519 519 lhs,rhs = item.split('=',1)
520 520 # Substitute longnames for aliases.
521 521 if lhs in aliases:
522 522 lhs = aliases[lhs]
523 523 if '.' not in lhs:
524 524 # probably a mistyped alias, but not technically illegal
525 525 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
526 526 try:
527 527 self._exec_config_str(lhs, rhs)
528 528 except Exception:
529 529 raise ArgumentError("Invalid argument: '%s'" % raw)
530 530
531 531 elif flag_pattern.match(raw):
532 532 if item in flags:
533 533 cfg,help = flags[item]
534 534 self._load_flag(cfg)
535 535 else:
536 536 raise ArgumentError("Unrecognized flag: '%s'"%raw)
537 537 elif raw.startswith('-'):
538 538 kv = '--'+item
539 539 if kv_pattern.match(kv):
540 540 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
541 541 else:
542 542 raise ArgumentError("Invalid argument: '%s'"%raw)
543 543 else:
544 544 # keep all args that aren't valid in a list,
545 545 # in case our parent knows what to do with them.
546 546 self.extra_args.append(item)
547 547 return self.config
548 548
549 549 class ArgParseConfigLoader(CommandLineConfigLoader):
550 550 """A loader that uses the argparse module to load from the command line."""
551 551
552 552 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
553 553 """Create a config loader for use with argparse.
554 554
555 555 Parameters
556 556 ----------
557 557
558 558 argv : optional, list
559 559 If given, used to read command-line arguments from, otherwise
560 560 sys.argv[1:] is used.
561 561
562 562 parser_args : tuple
563 563 A tuple of positional arguments that will be passed to the
564 564 constructor of :class:`argparse.ArgumentParser`.
565 565
566 566 parser_kw : dict
567 567 A tuple of keyword arguments that will be passed to the
568 568 constructor of :class:`argparse.ArgumentParser`.
569 569
570 570 Returns
571 571 -------
572 572 config : Config
573 573 The resulting Config object.
574 574 """
575 575 super(CommandLineConfigLoader, self).__init__()
576 576 self.clear()
577 577 if argv is None:
578 578 argv = sys.argv[1:]
579 579 self.argv = argv
580 580 self.aliases = aliases or {}
581 581 self.flags = flags or {}
582 582
583 583 self.parser_args = parser_args
584 584 self.version = parser_kw.pop("version", None)
585 585 kwargs = dict(argument_default=argparse.SUPPRESS)
586 586 kwargs.update(parser_kw)
587 587 self.parser_kw = kwargs
588 588
589 589 def load_config(self, argv=None, aliases=None, flags=None):
590 590 """Parse command line arguments and return as a Config object.
591 591
592 592 Parameters
593 593 ----------
594 594
595 595 args : optional, list
596 596 If given, a list with the structure of sys.argv[1:] to parse
597 597 arguments from. If not given, the instance's self.argv attribute
598 598 (given at construction time) is used."""
599 599 self.clear()
600 600 if argv is None:
601 601 argv = self.argv
602 602 if aliases is None:
603 603 aliases = self.aliases
604 604 if flags is None:
605 605 flags = self.flags
606 606 self._create_parser(aliases, flags)
607 607 self._parse_args(argv)
608 608 self._convert_to_config()
609 609 return self.config
610 610
611 611 def get_extra_args(self):
612 612 if hasattr(self, 'extra_args'):
613 613 return self.extra_args
614 614 else:
615 615 return []
616 616
617 617 def _create_parser(self, aliases=None, flags=None):
618 618 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
619 619 self._add_arguments(aliases, flags)
620 620
621 621 def _add_arguments(self, aliases=None, flags=None):
622 622 raise NotImplementedError("subclasses must implement _add_arguments")
623 623
624 624 def _parse_args(self, args):
625 625 """self.parser->self.parsed_data"""
626 626 # decode sys.argv to support unicode command-line options
627 627 enc = DEFAULT_ENCODING
628 628 uargs = [py3compat.cast_unicode(a, enc) for a in args]
629 629 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
630 630
631 631 def _convert_to_config(self):
632 632 """self.parsed_data->self.config"""
633 633 for k, v in vars(self.parsed_data).iteritems():
634 634 exec "self.config.%s = v"%k in locals(), globals()
635 635
636 636 class KVArgParseConfigLoader(ArgParseConfigLoader):
637 637 """A config loader that loads aliases and flags with argparse,
638 638 but will use KVLoader for the rest. This allows better parsing
639 639 of common args, such as `ipython -c 'print 5'`, but still gets
640 640 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
641 641
642 642 def _add_arguments(self, aliases=None, flags=None):
643 643 self.alias_flags = {}
644 644 # print aliases, flags
645 645 if aliases is None:
646 646 aliases = self.aliases
647 647 if flags is None:
648 648 flags = self.flags
649 649 paa = self.parser.add_argument
650 650 for key,value in aliases.iteritems():
651 651 if key in flags:
652 652 # flags
653 653 nargs = '?'
654 654 else:
655 655 nargs = None
656 656 if len(key) is 1:
657 657 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
658 658 else:
659 659 paa('--'+key, type=unicode, dest=value, nargs=nargs)
660 660 for key, (value, help) in flags.iteritems():
661 661 if key in self.aliases:
662 662 #
663 663 self.alias_flags[self.aliases[key]] = value
664 664 continue
665 665 if len(key) is 1:
666 666 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
667 667 else:
668 668 paa('--'+key, action='append_const', dest='_flags', const=value)
669 669
670 670 def _convert_to_config(self):
671 671 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
672 672 # remove subconfigs list from namespace before transforming the Namespace
673 673 if '_flags' in self.parsed_data:
674 674 subcs = self.parsed_data._flags
675 675 del self.parsed_data._flags
676 676 else:
677 677 subcs = []
678 678
679 679 for k, v in vars(self.parsed_data).iteritems():
680 680 if v is None:
681 681 # it was a flag that shares the name of an alias
682 682 subcs.append(self.alias_flags[k])
683 683 else:
684 684 # eval the KV assignment
685 685 self._exec_config_str(k, v)
686 686
687 687 for subc in subcs:
688 688 self._load_flag(subc)
689 689
690 690 if self.extra_args:
691 691 sub_parser = KeyValueConfigLoader()
692 692 sub_parser.load_config(self.extra_args)
693 693 self.config.merge(sub_parser.config)
694 694 self.extra_args = sub_parser.extra_args
695 695
696 696
697 697 def load_pyconfig_files(config_files, path):
698 698 """Load multiple Python config files, merging each of them in turn.
699 699
700 700 Parameters
701 701 ==========
702 702 config_files : list of str
703 703 List of config files names to load and merge into the config.
704 704 path : unicode
705 705 The full path to the location of the config files.
706 706 """
707 707 config = Config()
708 708 for cf in config_files:
709 709 loader = PyFileConfigLoader(cf, path=path)
710 710 try:
711 711 next_config = loader.load_config()
712 712 except ConfigFileNotFound:
713 713 pass
714 714 except:
715 715 raise
716 716 else:
717 717 config.merge(next_config)
718 718 return config
@@ -1,246 +1,246 b''
1 1 ''' A decorator-based method of constructing IPython magics with `argparse`
2 2 option handling.
3 3
4 4 New magic functions can be defined like so::
5 5
6 6 from IPython.core.magic_arguments import (argument, magic_arguments,
7 7 parse_argstring)
8 8
9 9 @magic_arguments()
10 10 @argument('-o', '--option', help='An optional argument.')
11 11 @argument('arg', type=int, help='An integer positional argument.')
12 12 def magic_cool(self, arg):
13 13 """ A really cool magic command.
14 14
15 15 """
16 16 args = parse_argstring(magic_cool, arg)
17 17 ...
18 18
19 19 The `@magic_arguments` decorator marks the function as having argparse arguments.
20 20 The `@argument` decorator adds an argument using the same syntax as argparse's
21 21 `add_argument()` method. More sophisticated uses may also require the
22 22 `@argument_group` or `@kwds` decorator to customize the formatting and the
23 23 parsing.
24 24
25 25 Help text for the magic is automatically generated from the docstring and the
26 26 arguments::
27 27
28 28 In[1]: %cool?
29 29 %cool [-o OPTION] arg
30 30
31 31 A really cool magic command.
32 32
33 33 positional arguments:
34 34 arg An integer positional argument.
35 35
36 36 optional arguments:
37 37 -o OPTION, --option OPTION
38 38 An optional argument.
39 39
40 40 Inheritance diagram:
41 41
42 42 .. inheritance-diagram:: IPython.core.magic_arguments
43 43 :parts: 3
44 44
45 45 '''
46 46 #-----------------------------------------------------------------------------
47 47 # Copyright (C) 2010-2011, IPython Development Team.
48 48 #
49 49 # Distributed under the terms of the Modified BSD License.
50 50 #
51 51 # The full license is in the file COPYING.txt, distributed with this software.
52 52 #-----------------------------------------------------------------------------
53 import argparse
53 54
54 55 # Our own imports
55 from IPython.external import argparse
56 56 from IPython.core.error import UsageError
57 57 from IPython.utils.process import arg_split
58 58 from IPython.utils.text import dedent
59 59
60 60 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
61 61 """ A HelpFormatter which dedents but otherwise preserves indentation.
62 62 """
63 63 def _fill_text(self, text, width, indent):
64 64 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
65 65
66 66 class MagicArgumentParser(argparse.ArgumentParser):
67 67 """ An ArgumentParser tweaked for use by IPython magics.
68 68 """
69 69 def __init__(self,
70 70 prog=None,
71 71 usage=None,
72 72 description=None,
73 73 epilog=None,
74 74 parents=None,
75 75 formatter_class=MagicHelpFormatter,
76 76 prefix_chars='-',
77 77 argument_default=None,
78 78 conflict_handler='error',
79 79 add_help=False):
80 80 if parents is None:
81 81 parents = []
82 82 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
83 83 description=description, epilog=epilog,
84 84 parents=parents, formatter_class=formatter_class,
85 85 prefix_chars=prefix_chars, argument_default=argument_default,
86 86 conflict_handler=conflict_handler, add_help=add_help)
87 87
88 88 def error(self, message):
89 89 """ Raise a catchable error instead of exiting.
90 90 """
91 91 raise UsageError(message)
92 92
93 93 def parse_argstring(self, argstring):
94 94 """ Split a string into an argument list and parse that argument list.
95 95 """
96 96 argv = arg_split(argstring)
97 97 return self.parse_args(argv)
98 98
99 99
100 100 def construct_parser(magic_func):
101 101 """ Construct an argument parser using the function decorations.
102 102 """
103 103 kwds = getattr(magic_func, 'argcmd_kwds', {})
104 104 if 'description' not in kwds:
105 105 kwds['description'] = getattr(magic_func, '__doc__', None)
106 106 arg_name = real_name(magic_func)
107 107 parser = MagicArgumentParser(arg_name, **kwds)
108 108 # Reverse the list of decorators in order to apply them in the
109 109 # order in which they appear in the source.
110 110 group = None
111 111 for deco in magic_func.decorators[::-1]:
112 112 result = deco.add_to_parser(parser, group)
113 113 if result is not None:
114 114 group = result
115 115
116 116 # Replace the starting 'usage: ' with IPython's %.
117 117 help_text = parser.format_help()
118 118 if help_text.startswith('usage: '):
119 119 help_text = help_text.replace('usage: ', '%', 1)
120 120 else:
121 121 help_text = '%' + help_text
122 122
123 123 # Replace the magic function's docstring with the full help text.
124 124 magic_func.__doc__ = help_text
125 125
126 126 return parser
127 127
128 128
129 129 def parse_argstring(magic_func, argstring):
130 130 """ Parse the string of arguments for the given magic function.
131 131 """
132 132 return magic_func.parser.parse_argstring(argstring)
133 133
134 134
135 135 def real_name(magic_func):
136 136 """ Find the real name of the magic.
137 137 """
138 138 magic_name = magic_func.__name__
139 139 if magic_name.startswith('magic_'):
140 140 magic_name = magic_name[len('magic_'):]
141 141 return getattr(magic_func, 'argcmd_name', magic_name)
142 142
143 143
144 144 class ArgDecorator(object):
145 145 """ Base class for decorators to add ArgumentParser information to a method.
146 146 """
147 147
148 148 def __call__(self, func):
149 149 if not getattr(func, 'has_arguments', False):
150 150 func.has_arguments = True
151 151 func.decorators = []
152 152 func.decorators.append(self)
153 153 return func
154 154
155 155 def add_to_parser(self, parser, group):
156 156 """ Add this object's information to the parser, if necessary.
157 157 """
158 158 pass
159 159
160 160
161 161 class magic_arguments(ArgDecorator):
162 162 """ Mark the magic as having argparse arguments and possibly adjust the
163 163 name.
164 164 """
165 165
166 166 def __init__(self, name=None):
167 167 self.name = name
168 168
169 169 def __call__(self, func):
170 170 if not getattr(func, 'has_arguments', False):
171 171 func.has_arguments = True
172 172 func.decorators = []
173 173 if self.name is not None:
174 174 func.argcmd_name = self.name
175 175 # This should be the first decorator in the list of decorators, thus the
176 176 # last to execute. Build the parser.
177 177 func.parser = construct_parser(func)
178 178 return func
179 179
180 180
181 181 class ArgMethodWrapper(ArgDecorator):
182 182
183 183 """
184 184 Base class to define a wrapper for ArgumentParser method.
185 185
186 186 Child class must define either `_method_name` or `add_to_parser`.
187 187
188 188 """
189 189
190 190 _method_name = None
191 191
192 192 def __init__(self, *args, **kwds):
193 193 self.args = args
194 194 self.kwds = kwds
195 195
196 196 def add_to_parser(self, parser, group):
197 197 """ Add this object's information to the parser.
198 198 """
199 199 if group is not None:
200 200 parser = group
201 201 getattr(parser, self._method_name)(*self.args, **self.kwds)
202 202 return None
203 203
204 204
205 205 class argument(ArgMethodWrapper):
206 206 """ Store arguments and keywords to pass to add_argument().
207 207
208 208 Instances also serve to decorate command methods.
209 209 """
210 210 _method_name = 'add_argument'
211 211
212 212
213 213 class defaults(ArgMethodWrapper):
214 214 """ Store arguments and keywords to pass to set_defaults().
215 215
216 216 Instances also serve to decorate command methods.
217 217 """
218 218 _method_name = 'set_defaults'
219 219
220 220
221 221 class argument_group(ArgMethodWrapper):
222 222 """ Store arguments and keywords to pass to add_argument_group().
223 223
224 224 Instances also serve to decorate command methods.
225 225 """
226 226
227 227 def add_to_parser(self, parser, group):
228 228 """ Add this object's information to the parser.
229 229 """
230 230 return parser.add_argument_group(*self.args, **self.kwds)
231 231
232 232
233 233 class kwds(ArgDecorator):
234 234 """ Provide other keywords to the sub-parser constructor.
235 235 """
236 236 def __init__(self, **kwds):
237 237 self.kwds = kwds
238 238
239 239 def __call__(self, func):
240 240 func = super(kwds, self).__call__(func)
241 241 func.argcmd_kwds = self.kwds
242 242 return func
243 243
244 244
245 245 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
246 246 'parse_argstring']
@@ -1,118 +1,118 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 2010-2011, IPython Development Team.
3 3 #
4 4 # Distributed under the terms of the Modified BSD License.
5 5 #
6 6 # The full license is in the file COPYING.txt, distributed with this software.
7 7 #-----------------------------------------------------------------------------
8 8
9 import argparse
9 10 from nose.tools import assert_equal
10 11
11 from IPython.external import argparse
12 12 from IPython.core.magic_arguments import (argument, argument_group, kwds,
13 13 magic_arguments, parse_argstring, real_name)
14 14
15 15
16 16 @magic_arguments()
17 17 @argument('-f', '--foo', help="an argument")
18 18 def magic_foo1(self, args):
19 19 """ A docstring.
20 20 """
21 21 return parse_argstring(magic_foo1, args)
22 22
23 23
24 24 @magic_arguments()
25 25 def magic_foo2(self, args):
26 26 """ A docstring.
27 27 """
28 28 return parse_argstring(magic_foo2, args)
29 29
30 30
31 31 @magic_arguments()
32 32 @argument('-f', '--foo', help="an argument")
33 33 @argument_group('Group')
34 34 @argument('-b', '--bar', help="a grouped argument")
35 35 @argument_group('Second Group')
36 36 @argument('-z', '--baz', help="another grouped argument")
37 37 def magic_foo3(self, args):
38 38 """ A docstring.
39 39 """
40 40 return parse_argstring(magic_foo3, args)
41 41
42 42
43 43 @magic_arguments()
44 44 @kwds(argument_default=argparse.SUPPRESS)
45 45 @argument('-f', '--foo', help="an argument")
46 46 def magic_foo4(self, args):
47 47 """ A docstring.
48 48 """
49 49 return parse_argstring(magic_foo4, args)
50 50
51 51
52 52 @magic_arguments('frobnicate')
53 53 @argument('-f', '--foo', help="an argument")
54 54 def magic_foo5(self, args):
55 55 """ A docstring.
56 56 """
57 57 return parse_argstring(magic_foo5, args)
58 58
59 59
60 60 @magic_arguments()
61 61 @argument('-f', '--foo', help="an argument")
62 62 def magic_magic_foo(self, args):
63 63 """ A docstring.
64 64 """
65 65 return parse_argstring(magic_magic_foo, args)
66 66
67 67
68 68 @magic_arguments()
69 69 @argument('-f', '--foo', help="an argument")
70 70 def foo(self, args):
71 71 """ A docstring.
72 72 """
73 73 return parse_argstring(foo, args)
74 74
75 75
76 76 def test_magic_arguments():
77 77 assert_equal(magic_foo1.__doc__, '%foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
78 78 assert_equal(getattr(magic_foo1, 'argcmd_name', None), None)
79 79 assert_equal(real_name(magic_foo1), 'foo1')
80 80 assert_equal(magic_foo1(None, ''), argparse.Namespace(foo=None))
81 81 assert hasattr(magic_foo1, 'has_arguments')
82 82
83 83 assert_equal(magic_foo2.__doc__, '%foo2\n\n A docstring.\n')
84 84 assert_equal(getattr(magic_foo2, 'argcmd_name', None), None)
85 85 assert_equal(real_name(magic_foo2), 'foo2')
86 86 assert_equal(magic_foo2(None, ''), argparse.Namespace())
87 87 assert hasattr(magic_foo2, 'has_arguments')
88 88
89 89 assert_equal(magic_foo3.__doc__, '%foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n')
90 90 assert_equal(getattr(magic_foo3, 'argcmd_name', None), None)
91 91 assert_equal(real_name(magic_foo3), 'foo3')
92 92 assert_equal(magic_foo3(None, ''),
93 93 argparse.Namespace(bar=None, baz=None, foo=None))
94 94 assert hasattr(magic_foo3, 'has_arguments')
95 95
96 96 assert_equal(magic_foo4.__doc__, '%foo4 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
97 97 assert_equal(getattr(magic_foo4, 'argcmd_name', None), None)
98 98 assert_equal(real_name(magic_foo4), 'foo4')
99 99 assert_equal(magic_foo4(None, ''), argparse.Namespace())
100 100 assert hasattr(magic_foo4, 'has_arguments')
101 101
102 102 assert_equal(magic_foo5.__doc__, '%frobnicate [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
103 103 assert_equal(getattr(magic_foo5, 'argcmd_name', None), 'frobnicate')
104 104 assert_equal(real_name(magic_foo5), 'frobnicate')
105 105 assert_equal(magic_foo5(None, ''), argparse.Namespace(foo=None))
106 106 assert hasattr(magic_foo5, 'has_arguments')
107 107
108 108 assert_equal(magic_magic_foo.__doc__, '%magic_foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
109 109 assert_equal(getattr(magic_magic_foo, 'argcmd_name', None), None)
110 110 assert_equal(real_name(magic_magic_foo), 'magic_foo')
111 111 assert_equal(magic_magic_foo(None, ''), argparse.Namespace(foo=None))
112 112 assert hasattr(magic_magic_foo, 'has_arguments')
113 113
114 114 assert_equal(foo.__doc__, '%foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
115 115 assert_equal(getattr(foo, 'argcmd_name', None), None)
116 116 assert_equal(real_name(foo), 'foo')
117 117 assert_equal(foo(None, ''), argparse.Namespace(foo=None))
118 118 assert hasattr(foo, 'has_arguments')
@@ -1,331 +1,331 b''
1 1 #!/usr/bin/python
2 2 """Utility function for installing MathJax javascript library into
3 3 the notebook's 'static' directory, for offline use.
4 4
5 5 Authors:
6 6
7 7 * Min RK
8 8 * Mark Sienkiewicz
9 9 * Matthias Bussonnier
10 10
11 11 To download and install MathJax:
12 12
13 13 From Python:
14 14
15 15 >>> from IPython.external.mathjax import install_mathjax
16 16 >>> install_mathjax()
17 17
18 18 From the command line:
19 19
20 20 $ python -m IPython.external.mathjax
21 21
22 22 To a specific profile:
23 23
24 24 $ python -m IPython.external.mathjax --profile=research
25 25
26 26 To install MathJax from a file you have already downloaded:
27 27
28 28 $ python -m IPython.external.mathjax mathjax-xxx.tar.gz
29 29 $ python -m IPython.external.mathjax mathjax-xxx.zip
30 30
31 31 It will not install MathJax if it is already there. Use -r to
32 32 replace the existing copy of MathJax.
33 33
34 34 To find the directory where IPython would like MathJax installed:
35 35
36 36 $ python -m IPython.external.mathjax -d
37 37
38 38 """
39 39
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Copyright (C) 2008-2011 The IPython Development Team
43 43 #
44 44 # Distributed under the terms of the BSD License. The full license is in
45 45 # the file COPYING, distributed as part of this software.
46 46 #-----------------------------------------------------------------------------
47 47
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Imports
51 51 #-----------------------------------------------------------------------------
52 52
53 import argparse
53 54 import os
54 55 import shutil
55 56 import sys
56 57 import tarfile
57 58 import urllib2
58 59 import zipfile
59 60
60 61
61 62 from IPython.utils.path import locate_profile
62 from IPython.external import argparse
63 63 #-----------------------------------------------------------------------------
64 64 #
65 65 #-----------------------------------------------------------------------------
66 66
67 67 # Where mathjax will be installed.
68 68
69 69 static = os.path.join(locate_profile('default'), 'static')
70 70 default_dest = os.path.join(static, 'mathjax')
71 71
72 72 ##
73 73
74 74 # Test for access to install mathjax.
75 75
76 76 def check_perms(dest, replace=False):
77 77 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
78 78 components = dest.split(os.path.sep)
79 79 subpaths = [ os.path.sep+os.path.sep.join(components[1:i]) for i in range(1,len(components))]
80 80
81 81 existing_path = filter(os.path.exists, subpaths)
82 82 last_writable = existing_path[-1]
83 83 if not os.access(last_writable, os.W_OK):
84 84 raise IOError("Need have write access to %s" % parent)
85 85 not_existing = [ path for path in subpaths if path not in existing_path]
86 86 # subfolder we will create, will obviously be writable
87 87 # should we still considere checking separately that
88 88 # ipython profiles have been created ?
89 89 for folder in not_existing:
90 90 os.mkdir(folder)
91 91
92 92 if os.path.exists(dest):
93 93 if replace:
94 94 if not os.access(dest, os.W_OK):
95 95 raise IOError("Need have write access to %s" % dest)
96 96 print "removing previous MathJax install"
97 97 shutil.rmtree(dest)
98 98 return True
99 99 else:
100 100 print "offline MathJax apparently already installed"
101 101 return False
102 102 else :
103 103 return True
104 104
105 105 ##
106 106
107 107 def extract_tar( fd, dest ) :
108 108 # use 'r|gz' stream mode, because socket file-like objects can't seek:
109 109 tar = tarfile.open(fileobj=fd, mode='r|gz')
110 110
111 111 # we just happen to know that the first entry in the mathjax
112 112 # archive is the directory that the remaining members are in.
113 113 topdir = tar.firstmember.path
114 114
115 115 # extract the archive (contains a single directory) to the static/ directory
116 116 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
117 117 tar.extractall(parent)
118 118
119 119 # it will be mathjax-MathJax-<sha>, rename to just mathjax
120 120 os.rename(os.path.join(parent, topdir), dest)
121 121
122 122 ##
123 123
124 124 def extract_zip( fd, dest ) :
125 125 z = zipfile.ZipFile( fd, 'r' )
126 126
127 127 # we just happen to know that the first entry in the mathjax
128 128 # archive is the directory that the remaining members are in.
129 129 topdir = z.namelist()[0]
130 130
131 131 # extract the archive (contains a single directory) to the static/ directory
132 132 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
133 133 z.extractall( parent )
134 134
135 135 # it will be mathjax-MathJax-<sha>, rename to just mathjax
136 136 d = os.path.join(parent, topdir)
137 137 print d
138 138 os.rename(os.path.join(parent, topdir), dest)
139 139
140 140 ##
141 141
142 142 def install_mathjax(tag='v2.0', dest=default_dest, replace=False, file=None, extractor=extract_tar ):
143 143 """Download and/or install MathJax for offline use.
144 144
145 145 This will install mathjax to the 'static' dir in the IPython notebook
146 146 package, so it will fail if the caller does not have write access
147 147 to that location.
148 148
149 149 MathJax is a ~15MB download, and ~150MB installed.
150 150
151 151 Parameters
152 152 ----------
153 153
154 154 replace : bool [False]
155 155 Whether to remove and replace an existing install.
156 156 dest : str [path to default profile]
157 157 Where to locally install mathjax
158 158 tag : str ['v2.0']
159 159 Which tag to download. Default is 'v2.0', the current stable release,
160 160 but alternatives include 'v1.1a' and 'master'.
161 161 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
162 162 File handle from which to untar/unzip/... mathjax
163 163 extractor : function
164 164 Method tu use to untar/unzip/... `file`
165 165 """
166 166 if not check_perms(dest, replace) :
167 167 return
168 168
169 169 if file is None :
170 170 # download mathjax
171 171 mathjax_url = "https://github.com/mathjax/MathJax/tarball/%s" % tag
172 172 print "Downloading mathjax source from %s" % mathjax_url
173 173 response = urllib2.urlopen(mathjax_url)
174 174 file = response.fp
175 175
176 176 print "Extracting to %s" % dest
177 177 extractor( file, dest )
178 178
179 179 ##
180 180
181 181 def test_func( remove, dest) :
182 182 """See if mathjax appears to be installed correctly"""
183 183 status = 0
184 184 if not os.path.isdir( dest ) :
185 185 print "%s directory not found" % dest
186 186 status = 1
187 187 if not os.path.exists( dest + "/MathJax.js" ) :
188 188 print "MathJax.js not present in %s" % dest
189 189 status = 1
190 190 print "ok"
191 191 if remove and os.path.exists(dest):
192 192 shutil.rmtree( dest )
193 193 return status
194 194
195 195 ##
196 196
197 197 def main() :
198 198 # This main is just simple enough that it is not worth the
199 199 # complexity of argparse
200 200
201 201 # What directory is mathjax in?
202 202 parser = argparse.ArgumentParser(
203 203 description="""Install mathjax from internet or local archive""",
204 204 )
205 205
206 206 parser.add_argument(
207 207 '-p',
208 208 '--profile',
209 209 default='default',
210 210 help='profile to install MathJax to (default is default)')
211 211
212 212 parser.add_argument(
213 213 '-i',
214 214 '--install-dir',
215 215 help='custom installation directory')
216 216
217 217 parser.add_argument(
218 218 '-d',
219 219 '--dest',
220 220 action='store_true',
221 221 help='print where current mathjax would be installed and exit')
222 222 parser.add_argument(
223 223 '-r',
224 224 '--replace',
225 225 action='store_true',
226 226 help='Whether to replace current mathjax if it already exists')
227 227 parser.add_argument(
228 228 '-t',
229 229 '--test',
230 230 action='store_true')
231 231 parser.add_argument('tarball',
232 232 help="the local tar/zip-ball containing mathjax",
233 233 nargs='?',
234 234 metavar='tarball')
235 235
236 236 pargs = parser.parse_args()
237 237
238 238 if pargs.install_dir:
239 239 # Explicit install_dir overrides profile
240 240 dest = pargs.install_dir
241 241 else:
242 242 profile = pargs.profile
243 243 dest = os.path.join(locate_profile(profile), 'static', 'mathjax')
244 244
245 245 if pargs.dest :
246 246 print dest
247 247 return
248 248
249 249 # remove/replace existing mathjax?
250 250 if pargs.replace :
251 251 replace = True
252 252 else :
253 253 replace = False
254 254
255 255 # undocumented test interface
256 256 if pargs.test :
257 257 return test_func( replace, dest)
258 258
259 259 # do it
260 260 if pargs.tarball :
261 261 fname = pargs.tarball
262 262
263 263 # automatically detect zip/tar - could do something based
264 264 # on file content, but really not cost-effective here.
265 265 if fname.endswith('.zip') :
266 266 extractor = extract_zip
267 267 else :
268 268 extractor = extract_tar
269 269 # do it
270 270 install_mathjax(file=open(fname, "r"), replace=replace, extractor=extractor, dest=dest )
271 271 else:
272 272 install_mathjax(replace=replace, dest=dest)
273 273
274 274
275 275 if __name__ == '__main__' :
276 276 sys.exit(main())
277 277
278 278 __all__ = ['install_mathjax', 'main', 'default_dest']
279 279
280 280 """
281 281 Test notes:
282 282
283 283 IPython uses IPython.testing.iptest as a custom test controller
284 284 (though it is based on nose). It might be possible to fit automatic
285 285 tests of installation into that framework, but it looks awkward to me.
286 286 So, here is a manual procedure for testing this automatic installer.
287 287
288 288 Mark Sienkiewicz, 2012-08-06
289 289 first 8 letters of my last name @ stsci.edu
290 290
291 291 # remove mathjax from the installed ipython instance
292 292 # IOError ok if mathjax was never installed yet.
293 293
294 294 python -m IPython.external.mathjax --test -r
295 295
296 296 # download and install mathjax from command line:
297 297
298 298 python -m IPython.external.mathjax
299 299 python -m IPython.external.mathjax --test -r
300 300
301 301 # download and install from within python
302 302
303 303 python -c "from IPython.external.mathjax import install_mathjax; install_mathjax()"
304 304 python -m IPython.external.mathjax --test -r
305 305
306 306 # view http://www.mathjax.org/download/ in your browser
307 307 # save-as the link for MathJax-2.0 near the bottom of the page.
308 308 # The file it offers is mathjax-MathJax-v2.0-20-g07669ac.zip
309 309
310 310 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.zip
311 311 python -m IPython.external.mathjax --test -r
312 312
313 313 # download https://github.com/mathjax/MathJax/tarball/v2.0 in your browser
314 314 # (this is the url used internally by install_mathjax)
315 315 # The file it offers is mathjax-MathJax-v2.0-20-g07669ac.tar.gz
316 316
317 317 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.tar.gz
318 318
319 319 python -m IPython.external.mathjax --test
320 320 # note no -r
321 321
322 322 # install it again while it is already there
323 323
324 324 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.tar.gz
325 325 # says "offline MathJax apparently already installed"
326 326
327 327 python -m IPython.external.mathjax ~/mathjax-MathJax-v2.0-20-g07669ac.tar.gz
328 328 python -m IPython.external.mathjax --test
329 329
330 330
331 331 """
@@ -1,88 +1,88 b''
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf8 -*-
3 import argparse
4 import traceback
5 import json
3 6
4 7 from IPython.external.jsonschema import Draft3Validator, validate, ValidationError
5 8 import IPython.external.jsonpointer as jsonpointer
6 from IPython.external import argparse
7 import traceback
8 import json
9 9
10 10 def nbvalidate(nbjson, schema='v3.withref.json', key=None,verbose=True):
11 11 v3schema = resolve_ref(json.load(open(schema,'r')))
12 12 if key :
13 13 v3schema = jsonpointer.resolve_pointer(v3schema,key)
14 14 errors = 0
15 15 v = Draft3Validator(v3schema);
16 16 for error in v.iter_errors(nbjson):
17 17 errors = errors + 1
18 18 if verbose:
19 19 print(error)
20 20 return errors
21 21
22 22 def resolve_ref(json, base=None):
23 23 """return a json with resolved internal references
24 24
25 25 only support local reference to the same json
26 26 """
27 27 if not base :
28 28 base = json
29 29
30 30 temp = None
31 31 if type(json) is list:
32 32 temp = [];
33 33 for item in json:
34 34 temp.append(resolve_ref(item, base=base))
35 35 elif type(json) is dict:
36 36 temp = {};
37 37 for key,value in json.iteritems():
38 38 if key == '$ref':
39 39 return resolve_ref(jsonpointer.resolve_pointer(base,value), base=base)
40 40 else :
41 41 temp[key]=resolve_ref(value, base=base)
42 42 else :
43 43 return json
44 44 return temp
45 45
46 46 def convert(namein, nameout, indent=2):
47 47 """resolve the references of namein, save the result in nameout"""
48 48 jsn = None
49 49 with open(namein) as file :
50 50 jsn = json.load(file)
51 51 v = resolve_ref(jsn, base=jsn)
52 52 x = jsonpointer.resolve_pointer(v, '/notebook')
53 53 with open(nameout,'w') as file:
54 54 json.dump(x,file,indent=indent)
55 55
56 56
57 57 if __name__ == '__main__':
58 58 parser = argparse.ArgumentParser()
59 59 parser.add_argument('-s', '--schema',
60 60 type=str, default='v3.withref.json')
61 61
62 62 parser.add_argument('-k', '--key',
63 63 type=str, default='/notebook',
64 64 help='subkey to extract json schema from json file')
65 65
66 66 parser.add_argument("-v", "--verbose", action="store_true",
67 67 help="increase output verbosity")
68 68
69 69 parser.add_argument('filename',
70 70 type=str,
71 71 help="file to validate",
72 72 nargs='*',
73 73 metavar='names')
74 74
75 75 args = parser.parse_args()
76 76 for name in args.filename :
77 77 nerror = nbvalidate(json.load(open(name,'r')),
78 78 schema=args.schema,
79 79 key=args.key,
80 80 verbose=args.verbose)
81 81 if nerror is 0:
82 82 print u"[Pass]",name
83 83 else :
84 84 print u"[ ]",name,'(%d)'%(nerror)
85 85 if args.verbose :
86 86 print '=================================================='
87 87
88 88
General Comments 0
You need to be logged in to leave comments. Login now