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