##// END OF EJS Templates
use argparse to parse aliases & flags
MinRK -
Show More
@@ -1,431 +1,431 b''
1 1 # encoding: utf-8
2 2 """
3 3 A base class for a configurable application.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Min RK
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import logging
23 23 import os
24 24 import re
25 25 import sys
26 26 from copy import deepcopy
27 27
28 28 from IPython.config.configurable import SingletonConfigurable
29 29 from IPython.config.loader import (
30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError
31 31 )
32 32
33 33 from IPython.utils.traitlets import (
34 34 Unicode, List, Int, Enum, Dict, Instance, TraitError
35 35 )
36 36 from IPython.utils.importstring import import_item
37 37 from IPython.utils.text import indent, wrap_paragraphs, dedent
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # function for re-wrapping a helpstring
41 41 #-----------------------------------------------------------------------------
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Descriptions for the various sections
45 45 #-----------------------------------------------------------------------------
46 46
47 47 # merge flags&aliases into options
48 48 option_description = """
49 49 IPython command-line arguments are passed as '--<flag>', or '--<name>=<value>'.
50 50
51 51 Arguments that take values are actually convenience aliases to full
52 52 Configurables, whose aliases are listed on the help line. For more information
53 53 on full configurables, see '--help-all'.
54 54 """.strip() # trim newlines of front and back
55 55
56 56 keyvalue_description = """
57 57 Parameters are set from command-line arguments of the form:
58 58 `--Class.trait=value`.
59 59 This line is evaluated in Python, so simple expressions are allowed, e.g.::
60 60 `--C.a='range(3)'` For setting C.a=[0,1,2].
61 61 """.strip() # trim newlines of front and back
62 62
63 63 subcommand_description = """
64 64 Subcommands are launched as `{app} cmd [args]`. For information on using
65 65 subcommand 'cmd', do: `{app} cmd -h`.
66 66 """.strip().format(app=os.path.basename(sys.argv[0]))
67 67 # get running program name
68 68
69 69 #-----------------------------------------------------------------------------
70 70 # Application class
71 71 #-----------------------------------------------------------------------------
72 72
73 73
74 74 class ApplicationError(Exception):
75 75 pass
76 76
77 77
78 78 class Application(SingletonConfigurable):
79 79 """A singleton application with full configuration support."""
80 80
81 81 # The name of the application, will usually match the name of the command
82 82 # line application
83 83 name = Unicode(u'application')
84 84
85 85 # The description of the application that is printed at the beginning
86 86 # of the help.
87 87 description = Unicode(u'This is an application.')
88 88 # default section descriptions
89 89 option_description = Unicode(option_description)
90 90 keyvalue_description = Unicode(keyvalue_description)
91 91 subcommand_description = Unicode(subcommand_description)
92 92
93 93 # The usage and example string that goes at the end of the help string.
94 94 examples = Unicode()
95 95
96 96 # A sequence of Configurable subclasses whose config=True attributes will
97 97 # be exposed at the command line.
98 98 classes = List([])
99 99
100 100 # The version string of this application.
101 101 version = Unicode(u'0.0')
102 102
103 103 # The log level for the application
104 104 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
105 105 default_value=logging.WARN,
106 106 config=True,
107 107 help="Set the log level by value or name.")
108 108 def _log_level_changed(self, name, old, new):
109 109 """Adjust the log level when log_level is set."""
110 110 if isinstance(new, basestring):
111 111 new = getattr(logging, new)
112 112 self.log_level = new
113 113 self.log.setLevel(new)
114 114
115 115 # the alias map for configurables
116 116 aliases = Dict({'log-level' : 'Application.log_level'})
117 117
118 118 # flags for loading Configurables or store_const style flags
119 119 # flags are loaded from this dict by '--key' flags
120 120 # this must be a dict of two-tuples, the first element being the Config/dict
121 121 # and the second being the help string for the flag
122 122 flags = Dict()
123 123 def _flags_changed(self, name, old, new):
124 124 """ensure flags dict is valid"""
125 125 for key,value in new.iteritems():
126 126 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
127 127 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
128 128 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
129 129
130 130
131 131 # subcommands for launching other applications
132 132 # if this is not empty, this will be a parent Application
133 133 # this must be a dict of two-tuples,
134 134 # the first element being the application class/import string
135 135 # and the second being the help string for the subcommand
136 136 subcommands = Dict()
137 137 # parse_command_line will initialize a subapp, if requested
138 138 subapp = Instance('IPython.config.application.Application', allow_none=True)
139 139
140 140 # extra command-line arguments that don't set config values
141 141 extra_args = List(Unicode)
142 142
143 143
144 144 def __init__(self, **kwargs):
145 145 SingletonConfigurable.__init__(self, **kwargs)
146 146 # Ensure my class is in self.classes, so my attributes appear in command line
147 147 # options and config files.
148 148 if self.__class__ not in self.classes:
149 149 self.classes.insert(0, self.__class__)
150 150
151 151 self.init_logging()
152 152
153 153 def _config_changed(self, name, old, new):
154 154 SingletonConfigurable._config_changed(self, name, old, new)
155 155 self.log.debug('Config changed:')
156 156 self.log.debug(repr(new))
157 157
158 158 def init_logging(self):
159 159 """Start logging for this application.
160 160
161 161 The default is to log to stdout using a StreaHandler. The log level
162 162 starts at loggin.WARN, but this can be adjusted by setting the
163 163 ``log_level`` attribute.
164 164 """
165 165 self.log = logging.getLogger(self.__class__.__name__)
166 166 self.log.setLevel(self.log_level)
167 167 if sys.executable.endswith('pythonw.exe'):
168 168 # this should really go to a file, but file-logging is only
169 169 # hooked up in parallel applications
170 170 self._log_handler = logging.StreamHandler(open(os.devnull, 'w'))
171 171 else:
172 172 self._log_handler = logging.StreamHandler()
173 173 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
174 174 self._log_handler.setFormatter(self._log_formatter)
175 175 self.log.addHandler(self._log_handler)
176 176
177 177 def initialize(self, argv=None):
178 178 """Do the basic steps to configure me.
179 179
180 180 Override in subclasses.
181 181 """
182 182 self.parse_command_line(argv)
183 183
184 184
185 185 def start(self):
186 186 """Start the app mainloop.
187 187
188 188 Override in subclasses.
189 189 """
190 190 if self.subapp is not None:
191 191 return self.subapp.start()
192 192
193 193 def print_alias_help(self):
194 194 """Print the alias part of the help."""
195 195 if not self.aliases:
196 196 return
197 197
198 198 lines = []
199 199 classdict = {}
200 200 for cls in self.classes:
201 201 # include all parents (up to, but excluding Configurable) in available names
202 202 for c in cls.mro()[:-3]:
203 203 classdict[c.__name__] = c
204 204
205 205 for alias, longname in self.aliases.iteritems():
206 206 classname, traitname = longname.split('.',1)
207 207 cls = classdict[classname]
208 208
209 209 trait = cls.class_traits(config=True)[traitname]
210 210 help = cls.class_get_trait_help(trait).splitlines()
211 211 # reformat first line
212 212 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
213 213 lines.extend(help)
214 214 # lines.append('')
215 215 print os.linesep.join(lines)
216 216
217 217 def print_flag_help(self):
218 218 """Print the flag part of the help."""
219 219 if not self.flags:
220 220 return
221 221
222 222 lines = []
223 223 for m, (cfg,help) in self.flags.iteritems():
224 224 lines.append('--'+m)
225 225 lines.append(indent(dedent(help.strip())))
226 226 # lines.append('')
227 227 print os.linesep.join(lines)
228 228
229 229 def print_options(self):
230 230 if not self.flags and not self.aliases:
231 231 return
232 232 lines = ['Options']
233 233 lines.append('-'*len(lines[0]))
234 234 lines.append('')
235 235 for p in wrap_paragraphs(self.option_description):
236 236 lines.append(p)
237 237 lines.append('')
238 238 print os.linesep.join(lines)
239 239 self.print_flag_help()
240 240 self.print_alias_help()
241 241 print
242 242
243 243 def print_subcommands(self):
244 244 """Print the subcommand part of the help."""
245 245 if not self.subcommands:
246 246 return
247 247
248 248 lines = ["Subcommands"]
249 249 lines.append('-'*len(lines[0]))
250 250 lines.append('')
251 251 for p in wrap_paragraphs(self.subcommand_description):
252 252 lines.append(p)
253 253 lines.append('')
254 254 for subc, (cls, help) in self.subcommands.iteritems():
255 255 lines.append(subc)
256 256 if help:
257 257 lines.append(indent(dedent(help.strip())))
258 258 lines.append('')
259 259 print os.linesep.join(lines)
260 260
261 261 def print_help(self, classes=False):
262 262 """Print the help for each Configurable class in self.classes.
263 263
264 264 If classes=False (the default), only flags and aliases are printed.
265 265 """
266 266 self.print_subcommands()
267 267 self.print_options()
268 268
269 269 if classes:
270 270 if self.classes:
271 271 print "Class parameters"
272 272 print "----------------"
273 273 print
274 274 for p in wrap_paragraphs(self.keyvalue_description):
275 275 print p
276 276 print
277 277
278 278 for cls in self.classes:
279 279 cls.class_print_help()
280 280 print
281 281 else:
282 282 print "To see all available configurables, use `--help-all`"
283 283 print
284 284
285 285 def print_description(self):
286 286 """Print the application description."""
287 287 for p in wrap_paragraphs(self.description):
288 288 print p
289 289 print
290 290
291 291 def print_examples(self):
292 292 """Print usage and examples.
293 293
294 294 This usage string goes at the end of the command line help string
295 295 and should contain examples of the application's usage.
296 296 """
297 297 if self.examples:
298 298 print "Examples"
299 299 print "--------"
300 300 print
301 301 print indent(dedent(self.examples.strip()))
302 302 print
303 303
304 304 def print_version(self):
305 305 """Print the version string."""
306 306 print self.version
307 307
308 308 def update_config(self, config):
309 309 """Fire the traits events when the config is updated."""
310 310 # Save a copy of the current config.
311 311 newconfig = deepcopy(self.config)
312 312 # Merge the new config into the current one.
313 313 newconfig._merge(config)
314 314 # Save the combined config as self.config, which triggers the traits
315 315 # events.
316 316 self.config = newconfig
317 317
318 318 def initialize_subcommand(self, subc, argv=None):
319 319 """Initialize a subcommand with argv."""
320 320 subapp,help = self.subcommands.get(subc)
321 321
322 322 if isinstance(subapp, basestring):
323 323 subapp = import_item(subapp)
324 324
325 325 # clear existing instances
326 326 self.__class__.clear_instance()
327 327 # instantiate
328 328 self.subapp = subapp.instance()
329 329 # and initialize subapp
330 330 self.subapp.initialize(argv)
331 331
332 332 def parse_command_line(self, argv=None):
333 333 """Parse the command line arguments."""
334 334 argv = sys.argv[1:] if argv is None else argv
335 335
336 336 if self.subcommands and len(argv) > 0:
337 337 # we have subcommands, and one may have been specified
338 338 subc, subargv = argv[0], argv[1:]
339 339 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
340 340 # it's a subcommand, and *not* a flag or class parameter
341 341 return self.initialize_subcommand(subc, subargv)
342 342
343 343 if '-h' in argv or '--help' in argv or '--help-all' in argv:
344 344 self.print_description()
345 345 self.print_help('--help-all' in argv)
346 346 self.print_examples()
347 347 self.exit(0)
348 348
349 349 if '--version' in argv:
350 350 self.print_version()
351 351 self.exit(0)
352 352
353 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
353 loader = KVArgParseConfigLoader(argv=argv, aliases=self.aliases,
354 354 flags=self.flags)
355 355 try:
356 356 config = loader.load_config()
357 357 self.update_config(config)
358 358 except (TraitError, ArgumentError) as e:
359 359 self.print_description()
360 360 self.print_help()
361 361 self.print_examples()
362 362 self.log.fatal(str(e))
363 363 self.exit(1)
364 364 # store unparsed args in extra_args
365 365 self.extra_args = loader.extra_args
366 366
367 367 def load_config_file(self, filename, path=None):
368 368 """Load a .py based config file by filename and path."""
369 369 loader = PyFileConfigLoader(filename, path=path)
370 370 try:
371 371 config = loader.load_config()
372 372 except IOError:
373 373 # problem with the file (probably doesn't exist), raise
374 374 raise
375 375 except Exception:
376 376 # problem while running the file
377 377 self.log.error("Exception while loading config file %s [path=%s]"%
378 378 (filename, path), exc_info=True)
379 379 else:
380 380 self.update_config(config)
381 381
382 382 def generate_config_file(self):
383 383 """generate default config file from Configurables"""
384 384 lines = ["# Configuration file for %s."%self.name]
385 385 lines.append('')
386 386 lines.append('c = get_config()')
387 387 lines.append('')
388 388 for cls in self.classes:
389 389 lines.append(cls.class_config_section())
390 390 return '\n'.join(lines)
391 391
392 392 def exit(self, exit_status=0):
393 393 self.log.debug("Exiting application: %s" % self.name)
394 394 sys.exit(exit_status)
395 395
396 396 #-----------------------------------------------------------------------------
397 397 # utility functions, for convenience
398 398 #-----------------------------------------------------------------------------
399 399
400 400 def boolean_flag(name, configurable, set_help='', unset_help=''):
401 401 """Helper for building basic --trait, --no-trait flags.
402 402
403 403 Parameters
404 404 ----------
405 405
406 406 name : str
407 407 The name of the flag.
408 408 configurable : str
409 409 The 'Class.trait' string of the trait to be set/unset with the flag
410 410 set_help : unicode
411 411 help string for --name flag
412 412 unset_help : unicode
413 413 help string for --no-name flag
414 414
415 415 Returns
416 416 -------
417 417
418 418 cfg : dict
419 419 A dict with two keys: 'name', and 'no-name', for setting and unsetting
420 420 the trait, respectively.
421 421 """
422 422 # default helpstrings
423 423 set_help = set_help or "set %s=True"%configurable
424 424 unset_help = unset_help or "set %s=False"%configurable
425 425
426 426 cls,trait = configurable.split('.')
427 427
428 428 setter = {cls : {trait : True}}
429 429 unsetter = {cls : {trait : False}}
430 430 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
431 431
@@ -1,588 +1,660 b''
1 1 """A simple configuration system.
2 2
3 3 Authors
4 4 -------
5 5 * Brian Granger
6 6 * Fernando Perez
7 7 * Min RK
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import __builtin__
22 22 import re
23 23 import sys
24 24
25 25 from IPython.external import argparse
26 26 from IPython.utils.path import filefind, get_ipython_dir
27 27 from IPython.utils import warn
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Exceptions
31 31 #-----------------------------------------------------------------------------
32 32
33 33
34 34 class ConfigError(Exception):
35 35 pass
36 36
37 37
38 38 class ConfigLoaderError(ConfigError):
39 39 pass
40 40
41 41 class ArgumentError(ConfigLoaderError):
42 42 pass
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Argparse fix
46 46 #-----------------------------------------------------------------------------
47 47
48 48 # Unfortunately argparse by default prints help messages to stderr instead of
49 49 # stdout. This makes it annoying to capture long help screens at the command
50 50 # line, since one must know how to pipe stderr, which many users don't know how
51 51 # to do. So we override the print_help method with one that defaults to
52 52 # stdout and use our class instead.
53 53
54 54 class ArgumentParser(argparse.ArgumentParser):
55 55 """Simple argparse subclass that prints help to stdout by default."""
56 56
57 57 def print_help(self, file=None):
58 58 if file is None:
59 59 file = sys.stdout
60 60 return super(ArgumentParser, self).print_help(file)
61 61
62 62 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Config class for holding config information
66 66 #-----------------------------------------------------------------------------
67 67
68 68
69 69 class Config(dict):
70 70 """An attribute based dict that can do smart merges."""
71 71
72 72 def __init__(self, *args, **kwds):
73 73 dict.__init__(self, *args, **kwds)
74 74 # This sets self.__dict__ = self, but it has to be done this way
75 75 # because we are also overriding __setattr__.
76 76 dict.__setattr__(self, '__dict__', self)
77 77
78 78 def _merge(self, other):
79 79 to_update = {}
80 80 for k, v in other.iteritems():
81 81 if not self.has_key(k):
82 82 to_update[k] = v
83 83 else: # I have this key
84 84 if isinstance(v, Config):
85 85 # Recursively merge common sub Configs
86 86 self[k]._merge(v)
87 87 else:
88 88 # Plain updates for non-Configs
89 89 to_update[k] = v
90 90
91 91 self.update(to_update)
92 92
93 93 def _is_section_key(self, key):
94 94 if key[0].upper()==key[0] and not key.startswith('_'):
95 95 return True
96 96 else:
97 97 return False
98 98
99 99 def __contains__(self, key):
100 100 if self._is_section_key(key):
101 101 return True
102 102 else:
103 103 return super(Config, self).__contains__(key)
104 104 # .has_key is deprecated for dictionaries.
105 105 has_key = __contains__
106 106
107 107 def _has_section(self, key):
108 108 if self._is_section_key(key):
109 109 if super(Config, self).__contains__(key):
110 110 return True
111 111 return False
112 112
113 113 def copy(self):
114 114 return type(self)(dict.copy(self))
115 115
116 116 def __copy__(self):
117 117 return self.copy()
118 118
119 119 def __deepcopy__(self, memo):
120 120 import copy
121 121 return type(self)(copy.deepcopy(self.items()))
122 122
123 123 def __getitem__(self, key):
124 124 # We cannot use directly self._is_section_key, because it triggers
125 125 # infinite recursion on top of PyPy. Instead, we manually fish the
126 126 # bound method.
127 127 is_section_key = self.__class__._is_section_key.__get__(self)
128 128
129 129 # Because we use this for an exec namespace, we need to delegate
130 130 # the lookup of names in __builtin__ to itself. This means
131 131 # that you can't have section or attribute names that are
132 132 # builtins.
133 133 try:
134 134 return getattr(__builtin__, key)
135 135 except AttributeError:
136 136 pass
137 137 if is_section_key(key):
138 138 try:
139 139 return dict.__getitem__(self, key)
140 140 except KeyError:
141 141 c = Config()
142 142 dict.__setitem__(self, key, c)
143 143 return c
144 144 else:
145 145 return dict.__getitem__(self, key)
146 146
147 147 def __setitem__(self, key, value):
148 148 # Don't allow names in __builtin__ to be modified.
149 149 if hasattr(__builtin__, key):
150 150 raise ConfigError('Config variable names cannot have the same name '
151 151 'as a Python builtin: %s' % key)
152 152 if self._is_section_key(key):
153 153 if not isinstance(value, Config):
154 154 raise ValueError('values whose keys begin with an uppercase '
155 155 'char must be Config instances: %r, %r' % (key, value))
156 156 else:
157 157 dict.__setitem__(self, key, value)
158 158
159 159 def __getattr__(self, key):
160 160 try:
161 161 return self.__getitem__(key)
162 162 except KeyError, e:
163 163 raise AttributeError(e)
164 164
165 165 def __setattr__(self, key, value):
166 166 try:
167 167 self.__setitem__(key, value)
168 168 except KeyError, e:
169 169 raise AttributeError(e)
170 170
171 171 def __delattr__(self, key):
172 172 try:
173 173 dict.__delitem__(self, key)
174 174 except KeyError, e:
175 175 raise AttributeError(e)
176 176
177 177
178 178 #-----------------------------------------------------------------------------
179 179 # Config loading classes
180 180 #-----------------------------------------------------------------------------
181 181
182 182
183 183 class ConfigLoader(object):
184 184 """A object for loading configurations from just about anywhere.
185 185
186 186 The resulting configuration is packaged as a :class:`Struct`.
187 187
188 188 Notes
189 189 -----
190 190 A :class:`ConfigLoader` does one thing: load a config from a source
191 191 (file, command line arguments) and returns the data as a :class:`Struct`.
192 192 There are lots of things that :class:`ConfigLoader` does not do. It does
193 193 not implement complex logic for finding config files. It does not handle
194 194 default values or merge multiple configs. These things need to be
195 195 handled elsewhere.
196 196 """
197 197
198 198 def __init__(self):
199 199 """A base class for config loaders.
200 200
201 201 Examples
202 202 --------
203 203
204 204 >>> cl = ConfigLoader()
205 205 >>> config = cl.load_config()
206 206 >>> config
207 207 {}
208 208 """
209 209 self.clear()
210 210
211 211 def clear(self):
212 212 self.config = Config()
213 213
214 214 def load_config(self):
215 215 """Load a config from somewhere, return a :class:`Config` instance.
216 216
217 217 Usually, this will cause self.config to be set and then returned.
218 218 However, in most cases, :meth:`ConfigLoader.clear` should be called
219 219 to erase any previous state.
220 220 """
221 221 self.clear()
222 222 return self.config
223 223
224 224
225 225 class FileConfigLoader(ConfigLoader):
226 226 """A base class for file based configurations.
227 227
228 228 As we add more file based config loaders, the common logic should go
229 229 here.
230 230 """
231 231 pass
232 232
233 233
234 234 class PyFileConfigLoader(FileConfigLoader):
235 235 """A config loader for pure python files.
236 236
237 237 This calls execfile on a plain python file and looks for attributes
238 238 that are all caps. These attribute are added to the config Struct.
239 239 """
240 240
241 241 def __init__(self, filename, path=None):
242 242 """Build a config loader for a filename and path.
243 243
244 244 Parameters
245 245 ----------
246 246 filename : str
247 247 The file name of the config file.
248 248 path : str, list, tuple
249 249 The path to search for the config file on, or a sequence of
250 250 paths to try in order.
251 251 """
252 252 super(PyFileConfigLoader, self).__init__()
253 253 self.filename = filename
254 254 self.path = path
255 255 self.full_filename = ''
256 256 self.data = None
257 257
258 258 def load_config(self):
259 259 """Load the config from a file and return it as a Struct."""
260 260 self.clear()
261 261 self._find_file()
262 262 self._read_file_as_dict()
263 263 self._convert_to_config()
264 264 return self.config
265 265
266 266 def _find_file(self):
267 267 """Try to find the file by searching the paths."""
268 268 self.full_filename = filefind(self.filename, self.path)
269 269
270 270 def _read_file_as_dict(self):
271 271 """Load the config file into self.config, with recursive loading."""
272 272 # This closure is made available in the namespace that is used
273 273 # to exec the config file. It allows users to call
274 274 # load_subconfig('myconfig.py') to load config files recursively.
275 275 # It needs to be a closure because it has references to self.path
276 276 # and self.config. The sub-config is loaded with the same path
277 277 # as the parent, but it uses an empty config which is then merged
278 278 # with the parents.
279 279
280 280 # If a profile is specified, the config file will be loaded
281 281 # from that profile
282 282
283 283 def load_subconfig(fname, profile=None):
284 284 # import here to prevent circular imports
285 285 from IPython.core.profiledir import ProfileDir, ProfileDirError
286 286 if profile is not None:
287 287 try:
288 288 profile_dir = ProfileDir.find_profile_dir_by_name(
289 289 get_ipython_dir(),
290 290 profile,
291 291 )
292 292 except ProfileDirError:
293 293 return
294 294 path = profile_dir.location
295 295 else:
296 296 path = self.path
297 297 loader = PyFileConfigLoader(fname, path)
298 298 try:
299 299 sub_config = loader.load_config()
300 300 except IOError:
301 301 # Pass silently if the sub config is not there. This happens
302 302 # when a user s using a profile, but not the default config.
303 303 pass
304 304 else:
305 305 self.config._merge(sub_config)
306 306
307 307 # Again, this needs to be a closure and should be used in config
308 308 # files to get the config being loaded.
309 309 def get_config():
310 310 return self.config
311 311
312 312 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
313 313 fs_encoding = sys.getfilesystemencoding() or 'ascii'
314 314 conf_filename = self.full_filename.encode(fs_encoding)
315 315 execfile(conf_filename, namespace)
316 316
317 317 def _convert_to_config(self):
318 318 if self.data is None:
319 319 ConfigLoaderError('self.data does not exist')
320 320
321 321
322 322 class CommandLineConfigLoader(ConfigLoader):
323 323 """A config loader for command line arguments.
324 324
325 325 As we add more command line based loaders, the common logic should go
326 326 here.
327 327 """
328 328
329 def _exec_config_str(self, lhs, rhs):
330 exec_str = 'self.config.' + lhs + '=' + rhs
331 try:
332 # Try to see if regular Python syntax will work. This
333 # won't handle strings as the quote marks are removed
334 # by the system shell.
335 exec exec_str in locals(), globals()
336 except (NameError, SyntaxError):
337 # This case happens if the rhs is a string but without
338 # the quote marks. Use repr, to get quote marks, and
339 # 'u' prefix and see if
340 # it succeeds. If it still fails, we let it raise.
341 exec_str = u'self.config.' + lhs + '=' + repr(rhs)
342 exec exec_str in locals(), globals()
343
344 def _load_flag(self, cfg):
345 """update self.config from a flag, which can be a dict or Config"""
346 if isinstance(cfg, (dict, Config)):
347 # don't clobber whole config sections, update
348 # each section from config:
349 for sec,c in cfg.iteritems():
350 self.config[sec].update(c)
351 else:
352 raise ValueError("Invalid flag: '%s'"%raw)
353
329 354 # raw --identifier=value pattern
330 355 # but *also* accept '-' as wordsep, for aliases
331 356 # accepts: --foo=a
332 357 # --Class.trait=value
333 358 # --alias-name=value
334 359 # rejects: -foo=value
335 360 # --foo
336 361 # --Class.trait
337 362 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
338 363
339 364 # just flags, no assignments, with two *or one* leading '-'
340 365 # accepts: --foo
341 366 # -foo-bar-again
342 367 # rejects: --anything=anything
343 368 # --two.word
344 369
345 370 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
346 371
347 372 class KeyValueConfigLoader(CommandLineConfigLoader):
348 373 """A config loader that loads key value pairs from the command line.
349 374
350 375 This allows command line options to be gives in the following form::
351 376
352 377 ipython --profile="foo" --InteractiveShell.autocall=False
353 378 """
354 379
355 380 def __init__(self, argv=None, aliases=None, flags=None):
356 381 """Create a key value pair config loader.
357 382
358 383 Parameters
359 384 ----------
360 385 argv : list
361 386 A list that has the form of sys.argv[1:] which has unicode
362 387 elements of the form u"key=value". If this is None (default),
363 388 then sys.argv[1:] will be used.
364 389 aliases : dict
365 390 A dict of aliases for configurable traits.
366 391 Keys are the short aliases, Values are the resolved trait.
367 392 Of the form: `{'alias' : 'Configurable.trait'}`
368 393 flags : dict
369 394 A dict of flags, keyed by str name. Vaues can be Config objects,
370 395 dicts, or "key=value" strings. If Config or dict, when the flag
371 396 is triggered, The flag is loaded as `self.config.update(m)`.
372 397
373 398 Returns
374 399 -------
375 400 config : Config
376 401 The resulting Config object.
377 402
378 403 Examples
379 404 --------
380 405
381 406 >>> from IPython.config.loader import KeyValueConfigLoader
382 407 >>> cl = KeyValueConfigLoader()
383 408 >>> cl.load_config(["--A.name='brian'","--B.number=0"])
384 409 {'A': {'name': 'brian'}, 'B': {'number': 0}}
385 410 """
386 411 self.clear()
387 412 if argv is None:
388 413 argv = sys.argv[1:]
389 414 self.argv = argv
390 415 self.aliases = aliases or {}
391 416 self.flags = flags or {}
392 417
393 418
394 419 def clear(self):
395 420 super(KeyValueConfigLoader, self).clear()
396 421 self.extra_args = []
397 422
398 423
399 424 def _decode_argv(self, argv, enc=None):
400 425 """decode argv if bytes, using stin.encoding, falling back on default enc"""
401 426 uargv = []
402 427 if enc is None:
403 428 enc = sys.stdin.encoding or sys.getdefaultencoding()
404 429 for arg in argv:
405 430 if not isinstance(arg, unicode):
406 431 # only decode if not already decoded
407 432 arg = arg.decode(enc)
408 433 uargv.append(arg)
409 434 return uargv
410 435
411 436
412 437 def load_config(self, argv=None, aliases=None, flags=None):
413 438 """Parse the configuration and generate the Config object.
414 439
415 440 After loading, any arguments that are not key-value or
416 441 flags will be stored in self.extra_args - a list of
417 442 unparsed command-line arguments. This is used for
418 443 arguments such as input files or subcommands.
419 444
420 445 Parameters
421 446 ----------
422 447 argv : list, optional
423 448 A list that has the form of sys.argv[1:] which has unicode
424 449 elements of the form u"key=value". If this is None (default),
425 450 then self.argv will be used.
426 451 aliases : dict
427 452 A dict of aliases for configurable traits.
428 453 Keys are the short aliases, Values are the resolved trait.
429 454 Of the form: `{'alias' : 'Configurable.trait'}`
430 455 flags : dict
431 456 A dict of flags, keyed by str name. Values can be Config objects
432 457 or dicts. When the flag is triggered, The config is loaded as
433 458 `self.config.update(cfg)`.
434 459 """
435 460 from IPython.config.configurable import Configurable
436 461
437 462 self.clear()
438 463 if argv is None:
439 464 argv = self.argv
440 465 if aliases is None:
441 466 aliases = self.aliases
442 467 if flags is None:
443 468 flags = self.flags
444 469
445 470 # ensure argv is a list of unicode strings:
446 471 uargv = self._decode_argv(argv)
447 472 for idx,raw in enumerate(uargv):
448 473 # strip leading '-'
449 474 item = raw.lstrip('-')
450 475
451 476 if raw == '--':
452 477 # don't parse arguments after '--'
453 478 # this is useful for relaying arguments to scripts, e.g.
454 479 # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py
455 480 self.extra_args.extend(uargv[idx+1:])
456 481 break
457 482
458 483 if kv_pattern.match(raw):
459 484 lhs,rhs = item.split('=',1)
460 485 # Substitute longnames for aliases.
461 486 if lhs in aliases:
462 487 lhs = aliases[lhs]
463 488 if '.' not in lhs:
464 489 # probably a mistyped alias, but not technically illegal
465 490 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
466 exec_str = 'self.config.' + lhs + '=' + rhs
467 try:
468 # Try to see if regular Python syntax will work. This
469 # won't handle strings as the quote marks are removed
470 # by the system shell.
471 exec exec_str in locals(), globals()
472 except (NameError, SyntaxError):
473 # This case happens if the rhs is a string but without
474 # the quote marks. Use repr, to get quote marks, and
475 # 'u' prefix and see if
476 # it succeeds. If it still fails, we let it raise.
477 exec_str = u'self.config.' + lhs + '=' + repr(rhs)
478 exec exec_str in locals(), globals()
491 self._exec_config_str(lhs, rhs)
492
479 493 elif flag_pattern.match(raw):
480 494 if item in flags:
481 495 cfg,help = flags[item]
482 if isinstance(cfg, (dict, Config)):
483 # don't clobber whole config sections, update
484 # each section from config:
485 for sec,c in cfg.iteritems():
486 self.config[sec].update(c)
487 else:
488 raise ValueError("Invalid flag: '%s'"%raw)
496 self._load_flag(cfg)
489 497 else:
490 498 raise ArgumentError("Unrecognized flag: '%s'"%raw)
491 499 elif raw.startswith('-'):
492 500 kv = '--'+item
493 501 if kv_pattern.match(kv):
494 502 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
495 503 else:
496 504 raise ArgumentError("Invalid argument: '%s'"%raw)
497 505 else:
498 506 # keep all args that aren't valid in a list,
499 507 # in case our parent knows what to do with them.
500 508 self.extra_args.append(item)
501 509 return self.config
502 510
503 511 class ArgParseConfigLoader(CommandLineConfigLoader):
504 512 """A loader that uses the argparse module to load from the command line."""
505 513
506 def __init__(self, argv=None, *parser_args, **parser_kw):
514 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
507 515 """Create a config loader for use with argparse.
508 516
509 517 Parameters
510 518 ----------
511 519
512 520 argv : optional, list
513 521 If given, used to read command-line arguments from, otherwise
514 522 sys.argv[1:] is used.
515 523
516 524 parser_args : tuple
517 525 A tuple of positional arguments that will be passed to the
518 526 constructor of :class:`argparse.ArgumentParser`.
519 527
520 528 parser_kw : dict
521 529 A tuple of keyword arguments that will be passed to the
522 530 constructor of :class:`argparse.ArgumentParser`.
523 531
524 532 Returns
525 533 -------
526 534 config : Config
527 535 The resulting Config object.
528 536 """
529 537 super(CommandLineConfigLoader, self).__init__()
530 if argv == None:
538 self.clear()
539 if argv is None:
531 540 argv = sys.argv[1:]
532 541 self.argv = argv
542 self.aliases = aliases or {}
543 self.flags = flags or {}
544
533 545 self.parser_args = parser_args
534 546 self.version = parser_kw.pop("version", None)
535 547 kwargs = dict(argument_default=argparse.SUPPRESS)
536 548 kwargs.update(parser_kw)
537 549 self.parser_kw = kwargs
538 550
539 def load_config(self, argv=None):
551 def load_config(self, argv=None, aliases=None, flags=None):
540 552 """Parse command line arguments and return as a Config object.
541 553
542 554 Parameters
543 555 ----------
544 556
545 557 args : optional, list
546 558 If given, a list with the structure of sys.argv[1:] to parse
547 559 arguments from. If not given, the instance's self.argv attribute
548 560 (given at construction time) is used."""
549 561 self.clear()
550 562 if argv is None:
551 563 argv = self.argv
552 self._create_parser()
564 if aliases is None:
565 aliases = self.aliases
566 if flags is None:
567 flags = self.flags
568 self._create_parser(aliases, flags)
553 569 self._parse_args(argv)
554 570 self._convert_to_config()
555 571 return self.config
556 572
557 573 def get_extra_args(self):
558 574 if hasattr(self, 'extra_args'):
559 575 return self.extra_args
560 576 else:
561 577 return []
562 578
563 def _create_parser(self):
579 def _create_parser(self, aliases=None, flags=None):
564 580 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
565 self._add_arguments()
581 self._add_arguments(aliases, flags)
566 582
567 def _add_arguments(self):
583 def _add_arguments(self, aliases=None, flags=None):
568 584 raise NotImplementedError("subclasses must implement _add_arguments")
569 585
570 586 def _parse_args(self, args):
571 587 """self.parser->self.parsed_data"""
572 588 # decode sys.argv to support unicode command-line options
573 589 uargs = []
574 590 for a in args:
575 591 if isinstance(a, str):
576 592 # don't decode if we already got unicode
577 593 a = a.decode(sys.stdin.encoding or
578 594 sys.getdefaultencoding())
579 595 uargs.append(a)
580 596 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
581 597
582 598 def _convert_to_config(self):
583 599 """self.parsed_data->self.config"""
584 600 for k, v in vars(self.parsed_data).iteritems():
585 exec_str = 'self.config.' + k + '= v'
586 exec exec_str in locals(), globals()
587
588
601 self._exec_config_str(k, v)
602
603 class KVArgParseConfigLoader(ArgParseConfigLoader):
604 """A config loader that loads aliases and flags with argparse,
605 but will use KVLoader for the rest. This allows better parsing
606 of common args, such as `ipython -c 'print 5'`, but still gets
607 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
608 def _add_arguments(self, aliases=None, flags=None):
609 self.alias_flags = {}
610 # print aliases, flags
611 if aliases is None:
612 aliases = self.aliases
613 if flags is None:
614 flags = self.flags
615 paa = self.parser.add_argument
616 for key,value in aliases.iteritems():
617 if key in flags:
618 # flags
619 nargs = '?'
620 else:
621 nargs = None
622 if len(key) is 1:
623 paa('-'+key, '--'+key, type=str, dest=value, nargs=nargs)
624 else:
625 paa('--'+key, type=str, dest=value, nargs=nargs)
626 for key, (value, help) in flags.iteritems():
627 if key in self.aliases:
628 #
629 self.alias_flags[self.aliases[key]] = value
630 continue
631 if len(key) is 1:
632 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
633 else:
634 paa('--'+key, action='append_const', dest='_flags', const=value)
635
636 def _convert_to_config(self):
637 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
638 # remove subconfigs list from namespace before transforming the Namespace
639 if '_flags' in self.parsed_data:
640 subcs = self.parsed_data._flags
641 del self.parsed_data._flags
642 else:
643 subcs = []
644
645 for k, v in vars(self.parsed_data).iteritems():
646 if v is None:
647 # it was a flag that shares the name of an alias
648 subcs.append(self.alias_flags[k])
649 else:
650 # eval the KV assignment
651 self._exec_config_str(k, v)
652
653 for subc in subcs:
654 self.config.update(subc)
655
656 if self.extra_args:
657 sub_parser = KeyValueConfigLoader()
658 sub_parser.load_config(self.extra_args)
659 self.config.update(sub_parser.config)
660 self.extra_args = sub_parser.extra_args
General Comments 0
You need to be logged in to leave comments. Login now