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 |
""" |
|
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 |
""" |
|
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() |
|
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 |
""" |
|
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() |
|
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 |
|
255 | |||
|
256 | for p in wrap_paragraphs(self.keyvalue_description): | |||
|
257 | print p | |||
|
258 | ||||
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 |
|
262 | |||
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 |
|
265 | |||
251 |
|
266 | |||
252 | def print_description(self): |
|
267 | def print_description(self): | |
253 | """Print the application description.""" |
|
268 | """Print the application description.""" | |
254 |
|
|
269 | for p in wrap_paragraphs(self.description): | |
255 |
|
270 | print p | ||
|
271 | ||||
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 |
""" |
|
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