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