##// END OF EJS Templates
print usage on invalid command-line arguments
MinRK -
Show More
@@ -1,360 +1,368 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 """
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 from copy import deepcopy
22 22 import logging
23 23 import sys
24 24
25 25 from IPython.config.configurable import SingletonConfigurable
26 26 from IPython.config.loader import (
27 KeyValueConfigLoader, PyFileConfigLoader, Config
27 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
28 28 )
29 29
30 30 from IPython.utils.traitlets import (
31 31 Unicode, List, Int, Enum, Dict, Instance
32 32 )
33 33 from IPython.utils.importstring import import_item
34 34 from IPython.utils.text import indent
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Descriptions for the various sections
38 38 #-----------------------------------------------------------------------------
39 39
40 40 flag_description = """
41 41 Flags are command-line arguments passed as '--<flag>'.
42 42 These take no parameters, unlike regular key-value arguments.
43 43 They are typically used for setting boolean flags, or enabling
44 44 modes that involve setting multiple options together.
45 45 """.strip() # trim newlines of front and back
46 46
47 47 alias_description = """
48 48 These are commonly set parameters, given abbreviated aliases for convenience.
49 49 They are set in the same `name=value` way as class parameters, where
50 50 <name> is replaced by the real parameter for which it is an alias.
51 51 """.strip() # trim newlines of front and back
52 52
53 53 keyvalue_description = """
54 54 Parameters are set from command-line arguments of the form:
55 55 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
56 56 This line is evaluated in Python, so simple expressions are allowed, e.g.
57 57 `C.a='range(3)'` For setting C.a=[0,1,2]
58 58 """.strip() # trim newlines of front and back
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Application class
62 62 #-----------------------------------------------------------------------------
63 63
64 64
65 65 class ApplicationError(Exception):
66 66 pass
67 67
68 68
69 69 class Application(SingletonConfigurable):
70 70 """A singleton application with full configuration support."""
71 71
72 72 # The name of the application, will usually match the name of the command
73 73 # line application
74 74 name = Unicode(u'application')
75 75
76 76 # The description of the application that is printed at the beginning
77 77 # of the help.
78 78 description = Unicode(u'This is an application.')
79 79 # default section descriptions
80 80 flag_description = Unicode(flag_description)
81 81 alias_description = Unicode(alias_description)
82 82 keyvalue_description = Unicode(keyvalue_description)
83 83
84 84
85 85 # A sequence of Configurable subclasses whose config=True attributes will
86 86 # be exposed at the command line.
87 87 classes = List([])
88 88
89 89 # The version string of this application.
90 90 version = Unicode(u'0.0')
91 91
92 92 # The log level for the application
93 93 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
94 94 config=True,
95 95 help="Set the log level.")
96 96
97 97 # the alias map for configurables
98 98 aliases = Dict(dict(log_level='Application.log_level'))
99 99
100 100 # flags for loading Configurables or store_const style flags
101 101 # flags are loaded from this dict by '--key' flags
102 102 # this must be a dict of two-tuples, the first element being the Config/dict
103 103 # and the second being the help string for the flag
104 104 flags = Dict()
105 105
106 106 # subcommands for launching other applications
107 107 # if this is not empty, this will be a parent Application
108 108 # this must be a dict of two-tuples, the first element being the application class/import string
109 109 # and the second being the help string for the subcommand
110 110 subcommands = Dict()
111 111 # parse_command_line will initialize a subapp, if requested
112 112 subapp = Instance('IPython.config.application.Application', allow_none=True)
113 113
114 114
115 115 def __init__(self, **kwargs):
116 116 SingletonConfigurable.__init__(self, **kwargs)
117 117 # Add my class to self.classes so my attributes appear in command line
118 118 # options.
119 119 self.classes.insert(0, self.__class__)
120 120
121 121 # ensure self.flags dict is valid
122 122 for key,value in self.flags.iteritems():
123 123 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
124 124 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
125 125 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
126 126 self.init_logging()
127 127
128 128 def _config_changed(self, name, old, new):
129 129 SingletonConfigurable._config_changed(self, name, old, new)
130 130 self.log.debug('Config changed:')
131 131 self.log.debug(repr(new))
132 132
133 133 def init_logging(self):
134 134 """Start logging for this application.
135 135
136 136 The default is to log to stdout using a StreaHandler. The log level
137 137 starts at loggin.WARN, but this can be adjusted by setting the
138 138 ``log_level`` attribute.
139 139 """
140 140 self.log = logging.getLogger(self.__class__.__name__)
141 141 self.log.setLevel(self.log_level)
142 142 self._log_handler = logging.StreamHandler()
143 143 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
144 144 self._log_handler.setFormatter(self._log_formatter)
145 145 self.log.addHandler(self._log_handler)
146 146
147 147 def initialize(self, argv=None):
148 148 """Do the basic steps to configure me.
149 149
150 150 Override in subclasses.
151 151 """
152 152 self.parse_command_line(argv)
153 153
154 154
155 155 def start(self):
156 156 """Start the app mainloop.
157 157
158 158 Override in subclasses.
159 159 """
160 160 if self.subapp is not None:
161 161 return self.subapp.start()
162 162
163 163 def _log_level_changed(self, name, old, new):
164 164 """Adjust the log level when log_level is set."""
165 165 self.log.setLevel(new)
166 166
167 167 def print_alias_help(self):
168 168 """print the alias part of the help"""
169 169 if not self.aliases:
170 170 return
171 171
172 172 lines = ['Aliases']
173 173 lines.append('-'*len(lines[0]))
174 174 lines.append(self.alias_description)
175 175 lines.append('')
176 176
177 177 classdict = {}
178 for c in self.classes:
179 classdict[c.__name__] = c
178 for cls in self.classes:
179 # include all parents in available names
180 for c in cls.mro():
181 classdict[c.__name__] = c
180 182
181 183 for alias, longname in self.aliases.iteritems():
182 184 classname, traitname = longname.split('.',1)
183 185 cls = classdict[classname]
184 186
185 187 trait = cls.class_traits(config=True)[traitname]
186 188 help = cls.class_get_trait_help(trait)
187 189 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
188 190 lines.append(help)
189 191 lines.append('')
190 192 print '\n'.join(lines)
191 193
192 194 def print_flag_help(self):
193 195 """print the flag part of the help"""
194 196 if not self.flags:
195 197 return
196 198
197 199 lines = ['Flags']
198 200 lines.append('-'*len(lines[0]))
199 201 lines.append(self.flag_description)
200 202 lines.append('')
201 203
202 204 for m, (cfg,help) in self.flags.iteritems():
203 205 lines.append('--'+m)
204 206 lines.append(indent(help, flatten=True))
205 207 lines.append('')
206 208 print '\n'.join(lines)
207 209
208 210 def print_subcommands(self):
209 211 """print the subcommand part of the help"""
210 212 if not self.subcommands:
211 213 return
212 214
213 215 lines = ["Subcommands"]
214 216 lines.append('-'*len(lines[0]))
215 for subc, cls,help in self.subcommands:
217 for subc, (cls,help) in self.subcommands.iteritems():
216 218 lines.append("%s : %s"%(subc, cls))
217 219 if help:
218 220 lines.append(indent(help, flatten=True))
219 221 lines.append('')
220 222 print '\n'.join(lines)
221 223
222 224 def print_help(self, classes=False):
223 225 """Print the help for each Configurable class in self.classes.
224 226
225 227 If classes=False (the default), only flags and aliases are printed
226 228 """
227 229 self.print_flag_help()
228 230 self.print_alias_help()
229 231
230 232 if classes:
231 233 if self.classes:
232 234 print "Class parameters"
233 235 print "----------------"
234 236 print self.keyvalue_description
235 237 print
236 238
237 239 for cls in self.classes:
238 240 cls.class_print_help()
239 241 print
240 242 else:
241 243 print "To see all available configurables, use `--help-all`"
242 244 print
243 245
244 246 def print_description(self):
245 247 """Print the application description."""
246 248 print self.description
247 249 print
248 250
249 251 def print_version(self):
250 252 """Print the version string."""
251 253 print self.version
252 254
253 255 def update_config(self, config):
254 256 """Fire the traits events when the config is updated."""
255 257 # Save a copy of the current config.
256 258 newconfig = deepcopy(self.config)
257 259 # Merge the new config into the current one.
258 260 newconfig._merge(config)
259 261 # Save the combined config as self.config, which triggers the traits
260 262 # events.
261 263 self.config = newconfig
262 264
263 265 def initialize_subcommand(self, subc, argv=None):
264 266 """Initialize a subcommand with argv"""
265 267 if '-h' in subc:
266 268 # requested help
267 269 self.print_description()
268 270 self.print_subcommands()
269 271 self.exit(0)
270 subapp = self.subcommands.get(subc, None)
272 subapp,help = self.subcommands.get(subc, (None,None))
271 273 if subapp is None:
272 274 self.print_description()
273 275 print "No such subcommand: %r"%subc
274 276 print
275 277 self.print_subcommands()
276 278 self.exit(1)
277
279
278 280 if isinstance(subapp, basestring):
279 281 subapp = import_item(subapp)
280 282
281 283 # instantiate
282 284 self.subapp = subapp()
283 285 # and initialize subapp
284 286 self.subapp.initialize(argv)
285 287
286 288 def parse_command_line(self, argv=None):
287 289 """Parse the command line arguments."""
288 290 argv = sys.argv[1:] if argv is None else argv
289 291
290 292 if self.subcommands:
291 293 # we have subcommands
292 294 if len(argv) == 0:
293 295 # none specified
294 296 self.print_description()
295 297 self.print_subcommands()
296 298 self.exit(1)
297 299
298 300 return self.initialize_subcommand(argv[0], argv[1:])
299 301
300 302 if '-h' in argv or '--help' in argv or '--help-all' in argv:
301 303 self.print_description()
302 304 self.print_help('--help-all' in argv)
303 305 self.exit(0)
304 306
305 307 if '--version' in argv:
306 308 self.print_version()
307 309 self.exit(0)
308 310
309 311 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
310 312 flags=self.flags)
311 config = loader.load_config()
313 try:
314 config = loader.load_config()
315 except ArgumentError as e:
316 self.log.fatal(str(e))
317 self.print_description()
318 self.print_help()
319 self.exit(1)
312 320 self.update_config(config)
313 321
314 322 def load_config_file(self, filename, path=None):
315 323 """Load a .py based config file by filename and path."""
316 324 loader = PyFileConfigLoader(filename, path=path)
317 325 config = loader.load_config()
318 326 self.update_config(config)
319 327
320 328 def exit(self, exit_status=0):
321 329 self.log.debug("Exiting application: %s" % self.name)
322 330 sys.exit(exit_status)
323 331
324 332 #-----------------------------------------------------------------------------
325 333 # utility functions, for convenience
326 334 #-----------------------------------------------------------------------------
327 335
328 336 def boolean_flag(name, configurable, set_help='', unset_help=''):
329 337 """helper for building basic --trait, --no-trait flags
330 338
331 339 Parameters
332 340 ----------
333 341
334 342 name : str
335 343 The name of the flag.
336 344 configurable : str
337 345 The 'Class.trait' string of the trait to be set/unset with the flag
338 346 set_help : unicode
339 347 help string for --name flag
340 348 unset_help : unicode
341 349 help string for --no-name flag
342 350
343 351 Returns
344 352 -------
345 353
346 354 cfg : dict
347 355 A dict with two keys: 'name', and 'no-name', for setting and unsetting
348 356 the trait, respectively.
349 357 """
350 358 # default helpstrings
351 359 set_help = set_help or "set %s=True"%configurable
352 360 unset_help = unset_help or "set %s=False"%configurable
353 361
354 362 cls,trait = configurable.split('.')
355 363
356 364 setter = Config()
357 365 setter[cls][trait] = True
358 366 unsetter = Config()
359 367 unsetter[cls][trait] = False
360 368 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
@@ -1,501 +1,504 b''
1 1 """A simple configuration system.
2 2
3 3 Authors
4 4 -------
5 5 * Brian Granger
6 6 * Fernando Perez
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2009 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import __builtin__
21 21 import re
22 22 import sys
23 23
24 24 from IPython.external import argparse
25 25 from IPython.utils.path import filefind
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Exceptions
29 29 #-----------------------------------------------------------------------------
30 30
31 31
32 32 class ConfigError(Exception):
33 33 pass
34 34
35 35
36 36 class ConfigLoaderError(ConfigError):
37 37 pass
38 38
39 class ArgumentError(ConfigLoaderError):
40 pass
41
39 42 #-----------------------------------------------------------------------------
40 43 # Argparse fix
41 44 #-----------------------------------------------------------------------------
42 45
43 46 # Unfortunately argparse by default prints help messages to stderr instead of
44 47 # stdout. This makes it annoying to capture long help screens at the command
45 48 # line, since one must know how to pipe stderr, which many users don't know how
46 49 # to do. So we override the print_help method with one that defaults to
47 50 # stdout and use our class instead.
48 51
49 52 class ArgumentParser(argparse.ArgumentParser):
50 53 """Simple argparse subclass that prints help to stdout by default."""
51 54
52 55 def print_help(self, file=None):
53 56 if file is None:
54 57 file = sys.stdout
55 58 return super(ArgumentParser, self).print_help(file)
56 59
57 60 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
58 61
59 62 #-----------------------------------------------------------------------------
60 63 # Config class for holding config information
61 64 #-----------------------------------------------------------------------------
62 65
63 66
64 67 class Config(dict):
65 68 """An attribute based dict that can do smart merges."""
66 69
67 70 def __init__(self, *args, **kwds):
68 71 dict.__init__(self, *args, **kwds)
69 72 # This sets self.__dict__ = self, but it has to be done this way
70 73 # because we are also overriding __setattr__.
71 74 dict.__setattr__(self, '__dict__', self)
72 75
73 76 def _merge(self, other):
74 77 to_update = {}
75 78 for k, v in other.iteritems():
76 79 if not self.has_key(k):
77 80 to_update[k] = v
78 81 else: # I have this key
79 82 if isinstance(v, Config):
80 83 # Recursively merge common sub Configs
81 84 self[k]._merge(v)
82 85 else:
83 86 # Plain updates for non-Configs
84 87 to_update[k] = v
85 88
86 89 self.update(to_update)
87 90
88 91 def _is_section_key(self, key):
89 92 if key[0].upper()==key[0] and not key.startswith('_'):
90 93 return True
91 94 else:
92 95 return False
93 96
94 97 def __contains__(self, key):
95 98 if self._is_section_key(key):
96 99 return True
97 100 else:
98 101 return super(Config, self).__contains__(key)
99 102 # .has_key is deprecated for dictionaries.
100 103 has_key = __contains__
101 104
102 105 def _has_section(self, key):
103 106 if self._is_section_key(key):
104 107 if super(Config, self).__contains__(key):
105 108 return True
106 109 return False
107 110
108 111 def copy(self):
109 112 return type(self)(dict.copy(self))
110 113
111 114 def __copy__(self):
112 115 return self.copy()
113 116
114 117 def __deepcopy__(self, memo):
115 118 import copy
116 119 return type(self)(copy.deepcopy(self.items()))
117 120
118 121 def __getitem__(self, key):
119 122 # We cannot use directly self._is_section_key, because it triggers
120 123 # infinite recursion on top of PyPy. Instead, we manually fish the
121 124 # bound method.
122 125 is_section_key = self.__class__._is_section_key.__get__(self)
123 126
124 127 # Because we use this for an exec namespace, we need to delegate
125 128 # the lookup of names in __builtin__ to itself. This means
126 129 # that you can't have section or attribute names that are
127 130 # builtins.
128 131 try:
129 132 return getattr(__builtin__, key)
130 133 except AttributeError:
131 134 pass
132 135 if is_section_key(key):
133 136 try:
134 137 return dict.__getitem__(self, key)
135 138 except KeyError:
136 139 c = Config()
137 140 dict.__setitem__(self, key, c)
138 141 return c
139 142 else:
140 143 return dict.__getitem__(self, key)
141 144
142 145 def __setitem__(self, key, value):
143 146 # Don't allow names in __builtin__ to be modified.
144 147 if hasattr(__builtin__, key):
145 148 raise ConfigError('Config variable names cannot have the same name '
146 149 'as a Python builtin: %s' % key)
147 150 if self._is_section_key(key):
148 151 if not isinstance(value, Config):
149 152 raise ValueError('values whose keys begin with an uppercase '
150 153 'char must be Config instances: %r, %r' % (key, value))
151 154 else:
152 155 dict.__setitem__(self, key, value)
153 156
154 157 def __getattr__(self, key):
155 158 try:
156 159 return self.__getitem__(key)
157 160 except KeyError, e:
158 161 raise AttributeError(e)
159 162
160 163 def __setattr__(self, key, value):
161 164 try:
162 165 self.__setitem__(key, value)
163 166 except KeyError, e:
164 167 raise AttributeError(e)
165 168
166 169 def __delattr__(self, key):
167 170 try:
168 171 dict.__delitem__(self, key)
169 172 except KeyError, e:
170 173 raise AttributeError(e)
171 174
172 175
173 176 #-----------------------------------------------------------------------------
174 177 # Config loading classes
175 178 #-----------------------------------------------------------------------------
176 179
177 180
178 181 class ConfigLoader(object):
179 182 """A object for loading configurations from just about anywhere.
180 183
181 184 The resulting configuration is packaged as a :class:`Struct`.
182 185
183 186 Notes
184 187 -----
185 188 A :class:`ConfigLoader` does one thing: load a config from a source
186 189 (file, command line arguments) and returns the data as a :class:`Struct`.
187 190 There are lots of things that :class:`ConfigLoader` does not do. It does
188 191 not implement complex logic for finding config files. It does not handle
189 192 default values or merge multiple configs. These things need to be
190 193 handled elsewhere.
191 194 """
192 195
193 196 def __init__(self):
194 197 """A base class for config loaders.
195 198
196 199 Examples
197 200 --------
198 201
199 202 >>> cl = ConfigLoader()
200 203 >>> config = cl.load_config()
201 204 >>> config
202 205 {}
203 206 """
204 207 self.clear()
205 208
206 209 def clear(self):
207 210 self.config = Config()
208 211
209 212 def load_config(self):
210 213 """Load a config from somewhere, return a :class:`Config` instance.
211 214
212 215 Usually, this will cause self.config to be set and then returned.
213 216 However, in most cases, :meth:`ConfigLoader.clear` should be called
214 217 to erase any previous state.
215 218 """
216 219 self.clear()
217 220 return self.config
218 221
219 222
220 223 class FileConfigLoader(ConfigLoader):
221 224 """A base class for file based configurations.
222 225
223 226 As we add more file based config loaders, the common logic should go
224 227 here.
225 228 """
226 229 pass
227 230
228 231
229 232 class PyFileConfigLoader(FileConfigLoader):
230 233 """A config loader for pure python files.
231 234
232 235 This calls execfile on a plain python file and looks for attributes
233 236 that are all caps. These attribute are added to the config Struct.
234 237 """
235 238
236 239 def __init__(self, filename, path=None):
237 240 """Build a config loader for a filename and path.
238 241
239 242 Parameters
240 243 ----------
241 244 filename : str
242 245 The file name of the config file.
243 246 path : str, list, tuple
244 247 The path to search for the config file on, or a sequence of
245 248 paths to try in order.
246 249 """
247 250 super(PyFileConfigLoader, self).__init__()
248 251 self.filename = filename
249 252 self.path = path
250 253 self.full_filename = ''
251 254 self.data = None
252 255
253 256 def load_config(self):
254 257 """Load the config from a file and return it as a Struct."""
255 258 self.clear()
256 259 self._find_file()
257 260 self._read_file_as_dict()
258 261 self._convert_to_config()
259 262 return self.config
260 263
261 264 def _find_file(self):
262 265 """Try to find the file by searching the paths."""
263 266 self.full_filename = filefind(self.filename, self.path)
264 267
265 268 def _read_file_as_dict(self):
266 269 """Load the config file into self.config, with recursive loading."""
267 270 # This closure is made available in the namespace that is used
268 271 # to exec the config file. This allows users to call
269 272 # load_subconfig('myconfig.py') to load config files recursively.
270 273 # It needs to be a closure because it has references to self.path
271 274 # and self.config. The sub-config is loaded with the same path
272 275 # as the parent, but it uses an empty config which is then merged
273 276 # with the parents.
274 277 def load_subconfig(fname):
275 278 loader = PyFileConfigLoader(fname, self.path)
276 279 try:
277 280 sub_config = loader.load_config()
278 281 except IOError:
279 282 # Pass silently if the sub config is not there. This happens
280 283 # when a user us using a profile, but not the default config.
281 284 pass
282 285 else:
283 286 self.config._merge(sub_config)
284 287
285 288 # Again, this needs to be a closure and should be used in config
286 289 # files to get the config being loaded.
287 290 def get_config():
288 291 return self.config
289 292
290 293 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
291 294 fs_encoding = sys.getfilesystemencoding() or 'ascii'
292 295 conf_filename = self.full_filename.encode(fs_encoding)
293 296 execfile(conf_filename, namespace)
294 297
295 298 def _convert_to_config(self):
296 299 if self.data is None:
297 300 ConfigLoaderError('self.data does not exist')
298 301
299 302
300 303 class CommandLineConfigLoader(ConfigLoader):
301 304 """A config loader for command line arguments.
302 305
303 306 As we add more command line based loaders, the common logic should go
304 307 here.
305 308 """
306 309
307 310 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
308 311 flag_pattern = re.compile(r'\-\-\w+(\-\w)*')
309 312
310 313 class KeyValueConfigLoader(CommandLineConfigLoader):
311 314 """A config loader that loads key value pairs from the command line.
312 315
313 316 This allows command line options to be gives in the following form::
314 317
315 318 ipython Global.profile="foo" InteractiveShell.autocall=False
316 319 """
317 320
318 321 def __init__(self, argv=None, aliases=None, flags=None):
319 322 """Create a key value pair config loader.
320 323
321 324 Parameters
322 325 ----------
323 326 argv : list
324 327 A list that has the form of sys.argv[1:] which has unicode
325 328 elements of the form u"key=value". If this is None (default),
326 329 then sys.argv[1:] will be used.
327 330 aliases : dict
328 331 A dict of aliases for configurable traits.
329 332 Keys are the short aliases, Values are the resolved trait.
330 333 Of the form: `{'alias' : 'Configurable.trait'}`
331 334 flags : dict
332 335 A dict of flags, keyed by str name. Vaues can be Config objects,
333 336 dicts, or "key=value" strings. If Config or dict, when the flag
334 337 is triggered, The flag is loaded as `self.config.update(m)`.
335 338
336 339 Returns
337 340 -------
338 341 config : Config
339 342 The resulting Config object.
340 343
341 344 Examples
342 345 --------
343 346
344 347 >>> from IPython.config.loader import KeyValueConfigLoader
345 348 >>> cl = KeyValueConfigLoader()
346 349 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
347 350 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
348 351 """
349 352 if argv is None:
350 353 argv = sys.argv[1:]
351 354 self.argv = argv
352 355 self.aliases = aliases or {}
353 356 self.flags = flags or {}
354 357
355 358 def load_config(self, argv=None, aliases=None, flags=None):
356 359 """Parse the configuration and generate the Config object.
357 360
358 361 Parameters
359 362 ----------
360 363 argv : list, optional
361 364 A list that has the form of sys.argv[1:] which has unicode
362 365 elements of the form u"key=value". If this is None (default),
363 366 then self.argv will be used.
364 367 aliases : dict
365 368 A dict of aliases for configurable traits.
366 369 Keys are the short aliases, Values are the resolved trait.
367 370 Of the form: `{'alias' : 'Configurable.trait'}`
368 371 flags : dict
369 372 A dict of flags, keyed by str name. Values can be Config objects
370 373 or dicts. When the flag is triggered, The config is loaded as
371 374 `self.config.update(cfg)`.
372 375 """
373 376 from IPython.config.configurable import Configurable
374 377
375 378 self.clear()
376 379 if argv is None:
377 380 argv = self.argv
378 381 if aliases is None:
379 382 aliases = self.aliases
380 383 if flags is None:
381 384 flags = self.flags
382 385
383 386 for item in argv:
384 387 if kv_pattern.match(item):
385 388 lhs,rhs = item.split('=',1)
386 389 # Substitute longnames for aliases.
387 390 if lhs in aliases:
388 391 lhs = aliases[lhs]
389 392 exec_str = 'self.config.' + lhs + '=' + rhs
390 393 try:
391 394 # Try to see if regular Python syntax will work. This
392 395 # won't handle strings as the quote marks are removed
393 396 # by the system shell.
394 397 exec exec_str in locals(), globals()
395 398 except (NameError, SyntaxError):
396 399 # This case happens if the rhs is a string but without
397 400 # the quote marks. We add the quote marks and see if
398 401 # it succeeds. If it still fails, we let it raise.
399 402 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
400 403 exec exec_str in locals(), globals()
401 404 elif flag_pattern.match(item):
402 405 # trim leading '--'
403 406 m = item[2:]
404 407 cfg,_ = flags.get(m, (None,None))
405 408 if cfg is None:
406 raise ValueError("Unrecognized flag: %r"%item)
409 raise ArgumentError("Unrecognized flag: %r"%item)
407 410 elif isinstance(cfg, (dict, Config)):
408 411 # update self.config with Config:
409 412 self.config.update(cfg)
410 413 else:
411 414 raise ValueError("Invalid flag: %r"%flag)
412 415 else:
413 raise ValueError("Invalid argument: %r"%item)
416 raise ArgumentError("Invalid argument: %r"%item)
414 417 return self.config
415 418
416 419 class ArgParseConfigLoader(CommandLineConfigLoader):
417 420 """A loader that uses the argparse module to load from the command line."""
418 421
419 422 def __init__(self, argv=None, *parser_args, **parser_kw):
420 423 """Create a config loader for use with argparse.
421 424
422 425 Parameters
423 426 ----------
424 427
425 428 argv : optional, list
426 429 If given, used to read command-line arguments from, otherwise
427 430 sys.argv[1:] is used.
428 431
429 432 parser_args : tuple
430 433 A tuple of positional arguments that will be passed to the
431 434 constructor of :class:`argparse.ArgumentParser`.
432 435
433 436 parser_kw : dict
434 437 A tuple of keyword arguments that will be passed to the
435 438 constructor of :class:`argparse.ArgumentParser`.
436 439
437 440 Returns
438 441 -------
439 442 config : Config
440 443 The resulting Config object.
441 444 """
442 445 super(CommandLineConfigLoader, self).__init__()
443 446 if argv == None:
444 447 argv = sys.argv[1:]
445 448 self.argv = argv
446 449 self.parser_args = parser_args
447 450 self.version = parser_kw.pop("version", None)
448 451 kwargs = dict(argument_default=argparse.SUPPRESS)
449 452 kwargs.update(parser_kw)
450 453 self.parser_kw = kwargs
451 454
452 455 def load_config(self, argv=None):
453 456 """Parse command line arguments and return as a Config object.
454 457
455 458 Parameters
456 459 ----------
457 460
458 461 args : optional, list
459 462 If given, a list with the structure of sys.argv[1:] to parse
460 463 arguments from. If not given, the instance's self.argv attribute
461 464 (given at construction time) is used."""
462 465 self.clear()
463 466 if argv is None:
464 467 argv = self.argv
465 468 self._create_parser()
466 469 self._parse_args(argv)
467 470 self._convert_to_config()
468 471 return self.config
469 472
470 473 def get_extra_args(self):
471 474 if hasattr(self, 'extra_args'):
472 475 return self.extra_args
473 476 else:
474 477 return []
475 478
476 479 def _create_parser(self):
477 480 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
478 481 self._add_arguments()
479 482
480 483 def _add_arguments(self):
481 484 raise NotImplementedError("subclasses must implement _add_arguments")
482 485
483 486 def _parse_args(self, args):
484 487 """self.parser->self.parsed_data"""
485 488 # decode sys.argv to support unicode command-line options
486 489 uargs = []
487 490 for a in args:
488 491 if isinstance(a, str):
489 492 # don't decode if we already got unicode
490 493 a = a.decode(sys.stdin.encoding or
491 494 sys.getdefaultencoding())
492 495 uargs.append(a)
493 496 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
494 497
495 498 def _convert_to_config(self):
496 499 """self.parsed_data->self.config"""
497 500 for k, v in vars(self.parsed_data).iteritems():
498 501 exec_str = 'self.config.' + k + '= v'
499 502 exec exec_str in locals(), globals()
500 503
501 504
General Comments 0
You need to be logged in to leave comments. Login now