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