##// END OF EJS Templates
add LazyConfig.prepend
MinRK -
Show More
@@ -1,812 +1,821 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 27 import argparse
28 28 import copy
29 29 import os
30 30 import re
31 31 import sys
32 32
33 33 from IPython.utils.path import filefind, get_ipython_dir
34 34 from IPython.utils import py3compat, warn
35 35 from IPython.utils.encoding import DEFAULT_ENCODING
36 36 from IPython.utils.traitlets import HasTraits, List, Any, TraitError
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Exceptions
40 40 #-----------------------------------------------------------------------------
41 41
42 42
43 43 class ConfigError(Exception):
44 44 pass
45 45
46 46 class ConfigLoaderError(ConfigError):
47 47 pass
48 48
49 49 class ConfigFileNotFound(ConfigError):
50 50 pass
51 51
52 52 class ArgumentError(ConfigLoaderError):
53 53 pass
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Argparse fix
57 57 #-----------------------------------------------------------------------------
58 58
59 59 # Unfortunately argparse by default prints help messages to stderr instead of
60 60 # stdout. This makes it annoying to capture long help screens at the command
61 61 # line, since one must know how to pipe stderr, which many users don't know how
62 62 # to do. So we override the print_help method with one that defaults to
63 63 # stdout and use our class instead.
64 64
65 65 class ArgumentParser(argparse.ArgumentParser):
66 66 """Simple argparse subclass that prints help to stdout by default."""
67 67
68 68 def print_help(self, file=None):
69 69 if file is None:
70 70 file = sys.stdout
71 71 return super(ArgumentParser, self).print_help(file)
72 72
73 73 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # Config class for holding config information
77 77 #-----------------------------------------------------------------------------
78 78
79 79 class LazyConfigValue(HasTraits):
80 80 """Proxy object for exposing methods on configurable containers
81 81
82 82 Exposes:
83 83
84 84 - append, extend, insert on lists
85 85 - update on dicts
86 86 - update, add on sets
87 87 """
88 88
89 89 _value = None
90 90
91 91 # list methods
92 92 _extend = List()
93 _prepend = List()
93 94
94 95 def append(self, obj):
95 96 self._extend.append(obj)
96 97
97 98 def extend(self, other):
98 99 self._extend.extend(other)
99 100
101 def prepend(self, other):
102 """like list.extend, but for the front"""
103 self._prepend[:0] = other
104
100 105 _inserts = List()
101 106 def insert(self, index, other):
102 107 if not isinstance(index, int):
103 108 raise TypeError("An integer is required")
104 109 self._inserts.append((index, other))
105 110
106 111 # dict methods
107 112 # update is used for both dict and set
108 113 _update = Any()
109 114 def update(self, other):
110 115 if self._update is None:
111 116 if isinstance(other, dict):
112 117 self._update = {}
113 118 else:
114 119 self._update = set()
115 120 self._update.update(other)
116 121
117 122 # set methods
118 123 def add(self, obj):
119 124 self.update({obj})
120 125
121 126 def get_value(self, initial):
122 127 """construct the value from the initial one
123 128
124 129 after applying any insert / extend / update changes
125 130 """
126 131 if self._value is not None:
127 132 return self._value
128 133 value = copy.deepcopy(initial)
129 134 if isinstance(value, list):
130 135 for idx, obj in self._inserts:
131 136 value.insert(idx, obj)
137 value[:0] = self._prepend
132 138 value.extend(self._extend)
139
133 140 elif isinstance(value, dict):
134 141 if self._update:
135 142 value.update(self._update)
136 143 elif isinstance(value, set):
137 144 if self._update:
138 145 value.update(self._update)
139 146 self._value = value
140 147 return value
141 148
142 149 def to_dict(self):
143 150 """return JSONable dict form of my data
144 151
145 Currently update as dict or set, extend as list, and inserts as list of tuples.
152 Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
146 153 """
147 154 d = {}
148 155 if self._update:
149 156 d['update'] = self._update
150 157 if self._extend:
151 158 d['extend'] = self._extend
159 if self._prepend:
160 d['prepend'] = self._prepend
152 161 elif self._inserts:
153 162 d['inserts'] = self._inserts
154 163 return d
155 164
156 165
157 166 class Config(dict):
158 167 """An attribute based dict that can do smart merges."""
159 168
160 169 def __init__(self, *args, **kwds):
161 170 dict.__init__(self, *args, **kwds)
162 171 # This sets self.__dict__ = self, but it has to be done this way
163 172 # because we are also overriding __setattr__.
164 173 dict.__setattr__(self, '__dict__', self)
165 174 self._ensure_subconfig()
166 175
167 176 def _ensure_subconfig(self):
168 177 """ensure that sub-dicts that should be Config objects are
169 178
170 179 casts dicts that are under section keys to Config objects,
171 180 which is necessary for constructing Config objects from dict literals.
172 181 """
173 182 for key in self:
174 183 obj = self[key]
175 184 if self._is_section_key(key) \
176 185 and isinstance(obj, dict) \
177 186 and not isinstance(obj, Config):
178 187 dict.__setattr__(self, key, Config(obj))
179 188
180 189 def _merge(self, other):
181 190 """deprecated alias, use Config.merge()"""
182 191 self.merge(other)
183 192
184 193 def merge(self, other):
185 194 """merge another config object into this one"""
186 195 to_update = {}
187 196 for k, v in other.iteritems():
188 197 if k not in self:
189 198 to_update[k] = v
190 199 else: # I have this key
191 200 if isinstance(v, Config) and isinstance(self[k], Config):
192 201 # Recursively merge common sub Configs
193 202 self[k].merge(v)
194 203 else:
195 204 # Plain updates for non-Configs
196 205 to_update[k] = v
197 206
198 207 self.update(to_update)
199 208
200 209 def _is_section_key(self, key):
201 210 if key[0].upper()==key[0] and not key.startswith('_'):
202 211 return True
203 212 else:
204 213 return False
205 214
206 215 def __contains__(self, key):
207 216 # allow nested contains of the form `Section.key in config`
208 217 if '.' in key:
209 218 first, remainder = key.split('.', 1)
210 219 if first not in self:
211 220 return False
212 221 return remainder in self[first]
213 222
214 223 # we always have Sections
215 224 if self._is_section_key(key):
216 225 return True
217 226 else:
218 227 return super(Config, self).__contains__(key)
219 228 # .has_key is deprecated for dictionaries.
220 229 has_key = __contains__
221 230
222 231 def _has_section(self, key):
223 232 if self._is_section_key(key):
224 233 if super(Config, self).__contains__(key):
225 234 return True
226 235 return False
227 236
228 237 def copy(self):
229 238 return type(self)(dict.copy(self))
230 239
231 240 def __copy__(self):
232 241 return self.copy()
233 242
234 243 def __deepcopy__(self, memo):
235 244 import copy
236 245 return type(self)(copy.deepcopy(self.items()))
237 246
238 247 def __getitem__(self, key):
239 248 # We cannot use directly self._is_section_key, because it triggers
240 249 # infinite recursion on top of PyPy. Instead, we manually fish the
241 250 # bound method.
242 251 is_section_key = self.__class__._is_section_key.__get__(self)
243 252
244 253 # Because we use this for an exec namespace, we need to delegate
245 254 # the lookup of names in __builtin__ to itself. This means
246 255 # that you can't have section or attribute names that are
247 256 # builtins.
248 257 try:
249 258 return getattr(builtin_mod, key)
250 259 except AttributeError:
251 260 pass
252 261 if is_section_key(key):
253 262 try:
254 263 return dict.__getitem__(self, key)
255 264 except KeyError:
256 265 c = Config()
257 266 dict.__setitem__(self, key, c)
258 267 return c
259 268 else:
260 269 try:
261 270 return dict.__getitem__(self, key)
262 271 except KeyError:
263 272 # undefined
264 273 v = LazyConfigValue()
265 274 dict.__setitem__(self, key, v)
266 275 return v
267 276
268 277
269 278 def __setitem__(self, key, value):
270 279 if self._is_section_key(key):
271 280 if not isinstance(value, Config):
272 281 raise ValueError('values whose keys begin with an uppercase '
273 282 'char must be Config instances: %r, %r' % (key, value))
274 283 else:
275 284 dict.__setitem__(self, key, value)
276 285
277 286 def __getattr__(self, key):
278 287 try:
279 288 return self.__getitem__(key)
280 289 except KeyError as e:
281 290 raise AttributeError(e)
282 291
283 292 def __setattr__(self, key, value):
284 293 try:
285 294 self.__setitem__(key, value)
286 295 except KeyError as e:
287 296 raise AttributeError(e)
288 297
289 298 def __delattr__(self, key):
290 299 try:
291 300 dict.__delitem__(self, key)
292 301 except KeyError as e:
293 302 raise AttributeError(e)
294 303
295 304
296 305 #-----------------------------------------------------------------------------
297 306 # Config loading classes
298 307 #-----------------------------------------------------------------------------
299 308
300 309
301 310 class ConfigLoader(object):
302 311 """A object for loading configurations from just about anywhere.
303 312
304 313 The resulting configuration is packaged as a :class:`Struct`.
305 314
306 315 Notes
307 316 -----
308 317 A :class:`ConfigLoader` does one thing: load a config from a source
309 318 (file, command line arguments) and returns the data as a :class:`Struct`.
310 319 There are lots of things that :class:`ConfigLoader` does not do. It does
311 320 not implement complex logic for finding config files. It does not handle
312 321 default values or merge multiple configs. These things need to be
313 322 handled elsewhere.
314 323 """
315 324
316 325 def __init__(self):
317 326 """A base class for config loaders.
318 327
319 328 Examples
320 329 --------
321 330
322 331 >>> cl = ConfigLoader()
323 332 >>> config = cl.load_config()
324 333 >>> config
325 334 {}
326 335 """
327 336 self.clear()
328 337
329 338 def clear(self):
330 339 self.config = Config()
331 340
332 341 def load_config(self):
333 342 """Load a config from somewhere, return a :class:`Config` instance.
334 343
335 344 Usually, this will cause self.config to be set and then returned.
336 345 However, in most cases, :meth:`ConfigLoader.clear` should be called
337 346 to erase any previous state.
338 347 """
339 348 self.clear()
340 349 return self.config
341 350
342 351
343 352 class FileConfigLoader(ConfigLoader):
344 353 """A base class for file based configurations.
345 354
346 355 As we add more file based config loaders, the common logic should go
347 356 here.
348 357 """
349 358 pass
350 359
351 360
352 361 class PyFileConfigLoader(FileConfigLoader):
353 362 """A config loader for pure python files.
354 363
355 364 This calls execfile on a plain python file and looks for attributes
356 365 that are all caps. These attribute are added to the config Struct.
357 366 """
358 367
359 368 def __init__(self, filename, path=None):
360 369 """Build a config loader for a filename and path.
361 370
362 371 Parameters
363 372 ----------
364 373 filename : str
365 374 The file name of the config file.
366 375 path : str, list, tuple
367 376 The path to search for the config file on, or a sequence of
368 377 paths to try in order.
369 378 """
370 379 super(PyFileConfigLoader, self).__init__()
371 380 self.filename = filename
372 381 self.path = path
373 382 self.full_filename = ''
374 383 self.data = None
375 384
376 385 def load_config(self):
377 386 """Load the config from a file and return it as a Struct."""
378 387 self.clear()
379 388 try:
380 389 self._find_file()
381 390 except IOError as e:
382 391 raise ConfigFileNotFound(str(e))
383 392 self._read_file_as_dict()
384 393 self._convert_to_config()
385 394 return self.config
386 395
387 396 def _find_file(self):
388 397 """Try to find the file by searching the paths."""
389 398 self.full_filename = filefind(self.filename, self.path)
390 399
391 400 def _read_file_as_dict(self):
392 401 """Load the config file into self.config, with recursive loading."""
393 402 # This closure is made available in the namespace that is used
394 403 # to exec the config file. It allows users to call
395 404 # load_subconfig('myconfig.py') to load config files recursively.
396 405 # It needs to be a closure because it has references to self.path
397 406 # and self.config. The sub-config is loaded with the same path
398 407 # as the parent, but it uses an empty config which is then merged
399 408 # with the parents.
400 409
401 410 # If a profile is specified, the config file will be loaded
402 411 # from that profile
403 412
404 413 def load_subconfig(fname, profile=None):
405 414 # import here to prevent circular imports
406 415 from IPython.core.profiledir import ProfileDir, ProfileDirError
407 416 if profile is not None:
408 417 try:
409 418 profile_dir = ProfileDir.find_profile_dir_by_name(
410 419 get_ipython_dir(),
411 420 profile,
412 421 )
413 422 except ProfileDirError:
414 423 return
415 424 path = profile_dir.location
416 425 else:
417 426 path = self.path
418 427 loader = PyFileConfigLoader(fname, path)
419 428 try:
420 429 sub_config = loader.load_config()
421 430 except ConfigFileNotFound:
422 431 # Pass silently if the sub config is not there. This happens
423 432 # when a user s using a profile, but not the default config.
424 433 pass
425 434 else:
426 435 self.config.merge(sub_config)
427 436
428 437 # Again, this needs to be a closure and should be used in config
429 438 # files to get the config being loaded.
430 439 def get_config():
431 440 return self.config
432 441
433 442 namespace = dict(
434 443 load_subconfig=load_subconfig,
435 444 get_config=get_config,
436 445 __file__=self.full_filename,
437 446 )
438 447 fs_encoding = sys.getfilesystemencoding() or 'ascii'
439 448 conf_filename = self.full_filename.encode(fs_encoding)
440 449 py3compat.execfile(conf_filename, namespace)
441 450
442 451 def _convert_to_config(self):
443 452 if self.data is None:
444 453 ConfigLoaderError('self.data does not exist')
445 454
446 455
447 456 class CommandLineConfigLoader(ConfigLoader):
448 457 """A config loader for command line arguments.
449 458
450 459 As we add more command line based loaders, the common logic should go
451 460 here.
452 461 """
453 462
454 463 def _exec_config_str(self, lhs, rhs):
455 464 """execute self.config.<lhs> = <rhs>
456 465
457 466 * expands ~ with expanduser
458 467 * tries to assign with raw eval, otherwise assigns with just the string,
459 468 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
460 469 equivalent are `--C.a=4` and `--C.a='4'`.
461 470 """
462 471 rhs = os.path.expanduser(rhs)
463 472 try:
464 473 # Try to see if regular Python syntax will work. This
465 474 # won't handle strings as the quote marks are removed
466 475 # by the system shell.
467 476 value = eval(rhs)
468 477 except (NameError, SyntaxError):
469 478 # This case happens if the rhs is a string.
470 479 value = rhs
471 480
472 481 exec u'self.config.%s = value' % lhs
473 482
474 483 def _load_flag(self, cfg):
475 484 """update self.config from a flag, which can be a dict or Config"""
476 485 if isinstance(cfg, (dict, Config)):
477 486 # don't clobber whole config sections, update
478 487 # each section from config:
479 488 for sec,c in cfg.iteritems():
480 489 self.config[sec].update(c)
481 490 else:
482 491 raise TypeError("Invalid flag: %r" % cfg)
483 492
484 493 # raw --identifier=value pattern
485 494 # but *also* accept '-' as wordsep, for aliases
486 495 # accepts: --foo=a
487 496 # --Class.trait=value
488 497 # --alias-name=value
489 498 # rejects: -foo=value
490 499 # --foo
491 500 # --Class.trait
492 501 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
493 502
494 503 # just flags, no assignments, with two *or one* leading '-'
495 504 # accepts: --foo
496 505 # -foo-bar-again
497 506 # rejects: --anything=anything
498 507 # --two.word
499 508
500 509 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
501 510
502 511 class KeyValueConfigLoader(CommandLineConfigLoader):
503 512 """A config loader that loads key value pairs from the command line.
504 513
505 514 This allows command line options to be gives in the following form::
506 515
507 516 ipython --profile="foo" --InteractiveShell.autocall=False
508 517 """
509 518
510 519 def __init__(self, argv=None, aliases=None, flags=None):
511 520 """Create a key value pair config loader.
512 521
513 522 Parameters
514 523 ----------
515 524 argv : list
516 525 A list that has the form of sys.argv[1:] which has unicode
517 526 elements of the form u"key=value". If this is None (default),
518 527 then sys.argv[1:] will be used.
519 528 aliases : dict
520 529 A dict of aliases for configurable traits.
521 530 Keys are the short aliases, Values are the resolved trait.
522 531 Of the form: `{'alias' : 'Configurable.trait'}`
523 532 flags : dict
524 533 A dict of flags, keyed by str name. Vaues can be Config objects,
525 534 dicts, or "key=value" strings. If Config or dict, when the flag
526 535 is triggered, The flag is loaded as `self.config.update(m)`.
527 536
528 537 Returns
529 538 -------
530 539 config : Config
531 540 The resulting Config object.
532 541
533 542 Examples
534 543 --------
535 544
536 545 >>> from IPython.config.loader import KeyValueConfigLoader
537 546 >>> cl = KeyValueConfigLoader()
538 547 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
539 548 >>> sorted(d.items())
540 549 [('A', {'name': 'brian'}), ('B', {'number': 0})]
541 550 """
542 551 self.clear()
543 552 if argv is None:
544 553 argv = sys.argv[1:]
545 554 self.argv = argv
546 555 self.aliases = aliases or {}
547 556 self.flags = flags or {}
548 557
549 558
550 559 def clear(self):
551 560 super(KeyValueConfigLoader, self).clear()
552 561 self.extra_args = []
553 562
554 563
555 564 def _decode_argv(self, argv, enc=None):
556 565 """decode argv if bytes, using stin.encoding, falling back on default enc"""
557 566 uargv = []
558 567 if enc is None:
559 568 enc = DEFAULT_ENCODING
560 569 for arg in argv:
561 570 if not isinstance(arg, unicode):
562 571 # only decode if not already decoded
563 572 arg = arg.decode(enc)
564 573 uargv.append(arg)
565 574 return uargv
566 575
567 576
568 577 def load_config(self, argv=None, aliases=None, flags=None):
569 578 """Parse the configuration and generate the Config object.
570 579
571 580 After loading, any arguments that are not key-value or
572 581 flags will be stored in self.extra_args - a list of
573 582 unparsed command-line arguments. This is used for
574 583 arguments such as input files or subcommands.
575 584
576 585 Parameters
577 586 ----------
578 587 argv : list, optional
579 588 A list that has the form of sys.argv[1:] which has unicode
580 589 elements of the form u"key=value". If this is None (default),
581 590 then self.argv will be used.
582 591 aliases : dict
583 592 A dict of aliases for configurable traits.
584 593 Keys are the short aliases, Values are the resolved trait.
585 594 Of the form: `{'alias' : 'Configurable.trait'}`
586 595 flags : dict
587 596 A dict of flags, keyed by str name. Values can be Config objects
588 597 or dicts. When the flag is triggered, The config is loaded as
589 598 `self.config.update(cfg)`.
590 599 """
591 600 self.clear()
592 601 if argv is None:
593 602 argv = self.argv
594 603 if aliases is None:
595 604 aliases = self.aliases
596 605 if flags is None:
597 606 flags = self.flags
598 607
599 608 # ensure argv is a list of unicode strings:
600 609 uargv = self._decode_argv(argv)
601 610 for idx,raw in enumerate(uargv):
602 611 # strip leading '-'
603 612 item = raw.lstrip('-')
604 613
605 614 if raw == '--':
606 615 # don't parse arguments after '--'
607 616 # this is useful for relaying arguments to scripts, e.g.
608 617 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
609 618 self.extra_args.extend(uargv[idx+1:])
610 619 break
611 620
612 621 if kv_pattern.match(raw):
613 622 lhs,rhs = item.split('=',1)
614 623 # Substitute longnames for aliases.
615 624 if lhs in aliases:
616 625 lhs = aliases[lhs]
617 626 if '.' not in lhs:
618 627 # probably a mistyped alias, but not technically illegal
619 628 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
620 629 try:
621 630 self._exec_config_str(lhs, rhs)
622 631 except Exception:
623 632 raise ArgumentError("Invalid argument: '%s'" % raw)
624 633
625 634 elif flag_pattern.match(raw):
626 635 if item in flags:
627 636 cfg,help = flags[item]
628 637 self._load_flag(cfg)
629 638 else:
630 639 raise ArgumentError("Unrecognized flag: '%s'"%raw)
631 640 elif raw.startswith('-'):
632 641 kv = '--'+item
633 642 if kv_pattern.match(kv):
634 643 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
635 644 else:
636 645 raise ArgumentError("Invalid argument: '%s'"%raw)
637 646 else:
638 647 # keep all args that aren't valid in a list,
639 648 # in case our parent knows what to do with them.
640 649 self.extra_args.append(item)
641 650 return self.config
642 651
643 652 class ArgParseConfigLoader(CommandLineConfigLoader):
644 653 """A loader that uses the argparse module to load from the command line."""
645 654
646 655 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
647 656 """Create a config loader for use with argparse.
648 657
649 658 Parameters
650 659 ----------
651 660
652 661 argv : optional, list
653 662 If given, used to read command-line arguments from, otherwise
654 663 sys.argv[1:] is used.
655 664
656 665 parser_args : tuple
657 666 A tuple of positional arguments that will be passed to the
658 667 constructor of :class:`argparse.ArgumentParser`.
659 668
660 669 parser_kw : dict
661 670 A tuple of keyword arguments that will be passed to the
662 671 constructor of :class:`argparse.ArgumentParser`.
663 672
664 673 Returns
665 674 -------
666 675 config : Config
667 676 The resulting Config object.
668 677 """
669 678 super(CommandLineConfigLoader, self).__init__()
670 679 self.clear()
671 680 if argv is None:
672 681 argv = sys.argv[1:]
673 682 self.argv = argv
674 683 self.aliases = aliases or {}
675 684 self.flags = flags or {}
676 685
677 686 self.parser_args = parser_args
678 687 self.version = parser_kw.pop("version", None)
679 688 kwargs = dict(argument_default=argparse.SUPPRESS)
680 689 kwargs.update(parser_kw)
681 690 self.parser_kw = kwargs
682 691
683 692 def load_config(self, argv=None, aliases=None, flags=None):
684 693 """Parse command line arguments and return as a Config object.
685 694
686 695 Parameters
687 696 ----------
688 697
689 698 args : optional, list
690 699 If given, a list with the structure of sys.argv[1:] to parse
691 700 arguments from. If not given, the instance's self.argv attribute
692 701 (given at construction time) is used."""
693 702 self.clear()
694 703 if argv is None:
695 704 argv = self.argv
696 705 if aliases is None:
697 706 aliases = self.aliases
698 707 if flags is None:
699 708 flags = self.flags
700 709 self._create_parser(aliases, flags)
701 710 self._parse_args(argv)
702 711 self._convert_to_config()
703 712 return self.config
704 713
705 714 def get_extra_args(self):
706 715 if hasattr(self, 'extra_args'):
707 716 return self.extra_args
708 717 else:
709 718 return []
710 719
711 720 def _create_parser(self, aliases=None, flags=None):
712 721 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
713 722 self._add_arguments(aliases, flags)
714 723
715 724 def _add_arguments(self, aliases=None, flags=None):
716 725 raise NotImplementedError("subclasses must implement _add_arguments")
717 726
718 727 def _parse_args(self, args):
719 728 """self.parser->self.parsed_data"""
720 729 # decode sys.argv to support unicode command-line options
721 730 enc = DEFAULT_ENCODING
722 731 uargs = [py3compat.cast_unicode(a, enc) for a in args]
723 732 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
724 733
725 734 def _convert_to_config(self):
726 735 """self.parsed_data->self.config"""
727 736 for k, v in vars(self.parsed_data).iteritems():
728 737 exec "self.config.%s = v"%k in locals(), globals()
729 738
730 739 class KVArgParseConfigLoader(ArgParseConfigLoader):
731 740 """A config loader that loads aliases and flags with argparse,
732 741 but will use KVLoader for the rest. This allows better parsing
733 742 of common args, such as `ipython -c 'print 5'`, but still gets
734 743 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
735 744
736 745 def _add_arguments(self, aliases=None, flags=None):
737 746 self.alias_flags = {}
738 747 # print aliases, flags
739 748 if aliases is None:
740 749 aliases = self.aliases
741 750 if flags is None:
742 751 flags = self.flags
743 752 paa = self.parser.add_argument
744 753 for key,value in aliases.iteritems():
745 754 if key in flags:
746 755 # flags
747 756 nargs = '?'
748 757 else:
749 758 nargs = None
750 759 if len(key) is 1:
751 760 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
752 761 else:
753 762 paa('--'+key, type=unicode, dest=value, nargs=nargs)
754 763 for key, (value, help) in flags.iteritems():
755 764 if key in self.aliases:
756 765 #
757 766 self.alias_flags[self.aliases[key]] = value
758 767 continue
759 768 if len(key) is 1:
760 769 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
761 770 else:
762 771 paa('--'+key, action='append_const', dest='_flags', const=value)
763 772
764 773 def _convert_to_config(self):
765 774 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
766 775 # remove subconfigs list from namespace before transforming the Namespace
767 776 if '_flags' in self.parsed_data:
768 777 subcs = self.parsed_data._flags
769 778 del self.parsed_data._flags
770 779 else:
771 780 subcs = []
772 781
773 782 for k, v in vars(self.parsed_data).iteritems():
774 783 if v is None:
775 784 # it was a flag that shares the name of an alias
776 785 subcs.append(self.alias_flags[k])
777 786 else:
778 787 # eval the KV assignment
779 788 self._exec_config_str(k, v)
780 789
781 790 for subc in subcs:
782 791 self._load_flag(subc)
783 792
784 793 if self.extra_args:
785 794 sub_parser = KeyValueConfigLoader()
786 795 sub_parser.load_config(self.extra_args)
787 796 self.config.merge(sub_parser.config)
788 797 self.extra_args = sub_parser.extra_args
789 798
790 799
791 800 def load_pyconfig_files(config_files, path):
792 801 """Load multiple Python config files, merging each of them in turn.
793 802
794 803 Parameters
795 804 ==========
796 805 config_files : list of str
797 806 List of config files names to load and merge into the config.
798 807 path : unicode
799 808 The full path to the location of the config files.
800 809 """
801 810 config = Config()
802 811 for cf in config_files:
803 812 loader = PyFileConfigLoader(cf, path=path)
804 813 try:
805 814 next_config = loader.load_config()
806 815 except ConfigFileNotFound:
807 816 pass
808 817 except:
809 818 raise
810 819 else:
811 820 config.merge(next_config)
812 821 return config
General Comments 0
You need to be logged in to leave comments. Login now