##// END OF EJS Templates
Import argparse directly from stdlib
Thomas Kluyver -
Show More
@@ -1,718 +1,718 b''
1 """A simple configuration system.
1 """A simple configuration system.
2
2
3 Inheritance diagram:
3 Inheritance diagram:
4
4
5 .. inheritance-diagram:: IPython.config.loader
5 .. inheritance-diagram:: IPython.config.loader
6 :parts: 3
6 :parts: 3
7
7
8 Authors
8 Authors
9 -------
9 -------
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min RK
12 * Min RK
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 import __builtin__ as builtin_mod
26 import __builtin__ as builtin_mod
27 import argparse
27 import os
28 import os
28 import re
29 import re
29 import sys
30 import sys
30
31
31 from IPython.external import argparse
32 from IPython.utils.path import filefind, get_ipython_dir
32 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils import py3compat, warn
33 from IPython.utils import py3compat, warn
34 from IPython.utils.encoding import DEFAULT_ENCODING
34 from IPython.utils.encoding import DEFAULT_ENCODING
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Exceptions
37 # Exceptions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40
40
41 class ConfigError(Exception):
41 class ConfigError(Exception):
42 pass
42 pass
43
43
44 class ConfigLoaderError(ConfigError):
44 class ConfigLoaderError(ConfigError):
45 pass
45 pass
46
46
47 class ConfigFileNotFound(ConfigError):
47 class ConfigFileNotFound(ConfigError):
48 pass
48 pass
49
49
50 class ArgumentError(ConfigLoaderError):
50 class ArgumentError(ConfigLoaderError):
51 pass
51 pass
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Argparse fix
54 # Argparse fix
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 # Unfortunately argparse by default prints help messages to stderr instead of
57 # Unfortunately argparse by default prints help messages to stderr instead of
58 # stdout. This makes it annoying to capture long help screens at the command
58 # stdout. This makes it annoying to capture long help screens at the command
59 # line, since one must know how to pipe stderr, which many users don't know how
59 # line, since one must know how to pipe stderr, which many users don't know how
60 # to do. So we override the print_help method with one that defaults to
60 # to do. So we override the print_help method with one that defaults to
61 # stdout and use our class instead.
61 # stdout and use our class instead.
62
62
63 class ArgumentParser(argparse.ArgumentParser):
63 class ArgumentParser(argparse.ArgumentParser):
64 """Simple argparse subclass that prints help to stdout by default."""
64 """Simple argparse subclass that prints help to stdout by default."""
65
65
66 def print_help(self, file=None):
66 def print_help(self, file=None):
67 if file is None:
67 if file is None:
68 file = sys.stdout
68 file = sys.stdout
69 return super(ArgumentParser, self).print_help(file)
69 return super(ArgumentParser, self).print_help(file)
70
70
71 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
71 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Config class for holding config information
74 # Config class for holding config information
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77
77
78 class Config(dict):
78 class Config(dict):
79 """An attribute based dict that can do smart merges."""
79 """An attribute based dict that can do smart merges."""
80
80
81 def __init__(self, *args, **kwds):
81 def __init__(self, *args, **kwds):
82 dict.__init__(self, *args, **kwds)
82 dict.__init__(self, *args, **kwds)
83 # This sets self.__dict__ = self, but it has to be done this way
83 # This sets self.__dict__ = self, but it has to be done this way
84 # because we are also overriding __setattr__.
84 # because we are also overriding __setattr__.
85 dict.__setattr__(self, '__dict__', self)
85 dict.__setattr__(self, '__dict__', self)
86 self._ensure_subconfig()
86 self._ensure_subconfig()
87
87
88 def _ensure_subconfig(self):
88 def _ensure_subconfig(self):
89 """ensure that sub-dicts that should be Config objects are
89 """ensure that sub-dicts that should be Config objects are
90
90
91 casts dicts that are under section keys to Config objects,
91 casts dicts that are under section keys to Config objects,
92 which is necessary for constructing Config objects from dict literals.
92 which is necessary for constructing Config objects from dict literals.
93 """
93 """
94 for key in self:
94 for key in self:
95 obj = self[key]
95 obj = self[key]
96 if self._is_section_key(key) \
96 if self._is_section_key(key) \
97 and isinstance(obj, dict) \
97 and isinstance(obj, dict) \
98 and not isinstance(obj, Config):
98 and not isinstance(obj, Config):
99 dict.__setattr__(self, key, Config(obj))
99 dict.__setattr__(self, key, Config(obj))
100
100
101 def _merge(self, other):
101 def _merge(self, other):
102 """deprecated alias, use Config.merge()"""
102 """deprecated alias, use Config.merge()"""
103 self.merge(other)
103 self.merge(other)
104
104
105 def merge(self, other):
105 def merge(self, other):
106 """merge another config object into this one"""
106 """merge another config object into this one"""
107 to_update = {}
107 to_update = {}
108 for k, v in other.iteritems():
108 for k, v in other.iteritems():
109 if k not in self:
109 if k not in self:
110 to_update[k] = v
110 to_update[k] = v
111 else: # I have this key
111 else: # I have this key
112 if isinstance(v, Config) and isinstance(self[k], Config):
112 if isinstance(v, Config) and isinstance(self[k], Config):
113 # Recursively merge common sub Configs
113 # Recursively merge common sub Configs
114 self[k].merge(v)
114 self[k].merge(v)
115 else:
115 else:
116 # Plain updates for non-Configs
116 # Plain updates for non-Configs
117 to_update[k] = v
117 to_update[k] = v
118
118
119 self.update(to_update)
119 self.update(to_update)
120
120
121 def _is_section_key(self, key):
121 def _is_section_key(self, key):
122 if key[0].upper()==key[0] and not key.startswith('_'):
122 if key[0].upper()==key[0] and not key.startswith('_'):
123 return True
123 return True
124 else:
124 else:
125 return False
125 return False
126
126
127 def __contains__(self, key):
127 def __contains__(self, key):
128 if self._is_section_key(key):
128 if self._is_section_key(key):
129 return True
129 return True
130 else:
130 else:
131 return super(Config, self).__contains__(key)
131 return super(Config, self).__contains__(key)
132 # .has_key is deprecated for dictionaries.
132 # .has_key is deprecated for dictionaries.
133 has_key = __contains__
133 has_key = __contains__
134
134
135 def _has_section(self, key):
135 def _has_section(self, key):
136 if self._is_section_key(key):
136 if self._is_section_key(key):
137 if super(Config, self).__contains__(key):
137 if super(Config, self).__contains__(key):
138 return True
138 return True
139 return False
139 return False
140
140
141 def copy(self):
141 def copy(self):
142 return type(self)(dict.copy(self))
142 return type(self)(dict.copy(self))
143
143
144 def __copy__(self):
144 def __copy__(self):
145 return self.copy()
145 return self.copy()
146
146
147 def __deepcopy__(self, memo):
147 def __deepcopy__(self, memo):
148 import copy
148 import copy
149 return type(self)(copy.deepcopy(self.items()))
149 return type(self)(copy.deepcopy(self.items()))
150
150
151 def __getitem__(self, key):
151 def __getitem__(self, key):
152 # We cannot use directly self._is_section_key, because it triggers
152 # We cannot use directly self._is_section_key, because it triggers
153 # infinite recursion on top of PyPy. Instead, we manually fish the
153 # infinite recursion on top of PyPy. Instead, we manually fish the
154 # bound method.
154 # bound method.
155 is_section_key = self.__class__._is_section_key.__get__(self)
155 is_section_key = self.__class__._is_section_key.__get__(self)
156
156
157 # Because we use this for an exec namespace, we need to delegate
157 # Because we use this for an exec namespace, we need to delegate
158 # the lookup of names in __builtin__ to itself. This means
158 # the lookup of names in __builtin__ to itself. This means
159 # that you can't have section or attribute names that are
159 # that you can't have section or attribute names that are
160 # builtins.
160 # builtins.
161 try:
161 try:
162 return getattr(builtin_mod, key)
162 return getattr(builtin_mod, key)
163 except AttributeError:
163 except AttributeError:
164 pass
164 pass
165 if is_section_key(key):
165 if is_section_key(key):
166 try:
166 try:
167 return dict.__getitem__(self, key)
167 return dict.__getitem__(self, key)
168 except KeyError:
168 except KeyError:
169 c = Config()
169 c = Config()
170 dict.__setitem__(self, key, c)
170 dict.__setitem__(self, key, c)
171 return c
171 return c
172 else:
172 else:
173 return dict.__getitem__(self, key)
173 return dict.__getitem__(self, key)
174
174
175 def __setitem__(self, key, value):
175 def __setitem__(self, key, value):
176 if self._is_section_key(key):
176 if self._is_section_key(key):
177 if not isinstance(value, Config):
177 if not isinstance(value, Config):
178 raise ValueError('values whose keys begin with an uppercase '
178 raise ValueError('values whose keys begin with an uppercase '
179 'char must be Config instances: %r, %r' % (key, value))
179 'char must be Config instances: %r, %r' % (key, value))
180 else:
180 else:
181 dict.__setitem__(self, key, value)
181 dict.__setitem__(self, key, value)
182
182
183 def __getattr__(self, key):
183 def __getattr__(self, key):
184 try:
184 try:
185 return self.__getitem__(key)
185 return self.__getitem__(key)
186 except KeyError as e:
186 except KeyError as e:
187 raise AttributeError(e)
187 raise AttributeError(e)
188
188
189 def __setattr__(self, key, value):
189 def __setattr__(self, key, value):
190 try:
190 try:
191 self.__setitem__(key, value)
191 self.__setitem__(key, value)
192 except KeyError as e:
192 except KeyError as e:
193 raise AttributeError(e)
193 raise AttributeError(e)
194
194
195 def __delattr__(self, key):
195 def __delattr__(self, key):
196 try:
196 try:
197 dict.__delitem__(self, key)
197 dict.__delitem__(self, key)
198 except KeyError as e:
198 except KeyError as e:
199 raise AttributeError(e)
199 raise AttributeError(e)
200
200
201
201
202 #-----------------------------------------------------------------------------
202 #-----------------------------------------------------------------------------
203 # Config loading classes
203 # Config loading classes
204 #-----------------------------------------------------------------------------
204 #-----------------------------------------------------------------------------
205
205
206
206
207 class ConfigLoader(object):
207 class ConfigLoader(object):
208 """A object for loading configurations from just about anywhere.
208 """A object for loading configurations from just about anywhere.
209
209
210 The resulting configuration is packaged as a :class:`Struct`.
210 The resulting configuration is packaged as a :class:`Struct`.
211
211
212 Notes
212 Notes
213 -----
213 -----
214 A :class:`ConfigLoader` does one thing: load a config from a source
214 A :class:`ConfigLoader` does one thing: load a config from a source
215 (file, command line arguments) and returns the data as a :class:`Struct`.
215 (file, command line arguments) and returns the data as a :class:`Struct`.
216 There are lots of things that :class:`ConfigLoader` does not do. It does
216 There are lots of things that :class:`ConfigLoader` does not do. It does
217 not implement complex logic for finding config files. It does not handle
217 not implement complex logic for finding config files. It does not handle
218 default values or merge multiple configs. These things need to be
218 default values or merge multiple configs. These things need to be
219 handled elsewhere.
219 handled elsewhere.
220 """
220 """
221
221
222 def __init__(self):
222 def __init__(self):
223 """A base class for config loaders.
223 """A base class for config loaders.
224
224
225 Examples
225 Examples
226 --------
226 --------
227
227
228 >>> cl = ConfigLoader()
228 >>> cl = ConfigLoader()
229 >>> config = cl.load_config()
229 >>> config = cl.load_config()
230 >>> config
230 >>> config
231 {}
231 {}
232 """
232 """
233 self.clear()
233 self.clear()
234
234
235 def clear(self):
235 def clear(self):
236 self.config = Config()
236 self.config = Config()
237
237
238 def load_config(self):
238 def load_config(self):
239 """Load a config from somewhere, return a :class:`Config` instance.
239 """Load a config from somewhere, return a :class:`Config` instance.
240
240
241 Usually, this will cause self.config to be set and then returned.
241 Usually, this will cause self.config to be set and then returned.
242 However, in most cases, :meth:`ConfigLoader.clear` should be called
242 However, in most cases, :meth:`ConfigLoader.clear` should be called
243 to erase any previous state.
243 to erase any previous state.
244 """
244 """
245 self.clear()
245 self.clear()
246 return self.config
246 return self.config
247
247
248
248
249 class FileConfigLoader(ConfigLoader):
249 class FileConfigLoader(ConfigLoader):
250 """A base class for file based configurations.
250 """A base class for file based configurations.
251
251
252 As we add more file based config loaders, the common logic should go
252 As we add more file based config loaders, the common logic should go
253 here.
253 here.
254 """
254 """
255 pass
255 pass
256
256
257
257
258 class PyFileConfigLoader(FileConfigLoader):
258 class PyFileConfigLoader(FileConfigLoader):
259 """A config loader for pure python files.
259 """A config loader for pure python files.
260
260
261 This calls execfile on a plain python file and looks for attributes
261 This calls execfile on a plain python file and looks for attributes
262 that are all caps. These attribute are added to the config Struct.
262 that are all caps. These attribute are added to the config Struct.
263 """
263 """
264
264
265 def __init__(self, filename, path=None):
265 def __init__(self, filename, path=None):
266 """Build a config loader for a filename and path.
266 """Build a config loader for a filename and path.
267
267
268 Parameters
268 Parameters
269 ----------
269 ----------
270 filename : str
270 filename : str
271 The file name of the config file.
271 The file name of the config file.
272 path : str, list, tuple
272 path : str, list, tuple
273 The path to search for the config file on, or a sequence of
273 The path to search for the config file on, or a sequence of
274 paths to try in order.
274 paths to try in order.
275 """
275 """
276 super(PyFileConfigLoader, self).__init__()
276 super(PyFileConfigLoader, self).__init__()
277 self.filename = filename
277 self.filename = filename
278 self.path = path
278 self.path = path
279 self.full_filename = ''
279 self.full_filename = ''
280 self.data = None
280 self.data = None
281
281
282 def load_config(self):
282 def load_config(self):
283 """Load the config from a file and return it as a Struct."""
283 """Load the config from a file and return it as a Struct."""
284 self.clear()
284 self.clear()
285 try:
285 try:
286 self._find_file()
286 self._find_file()
287 except IOError as e:
287 except IOError as e:
288 raise ConfigFileNotFound(str(e))
288 raise ConfigFileNotFound(str(e))
289 self._read_file_as_dict()
289 self._read_file_as_dict()
290 self._convert_to_config()
290 self._convert_to_config()
291 return self.config
291 return self.config
292
292
293 def _find_file(self):
293 def _find_file(self):
294 """Try to find the file by searching the paths."""
294 """Try to find the file by searching the paths."""
295 self.full_filename = filefind(self.filename, self.path)
295 self.full_filename = filefind(self.filename, self.path)
296
296
297 def _read_file_as_dict(self):
297 def _read_file_as_dict(self):
298 """Load the config file into self.config, with recursive loading."""
298 """Load the config file into self.config, with recursive loading."""
299 # This closure is made available in the namespace that is used
299 # This closure is made available in the namespace that is used
300 # to exec the config file. It allows users to call
300 # to exec the config file. It allows users to call
301 # load_subconfig('myconfig.py') to load config files recursively.
301 # load_subconfig('myconfig.py') to load config files recursively.
302 # It needs to be a closure because it has references to self.path
302 # It needs to be a closure because it has references to self.path
303 # and self.config. The sub-config is loaded with the same path
303 # and self.config. The sub-config is loaded with the same path
304 # as the parent, but it uses an empty config which is then merged
304 # as the parent, but it uses an empty config which is then merged
305 # with the parents.
305 # with the parents.
306
306
307 # If a profile is specified, the config file will be loaded
307 # If a profile is specified, the config file will be loaded
308 # from that profile
308 # from that profile
309
309
310 def load_subconfig(fname, profile=None):
310 def load_subconfig(fname, profile=None):
311 # import here to prevent circular imports
311 # import here to prevent circular imports
312 from IPython.core.profiledir import ProfileDir, ProfileDirError
312 from IPython.core.profiledir import ProfileDir, ProfileDirError
313 if profile is not None:
313 if profile is not None:
314 try:
314 try:
315 profile_dir = ProfileDir.find_profile_dir_by_name(
315 profile_dir = ProfileDir.find_profile_dir_by_name(
316 get_ipython_dir(),
316 get_ipython_dir(),
317 profile,
317 profile,
318 )
318 )
319 except ProfileDirError:
319 except ProfileDirError:
320 return
320 return
321 path = profile_dir.location
321 path = profile_dir.location
322 else:
322 else:
323 path = self.path
323 path = self.path
324 loader = PyFileConfigLoader(fname, path)
324 loader = PyFileConfigLoader(fname, path)
325 try:
325 try:
326 sub_config = loader.load_config()
326 sub_config = loader.load_config()
327 except ConfigFileNotFound:
327 except ConfigFileNotFound:
328 # Pass silently if the sub config is not there. This happens
328 # Pass silently if the sub config is not there. This happens
329 # when a user s using a profile, but not the default config.
329 # when a user s using a profile, but not the default config.
330 pass
330 pass
331 else:
331 else:
332 self.config.merge(sub_config)
332 self.config.merge(sub_config)
333
333
334 # Again, this needs to be a closure and should be used in config
334 # Again, this needs to be a closure and should be used in config
335 # files to get the config being loaded.
335 # files to get the config being loaded.
336 def get_config():
336 def get_config():
337 return self.config
337 return self.config
338
338
339 namespace = dict(
339 namespace = dict(
340 load_subconfig=load_subconfig,
340 load_subconfig=load_subconfig,
341 get_config=get_config,
341 get_config=get_config,
342 __file__=self.full_filename,
342 __file__=self.full_filename,
343 )
343 )
344 fs_encoding = sys.getfilesystemencoding() or 'ascii'
344 fs_encoding = sys.getfilesystemencoding() or 'ascii'
345 conf_filename = self.full_filename.encode(fs_encoding)
345 conf_filename = self.full_filename.encode(fs_encoding)
346 py3compat.execfile(conf_filename, namespace)
346 py3compat.execfile(conf_filename, namespace)
347
347
348 def _convert_to_config(self):
348 def _convert_to_config(self):
349 if self.data is None:
349 if self.data is None:
350 ConfigLoaderError('self.data does not exist')
350 ConfigLoaderError('self.data does not exist')
351
351
352
352
353 class CommandLineConfigLoader(ConfigLoader):
353 class CommandLineConfigLoader(ConfigLoader):
354 """A config loader for command line arguments.
354 """A config loader for command line arguments.
355
355
356 As we add more command line based loaders, the common logic should go
356 As we add more command line based loaders, the common logic should go
357 here.
357 here.
358 """
358 """
359
359
360 def _exec_config_str(self, lhs, rhs):
360 def _exec_config_str(self, lhs, rhs):
361 """execute self.config.<lhs> = <rhs>
361 """execute self.config.<lhs> = <rhs>
362
362
363 * expands ~ with expanduser
363 * expands ~ with expanduser
364 * tries to assign with raw eval, otherwise assigns with just the string,
364 * tries to assign with raw eval, otherwise assigns with just the string,
365 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
365 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
366 equivalent are `--C.a=4` and `--C.a='4'`.
366 equivalent are `--C.a=4` and `--C.a='4'`.
367 """
367 """
368 rhs = os.path.expanduser(rhs)
368 rhs = os.path.expanduser(rhs)
369 try:
369 try:
370 # Try to see if regular Python syntax will work. This
370 # Try to see if regular Python syntax will work. This
371 # won't handle strings as the quote marks are removed
371 # won't handle strings as the quote marks are removed
372 # by the system shell.
372 # by the system shell.
373 value = eval(rhs)
373 value = eval(rhs)
374 except (NameError, SyntaxError):
374 except (NameError, SyntaxError):
375 # This case happens if the rhs is a string.
375 # This case happens if the rhs is a string.
376 value = rhs
376 value = rhs
377
377
378 exec u'self.config.%s = value' % lhs
378 exec u'self.config.%s = value' % lhs
379
379
380 def _load_flag(self, cfg):
380 def _load_flag(self, cfg):
381 """update self.config from a flag, which can be a dict or Config"""
381 """update self.config from a flag, which can be a dict or Config"""
382 if isinstance(cfg, (dict, Config)):
382 if isinstance(cfg, (dict, Config)):
383 # don't clobber whole config sections, update
383 # don't clobber whole config sections, update
384 # each section from config:
384 # each section from config:
385 for sec,c in cfg.iteritems():
385 for sec,c in cfg.iteritems():
386 self.config[sec].update(c)
386 self.config[sec].update(c)
387 else:
387 else:
388 raise TypeError("Invalid flag: %r" % cfg)
388 raise TypeError("Invalid flag: %r" % cfg)
389
389
390 # raw --identifier=value pattern
390 # raw --identifier=value pattern
391 # but *also* accept '-' as wordsep, for aliases
391 # but *also* accept '-' as wordsep, for aliases
392 # accepts: --foo=a
392 # accepts: --foo=a
393 # --Class.trait=value
393 # --Class.trait=value
394 # --alias-name=value
394 # --alias-name=value
395 # rejects: -foo=value
395 # rejects: -foo=value
396 # --foo
396 # --foo
397 # --Class.trait
397 # --Class.trait
398 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
398 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
399
399
400 # just flags, no assignments, with two *or one* leading '-'
400 # just flags, no assignments, with two *or one* leading '-'
401 # accepts: --foo
401 # accepts: --foo
402 # -foo-bar-again
402 # -foo-bar-again
403 # rejects: --anything=anything
403 # rejects: --anything=anything
404 # --two.word
404 # --two.word
405
405
406 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
406 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
407
407
408 class KeyValueConfigLoader(CommandLineConfigLoader):
408 class KeyValueConfigLoader(CommandLineConfigLoader):
409 """A config loader that loads key value pairs from the command line.
409 """A config loader that loads key value pairs from the command line.
410
410
411 This allows command line options to be gives in the following form::
411 This allows command line options to be gives in the following form::
412
412
413 ipython --profile="foo" --InteractiveShell.autocall=False
413 ipython --profile="foo" --InteractiveShell.autocall=False
414 """
414 """
415
415
416 def __init__(self, argv=None, aliases=None, flags=None):
416 def __init__(self, argv=None, aliases=None, flags=None):
417 """Create a key value pair config loader.
417 """Create a key value pair config loader.
418
418
419 Parameters
419 Parameters
420 ----------
420 ----------
421 argv : list
421 argv : list
422 A list that has the form of sys.argv[1:] which has unicode
422 A list that has the form of sys.argv[1:] which has unicode
423 elements of the form u"key=value". If this is None (default),
423 elements of the form u"key=value". If this is None (default),
424 then sys.argv[1:] will be used.
424 then sys.argv[1:] will be used.
425 aliases : dict
425 aliases : dict
426 A dict of aliases for configurable traits.
426 A dict of aliases for configurable traits.
427 Keys are the short aliases, Values are the resolved trait.
427 Keys are the short aliases, Values are the resolved trait.
428 Of the form: `{'alias' : 'Configurable.trait'}`
428 Of the form: `{'alias' : 'Configurable.trait'}`
429 flags : dict
429 flags : dict
430 A dict of flags, keyed by str name. Vaues can be Config objects,
430 A dict of flags, keyed by str name. Vaues can be Config objects,
431 dicts, or "key=value" strings. If Config or dict, when the flag
431 dicts, or "key=value" strings. If Config or dict, when the flag
432 is triggered, The flag is loaded as `self.config.update(m)`.
432 is triggered, The flag is loaded as `self.config.update(m)`.
433
433
434 Returns
434 Returns
435 -------
435 -------
436 config : Config
436 config : Config
437 The resulting Config object.
437 The resulting Config object.
438
438
439 Examples
439 Examples
440 --------
440 --------
441
441
442 >>> from IPython.config.loader import KeyValueConfigLoader
442 >>> from IPython.config.loader import KeyValueConfigLoader
443 >>> cl = KeyValueConfigLoader()
443 >>> cl = KeyValueConfigLoader()
444 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
444 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
445 >>> sorted(d.items())
445 >>> sorted(d.items())
446 [('A', {'name': 'brian'}), ('B', {'number': 0})]
446 [('A', {'name': 'brian'}), ('B', {'number': 0})]
447 """
447 """
448 self.clear()
448 self.clear()
449 if argv is None:
449 if argv is None:
450 argv = sys.argv[1:]
450 argv = sys.argv[1:]
451 self.argv = argv
451 self.argv = argv
452 self.aliases = aliases or {}
452 self.aliases = aliases or {}
453 self.flags = flags or {}
453 self.flags = flags or {}
454
454
455
455
456 def clear(self):
456 def clear(self):
457 super(KeyValueConfigLoader, self).clear()
457 super(KeyValueConfigLoader, self).clear()
458 self.extra_args = []
458 self.extra_args = []
459
459
460
460
461 def _decode_argv(self, argv, enc=None):
461 def _decode_argv(self, argv, enc=None):
462 """decode argv if bytes, using stin.encoding, falling back on default enc"""
462 """decode argv if bytes, using stin.encoding, falling back on default enc"""
463 uargv = []
463 uargv = []
464 if enc is None:
464 if enc is None:
465 enc = DEFAULT_ENCODING
465 enc = DEFAULT_ENCODING
466 for arg in argv:
466 for arg in argv:
467 if not isinstance(arg, unicode):
467 if not isinstance(arg, unicode):
468 # only decode if not already decoded
468 # only decode if not already decoded
469 arg = arg.decode(enc)
469 arg = arg.decode(enc)
470 uargv.append(arg)
470 uargv.append(arg)
471 return uargv
471 return uargv
472
472
473
473
474 def load_config(self, argv=None, aliases=None, flags=None):
474 def load_config(self, argv=None, aliases=None, flags=None):
475 """Parse the configuration and generate the Config object.
475 """Parse the configuration and generate the Config object.
476
476
477 After loading, any arguments that are not key-value or
477 After loading, any arguments that are not key-value or
478 flags will be stored in self.extra_args - a list of
478 flags will be stored in self.extra_args - a list of
479 unparsed command-line arguments. This is used for
479 unparsed command-line arguments. This is used for
480 arguments such as input files or subcommands.
480 arguments such as input files or subcommands.
481
481
482 Parameters
482 Parameters
483 ----------
483 ----------
484 argv : list, optional
484 argv : list, optional
485 A list that has the form of sys.argv[1:] which has unicode
485 A list that has the form of sys.argv[1:] which has unicode
486 elements of the form u"key=value". If this is None (default),
486 elements of the form u"key=value". If this is None (default),
487 then self.argv will be used.
487 then self.argv will be used.
488 aliases : dict
488 aliases : dict
489 A dict of aliases for configurable traits.
489 A dict of aliases for configurable traits.
490 Keys are the short aliases, Values are the resolved trait.
490 Keys are the short aliases, Values are the resolved trait.
491 Of the form: `{'alias' : 'Configurable.trait'}`
491 Of the form: `{'alias' : 'Configurable.trait'}`
492 flags : dict
492 flags : dict
493 A dict of flags, keyed by str name. Values can be Config objects
493 A dict of flags, keyed by str name. Values can be Config objects
494 or dicts. When the flag is triggered, The config is loaded as
494 or dicts. When the flag is triggered, The config is loaded as
495 `self.config.update(cfg)`.
495 `self.config.update(cfg)`.
496 """
496 """
497 self.clear()
497 self.clear()
498 if argv is None:
498 if argv is None:
499 argv = self.argv
499 argv = self.argv
500 if aliases is None:
500 if aliases is None:
501 aliases = self.aliases
501 aliases = self.aliases
502 if flags is None:
502 if flags is None:
503 flags = self.flags
503 flags = self.flags
504
504
505 # ensure argv is a list of unicode strings:
505 # ensure argv is a list of unicode strings:
506 uargv = self._decode_argv(argv)
506 uargv = self._decode_argv(argv)
507 for idx,raw in enumerate(uargv):
507 for idx,raw in enumerate(uargv):
508 # strip leading '-'
508 # strip leading '-'
509 item = raw.lstrip('-')
509 item = raw.lstrip('-')
510
510
511 if raw == '--':
511 if raw == '--':
512 # don't parse arguments after '--'
512 # don't parse arguments after '--'
513 # this is useful for relaying arguments to scripts, e.g.
513 # this is useful for relaying arguments to scripts, e.g.
514 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
514 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
515 self.extra_args.extend(uargv[idx+1:])
515 self.extra_args.extend(uargv[idx+1:])
516 break
516 break
517
517
518 if kv_pattern.match(raw):
518 if kv_pattern.match(raw):
519 lhs,rhs = item.split('=',1)
519 lhs,rhs = item.split('=',1)
520 # Substitute longnames for aliases.
520 # Substitute longnames for aliases.
521 if lhs in aliases:
521 if lhs in aliases:
522 lhs = aliases[lhs]
522 lhs = aliases[lhs]
523 if '.' not in lhs:
523 if '.' not in lhs:
524 # probably a mistyped alias, but not technically illegal
524 # probably a mistyped alias, but not technically illegal
525 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
525 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
526 try:
526 try:
527 self._exec_config_str(lhs, rhs)
527 self._exec_config_str(lhs, rhs)
528 except Exception:
528 except Exception:
529 raise ArgumentError("Invalid argument: '%s'" % raw)
529 raise ArgumentError("Invalid argument: '%s'" % raw)
530
530
531 elif flag_pattern.match(raw):
531 elif flag_pattern.match(raw):
532 if item in flags:
532 if item in flags:
533 cfg,help = flags[item]
533 cfg,help = flags[item]
534 self._load_flag(cfg)
534 self._load_flag(cfg)
535 else:
535 else:
536 raise ArgumentError("Unrecognized flag: '%s'"%raw)
536 raise ArgumentError("Unrecognized flag: '%s'"%raw)
537 elif raw.startswith('-'):
537 elif raw.startswith('-'):
538 kv = '--'+item
538 kv = '--'+item
539 if kv_pattern.match(kv):
539 if kv_pattern.match(kv):
540 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
540 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
541 else:
541 else:
542 raise ArgumentError("Invalid argument: '%s'"%raw)
542 raise ArgumentError("Invalid argument: '%s'"%raw)
543 else:
543 else:
544 # keep all args that aren't valid in a list,
544 # keep all args that aren't valid in a list,
545 # in case our parent knows what to do with them.
545 # in case our parent knows what to do with them.
546 self.extra_args.append(item)
546 self.extra_args.append(item)
547 return self.config
547 return self.config
548
548
549 class ArgParseConfigLoader(CommandLineConfigLoader):
549 class ArgParseConfigLoader(CommandLineConfigLoader):
550 """A loader that uses the argparse module to load from the command line."""
550 """A loader that uses the argparse module to load from the command line."""
551
551
552 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
552 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
553 """Create a config loader for use with argparse.
553 """Create a config loader for use with argparse.
554
554
555 Parameters
555 Parameters
556 ----------
556 ----------
557
557
558 argv : optional, list
558 argv : optional, list
559 If given, used to read command-line arguments from, otherwise
559 If given, used to read command-line arguments from, otherwise
560 sys.argv[1:] is used.
560 sys.argv[1:] is used.
561
561
562 parser_args : tuple
562 parser_args : tuple
563 A tuple of positional arguments that will be passed to the
563 A tuple of positional arguments that will be passed to the
564 constructor of :class:`argparse.ArgumentParser`.
564 constructor of :class:`argparse.ArgumentParser`.
565
565
566 parser_kw : dict
566 parser_kw : dict
567 A tuple of keyword arguments that will be passed to the
567 A tuple of keyword arguments that will be passed to the
568 constructor of :class:`argparse.ArgumentParser`.
568 constructor of :class:`argparse.ArgumentParser`.
569
569
570 Returns
570 Returns
571 -------
571 -------
572 config : Config
572 config : Config
573 The resulting Config object.
573 The resulting Config object.
574 """
574 """
575 super(CommandLineConfigLoader, self).__init__()
575 super(CommandLineConfigLoader, self).__init__()
576 self.clear()
576 self.clear()
577 if argv is None:
577 if argv is None:
578 argv = sys.argv[1:]
578 argv = sys.argv[1:]
579 self.argv = argv
579 self.argv = argv
580 self.aliases = aliases or {}
580 self.aliases = aliases or {}
581 self.flags = flags or {}
581 self.flags = flags or {}
582
582
583 self.parser_args = parser_args
583 self.parser_args = parser_args
584 self.version = parser_kw.pop("version", None)
584 self.version = parser_kw.pop("version", None)
585 kwargs = dict(argument_default=argparse.SUPPRESS)
585 kwargs = dict(argument_default=argparse.SUPPRESS)
586 kwargs.update(parser_kw)
586 kwargs.update(parser_kw)
587 self.parser_kw = kwargs
587 self.parser_kw = kwargs
588
588
589 def load_config(self, argv=None, aliases=None, flags=None):
589 def load_config(self, argv=None, aliases=None, flags=None):
590 """Parse command line arguments and return as a Config object.
590 """Parse command line arguments and return as a Config object.
591
591
592 Parameters
592 Parameters
593 ----------
593 ----------
594
594
595 args : optional, list
595 args : optional, list
596 If given, a list with the structure of sys.argv[1:] to parse
596 If given, a list with the structure of sys.argv[1:] to parse
597 arguments from. If not given, the instance's self.argv attribute
597 arguments from. If not given, the instance's self.argv attribute
598 (given at construction time) is used."""
598 (given at construction time) is used."""
599 self.clear()
599 self.clear()
600 if argv is None:
600 if argv is None:
601 argv = self.argv
601 argv = self.argv
602 if aliases is None:
602 if aliases is None:
603 aliases = self.aliases
603 aliases = self.aliases
604 if flags is None:
604 if flags is None:
605 flags = self.flags
605 flags = self.flags
606 self._create_parser(aliases, flags)
606 self._create_parser(aliases, flags)
607 self._parse_args(argv)
607 self._parse_args(argv)
608 self._convert_to_config()
608 self._convert_to_config()
609 return self.config
609 return self.config
610
610
611 def get_extra_args(self):
611 def get_extra_args(self):
612 if hasattr(self, 'extra_args'):
612 if hasattr(self, 'extra_args'):
613 return self.extra_args
613 return self.extra_args
614 else:
614 else:
615 return []
615 return []
616
616
617 def _create_parser(self, aliases=None, flags=None):
617 def _create_parser(self, aliases=None, flags=None):
618 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
618 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
619 self._add_arguments(aliases, flags)
619 self._add_arguments(aliases, flags)
620
620
621 def _add_arguments(self, aliases=None, flags=None):
621 def _add_arguments(self, aliases=None, flags=None):
622 raise NotImplementedError("subclasses must implement _add_arguments")
622 raise NotImplementedError("subclasses must implement _add_arguments")
623
623
624 def _parse_args(self, args):
624 def _parse_args(self, args):
625 """self.parser->self.parsed_data"""
625 """self.parser->self.parsed_data"""
626 # decode sys.argv to support unicode command-line options
626 # decode sys.argv to support unicode command-line options
627 enc = DEFAULT_ENCODING
627 enc = DEFAULT_ENCODING
628 uargs = [py3compat.cast_unicode(a, enc) for a in args]
628 uargs = [py3compat.cast_unicode(a, enc) for a in args]
629 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
629 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
630
630
631 def _convert_to_config(self):
631 def _convert_to_config(self):
632 """self.parsed_data->self.config"""
632 """self.parsed_data->self.config"""
633 for k, v in vars(self.parsed_data).iteritems():
633 for k, v in vars(self.parsed_data).iteritems():
634 exec "self.config.%s = v"%k in locals(), globals()
634 exec "self.config.%s = v"%k in locals(), globals()
635
635
636 class KVArgParseConfigLoader(ArgParseConfigLoader):
636 class KVArgParseConfigLoader(ArgParseConfigLoader):
637 """A config loader that loads aliases and flags with argparse,
637 """A config loader that loads aliases and flags with argparse,
638 but will use KVLoader for the rest. This allows better parsing
638 but will use KVLoader for the rest. This allows better parsing
639 of common args, such as `ipython -c 'print 5'`, but still gets
639 of common args, such as `ipython -c 'print 5'`, but still gets
640 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
640 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
641
641
642 def _add_arguments(self, aliases=None, flags=None):
642 def _add_arguments(self, aliases=None, flags=None):
643 self.alias_flags = {}
643 self.alias_flags = {}
644 # print aliases, flags
644 # print aliases, flags
645 if aliases is None:
645 if aliases is None:
646 aliases = self.aliases
646 aliases = self.aliases
647 if flags is None:
647 if flags is None:
648 flags = self.flags
648 flags = self.flags
649 paa = self.parser.add_argument
649 paa = self.parser.add_argument
650 for key,value in aliases.iteritems():
650 for key,value in aliases.iteritems():
651 if key in flags:
651 if key in flags:
652 # flags
652 # flags
653 nargs = '?'
653 nargs = '?'
654 else:
654 else:
655 nargs = None
655 nargs = None
656 if len(key) is 1:
656 if len(key) is 1:
657 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
657 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
658 else:
658 else:
659 paa('--'+key, type=unicode, dest=value, nargs=nargs)
659 paa('--'+key, type=unicode, dest=value, nargs=nargs)
660 for key, (value, help) in flags.iteritems():
660 for key, (value, help) in flags.iteritems():
661 if key in self.aliases:
661 if key in self.aliases:
662 #
662 #
663 self.alias_flags[self.aliases[key]] = value
663 self.alias_flags[self.aliases[key]] = value
664 continue
664 continue
665 if len(key) is 1:
665 if len(key) is 1:
666 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
666 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
667 else:
667 else:
668 paa('--'+key, action='append_const', dest='_flags', const=value)
668 paa('--'+key, action='append_const', dest='_flags', const=value)
669
669
670 def _convert_to_config(self):
670 def _convert_to_config(self):
671 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
671 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
672 # remove subconfigs list from namespace before transforming the Namespace
672 # remove subconfigs list from namespace before transforming the Namespace
673 if '_flags' in self.parsed_data:
673 if '_flags' in self.parsed_data:
674 subcs = self.parsed_data._flags
674 subcs = self.parsed_data._flags
675 del self.parsed_data._flags
675 del self.parsed_data._flags
676 else:
676 else:
677 subcs = []
677 subcs = []
678
678
679 for k, v in vars(self.parsed_data).iteritems():
679 for k, v in vars(self.parsed_data).iteritems():
680 if v is None:
680 if v is None:
681 # it was a flag that shares the name of an alias
681 # it was a flag that shares the name of an alias
682 subcs.append(self.alias_flags[k])
682 subcs.append(self.alias_flags[k])
683 else:
683 else:
684 # eval the KV assignment
684 # eval the KV assignment
685 self._exec_config_str(k, v)
685 self._exec_config_str(k, v)
686
686
687 for subc in subcs:
687 for subc in subcs:
688 self._load_flag(subc)
688 self._load_flag(subc)
689
689
690 if self.extra_args:
690 if self.extra_args:
691 sub_parser = KeyValueConfigLoader()
691 sub_parser = KeyValueConfigLoader()
692 sub_parser.load_config(self.extra_args)
692 sub_parser.load_config(self.extra_args)
693 self.config.merge(sub_parser.config)
693 self.config.merge(sub_parser.config)
694 self.extra_args = sub_parser.extra_args
694 self.extra_args = sub_parser.extra_args
695
695
696
696
697 def load_pyconfig_files(config_files, path):
697 def load_pyconfig_files(config_files, path):
698 """Load multiple Python config files, merging each of them in turn.
698 """Load multiple Python config files, merging each of them in turn.
699
699
700 Parameters
700 Parameters
701 ==========
701 ==========
702 config_files : list of str
702 config_files : list of str
703 List of config files names to load and merge into the config.
703 List of config files names to load and merge into the config.
704 path : unicode
704 path : unicode
705 The full path to the location of the config files.
705 The full path to the location of the config files.
706 """
706 """
707 config = Config()
707 config = Config()
708 for cf in config_files:
708 for cf in config_files:
709 loader = PyFileConfigLoader(cf, path=path)
709 loader = PyFileConfigLoader(cf, path=path)
710 try:
710 try:
711 next_config = loader.load_config()
711 next_config = loader.load_config()
712 except ConfigFileNotFound:
712 except ConfigFileNotFound:
713 pass
713 pass
714 except:
714 except:
715 raise
715 raise
716 else:
716 else:
717 config.merge(next_config)
717 config.merge(next_config)
718 return config
718 return config
@@ -1,246 +1,246 b''
1 ''' A decorator-based method of constructing IPython magics with `argparse`
1 ''' A decorator-based method of constructing IPython magics with `argparse`
2 option handling.
2 option handling.
3
3
4 New magic functions can be defined like so::
4 New magic functions can be defined like so::
5
5
6 from IPython.core.magic_arguments import (argument, magic_arguments,
6 from IPython.core.magic_arguments import (argument, magic_arguments,
7 parse_argstring)
7 parse_argstring)
8
8
9 @magic_arguments()
9 @magic_arguments()
10 @argument('-o', '--option', help='An optional argument.')
10 @argument('-o', '--option', help='An optional argument.')
11 @argument('arg', type=int, help='An integer positional argument.')
11 @argument('arg', type=int, help='An integer positional argument.')
12 def magic_cool(self, arg):
12 def magic_cool(self, arg):
13 """ A really cool magic command.
13 """ A really cool magic command.
14
14
15 """
15 """
16 args = parse_argstring(magic_cool, arg)
16 args = parse_argstring(magic_cool, arg)
17 ...
17 ...
18
18
19 The `@magic_arguments` decorator marks the function as having argparse arguments.
19 The `@magic_arguments` decorator marks the function as having argparse arguments.
20 The `@argument` decorator adds an argument using the same syntax as argparse's
20 The `@argument` decorator adds an argument using the same syntax as argparse's
21 `add_argument()` method. More sophisticated uses may also require the
21 `add_argument()` method. More sophisticated uses may also require the
22 `@argument_group` or `@kwds` decorator to customize the formatting and the
22 `@argument_group` or `@kwds` decorator to customize the formatting and the
23 parsing.
23 parsing.
24
24
25 Help text for the magic is automatically generated from the docstring and the
25 Help text for the magic is automatically generated from the docstring and the
26 arguments::
26 arguments::
27
27
28 In[1]: %cool?
28 In[1]: %cool?
29 %cool [-o OPTION] arg
29 %cool [-o OPTION] arg
30
30
31 A really cool magic command.
31 A really cool magic command.
32
32
33 positional arguments:
33 positional arguments:
34 arg An integer positional argument.
34 arg An integer positional argument.
35
35
36 optional arguments:
36 optional arguments:
37 -o OPTION, --option OPTION
37 -o OPTION, --option OPTION
38 An optional argument.
38 An optional argument.
39
39
40 Inheritance diagram:
40 Inheritance diagram:
41
41
42 .. inheritance-diagram:: IPython.core.magic_arguments
42 .. inheritance-diagram:: IPython.core.magic_arguments
43 :parts: 3
43 :parts: 3
44
44
45 '''
45 '''
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Copyright (C) 2010-2011, IPython Development Team.
47 # Copyright (C) 2010-2011, IPython Development Team.
48 #
48 #
49 # Distributed under the terms of the Modified BSD License.
49 # Distributed under the terms of the Modified BSD License.
50 #
50 #
51 # The full license is in the file COPYING.txt, distributed with this software.
51 # The full license is in the file COPYING.txt, distributed with this software.
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 import argparse
53
54
54 # Our own imports
55 # Our own imports
55 from IPython.external import argparse
56 from IPython.core.error import UsageError
56 from IPython.core.error import UsageError
57 from IPython.utils.process import arg_split
57 from IPython.utils.process import arg_split
58 from IPython.utils.text import dedent
58 from IPython.utils.text import dedent
59
59
60 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
60 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
61 """ A HelpFormatter which dedents but otherwise preserves indentation.
61 """ A HelpFormatter which dedents but otherwise preserves indentation.
62 """
62 """
63 def _fill_text(self, text, width, indent):
63 def _fill_text(self, text, width, indent):
64 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
64 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
65
65
66 class MagicArgumentParser(argparse.ArgumentParser):
66 class MagicArgumentParser(argparse.ArgumentParser):
67 """ An ArgumentParser tweaked for use by IPython magics.
67 """ An ArgumentParser tweaked for use by IPython magics.
68 """
68 """
69 def __init__(self,
69 def __init__(self,
70 prog=None,
70 prog=None,
71 usage=None,
71 usage=None,
72 description=None,
72 description=None,
73 epilog=None,
73 epilog=None,
74 parents=None,
74 parents=None,
75 formatter_class=MagicHelpFormatter,
75 formatter_class=MagicHelpFormatter,
76 prefix_chars='-',
76 prefix_chars='-',
77 argument_default=None,
77 argument_default=None,
78 conflict_handler='error',
78 conflict_handler='error',
79 add_help=False):
79 add_help=False):
80 if parents is None:
80 if parents is None:
81 parents = []
81 parents = []
82 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
82 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
83 description=description, epilog=epilog,
83 description=description, epilog=epilog,
84 parents=parents, formatter_class=formatter_class,
84 parents=parents, formatter_class=formatter_class,
85 prefix_chars=prefix_chars, argument_default=argument_default,
85 prefix_chars=prefix_chars, argument_default=argument_default,
86 conflict_handler=conflict_handler, add_help=add_help)
86 conflict_handler=conflict_handler, add_help=add_help)
87
87
88 def error(self, message):
88 def error(self, message):
89 """ Raise a catchable error instead of exiting.
89 """ Raise a catchable error instead of exiting.
90 """
90 """
91 raise UsageError(message)
91 raise UsageError(message)
92
92
93 def parse_argstring(self, argstring):
93 def parse_argstring(self, argstring):
94 """ Split a string into an argument list and parse that argument list.
94 """ Split a string into an argument list and parse that argument list.
95 """
95 """
96 argv = arg_split(argstring)
96 argv = arg_split(argstring)
97 return self.parse_args(argv)
97 return self.parse_args(argv)
98
98
99
99
100 def construct_parser(magic_func):
100 def construct_parser(magic_func):
101 """ Construct an argument parser using the function decorations.
101 """ Construct an argument parser using the function decorations.
102 """
102 """
103 kwds = getattr(magic_func, 'argcmd_kwds', {})
103 kwds = getattr(magic_func, 'argcmd_kwds', {})
104 if 'description' not in kwds:
104 if 'description' not in kwds:
105 kwds['description'] = getattr(magic_func, '__doc__', None)
105 kwds['description'] = getattr(magic_func, '__doc__', None)
106 arg_name = real_name(magic_func)
106 arg_name = real_name(magic_func)
107 parser = MagicArgumentParser(arg_name, **kwds)
107 parser = MagicArgumentParser(arg_name, **kwds)
108 # Reverse the list of decorators in order to apply them in the
108 # Reverse the list of decorators in order to apply them in the
109 # order in which they appear in the source.
109 # order in which they appear in the source.
110 group = None
110 group = None
111 for deco in magic_func.decorators[::-1]:
111 for deco in magic_func.decorators[::-1]:
112 result = deco.add_to_parser(parser, group)
112 result = deco.add_to_parser(parser, group)
113 if result is not None:
113 if result is not None:
114 group = result
114 group = result
115
115
116 # Replace the starting 'usage: ' with IPython's %.
116 # Replace the starting 'usage: ' with IPython's %.
117 help_text = parser.format_help()
117 help_text = parser.format_help()
118 if help_text.startswith('usage: '):
118 if help_text.startswith('usage: '):
119 help_text = help_text.replace('usage: ', '%', 1)
119 help_text = help_text.replace('usage: ', '%', 1)
120 else:
120 else:
121 help_text = '%' + help_text
121 help_text = '%' + help_text
122
122
123 # Replace the magic function's docstring with the full help text.
123 # Replace the magic function's docstring with the full help text.
124 magic_func.__doc__ = help_text
124 magic_func.__doc__ = help_text
125
125
126 return parser
126 return parser
127
127
128
128
129 def parse_argstring(magic_func, argstring):
129 def parse_argstring(magic_func, argstring):
130 """ Parse the string of arguments for the given magic function.
130 """ Parse the string of arguments for the given magic function.
131 """
131 """
132 return magic_func.parser.parse_argstring(argstring)
132 return magic_func.parser.parse_argstring(argstring)
133
133
134
134
135 def real_name(magic_func):
135 def real_name(magic_func):
136 """ Find the real name of the magic.
136 """ Find the real name of the magic.
137 """
137 """
138 magic_name = magic_func.__name__
138 magic_name = magic_func.__name__
139 if magic_name.startswith('magic_'):
139 if magic_name.startswith('magic_'):
140 magic_name = magic_name[len('magic_'):]
140 magic_name = magic_name[len('magic_'):]
141 return getattr(magic_func, 'argcmd_name', magic_name)
141 return getattr(magic_func, 'argcmd_name', magic_name)
142
142
143
143
144 class ArgDecorator(object):
144 class ArgDecorator(object):
145 """ Base class for decorators to add ArgumentParser information to a method.
145 """ Base class for decorators to add ArgumentParser information to a method.
146 """
146 """
147
147
148 def __call__(self, func):
148 def __call__(self, func):
149 if not getattr(func, 'has_arguments', False):
149 if not getattr(func, 'has_arguments', False):
150 func.has_arguments = True
150 func.has_arguments = True
151 func.decorators = []
151 func.decorators = []
152 func.decorators.append(self)
152 func.decorators.append(self)
153 return func
153 return func
154
154
155 def add_to_parser(self, parser, group):
155 def add_to_parser(self, parser, group):
156 """ Add this object's information to the parser, if necessary.
156 """ Add this object's information to the parser, if necessary.
157 """
157 """
158 pass
158 pass
159
159
160
160
161 class magic_arguments(ArgDecorator):
161 class magic_arguments(ArgDecorator):
162 """ Mark the magic as having argparse arguments and possibly adjust the
162 """ Mark the magic as having argparse arguments and possibly adjust the
163 name.
163 name.
164 """
164 """
165
165
166 def __init__(self, name=None):
166 def __init__(self, name=None):
167 self.name = name
167 self.name = name
168
168
169 def __call__(self, func):
169 def __call__(self, func):
170 if not getattr(func, 'has_arguments', False):
170 if not getattr(func, 'has_arguments', False):
171 func.has_arguments = True
171 func.has_arguments = True
172 func.decorators = []
172 func.decorators = []
173 if self.name is not None:
173 if self.name is not None:
174 func.argcmd_name = self.name
174 func.argcmd_name = self.name
175 # This should be the first decorator in the list of decorators, thus the
175 # This should be the first decorator in the list of decorators, thus the
176 # last to execute. Build the parser.
176 # last to execute. Build the parser.
177 func.parser = construct_parser(func)
177 func.parser = construct_parser(func)
178 return func
178 return func
179
179
180
180
181 class ArgMethodWrapper(ArgDecorator):
181 class ArgMethodWrapper(ArgDecorator):
182
182
183 """
183 """
184 Base class to define a wrapper for ArgumentParser method.
184 Base class to define a wrapper for ArgumentParser method.
185
185
186 Child class must define either `_method_name` or `add_to_parser`.
186 Child class must define either `_method_name` or `add_to_parser`.
187
187
188 """
188 """
189
189
190 _method_name = None
190 _method_name = None
191
191
192 def __init__(self, *args, **kwds):
192 def __init__(self, *args, **kwds):
193 self.args = args
193 self.args = args
194 self.kwds = kwds
194 self.kwds = kwds
195
195
196 def add_to_parser(self, parser, group):
196 def add_to_parser(self, parser, group):
197 """ Add this object's information to the parser.
197 """ Add this object's information to the parser.
198 """
198 """
199 if group is not None:
199 if group is not None:
200 parser = group
200 parser = group
201 getattr(parser, self._method_name)(*self.args, **self.kwds)
201 getattr(parser, self._method_name)(*self.args, **self.kwds)
202 return None
202 return None
203
203
204
204
205 class argument(ArgMethodWrapper):
205 class argument(ArgMethodWrapper):
206 """ Store arguments and keywords to pass to add_argument().
206 """ Store arguments and keywords to pass to add_argument().
207
207
208 Instances also serve to decorate command methods.
208 Instances also serve to decorate command methods.
209 """
209 """
210 _method_name = 'add_argument'
210 _method_name = 'add_argument'
211
211
212
212
213 class defaults(ArgMethodWrapper):
213 class defaults(ArgMethodWrapper):
214 """ Store arguments and keywords to pass to set_defaults().
214 """ Store arguments and keywords to pass to set_defaults().
215
215
216 Instances also serve to decorate command methods.
216 Instances also serve to decorate command methods.
217 """
217 """
218 _method_name = 'set_defaults'
218 _method_name = 'set_defaults'
219
219
220
220
221 class argument_group(ArgMethodWrapper):
221 class argument_group(ArgMethodWrapper):
222 """ Store arguments and keywords to pass to add_argument_group().
222 """ Store arguments and keywords to pass to add_argument_group().
223
223
224 Instances also serve to decorate command methods.
224 Instances also serve to decorate command methods.
225 """
225 """
226
226
227 def add_to_parser(self, parser, group):
227 def add_to_parser(self, parser, group):
228 """ Add this object's information to the parser.
228 """ Add this object's information to the parser.
229 """
229 """
230 return parser.add_argument_group(*self.args, **self.kwds)
230 return parser.add_argument_group(*self.args, **self.kwds)
231
231
232
232
233 class kwds(ArgDecorator):
233 class kwds(ArgDecorator):
234 """ Provide other keywords to the sub-parser constructor.
234 """ Provide other keywords to the sub-parser constructor.
235 """
235 """
236 def __init__(self, **kwds):
236 def __init__(self, **kwds):
237 self.kwds = kwds
237 self.kwds = kwds
238
238
239 def __call__(self, func):
239 def __call__(self, func):
240 func = super(kwds, self).__call__(func)
240 func = super(kwds, self).__call__(func)
241 func.argcmd_kwds = self.kwds
241 func.argcmd_kwds = self.kwds
242 return func
242 return func
243
243
244
244
245 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
245 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
246 'parse_argstring']
246 'parse_argstring']
@@ -1,118 +1,118 b''
1 #-----------------------------------------------------------------------------
1 #-----------------------------------------------------------------------------
2 # Copyright (C) 2010-2011, IPython Development Team.
2 # Copyright (C) 2010-2011, IPython Development Team.
3 #
3 #
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5 #
5 #
6 # The full license is in the file COPYING.txt, distributed with this software.
6 # The full license is in the file COPYING.txt, distributed with this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 import argparse
9 from nose.tools import assert_equal
10 from nose.tools import assert_equal
10
11
11 from IPython.external import argparse
12 from IPython.core.magic_arguments import (argument, argument_group, kwds,
12 from IPython.core.magic_arguments import (argument, argument_group, kwds,
13 magic_arguments, parse_argstring, real_name)
13 magic_arguments, parse_argstring, real_name)
14
14
15
15
16 @magic_arguments()
16 @magic_arguments()
17 @argument('-f', '--foo', help="an argument")
17 @argument('-f', '--foo', help="an argument")
18 def magic_foo1(self, args):
18 def magic_foo1(self, args):
19 """ A docstring.
19 """ A docstring.
20 """
20 """
21 return parse_argstring(magic_foo1, args)
21 return parse_argstring(magic_foo1, args)
22
22
23
23
24 @magic_arguments()
24 @magic_arguments()
25 def magic_foo2(self, args):
25 def magic_foo2(self, args):
26 """ A docstring.
26 """ A docstring.
27 """
27 """
28 return parse_argstring(magic_foo2, args)
28 return parse_argstring(magic_foo2, args)
29
29
30
30
31 @magic_arguments()
31 @magic_arguments()
32 @argument('-f', '--foo', help="an argument")
32 @argument('-f', '--foo', help="an argument")
33 @argument_group('Group')
33 @argument_group('Group')
34 @argument('-b', '--bar', help="a grouped argument")
34 @argument('-b', '--bar', help="a grouped argument")
35 @argument_group('Second Group')
35 @argument_group('Second Group')
36 @argument('-z', '--baz', help="another grouped argument")
36 @argument('-z', '--baz', help="another grouped argument")
37 def magic_foo3(self, args):
37 def magic_foo3(self, args):
38 """ A docstring.
38 """ A docstring.
39 """
39 """
40 return parse_argstring(magic_foo3, args)
40 return parse_argstring(magic_foo3, args)
41
41
42
42
43 @magic_arguments()
43 @magic_arguments()
44 @kwds(argument_default=argparse.SUPPRESS)
44 @kwds(argument_default=argparse.SUPPRESS)
45 @argument('-f', '--foo', help="an argument")
45 @argument('-f', '--foo', help="an argument")
46 def magic_foo4(self, args):
46 def magic_foo4(self, args):
47 """ A docstring.
47 """ A docstring.
48 """
48 """
49 return parse_argstring(magic_foo4, args)
49 return parse_argstring(magic_foo4, args)
50
50
51
51
52 @magic_arguments('frobnicate')
52 @magic_arguments('frobnicate')
53 @argument('-f', '--foo', help="an argument")
53 @argument('-f', '--foo', help="an argument")
54 def magic_foo5(self, args):
54 def magic_foo5(self, args):
55 """ A docstring.
55 """ A docstring.
56 """
56 """
57 return parse_argstring(magic_foo5, args)
57 return parse_argstring(magic_foo5, args)
58
58
59
59
60 @magic_arguments()
60 @magic_arguments()
61 @argument('-f', '--foo', help="an argument")
61 @argument('-f', '--foo', help="an argument")
62 def magic_magic_foo(self, args):
62 def magic_magic_foo(self, args):
63 """ A docstring.
63 """ A docstring.
64 """
64 """
65 return parse_argstring(magic_magic_foo, args)
65 return parse_argstring(magic_magic_foo, args)
66
66
67
67
68 @magic_arguments()
68 @magic_arguments()
69 @argument('-f', '--foo', help="an argument")
69 @argument('-f', '--foo', help="an argument")
70 def foo(self, args):
70 def foo(self, args):
71 """ A docstring.
71 """ A docstring.
72 """
72 """
73 return parse_argstring(foo, args)
73 return parse_argstring(foo, args)
74
74
75
75
76 def test_magic_arguments():
76 def test_magic_arguments():
77 assert_equal(magic_foo1.__doc__, '%foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
77 assert_equal(magic_foo1.__doc__, '%foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
78 assert_equal(getattr(magic_foo1, 'argcmd_name', None), None)
78 assert_equal(getattr(magic_foo1, 'argcmd_name', None), None)
79 assert_equal(real_name(magic_foo1), 'foo1')
79 assert_equal(real_name(magic_foo1), 'foo1')
80 assert_equal(magic_foo1(None, ''), argparse.Namespace(foo=None))
80 assert_equal(magic_foo1(None, ''), argparse.Namespace(foo=None))
81 assert hasattr(magic_foo1, 'has_arguments')
81 assert hasattr(magic_foo1, 'has_arguments')
82
82
83 assert_equal(magic_foo2.__doc__, '%foo2\n\n A docstring.\n')
83 assert_equal(magic_foo2.__doc__, '%foo2\n\n A docstring.\n')
84 assert_equal(getattr(magic_foo2, 'argcmd_name', None), None)
84 assert_equal(getattr(magic_foo2, 'argcmd_name', None), None)
85 assert_equal(real_name(magic_foo2), 'foo2')
85 assert_equal(real_name(magic_foo2), 'foo2')
86 assert_equal(magic_foo2(None, ''), argparse.Namespace())
86 assert_equal(magic_foo2(None, ''), argparse.Namespace())
87 assert hasattr(magic_foo2, 'has_arguments')
87 assert hasattr(magic_foo2, 'has_arguments')
88
88
89 assert_equal(magic_foo3.__doc__, '%foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n')
89 assert_equal(magic_foo3.__doc__, '%foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n')
90 assert_equal(getattr(magic_foo3, 'argcmd_name', None), None)
90 assert_equal(getattr(magic_foo3, 'argcmd_name', None), None)
91 assert_equal(real_name(magic_foo3), 'foo3')
91 assert_equal(real_name(magic_foo3), 'foo3')
92 assert_equal(magic_foo3(None, ''),
92 assert_equal(magic_foo3(None, ''),
93 argparse.Namespace(bar=None, baz=None, foo=None))
93 argparse.Namespace(bar=None, baz=None, foo=None))
94 assert hasattr(magic_foo3, 'has_arguments')
94 assert hasattr(magic_foo3, 'has_arguments')
95
95
96 assert_equal(magic_foo4.__doc__, '%foo4 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
96 assert_equal(magic_foo4.__doc__, '%foo4 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
97 assert_equal(getattr(magic_foo4, 'argcmd_name', None), None)
97 assert_equal(getattr(magic_foo4, 'argcmd_name', None), None)
98 assert_equal(real_name(magic_foo4), 'foo4')
98 assert_equal(real_name(magic_foo4), 'foo4')
99 assert_equal(magic_foo4(None, ''), argparse.Namespace())
99 assert_equal(magic_foo4(None, ''), argparse.Namespace())
100 assert hasattr(magic_foo4, 'has_arguments')
100 assert hasattr(magic_foo4, 'has_arguments')
101
101
102 assert_equal(magic_foo5.__doc__, '%frobnicate [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
102 assert_equal(magic_foo5.__doc__, '%frobnicate [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
103 assert_equal(getattr(magic_foo5, 'argcmd_name', None), 'frobnicate')
103 assert_equal(getattr(magic_foo5, 'argcmd_name', None), 'frobnicate')
104 assert_equal(real_name(magic_foo5), 'frobnicate')
104 assert_equal(real_name(magic_foo5), 'frobnicate')
105 assert_equal(magic_foo5(None, ''), argparse.Namespace(foo=None))
105 assert_equal(magic_foo5(None, ''), argparse.Namespace(foo=None))
106 assert hasattr(magic_foo5, 'has_arguments')
106 assert hasattr(magic_foo5, 'has_arguments')
107
107
108 assert_equal(magic_magic_foo.__doc__, '%magic_foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
108 assert_equal(magic_magic_foo.__doc__, '%magic_foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
109 assert_equal(getattr(magic_magic_foo, 'argcmd_name', None), None)
109 assert_equal(getattr(magic_magic_foo, 'argcmd_name', None), None)
110 assert_equal(real_name(magic_magic_foo), 'magic_foo')
110 assert_equal(real_name(magic_magic_foo), 'magic_foo')
111 assert_equal(magic_magic_foo(None, ''), argparse.Namespace(foo=None))
111 assert_equal(magic_magic_foo(None, ''), argparse.Namespace(foo=None))
112 assert hasattr(magic_magic_foo, 'has_arguments')
112 assert hasattr(magic_magic_foo, 'has_arguments')
113
113
114 assert_equal(foo.__doc__, '%foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
114 assert_equal(foo.__doc__, '%foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
115 assert_equal(getattr(foo, 'argcmd_name', None), None)
115 assert_equal(getattr(foo, 'argcmd_name', None), None)
116 assert_equal(real_name(foo), 'foo')
116 assert_equal(real_name(foo), 'foo')
117 assert_equal(foo(None, ''), argparse.Namespace(foo=None))
117 assert_equal(foo(None, ''), argparse.Namespace(foo=None))
118 assert hasattr(foo, 'has_arguments')
118 assert hasattr(foo, 'has_arguments')
@@ -1,331 +1,331 b''
1 #!/usr/bin/python
1 #!/usr/bin/python
2 """Utility function for installing MathJax javascript library into
2 """Utility function for installing MathJax javascript library into
3 the notebook's 'static' directory, for offline use.
3 the notebook's 'static' directory, for offline use.
4
4
5 Authors:
5 Authors:
6
6
7 * Min RK
7 * Min RK
8 * Mark Sienkiewicz
8 * Mark Sienkiewicz
9 * Matthias Bussonnier
9 * Matthias Bussonnier
10
10
11 To download and install MathJax:
11 To download and install MathJax:
12
12
13 From Python:
13 From Python:
14
14
15 >>> from IPython.external.mathjax import install_mathjax
15 >>> from IPython.external.mathjax import install_mathjax
16 >>> install_mathjax()
16 >>> install_mathjax()
17
17
18 From the command line:
18 From the command line:
19
19
20 $ python -m IPython.external.mathjax
20 $ python -m IPython.external.mathjax
21
21
22 To a specific profile:
22 To a specific profile:
23
23
24 $ python -m IPython.external.mathjax --profile=research
24 $ python -m IPython.external.mathjax --profile=research
25
25
26 To install MathJax from a file you have already downloaded:
26 To install MathJax from a file you have already downloaded:
27
27
28 $ python -m IPython.external.mathjax mathjax-xxx.tar.gz
28 $ python -m IPython.external.mathjax mathjax-xxx.tar.gz
29 $ python -m IPython.external.mathjax mathjax-xxx.zip
29 $ python -m IPython.external.mathjax mathjax-xxx.zip
30
30
31 It will not install MathJax if it is already there. Use -r to
31 It will not install MathJax if it is already there. Use -r to
32 replace the existing copy of MathJax.
32 replace the existing copy of MathJax.
33
33
34 To find the directory where IPython would like MathJax installed:
34 To find the directory where IPython would like MathJax installed:
35
35
36 $ python -m IPython.external.mathjax -d
36 $ python -m IPython.external.mathjax -d
37
37
38 """
38 """
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Copyright (C) 2008-2011 The IPython Development Team
42 # Copyright (C) 2008-2011 The IPython Development Team
43 #
43 #
44 # Distributed under the terms of the BSD License. The full license is in
44 # Distributed under the terms of the BSD License. The full license is in
45 # the file COPYING, distributed as part of this software.
45 # the file COPYING, distributed as part of this software.
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Imports
50 # Imports
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 import argparse
53 import os
54 import os
54 import shutil
55 import shutil
55 import sys
56 import sys
56 import tarfile
57 import tarfile
57 import urllib2
58 import urllib2
58 import zipfile
59 import zipfile
59
60
60
61
61 from IPython.utils.path import locate_profile
62 from IPython.utils.path import locate_profile
62 from IPython.external import argparse
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 #
64 #
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 # Where mathjax will be installed.
67 # Where mathjax will be installed.
68
68
69 static = os.path.join(locate_profile('default'), 'static')
69 static = os.path.join(locate_profile('default'), 'static')
70 default_dest = os.path.join(static, 'mathjax')
70 default_dest = os.path.join(static, 'mathjax')
71
71
72 ##
72 ##
73
73
74 # Test for access to install mathjax.
74 # Test for access to install mathjax.
75
75
76 def check_perms(dest, replace=False):
76 def check_perms(dest, replace=False):
77 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
77 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
78 components = dest.split(os.path.sep)
78 components = dest.split(os.path.sep)
79 subpaths = [ os.path.sep+os.path.sep.join(components[1:i]) for i in range(1,len(components))]
79 subpaths = [ os.path.sep+os.path.sep.join(components[1:i]) for i in range(1,len(components))]
80
80
81 existing_path = filter(os.path.exists, subpaths)
81 existing_path = filter(os.path.exists, subpaths)
82 last_writable = existing_path[-1]
82 last_writable = existing_path[-1]
83 if not os.access(last_writable, os.W_OK):
83 if not os.access(last_writable, os.W_OK):
84 raise IOError("Need have write access to %s" % parent)
84 raise IOError("Need have write access to %s" % parent)
85 not_existing = [ path for path in subpaths if path not in existing_path]
85 not_existing = [ path for path in subpaths if path not in existing_path]
86 # subfolder we will create, will obviously be writable
86 # subfolder we will create, will obviously be writable
87 # should we still considere checking separately that
87 # should we still considere checking separately that
88 # ipython profiles have been created ?
88 # ipython profiles have been created ?
89 for folder in not_existing:
89 for folder in not_existing:
90 os.mkdir(folder)
90 os.mkdir(folder)
91
91
92 if os.path.exists(dest):
92 if os.path.exists(dest):
93 if replace:
93 if replace:
94 if not os.access(dest, os.W_OK):
94 if not os.access(dest, os.W_OK):
95 raise IOError("Need have write access to %s" % dest)
95 raise IOError("Need have write access to %s" % dest)
96 print "removing previous MathJax install"
96 print "removing previous MathJax install"
97 shutil.rmtree(dest)
97 shutil.rmtree(dest)
98 return True
98 return True
99 else:
99 else:
100 print "offline MathJax apparently already installed"
100 print "offline MathJax apparently already installed"
101 return False
101 return False
102 else :
102 else :
103 return True
103 return True
104
104
105 ##
105 ##
106
106
107 def extract_tar( fd, dest ) :
107 def extract_tar( fd, dest ) :
108 # use 'r|gz' stream mode, because socket file-like objects can't seek:
108 # use 'r|gz' stream mode, because socket file-like objects can't seek:
109 tar = tarfile.open(fileobj=fd, mode='r|gz')
109 tar = tarfile.open(fileobj=fd, mode='r|gz')
110
110
111 # we just happen to know that the first entry in the mathjax
111 # we just happen to know that the first entry in the mathjax
112 # archive is the directory that the remaining members are in.
112 # archive is the directory that the remaining members are in.
113 topdir = tar.firstmember.path
113 topdir = tar.firstmember.path
114
114
115 # extract the archive (contains a single directory) to the static/ directory
115 # extract the archive (contains a single directory) to the static/ directory
116 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
116 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
117 tar.extractall(parent)
117 tar.extractall(parent)
118
118
119 # it will be mathjax-MathJax-<sha>, rename to just mathjax
119 # it will be mathjax-MathJax-<sha>, rename to just mathjax
120 os.rename(os.path.join(parent, topdir), dest)
120 os.rename(os.path.join(parent, topdir), dest)
121
121
122 ##
122 ##
123
123
124 def extract_zip( fd, dest ) :
124 def extract_zip( fd, dest ) :
125 z = zipfile.ZipFile( fd, 'r' )
125 z = zipfile.ZipFile( fd, 'r' )
126
126
127 # we just happen to know that the first entry in the mathjax
127 # we just happen to know that the first entry in the mathjax
128 # archive is the directory that the remaining members are in.
128 # archive is the directory that the remaining members are in.
129 topdir = z.namelist()[0]
129 topdir = z.namelist()[0]
130
130
131 # extract the archive (contains a single directory) to the static/ directory
131 # extract the archive (contains a single directory) to the static/ directory
132 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
132 parent = os.path.abspath(os.path.join(dest, os.path.pardir))
133 z.extractall( parent )
133 z.extractall( parent )
134
134
135 # it will be mathjax-MathJax-<sha>, rename to just mathjax
135 # it will be mathjax-MathJax-<sha>, rename to just mathjax
136 d = os.path.join(parent, topdir)
136 d = os.path.join(parent, topdir)
137 print d
137 print d
138 os.rename(os.path.join(parent, topdir), dest)
138 os.rename(os.path.join(parent, topdir), dest)
139
139
140 ##
140 ##
141
141
142 def install_mathjax(tag='v2.0', dest=default_dest, replace=False, file=None, extractor=extract_tar ):
142 def install_mathjax(tag='v2.0', dest=default_dest, replace=False, file=None, extractor=extract_tar ):
143 """Download and/or install MathJax for offline use.
143 """Download and/or install MathJax for offline use.
144
144
145 This will install mathjax to the 'static' dir in the IPython notebook
145 This will install mathjax to the 'static' dir in the IPython notebook
146 package, so it will fail if the caller does not have write access
146 package, so it will fail if the caller does not have write access
147 to that location.
147 to that location.
148
148
149 MathJax is a ~15MB download, and ~150MB installed.
149 MathJax is a ~15MB download, and ~150MB installed.
150
150
151 Parameters
151 Parameters
152 ----------
152 ----------
153
153
154 replace : bool [False]
154 replace : bool [False]
155 Whether to remove and replace an existing install.
155 Whether to remove and replace an existing install.
156 dest : str [path to default profile]
156 dest : str [path to default profile]
157 Where to locally install mathjax
157 Where to locally install mathjax
158 tag : str ['v2.0']
158 tag : str ['v2.0']
159 Which tag to download. Default is 'v2.0', the current stable release,
159 Which tag to download. Default is 'v2.0', the current stable release,
160 but alternatives include 'v1.1a' and 'master'.
160 but alternatives include 'v1.1a' and 'master'.
161 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
161 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
162 File handle from which to untar/unzip/... mathjax
162 File handle from which to untar/unzip/... mathjax
163 extractor : function
163 extractor : function
164 Method tu use to untar/unzip/... `file`
164 Method tu use to untar/unzip/... `file`
165 """
165 """
166 if not check_perms(dest, replace) :
166 if not check_perms(dest, replace) :
167 return
167 return
168
168
169 if file is None :
169 if file is None :
170 # download mathjax
170 # download mathjax
171 mathjax_url = "https://github.com/mathjax/MathJax/tarball/%s" % tag
171 mathjax_url = "https://github.com/mathjax/MathJax/tarball/%s" % tag
172 print "Downloading mathjax source from %s" % mathjax_url
172 print "Downloading mathjax source from %s" % mathjax_url
173 response = urllib2.urlopen(mathjax_url)
173 response = urllib2.urlopen(mathjax_url)
174 file = response.fp
174 file = response.fp
175
175
176 print "Extracting to %s" % dest
176 print "Extracting to %s" % dest
177 extractor( file, dest )
177 extractor( file, dest )
178
178
179 ##
179 ##
180
180
181 def test_func( remove, dest) :
181 def test_func( remove, dest) :
182 """See if mathjax appears to be installed correctly"""
182 """See if mathjax appears to be installed correctly"""
183 status = 0
183 status = 0
184 if not os.path.isdir( dest ) :
184 if not os.path.isdir( dest ) :
185 print "%s directory not found" % dest
185 print "%s directory not found" % dest
186 status = 1
186 status = 1
187 if not os.path.exists( dest + "/MathJax.js" ) :
187 if not os.path.exists( dest + "/MathJax.js" ) :
188 print "MathJax.js not present in %s" % dest
188 print "MathJax.js not present in %s" % dest
189 status = 1
189 status = 1
190 print "ok"
190 print "ok"
191 if remove and os.path.exists(dest):
191 if remove and os.path.exists(dest):
192 shutil.rmtree( dest )
192 shutil.rmtree( dest )
193 return status
193 return status
194
194
195 ##
195 ##
196
196
197 def main() :
197 def main() :
198 # This main is just simple enough that it is not worth the
198 # This main is just simple enough that it is not worth the
199 # complexity of argparse
199 # complexity of argparse
200
200
201 # What directory is mathjax in?
201 # What directory is mathjax in?
202 parser = argparse.ArgumentParser(
202 parser = argparse.ArgumentParser(
203 description="""Install mathjax from internet or local archive""",
203 description="""Install mathjax from internet or local archive""",
204 )
204 )
205
205
206 parser.add_argument(
206 parser.add_argument(
207 '-p',
207 '-p',
208 '--profile',
208 '--profile',
209 default='default',
209 default='default',
210 help='profile to install MathJax to (default is default)')
210 help='profile to install MathJax to (default is default)')
211
211
212 parser.add_argument(
212 parser.add_argument(
213 '-i',
213 '-i',
214 '--install-dir',
214 '--install-dir',
215 help='custom installation directory')
215 help='custom installation directory')
216
216
217 parser.add_argument(
217 parser.add_argument(
218 '-d',
218 '-d',
219 '--dest',
219 '--dest',
220 action='store_true',
220 action='store_true',
221 help='print where current mathjax would be installed and exit')
221 help='print where current mathjax would be installed and exit')
222 parser.add_argument(
222 parser.add_argument(
223 '-r',
223 '-r',
224 '--replace',
224 '--replace',
225 action='store_true',
225 action='store_true',
226 help='Whether to replace current mathjax if it already exists')
226 help='Whether to replace current mathjax if it already exists')
227 parser.add_argument(
227 parser.add_argument(
228 '-t',
228 '-t',
229 '--test',
229 '--test',
230 action='store_true')
230 action='store_true')
231 parser.add_argument('tarball',
231 parser.add_argument('tarball',
232 help="the local tar/zip-ball containing mathjax",
232 help="the local tar/zip-ball containing mathjax",
233 nargs='?',
233 nargs='?',
234 metavar='tarball')
234 metavar='tarball')
235
235
236 pargs = parser.parse_args()
236 pargs = parser.parse_args()
237
237
238 if pargs.install_dir:
238 if pargs.install_dir:
239 # Explicit install_dir overrides profile
239 # Explicit install_dir overrides profile
240 dest = pargs.install_dir
240 dest = pargs.install_dir
241 else:
241 else:
242 profile = pargs.profile
242 profile = pargs.profile
243 dest = os.path.join(locate_profile(profile), 'static', 'mathjax')
243 dest = os.path.join(locate_profile(profile), 'static', 'mathjax')
244
244
245 if pargs.dest :
245 if pargs.dest :
246 print dest
246 print dest
247 return
247 return
248
248
249 # remove/replace existing mathjax?
249 # remove/replace existing mathjax?
250 if pargs.replace :
250 if pargs.replace :
251 replace = True
251 replace = True
252 else :
252 else :
253 replace = False
253 replace = False
254
254
255 # undocumented test interface
255 # undocumented test interface
256 if pargs.test :
256 if pargs.test :
257 return test_func( replace, dest)
257 return test_func( replace, dest)
258
258
259 # do it
259 # do it
260 if pargs.tarball :
260 if pargs.tarball :
261 fname = pargs.tarball
261 fname = pargs.tarball
262
262
263 # automatically detect zip/tar - could do something based
263 # automatically detect zip/tar - could do something based
264 # on file content, but really not cost-effective here.
264 # on file content, but really not cost-effective here.
265 if fname.endswith('.zip') :
265 if fname.endswith('.zip') :
266 extractor = extract_zip
266 extractor = extract_zip
267 else :
267 else :
268 extractor = extract_tar
268 extractor = extract_tar
269 # do it
269 # do it
270 install_mathjax(file=open(fname, "r"), replace=replace, extractor=extractor, dest=dest )
270 install_mathjax(file=open(fname, "r"), replace=replace, extractor=extractor, dest=dest )
271 else:
271 else:
272 install_mathjax(replace=replace, dest=dest)
272 install_mathjax(replace=replace, dest=dest)
273
273
274
274
275 if __name__ == '__main__' :
275 if __name__ == '__main__' :
276 sys.exit(main())
276 sys.exit(main())
277
277
278 __all__ = ['install_mathjax', 'main', 'default_dest']
278 __all__ = ['install_mathjax', 'main', 'default_dest']
279
279
280 """
280 """
281 Test notes:
281 Test notes:
282
282
283 IPython uses IPython.testing.iptest as a custom test controller
283 IPython uses IPython.testing.iptest as a custom test controller
284 (though it is based on nose). It might be possible to fit automatic
284 (though it is based on nose). It might be possible to fit automatic
285 tests of installation into that framework, but it looks awkward to me.
285 tests of installation into that framework, but it looks awkward to me.
286 So, here is a manual procedure for testing this automatic installer.
286 So, here is a manual procedure for testing this automatic installer.
287
287
288 Mark Sienkiewicz, 2012-08-06
288 Mark Sienkiewicz, 2012-08-06
289 first 8 letters of my last name @ stsci.edu
289 first 8 letters of my last name @ stsci.edu
290
290
291 # remove mathjax from the installed ipython instance
291 # remove mathjax from the installed ipython instance
292 # IOError ok if mathjax was never installed yet.
292 # IOError ok if mathjax was never installed yet.
293
293
294 python -m IPython.external.mathjax --test -r
294 python -m IPython.external.mathjax --test -r
295
295
296 # download and install mathjax from command line:
296 # download and install mathjax from command line:
297
297
298 python -m IPython.external.mathjax
298 python -m IPython.external.mathjax
299 python -m IPython.external.mathjax --test -r
299 python -m IPython.external.mathjax --test -r
300
300
301 # download and install from within python
301 # download and install from within python
302
302
303 python -c "from IPython.external.mathjax import install_mathjax; install_mathjax()"
303 python -c "from IPython.external.mathjax import install_mathjax; install_mathjax()"
304 python -m IPython.external.mathjax --test -r
304 python -m IPython.external.mathjax --test -r
305
305
306 # view http://www.mathjax.org/download/ in your browser
306 # view http://www.mathjax.org/download/ in your browser
307 # save-as the link for MathJax-2.0 near the bottom of the page.
307 # save-as the link for MathJax-2.0 near the bottom of the page.
308 # The file it offers is mathjax-MathJax-v2.0-20-g07669ac.zip
308 # The file it offers is mathjax-MathJax-v2.0-20-g07669ac.zip
309
309
310 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.zip
310 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.zip
311 python -m IPython.external.mathjax --test -r
311 python -m IPython.external.mathjax --test -r
312
312
313 # download https://github.com/mathjax/MathJax/tarball/v2.0 in your browser
313 # download https://github.com/mathjax/MathJax/tarball/v2.0 in your browser
314 # (this is the url used internally by install_mathjax)
314 # (this is the url used internally by install_mathjax)
315 # The file it offers is mathjax-MathJax-v2.0-20-g07669ac.tar.gz
315 # The file it offers is mathjax-MathJax-v2.0-20-g07669ac.tar.gz
316
316
317 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.tar.gz
317 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.tar.gz
318
318
319 python -m IPython.external.mathjax --test
319 python -m IPython.external.mathjax --test
320 # note no -r
320 # note no -r
321
321
322 # install it again while it is already there
322 # install it again while it is already there
323
323
324 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.tar.gz
324 python -m IPython.external.mathjax mathjax-MathJax-v2.0-20-g07669ac.tar.gz
325 # says "offline MathJax apparently already installed"
325 # says "offline MathJax apparently already installed"
326
326
327 python -m IPython.external.mathjax ~/mathjax-MathJax-v2.0-20-g07669ac.tar.gz
327 python -m IPython.external.mathjax ~/mathjax-MathJax-v2.0-20-g07669ac.tar.gz
328 python -m IPython.external.mathjax --test
328 python -m IPython.external.mathjax --test
329
329
330
330
331 """
331 """
@@ -1,88 +1,88 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
2 # -*- coding: utf8 -*-
3 import argparse
4 import traceback
5 import json
3
6
4 from IPython.external.jsonschema import Draft3Validator, validate, ValidationError
7 from IPython.external.jsonschema import Draft3Validator, validate, ValidationError
5 import IPython.external.jsonpointer as jsonpointer
8 import IPython.external.jsonpointer as jsonpointer
6 from IPython.external import argparse
7 import traceback
8 import json
9
9
10 def nbvalidate(nbjson, schema='v3.withref.json', key=None,verbose=True):
10 def nbvalidate(nbjson, schema='v3.withref.json', key=None,verbose=True):
11 v3schema = resolve_ref(json.load(open(schema,'r')))
11 v3schema = resolve_ref(json.load(open(schema,'r')))
12 if key :
12 if key :
13 v3schema = jsonpointer.resolve_pointer(v3schema,key)
13 v3schema = jsonpointer.resolve_pointer(v3schema,key)
14 errors = 0
14 errors = 0
15 v = Draft3Validator(v3schema);
15 v = Draft3Validator(v3schema);
16 for error in v.iter_errors(nbjson):
16 for error in v.iter_errors(nbjson):
17 errors = errors + 1
17 errors = errors + 1
18 if verbose:
18 if verbose:
19 print(error)
19 print(error)
20 return errors
20 return errors
21
21
22 def resolve_ref(json, base=None):
22 def resolve_ref(json, base=None):
23 """return a json with resolved internal references
23 """return a json with resolved internal references
24
24
25 only support local reference to the same json
25 only support local reference to the same json
26 """
26 """
27 if not base :
27 if not base :
28 base = json
28 base = json
29
29
30 temp = None
30 temp = None
31 if type(json) is list:
31 if type(json) is list:
32 temp = [];
32 temp = [];
33 for item in json:
33 for item in json:
34 temp.append(resolve_ref(item, base=base))
34 temp.append(resolve_ref(item, base=base))
35 elif type(json) is dict:
35 elif type(json) is dict:
36 temp = {};
36 temp = {};
37 for key,value in json.iteritems():
37 for key,value in json.iteritems():
38 if key == '$ref':
38 if key == '$ref':
39 return resolve_ref(jsonpointer.resolve_pointer(base,value), base=base)
39 return resolve_ref(jsonpointer.resolve_pointer(base,value), base=base)
40 else :
40 else :
41 temp[key]=resolve_ref(value, base=base)
41 temp[key]=resolve_ref(value, base=base)
42 else :
42 else :
43 return json
43 return json
44 return temp
44 return temp
45
45
46 def convert(namein, nameout, indent=2):
46 def convert(namein, nameout, indent=2):
47 """resolve the references of namein, save the result in nameout"""
47 """resolve the references of namein, save the result in nameout"""
48 jsn = None
48 jsn = None
49 with open(namein) as file :
49 with open(namein) as file :
50 jsn = json.load(file)
50 jsn = json.load(file)
51 v = resolve_ref(jsn, base=jsn)
51 v = resolve_ref(jsn, base=jsn)
52 x = jsonpointer.resolve_pointer(v, '/notebook')
52 x = jsonpointer.resolve_pointer(v, '/notebook')
53 with open(nameout,'w') as file:
53 with open(nameout,'w') as file:
54 json.dump(x,file,indent=indent)
54 json.dump(x,file,indent=indent)
55
55
56
56
57 if __name__ == '__main__':
57 if __name__ == '__main__':
58 parser = argparse.ArgumentParser()
58 parser = argparse.ArgumentParser()
59 parser.add_argument('-s', '--schema',
59 parser.add_argument('-s', '--schema',
60 type=str, default='v3.withref.json')
60 type=str, default='v3.withref.json')
61
61
62 parser.add_argument('-k', '--key',
62 parser.add_argument('-k', '--key',
63 type=str, default='/notebook',
63 type=str, default='/notebook',
64 help='subkey to extract json schema from json file')
64 help='subkey to extract json schema from json file')
65
65
66 parser.add_argument("-v", "--verbose", action="store_true",
66 parser.add_argument("-v", "--verbose", action="store_true",
67 help="increase output verbosity")
67 help="increase output verbosity")
68
68
69 parser.add_argument('filename',
69 parser.add_argument('filename',
70 type=str,
70 type=str,
71 help="file to validate",
71 help="file to validate",
72 nargs='*',
72 nargs='*',
73 metavar='names')
73 metavar='names')
74
74
75 args = parser.parse_args()
75 args = parser.parse_args()
76 for name in args.filename :
76 for name in args.filename :
77 nerror = nbvalidate(json.load(open(name,'r')),
77 nerror = nbvalidate(json.load(open(name,'r')),
78 schema=args.schema,
78 schema=args.schema,
79 key=args.key,
79 key=args.key,
80 verbose=args.verbose)
80 verbose=args.verbose)
81 if nerror is 0:
81 if nerror is 0:
82 print u"[Pass]",name
82 print u"[Pass]",name
83 else :
83 else :
84 print u"[ ]",name,'(%d)'%(nerror)
84 print u"[ ]",name,'(%d)'%(nerror)
85 if args.verbose :
85 if args.verbose :
86 print '=================================================='
86 print '=================================================='
87
87
88
88
General Comments 0
You need to be logged in to leave comments. Login now