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