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