##// END OF EJS Templates
wrap helpstring output to 80 cols
MinRK -
Show More
@@ -1,362 +1,379 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Min RK
8 * Min RK
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 from copy import deepcopy
22 from copy import deepcopy
23 import logging
23 import logging
24 import re
24 import re
25 import sys
25 import sys
26
26
27 from IPython.config.configurable import SingletonConfigurable
27 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.loader import (
28 from IPython.config.loader import (
29 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
29 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 )
30 )
31
31
32 from IPython.utils.traitlets import (
32 from IPython.utils.traitlets import (
33 Unicode, List, Int, Enum, Dict, Instance
33 Unicode, List, Int, Enum, Dict, Instance
34 )
34 )
35 from IPython.utils.importstring import import_item
35 from IPython.utils.importstring import import_item
36 from IPython.utils.text import indent
36 from IPython.utils.text import indent, wrap_paragraphs, dedent
37
38 #-----------------------------------------------------------------------------
39 # function for re-wrapping a helpstring
40 #-----------------------------------------------------------------------------
37
41
38 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
39 # Descriptions for the various sections
43 # Descriptions for the various sections
40 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
41
45
42 flag_description = """
46 flag_description = """
43 Flags are command-line arguments passed as '--<flag>'.
47 Flags are command-line arguments passed as '--<flag>'.
44 These take no parameters, unlike regular key-value arguments.
48 These take no parameters, unlike regular key-value arguments.
45 They are typically used for setting boolean flags, or enabling
49 They are typically used for setting boolean flags, or enabling
46 modes that involve setting multiple options together.
50 modes that involve setting multiple options together.
51
52 Flags *always* begin with '--', never just one '-'.
47 """.strip() # trim newlines of front and back
53 """.strip() # trim newlines of front and back
48
54
49 alias_description = """
55 alias_description = """
50 These are commonly set parameters, given abbreviated aliases for convenience.
56 These are commonly set parameters, given abbreviated aliases for convenience.
51 They are set in the same `name=value` way as class parameters, where
57 They are set in the same `name=value` way as class parameters, where
52 <name> is replaced by the real parameter for which it is an alias.
58 <name> is replaced by the real parameter for which it is an alias.
53 """.strip() # trim newlines of front and back
59 """.strip() # trim newlines of front and back
54
60
55 keyvalue_description = """
61 keyvalue_description = """
56 Parameters are set from command-line arguments of the form:
62 Parameters are set from command-line arguments of the form:
57 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
63 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
58 This line is evaluated in Python, so simple expressions are allowed, e.g.
64 This line is evaluated in Python, so simple expressions are allowed, e.g.
59 `C.a='range(3)'` For setting C.a=[0,1,2]
65 `C.a='range(3)'` For setting C.a=[0,1,2]
60 """.strip() # trim newlines of front and back
66 """.strip() # trim newlines of front and back
61
67
62 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
63 # Application class
69 # Application class
64 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
65
71
66
72
67 class ApplicationError(Exception):
73 class ApplicationError(Exception):
68 pass
74 pass
69
75
70
76
71 class Application(SingletonConfigurable):
77 class Application(SingletonConfigurable):
72 """A singleton application with full configuration support."""
78 """A singleton application with full configuration support."""
73
79
74 # The name of the application, will usually match the name of the command
80 # The name of the application, will usually match the name of the command
75 # line application
81 # line application
76 name = Unicode(u'application')
82 name = Unicode(u'application')
77
83
78 # The description of the application that is printed at the beginning
84 # The description of the application that is printed at the beginning
79 # of the help.
85 # of the help.
80 description = Unicode(u'This is an application.')
86 description = Unicode(u'This is an application.')
81 # default section descriptions
87 # default section descriptions
82 flag_description = Unicode(flag_description)
88 flag_description = Unicode(flag_description)
83 alias_description = Unicode(alias_description)
89 alias_description = Unicode(alias_description)
84 keyvalue_description = Unicode(keyvalue_description)
90 keyvalue_description = Unicode(keyvalue_description)
85
91
86
92
87 # A sequence of Configurable subclasses whose config=True attributes will
93 # A sequence of Configurable subclasses whose config=True attributes will
88 # be exposed at the command line.
94 # be exposed at the command line.
89 classes = List([])
95 classes = List([])
90
96
91 # The version string of this application.
97 # The version string of this application.
92 version = Unicode(u'0.0')
98 version = Unicode(u'0.0')
93
99
94 # The log level for the application
100 # The log level for the application
95 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
101 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
96 config=True,
102 config=True,
97 help="Set the log level.")
103 help="Set the log level.")
98
104
99 # the alias map for configurables
105 # the alias map for configurables
100 aliases = Dict(dict(log_level='Application.log_level'))
106 aliases = Dict(dict(log_level='Application.log_level'))
101
107
102 # flags for loading Configurables or store_const style flags
108 # flags for loading Configurables or store_const style flags
103 # flags are loaded from this dict by '--key' flags
109 # flags are loaded from this dict by '--key' flags
104 # this must be a dict of two-tuples, the first element being the Config/dict
110 # this must be a dict of two-tuples, the first element being the Config/dict
105 # and the second being the help string for the flag
111 # and the second being the help string for the flag
106 flags = Dict()
112 flags = Dict()
113 def _flags_changed(self, name, old, new):
114 """ensure flags dict is valid"""
115 for key,value in new.iteritems():
116 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
117 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
118 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
119
107
120
108 # subcommands for launching other applications
121 # subcommands for launching other applications
109 # if this is not empty, this will be a parent Application
122 # if this is not empty, this will be a parent Application
110 # this must be a dict of two-tuples, the first element being the application class/import string
123 # this must be a dict of two-tuples,
124 # the first element being the application class/import string
111 # and the second being the help string for the subcommand
125 # and the second being the help string for the subcommand
112 subcommands = Dict()
126 subcommands = Dict()
113 # parse_command_line will initialize a subapp, if requested
127 # parse_command_line will initialize a subapp, if requested
114 subapp = Instance('IPython.config.application.Application', allow_none=True)
128 subapp = Instance('IPython.config.application.Application', allow_none=True)
115
129
116 # extra command-line arguments that don't set config values
130 # extra command-line arguments that don't set config values
117 extra_args = List(Unicode)
131 extra_args = List(Unicode)
118
132
119
133
120 def __init__(self, **kwargs):
134 def __init__(self, **kwargs):
121 SingletonConfigurable.__init__(self, **kwargs)
135 SingletonConfigurable.__init__(self, **kwargs)
122 # Add my class to self.classes so my attributes appear in command line
136 # Add my class to self.classes so my attributes appear in command line
123 # options.
137 # options.
124 self.classes.insert(0, self.__class__)
138 self.classes.insert(0, self.__class__)
125
139
126 # ensure self.flags dict is valid
127 for key,value in self.flags.iteritems():
128 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
129 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
130 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
131 self.init_logging()
140 self.init_logging()
132
141
133 def _config_changed(self, name, old, new):
142 def _config_changed(self, name, old, new):
134 SingletonConfigurable._config_changed(self, name, old, new)
143 SingletonConfigurable._config_changed(self, name, old, new)
135 self.log.debug('Config changed:')
144 self.log.debug('Config changed:')
136 self.log.debug(repr(new))
145 self.log.debug(repr(new))
137
146
138 def init_logging(self):
147 def init_logging(self):
139 """Start logging for this application.
148 """Start logging for this application.
140
149
141 The default is to log to stdout using a StreaHandler. The log level
150 The default is to log to stdout using a StreaHandler. The log level
142 starts at loggin.WARN, but this can be adjusted by setting the
151 starts at loggin.WARN, but this can be adjusted by setting the
143 ``log_level`` attribute.
152 ``log_level`` attribute.
144 """
153 """
145 self.log = logging.getLogger(self.__class__.__name__)
154 self.log = logging.getLogger(self.__class__.__name__)
146 self.log.setLevel(self.log_level)
155 self.log.setLevel(self.log_level)
147 self._log_handler = logging.StreamHandler()
156 self._log_handler = logging.StreamHandler()
148 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
157 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
149 self._log_handler.setFormatter(self._log_formatter)
158 self._log_handler.setFormatter(self._log_formatter)
150 self.log.addHandler(self._log_handler)
159 self.log.addHandler(self._log_handler)
151
160
152 def initialize(self, argv=None):
161 def initialize(self, argv=None):
153 """Do the basic steps to configure me.
162 """Do the basic steps to configure me.
154
163
155 Override in subclasses.
164 Override in subclasses.
156 """
165 """
157 self.parse_command_line(argv)
166 self.parse_command_line(argv)
158
167
159
168
160 def start(self):
169 def start(self):
161 """Start the app mainloop.
170 """Start the app mainloop.
162
171
163 Override in subclasses.
172 Override in subclasses.
164 """
173 """
165 if self.subapp is not None:
174 if self.subapp is not None:
166 return self.subapp.start()
175 return self.subapp.start()
167
176
168 def _log_level_changed(self, name, old, new):
177 def _log_level_changed(self, name, old, new):
169 """Adjust the log level when log_level is set."""
178 """Adjust the log level when log_level is set."""
170 self.log.setLevel(new)
179 self.log.setLevel(new)
171
180
172 def print_alias_help(self):
181 def print_alias_help(self):
173 """print the alias part of the help"""
182 """Print the alias part of the help."""
174 if not self.aliases:
183 if not self.aliases:
175 return
184 return
176
185
177 lines = ['Aliases']
186 lines = ['Aliases']
178 lines.append('-'*len(lines[0]))
187 lines.append('-'*len(lines[0]))
179 lines.append(self.alias_description)
180 lines.append('')
188 lines.append('')
189 for p in wrap_paragraphs(self.alias_description):
190 lines.append(p)
191 lines.append('')
181
192
182 classdict = {}
193 classdict = {}
183 for cls in self.classes:
194 for cls in self.classes:
184 # include all parents (up to, but excluding Configurable) in available names
195 # include all parents (up to, but excluding Configurable) in available names
185 for c in cls.mro()[:-3]:
196 for c in cls.mro()[:-3]:
186 classdict[c.__name__] = c
197 classdict[c.__name__] = c
187
198
188 for alias, longname in self.aliases.iteritems():
199 for alias, longname in self.aliases.iteritems():
189 classname, traitname = longname.split('.',1)
200 classname, traitname = longname.split('.',1)
190 cls = classdict[classname]
201 cls = classdict[classname]
191
202
192 trait = cls.class_traits(config=True)[traitname]
203 trait = cls.class_traits(config=True)[traitname]
193 help = cls.class_get_trait_help(trait)
204 help = cls.class_get_trait_help(trait)
194 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
205 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
195 lines.append(help)
206 lines.append(help)
196 lines.append('')
207 lines.append('')
197 print '\n'.join(lines)
208 print '\n'.join(lines)
198
209
199 def print_flag_help(self):
210 def print_flag_help(self):
200 """print the flag part of the help"""
211 """Print the flag part of the help."""
201 if not self.flags:
212 if not self.flags:
202 return
213 return
203
214
204 lines = ['Flags']
215 lines = ['Flags']
205 lines.append('-'*len(lines[0]))
216 lines.append('-'*len(lines[0]))
206 lines.append(self.flag_description)
207 lines.append('')
217 lines.append('')
218 for p in wrap_paragraphs(self.flag_description):
219 lines.append(p)
220 lines.append('')
208
221
209 for m, (cfg,help) in self.flags.iteritems():
222 for m, (cfg,help) in self.flags.iteritems():
210 lines.append('--'+m)
223 lines.append('--'+m)
211 lines.append(indent(help.strip(), flatten=True))
224 lines.append(indent(dedent(help.strip())))
212 lines.append('')
225 lines.append('')
213 print '\n'.join(lines)
226 print '\n'.join(lines)
214
227
215 def print_subcommands(self):
228 def print_subcommands(self):
216 """print the subcommand part of the help"""
229 """Print the subcommand part of the help."""
217 if not self.subcommands:
230 if not self.subcommands:
218 return
231 return
219
232
220 lines = ["Subcommands"]
233 lines = ["Subcommands"]
221 lines.append('-'*len(lines[0]))
234 lines.append('-'*len(lines[0]))
222 for subc, (cls,help) in self.subcommands.iteritems():
235 for subc, (cls,help) in self.subcommands.iteritems():
223 lines.append("%s : %s"%(subc, cls))
236 lines.append("%s : %s"%(subc, cls))
224 if help:
237 if help:
225 lines.append(indent(help.strip(), flatten=True))
238 lines.append(indent(dedent(help.strip())))
226 lines.append('')
239 lines.append('')
227 print '\n'.join(lines)
240 print '\n'.join(lines)
228
241
229 def print_help(self, classes=False):
242 def print_help(self, classes=False):
230 """Print the help for each Configurable class in self.classes.
243 """Print the help for each Configurable class in self.classes.
231
244
232 If classes=False (the default), only flags and aliases are printed
245 If classes=False (the default), only flags and aliases are printed.
233 """
246 """
234 self.print_subcommands()
247 self.print_subcommands()
235 self.print_flag_help()
248 self.print_flag_help()
236 self.print_alias_help()
249 self.print_alias_help()
237
250
238 if classes:
251 if classes:
239 if self.classes:
252 if self.classes:
240 print "Class parameters"
253 print "Class parameters"
241 print "----------------"
254 print "----------------"
242 print self.keyvalue_description
243 print
255 print
256 for p in wrap_paragraphs(self.keyvalue_description):
257 print p
258 print
244
259
245 for cls in self.classes:
260 for cls in self.classes:
246 cls.class_print_help()
261 cls.class_print_help()
247 print
262 print
248 else:
263 else:
249 print "To see all available configurables, use `--help-all`"
264 print "To see all available configurables, use `--help-all`"
250 print
265 print
251
266
252 def print_description(self):
267 def print_description(self):
253 """Print the application description."""
268 """Print the application description."""
254 print self.description
269 for p in wrap_paragraphs(self.description):
255 print
270 print p
271 print
256
272
257 def print_version(self):
273 def print_version(self):
258 """Print the version string."""
274 """Print the version string."""
259 print self.version
275 print self.version
260
276
261 def update_config(self, config):
277 def update_config(self, config):
262 """Fire the traits events when the config is updated."""
278 """Fire the traits events when the config is updated."""
263 # Save a copy of the current config.
279 # Save a copy of the current config.
264 newconfig = deepcopy(self.config)
280 newconfig = deepcopy(self.config)
265 # Merge the new config into the current one.
281 # Merge the new config into the current one.
266 newconfig._merge(config)
282 newconfig._merge(config)
267 # Save the combined config as self.config, which triggers the traits
283 # Save the combined config as self.config, which triggers the traits
268 # events.
284 # events.
269 self.config = newconfig
285 self.config = newconfig
270
286
271 def initialize_subcommand(self, subc, argv=None):
287 def initialize_subcommand(self, subc, argv=None):
272 """Initialize a subcommand with argv"""
288 """Initialize a subcommand with argv."""
273 subapp,help = self.subcommands.get(subc)
289 subapp,help = self.subcommands.get(subc)
274
290
275 if isinstance(subapp, basestring):
291 if isinstance(subapp, basestring):
276 subapp = import_item(subapp)
292 subapp = import_item(subapp)
277
293
278 # clear existing instances
294 # clear existing instances
279 self.__class__.clear_instance()
295 self.__class__.clear_instance()
280 # instantiate
296 # instantiate
281 self.subapp = subapp.instance()
297 self.subapp = subapp.instance()
282 # and initialize subapp
298 # and initialize subapp
283 self.subapp.initialize(argv)
299 self.subapp.initialize(argv)
284
300
285 def parse_command_line(self, argv=None):
301 def parse_command_line(self, argv=None):
286 """Parse the command line arguments."""
302 """Parse the command line arguments."""
287 argv = sys.argv[1:] if argv is None else argv
303 argv = sys.argv[1:] if argv is None else argv
288
304
289 if self.subcommands and len(argv) > 0:
305 if self.subcommands and len(argv) > 0:
290 # we have subcommands, and one may have been specified
306 # we have subcommands, and one may have been specified
291 subc, subargv = argv[0], argv[1:]
307 subc, subargv = argv[0], argv[1:]
292 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
308 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
293 # it's a subcommand, and *not* a flag or class parameter
309 # it's a subcommand, and *not* a flag or class parameter
294 return self.initialize_subcommand(subc, subargv)
310 return self.initialize_subcommand(subc, subargv)
295
311
296 if '-h' in argv or '--help' in argv or '--help-all' in argv:
312 if '-h' in argv or '--help' in argv or '--help-all' in argv:
297 self.print_description()
313 self.print_description()
298 self.print_help('--help-all' in argv)
314 self.print_help('--help-all' in argv)
299 self.exit(0)
315 self.exit(0)
300
316
301 if '--version' in argv:
317 if '--version' in argv:
302 self.print_version()
318 self.print_version()
303 self.exit(0)
319 self.exit(0)
304
320
305 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
321 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
306 flags=self.flags)
322 flags=self.flags)
307 try:
323 try:
308 config = loader.load_config()
324 config = loader.load_config()
309 except ArgumentError as e:
325 except ArgumentError as e:
310 self.log.fatal(str(e))
326 self.log.fatal(str(e))
311 self.print_description()
327 self.print_description()
312 self.print_help()
328 self.print_help()
313 self.exit(1)
329 self.exit(1)
314 self.update_config(config)
330 self.update_config(config)
315 # store unparsed args in extra_args
331 # store unparsed args in extra_args
316 self.extra_args = loader.extra_args
332 self.extra_args = loader.extra_args
317
333
318 def load_config_file(self, filename, path=None):
334 def load_config_file(self, filename, path=None):
319 """Load a .py based config file by filename and path."""
335 """Load a .py based config file by filename and path."""
320 loader = PyFileConfigLoader(filename, path=path)
336 loader = PyFileConfigLoader(filename, path=path)
321 config = loader.load_config()
337 config = loader.load_config()
322 self.update_config(config)
338 self.update_config(config)
323
339
324 def exit(self, exit_status=0):
340 def exit(self, exit_status=0):
325 self.log.debug("Exiting application: %s" % self.name)
341 self.log.debug("Exiting application: %s" % self.name)
326 sys.exit(exit_status)
342 sys.exit(exit_status)
327
343
328 #-----------------------------------------------------------------------------
344 #-----------------------------------------------------------------------------
329 # utility functions, for convenience
345 # utility functions, for convenience
330 #-----------------------------------------------------------------------------
346 #-----------------------------------------------------------------------------
331
347
332 def boolean_flag(name, configurable, set_help='', unset_help=''):
348 def boolean_flag(name, configurable, set_help='', unset_help=''):
333 """helper for building basic --trait, --no-trait flags
349 """Helper for building basic --trait, --no-trait flags.
334
350
335 Parameters
351 Parameters
336 ----------
352 ----------
337
353
338 name : str
354 name : str
339 The name of the flag.
355 The name of the flag.
340 configurable : str
356 configurable : str
341 The 'Class.trait' string of the trait to be set/unset with the flag
357 The 'Class.trait' string of the trait to be set/unset with the flag
342 set_help : unicode
358 set_help : unicode
343 help string for --name flag
359 help string for --name flag
344 unset_help : unicode
360 unset_help : unicode
345 help string for --no-name flag
361 help string for --no-name flag
346
362
347 Returns
363 Returns
348 -------
364 -------
349
365
350 cfg : dict
366 cfg : dict
351 A dict with two keys: 'name', and 'no-name', for setting and unsetting
367 A dict with two keys: 'name', and 'no-name', for setting and unsetting
352 the trait, respectively.
368 the trait, respectively.
353 """
369 """
354 # default helpstrings
370 # default helpstrings
355 set_help = set_help or "set %s=True"%configurable
371 set_help = set_help or "set %s=True"%configurable
356 unset_help = unset_help or "set %s=False"%configurable
372 unset_help = unset_help or "set %s=False"%configurable
357
373
358 cls,trait = configurable.split('.')
374 cls,trait = configurable.split('.')
359
375
360 setter = {cls : {trait : True}}
376 setter = {cls : {trait : True}}
361 unsetter = {cls : {trait : False}}
377 unsetter = {cls : {trait : False}}
362 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
378 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
379
@@ -1,279 +1,283 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A base class for objects that are configurable.
4 A base class for objects that are configurable.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez
9 * Fernando Perez
10 * Min RK
10 * Min RK
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 from copy import deepcopy
25 import datetime
24 import datetime
25 from copy import deepcopy
26
26
27 from loader import Config
27 from loader import Config
28 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.traitlets import HasTraits, Instance
29 from IPython.utils.text import indent
29 from IPython.utils.text import indent, wrap_paragraphs
30
30
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Helper classes for Configurables
33 # Helper classes for Configurables
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class ConfigurableError(Exception):
37 class ConfigurableError(Exception):
38 pass
38 pass
39
39
40
40
41 class MultipleInstanceError(ConfigurableError):
41 class MultipleInstanceError(ConfigurableError):
42 pass
42 pass
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Configurable implementation
45 # Configurable implementation
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 class Configurable(HasTraits):
48 class Configurable(HasTraits):
49
49
50 config = Instance(Config,(),{})
50 config = Instance(Config,(),{})
51 created = None
51 created = None
52
52
53 def __init__(self, **kwargs):
53 def __init__(self, **kwargs):
54 """Create a conigurable given a config config.
54 """Create a configurable given a config config.
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 config : Config
58 config : Config
59 If this is empty, default values are used. If config is a
59 If this is empty, default values are used. If config is a
60 :class:`Config` instance, it will be used to configure the
60 :class:`Config` instance, it will be used to configure the
61 instance.
61 instance.
62
62
63 Notes
63 Notes
64 -----
64 -----
65 Subclasses of Configurable must call the :meth:`__init__` method of
65 Subclasses of Configurable must call the :meth:`__init__` method of
66 :class:`Configurable` *before* doing anything else and using
66 :class:`Configurable` *before* doing anything else and using
67 :func:`super`::
67 :func:`super`::
68
68
69 class MyConfigurable(Configurable):
69 class MyConfigurable(Configurable):
70 def __init__(self, config=None):
70 def __init__(self, config=None):
71 super(MyConfigurable, self).__init__(config)
71 super(MyConfigurable, self).__init__(config)
72 # Then any other code you need to finish initialization.
72 # Then any other code you need to finish initialization.
73
73
74 This ensures that instances will be configured properly.
74 This ensures that instances will be configured properly.
75 """
75 """
76 config = kwargs.pop('config', None)
76 config = kwargs.pop('config', None)
77 if config is not None:
77 if config is not None:
78 # We used to deepcopy, but for now we are trying to just save
78 # We used to deepcopy, but for now we are trying to just save
79 # by reference. This *could* have side effects as all components
79 # by reference. This *could* have side effects as all components
80 # will share config. In fact, I did find such a side effect in
80 # will share config. In fact, I did find such a side effect in
81 # _config_changed below. If a config attribute value was a mutable type
81 # _config_changed below. If a config attribute value was a mutable type
82 # all instances of a component were getting the same copy, effectively
82 # all instances of a component were getting the same copy, effectively
83 # making that a class attribute.
83 # making that a class attribute.
84 # self.config = deepcopy(config)
84 # self.config = deepcopy(config)
85 self.config = config
85 self.config = config
86 # This should go second so individual keyword arguments override
86 # This should go second so individual keyword arguments override
87 # the values in config.
87 # the values in config.
88 super(Configurable, self).__init__(**kwargs)
88 super(Configurable, self).__init__(**kwargs)
89 self.created = datetime.datetime.now()
89 self.created = datetime.datetime.now()
90
90
91 #-------------------------------------------------------------------------
91 #-------------------------------------------------------------------------
92 # Static trait notifiations
92 # Static trait notifiations
93 #-------------------------------------------------------------------------
93 #-------------------------------------------------------------------------
94
94
95 def _config_changed(self, name, old, new):
95 def _config_changed(self, name, old, new):
96 """Update all the class traits having ``config=True`` as metadata.
96 """Update all the class traits having ``config=True`` as metadata.
97
97
98 For any class trait with a ``config`` metadata attribute that is
98 For any class trait with a ``config`` metadata attribute that is
99 ``True``, we update the trait with the value of the corresponding
99 ``True``, we update the trait with the value of the corresponding
100 config entry.
100 config entry.
101 """
101 """
102 # Get all traits with a config metadata entry that is True
102 # Get all traits with a config metadata entry that is True
103 traits = self.traits(config=True)
103 traits = self.traits(config=True)
104
104
105 # We auto-load config section for this class as well as any parent
105 # We auto-load config section for this class as well as any parent
106 # classes that are Configurable subclasses. This starts with Configurable
106 # classes that are Configurable subclasses. This starts with Configurable
107 # and works down the mro loading the config for each section.
107 # and works down the mro loading the config for each section.
108 section_names = [cls.__name__ for cls in \
108 section_names = [cls.__name__ for cls in \
109 reversed(self.__class__.__mro__) if
109 reversed(self.__class__.__mro__) if
110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
111
111
112 for sname in section_names:
112 for sname in section_names:
113 # Don't do a blind getattr as that would cause the config to
113 # Don't do a blind getattr as that would cause the config to
114 # dynamically create the section with name self.__class__.__name__.
114 # dynamically create the section with name self.__class__.__name__.
115 if new._has_section(sname):
115 if new._has_section(sname):
116 my_config = new[sname]
116 my_config = new[sname]
117 for k, v in traits.iteritems():
117 for k, v in traits.iteritems():
118 # Don't allow traitlets with config=True to start with
118 # Don't allow traitlets with config=True to start with
119 # uppercase. Otherwise, they are confused with Config
119 # uppercase. Otherwise, they are confused with Config
120 # subsections. But, developers shouldn't have uppercase
120 # subsections. But, developers shouldn't have uppercase
121 # attributes anyways! (PEP 6)
121 # attributes anyways! (PEP 6)
122 if k[0].upper()==k[0] and not k.startswith('_'):
122 if k[0].upper()==k[0] and not k.startswith('_'):
123 raise ConfigurableError('Configurable traitlets with '
123 raise ConfigurableError('Configurable traitlets with '
124 'config=True must start with a lowercase so they are '
124 'config=True must start with a lowercase so they are '
125 'not confused with Config subsections: %s.%s' % \
125 'not confused with Config subsections: %s.%s' % \
126 (self.__class__.__name__, k))
126 (self.__class__.__name__, k))
127 try:
127 try:
128 # Here we grab the value from the config
128 # Here we grab the value from the config
129 # If k has the naming convention of a config
129 # If k has the naming convention of a config
130 # section, it will be auto created.
130 # section, it will be auto created.
131 config_value = my_config[k]
131 config_value = my_config[k]
132 except KeyError:
132 except KeyError:
133 pass
133 pass
134 else:
134 else:
135 # print "Setting %s.%s from %s.%s=%r" % \
135 # print "Setting %s.%s from %s.%s=%r" % \
136 # (self.__class__.__name__,k,sname,k,config_value)
136 # (self.__class__.__name__,k,sname,k,config_value)
137 # We have to do a deepcopy here if we don't deepcopy the entire
137 # We have to do a deepcopy here if we don't deepcopy the entire
138 # config object. If we don't, a mutable config_value will be
138 # config object. If we don't, a mutable config_value will be
139 # shared by all instances, effectively making it a class attribute.
139 # shared by all instances, effectively making it a class attribute.
140 setattr(self, k, deepcopy(config_value))
140 setattr(self, k, deepcopy(config_value))
141
141
142 @classmethod
142 @classmethod
143 def class_get_help(cls):
143 def class_get_help(cls):
144 """Get the help string for this class in ReST format."""
144 """Get the help string for this class in ReST format."""
145 cls_traits = cls.class_traits(config=True)
145 cls_traits = cls.class_traits(config=True)
146 final_help = []
146 final_help = []
147 final_help.append(u'%s options' % cls.__name__)
147 final_help.append(u'%s options' % cls.__name__)
148 final_help.append(len(final_help[0])*u'-')
148 final_help.append(len(final_help[0])*u'-')
149 for k,v in cls.class_traits(config=True).iteritems():
149 for k,v in cls.class_traits(config=True).iteritems():
150 help = cls.class_get_trait_help(v)
150 help = cls.class_get_trait_help(v)
151 final_help.append(help)
151 final_help.append(help)
152 return '\n'.join(final_help)
152 return '\n'.join(final_help)
153
153
154 @classmethod
154 @classmethod
155 def class_get_trait_help(cls, trait):
155 def class_get_trait_help(cls, trait):
156 """Get the help string for a single """
156 """Get the help string for a single trait."""
157 lines = []
157 lines = []
158 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
158 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
159 lines.append(header)
159 try:
160 try:
160 dvr = repr(trait.get_default_value())
161 dvr = repr(trait.get_default_value())
161 except Exception:
162 except Exception:
162 dvr = None # ignore defaults we can't construct
163 dvr = None # ignore defaults we can't construct
163 if dvr is not None:
164 if dvr is not None:
164 header += ' [default: %s]'%dvr
165 if len(dvr) > 64:
165 lines.append(header)
166 dvr = dvr[:61]+'...'
167 lines.append(indent('Default: %s'%dvr, 4))
168 if 'Enum' in trait.__class__.__name__:
169 # include Enum choices
170 lines.append(indent('Choices: %r'%(trait.values,)))
166
171
167 help = trait.get_metadata('help')
172 help = trait.get_metadata('help')
168 if help is not None:
173 if help is not None:
169 lines.append(indent(help.strip(), flatten=True))
174 help = '\n'.join(wrap_paragraphs(help, 76))
170 if 'Enum' in trait.__class__.__name__:
175 lines.append(indent(help, 4))
171 # include Enum choices
172 lines.append(indent('Choices: %r'%(trait.values,), flatten=True))
173 return '\n'.join(lines)
176 return '\n'.join(lines)
174
177
175 @classmethod
178 @classmethod
176 def class_print_help(cls):
179 def class_print_help(cls):
180 """Get the help string for a single trait and print it."""
177 print cls.class_get_help()
181 print cls.class_get_help()
178
182
179
183
180 class SingletonConfigurable(Configurable):
184 class SingletonConfigurable(Configurable):
181 """A configurable that only allows one instance.
185 """A configurable that only allows one instance.
182
186
183 This class is for classes that should only have one instance of itself
187 This class is for classes that should only have one instance of itself
184 or *any* subclass. To create and retrieve such a class use the
188 or *any* subclass. To create and retrieve such a class use the
185 :meth:`SingletonConfigurable.instance` method.
189 :meth:`SingletonConfigurable.instance` method.
186 """
190 """
187
191
188 _instance = None
192 _instance = None
189
193
190 @classmethod
194 @classmethod
191 def _walk_mro(cls):
195 def _walk_mro(cls):
192 """Walk the cls.mro() for parent classes that are also singletons
196 """Walk the cls.mro() for parent classes that are also singletons
193
197
194 For use in instance()
198 For use in instance()
195 """
199 """
196
200
197 for subclass in cls.mro():
201 for subclass in cls.mro():
198 if issubclass(cls, subclass) and \
202 if issubclass(cls, subclass) and \
199 issubclass(subclass, SingletonConfigurable) and \
203 issubclass(subclass, SingletonConfigurable) and \
200 subclass != SingletonConfigurable:
204 subclass != SingletonConfigurable:
201 yield subclass
205 yield subclass
202
206
203 @classmethod
207 @classmethod
204 def clear_instance(cls):
208 def clear_instance(cls):
205 """unset _instance for this class and singleton parents.
209 """unset _instance for this class and singleton parents.
206 """
210 """
207 if not cls.initialized():
211 if not cls.initialized():
208 return
212 return
209 for subclass in cls._walk_mro():
213 for subclass in cls._walk_mro():
210 if isinstance(subclass._instance, cls):
214 if isinstance(subclass._instance, cls):
211 # only clear instances that are instances
215 # only clear instances that are instances
212 # of the calling class
216 # of the calling class
213 subclass._instance = None
217 subclass._instance = None
214
218
215 @classmethod
219 @classmethod
216 def instance(cls, *args, **kwargs):
220 def instance(cls, *args, **kwargs):
217 """Returns a global instance of this class.
221 """Returns a global instance of this class.
218
222
219 This method create a new instance if none have previously been created
223 This method create a new instance if none have previously been created
220 and returns a previously created instance is one already exists.
224 and returns a previously created instance is one already exists.
221
225
222 The arguments and keyword arguments passed to this method are passed
226 The arguments and keyword arguments passed to this method are passed
223 on to the :meth:`__init__` method of the class upon instantiation.
227 on to the :meth:`__init__` method of the class upon instantiation.
224
228
225 Examples
229 Examples
226 --------
230 --------
227
231
228 Create a singleton class using instance, and retrieve it::
232 Create a singleton class using instance, and retrieve it::
229
233
230 >>> from IPython.config.configurable import SingletonConfigurable
234 >>> from IPython.config.configurable import SingletonConfigurable
231 >>> class Foo(SingletonConfigurable): pass
235 >>> class Foo(SingletonConfigurable): pass
232 >>> foo = Foo.instance()
236 >>> foo = Foo.instance()
233 >>> foo == Foo.instance()
237 >>> foo == Foo.instance()
234 True
238 True
235
239
236 Create a subclass that is retrived using the base class instance::
240 Create a subclass that is retrived using the base class instance::
237
241
238 >>> class Bar(SingletonConfigurable): pass
242 >>> class Bar(SingletonConfigurable): pass
239 >>> class Bam(Bar): pass
243 >>> class Bam(Bar): pass
240 >>> bam = Bam.instance()
244 >>> bam = Bam.instance()
241 >>> bam == Bar.instance()
245 >>> bam == Bar.instance()
242 True
246 True
243 """
247 """
244 # Create and save the instance
248 # Create and save the instance
245 if cls._instance is None:
249 if cls._instance is None:
246 inst = cls(*args, **kwargs)
250 inst = cls(*args, **kwargs)
247 # Now make sure that the instance will also be returned by
251 # Now make sure that the instance will also be returned by
248 # parent classes' _instance attribute.
252 # parent classes' _instance attribute.
249 for subclass in cls._walk_mro():
253 for subclass in cls._walk_mro():
250 subclass._instance = inst
254 subclass._instance = inst
251
255
252 if isinstance(cls._instance, cls):
256 if isinstance(cls._instance, cls):
253 return cls._instance
257 return cls._instance
254 else:
258 else:
255 raise MultipleInstanceError(
259 raise MultipleInstanceError(
256 'Multiple incompatible subclass instances of '
260 'Multiple incompatible subclass instances of '
257 '%s are being created.' % cls.__name__
261 '%s are being created.' % cls.__name__
258 )
262 )
259
263
260 @classmethod
264 @classmethod
261 def initialized(cls):
265 def initialized(cls):
262 """Has an instance been created?"""
266 """Has an instance been created?"""
263 return hasattr(cls, "_instance") and cls._instance is not None
267 return hasattr(cls, "_instance") and cls._instance is not None
264
268
265
269
266 class LoggingConfigurable(Configurable):
270 class LoggingConfigurable(Configurable):
267 """A parent class for Configurables that log.
271 """A parent class for Configurables that log.
268
272
269 Subclasses have a log trait, and the default behavior
273 Subclasses have a log trait, and the default behavior
270 is to get the logger from the currently running Application
274 is to get the logger from the currently running Application
271 via Application.instance().log.
275 via Application.instance().log.
272 """
276 """
273
277
274 log = Instance('logging.Logger')
278 log = Instance('logging.Logger')
275 def _log_default(self):
279 def _log_default(self):
276 from IPython.config.application import Application
280 from IPython.config.application import Application
277 return Application.instance().log
281 return Application.instance().log
278
282
279 No newline at end of file
283
@@ -1,560 +1,613 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import __main__
17 import __main__
18
18
19 import os
19 import os
20 import re
20 import re
21 import shutil
21 import shutil
22 import textwrap
22 from string import Formatter
23 from string import Formatter
23
24
24 from IPython.external.path import path
25 from IPython.external.path import path
25
26
26 from IPython.utils.io import nlprint
27 from IPython.utils.io import nlprint
27 from IPython.utils.data import flatten
28 from IPython.utils.data import flatten
28
29
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30 # Code
31 # Code
31 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
32
33
33
34
34 def unquote_ends(istr):
35 def unquote_ends(istr):
35 """Remove a single pair of quotes from the endpoints of a string."""
36 """Remove a single pair of quotes from the endpoints of a string."""
36
37
37 if not istr:
38 if not istr:
38 return istr
39 return istr
39 if (istr[0]=="'" and istr[-1]=="'") or \
40 if (istr[0]=="'" and istr[-1]=="'") or \
40 (istr[0]=='"' and istr[-1]=='"'):
41 (istr[0]=='"' and istr[-1]=='"'):
41 return istr[1:-1]
42 return istr[1:-1]
42 else:
43 else:
43 return istr
44 return istr
44
45
45
46
46 class LSString(str):
47 class LSString(str):
47 """String derivative with a special access attributes.
48 """String derivative with a special access attributes.
48
49
49 These are normal strings, but with the special attributes:
50 These are normal strings, but with the special attributes:
50
51
51 .l (or .list) : value as list (split on newlines).
52 .l (or .list) : value as list (split on newlines).
52 .n (or .nlstr): original value (the string itself).
53 .n (or .nlstr): original value (the string itself).
53 .s (or .spstr): value as whitespace-separated string.
54 .s (or .spstr): value as whitespace-separated string.
54 .p (or .paths): list of path objects
55 .p (or .paths): list of path objects
55
56
56 Any values which require transformations are computed only once and
57 Any values which require transformations are computed only once and
57 cached.
58 cached.
58
59
59 Such strings are very useful to efficiently interact with the shell, which
60 Such strings are very useful to efficiently interact with the shell, which
60 typically only understands whitespace-separated options for commands."""
61 typically only understands whitespace-separated options for commands."""
61
62
62 def get_list(self):
63 def get_list(self):
63 try:
64 try:
64 return self.__list
65 return self.__list
65 except AttributeError:
66 except AttributeError:
66 self.__list = self.split('\n')
67 self.__list = self.split('\n')
67 return self.__list
68 return self.__list
68
69
69 l = list = property(get_list)
70 l = list = property(get_list)
70
71
71 def get_spstr(self):
72 def get_spstr(self):
72 try:
73 try:
73 return self.__spstr
74 return self.__spstr
74 except AttributeError:
75 except AttributeError:
75 self.__spstr = self.replace('\n',' ')
76 self.__spstr = self.replace('\n',' ')
76 return self.__spstr
77 return self.__spstr
77
78
78 s = spstr = property(get_spstr)
79 s = spstr = property(get_spstr)
79
80
80 def get_nlstr(self):
81 def get_nlstr(self):
81 return self
82 return self
82
83
83 n = nlstr = property(get_nlstr)
84 n = nlstr = property(get_nlstr)
84
85
85 def get_paths(self):
86 def get_paths(self):
86 try:
87 try:
87 return self.__paths
88 return self.__paths
88 except AttributeError:
89 except AttributeError:
89 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
90 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
90 return self.__paths
91 return self.__paths
91
92
92 p = paths = property(get_paths)
93 p = paths = property(get_paths)
93
94
94 # FIXME: We need to reimplement type specific displayhook and then add this
95 # FIXME: We need to reimplement type specific displayhook and then add this
95 # back as a custom printer. This should also be moved outside utils into the
96 # back as a custom printer. This should also be moved outside utils into the
96 # core.
97 # core.
97
98
98 # def print_lsstring(arg):
99 # def print_lsstring(arg):
99 # """ Prettier (non-repr-like) and more informative printer for LSString """
100 # """ Prettier (non-repr-like) and more informative printer for LSString """
100 # print "LSString (.p, .n, .l, .s available). Value:"
101 # print "LSString (.p, .n, .l, .s available). Value:"
101 # print arg
102 # print arg
102 #
103 #
103 #
104 #
104 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
105 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
105
106
106
107
107 class SList(list):
108 class SList(list):
108 """List derivative with a special access attributes.
109 """List derivative with a special access attributes.
109
110
110 These are normal lists, but with the special attributes:
111 These are normal lists, but with the special attributes:
111
112
112 .l (or .list) : value as list (the list itself).
113 .l (or .list) : value as list (the list itself).
113 .n (or .nlstr): value as a string, joined on newlines.
114 .n (or .nlstr): value as a string, joined on newlines.
114 .s (or .spstr): value as a string, joined on spaces.
115 .s (or .spstr): value as a string, joined on spaces.
115 .p (or .paths): list of path objects
116 .p (or .paths): list of path objects
116
117
117 Any values which require transformations are computed only once and
118 Any values which require transformations are computed only once and
118 cached."""
119 cached."""
119
120
120 def get_list(self):
121 def get_list(self):
121 return self
122 return self
122
123
123 l = list = property(get_list)
124 l = list = property(get_list)
124
125
125 def get_spstr(self):
126 def get_spstr(self):
126 try:
127 try:
127 return self.__spstr
128 return self.__spstr
128 except AttributeError:
129 except AttributeError:
129 self.__spstr = ' '.join(self)
130 self.__spstr = ' '.join(self)
130 return self.__spstr
131 return self.__spstr
131
132
132 s = spstr = property(get_spstr)
133 s = spstr = property(get_spstr)
133
134
134 def get_nlstr(self):
135 def get_nlstr(self):
135 try:
136 try:
136 return self.__nlstr
137 return self.__nlstr
137 except AttributeError:
138 except AttributeError:
138 self.__nlstr = '\n'.join(self)
139 self.__nlstr = '\n'.join(self)
139 return self.__nlstr
140 return self.__nlstr
140
141
141 n = nlstr = property(get_nlstr)
142 n = nlstr = property(get_nlstr)
142
143
143 def get_paths(self):
144 def get_paths(self):
144 try:
145 try:
145 return self.__paths
146 return self.__paths
146 except AttributeError:
147 except AttributeError:
147 self.__paths = [path(p) for p in self if os.path.exists(p)]
148 self.__paths = [path(p) for p in self if os.path.exists(p)]
148 return self.__paths
149 return self.__paths
149
150
150 p = paths = property(get_paths)
151 p = paths = property(get_paths)
151
152
152 def grep(self, pattern, prune = False, field = None):
153 def grep(self, pattern, prune = False, field = None):
153 """ Return all strings matching 'pattern' (a regex or callable)
154 """ Return all strings matching 'pattern' (a regex or callable)
154
155
155 This is case-insensitive. If prune is true, return all items
156 This is case-insensitive. If prune is true, return all items
156 NOT matching the pattern.
157 NOT matching the pattern.
157
158
158 If field is specified, the match must occur in the specified
159 If field is specified, the match must occur in the specified
159 whitespace-separated field.
160 whitespace-separated field.
160
161
161 Examples::
162 Examples::
162
163
163 a.grep( lambda x: x.startswith('C') )
164 a.grep( lambda x: x.startswith('C') )
164 a.grep('Cha.*log', prune=1)
165 a.grep('Cha.*log', prune=1)
165 a.grep('chm', field=-1)
166 a.grep('chm', field=-1)
166 """
167 """
167
168
168 def match_target(s):
169 def match_target(s):
169 if field is None:
170 if field is None:
170 return s
171 return s
171 parts = s.split()
172 parts = s.split()
172 try:
173 try:
173 tgt = parts[field]
174 tgt = parts[field]
174 return tgt
175 return tgt
175 except IndexError:
176 except IndexError:
176 return ""
177 return ""
177
178
178 if isinstance(pattern, basestring):
179 if isinstance(pattern, basestring):
179 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
180 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
180 else:
181 else:
181 pred = pattern
182 pred = pattern
182 if not prune:
183 if not prune:
183 return SList([el for el in self if pred(match_target(el))])
184 return SList([el for el in self if pred(match_target(el))])
184 else:
185 else:
185 return SList([el for el in self if not pred(match_target(el))])
186 return SList([el for el in self if not pred(match_target(el))])
186
187
187 def fields(self, *fields):
188 def fields(self, *fields):
188 """ Collect whitespace-separated fields from string list
189 """ Collect whitespace-separated fields from string list
189
190
190 Allows quick awk-like usage of string lists.
191 Allows quick awk-like usage of string lists.
191
192
192 Example data (in var a, created by 'a = !ls -l')::
193 Example data (in var a, created by 'a = !ls -l')::
193 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
194 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
194 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
195 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
195
196
196 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
197 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
197 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
198 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
198 (note the joining by space).
199 (note the joining by space).
199 a.fields(-1) is ['ChangeLog', 'IPython']
200 a.fields(-1) is ['ChangeLog', 'IPython']
200
201
201 IndexErrors are ignored.
202 IndexErrors are ignored.
202
203
203 Without args, fields() just split()'s the strings.
204 Without args, fields() just split()'s the strings.
204 """
205 """
205 if len(fields) == 0:
206 if len(fields) == 0:
206 return [el.split() for el in self]
207 return [el.split() for el in self]
207
208
208 res = SList()
209 res = SList()
209 for el in [f.split() for f in self]:
210 for el in [f.split() for f in self]:
210 lineparts = []
211 lineparts = []
211
212
212 for fd in fields:
213 for fd in fields:
213 try:
214 try:
214 lineparts.append(el[fd])
215 lineparts.append(el[fd])
215 except IndexError:
216 except IndexError:
216 pass
217 pass
217 if lineparts:
218 if lineparts:
218 res.append(" ".join(lineparts))
219 res.append(" ".join(lineparts))
219
220
220 return res
221 return res
221
222
222 def sort(self,field= None, nums = False):
223 def sort(self,field= None, nums = False):
223 """ sort by specified fields (see fields())
224 """ sort by specified fields (see fields())
224
225
225 Example::
226 Example::
226 a.sort(1, nums = True)
227 a.sort(1, nums = True)
227
228
228 Sorts a by second field, in numerical order (so that 21 > 3)
229 Sorts a by second field, in numerical order (so that 21 > 3)
229
230
230 """
231 """
231
232
232 #decorate, sort, undecorate
233 #decorate, sort, undecorate
233 if field is not None:
234 if field is not None:
234 dsu = [[SList([line]).fields(field), line] for line in self]
235 dsu = [[SList([line]).fields(field), line] for line in self]
235 else:
236 else:
236 dsu = [[line, line] for line in self]
237 dsu = [[line, line] for line in self]
237 if nums:
238 if nums:
238 for i in range(len(dsu)):
239 for i in range(len(dsu)):
239 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
240 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
240 try:
241 try:
241 n = int(numstr)
242 n = int(numstr)
242 except ValueError:
243 except ValueError:
243 n = 0;
244 n = 0;
244 dsu[i][0] = n
245 dsu[i][0] = n
245
246
246
247
247 dsu.sort()
248 dsu.sort()
248 return SList([t[1] for t in dsu])
249 return SList([t[1] for t in dsu])
249
250
250
251
251 # FIXME: We need to reimplement type specific displayhook and then add this
252 # FIXME: We need to reimplement type specific displayhook and then add this
252 # back as a custom printer. This should also be moved outside utils into the
253 # back as a custom printer. This should also be moved outside utils into the
253 # core.
254 # core.
254
255
255 # def print_slist(arg):
256 # def print_slist(arg):
256 # """ Prettier (non-repr-like) and more informative printer for SList """
257 # """ Prettier (non-repr-like) and more informative printer for SList """
257 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
258 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
258 # if hasattr(arg, 'hideonce') and arg.hideonce:
259 # if hasattr(arg, 'hideonce') and arg.hideonce:
259 # arg.hideonce = False
260 # arg.hideonce = False
260 # return
261 # return
261 #
262 #
262 # nlprint(arg)
263 # nlprint(arg)
263 #
264 #
264 # print_slist = result_display.when_type(SList)(print_slist)
265 # print_slist = result_display.when_type(SList)(print_slist)
265
266
266
267
267 def esc_quotes(strng):
268 def esc_quotes(strng):
268 """Return the input string with single and double quotes escaped out"""
269 """Return the input string with single and double quotes escaped out"""
269
270
270 return strng.replace('"','\\"').replace("'","\\'")
271 return strng.replace('"','\\"').replace("'","\\'")
271
272
272
273
273 def make_quoted_expr(s):
274 def make_quoted_expr(s):
274 """Return string s in appropriate quotes, using raw string if possible.
275 """Return string s in appropriate quotes, using raw string if possible.
275
276
276 XXX - example removed because it caused encoding errors in documentation
277 XXX - example removed because it caused encoding errors in documentation
277 generation. We need a new example that doesn't contain invalid chars.
278 generation. We need a new example that doesn't contain invalid chars.
278
279
279 Note the use of raw string and padding at the end to allow trailing
280 Note the use of raw string and padding at the end to allow trailing
280 backslash.
281 backslash.
281 """
282 """
282
283
283 tail = ''
284 tail = ''
284 tailpadding = ''
285 tailpadding = ''
285 raw = ''
286 raw = ''
286 ucode = 'u'
287 ucode = 'u'
287 if "\\" in s:
288 if "\\" in s:
288 raw = 'r'
289 raw = 'r'
289 if s.endswith('\\'):
290 if s.endswith('\\'):
290 tail = '[:-1]'
291 tail = '[:-1]'
291 tailpadding = '_'
292 tailpadding = '_'
292 if '"' not in s:
293 if '"' not in s:
293 quote = '"'
294 quote = '"'
294 elif "'" not in s:
295 elif "'" not in s:
295 quote = "'"
296 quote = "'"
296 elif '"""' not in s and not s.endswith('"'):
297 elif '"""' not in s and not s.endswith('"'):
297 quote = '"""'
298 quote = '"""'
298 elif "'''" not in s and not s.endswith("'"):
299 elif "'''" not in s and not s.endswith("'"):
299 quote = "'''"
300 quote = "'''"
300 else:
301 else:
301 # give up, backslash-escaped string will do
302 # give up, backslash-escaped string will do
302 return '"%s"' % esc_quotes(s)
303 return '"%s"' % esc_quotes(s)
303 res = ucode + raw + quote + s + tailpadding + quote + tail
304 res = ucode + raw + quote + s + tailpadding + quote + tail
304 return res
305 return res
305
306
306
307
307 def qw(words,flat=0,sep=None,maxsplit=-1):
308 def qw(words,flat=0,sep=None,maxsplit=-1):
308 """Similar to Perl's qw() operator, but with some more options.
309 """Similar to Perl's qw() operator, but with some more options.
309
310
310 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
311 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
311
312
312 words can also be a list itself, and with flat=1, the output will be
313 words can also be a list itself, and with flat=1, the output will be
313 recursively flattened.
314 recursively flattened.
314
315
315 Examples:
316 Examples:
316
317
317 >>> qw('1 2')
318 >>> qw('1 2')
318 ['1', '2']
319 ['1', '2']
319
320
320 >>> qw(['a b','1 2',['m n','p q']])
321 >>> qw(['a b','1 2',['m n','p q']])
321 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
322 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
322
323
323 >>> qw(['a b','1 2',['m n','p q']],flat=1)
324 >>> qw(['a b','1 2',['m n','p q']],flat=1)
324 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
325 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
325 """
326 """
326
327
327 if isinstance(words, basestring):
328 if isinstance(words, basestring):
328 return [word.strip() for word in words.split(sep,maxsplit)
329 return [word.strip() for word in words.split(sep,maxsplit)
329 if word and not word.isspace() ]
330 if word and not word.isspace() ]
330 if flat:
331 if flat:
331 return flatten(map(qw,words,[1]*len(words)))
332 return flatten(map(qw,words,[1]*len(words)))
332 return map(qw,words)
333 return map(qw,words)
333
334
334
335
335 def qwflat(words,sep=None,maxsplit=-1):
336 def qwflat(words,sep=None,maxsplit=-1):
336 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
337 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
337 return qw(words,1,sep,maxsplit)
338 return qw(words,1,sep,maxsplit)
338
339
339
340
340 def qw_lol(indata):
341 def qw_lol(indata):
341 """qw_lol('a b') -> [['a','b']],
342 """qw_lol('a b') -> [['a','b']],
342 otherwise it's just a call to qw().
343 otherwise it's just a call to qw().
343
344
344 We need this to make sure the modules_some keys *always* end up as a
345 We need this to make sure the modules_some keys *always* end up as a
345 list of lists."""
346 list of lists."""
346
347
347 if isinstance(indata, basestring):
348 if isinstance(indata, basestring):
348 return [qw(indata)]
349 return [qw(indata)]
349 else:
350 else:
350 return qw(indata)
351 return qw(indata)
351
352
352
353
353 def grep(pat,list,case=1):
354 def grep(pat,list,case=1):
354 """Simple minded grep-like function.
355 """Simple minded grep-like function.
355 grep(pat,list) returns occurrences of pat in list, None on failure.
356 grep(pat,list) returns occurrences of pat in list, None on failure.
356
357
357 It only does simple string matching, with no support for regexps. Use the
358 It only does simple string matching, with no support for regexps. Use the
358 option case=0 for case-insensitive matching."""
359 option case=0 for case-insensitive matching."""
359
360
360 # This is pretty crude. At least it should implement copying only references
361 # This is pretty crude. At least it should implement copying only references
361 # to the original data in case it's big. Now it copies the data for output.
362 # to the original data in case it's big. Now it copies the data for output.
362 out=[]
363 out=[]
363 if case:
364 if case:
364 for term in list:
365 for term in list:
365 if term.find(pat)>-1: out.append(term)
366 if term.find(pat)>-1: out.append(term)
366 else:
367 else:
367 lpat=pat.lower()
368 lpat=pat.lower()
368 for term in list:
369 for term in list:
369 if term.lower().find(lpat)>-1: out.append(term)
370 if term.lower().find(lpat)>-1: out.append(term)
370
371
371 if len(out): return out
372 if len(out): return out
372 else: return None
373 else: return None
373
374
374
375
375 def dgrep(pat,*opts):
376 def dgrep(pat,*opts):
376 """Return grep() on dir()+dir(__builtins__).
377 """Return grep() on dir()+dir(__builtins__).
377
378
378 A very common use of grep() when working interactively."""
379 A very common use of grep() when working interactively."""
379
380
380 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
381 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
381
382
382
383
383 def idgrep(pat):
384 def idgrep(pat):
384 """Case-insensitive dgrep()"""
385 """Case-insensitive dgrep()"""
385
386
386 return dgrep(pat,0)
387 return dgrep(pat,0)
387
388
388
389
389 def igrep(pat,list):
390 def igrep(pat,list):
390 """Synonym for case-insensitive grep."""
391 """Synonym for case-insensitive grep."""
391
392
392 return grep(pat,list,case=0)
393 return grep(pat,list,case=0)
393
394
394
395
395 def indent(instr,nspaces=4, ntabs=0, flatten=False):
396 def indent(instr,nspaces=4, ntabs=0, flatten=False):
396 """Indent a string a given number of spaces or tabstops.
397 """Indent a string a given number of spaces or tabstops.
397
398
398 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
399 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
399
400
400 Parameters
401 Parameters
401 ----------
402 ----------
402
403
403 instr : basestring
404 instr : basestring
404 The string to be indented.
405 The string to be indented.
405 nspaces : int (default: 4)
406 nspaces : int (default: 4)
406 The number of spaces to be indented.
407 The number of spaces to be indented.
407 ntabs : int (default: 0)
408 ntabs : int (default: 0)
408 The number of tabs to be indented.
409 The number of tabs to be indented.
409 flatten : bool (default: False)
410 flatten : bool (default: False)
410 Whether to scrub existing indentation. If True, all lines will be
411 Whether to scrub existing indentation. If True, all lines will be
411 aligned to the same indentation. If False, existing indentation will
412 aligned to the same indentation. If False, existing indentation will
412 be strictly increased.
413 be strictly increased.
413
414
414 Returns
415 Returns
415 -------
416 -------
416
417
417 str|unicode : string indented by ntabs and nspaces.
418 str|unicode : string indented by ntabs and nspaces.
418
419
419 """
420 """
420 if instr is None:
421 if instr is None:
421 return
422 return
422 ind = '\t'*ntabs+' '*nspaces
423 ind = '\t'*ntabs+' '*nspaces
423 if flatten:
424 if flatten:
424 pat = re.compile(r'^\s*', re.MULTILINE)
425 pat = re.compile(r'^\s*', re.MULTILINE)
425 else:
426 else:
426 pat = re.compile(r'^', re.MULTILINE)
427 pat = re.compile(r'^', re.MULTILINE)
427 outstr = re.sub(pat, ind, instr)
428 outstr = re.sub(pat, ind, instr)
428 if outstr.endswith(os.linesep+ind):
429 if outstr.endswith(os.linesep+ind):
429 return outstr[:-len(ind)]
430 return outstr[:-len(ind)]
430 else:
431 else:
431 return outstr
432 return outstr
432
433
433 def native_line_ends(filename,backup=1):
434 def native_line_ends(filename,backup=1):
434 """Convert (in-place) a file to line-ends native to the current OS.
435 """Convert (in-place) a file to line-ends native to the current OS.
435
436
436 If the optional backup argument is given as false, no backup of the
437 If the optional backup argument is given as false, no backup of the
437 original file is left. """
438 original file is left. """
438
439
439 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
440 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
440
441
441 bak_filename = filename + backup_suffixes[os.name]
442 bak_filename = filename + backup_suffixes[os.name]
442
443
443 original = open(filename).read()
444 original = open(filename).read()
444 shutil.copy2(filename,bak_filename)
445 shutil.copy2(filename,bak_filename)
445 try:
446 try:
446 new = open(filename,'wb')
447 new = open(filename,'wb')
447 new.write(os.linesep.join(original.splitlines()))
448 new.write(os.linesep.join(original.splitlines()))
448 new.write(os.linesep) # ALWAYS put an eol at the end of the file
449 new.write(os.linesep) # ALWAYS put an eol at the end of the file
449 new.close()
450 new.close()
450 except:
451 except:
451 os.rename(bak_filename,filename)
452 os.rename(bak_filename,filename)
452 if not backup:
453 if not backup:
453 try:
454 try:
454 os.remove(bak_filename)
455 os.remove(bak_filename)
455 except:
456 except:
456 pass
457 pass
457
458
458
459
459 def list_strings(arg):
460 def list_strings(arg):
460 """Always return a list of strings, given a string or list of strings
461 """Always return a list of strings, given a string or list of strings
461 as input.
462 as input.
462
463
463 :Examples:
464 :Examples:
464
465
465 In [7]: list_strings('A single string')
466 In [7]: list_strings('A single string')
466 Out[7]: ['A single string']
467 Out[7]: ['A single string']
467
468
468 In [8]: list_strings(['A single string in a list'])
469 In [8]: list_strings(['A single string in a list'])
469 Out[8]: ['A single string in a list']
470 Out[8]: ['A single string in a list']
470
471
471 In [9]: list_strings(['A','list','of','strings'])
472 In [9]: list_strings(['A','list','of','strings'])
472 Out[9]: ['A', 'list', 'of', 'strings']
473 Out[9]: ['A', 'list', 'of', 'strings']
473 """
474 """
474
475
475 if isinstance(arg,basestring): return [arg]
476 if isinstance(arg,basestring): return [arg]
476 else: return arg
477 else: return arg
477
478
478
479
479 def marquee(txt='',width=78,mark='*'):
480 def marquee(txt='',width=78,mark='*'):
480 """Return the input string centered in a 'marquee'.
481 """Return the input string centered in a 'marquee'.
481
482
482 :Examples:
483 :Examples:
483
484
484 In [16]: marquee('A test',40)
485 In [16]: marquee('A test',40)
485 Out[16]: '**************** A test ****************'
486 Out[16]: '**************** A test ****************'
486
487
487 In [17]: marquee('A test',40,'-')
488 In [17]: marquee('A test',40,'-')
488 Out[17]: '---------------- A test ----------------'
489 Out[17]: '---------------- A test ----------------'
489
490
490 In [18]: marquee('A test',40,' ')
491 In [18]: marquee('A test',40,' ')
491 Out[18]: ' A test '
492 Out[18]: ' A test '
492
493
493 """
494 """
494 if not txt:
495 if not txt:
495 return (mark*width)[:width]
496 return (mark*width)[:width]
496 nmark = (width-len(txt)-2)/len(mark)/2
497 nmark = (width-len(txt)-2)/len(mark)/2
497 if nmark < 0: nmark =0
498 if nmark < 0: nmark =0
498 marks = mark*nmark
499 marks = mark*nmark
499 return '%s %s %s' % (marks,txt,marks)
500 return '%s %s %s' % (marks,txt,marks)
500
501
501
502
502 ini_spaces_re = re.compile(r'^(\s+)')
503 ini_spaces_re = re.compile(r'^(\s+)')
503
504
504 def num_ini_spaces(strng):
505 def num_ini_spaces(strng):
505 """Return the number of initial spaces in a string"""
506 """Return the number of initial spaces in a string"""
506
507
507 ini_spaces = ini_spaces_re.match(strng)
508 ini_spaces = ini_spaces_re.match(strng)
508 if ini_spaces:
509 if ini_spaces:
509 return ini_spaces.end()
510 return ini_spaces.end()
510 else:
511 else:
511 return 0
512 return 0
512
513
513
514
514 def format_screen(strng):
515 def format_screen(strng):
515 """Format a string for screen printing.
516 """Format a string for screen printing.
516
517
517 This removes some latex-type format codes."""
518 This removes some latex-type format codes."""
518 # Paragraph continue
519 # Paragraph continue
519 par_re = re.compile(r'\\$',re.MULTILINE)
520 par_re = re.compile(r'\\$',re.MULTILINE)
520 strng = par_re.sub('',strng)
521 strng = par_re.sub('',strng)
521 return strng
522 return strng
522
523
524 def dedent(text):
525 """Equivalent of textwrap.dedent that ignores unindented first line.
526
527 This means it will still dedent strings like:
528 '''foo
529 is a bar
530 '''
531
532 For use in wrap_paragraphs.
533 """
534
535 if text.startswith('\n'):
536 # text starts with blank line, don't ignore the first line
537 return textwrap.dedent(text)
538
539 # split first line
540 splits = text.split('\n',1)
541 if len(splits) == 1:
542 # only one line
543 return textwrap.dedent(text)
544
545 first, rest = splits
546 # dedent everything but the first line
547 rest = textwrap.dedent(rest)
548 return '\n'.join([first, rest])
549
550 def wrap_paragraphs(text, ncols=80):
551 """Wrap multiple paragraphs to fit a specified width.
552
553 This is equivalent to textwrap.wrap, but with support for multiple
554 paragraphs, as separated by empty lines.
555
556 Returns
557 -------
558
559 list of complete paragraphs, wrapped to fill `ncols` columns.
560 """
561 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
562 text = dedent(text).strip()
563 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
564 out_ps = []
565 indent_re = re.compile(r'\n\s+', re.MULTILINE)
566 for p in paragraphs:
567 # presume indentation that survives dedent is meaningful formatting,
568 # so don't fill unless text is flush.
569 if indent_re.search(p) is None:
570 # wrap paragraph
571 p = textwrap.fill(p, ncols)
572 out_ps.append(p)
573 return out_ps
574
575
523
576
524 class EvalFormatter(Formatter):
577 class EvalFormatter(Formatter):
525 """A String Formatter that allows evaluation of simple expressions.
578 """A String Formatter that allows evaluation of simple expressions.
526
579
527 Any time a format key is not found in the kwargs,
580 Any time a format key is not found in the kwargs,
528 it will be tried as an expression in the kwargs namespace.
581 it will be tried as an expression in the kwargs namespace.
529
582
530 This is to be used in templating cases, such as the parallel batch
583 This is to be used in templating cases, such as the parallel batch
531 script templates, where simple arithmetic on arguments is useful.
584 script templates, where simple arithmetic on arguments is useful.
532
585
533 Examples
586 Examples
534 --------
587 --------
535
588
536 In [1]: f = EvalFormatter()
589 In [1]: f = EvalFormatter()
537 In [2]: f.format('{n/4}', n=8)
590 In [2]: f.format('{n/4}', n=8)
538 Out[2]: '2'
591 Out[2]: '2'
539
592
540 In [3]: f.format('{range(3)}')
593 In [3]: f.format('{range(3)}')
541 Out[3]: '[0, 1, 2]'
594 Out[3]: '[0, 1, 2]'
542
595
543 In [4]: f.format('{3*2}')
596 In [4]: f.format('{3*2}')
544 Out[4]: '6'
597 Out[4]: '6'
545 """
598 """
546
599
547 def get_value(self, key, args, kwargs):
600 def get_value(self, key, args, kwargs):
548 if isinstance(key, (int, long)):
601 if isinstance(key, (int, long)):
549 return args[key]
602 return args[key]
550 elif key in kwargs:
603 elif key in kwargs:
551 return kwargs[key]
604 return kwargs[key]
552 else:
605 else:
553 # evaluate the expression using kwargs as namespace
606 # evaluate the expression using kwargs as namespace
554 try:
607 try:
555 return eval(key, kwargs)
608 return eval(key, kwargs)
556 except Exception:
609 except Exception:
557 # classify all bad expressions as key errors
610 # classify all bad expressions as key errors
558 raise KeyError(key)
611 raise KeyError(key)
559
612
560
613
General Comments 0
You need to be logged in to leave comments. Login now