Show More
@@ -1,386 +1,391 b'' | |||||
1 |
|
1 | |||
2 | """ Implementations for various useful completers |
|
2 | """ Implementations for various useful completers | |
3 |
|
3 | |||
4 | See Extensions/ipy_stock_completers.py on examples of how to enable a completer, |
|
4 | See Extensions/ipy_stock_completers.py on examples of how to enable a completer, | |
5 | but the basic idea is to do: |
|
5 | but the basic idea is to do: | |
6 |
|
6 | |||
7 | ip.set_hook('complete_command', svn_completer, str_key = 'svn') |
|
7 | ip.set_hook('complete_command', svn_completer, str_key = 'svn') | |
8 |
|
8 | |||
9 | """ |
|
9 | """ | |
10 | import IPython.ipapi |
|
10 | import IPython.ipapi | |
11 | import glob,os,shlex,sys |
|
11 | import glob,os,shlex,sys | |
12 | import inspect |
|
12 | import inspect | |
13 | from time import time |
|
13 | from time import time | |
14 | from zipimport import zipimporter |
|
14 | from zipimport import zipimporter | |
15 | ip = IPython.ipapi.get() |
|
15 | ip = IPython.ipapi.get() | |
16 |
|
16 | |||
17 | try: |
|
17 | try: | |
18 | set |
|
18 | set | |
19 | except: |
|
19 | except: | |
20 | from sets import Set as set |
|
20 | from sets import Set as set | |
21 |
|
21 | |||
22 | TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored |
|
22 | TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored | |
23 | TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up |
|
23 | TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up | |
24 |
|
24 | |||
25 | def quick_completer(cmd, completions): |
|
25 | def quick_completer(cmd, completions): | |
26 | """ Easily create a trivial completer for a command. |
|
26 | """ Easily create a trivial completer for a command. | |
27 |
|
27 | |||
28 | Takes either a list of completions, or all completions in string |
|
28 | Takes either a list of completions, or all completions in string | |
29 | (that will be split on whitespace) |
|
29 | (that will be split on whitespace) | |
30 |
|
30 | |||
31 | Example:: |
|
31 | Example:: | |
32 |
|
32 | |||
33 | [d:\ipython]|1> import ipy_completers |
|
33 | [d:\ipython]|1> import ipy_completers | |
34 | [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) |
|
34 | [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) | |
35 | [d:\ipython]|3> foo b<TAB> |
|
35 | [d:\ipython]|3> foo b<TAB> | |
36 | bar baz |
|
36 | bar baz | |
37 | [d:\ipython]|3> foo ba |
|
37 | [d:\ipython]|3> foo ba | |
38 | """ |
|
38 | """ | |
39 | if isinstance(completions, basestring): |
|
39 | if isinstance(completions, basestring): | |
40 |
|
40 | |||
41 | completions = completions.split() |
|
41 | completions = completions.split() | |
42 | def do_complete(self,event): |
|
42 | def do_complete(self,event): | |
43 | return completions |
|
43 | return completions | |
44 |
|
44 | |||
45 | ip.set_hook('complete_command',do_complete, str_key = cmd) |
|
45 | ip.set_hook('complete_command',do_complete, str_key = cmd) | |
46 |
|
46 | |||
47 | def getRootModules(): |
|
47 | def getRootModules(): | |
48 | """ |
|
48 | """ | |
49 | Returns a list containing the names of all the modules available in the |
|
49 | Returns a list containing the names of all the modules available in the | |
50 | folders of the pythonpath. |
|
50 | folders of the pythonpath. | |
51 | """ |
|
51 | """ | |
52 | modules = [] |
|
52 | modules = [] | |
53 | if ip.db.has_key('rootmodules'): |
|
53 | if ip.db.has_key('rootmodules'): | |
54 | return ip.db['rootmodules'] |
|
54 | return ip.db['rootmodules'] | |
55 | t = time() |
|
55 | t = time() | |
56 | store = False |
|
56 | store = False | |
57 | for path in sys.path: |
|
57 | for path in sys.path: | |
58 | modules += moduleList(path) |
|
58 | modules += moduleList(path) | |
59 | if time() - t >= TIMEOUT_STORAGE and not store: |
|
59 | if time() - t >= TIMEOUT_STORAGE and not store: | |
60 | store = True |
|
60 | store = True | |
61 | print "\nCaching the list of root modules, please wait!" |
|
61 | print "\nCaching the list of root modules, please wait!" | |
62 | print "(This will only be done once - type '%rehashx' to " + \ |
|
62 | print "(This will only be done once - type '%rehashx' to " + \ | |
63 | "reset cache!)" |
|
63 | "reset cache!)" | |
64 |
|
64 | |||
65 | if time() - t > TIMEOUT_GIVEUP: |
|
65 | if time() - t > TIMEOUT_GIVEUP: | |
66 | print "This is taking too long, we give up." |
|
66 | print "This is taking too long, we give up." | |
67 |
|
67 | |||
68 | ip.db['rootmodules'] = [] |
|
68 | ip.db['rootmodules'] = [] | |
69 | return [] |
|
69 | return [] | |
70 |
|
70 | |||
71 | modules += sys.builtin_module_names |
|
71 | modules += sys.builtin_module_names | |
72 |
|
72 | |||
73 | modules = list(set(modules)) |
|
73 | modules = list(set(modules)) | |
74 | if '__init__' in modules: |
|
74 | if '__init__' in modules: | |
75 | modules.remove('__init__') |
|
75 | modules.remove('__init__') | |
76 | modules = list(set(modules)) |
|
76 | modules = list(set(modules)) | |
77 | if store: |
|
77 | if store: | |
78 | ip.db['rootmodules'] = modules |
|
78 | ip.db['rootmodules'] = modules | |
79 | return modules |
|
79 | return modules | |
80 |
|
80 | |||
81 | def moduleList(path): |
|
81 | def moduleList(path): | |
82 | """ |
|
82 | """ | |
83 | Return the list containing the names of the modules available in the given |
|
83 | Return the list containing the names of the modules available in the given | |
84 | folder. |
|
84 | folder. | |
85 | """ |
|
85 | """ | |
86 |
|
86 | |||
87 | if os.path.isdir(path): |
|
87 | if os.path.isdir(path): | |
88 | folder_list = os.listdir(path) |
|
88 | folder_list = os.listdir(path) | |
89 | elif path.endswith('.egg'): |
|
89 | elif path.endswith('.egg'): | |
90 | try: |
|
90 | try: | |
91 | folder_list = [f for f in zipimporter(path)._files] |
|
91 | folder_list = [f for f in zipimporter(path)._files] | |
92 | except: |
|
92 | except: | |
93 | folder_list = [] |
|
93 | folder_list = [] | |
94 | else: |
|
94 | else: | |
95 | folder_list = [] |
|
95 | folder_list = [] | |
96 | #folder_list = glob.glob(os.path.join(path,'*')) |
|
96 | #folder_list = glob.glob(os.path.join(path,'*')) | |
97 | folder_list = [p for p in folder_list \ |
|
97 | folder_list = [p for p in folder_list \ | |
98 | if os.path.exists(os.path.join(path, p,'__init__.py'))\ |
|
98 | if os.path.exists(os.path.join(path, p,'__init__.py'))\ | |
99 | or p[-3:] in ('.py','.so')\ |
|
99 | or p[-3:] in ('.py','.so')\ | |
100 | or p[-4:] in ('.pyc','.pyo','.pyd')] |
|
100 | or p[-4:] in ('.pyc','.pyo','.pyd')] | |
101 |
|
101 | |||
102 | folder_list = [os.path.basename(p).split('.')[0] for p in folder_list] |
|
102 | folder_list = [os.path.basename(p).split('.')[0] for p in folder_list] | |
103 | return folder_list |
|
103 | return folder_list | |
104 |
|
104 | |||
105 | def moduleCompletion(line): |
|
105 | def moduleCompletion(line): | |
106 | """ |
|
106 | """ | |
107 | Returns a list containing the completion possibilities for an import line. |
|
107 | Returns a list containing the completion possibilities for an import line. | |
108 | The line looks like this : |
|
108 | The line looks like this : | |
109 | 'import xml.d' |
|
109 | 'import xml.d' | |
110 | 'from xml.dom import' |
|
110 | 'from xml.dom import' | |
111 | """ |
|
111 | """ | |
112 | def tryImport(mod, only_modules=False): |
|
112 | def tryImport(mod, only_modules=False): | |
113 | def isImportable(module, attr): |
|
113 | def isImportable(module, attr): | |
114 | if only_modules: |
|
114 | if only_modules: | |
115 | return inspect.ismodule(getattr(module, attr)) |
|
115 | return inspect.ismodule(getattr(module, attr)) | |
116 | else: |
|
116 | else: | |
117 | return not(attr[:2] == '__' and attr[-2:] == '__') |
|
117 | return not(attr[:2] == '__' and attr[-2:] == '__') | |
118 | try: |
|
118 | try: | |
119 | m = __import__(mod) |
|
119 | m = __import__(mod) | |
120 | except: |
|
120 | except: | |
121 | return [] |
|
121 | return [] | |
122 | mods = mod.split('.') |
|
122 | mods = mod.split('.') | |
123 | for module in mods[1:]: |
|
123 | for module in mods[1:]: | |
124 | m = getattr(m,module) |
|
124 | m = getattr(m,module) | |
125 | if (not hasattr(m, '__file__')) or (not only_modules) or\ |
|
125 | if (not hasattr(m, '__file__')) or (not only_modules) or\ | |
126 | (hasattr(m, '__file__') and '__init__' in m.__file__): |
|
126 | (hasattr(m, '__file__') and '__init__' in m.__file__): | |
127 | completion_list = [attr for attr in dir(m) if isImportable(m, attr)] |
|
127 | completion_list = [attr for attr in dir(m) if isImportable(m, attr)] | |
128 | completion_list.extend(getattr(m,'__all__',[])) |
|
128 | completion_list.extend(getattr(m,'__all__',[])) | |
129 | if hasattr(m, '__file__') and '__init__' in m.__file__: |
|
129 | if hasattr(m, '__file__') and '__init__' in m.__file__: | |
130 | completion_list.extend(moduleList(os.path.dirname(m.__file__))) |
|
130 | completion_list.extend(moduleList(os.path.dirname(m.__file__))) | |
131 | completion_list = list(set(completion_list)) |
|
131 | completion_list = list(set(completion_list)) | |
132 | if '__init__' in completion_list: |
|
132 | if '__init__' in completion_list: | |
133 | completion_list.remove('__init__') |
|
133 | completion_list.remove('__init__') | |
134 | return completion_list |
|
134 | return completion_list | |
135 |
|
135 | |||
136 | words = line.split(' ') |
|
136 | words = line.split(' ') | |
137 | if len(words) == 3 and words[0] == 'from': |
|
137 | if len(words) == 3 and words[0] == 'from': | |
138 | return ['import '] |
|
138 | return ['import '] | |
139 | if len(words) < 3 and (words[0] in ['import','from']) : |
|
139 | if len(words) < 3 and (words[0] in ['import','from']) : | |
140 | if len(words) == 1: |
|
140 | if len(words) == 1: | |
141 | return getRootModules() |
|
141 | return getRootModules() | |
142 | mod = words[1].split('.') |
|
142 | mod = words[1].split('.') | |
143 | if len(mod) < 2: |
|
143 | if len(mod) < 2: | |
144 | return getRootModules() |
|
144 | return getRootModules() | |
145 | completion_list = tryImport('.'.join(mod[:-1]), True) |
|
145 | completion_list = tryImport('.'.join(mod[:-1]), True) | |
146 | completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list] |
|
146 | completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list] | |
147 | return completion_list |
|
147 | return completion_list | |
148 | if len(words) >= 3 and words[0] == 'from': |
|
148 | if len(words) >= 3 and words[0] == 'from': | |
149 | mod = words[1] |
|
149 | mod = words[1] | |
150 | return tryImport(mod) |
|
150 | return tryImport(mod) | |
151 |
|
151 | |||
152 | def vcs_completer(commands, event): |
|
152 | def vcs_completer(commands, event): | |
153 | """ utility to make writing typical version control app completers easier |
|
153 | """ utility to make writing typical version control app completers easier | |
154 |
|
154 | |||
155 | VCS command line apps typically have the format: |
|
155 | VCS command line apps typically have the format: | |
156 |
|
156 | |||
157 | [sudo ]PROGNAME [help] [command] file file... |
|
157 | [sudo ]PROGNAME [help] [command] file file... | |
158 |
|
158 | |||
159 | """ |
|
159 | """ | |
160 |
|
160 | |||
161 |
|
161 | |||
162 | cmd_param = event.line.split() |
|
162 | cmd_param = event.line.split() | |
163 | if event.line.endswith(' '): |
|
163 | if event.line.endswith(' '): | |
164 | cmd_param.append('') |
|
164 | cmd_param.append('') | |
165 |
|
165 | |||
166 | if cmd_param[0] == 'sudo': |
|
166 | if cmd_param[0] == 'sudo': | |
167 | cmd_param = cmd_param[1:] |
|
167 | cmd_param = cmd_param[1:] | |
168 |
|
168 | |||
169 | if len(cmd_param) == 2 or 'help' in cmd_param: |
|
169 | if len(cmd_param) == 2 or 'help' in cmd_param: | |
170 | return commands.split() |
|
170 | return commands.split() | |
171 |
|
171 | |||
172 | return ip.IP.Completer.file_matches(event.symbol) |
|
172 | return ip.IP.Completer.file_matches(event.symbol) | |
173 |
|
173 | |||
174 |
|
174 | |||
175 | pkg_cache = None |
|
175 | pkg_cache = None | |
176 |
|
176 | |||
177 | def module_completer(self,event): |
|
177 | def module_completer(self,event): | |
178 | """ Give completions after user has typed 'import ...' or 'from ...'""" |
|
178 | """ Give completions after user has typed 'import ...' or 'from ...'""" | |
179 |
|
179 | |||
180 | # This works in all versions of python. While 2.5 has |
|
180 | # This works in all versions of python. While 2.5 has | |
181 | # pkgutil.walk_packages(), that particular routine is fairly dangerous, |
|
181 | # pkgutil.walk_packages(), that particular routine is fairly dangerous, | |
182 | # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full |
|
182 | # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full | |
183 | # of possibly problematic side effects. |
|
183 | # of possibly problematic side effects. | |
184 | # This search the folders in the sys.path for available modules. |
|
184 | # This search the folders in the sys.path for available modules. | |
185 |
|
185 | |||
186 | return moduleCompletion(event.line) |
|
186 | return moduleCompletion(event.line) | |
187 |
|
187 | |||
188 |
|
188 | |||
189 | svn_commands = """\ |
|
189 | svn_commands = """\ | |
190 | add blame praise annotate ann cat checkout co cleanup commit ci copy |
|
190 | add blame praise annotate ann cat checkout co cleanup commit ci copy | |
191 | cp delete del remove rm diff di export help ? h import info list ls |
|
191 | cp delete del remove rm diff di export help ? h import info list ls | |
192 | lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit |
|
192 | lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit | |
193 | pe propget pget pg proplist plist pl propset pset ps resolved revert |
|
193 | pe propget pget pg proplist plist pl propset pset ps resolved revert | |
194 | status stat st switch sw unlock update |
|
194 | status stat st switch sw unlock update | |
195 | """ |
|
195 | """ | |
196 |
|
196 | |||
197 | def svn_completer(self,event): |
|
197 | def svn_completer(self,event): | |
198 | return vcs_completer(svn_commands, event) |
|
198 | return vcs_completer(svn_commands, event) | |
199 |
|
199 | |||
200 |
|
200 | |||
201 | hg_commands = """ |
|
201 | hg_commands = """ | |
202 | add addremove annotate archive backout branch branches bundle cat |
|
202 | add addremove annotate archive backout branch branches bundle cat | |
203 | clone commit copy diff export grep heads help identify import incoming |
|
203 | clone commit copy diff export grep heads help identify import incoming | |
204 | init locate log manifest merge outgoing parents paths pull push |
|
204 | init locate log manifest merge outgoing parents paths pull push | |
205 | qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport |
|
205 | qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport | |
206 | qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave |
|
206 | qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave | |
207 | qselect qseries qtop qunapplied recover remove rename revert rollback |
|
207 | qselect qseries qtop qunapplied recover remove rename revert rollback | |
208 | root serve showconfig status strip tag tags tip unbundle update verify |
|
208 | root serve showconfig status strip tag tags tip unbundle update verify | |
209 | version |
|
209 | version | |
210 | """ |
|
210 | """ | |
211 |
|
211 | |||
212 | def hg_completer(self,event): |
|
212 | def hg_completer(self,event): | |
213 | """ Completer for mercurial commands """ |
|
213 | """ Completer for mercurial commands """ | |
214 |
|
214 | |||
215 | return vcs_completer(hg_commands, event) |
|
215 | return vcs_completer(hg_commands, event) | |
216 |
|
216 | |||
217 |
|
217 | |||
218 |
|
218 | |||
219 | __bzr_commands = None |
|
219 | __bzr_commands = None | |
220 |
|
220 | |||
221 | def bzr_commands(): |
|
221 | def bzr_commands(): | |
222 | global __bzr_commands |
|
222 | global __bzr_commands | |
223 | if __bzr_commands is not None: |
|
223 | if __bzr_commands is not None: | |
224 | return __bzr_commands |
|
224 | return __bzr_commands | |
225 | out = os.popen('bzr help commands') |
|
225 | out = os.popen('bzr help commands') | |
226 | __bzr_commands = [l.split()[0] for l in out] |
|
226 | __bzr_commands = [l.split()[0] for l in out] | |
227 | return __bzr_commands |
|
227 | return __bzr_commands | |
228 |
|
228 | |||
229 | def bzr_completer(self,event): |
|
229 | def bzr_completer(self,event): | |
230 | """ Completer for bazaar commands """ |
|
230 | """ Completer for bazaar commands """ | |
231 | cmd_param = event.line.split() |
|
231 | cmd_param = event.line.split() | |
232 | if event.line.endswith(' '): |
|
232 | if event.line.endswith(' '): | |
233 | cmd_param.append('') |
|
233 | cmd_param.append('') | |
234 |
|
234 | |||
235 | if len(cmd_param) > 2: |
|
235 | if len(cmd_param) > 2: | |
236 | cmd = cmd_param[1] |
|
236 | cmd = cmd_param[1] | |
237 | param = cmd_param[-1] |
|
237 | param = cmd_param[-1] | |
238 | output_file = (param == '--output=') |
|
238 | output_file = (param == '--output=') | |
239 | if cmd == 'help': |
|
239 | if cmd == 'help': | |
240 | return bzr_commands() |
|
240 | return bzr_commands() | |
241 | elif cmd in ['bundle-revisions','conflicts', |
|
241 | elif cmd in ['bundle-revisions','conflicts', | |
242 | 'deleted','nick','register-branch', |
|
242 | 'deleted','nick','register-branch', | |
243 | 'serve','unbind','upgrade','version', |
|
243 | 'serve','unbind','upgrade','version', | |
244 | 'whoami'] and not output_file: |
|
244 | 'whoami'] and not output_file: | |
245 | return [] |
|
245 | return [] | |
246 | else: |
|
246 | else: | |
247 | # the rest are probably file names |
|
247 | # the rest are probably file names | |
248 | return ip.IP.Completer.file_matches(event.symbol) |
|
248 | return ip.IP.Completer.file_matches(event.symbol) | |
249 |
|
249 | |||
250 | return bzr_commands() |
|
250 | return bzr_commands() | |
251 |
|
251 | |||
252 |
|
252 | |||
253 | def shlex_split(x): |
|
253 | def shlex_split(x): | |
254 | """Helper function to split lines into segments.""" |
|
254 | """Helper function to split lines into segments.""" | |
255 | #shlex.split raise exception if syntax error in sh syntax |
|
255 | #shlex.split raise exception if syntax error in sh syntax | |
256 | #for example if no closing " is found. This function keeps dropping |
|
256 | #for example if no closing " is found. This function keeps dropping | |
257 | #the last character of the line until shlex.split does not raise |
|
257 | #the last character of the line until shlex.split does not raise | |
258 | #exception. Adds end of the line to the result of shlex.split |
|
258 | #exception. Adds end of the line to the result of shlex.split | |
259 | #example: %run "c:/python -> ['%run','"c:/python'] |
|
259 | #example: %run "c:/python -> ['%run','"c:/python'] | |
260 | endofline=[] |
|
260 | endofline=[] | |
261 | while x!="": |
|
261 | while x!="": | |
262 | try: |
|
262 | try: | |
263 | comps=shlex.split(x) |
|
263 | comps=shlex.split(x) | |
264 | if len(endofline)>=1: |
|
264 | if len(endofline)>=1: | |
265 | comps.append("".join(endofline)) |
|
265 | comps.append("".join(endofline)) | |
266 | return comps |
|
266 | return comps | |
267 | except ValueError: |
|
267 | except ValueError: | |
268 | endofline=[x[-1:]]+endofline |
|
268 | endofline=[x[-1:]]+endofline | |
269 | x=x[:-1] |
|
269 | x=x[:-1] | |
270 | return ["".join(endofline)] |
|
270 | return ["".join(endofline)] | |
271 |
|
271 | |||
272 | def runlistpy(self, event): |
|
272 | def runlistpy(self, event): | |
273 | comps = shlex_split(event.line) |
|
273 | comps = shlex_split(event.line) | |
274 | relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"") |
|
274 | relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"") | |
275 |
|
275 | |||
276 | #print "\nev=",event # dbg |
|
276 | #print "\nev=",event # dbg | |
277 | #print "rp=",relpath # dbg |
|
277 | #print "rp=",relpath # dbg | |
278 | #print 'comps=',comps # dbg |
|
278 | #print 'comps=',comps # dbg | |
279 |
|
279 | |||
280 | lglob = glob.glob |
|
280 | lglob = glob.glob | |
281 | isdir = os.path.isdir |
|
281 | isdir = os.path.isdir | |
282 | if relpath.startswith('~'): |
|
282 | if relpath.startswith('~'): | |
283 | relpath = os.path.expanduser(relpath) |
|
283 | relpath = os.path.expanduser(relpath) | |
284 | dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') |
|
284 | dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') | |
285 | if isdir(f)] |
|
285 | if isdir(f)] | |
286 |
|
286 | |||
287 | # Find if the user has already typed the first filename, after which we |
|
287 | # Find if the user has already typed the first filename, after which we | |
288 | # should complete on all files, since after the first one other files may |
|
288 | # should complete on all files, since after the first one other files may | |
289 | # be arguments to the input script. |
|
289 | # be arguments to the input script. | |
290 | #filter( |
|
290 | #filter( | |
291 | if filter(lambda f: f.endswith('.py') or f.endswith('.ipy') or |
|
291 | if filter(lambda f: f.endswith('.py') or f.endswith('.ipy') or | |
292 | f.endswith('.pyw'),comps): |
|
292 | f.endswith('.pyw'),comps): | |
293 | pys = [f.replace('\\','/') for f in lglob('*')] |
|
293 | pys = [f.replace('\\','/') for f in lglob('*')] | |
294 | else: |
|
294 | else: | |
295 | pys = [f.replace('\\','/') |
|
295 | pys = [f.replace('\\','/') | |
296 | for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + |
|
296 | for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + | |
297 | lglob(relpath + '*.pyw')] |
|
297 | lglob(relpath + '*.pyw')] | |
298 | return dirs + pys |
|
298 | return dirs + pys | |
299 |
|
299 | |||
300 |
|
300 | |||
|
301 | greedy_cd_completer = False | |||
|
302 | ||||
301 | def cd_completer(self, event): |
|
303 | def cd_completer(self, event): | |
302 | relpath = event.symbol |
|
304 | relpath = event.symbol | |
303 | #print event # dbg |
|
305 | #print event # dbg | |
304 | if '-b' in event.line: |
|
306 | if '-b' in event.line: | |
305 | # return only bookmark completions |
|
307 | # return only bookmark completions | |
306 | bkms = self.db.get('bookmarks',{}) |
|
308 | bkms = self.db.get('bookmarks',{}) | |
307 | return bkms.keys() |
|
309 | return bkms.keys() | |
308 |
|
310 | |||
309 |
|
311 | |||
310 | if event.symbol == '-': |
|
312 | if event.symbol == '-': | |
311 | width_dh = str(len(str(len(ip.user_ns['_dh']) + 1))) |
|
313 | width_dh = str(len(str(len(ip.user_ns['_dh']) + 1))) | |
312 | # jump in directory history by number |
|
314 | # jump in directory history by number | |
313 | fmt = '-%0' + width_dh +'d [%s]' |
|
315 | fmt = '-%0' + width_dh +'d [%s]' | |
314 | ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] |
|
316 | ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] | |
315 | if len(ents) > 1: |
|
317 | if len(ents) > 1: | |
316 | return ents |
|
318 | return ents | |
317 | return [] |
|
319 | return [] | |
318 |
|
320 | |||
319 | if relpath.startswith('~'): |
|
321 | if relpath.startswith('~'): | |
320 | relpath = os.path.expanduser(relpath).replace('\\','/') |
|
322 | relpath = os.path.expanduser(relpath).replace('\\','/') | |
321 | found = [] |
|
323 | found = [] | |
322 | for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') |
|
324 | for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') | |
323 | if os.path.isdir(f)]: |
|
325 | if os.path.isdir(f)]: | |
324 | if ' ' in d: |
|
326 | if ' ' in d: | |
325 | # we don't want to deal with any of that, complex code |
|
327 | # we don't want to deal with any of that, complex code | |
326 | # for this is elsewhere |
|
328 | # for this is elsewhere | |
327 | raise IPython.ipapi.TryNext |
|
329 | raise IPython.ipapi.TryNext | |
328 | found.append( d ) |
|
330 | found.append( d ) | |
329 |
|
331 | |||
330 | if not found: |
|
332 | if not found: | |
331 | if os.path.isdir(relpath): |
|
333 | if os.path.isdir(relpath): | |
332 | return [relpath] |
|
334 | return [relpath] | |
333 | raise IPython.ipapi.TryNext |
|
335 | raise IPython.ipapi.TryNext | |
334 |
|
336 | |||
335 |
|
337 | |||
336 | def single_dir_expand(matches): |
|
338 | def single_dir_expand(matches): | |
337 | "Recursively expand match lists containing a single dir." |
|
339 | "Recursively expand match lists containing a single dir." | |
338 |
|
340 | |||
339 | if len(matches) == 1 and os.path.isdir(matches[0]): |
|
341 | if len(matches) == 1 and os.path.isdir(matches[0]): | |
340 | # Takes care of links to directories also. Use '/' |
|
342 | # Takes care of links to directories also. Use '/' | |
341 | # explicitly, even under Windows, so that name completions |
|
343 | # explicitly, even under Windows, so that name completions | |
342 | # don't end up escaped. |
|
344 | # don't end up escaped. | |
343 | d = matches[0] |
|
345 | d = matches[0] | |
344 | if d[-1] in ['/','\\']: |
|
346 | if d[-1] in ['/','\\']: | |
345 | d = d[:-1] |
|
347 | d = d[:-1] | |
346 |
|
348 | |||
347 | subdirs = [p for p in os.listdir(d) if os.path.isdir( d + '/' + p) and not p.startswith('.')] |
|
349 | subdirs = [p for p in os.listdir(d) if os.path.isdir( d + '/' + p) and not p.startswith('.')] | |
348 | if subdirs: |
|
350 | if subdirs: | |
349 | matches = [ (d + '/' + p) for p in subdirs ] |
|
351 | matches = [ (d + '/' + p) for p in subdirs ] | |
350 | return single_dir_expand(matches) |
|
352 | return single_dir_expand(matches) | |
351 | else: |
|
353 | else: | |
352 | return matches |
|
354 | return matches | |
353 | else: |
|
355 | else: | |
354 | return matches |
|
356 | return matches | |
355 |
|
357 | |||
356 | return single_dir_expand(found) |
|
358 | if greedy_cd_completer: | |
|
359 | return single_dir_expand(found) | |||
|
360 | else: | |||
|
361 | return found | |||
357 |
|
362 | |||
358 | def apt_get_packages(prefix): |
|
363 | def apt_get_packages(prefix): | |
359 | out = os.popen('apt-cache pkgnames') |
|
364 | out = os.popen('apt-cache pkgnames') | |
360 | for p in out: |
|
365 | for p in out: | |
361 | if p.startswith(prefix): |
|
366 | if p.startswith(prefix): | |
362 | yield p.rstrip() |
|
367 | yield p.rstrip() | |
363 |
|
368 | |||
364 |
|
369 | |||
365 | apt_commands = """\ |
|
370 | apt_commands = """\ | |
366 | update upgrade install remove purge source build-dep dist-upgrade |
|
371 | update upgrade install remove purge source build-dep dist-upgrade | |
367 | dselect-upgrade clean autoclean check""" |
|
372 | dselect-upgrade clean autoclean check""" | |
368 |
|
373 | |||
369 | def apt_completer(self, event): |
|
374 | def apt_completer(self, event): | |
370 | """ Completer for apt-get (uses apt-cache internally) |
|
375 | """ Completer for apt-get (uses apt-cache internally) | |
371 |
|
376 | |||
372 | """ |
|
377 | """ | |
373 |
|
378 | |||
374 |
|
379 | |||
375 | cmd_param = event.line.split() |
|
380 | cmd_param = event.line.split() | |
376 | if event.line.endswith(' '): |
|
381 | if event.line.endswith(' '): | |
377 | cmd_param.append('') |
|
382 | cmd_param.append('') | |
378 |
|
383 | |||
379 | if cmd_param[0] == 'sudo': |
|
384 | if cmd_param[0] == 'sudo': | |
380 | cmd_param = cmd_param[1:] |
|
385 | cmd_param = cmd_param[1:] | |
381 |
|
386 | |||
382 | if len(cmd_param) == 2 or 'help' in cmd_param: |
|
387 | if len(cmd_param) == 2 or 'help' in cmd_param: | |
383 | return apt_commands.split() |
|
388 | return apt_commands.split() | |
384 |
|
389 | |||
385 | return list(apt_get_packages(event.symbol)) |
|
390 | return list(apt_get_packages(event.symbol)) | |
386 |
|
391 |
@@ -1,251 +1,258 b'' | |||||
1 | """Shell mode for IPython. |
|
1 | """Shell mode for IPython. | |
2 |
|
2 | |||
3 | Start ipython in shell mode by invoking "ipython -p sh" |
|
3 | Start ipython in shell mode by invoking "ipython -p sh" | |
4 |
|
4 | |||
5 | (the old version, "ipython -p pysh" still works but this is the more "modern" |
|
5 | (the old version, "ipython -p pysh" still works but this is the more "modern" | |
6 | shell mode and is recommended for users who don't care about pysh-mode |
|
6 | shell mode and is recommended for users who don't care about pysh-mode | |
7 | compatibility) |
|
7 | compatibility) | |
8 | """ |
|
8 | """ | |
9 |
|
9 | |||
10 | from IPython import ipapi |
|
10 | from IPython import ipapi | |
11 | import os,textwrap |
|
11 | import os,textwrap | |
12 |
|
12 | |||
13 | # The import below effectively obsoletes your old-style ipythonrc[.ini], |
|
13 | # The import below effectively obsoletes your old-style ipythonrc[.ini], | |
14 | # so consider yourself warned! |
|
14 | # so consider yourself warned! | |
15 |
|
15 | |||
16 | import ipy_defaults |
|
16 | import ipy_defaults | |
17 |
|
17 | |||
18 | def main(): |
|
18 | def main(): | |
19 | ip = ipapi.get() |
|
19 | ip = ipapi.get() | |
20 | o = ip.options |
|
20 | o = ip.options | |
21 | # autocall to "full" mode (smart mode is default, I like full mode) |
|
21 | # autocall to "full" mode (smart mode is default, I like full mode) | |
22 |
|
22 | |||
23 | o.autocall = 2 |
|
23 | o.autocall = 2 | |
24 |
|
24 | |||
25 | # Jason Orendorff's path class is handy to have in user namespace |
|
25 | # Jason Orendorff's path class is handy to have in user namespace | |
26 | # if you are doing shell-like stuff |
|
26 | # if you are doing shell-like stuff | |
27 | try: |
|
27 | try: | |
28 | ip.ex("from IPython.external.path import path" ) |
|
28 | ip.ex("from IPython.external.path import path" ) | |
29 | except ImportError: |
|
29 | except ImportError: | |
30 | pass |
|
30 | pass | |
31 |
|
31 | |||
32 | # beefed up %env is handy in shell mode |
|
32 | # beefed up %env is handy in shell mode | |
33 | import envpersist |
|
33 | import envpersist | |
34 |
|
34 | |||
35 | # To see where mycmd resides (in path/aliases), do %which mycmd |
|
35 | # To see where mycmd resides (in path/aliases), do %which mycmd | |
36 | import ipy_which |
|
36 | import ipy_which | |
37 |
|
37 | |||
38 | # tab completers for hg, svn, ... |
|
38 | # tab completers for hg, svn, ... | |
39 | import ipy_app_completers |
|
39 | import ipy_app_completers | |
40 |
|
40 | |||
41 | # To make executables foo and bar in mybin usable without PATH change, do: |
|
41 | # To make executables foo and bar in mybin usable without PATH change, do: | |
42 | # %rehashdir c:/mybin |
|
42 | # %rehashdir c:/mybin | |
43 | # %store foo |
|
43 | # %store foo | |
44 | # %store bar |
|
44 | # %store bar | |
45 | import ipy_rehashdir |
|
45 | import ipy_rehashdir | |
46 |
|
46 | |||
47 | # does not work without subprocess module! |
|
47 | # does not work without subprocess module! | |
48 | #import ipy_signals |
|
48 | #import ipy_signals | |
49 |
|
49 | |||
50 | ip.ex('import os') |
|
50 | ip.ex('import os') | |
51 | ip.ex("def up(): os.chdir('..')") |
|
51 | ip.ex("def up(): os.chdir('..')") | |
52 | ip.user_ns['LA'] = LastArgFinder() |
|
52 | ip.user_ns['LA'] = LastArgFinder() | |
53 | # Nice prompt |
|
53 | # Nice prompt | |
54 |
|
54 | |||
55 | o.prompt_in1= r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Green|\#> ' |
|
55 | o.prompt_in1= r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Green|\#> ' | |
56 | o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green> ' |
|
56 | o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green> ' | |
57 | o.prompt_out= '<\#> ' |
|
57 | o.prompt_out= '<\#> ' | |
58 |
|
58 | |||
59 | from IPython import Release |
|
59 | from IPython import Release | |
60 |
|
60 | |||
61 | import sys |
|
61 | import sys | |
62 | # Non-chatty banner |
|
62 | # Non-chatty banner | |
63 | o.banner = "IPython %s [on Py %s]\n" % (Release.version,sys.version.split(None,1)[0]) |
|
63 | o.banner = "IPython %s [on Py %s]\n" % (Release.version,sys.version.split(None,1)[0]) | |
64 |
|
64 | |||
65 |
|
65 | |||
66 | ip.IP.default_option('cd','-q') |
|
66 | ip.IP.default_option('cd','-q') | |
67 | ip.IP.default_option('macro', '-r') |
|
67 | ip.IP.default_option('macro', '-r') | |
68 | # If you only rarely want to execute the things you %edit... |
|
68 | # If you only rarely want to execute the things you %edit... | |
69 | #ip.IP.default_option('edit','-x') |
|
69 | #ip.IP.default_option('edit','-x') | |
70 |
|
70 | |||
71 |
|
71 | |||
72 | o.prompts_pad_left="1" |
|
72 | o.prompts_pad_left="1" | |
73 | # Remove all blank lines in between prompts, like a normal shell. |
|
73 | # Remove all blank lines in between prompts, like a normal shell. | |
74 | o.separate_in="0" |
|
74 | o.separate_in="0" | |
75 | o.separate_out="0" |
|
75 | o.separate_out="0" | |
76 | o.separate_out2="0" |
|
76 | o.separate_out2="0" | |
77 |
|
77 | |||
78 | # now alias all syscommands |
|
78 | # now alias all syscommands | |
79 |
|
79 | |||
80 | db = ip.db |
|
80 | db = ip.db | |
81 |
|
81 | |||
82 | syscmds = db.get("syscmdlist",[] ) |
|
82 | syscmds = db.get("syscmdlist",[] ) | |
83 | if not syscmds: |
|
83 | if not syscmds: | |
84 | print textwrap.dedent(""" |
|
84 | print textwrap.dedent(""" | |
85 | System command list not initialized, probably the first run... |
|
85 | System command list not initialized, probably the first run... | |
86 | running %rehashx to refresh the command list. Run %rehashx |
|
86 | running %rehashx to refresh the command list. Run %rehashx | |
87 | again to refresh command list (after installing new software etc.) |
|
87 | again to refresh command list (after installing new software etc.) | |
88 | """) |
|
88 | """) | |
89 | ip.magic('rehashx') |
|
89 | ip.magic('rehashx') | |
90 | syscmds = db.get("syscmdlist") |
|
90 | syscmds = db.get("syscmdlist") | |
91 |
|
91 | |||
92 | # lowcase aliases on win32 only |
|
92 | # lowcase aliases on win32 only | |
93 | if os.name == 'posix': |
|
93 | if os.name == 'posix': | |
94 | mapper = lambda s:s |
|
94 | mapper = lambda s:s | |
95 | else: |
|
95 | else: | |
96 | def mapper(s): return s.lower() |
|
96 | def mapper(s): return s.lower() | |
97 |
|
97 | |||
98 | for cmd in syscmds: |
|
98 | for cmd in syscmds: | |
99 | # print "sys",cmd #dbg |
|
99 | # print "sys",cmd #dbg | |
100 | noext, ext = os.path.splitext(cmd) |
|
100 | noext, ext = os.path.splitext(cmd) | |
101 | key = mapper(noext) |
|
101 | key = mapper(noext) | |
102 | if key not in ip.IP.alias_table: |
|
102 | if key not in ip.IP.alias_table: | |
103 | ip.defalias(key, cmd) |
|
103 | ip.defalias(key, cmd) | |
104 |
|
104 | |||
105 | # mglob combines 'find', recursion, exclusion... '%mglob?' to learn more |
|
105 | # mglob combines 'find', recursion, exclusion... '%mglob?' to learn more | |
106 | ip.load("IPython.external.mglob") |
|
106 | ip.load("IPython.external.mglob") | |
107 |
|
107 | |||
108 | # win32 is crippled w/o cygwin, try to help it a little bit |
|
108 | # win32 is crippled w/o cygwin, try to help it a little bit | |
109 | if sys.platform == 'win32': |
|
109 | if sys.platform == 'win32': | |
110 | if 'cygwin' in os.environ['PATH'].lower(): |
|
110 | if 'cygwin' in os.environ['PATH'].lower(): | |
111 | # use the colors of cygwin ls (recommended) |
|
111 | # use the colors of cygwin ls (recommended) | |
112 | ip.defalias('d', 'ls -F --color=auto') |
|
112 | ip.defalias('d', 'ls -F --color=auto') | |
113 | else: |
|
113 | else: | |
114 | # get icp, imv, imkdir, igrep, irm,... |
|
114 | # get icp, imv, imkdir, igrep, irm,... | |
115 | ip.load('ipy_fsops') |
|
115 | ip.load('ipy_fsops') | |
116 |
|
116 | |||
117 | # and the next best thing to real 'ls -F' |
|
117 | # and the next best thing to real 'ls -F' | |
118 | ip.defalias('d','dir /w /og /on') |
|
118 | ip.defalias('d','dir /w /og /on') | |
119 |
|
119 | |||
|
120 | ip.set_hook('input_prefilter', dotslash_prefilter_f) | |||
120 | extend_shell_behavior(ip) |
|
121 | extend_shell_behavior(ip) | |
121 |
|
122 | |||
122 | class LastArgFinder: |
|
123 | class LastArgFinder: | |
123 | """ Allow $LA to work as "last argument of previous command", like $! in bash |
|
124 | """ Allow $LA to work as "last argument of previous command", like $! in bash | |
124 |
|
125 | |||
125 | To call this in normal IPython code, do LA() |
|
126 | To call this in normal IPython code, do LA() | |
126 | """ |
|
127 | """ | |
127 | def __call__(self, hist_idx = None): |
|
128 | def __call__(self, hist_idx = None): | |
128 | ip = ipapi.get() |
|
129 | ip = ipapi.get() | |
129 | if hist_idx is None: |
|
130 | if hist_idx is None: | |
130 | return str(self) |
|
131 | return str(self) | |
131 | return ip.IP.input_hist_raw[hist_idx].strip().split()[-1] |
|
132 | return ip.IP.input_hist_raw[hist_idx].strip().split()[-1] | |
132 | def __str__(self): |
|
133 | def __str__(self): | |
133 | ip = ipapi.get() |
|
134 | ip = ipapi.get() | |
134 | for cmd in reversed(ip.IP.input_hist_raw): |
|
135 | for cmd in reversed(ip.IP.input_hist_raw): | |
135 | parts = cmd.strip().split() |
|
136 | parts = cmd.strip().split() | |
136 | if len(parts) < 2 or parts[-1] in ['$LA', 'LA()']: |
|
137 | if len(parts) < 2 or parts[-1] in ['$LA', 'LA()']: | |
137 | continue |
|
138 | continue | |
138 | return parts[-1] |
|
139 | return parts[-1] | |
139 | return "" |
|
140 | return "" | |
140 |
|
141 | |||
141 |
|
142 | def dotslash_prefilter_f(self,line): | ||
142 |
|
143 | """ ./foo now runs foo as system command | ||
143 |
|
144 | |||
|
145 | Removes the need for doing !./foo | |||
|
146 | """ | |||
|
147 | import IPython.genutils | |||
|
148 | if line.startswith("./"): | |||
|
149 | return "_ip.system(" + IPython.genutils.make_quoted_expr(line)+")" | |||
|
150 | raise ipapi.TryNext | |||
144 |
|
151 | |||
145 | # XXX You do not need to understand the next function! |
|
152 | # XXX You do not need to understand the next function! | |
146 | # This should probably be moved out of profile |
|
153 | # This should probably be moved out of profile | |
147 |
|
154 | |||
148 | def extend_shell_behavior(ip): |
|
155 | def extend_shell_behavior(ip): | |
149 |
|
156 | |||
150 | # Instead of making signature a global variable tie it to IPSHELL. |
|
157 | # Instead of making signature a global variable tie it to IPSHELL. | |
151 | # In future if it is required to distinguish between different |
|
158 | # In future if it is required to distinguish between different | |
152 | # shells we can assign a signature per shell basis |
|
159 | # shells we can assign a signature per shell basis | |
153 | ip.IP.__sig__ = 0xa005 |
|
160 | ip.IP.__sig__ = 0xa005 | |
154 | # mark the IPSHELL with this signature |
|
161 | # mark the IPSHELL with this signature | |
155 | ip.IP.user_ns['__builtins__'].__dict__['__sig__'] = ip.IP.__sig__ |
|
162 | ip.IP.user_ns['__builtins__'].__dict__['__sig__'] = ip.IP.__sig__ | |
156 |
|
163 | |||
157 | from IPython.Itpl import ItplNS |
|
164 | from IPython.Itpl import ItplNS | |
158 | from IPython.genutils import shell |
|
165 | from IPython.genutils import shell | |
159 | # utility to expand user variables via Itpl |
|
166 | # utility to expand user variables via Itpl | |
160 | # xxx do something sensible with depth? |
|
167 | # xxx do something sensible with depth? | |
161 | ip.IP.var_expand = lambda cmd, lvars=None, depth=2: \ |
|
168 | ip.IP.var_expand = lambda cmd, lvars=None, depth=2: \ | |
162 | str(ItplNS(cmd, ip.IP.user_ns, get_locals())) |
|
169 | str(ItplNS(cmd, ip.IP.user_ns, get_locals())) | |
163 |
|
170 | |||
164 | def get_locals(): |
|
171 | def get_locals(): | |
165 | """ Substituting a variable through Itpl deep inside the IPSHELL stack |
|
172 | """ Substituting a variable through Itpl deep inside the IPSHELL stack | |
166 | requires the knowledge of all the variables in scope upto the last |
|
173 | requires the knowledge of all the variables in scope upto the last | |
167 | IPSHELL frame. This routine simply merges all the local variables |
|
174 | IPSHELL frame. This routine simply merges all the local variables | |
168 | on the IPSHELL stack without worrying about their scope rules |
|
175 | on the IPSHELL stack without worrying about their scope rules | |
169 | """ |
|
176 | """ | |
170 | import sys |
|
177 | import sys | |
171 | # note lambda expression constitues a function call |
|
178 | # note lambda expression constitues a function call | |
172 | # hence fno should be incremented by one |
|
179 | # hence fno should be incremented by one | |
173 | getsig = lambda fno: sys._getframe(fno+1).f_globals \ |
|
180 | getsig = lambda fno: sys._getframe(fno+1).f_globals \ | |
174 | ['__builtins__'].__dict__['__sig__'] |
|
181 | ['__builtins__'].__dict__['__sig__'] | |
175 | getlvars = lambda fno: sys._getframe(fno+1).f_locals |
|
182 | getlvars = lambda fno: sys._getframe(fno+1).f_locals | |
176 | # trackback until we enter the IPSHELL |
|
183 | # trackback until we enter the IPSHELL | |
177 | frame_no = 1 |
|
184 | frame_no = 1 | |
178 | sig = ip.IP.__sig__ |
|
185 | sig = ip.IP.__sig__ | |
179 | fsig = ~sig |
|
186 | fsig = ~sig | |
180 | while fsig != sig : |
|
187 | while fsig != sig : | |
181 | try: |
|
188 | try: | |
182 | fsig = getsig(frame_no) |
|
189 | fsig = getsig(frame_no) | |
183 | except (AttributeError, KeyError): |
|
190 | except (AttributeError, KeyError): | |
184 | frame_no += 1 |
|
191 | frame_no += 1 | |
185 | except ValueError: |
|
192 | except ValueError: | |
186 | # stack is depleted |
|
193 | # stack is depleted | |
187 | # call did not originate from IPSHELL |
|
194 | # call did not originate from IPSHELL | |
188 | return {} |
|
195 | return {} | |
189 | first_frame = frame_no |
|
196 | first_frame = frame_no | |
190 | # walk further back until we exit from IPSHELL or deplete stack |
|
197 | # walk further back until we exit from IPSHELL or deplete stack | |
191 | try: |
|
198 | try: | |
192 | while(sig == getsig(frame_no+1)): |
|
199 | while(sig == getsig(frame_no+1)): | |
193 | frame_no += 1 |
|
200 | frame_no += 1 | |
194 | except (AttributeError, KeyError, ValueError): |
|
201 | except (AttributeError, KeyError, ValueError): | |
195 | pass |
|
202 | pass | |
196 | # merge the locals from top down hence overriding |
|
203 | # merge the locals from top down hence overriding | |
197 | # any re-definitions of variables, functions etc. |
|
204 | # any re-definitions of variables, functions etc. | |
198 | lvars = {} |
|
205 | lvars = {} | |
199 | for fno in range(frame_no, first_frame-1, -1): |
|
206 | for fno in range(frame_no, first_frame-1, -1): | |
200 | lvars.update(getlvars(fno)) |
|
207 | lvars.update(getlvars(fno)) | |
201 | #print '\n'*5, first_frame, frame_no, '\n', lvars, '\n'*5 #dbg |
|
208 | #print '\n'*5, first_frame, frame_no, '\n', lvars, '\n'*5 #dbg | |
202 | return lvars |
|
209 | return lvars | |
203 |
|
210 | |||
204 | def _runlines(lines): |
|
211 | def _runlines(lines): | |
205 | """Run a string of one or more lines of source. |
|
212 | """Run a string of one or more lines of source. | |
206 |
|
213 | |||
207 | This method is capable of running a string containing multiple source |
|
214 | This method is capable of running a string containing multiple source | |
208 | lines, as if they had been entered at the IPython prompt. Since it |
|
215 | lines, as if they had been entered at the IPython prompt. Since it | |
209 | exposes IPython's processing machinery, the given strings can contain |
|
216 | exposes IPython's processing machinery, the given strings can contain | |
210 | magic calls (%magic), special shell access (!cmd), etc.""" |
|
217 | magic calls (%magic), special shell access (!cmd), etc.""" | |
211 |
|
218 | |||
212 | # We must start with a clean buffer, in case this is run from an |
|
219 | # We must start with a clean buffer, in case this is run from an | |
213 | # interactive IPython session (via a magic, for example). |
|
220 | # interactive IPython session (via a magic, for example). | |
214 | ip.IP.resetbuffer() |
|
221 | ip.IP.resetbuffer() | |
215 | lines = lines.split('\n') |
|
222 | lines = lines.split('\n') | |
216 | more = 0 |
|
223 | more = 0 | |
217 | command = '' |
|
224 | command = '' | |
218 | for line in lines: |
|
225 | for line in lines: | |
219 | # skip blank lines so we don't mess up the prompt counter, but do |
|
226 | # skip blank lines so we don't mess up the prompt counter, but do | |
220 | # NOT skip even a blank line if we are in a code block (more is |
|
227 | # NOT skip even a blank line if we are in a code block (more is | |
221 | # true) |
|
228 | # true) | |
222 | # if command is not empty trim the line |
|
229 | # if command is not empty trim the line | |
223 | if command != '' : |
|
230 | if command != '' : | |
224 | line = line.strip() |
|
231 | line = line.strip() | |
225 | # add the broken line to the command |
|
232 | # add the broken line to the command | |
226 | if line and line[-1] == '\\' : |
|
233 | if line and line[-1] == '\\' : | |
227 | command += line[0:-1] + ' ' |
|
234 | command += line[0:-1] + ' ' | |
228 | more = True |
|
235 | more = True | |
229 | continue |
|
236 | continue | |
230 | else : |
|
237 | else : | |
231 | # add the last (current) line to the command |
|
238 | # add the last (current) line to the command | |
232 | command += line |
|
239 | command += line | |
233 | if command or more: |
|
240 | if command or more: | |
234 | # push to raw history, so hist line numbers stay in sync |
|
241 | # push to raw history, so hist line numbers stay in sync | |
235 | ip.IP.input_hist_raw.append("# " + command + "\n") |
|
242 | ip.IP.input_hist_raw.append("# " + command + "\n") | |
236 |
|
243 | |||
237 | more = ip.IP.push(ip.IP.prefilter(command,more)) |
|
244 | more = ip.IP.push(ip.IP.prefilter(command,more)) | |
238 | command = '' |
|
245 | command = '' | |
239 | # IPython's runsource returns None if there was an error |
|
246 | # IPython's runsource returns None if there was an error | |
240 | # compiling the code. This allows us to stop processing right |
|
247 | # compiling the code. This allows us to stop processing right | |
241 | # away, so the user gets the error message at the right place. |
|
248 | # away, so the user gets the error message at the right place. | |
242 | if more is None: |
|
249 | if more is None: | |
243 | break |
|
250 | break | |
244 | # final newline in case the input didn't have it, so that the code |
|
251 | # final newline in case the input didn't have it, so that the code | |
245 | # actually does get executed |
|
252 | # actually does get executed | |
246 | if more: |
|
253 | if more: | |
247 | ip.IP.push('\n') |
|
254 | ip.IP.push('\n') | |
248 |
|
255 | |||
249 | ip.IP.runlines = _runlines |
|
256 | ip.IP.runlines = _runlines | |
250 |
|
257 | |||
251 | main() |
|
258 | main() |
@@ -1,107 +1,116 b'' | |||||
1 | """ User configuration file for IPython |
|
1 | """ User configuration file for IPython | |
2 |
|
2 | |||
3 | This is a more flexible and safe way to configure ipython than *rc files |
|
3 | This is a more flexible and safe way to configure ipython than *rc files | |
4 | (ipythonrc, ipythonrc-pysh etc.) |
|
4 | (ipythonrc, ipythonrc-pysh etc.) | |
5 |
|
5 | |||
6 | This file is always imported on ipython startup. You can import the |
|
6 | This file is always imported on ipython startup. You can import the | |
7 | ipython extensions you need here (see IPython/Extensions directory). |
|
7 | ipython extensions you need here (see IPython/Extensions directory). | |
8 |
|
8 | |||
9 | Feel free to edit this file to customize your ipython experience. |
|
9 | Feel free to edit this file to customize your ipython experience. | |
10 |
|
10 | |||
11 | Note that as such this file does nothing, for backwards compatibility. |
|
11 | Note that as such this file does nothing, for backwards compatibility. | |
12 | Consult e.g. file 'ipy_profile_sh.py' for an example of the things |
|
12 | Consult e.g. file 'ipy_profile_sh.py' for an example of the things | |
13 | you can do here. |
|
13 | you can do here. | |
14 |
|
14 | |||
15 | See http://ipython.scipy.org/moin/IpythonExtensionApi for detailed |
|
15 | See http://ipython.scipy.org/moin/IpythonExtensionApi for detailed | |
16 | description on what you could do here. |
|
16 | description on what you could do here. | |
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
19 | # Most of your config files and extensions will probably start with this import |
|
19 | # Most of your config files and extensions will probably start with this import | |
20 |
|
20 | |||
21 | import IPython.ipapi |
|
21 | import IPython.ipapi | |
22 | ip = IPython.ipapi.get() |
|
22 | ip = IPython.ipapi.get() | |
23 |
|
23 | |||
24 | # You probably want to uncomment this if you did %upgrade -nolegacy |
|
24 | # You probably want to uncomment this if you did %upgrade -nolegacy | |
25 | # import ipy_defaults |
|
25 | # import ipy_defaults | |
26 |
|
26 | |||
27 | import os |
|
27 | import os | |
28 |
|
28 | |||
29 | def main(): |
|
29 | def main(): | |
30 |
|
30 | |||
31 | # uncomment if you want to get ipython -p sh behaviour |
|
31 | # uncomment if you want to get ipython -p sh behaviour | |
32 | # without having to use command line switches |
|
32 | # without having to use command line switches | |
33 | # import ipy_profile_sh |
|
33 | # import ipy_profile_sh | |
34 |
|
34 | |||
35 | # Configure your favourite editor? |
|
35 | # Configure your favourite editor? | |
36 | # Good idea e.g. for %edit os.path.isfile |
|
36 | # Good idea e.g. for %edit os.path.isfile | |
37 |
|
37 | |||
38 | #import ipy_editors |
|
38 | #import ipy_editors | |
39 |
|
39 | |||
40 | # Choose one of these: |
|
40 | # Choose one of these: | |
41 |
|
41 | |||
42 | #ipy_editors.scite() |
|
42 | #ipy_editors.scite() | |
43 | #ipy_editors.scite('c:/opt/scite/scite.exe') |
|
43 | #ipy_editors.scite('c:/opt/scite/scite.exe') | |
44 | #ipy_editors.komodo() |
|
44 | #ipy_editors.komodo() | |
45 | #ipy_editors.idle() |
|
45 | #ipy_editors.idle() | |
46 | # ... or many others, try 'ipy_editors??' after import to see them |
|
46 | # ... or many others, try 'ipy_editors??' after import to see them | |
47 |
|
47 | |||
48 | # Or roll your own: |
|
48 | # Or roll your own: | |
49 | #ipy_editors.install_editor("c:/opt/jed +$line $file") |
|
49 | #ipy_editors.install_editor("c:/opt/jed +$line $file") | |
50 |
|
50 | |||
51 |
|
51 | |||
52 | o = ip.options |
|
52 | o = ip.options | |
53 | # An example on how to set options |
|
53 | # An example on how to set options | |
54 | #o.autocall = 1 |
|
54 | #o.autocall = 1 | |
55 | o.system_verbose = 0 |
|
55 | o.system_verbose = 0 | |
56 |
|
56 | |||
57 | #import_all("os sys") |
|
57 | #import_all("os sys") | |
58 | #execf('~/_ipython/ns.py') |
|
58 | #execf('~/_ipython/ns.py') | |
59 |
|
59 | |||
60 |
|
60 | |||
61 | # -- prompt |
|
61 | # -- prompt | |
62 | # A different, more compact set of prompts from the default ones, that |
|
62 | # A different, more compact set of prompts from the default ones, that | |
63 | # always show your current location in the filesystem: |
|
63 | # always show your current location in the filesystem: | |
64 |
|
64 | |||
65 | #o.prompt_in1 = r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Normal\n\C_Green|\#>' |
|
65 | #o.prompt_in1 = r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Normal\n\C_Green|\#>' | |
66 | #o.prompt_in2 = r'.\D: ' |
|
66 | #o.prompt_in2 = r'.\D: ' | |
67 | #o.prompt_out = r'[\#] ' |
|
67 | #o.prompt_out = r'[\#] ' | |
68 |
|
68 | |||
69 | # Try one of these color settings if you can't read the text easily |
|
69 | # Try one of these color settings if you can't read the text easily | |
70 | # autoexec is a list of IPython commands to execute on startup |
|
70 | # autoexec is a list of IPython commands to execute on startup | |
71 | #o.autoexec.append('%colors LightBG') |
|
71 | #o.autoexec.append('%colors LightBG') | |
72 | #o.autoexec.append('%colors NoColor') |
|
72 | #o.autoexec.append('%colors NoColor') | |
73 | #o.autoexec.append('%colors Linux') |
|
73 | #o.autoexec.append('%colors Linux') | |
74 |
|
74 | |||
75 | # for sane integer division that converts to float (1/2 == 0.5) |
|
75 | # for sane integer division that converts to float (1/2 == 0.5) | |
76 | #o.autoexec.append('from __future__ import division') |
|
76 | #o.autoexec.append('from __future__ import division') | |
77 |
|
77 | |||
78 | # For %tasks and %kill |
|
78 | # For %tasks and %kill | |
79 | #import jobctrl |
|
79 | #import jobctrl | |
80 |
|
80 | |||
81 | # For autoreloading of modules (%autoreload, %aimport) |
|
81 | # For autoreloading of modules (%autoreload, %aimport) | |
82 | #import ipy_autoreload |
|
82 | #import ipy_autoreload | |
83 |
|
83 | |||
84 | # For winpdb support (%wdb) |
|
84 | # For winpdb support (%wdb) | |
85 | #import ipy_winpdb |
|
85 | #import ipy_winpdb | |
86 |
|
86 | |||
87 | # For bzr completer, requires bzrlib (the python installation of bzr) |
|
87 | # For bzr completer, requires bzrlib (the python installation of bzr) | |
88 | #ip.load('ipy_bzr') |
|
88 | #ip.load('ipy_bzr') | |
89 |
|
89 | |||
90 | # Tab completer that is not quite so picky (i.e. |
|
90 | # Tab completer that is not quite so picky (i.e. | |
91 | # "foo".<TAB> and str(2).<TAB> will work). Complete |
|
91 | # "foo".<TAB> and str(2).<TAB> will work). Complete | |
92 | # at your own risk! |
|
92 | # at your own risk! | |
93 | #import ipy_greedycompleter |
|
93 | #import ipy_greedycompleter | |
94 |
|
94 | |||
|
95 | # If you are on Linux, you may be annoyed by | |||
|
96 | # "Display all N possibilities? (y or n)" on tab completion, | |||
|
97 | # as well as the paging through "more". Uncomment the following | |||
|
98 | # lines to disable that behaviour | |||
|
99 | #import readline | |||
|
100 | #readline.parse_and_bind('set completion-query-items 1000') | |||
|
101 | #readline.parse_and_bind('set page-completions no') | |||
|
102 | ||||
|
103 | ||||
95 |
|
104 | |||
96 |
|
105 | |||
97 | # some config helper functions you can use |
|
106 | # some config helper functions you can use | |
98 | def import_all(modules): |
|
107 | def import_all(modules): | |
99 | """ Usage: import_all("os sys") """ |
|
108 | """ Usage: import_all("os sys") """ | |
100 | for m in modules.split(): |
|
109 | for m in modules.split(): | |
101 | ip.ex("from %s import *" % m) |
|
110 | ip.ex("from %s import *" % m) | |
102 |
|
111 | |||
103 | def execf(fname): |
|
112 | def execf(fname): | |
104 | """ Execute a file in user namespace """ |
|
113 | """ Execute a file in user namespace """ | |
105 | ip.ex('execfile("%s")' % os.path.expanduser(fname)) |
|
114 | ip.ex('execfile("%s")' % os.path.expanduser(fname)) | |
106 |
|
115 | |||
107 | main() |
|
116 | main() |
@@ -1,395 +1,425 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 |
# -*- test-case-name: |
|
2 | # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*- | |
3 |
|
3 | |||
4 |
"""PyObjC classes to provide a Cocoa frontend to the |
|
4 | """PyObjC classes to provide a Cocoa frontend to the | |
|
5 | IPython.kernel.engineservice.IEngineBase. | |||
5 |
|
6 | |||
6 | The Cocoa frontend is divided into two classes: |
|
7 | To add an IPython interpreter to a cocoa app, instantiate an | |
7 | - IPythonCocoaController |
|
8 | IPythonCocoaController in a XIB and connect its textView outlet to an | |
8 | - IPythonCLITextViewDelegate |
|
9 | NSTextView instance in your UI. That's it. | |
9 |
|
10 | |||
10 | To add an IPython interpreter to a cocoa app, instantiate both of these classes in an XIB...[FINISH] |
|
11 | Author: Barry Wark | |
11 | """ |
|
12 | """ | |
12 |
|
13 | |||
13 | __docformat__ = "restructuredtext en" |
|
14 | __docformat__ = "restructuredtext en" | |
14 |
|
15 | |||
15 |
#----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
16 | # Copyright (C) 2008 Barry Wark <barrywark@gmail.com> |
|
17 | # Copyright (C) 2008 The IPython Development Team | |
17 | # |
|
18 | # | |
18 | # Distributed under the terms of the BSD License. The full license is in |
|
19 | # Distributed under the terms of the BSD License. The full license is in | |
19 | # the file COPYING, distributed as part of this software. |
|
20 | # the file COPYING, distributed as part of this software. | |
20 |
#----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
21 |
|
22 | |||
22 |
#----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
23 | # Imports |
|
24 | # Imports | |
24 |
#----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
25 |
|
26 | |||
26 | import objc |
|
27 | import objc | |
27 | import uuid |
|
28 | import uuid | |
28 |
|
29 | |||
29 | from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\ |
|
30 | from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\ | |
30 | NSLog, NSNotificationCenter, NSMakeRange,\ |
|
31 | NSLog, NSNotificationCenter, NSMakeRange,\ | |
31 | NSLocalizedString, NSIntersectionRange |
|
32 | NSLocalizedString, NSIntersectionRange | |
32 |
|
33 | |||
33 | from AppKit import NSApplicationWillTerminateNotification, NSBeep,\ |
|
34 | from AppKit import NSApplicationWillTerminateNotification, NSBeep,\ | |
34 | NSTextView, NSRulerView, NSVerticalRuler |
|
35 | NSTextView, NSRulerView, NSVerticalRuler | |
35 |
|
36 | |||
36 | from pprint import saferepr |
|
37 | from pprint import saferepr | |
37 |
|
38 | |||
38 | import IPython |
|
39 | import IPython | |
39 |
from IPython.kernel.engineservice import |
|
40 | from IPython.kernel.engineservice import ThreadedEngineService | |
40 | from IPython.frontend.frontendbase import FrontEndBase |
|
41 | from IPython.frontend.frontendbase import FrontEndBase | |
41 |
|
42 | |||
42 | from twisted.internet.threads import blockingCallFromThread |
|
43 | from twisted.internet.threads import blockingCallFromThread | |
43 | from twisted.python.failure import Failure |
|
44 | from twisted.python.failure import Failure | |
44 |
|
45 | |||
45 |
#------------------------------------------------------------------------------ |
|
46 | #------------------------------------------------------------------------------ | |
46 | # Classes to implement the Cocoa frontend |
|
47 | # Classes to implement the Cocoa frontend | |
47 |
#------------------------------------------------------------------------------ |
|
48 | #------------------------------------------------------------------------------ | |
48 |
|
49 | |||
49 | # TODO: |
|
50 | # TODO: | |
50 |
# 1. use MultiEngineClient and out-of-process engine rather than |
|
51 | # 1. use MultiEngineClient and out-of-process engine rather than | |
|
52 | # ThreadedEngineService? | |||
51 | # 2. integrate Xgrid launching of engines |
|
53 | # 2. integrate Xgrid launching of engines | |
52 |
|
54 | |||
53 |
|
55 | |||
54 |
|
56 | |||
55 |
|
57 | |||
56 | class IPythonCocoaController(NSObject, FrontEndBase): |
|
58 | class IPythonCocoaController(NSObject, FrontEndBase): | |
57 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) |
|
59 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) | |
58 | waitingForEngine = objc.ivar().bool() |
|
60 | waitingForEngine = objc.ivar().bool() | |
59 | textView = objc.IBOutlet() |
|
61 | textView = objc.IBOutlet() | |
60 |
|
62 | |||
61 | def init(self): |
|
63 | def init(self): | |
62 | self = super(IPythonCocoaController, self).init() |
|
64 | self = super(IPythonCocoaController, self).init() | |
63 | FrontEndBase.__init__(self, engine=ThreadedEngineService()) |
|
65 | FrontEndBase.__init__(self, engine=ThreadedEngineService()) | |
64 | if(self != None): |
|
66 | if(self != None): | |
65 | self._common_init() |
|
67 | self._common_init() | |
66 |
|
68 | |||
67 | return self |
|
69 | return self | |
68 |
|
70 | |||
69 | def _common_init(self): |
|
71 | def _common_init(self): | |
70 | """_common_init""" |
|
72 | """_common_init""" | |
71 |
|
73 | |||
72 | self.userNS = NSMutableDictionary.dictionary() |
|
74 | self.userNS = NSMutableDictionary.dictionary() | |
73 | self.waitingForEngine = False |
|
75 | self.waitingForEngine = False | |
74 |
|
76 | |||
75 | self.lines = {} |
|
77 | self.lines = {} | |
76 | self.tabSpaces = 4 |
|
78 | self.tabSpaces = 4 | |
77 | self.tabUsesSpaces = True |
|
79 | self.tabUsesSpaces = True | |
78 |
self.currentBlockID = self.next |
|
80 | self.currentBlockID = self.next_block_ID() | |
79 | self.blockRanges = {} # blockID=>NSRange |
|
81 | self.blockRanges = {} # blockID=>NSRange | |
80 |
|
82 | |||
81 |
|
83 | |||
82 | def awakeFromNib(self): |
|
84 | def awakeFromNib(self): | |
83 | """awakeFromNib""" |
|
85 | """awakeFromNib""" | |
84 |
|
86 | |||
85 | self._common_init() |
|
87 | self._common_init() | |
86 |
|
88 | |||
87 | # Start the IPython engine |
|
89 | # Start the IPython engine | |
88 | self.engine.startService() |
|
90 | self.engine.startService() | |
89 | NSLog('IPython engine started') |
|
91 | NSLog('IPython engine started') | |
90 |
|
92 | |||
91 | # Register for app termination |
|
93 | # Register for app termination | |
92 |
NSNotificationCenter.defaultCenter() |
|
94 | nc = NSNotificationCenter.defaultCenter() | |
93 | 'appWillTerminate:', |
|
95 | nc.addObserver_selector_name_object_( | |
94 |
|
|
96 | self, | |
95 |
|
|
97 | 'appWillTerminate:', | |
|
98 | NSApplicationWillTerminateNotification, | |||
|
99 | None) | |||
96 |
|
100 | |||
97 | self.textView.setDelegate_(self) |
|
101 | self.textView.setDelegate_(self) | |
98 | self.textView.enclosingScrollView().setHasVerticalRuler_(True) |
|
102 | self.textView.enclosingScrollView().setHasVerticalRuler_(True) | |
99 |
|
|
103 | r = NSRulerView.alloc().initWithScrollView_orientation_( | |
100 | self.textView.enclosingScrollView(), |
|
104 | self.textView.enclosingScrollView(), | |
101 | NSVerticalRuler) |
|
105 | NSVerticalRuler) | |
|
106 | self.verticalRulerView = r | |||
102 | self.verticalRulerView.setClientView_(self.textView) |
|
107 | self.verticalRulerView.setClientView_(self.textView) | |
103 |
self. |
|
108 | self._start_cli_banner() | |
104 |
|
109 | |||
105 |
|
110 | |||
106 | def appWillTerminate_(self, notification): |
|
111 | def appWillTerminate_(self, notification): | |
107 | """appWillTerminate""" |
|
112 | """appWillTerminate""" | |
108 |
|
113 | |||
109 | self.engine.stopService() |
|
114 | self.engine.stopService() | |
110 |
|
115 | |||
111 |
|
116 | |||
112 | def complete(self, token): |
|
117 | def complete(self, token): | |
113 | """Complete token in engine's user_ns |
|
118 | """Complete token in engine's user_ns | |
114 |
|
119 | |||
115 | Parameters |
|
120 | Parameters | |
116 | ---------- |
|
121 | ---------- | |
117 | token : string |
|
122 | token : string | |
118 |
|
123 | |||
119 | Result |
|
124 | Result | |
120 | ------ |
|
125 | ------ | |
121 | Deferred result of ipython1.kernel.engineservice.IEngineInteractive.complete |
|
126 | Deferred result of | |
|
127 | IPython.kernel.engineservice.IEngineBase.complete | |||
122 | """ |
|
128 | """ | |
123 |
|
129 | |||
124 | return self.engine.complete(token) |
|
130 | return self.engine.complete(token) | |
125 |
|
131 | |||
126 |
|
132 | |||
127 | def execute(self, block, blockID=None): |
|
133 | def execute(self, block, blockID=None): | |
128 | self.waitingForEngine = True |
|
134 | self.waitingForEngine = True | |
129 | self.willChangeValueForKey_('commandHistory') |
|
135 | self.willChangeValueForKey_('commandHistory') | |
130 | d = super(IPythonCocoaController, self).execute(block, blockID) |
|
136 | d = super(IPythonCocoaController, self).execute(block, blockID) | |
131 |
d.addBoth(self._engine |
|
137 | d.addBoth(self._engine_done) | |
132 |
d.addCallback(self._update |
|
138 | d.addCallback(self._update_user_ns) | |
133 |
|
139 | |||
134 | return d |
|
140 | return d | |
135 |
|
141 | |||
136 |
|
142 | |||
137 |
def _engine |
|
143 | def _engine_done(self, x): | |
138 | self.waitingForEngine = False |
|
144 | self.waitingForEngine = False | |
139 | self.didChangeValueForKey_('commandHistory') |
|
145 | self.didChangeValueForKey_('commandHistory') | |
140 | return x |
|
146 | return x | |
141 |
|
147 | |||
142 |
def _update |
|
148 | def _update_user_ns(self, result): | |
143 | """Update self.userNS from self.engine's namespace""" |
|
149 | """Update self.userNS from self.engine's namespace""" | |
144 | d = self.engine.keys() |
|
150 | d = self.engine.keys() | |
145 |
d.addCallback(self._get |
|
151 | d.addCallback(self._get_engine_namespace_values_for_keys) | |
146 |
|
152 | |||
147 | return result |
|
153 | return result | |
148 |
|
154 | |||
149 |
|
155 | |||
150 |
def _get |
|
156 | def _get_engine_namespace_values_for_keys(self, keys): | |
151 | d = self.engine.pull(keys) |
|
157 | d = self.engine.pull(keys) | |
152 |
d.addCallback(self._store |
|
158 | d.addCallback(self._store_engine_namespace_values, keys=keys) | |
153 |
|
159 | |||
154 |
|
160 | |||
155 |
def _store |
|
161 | def _store_engine_namespace_values(self, values, keys=[]): | |
156 | assert(len(values) == len(keys)) |
|
162 | assert(len(values) == len(keys)) | |
157 | self.willChangeValueForKey_('userNS') |
|
163 | self.willChangeValueForKey_('userNS') | |
158 | for (k,v) in zip(keys,values): |
|
164 | for (k,v) in zip(keys,values): | |
159 | self.userNS[k] = saferepr(v) |
|
165 | self.userNS[k] = saferepr(v) | |
160 | self.didChangeValueForKey_('userNS') |
|
166 | self.didChangeValueForKey_('userNS') | |
161 |
|
167 | |||
162 |
|
168 | |||
163 | def startCLIForTextView(self): |
|
169 | def update_cell_prompt(self, result): | |
|
170 | if(isinstance(result, Failure)): | |||
|
171 | blockID = result.blockID | |||
|
172 | else: | |||
|
173 | blockID = result['blockID'] | |||
|
174 | ||||
|
175 | ||||
|
176 | self.insert_text(self.input_prompt(result=result), | |||
|
177 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), | |||
|
178 | scrollToVisible=False | |||
|
179 | ) | |||
|
180 | ||||
|
181 | return result | |||
|
182 | ||||
|
183 | ||||
|
184 | def render_result(self, result): | |||
|
185 | blockID = result['blockID'] | |||
|
186 | inputRange = self.blockRanges[blockID] | |||
|
187 | del self.blockRanges[blockID] | |||
|
188 | ||||
|
189 | #print inputRange,self.current_block_range() | |||
|
190 | self.insert_text('\n' + | |||
|
191 | self.output_prompt(result) + | |||
|
192 | result.get('display',{}).get('pprint','') + | |||
|
193 | '\n\n', | |||
|
194 | textRange=NSMakeRange(inputRange.location+inputRange.length, | |||
|
195 | 0)) | |||
|
196 | return result | |||
|
197 | ||||
|
198 | ||||
|
199 | def render_error(self, failure): | |||
|
200 | self.insert_text('\n\n'+str(failure)+'\n\n') | |||
|
201 | self.start_new_block() | |||
|
202 | return failure | |||
|
203 | ||||
|
204 | ||||
|
205 | def _start_cli_banner(self): | |||
164 | """Print banner""" |
|
206 | """Print banner""" | |
165 |
|
207 | |||
166 |
banner = """IPython1 %s -- An enhanced Interactive Python.""" % |
|
208 | banner = """IPython1 %s -- An enhanced Interactive Python.""" % \ | |
|
209 | IPython.__version__ | |||
167 |
|
210 | |||
168 | self.insert_text(banner + '\n\n') |
|
211 | self.insert_text(banner + '\n\n') | |
169 |
|
212 | |||
170 | # NSTextView/IPythonTextView delegate methods |
|
213 | ||
|
214 | def start_new_block(self): | |||
|
215 | """""" | |||
|
216 | ||||
|
217 | self.currentBlockID = self.next_block_ID() | |||
|
218 | ||||
|
219 | ||||
|
220 | ||||
|
221 | def next_block_ID(self): | |||
|
222 | ||||
|
223 | return uuid.uuid4() | |||
|
224 | ||||
|
225 | def current_block_range(self): | |||
|
226 | return self.blockRanges.get(self.currentBlockID, | |||
|
227 | NSMakeRange(self.textView.textStorage().length(), | |||
|
228 | 0)) | |||
|
229 | ||||
|
230 | def current_block(self): | |||
|
231 | """The current block's text""" | |||
|
232 | ||||
|
233 | return self.text_for_range(self.current_block_range()) | |||
|
234 | ||||
|
235 | def text_for_range(self, textRange): | |||
|
236 | """text_for_range""" | |||
|
237 | ||||
|
238 | ts = self.textView.textStorage() | |||
|
239 | return ts.string().substringWithRange_(textRange) | |||
|
240 | ||||
|
241 | def current_line(self): | |||
|
242 | block = self.text_for_range(self.current_block_range()) | |||
|
243 | block = block.split('\n') | |||
|
244 | return block[-1] | |||
|
245 | ||||
|
246 | ||||
|
247 | def insert_text(self, string=None, textRange=None, scrollToVisible=True): | |||
|
248 | """Insert text into textView at textRange, updating blockRanges | |||
|
249 | as necessary | |||
|
250 | """ | |||
|
251 | ||||
|
252 | if(textRange == None): | |||
|
253 | #range for end of text | |||
|
254 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) | |||
|
255 | ||||
|
256 | for r in self.blockRanges.itervalues(): | |||
|
257 | intersection = NSIntersectionRange(r,textRange) | |||
|
258 | if(intersection.length == 0): #ranges don't intersect | |||
|
259 | if r.location >= textRange.location: | |||
|
260 | r.location += len(string) | |||
|
261 | else: #ranges intersect | |||
|
262 | if(r.location <= textRange.location): | |||
|
263 | assert(intersection.length == textRange.length) | |||
|
264 | r.length += textRange.length | |||
|
265 | else: | |||
|
266 | r.location += intersection.length | |||
|
267 | ||||
|
268 | self.textView.replaceCharactersInRange_withString_( | |||
|
269 | textRange, string) | |||
|
270 | self.textView.setSelectedRange_( | |||
|
271 | NSMakeRange(textRange.location+len(string), 0)) | |||
|
272 | if(scrollToVisible): | |||
|
273 | self.textView.scrollRangeToVisible_(textRange) | |||
|
274 | ||||
|
275 | ||||
|
276 | ||||
|
277 | ||||
|
278 | def replace_current_block_with_string(self, textView, string): | |||
|
279 | textView.replaceCharactersInRange_withString_( | |||
|
280 | self.current_block_range(), | |||
|
281 | string) | |||
|
282 | self.current_block_range().length = len(string) | |||
|
283 | r = NSMakeRange(textView.textStorage().length(), 0) | |||
|
284 | textView.scrollRangeToVisible_(r) | |||
|
285 | textView.setSelectedRange_(r) | |||
|
286 | ||||
|
287 | ||||
|
288 | def current_indent_string(self): | |||
|
289 | """returns string for indent or None if no indent""" | |||
|
290 | ||||
|
291 | if(len(self.current_block()) > 0): | |||
|
292 | lines = self.current_block().split('\n') | |||
|
293 | currentIndent = len(lines[-1]) - len(lines[-1]) | |||
|
294 | if(currentIndent == 0): | |||
|
295 | currentIndent = self.tabSpaces | |||
|
296 | ||||
|
297 | if(self.tabUsesSpaces): | |||
|
298 | result = ' ' * currentIndent | |||
|
299 | else: | |||
|
300 | result = '\t' * (currentIndent/self.tabSpaces) | |||
|
301 | else: | |||
|
302 | result = None | |||
|
303 | ||||
|
304 | return result | |||
|
305 | ||||
|
306 | ||||
|
307 | # NSTextView delegate methods... | |||
171 | def textView_doCommandBySelector_(self, textView, selector): |
|
308 | def textView_doCommandBySelector_(self, textView, selector): | |
172 | assert(textView == self.textView) |
|
309 | assert(textView == self.textView) | |
173 | NSLog("textView_doCommandBySelector_: "+selector) |
|
310 | NSLog("textView_doCommandBySelector_: "+selector) | |
174 |
|
311 | |||
175 |
|
312 | |||
176 | if(selector == 'insertNewline:'): |
|
313 | if(selector == 'insertNewline:'): | |
177 |
indent = self.current |
|
314 | indent = self.current_indent_string() | |
178 | if(indent): |
|
315 | if(indent): | |
179 |
line = indent + self.current |
|
316 | line = indent + self.current_line() | |
180 | else: |
|
317 | else: | |
181 |
line = self.current |
|
318 | line = self.current_line() | |
182 |
|
319 | |||
183 |
if(self.is_complete(self.current |
|
320 | if(self.is_complete(self.current_block())): | |
184 |
self.execute(self.current |
|
321 | self.execute(self.current_block(), | |
185 | blockID=self.currentBlockID) |
|
322 | blockID=self.currentBlockID) | |
186 |
self.start |
|
323 | self.start_new_block() | |
187 |
|
324 | |||
188 | return True |
|
325 | return True | |
189 |
|
326 | |||
190 | return False |
|
327 | return False | |
191 |
|
328 | |||
192 | elif(selector == 'moveUp:'): |
|
329 | elif(selector == 'moveUp:'): | |
193 |
prevBlock = self.get_history_previous(self.current |
|
330 | prevBlock = self.get_history_previous(self.current_block()) | |
194 | if(prevBlock != None): |
|
331 | if(prevBlock != None): | |
195 |
self.replace |
|
332 | self.replace_current_block_with_string(textView, prevBlock) | |
196 | else: |
|
333 | else: | |
197 | NSBeep() |
|
334 | NSBeep() | |
198 | return True |
|
335 | return True | |
199 |
|
336 | |||
200 | elif(selector == 'moveDown:'): |
|
337 | elif(selector == 'moveDown:'): | |
201 | nextBlock = self.get_history_next() |
|
338 | nextBlock = self.get_history_next() | |
202 | if(nextBlock != None): |
|
339 | if(nextBlock != None): | |
203 |
self.replace |
|
340 | self.replace_current_block_with_string(textView, nextBlock) | |
204 | else: |
|
341 | else: | |
205 | NSBeep() |
|
342 | NSBeep() | |
206 | return True |
|
343 | return True | |
207 |
|
344 | |||
208 | elif(selector == 'moveToBeginningOfParagraph:'): |
|
345 | elif(selector == 'moveToBeginningOfParagraph:'): | |
209 |
textView.setSelectedRange_(NSMakeRange( |
|
346 | textView.setSelectedRange_(NSMakeRange( | |
|
347 | self.current_block_range().location, | |||
|
348 | 0)) | |||
210 | return True |
|
349 | return True | |
211 | elif(selector == 'moveToEndOfParagraph:'): |
|
350 | elif(selector == 'moveToEndOfParagraph:'): | |
212 |
textView.setSelectedRange_(NSMakeRange( |
|
351 | textView.setSelectedRange_(NSMakeRange( | |
213 |
|
|
352 | self.current_block_range().location + \ | |
|
353 | self.current_block_range().length, 0)) | |||
214 | return True |
|
354 | return True | |
215 | elif(selector == 'deleteToEndOfParagraph:'): |
|
355 | elif(selector == 'deleteToEndOfParagraph:'): | |
216 |
if(textView.selectedRange().location <= |
|
356 | if(textView.selectedRange().location <= \ | |
|
357 | self.current_block_range().location): | |||
217 | # Intersect the selected range with the current line range |
|
358 | # Intersect the selected range with the current line range | |
218 |
if(self.current |
|
359 | if(self.current_block_range().length < 0): | |
219 | self.blockRanges[self.currentBlockID].length = 0 |
|
360 | self.blockRanges[self.currentBlockID].length = 0 | |
220 |
|
361 | |||
221 | r = NSIntersectionRange(textView.rangesForUserTextChange()[0], |
|
362 | r = NSIntersectionRange(textView.rangesForUserTextChange()[0], | |
222 |
self.current |
|
363 | self.current_block_range()) | |
223 |
|
364 | |||
224 | if(r.length > 0): #no intersection |
|
365 | if(r.length > 0): #no intersection | |
225 | textView.setSelectedRange_(r) |
|
366 | textView.setSelectedRange_(r) | |
226 |
|
367 | |||
227 | return False # don't actually handle the delete |
|
368 | return False # don't actually handle the delete | |
228 |
|
369 | |||
229 | elif(selector == 'insertTab:'): |
|
370 | elif(selector == 'insertTab:'): | |
230 |
if(len(self.current |
|
371 | if(len(self.current_line().strip()) == 0): #only white space | |
231 | return False |
|
372 | return False | |
232 | else: |
|
373 | else: | |
233 | self.textView.complete_(self) |
|
374 | self.textView.complete_(self) | |
234 | return True |
|
375 | return True | |
235 |
|
376 | |||
236 | elif(selector == 'deleteBackward:'): |
|
377 | elif(selector == 'deleteBackward:'): | |
237 | #if we're at the beginning of the current block, ignore |
|
378 | #if we're at the beginning of the current block, ignore | |
238 |
if(textView.selectedRange().location == |
|
379 | if(textView.selectedRange().location == \ | |
|
380 | self.current_block_range().location): | |||
239 | return True |
|
381 | return True | |
240 | else: |
|
382 | else: | |
241 |
self.current |
|
383 | self.current_block_range().length-=1 | |
242 | return False |
|
384 | return False | |
243 | return False |
|
385 | return False | |
244 |
|
386 | |||
245 |
|
387 | |||
246 |
def textView_shouldChangeTextInRanges_replacementStrings_(self, |
|
388 | def textView_shouldChangeTextInRanges_replacementStrings_(self, | |
|
389 | textView, ranges, replacementStrings): | |||
247 | """ |
|
390 | """ | |
248 | Delegate method for NSTextView. |
|
391 | Delegate method for NSTextView. | |
249 |
|
392 | |||
250 |
Refuse change text in ranges not at end, but make those changes at |
|
393 | Refuse change text in ranges not at end, but make those changes at | |
|
394 | end. | |||
251 | """ |
|
395 | """ | |
252 |
|
396 | |||
253 | #print 'textView_shouldChangeTextInRanges_replacementStrings_:',ranges,replacementStrings |
|
|||
254 | assert(len(ranges) == len(replacementStrings)) |
|
397 | assert(len(ranges) == len(replacementStrings)) | |
255 | allow = True |
|
398 | allow = True | |
256 | for r,s in zip(ranges, replacementStrings): |
|
399 | for r,s in zip(ranges, replacementStrings): | |
257 | r = r.rangeValue() |
|
400 | r = r.rangeValue() | |
258 | if(textView.textStorage().length() > 0 and |
|
401 | if(textView.textStorage().length() > 0 and | |
259 |
r.location < self.current |
|
402 | r.location < self.current_block_range().location): | |
260 | self.insert_text(s) |
|
403 | self.insert_text(s) | |
261 | allow = False |
|
404 | allow = False | |
262 |
|
405 | |||
263 |
|
406 | |||
264 |
self.blockRanges.setdefault(self.currentBlockID, |
|
407 | self.blockRanges.setdefault(self.currentBlockID, | |
|
408 | self.current_block_range()).length +=\ | |||
|
409 | len(s) | |||
265 |
|
410 | |||
266 | return allow |
|
411 | return allow | |
267 |
|
412 | |||
268 |
def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, |
|
413 | def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, | |
|
414 | textView, words, charRange, index): | |||
269 | try: |
|
415 | try: | |
270 |
t |
|
416 | ts = textView.textStorage() | |
|
417 | token = ts.string().substringWithRange_(charRange) | |||
271 | completions = blockingCallFromThread(self.complete, token) |
|
418 | completions = blockingCallFromThread(self.complete, token) | |
272 | except: |
|
419 | except: | |
273 | completions = objc.nil |
|
420 | completions = objc.nil | |
274 | NSBeep() |
|
421 | NSBeep() | |
275 |
|
422 | |||
276 | return (completions,0) |
|
423 | return (completions,0) | |
277 |
|
424 | |||
278 |
|
||||
279 | def startNewBlock(self): |
|
|||
280 | """""" |
|
|||
281 |
|
||||
282 | self.currentBlockID = self.nextBlockID() |
|
|||
283 |
|
||||
284 |
|
||||
285 |
|
||||
286 | def nextBlockID(self): |
|
|||
287 |
|
||||
288 | return uuid.uuid4() |
|
|||
289 |
|
||||
290 | def currentBlockRange(self): |
|
|||
291 | return self.blockRanges.get(self.currentBlockID, NSMakeRange(self.textView.textStorage().length(), 0)) |
|
|||
292 |
|
||||
293 | def currentBlock(self): |
|
|||
294 | """The current block's text""" |
|
|||
295 |
|
||||
296 | return self.textForRange(self.currentBlockRange()) |
|
|||
297 |
|
||||
298 | def textForRange(self, textRange): |
|
|||
299 | """textForRange""" |
|
|||
300 |
|
||||
301 | return self.textView.textStorage().string().substringWithRange_(textRange) |
|
|||
302 |
|
||||
303 | def currentLine(self): |
|
|||
304 | block = self.textForRange(self.currentBlockRange()) |
|
|||
305 | block = block.split('\n') |
|
|||
306 | return block[-1] |
|
|||
307 |
|
||||
308 | def update_cell_prompt(self, result): |
|
|||
309 | if(isinstance(result, Failure)): |
|
|||
310 | blockID = result.blockID |
|
|||
311 | else: |
|
|||
312 | blockID = result['blockID'] |
|
|||
313 |
|
||||
314 |
|
||||
315 | self.insert_text(self.input_prompt(result=result), |
|
|||
316 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), |
|
|||
317 | scrollToVisible=False |
|
|||
318 | ) |
|
|||
319 |
|
||||
320 | return result |
|
|||
321 |
|
||||
322 |
|
||||
323 | def render_result(self, result): |
|
|||
324 | blockID = result['blockID'] |
|
|||
325 | inputRange = self.blockRanges[blockID] |
|
|||
326 | del self.blockRanges[blockID] |
|
|||
327 |
|
||||
328 | #print inputRange,self.currentBlockRange() |
|
|||
329 | self.insert_text('\n' + |
|
|||
330 | self.output_prompt(result) + |
|
|||
331 | result.get('display',{}).get('pprint','') + |
|
|||
332 | '\n\n', |
|
|||
333 | textRange=NSMakeRange(inputRange.location+inputRange.length, 0)) |
|
|||
334 | return result |
|
|||
335 |
|
||||
336 |
|
||||
337 | def render_error(self, failure): |
|
|||
338 | self.insert_text('\n\n'+str(failure)+'\n\n') |
|
|||
339 | self.startNewBlock() |
|
|||
340 | return failure |
|
|||
341 |
|
||||
342 |
|
||||
343 | def insert_text(self, string=None, textRange=None, scrollToVisible=True): |
|
|||
344 | """Insert text into textView at textRange, updating blockRanges as necessary""" |
|
|||
345 |
|
||||
346 | if(textRange == None): |
|
|||
347 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text |
|
|||
348 |
|
||||
349 | for r in self.blockRanges.itervalues(): |
|
|||
350 | intersection = NSIntersectionRange(r,textRange) |
|
|||
351 | if(intersection.length == 0): #ranges don't intersect |
|
|||
352 | if r.location >= textRange.location: |
|
|||
353 | r.location += len(string) |
|
|||
354 | else: #ranges intersect |
|
|||
355 | if(r.location <= textRange.location): |
|
|||
356 | assert(intersection.length == textRange.length) |
|
|||
357 | r.length += textRange.length |
|
|||
358 | else: |
|
|||
359 | r.location += intersection.length |
|
|||
360 |
|
||||
361 | self.textView.replaceCharactersInRange_withString_(textRange, string) #textStorage().string() |
|
|||
362 | self.textView.setSelectedRange_(NSMakeRange(textRange.location+len(string), 0)) |
|
|||
363 | if(scrollToVisible): |
|
|||
364 | self.textView.scrollRangeToVisible_(textRange) |
|
|||
365 |
|
||||
366 |
|
||||
367 |
|
||||
368 | def replaceCurrentBlockWithString(self, textView, string): |
|
|||
369 | textView.replaceCharactersInRange_withString_(self.currentBlockRange(), |
|
|||
370 | string) |
|
|||
371 | self.currentBlockRange().length = len(string) |
|
|||
372 | r = NSMakeRange(textView.textStorage().length(), 0) |
|
|||
373 | textView.scrollRangeToVisible_(r) |
|
|||
374 | textView.setSelectedRange_(r) |
|
|||
375 |
|
||||
376 |
|
||||
377 | def currentIndentString(self): |
|
|||
378 | """returns string for indent or None if no indent""" |
|
|||
379 |
|
||||
380 | if(len(self.currentBlock()) > 0): |
|
|||
381 | lines = self.currentBlock().split('\n') |
|
|||
382 | currentIndent = len(lines[-1]) - len(lines[-1]) |
|
|||
383 | if(currentIndent == 0): |
|
|||
384 | currentIndent = self.tabSpaces |
|
|||
385 |
|
||||
386 | if(self.tabUsesSpaces): |
|
|||
387 | result = ' ' * currentIndent |
|
|||
388 | else: |
|
|||
389 | result = '\t' * (currentIndent/self.tabSpaces) |
|
|||
390 | else: |
|
|||
391 | result = None |
|
|||
392 |
|
||||
393 | return result |
|
|||
394 |
|
||||
395 |
|
425 |
@@ -1,77 +1,72 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 |
"""This file contains unittests for the |
|
2 | """This file contains unittests for the | |
3 |
|
3 | IPython.frontend.cocoa.cocoa_frontend module. | ||
4 | Things that should be tested: |
|
|||
5 |
|
||||
6 | - IPythonCocoaController instantiates an IEngineInteractive |
|
|||
7 | - IPythonCocoaController executes code on the engine |
|
|||
8 | - IPythonCocoaController mirrors engine's user_ns |
|
|||
9 | """ |
|
4 | """ | |
10 | __docformat__ = "restructuredtext en" |
|
5 | __docformat__ = "restructuredtext en" | |
11 |
|
6 | |||
12 |
#--------------------------------------------------------------------------- |
|
7 | #--------------------------------------------------------------------------- | |
13 | # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu> |
|
8 | # Copyright (C) 2005 The IPython Development Team | |
14 | # Brian E Granger <ellisonbg@gmail.com> |
|
9 | # | |
15 | # Benjamin Ragan-Kelley <benjaminrk@gmail.com> |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
16 | # |
|
11 | # the file COPYING, distributed as part of this software. | |
17 | # Distributed under the terms of the BSD License. The full license is in |
|
12 | #--------------------------------------------------------------------------- | |
18 | # the file COPYING, distributed as part of this software. |
|
13 | ||
19 |
#--------------------------------------------------------------------------- |
|
14 | #--------------------------------------------------------------------------- | |
20 |
|
15 | # Imports | ||
21 |
#--------------------------------------------------------------------------- |
|
16 | #--------------------------------------------------------------------------- | |
22 | # Imports |
|
17 | from IPython.kernel.core.interpreter import Interpreter | |
23 | #------------------------------------------------------------------------------- |
|
|||
24 | from IPython.kernel.core.interpreter import Interpreter |
|
|||
25 | import IPython.kernel.engineservice as es |
|
18 | import IPython.kernel.engineservice as es | |
26 | from IPython.testing.util import DeferredTestCase |
|
19 | from IPython.testing.util import DeferredTestCase | |
27 | from twisted.internet.defer import succeed |
|
20 | from twisted.internet.defer import succeed | |
28 | from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController |
|
21 | from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController | |
29 |
|
22 | |||
30 | from Foundation import NSMakeRect |
|
23 | from Foundation import NSMakeRect | |
31 | from AppKit import NSTextView, NSScrollView |
|
24 | from AppKit import NSTextView, NSScrollView | |
32 |
|
25 | |||
33 | class TestIPythonCocoaControler(DeferredTestCase): |
|
26 | class TestIPythonCocoaControler(DeferredTestCase): | |
34 | """Tests for IPythonCocoaController""" |
|
27 | """Tests for IPythonCocoaController""" | |
35 |
|
28 | |||
36 | def setUp(self): |
|
29 | def setUp(self): | |
37 | self.controller = IPythonCocoaController.alloc().init() |
|
30 | self.controller = IPythonCocoaController.alloc().init() | |
38 | self.engine = es.EngineService() |
|
31 | self.engine = es.EngineService() | |
39 | self.engine.startService() |
|
32 | self.engine.startService() | |
40 |
|
33 | |||
41 |
|
34 | |||
42 | def tearDown(self): |
|
35 | def tearDown(self): | |
43 | self.controller = None |
|
36 | self.controller = None | |
44 | self.engine.stopService() |
|
37 | self.engine.stopService() | |
45 |
|
38 | |||
46 | def testControllerExecutesCode(self): |
|
39 | def testControllerExecutesCode(self): | |
47 | code ="""5+5""" |
|
40 | code ="""5+5""" | |
48 | expected = Interpreter().execute(code) |
|
41 | expected = Interpreter().execute(code) | |
49 | del expected['number'] |
|
42 | del expected['number'] | |
50 | def removeNumberAndID(result): |
|
43 | def removeNumberAndID(result): | |
51 | del result['number'] |
|
44 | del result['number'] | |
52 | del result['id'] |
|
45 | del result['id'] | |
53 | return result |
|
46 | return result | |
54 | self.assertDeferredEquals(self.controller.execute(code).addCallback(removeNumberAndID), expected) |
|
47 | self.assertDeferredEquals( | |
|
48 | self.controller.execute(code).addCallback(removeNumberAndID), | |||
|
49 | expected) | |||
55 |
|
50 | |||
56 | def testControllerMirrorsUserNSWithValuesAsStrings(self): |
|
51 | def testControllerMirrorsUserNSWithValuesAsStrings(self): | |
57 | code = """userns1=1;userns2=2""" |
|
52 | code = """userns1=1;userns2=2""" | |
58 | def testControllerUserNS(result): |
|
53 | def testControllerUserNS(result): | |
59 | self.assertEquals(self.controller.userNS['userns1'], 1) |
|
54 | self.assertEquals(self.controller.userNS['userns1'], 1) | |
60 | self.assertEquals(self.controller.userNS['userns2'], 2) |
|
55 | self.assertEquals(self.controller.userNS['userns2'], 2) | |
61 |
|
56 | |||
62 | self.controller.execute(code).addCallback(testControllerUserNS) |
|
57 | self.controller.execute(code).addCallback(testControllerUserNS) | |
63 |
|
58 | |||
64 |
|
59 | |||
65 | def testControllerInstantiatesIEngine(self): |
|
60 | def testControllerInstantiatesIEngine(self): | |
66 | self.assert_(es.IEngineBase.providedBy(self.controller.engine)) |
|
61 | self.assert_(es.IEngineBase.providedBy(self.controller.engine)) | |
67 |
|
62 | |||
68 | def testControllerCompletesToken(self): |
|
63 | def testControllerCompletesToken(self): | |
69 | code = """longNameVariable=10""" |
|
64 | code = """longNameVariable=10""" | |
70 | def testCompletes(result): |
|
65 | def testCompletes(result): | |
71 | self.assert_("longNameVariable" in result) |
|
66 | self.assert_("longNameVariable" in result) | |
72 |
|
67 | |||
73 | def testCompleteToken(result): |
|
68 | def testCompleteToken(result): | |
74 | self.controller.complete("longNa").addCallback(testCompletes) |
|
69 | self.controller.complete("longNa").addCallback(testCompletes) | |
75 |
|
70 | |||
76 | self.controller.execute(code).addCallback(testCompletes) |
|
71 | self.controller.execute(code).addCallback(testCompletes) | |
77 |
|
72 |
@@ -1,326 +1,352 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
|
2 | # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*- | |||
2 | """ |
|
3 | """ | |
3 |
frontendbase provides an interface and base class for GUI frontends for |
|
4 | frontendbase provides an interface and base class for GUI frontends for | |
|
5 | IPython.kernel/IPython.kernel.core. | |||
4 |
|
6 | |||
5 | Frontend implementations will likely want to subclass FrontEndBase. |
|
7 | Frontend implementations will likely want to subclass FrontEndBase. | |
6 |
|
8 | |||
7 | Author: Barry Wark |
|
9 | Author: Barry Wark | |
8 | """ |
|
10 | """ | |
9 | __docformat__ = "restructuredtext en" |
|
11 | __docformat__ = "restructuredtext en" | |
10 |
|
12 | |||
11 | #------------------------------------------------------------------------------- |
|
13 | #------------------------------------------------------------------------------- | |
12 | # Copyright (C) 2008 The IPython Development Team |
|
14 | # Copyright (C) 2008 The IPython Development Team | |
13 | # |
|
15 | # | |
14 | # 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 | |
15 | # the file COPYING, distributed as part of this software. |
|
17 | # the file COPYING, distributed as part of this software. | |
16 | #------------------------------------------------------------------------------- |
|
18 | #------------------------------------------------------------------------------- | |
17 |
|
19 | |||
18 | #------------------------------------------------------------------------------- |
|
20 | #------------------------------------------------------------------------------- | |
19 | # Imports |
|
21 | # Imports | |
20 | #------------------------------------------------------------------------------- |
|
22 | #------------------------------------------------------------------------------- | |
21 | import string |
|
23 | import string | |
22 | import uuid |
|
24 | import uuid | |
23 | import _ast |
|
25 | import _ast | |
24 |
|
26 | |||
25 | import zope.interface as zi |
|
27 | import zope.interface as zi | |
26 |
|
28 | |||
27 | from IPython.kernel.core.history import FrontEndHistory |
|
29 | from IPython.kernel.core.history import FrontEndHistory | |
28 | from IPython.kernel.core.util import Bunch |
|
30 | from IPython.kernel.core.util import Bunch | |
29 | from IPython.kernel.engineservice import IEngineCore |
|
31 | from IPython.kernel.engineservice import IEngineCore | |
30 |
|
32 | |||
31 | from twisted.python.failure import Failure |
|
33 | from twisted.python.failure import Failure | |
32 |
|
34 | |||
33 | ############################################################################## |
|
35 | ############################################################################## | |
34 | # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or |
|
36 | # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or | |
35 | # not |
|
37 | # not | |
36 |
|
38 | |||
37 | rc = Bunch() |
|
39 | rc = Bunch() | |
38 | rc.prompt_in1 = r'In [$number]: ' |
|
40 | rc.prompt_in1 = r'In [$number]: ' | |
39 | rc.prompt_in2 = r'...' |
|
41 | rc.prompt_in2 = r'...' | |
40 | rc.prompt_out = r'Out [$number]: ' |
|
42 | rc.prompt_out = r'Out [$number]: ' | |
41 |
|
43 | |||
42 | ############################################################################## |
|
44 | ############################################################################## | |
43 |
|
45 | |||
44 | class IFrontEndFactory(zi.Interface): |
|
46 | class IFrontEndFactory(zi.Interface): | |
45 | """Factory interface for frontends.""" |
|
47 | """Factory interface for frontends.""" | |
46 |
|
48 | |||
47 | def __call__(engine=None, history=None): |
|
49 | def __call__(engine=None, history=None): | |
48 | """ |
|
50 | """ | |
49 | Parameters: |
|
51 | Parameters: | |
50 | interpreter : IPython.kernel.engineservice.IEngineCore |
|
52 | interpreter : IPython.kernel.engineservice.IEngineCore | |
51 | """ |
|
53 | """ | |
52 |
|
54 | |||
53 | pass |
|
55 | pass | |
54 |
|
56 | |||
55 |
|
57 | |||
56 |
|
58 | |||
57 | class IFrontEnd(zi.Interface): |
|
59 | class IFrontEnd(zi.Interface): | |
58 | """Interface for frontends. All methods return t.i.d.Deferred""" |
|
60 | """Interface for frontends. All methods return t.i.d.Deferred""" | |
59 |
|
61 | |||
60 |
zi.Attribute("input_prompt_template", "string.Template instance |
|
62 | zi.Attribute("input_prompt_template", "string.Template instance\ | |
61 | zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.") |
|
63 | substituteable with execute result.") | |
62 |
zi.Attribute(" |
|
64 | zi.Attribute("output_prompt_template", "string.Template instance\ | |
|
65 | substituteable with execute result.") | |||
|
66 | zi.Attribute("continuation_prompt_template", "string.Template instance\ | |||
|
67 | substituteable with execute result.") | |||
63 |
|
68 | |||
64 | def update_cell_prompt(self, result): |
|
69 | def update_cell_prompt(self, result): | |
65 | """Subclass may override to update the input prompt for a block. |
|
70 | """Subclass may override to update the input prompt for a block. | |
66 |
Since this method will be called as a |
|
71 | Since this method will be called as a | |
67 | implementations should return result when finished.""" |
|
72 | twisted.internet.defer.Deferred's callback, | |
|
73 | implementations should return result when finished. | |||
|
74 | ||||
|
75 | NB: result is a failure if the execute returned a failre. | |||
|
76 | To get the blockID, you should do something like:: | |||
|
77 | if(isinstance(result, twisted.python.failure.Failure)): | |||
|
78 | blockID = result.blockID | |||
|
79 | else: | |||
|
80 | blockID = result['blockID'] | |||
|
81 | """ | |||
68 |
|
82 | |||
69 | pass |
|
83 | pass | |
70 |
|
84 | |||
71 | def render_result(self, result): |
|
85 | def render_result(self, result): | |
72 |
"""Render the result of an execute call. Implementors may choose the |
|
86 | """Render the result of an execute call. Implementors may choose the | |
73 | For example, a notebook-style frontend might render a Chaco plot inline. |
|
87 | method of rendering. | |
|
88 | For example, a notebook-style frontend might render a Chaco plot | |||
|
89 | inline. | |||
74 |
|
90 | |||
75 | Parameters: |
|
91 | Parameters: | |
76 | result : dict (result of IEngineBase.execute ) |
|
92 | result : dict (result of IEngineBase.execute ) | |
77 |
|
93 | |||
78 | Result: |
|
94 | Result: | |
79 | Output of frontend rendering |
|
95 | Output of frontend rendering | |
80 | """ |
|
96 | """ | |
81 |
|
97 | |||
82 | pass |
|
98 | pass | |
83 |
|
99 | |||
84 | def render_error(self, failure): |
|
100 | def render_error(self, failure): | |
85 |
"""Subclasses must override to render the failure. Since this method |
|
101 | """Subclasses must override to render the failure. Since this method | |
86 |
twisted.internet.defer.Deferred's callback, |
|
102 | ill be called as a twisted.internet.defer.Deferred's callback, | |
87 | when finished.""" |
|
103 | implementations should return result when finished. | |
|
104 | """ | |||
88 |
|
105 | |||
89 | pass |
|
106 | pass | |
90 |
|
107 | |||
91 |
|
108 | |||
92 | def input_prompt(result={}): |
|
109 | def input_prompt(result={}): | |
93 |
"""Returns the input prompt by subsituting into |
|
110 | """Returns the input prompt by subsituting into | |
|
111 | self.input_prompt_template | |||
|
112 | """ | |||
94 | pass |
|
113 | pass | |
95 |
|
114 | |||
96 | def output_prompt(result): |
|
115 | def output_prompt(result): | |
97 |
"""Returns the output prompt by subsituting into |
|
116 | """Returns the output prompt by subsituting into | |
|
117 | self.output_prompt_template | |||
|
118 | """ | |||
98 |
|
119 | |||
99 | pass |
|
120 | pass | |
100 |
|
121 | |||
101 | def continuation_prompt(): |
|
122 | def continuation_prompt(): | |
102 |
"""Returns the continuation prompt by subsituting into |
|
123 | """Returns the continuation prompt by subsituting into | |
|
124 | self.continuation_prompt_template | |||
|
125 | """ | |||
103 |
|
126 | |||
104 | pass |
|
127 | pass | |
105 |
|
128 | |||
106 | def is_complete(block): |
|
129 | def is_complete(block): | |
107 | """Returns True if block is complete, False otherwise.""" |
|
130 | """Returns True if block is complete, False otherwise.""" | |
108 |
|
131 | |||
109 | pass |
|
132 | pass | |
110 |
|
133 | |||
111 | def compile_ast(block): |
|
134 | def compile_ast(block): | |
112 | """Compiles block to an _ast.AST""" |
|
135 | """Compiles block to an _ast.AST""" | |
113 |
|
136 | |||
114 | pass |
|
137 | pass | |
115 |
|
138 | |||
116 |
|
139 | |||
117 | def get_history_previous(currentBlock): |
|
140 | def get_history_previous(currentBlock): | |
118 | """Returns the block previous in the history. Saves currentBlock if |
|
141 | """Returns the block previous in the history. Saves currentBlock if | |
119 | the history_cursor is currently at the end of the input history""" |
|
142 | the history_cursor is currently at the end of the input history""" | |
120 | pass |
|
143 | pass | |
121 |
|
144 | |||
122 | def get_history_next(): |
|
145 | def get_history_next(): | |
123 | """Returns the next block in the history.""" |
|
146 | """Returns the next block in the history.""" | |
124 |
|
147 | |||
125 | pass |
|
148 | pass | |
126 |
|
149 | |||
127 |
|
150 | |||
128 | class FrontEndBase(object): |
|
151 | class FrontEndBase(object): | |
129 | """ |
|
152 | """ | |
130 | FrontEndBase manages the state tasks for a CLI frontend: |
|
153 | FrontEndBase manages the state tasks for a CLI frontend: | |
131 | - Input and output history management |
|
154 | - Input and output history management | |
132 | - Input/continuation and output prompt generation |
|
155 | - Input/continuation and output prompt generation | |
133 |
|
156 | |||
134 | Some issues (due to possibly unavailable engine): |
|
157 | Some issues (due to possibly unavailable engine): | |
135 | - How do we get the current cell number for the engine? |
|
158 | - How do we get the current cell number for the engine? | |
136 | - How do we handle completions? |
|
159 | - How do we handle completions? | |
137 | """ |
|
160 | """ | |
138 |
|
161 | |||
139 | zi.implements(IFrontEnd) |
|
162 | zi.implements(IFrontEnd) | |
140 | zi.classProvides(IFrontEndFactory) |
|
163 | zi.classProvides(IFrontEndFactory) | |
141 |
|
164 | |||
142 | history_cursor = 0 |
|
165 | history_cursor = 0 | |
143 |
|
166 | |||
144 | current_indent_level = 0 |
|
167 | current_indent_level = 0 | |
145 |
|
168 | |||
146 |
|
169 | |||
147 | input_prompt_template = string.Template(rc.prompt_in1) |
|
170 | input_prompt_template = string.Template(rc.prompt_in1) | |
148 | output_prompt_template = string.Template(rc.prompt_out) |
|
171 | output_prompt_template = string.Template(rc.prompt_out) | |
149 | continuation_prompt_template = string.Template(rc.prompt_in2) |
|
172 | continuation_prompt_template = string.Template(rc.prompt_in2) | |
150 |
|
173 | |||
151 | def __init__(self, engine=None, history=None): |
|
174 | def __init__(self, engine=None, history=None): | |
152 | assert(engine==None or IEngineCore.providedBy(engine)) |
|
175 | assert(engine==None or IEngineCore.providedBy(engine)) | |
153 | self.engine = IEngineCore(engine) |
|
176 | self.engine = IEngineCore(engine) | |
154 | if history is None: |
|
177 | if history is None: | |
155 | self.history = FrontEndHistory(input_cache=['']) |
|
178 | self.history = FrontEndHistory(input_cache=['']) | |
156 | else: |
|
179 | else: | |
157 | self.history = history |
|
180 | self.history = history | |
158 |
|
181 | |||
159 |
|
182 | |||
160 | def input_prompt(self, result={}): |
|
183 | def input_prompt(self, result={}): | |
161 | """Returns the current input prompt |
|
184 | """Returns the current input prompt | |
162 |
|
185 | |||
163 | It would be great to use ipython1.core.prompts.Prompt1 here |
|
186 | It would be great to use ipython1.core.prompts.Prompt1 here | |
164 | """ |
|
187 | """ | |
165 |
|
188 | |||
166 | result.setdefault('number','') |
|
189 | result.setdefault('number','') | |
167 |
|
190 | |||
168 | return self.input_prompt_template.safe_substitute(result) |
|
191 | return self.input_prompt_template.safe_substitute(result) | |
169 |
|
192 | |||
170 |
|
193 | |||
171 | def continuation_prompt(self): |
|
194 | def continuation_prompt(self): | |
172 | """Returns the current continuation prompt""" |
|
195 | """Returns the current continuation prompt""" | |
173 |
|
196 | |||
174 | return self.continuation_prompt_template.safe_substitute() |
|
197 | return self.continuation_prompt_template.safe_substitute() | |
175 |
|
198 | |||
176 | def output_prompt(self, result): |
|
199 | def output_prompt(self, result): | |
177 | """Returns the output prompt for result""" |
|
200 | """Returns the output prompt for result""" | |
178 |
|
201 | |||
179 | return self.output_prompt_template.safe_substitute(result) |
|
202 | return self.output_prompt_template.safe_substitute(result) | |
180 |
|
203 | |||
181 |
|
204 | |||
182 | def is_complete(self, block): |
|
205 | def is_complete(self, block): | |
183 | """Determine if block is complete. |
|
206 | """Determine if block is complete. | |
184 |
|
207 | |||
185 | Parameters |
|
208 | Parameters | |
186 | block : string |
|
209 | block : string | |
187 |
|
210 | |||
188 | Result |
|
211 | Result | |
189 | True if block can be sent to the engine without compile errors. |
|
212 | True if block can be sent to the engine without compile errors. | |
190 | False otherwise. |
|
213 | False otherwise. | |
191 | """ |
|
214 | """ | |
192 |
|
215 | |||
193 | try: |
|
216 | try: | |
194 | ast = self.compile_ast(block) |
|
217 | ast = self.compile_ast(block) | |
195 | except: |
|
218 | except: | |
196 | return False |
|
219 | return False | |
197 |
|
220 | |||
198 | lines = block.split('\n') |
|
221 | lines = block.split('\n') | |
199 | return (len(lines)==1 or str(lines[-1])=='') |
|
222 | return (len(lines)==1 or str(lines[-1])=='') | |
200 |
|
223 | |||
201 |
|
224 | |||
202 | def compile_ast(self, block): |
|
225 | def compile_ast(self, block): | |
203 | """Compile block to an AST |
|
226 | """Compile block to an AST | |
204 |
|
227 | |||
205 | Parameters: |
|
228 | Parameters: | |
206 | block : str |
|
229 | block : str | |
207 |
|
230 | |||
208 | Result: |
|
231 | Result: | |
209 | AST |
|
232 | AST | |
210 |
|
233 | |||
211 | Throws: |
|
234 | Throws: | |
212 | Exception if block cannot be compiled |
|
235 | Exception if block cannot be compiled | |
213 | """ |
|
236 | """ | |
214 |
|
237 | |||
215 | return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST) |
|
238 | return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST) | |
216 |
|
239 | |||
217 |
|
240 | |||
218 | def execute(self, block, blockID=None): |
|
241 | def execute(self, block, blockID=None): | |
219 | """Execute the block and return result. |
|
242 | """Execute the block and return result. | |
220 |
|
243 | |||
221 | Parameters: |
|
244 | Parameters: | |
222 | block : {str, AST} |
|
245 | block : {str, AST} | |
223 | blockID : any |
|
246 | blockID : any | |
224 |
Caller may provide an ID to identify this block. |
|
247 | Caller may provide an ID to identify this block. | |
|
248 | result['blockID'] := blockID | |||
225 |
|
249 | |||
226 | Result: |
|
250 | Result: | |
227 | Deferred result of self.interpreter.execute |
|
251 | Deferred result of self.interpreter.execute | |
228 | """ |
|
252 | """ | |
229 |
|
253 | |||
230 | if(not self.is_complete(block)): |
|
254 | if(not self.is_complete(block)): | |
231 | return Failure(Exception("Block is not compilable")) |
|
255 | return Failure(Exception("Block is not compilable")) | |
232 |
|
256 | |||
233 | if(blockID == None): |
|
257 | if(blockID == None): | |
234 | blockID = uuid.uuid4() #random UUID |
|
258 | blockID = uuid.uuid4() #random UUID | |
235 |
|
259 | |||
236 | d = self.engine.execute(block) |
|
260 | d = self.engine.execute(block) | |
237 | d.addCallback(self._add_history, block=block) |
|
261 | d.addCallback(self._add_history, block=block) | |
238 | d.addBoth(self._add_block_id, blockID) |
|
262 | d.addBoth(self._add_block_id, blockID) | |
239 | d.addBoth(self.update_cell_prompt) |
|
263 | d.addBoth(self.update_cell_prompt) | |
240 | d.addCallbacks(self.render_result, errback=self.render_error) |
|
264 | d.addCallbacks(self.render_result, errback=self.render_error) | |
241 |
|
265 | |||
242 | return d |
|
266 | return d | |
243 |
|
267 | |||
244 |
|
268 | |||
245 | def _add_block_id(self, result, blockID): |
|
269 | def _add_block_id(self, result, blockID): | |
246 |
"""Add the blockID to result or failure. Unfortunatley, we have to |
|
270 | """Add the blockID to result or failure. Unfortunatley, we have to | |
247 | differently than result dicts |
|
271 | treat failures differently than result dicts. | |
248 | """ |
|
272 | """ | |
249 |
|
273 | |||
250 | if(isinstance(result, Failure)): |
|
274 | if(isinstance(result, Failure)): | |
251 | result.blockID = blockID |
|
275 | result.blockID = blockID | |
252 | else: |
|
276 | else: | |
253 | result['blockID'] = blockID |
|
277 | result['blockID'] = blockID | |
254 |
|
278 | |||
255 | return result |
|
279 | return result | |
256 |
|
280 | |||
257 | def _add_history(self, result, block=None): |
|
281 | def _add_history(self, result, block=None): | |
258 | """Add block to the history""" |
|
282 | """Add block to the history""" | |
259 |
|
283 | |||
260 | assert(block != None) |
|
284 | assert(block != None) | |
261 | self.history.add_items([block]) |
|
285 | self.history.add_items([block]) | |
262 | self.history_cursor += 1 |
|
286 | self.history_cursor += 1 | |
263 |
|
287 | |||
264 | return result |
|
288 | return result | |
265 |
|
289 | |||
266 |
|
290 | |||
267 | def get_history_previous(self, currentBlock): |
|
291 | def get_history_previous(self, currentBlock): | |
268 | """ Returns previous history string and decrement history cursor. |
|
292 | """ Returns previous history string and decrement history cursor. | |
269 | """ |
|
293 | """ | |
270 | command = self.history.get_history_item(self.history_cursor - 1) |
|
294 | command = self.history.get_history_item(self.history_cursor - 1) | |
271 |
|
295 | |||
272 | if command is not None: |
|
296 | if command is not None: | |
273 | if(self.history_cursor == len(self.history.input_cache)): |
|
297 | if(self.history_cursor == len(self.history.input_cache)): | |
274 | self.history.input_cache[self.history_cursor] = currentBlock |
|
298 | self.history.input_cache[self.history_cursor] = currentBlock | |
275 | self.history_cursor -= 1 |
|
299 | self.history_cursor -= 1 | |
276 | return command |
|
300 | return command | |
277 |
|
301 | |||
278 |
|
302 | |||
279 | def get_history_next(self): |
|
303 | def get_history_next(self): | |
280 | """ Returns next history string and increment history cursor. |
|
304 | """ Returns next history string and increment history cursor. | |
281 | """ |
|
305 | """ | |
282 | command = self.history.get_history_item(self.history_cursor+1) |
|
306 | command = self.history.get_history_item(self.history_cursor+1) | |
283 |
|
307 | |||
284 | if command is not None: |
|
308 | if command is not None: | |
285 | self.history_cursor += 1 |
|
309 | self.history_cursor += 1 | |
286 | return command |
|
310 | return command | |
287 |
|
311 | |||
288 | ### |
|
312 | ### | |
289 | # Subclasses probably want to override these methods... |
|
313 | # Subclasses probably want to override these methods... | |
290 | ### |
|
314 | ### | |
291 |
|
315 | |||
292 | def update_cell_prompt(self, result): |
|
316 | def update_cell_prompt(self, result): | |
293 | """Subclass may override to update the input prompt for a block. |
|
317 | """Subclass may override to update the input prompt for a block. | |
294 |
Since this method will be called as a |
|
318 | Since this method will be called as a | |
295 | implementations should return result when finished. |
|
319 | twisted.internet.defer.Deferred's callback, implementations should | |
|
320 | return result when finished. | |||
296 |
|
321 | |||
297 |
N |
|
322 | NB: result is a failure if the execute returned a failre. | |
298 | do something like:: |
|
323 | To get the blockID, you should do something like:: | |
299 | if(isinstance(result, twisted.python.failure.Failure)): |
|
324 | if(isinstance(result, twisted.python.failure.Failure)): | |
300 | blockID = result.blockID |
|
325 | blockID = result.blockID | |
301 | else: |
|
326 | else: | |
302 | blockID = result['blockID'] |
|
327 | blockID = result['blockID'] | |
303 |
|
328 | |||
304 |
|
329 | |||
305 | """ |
|
330 | """ | |
306 |
|
331 | |||
307 | return result |
|
332 | return result | |
308 |
|
333 | |||
309 |
|
334 | |||
310 | def render_result(self, result): |
|
335 | def render_result(self, result): | |
311 |
"""Subclasses must override to render result. Since this method will |
|
336 | """Subclasses must override to render result. Since this method will | |
312 |
twisted.internet.defer.Deferred's callback, |
|
337 | be called as a twisted.internet.defer.Deferred's callback, | |
313 | when finished.""" |
|
338 | implementations should return result when finished. | |
|
339 | """ | |||
314 |
|
340 | |||
315 | return result |
|
341 | return result | |
316 |
|
342 | |||
317 |
|
343 | |||
318 | def render_error(self, failure): |
|
344 | def render_error(self, failure): | |
319 |
"""Subclasses must override to render the failure. Since this method |
|
345 | """Subclasses must override to render the failure. Since this method | |
320 |
twisted.internet.defer.Deferred's callback, |
|
346 | will be called as a twisted.internet.defer.Deferred's callback, | |
321 | when finished.""" |
|
347 | implementations should return result when finished.""" | |
322 |
|
348 | |||
323 | return failure |
|
349 | return failure | |
324 |
|
350 | |||
325 |
|
351 | |||
326 |
|
352 |
@@ -1,149 +1,151 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 |
|
2 | |||
3 | """This file contains unittests for the frontendbase module.""" |
|
3 | """This file contains unittests for the frontendbase module.""" | |
4 |
|
4 | |||
5 | __docformat__ = "restructuredtext en" |
|
5 | __docformat__ = "restructuredtext en" | |
6 |
|
6 | |||
7 |
#--------------------------------------------------------------------------- |
|
7 | #--------------------------------------------------------------------------- | |
8 | # Copyright (C) 2008 The IPython Development Team |
|
8 | # Copyright (C) 2008 The IPython Development Team | |
9 | # |
|
9 | # | |
10 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
11 | # the file COPYING, distributed as part of this software. |
|
11 | # the file COPYING, distributed as part of this software. | |
12 |
#--------------------------------------------------------------------------- |
|
12 | #--------------------------------------------------------------------------- | |
13 |
|
13 | |||
14 |
#--------------------------------------------------------------------------- |
|
14 | #--------------------------------------------------------------------------- | |
15 | # Imports |
|
15 | # Imports | |
16 |
#--------------------------------------------------------------------------- |
|
16 | #--------------------------------------------------------------------------- | |
17 |
|
17 | |||
18 | import unittest |
|
18 | import unittest | |
19 | from IPython.frontend import frontendbase |
|
19 | from IPython.frontend import frontendbase | |
20 | from IPython.kernel.engineservice import EngineService |
|
20 | from IPython.kernel.engineservice import EngineService | |
21 |
|
21 | |||
22 | class FrontEndCallbackChecker(frontendbase.FrontEndBase): |
|
22 | class FrontEndCallbackChecker(frontendbase.FrontEndBase): | |
23 | """FrontEndBase subclass for checking callbacks""" |
|
23 | """FrontEndBase subclass for checking callbacks""" | |
24 | def __init__(self, engine=None, history=None): |
|
24 | def __init__(self, engine=None, history=None): | |
25 |
super(FrontEndCallbackChecker, self).__init__(engine=engine, |
|
25 | super(FrontEndCallbackChecker, self).__init__(engine=engine, | |
|
26 | history=history) | |||
26 | self.updateCalled = False |
|
27 | self.updateCalled = False | |
27 | self.renderResultCalled = False |
|
28 | self.renderResultCalled = False | |
28 | self.renderErrorCalled = False |
|
29 | self.renderErrorCalled = False | |
29 |
|
30 | |||
30 | def update_cell_prompt(self, result): |
|
31 | def update_cell_prompt(self, result): | |
31 | self.updateCalled = True |
|
32 | self.updateCalled = True | |
32 | return result |
|
33 | return result | |
33 |
|
34 | |||
34 | def render_result(self, result): |
|
35 | def render_result(self, result): | |
35 | self.renderResultCalled = True |
|
36 | self.renderResultCalled = True | |
36 | return result |
|
37 | return result | |
37 |
|
38 | |||
38 |
|
39 | |||
39 | def render_error(self, failure): |
|
40 | def render_error(self, failure): | |
40 | self.renderErrorCalled = True |
|
41 | self.renderErrorCalled = True | |
41 | return failure |
|
42 | return failure | |
42 |
|
43 | |||
43 |
|
44 | |||
44 |
|
45 | |||
45 |
|
46 | |||
46 | class TestFrontendBase(unittest.TestCase): |
|
47 | class TestFrontendBase(unittest.TestCase): | |
47 | def setUp(self): |
|
48 | def setUp(self): | |
48 | """Setup the EngineService and FrontEndBase""" |
|
49 | """Setup the EngineService and FrontEndBase""" | |
49 |
|
50 | |||
50 | self.fb = FrontEndCallbackChecker(engine=EngineService()) |
|
51 | self.fb = FrontEndCallbackChecker(engine=EngineService()) | |
51 |
|
52 | |||
52 |
|
53 | |||
53 | def test_implements_IFrontEnd(self): |
|
54 | def test_implements_IFrontEnd(self): | |
54 |
assert(frontendbase.IFrontEnd.implementedBy( |
|
55 | assert(frontendbase.IFrontEnd.implementedBy( | |
|
56 | frontendbase.FrontEndBase)) | |||
55 |
|
57 | |||
56 |
|
58 | |||
57 | def test_is_complete_returns_False_for_incomplete_block(self): |
|
59 | def test_is_complete_returns_False_for_incomplete_block(self): | |
58 | """""" |
|
60 | """""" | |
59 |
|
61 | |||
60 | block = """def test(a):""" |
|
62 | block = """def test(a):""" | |
61 |
|
63 | |||
62 | assert(self.fb.is_complete(block) == False) |
|
64 | assert(self.fb.is_complete(block) == False) | |
63 |
|
65 | |||
64 | def test_is_complete_returns_True_for_complete_block(self): |
|
66 | def test_is_complete_returns_True_for_complete_block(self): | |
65 | """""" |
|
67 | """""" | |
66 |
|
68 | |||
67 | block = """def test(a): pass""" |
|
69 | block = """def test(a): pass""" | |
68 |
|
70 | |||
69 | assert(self.fb.is_complete(block)) |
|
71 | assert(self.fb.is_complete(block)) | |
70 |
|
72 | |||
71 | block = """a=3""" |
|
73 | block = """a=3""" | |
72 |
|
74 | |||
73 | assert(self.fb.is_complete(block)) |
|
75 | assert(self.fb.is_complete(block)) | |
74 |
|
76 | |||
75 |
|
77 | |||
76 | def test_blockID_added_to_result(self): |
|
78 | def test_blockID_added_to_result(self): | |
77 | block = """3+3""" |
|
79 | block = """3+3""" | |
78 |
|
80 | |||
79 | d = self.fb.execute(block, blockID='TEST_ID') |
|
81 | d = self.fb.execute(block, blockID='TEST_ID') | |
80 |
|
82 | |||
81 | d.addCallback(self.checkBlockID, expected='TEST_ID') |
|
83 | d.addCallback(self.checkBlockID, expected='TEST_ID') | |
82 |
|
84 | |||
83 | def test_blockID_added_to_failure(self): |
|
85 | def test_blockID_added_to_failure(self): | |
84 | block = "raise Exception()" |
|
86 | block = "raise Exception()" | |
85 |
|
87 | |||
86 | d = self.fb.execute(block,blockID='TEST_ID') |
|
88 | d = self.fb.execute(block,blockID='TEST_ID') | |
87 | d.addErrback(self.checkFailureID, expected='TEST_ID') |
|
89 | d.addErrback(self.checkFailureID, expected='TEST_ID') | |
88 |
|
90 | |||
89 | def checkBlockID(self, result, expected=""): |
|
91 | def checkBlockID(self, result, expected=""): | |
90 | assert(result['blockID'] == expected) |
|
92 | assert(result['blockID'] == expected) | |
91 |
|
93 | |||
92 |
|
94 | |||
93 | def checkFailureID(self, failure, expected=""): |
|
95 | def checkFailureID(self, failure, expected=""): | |
94 | assert(failure.blockID == expected) |
|
96 | assert(failure.blockID == expected) | |
95 |
|
97 | |||
96 |
|
98 | |||
97 | def test_callbacks_added_to_execute(self): |
|
99 | def test_callbacks_added_to_execute(self): | |
98 | """test that |
|
100 | """test that | |
99 | update_cell_prompt |
|
101 | update_cell_prompt | |
100 | render_result |
|
102 | render_result | |
101 |
|
103 | |||
102 | are added to execute request |
|
104 | are added to execute request | |
103 | """ |
|
105 | """ | |
104 |
|
106 | |||
105 | d = self.fb.execute("10+10") |
|
107 | d = self.fb.execute("10+10") | |
106 | d.addCallback(self.checkCallbacks) |
|
108 | d.addCallback(self.checkCallbacks) | |
107 |
|
109 | |||
108 |
|
110 | |||
109 | def checkCallbacks(self, result): |
|
111 | def checkCallbacks(self, result): | |
110 | assert(self.fb.updateCalled) |
|
112 | assert(self.fb.updateCalled) | |
111 | assert(self.fb.renderResultCalled) |
|
113 | assert(self.fb.renderResultCalled) | |
112 |
|
114 | |||
113 |
|
115 | |||
114 | def test_error_callback_added_to_execute(self): |
|
116 | def test_error_callback_added_to_execute(self): | |
115 | """test that render_error called on execution error""" |
|
117 | """test that render_error called on execution error""" | |
116 |
|
118 | |||
117 | d = self.fb.execute("raise Exception()") |
|
119 | d = self.fb.execute("raise Exception()") | |
118 | d.addCallback(self.checkRenderError) |
|
120 | d.addCallback(self.checkRenderError) | |
119 |
|
121 | |||
120 | def checkRenderError(self, result): |
|
122 | def checkRenderError(self, result): | |
121 | assert(self.fb.renderErrorCalled) |
|
123 | assert(self.fb.renderErrorCalled) | |
122 |
|
124 | |||
123 | def test_history_returns_expected_block(self): |
|
125 | def test_history_returns_expected_block(self): | |
124 | """Make sure history browsing doesn't fail""" |
|
126 | """Make sure history browsing doesn't fail""" | |
125 |
|
127 | |||
126 | blocks = ["a=1","a=2","a=3"] |
|
128 | blocks = ["a=1","a=2","a=3"] | |
127 | for b in blocks: |
|
129 | for b in blocks: | |
128 | d = self.fb.execute(b) |
|
130 | d = self.fb.execute(b) | |
129 |
|
131 | |||
130 | # d is now the deferred for the last executed block |
|
132 | # d is now the deferred for the last executed block | |
131 | d.addCallback(self.historyTests, blocks) |
|
133 | d.addCallback(self.historyTests, blocks) | |
132 |
|
134 | |||
133 |
|
135 | |||
134 | def historyTests(self, result, blocks): |
|
136 | def historyTests(self, result, blocks): | |
135 | """historyTests""" |
|
137 | """historyTests""" | |
136 |
|
138 | |||
137 | assert(len(blocks) >= 3) |
|
139 | assert(len(blocks) >= 3) | |
138 | assert(self.fb.get_history_previous("") == blocks[-2]) |
|
140 | assert(self.fb.get_history_previous("") == blocks[-2]) | |
139 | assert(self.fb.get_history_previous("") == blocks[-3]) |
|
141 | assert(self.fb.get_history_previous("") == blocks[-3]) | |
140 | assert(self.fb.get_history_next() == blocks[-2]) |
|
142 | assert(self.fb.get_history_next() == blocks[-2]) | |
141 |
|
143 | |||
142 |
|
144 | |||
143 | def test_history_returns_none_at_startup(self): |
|
145 | def test_history_returns_none_at_startup(self): | |
144 | """test_history_returns_none_at_startup""" |
|
146 | """test_history_returns_none_at_startup""" | |
145 |
|
147 | |||
146 | assert(self.fb.get_history_previous("")==None) |
|
148 | assert(self.fb.get_history_previous("")==None) | |
147 | assert(self.fb.get_history_next()==None) |
|
149 | assert(self.fb.get_history_next()==None) | |
148 |
|
150 | |||
149 |
|
151 |
@@ -1,2046 +1,2097 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """ |
|
2 | """ | |
3 | General purpose utilities. |
|
3 | General purpose utilities. | |
4 |
|
4 | |||
5 | This is a grab-bag of stuff I find useful in most programs I write. Some of |
|
5 | This is a grab-bag of stuff I find useful in most programs I write. Some of | |
6 | these things are also convenient when working at the command line. |
|
6 | these things are also convenient when working at the command line. | |
7 |
|
7 | |||
8 | $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $""" |
|
8 | $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $""" | |
9 |
|
9 | |||
10 | #***************************************************************************** |
|
10 | #***************************************************************************** | |
11 | # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu> |
|
11 | # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu> | |
12 | # |
|
12 | # | |
13 | # Distributed under the terms of the BSD License. The full license is in |
|
13 | # Distributed under the terms of the BSD License. The full license is in | |
14 | # the file COPYING, distributed as part of this software. |
|
14 | # the file COPYING, distributed as part of this software. | |
15 | #***************************************************************************** |
|
15 | #***************************************************************************** | |
16 |
|
16 | |||
17 | from IPython import Release |
|
17 | from IPython import Release | |
18 | __author__ = '%s <%s>' % Release.authors['Fernando'] |
|
18 | __author__ = '%s <%s>' % Release.authors['Fernando'] | |
19 | __license__ = Release.license |
|
19 | __license__ = Release.license | |
20 |
|
20 | |||
21 | #**************************************************************************** |
|
21 | #**************************************************************************** | |
22 | # required modules from the Python standard library |
|
22 | # required modules from the Python standard library | |
23 | import __main__ |
|
23 | import __main__ | |
24 | import commands |
|
24 | import commands | |
25 | try: |
|
25 | try: | |
26 | import doctest |
|
26 | import doctest | |
27 | except ImportError: |
|
27 | except ImportError: | |
28 | pass |
|
28 | pass | |
29 | import os |
|
29 | import os | |
|
30 | import platform | |||
30 | import re |
|
31 | import re | |
31 | import shlex |
|
32 | import shlex | |
32 | import shutil |
|
33 | import shutil | |
|
34 | import subprocess | |||
33 | import sys |
|
35 | import sys | |
34 | import tempfile |
|
36 | import tempfile | |
35 | import time |
|
37 | import time | |
36 | import types |
|
38 | import types | |
37 | import warnings |
|
39 | import warnings | |
38 |
|
40 | |||
39 | # Curses and termios are Unix-only modules |
|
41 | # Curses and termios are Unix-only modules | |
40 | try: |
|
42 | try: | |
41 | import curses |
|
43 | import curses | |
42 | # We need termios as well, so if its import happens to raise, we bail on |
|
44 | # We need termios as well, so if its import happens to raise, we bail on | |
43 | # using curses altogether. |
|
45 | # using curses altogether. | |
44 | import termios |
|
46 | import termios | |
45 | except ImportError: |
|
47 | except ImportError: | |
46 | USE_CURSES = False |
|
48 | USE_CURSES = False | |
47 | else: |
|
49 | else: | |
48 | # Curses on Solaris may not be complete, so we can't use it there |
|
50 | # Curses on Solaris may not be complete, so we can't use it there | |
49 | USE_CURSES = hasattr(curses,'initscr') |
|
51 | USE_CURSES = hasattr(curses,'initscr') | |
50 |
|
52 | |||
51 | # Other IPython utilities |
|
53 | # Other IPython utilities | |
52 | import IPython |
|
54 | import IPython | |
53 | from IPython.Itpl import Itpl,itpl,printpl |
|
55 | from IPython.Itpl import Itpl,itpl,printpl | |
54 | from IPython import DPyGetOpt, platutils |
|
56 | from IPython import DPyGetOpt, platutils | |
55 | from IPython.generics import result_display |
|
57 | from IPython.generics import result_display | |
56 | import IPython.ipapi |
|
58 | import IPython.ipapi | |
57 | from IPython.external.path import path |
|
59 | from IPython.external.path import path | |
58 | if os.name == "nt": |
|
60 | if os.name == "nt": | |
59 | from IPython.winconsole import get_console_size |
|
61 | from IPython.winconsole import get_console_size | |
60 |
|
62 | |||
61 | try: |
|
63 | try: | |
62 | set |
|
64 | set | |
63 | except: |
|
65 | except: | |
64 | from sets import Set as set |
|
66 | from sets import Set as set | |
65 |
|
67 | |||
66 |
|
68 | |||
67 | #**************************************************************************** |
|
69 | #**************************************************************************** | |
68 | # Exceptions |
|
70 | # Exceptions | |
69 | class Error(Exception): |
|
71 | class Error(Exception): | |
70 | """Base class for exceptions in this module.""" |
|
72 | """Base class for exceptions in this module.""" | |
71 | pass |
|
73 | pass | |
72 |
|
74 | |||
73 | #---------------------------------------------------------------------------- |
|
75 | #---------------------------------------------------------------------------- | |
74 | class IOStream: |
|
76 | class IOStream: | |
75 | def __init__(self,stream,fallback): |
|
77 | def __init__(self,stream,fallback): | |
76 | if not hasattr(stream,'write') or not hasattr(stream,'flush'): |
|
78 | if not hasattr(stream,'write') or not hasattr(stream,'flush'): | |
77 | stream = fallback |
|
79 | stream = fallback | |
78 | self.stream = stream |
|
80 | self.stream = stream | |
79 | self._swrite = stream.write |
|
81 | self._swrite = stream.write | |
80 | self.flush = stream.flush |
|
82 | self.flush = stream.flush | |
81 |
|
83 | |||
82 | def write(self,data): |
|
84 | def write(self,data): | |
83 | try: |
|
85 | try: | |
84 | self._swrite(data) |
|
86 | self._swrite(data) | |
85 | except: |
|
87 | except: | |
86 | try: |
|
88 | try: | |
87 | # print handles some unicode issues which may trip a plain |
|
89 | # print handles some unicode issues which may trip a plain | |
88 | # write() call. Attempt to emulate write() by using a |
|
90 | # write() call. Attempt to emulate write() by using a | |
89 | # trailing comma |
|
91 | # trailing comma | |
90 | print >> self.stream, data, |
|
92 | print >> self.stream, data, | |
91 | except: |
|
93 | except: | |
92 | # if we get here, something is seriously broken. |
|
94 | # if we get here, something is seriously broken. | |
93 | print >> sys.stderr, \ |
|
95 | print >> sys.stderr, \ | |
94 | 'ERROR - failed to write data to stream:', self.stream |
|
96 | 'ERROR - failed to write data to stream:', self.stream | |
95 |
|
97 | |||
96 | def close(self): |
|
98 | def close(self): | |
97 | pass |
|
99 | pass | |
98 |
|
100 | |||
99 |
|
101 | |||
100 | class IOTerm: |
|
102 | class IOTerm: | |
101 | """ Term holds the file or file-like objects for handling I/O operations. |
|
103 | """ Term holds the file or file-like objects for handling I/O operations. | |
102 |
|
104 | |||
103 | These are normally just sys.stdin, sys.stdout and sys.stderr but for |
|
105 | These are normally just sys.stdin, sys.stdout and sys.stderr but for | |
104 | Windows they can can replaced to allow editing the strings before they are |
|
106 | Windows they can can replaced to allow editing the strings before they are | |
105 | displayed.""" |
|
107 | displayed.""" | |
106 |
|
108 | |||
107 | # In the future, having IPython channel all its I/O operations through |
|
109 | # In the future, having IPython channel all its I/O operations through | |
108 | # this class will make it easier to embed it into other environments which |
|
110 | # this class will make it easier to embed it into other environments which | |
109 | # are not a normal terminal (such as a GUI-based shell) |
|
111 | # are not a normal terminal (such as a GUI-based shell) | |
110 | def __init__(self,cin=None,cout=None,cerr=None): |
|
112 | def __init__(self,cin=None,cout=None,cerr=None): | |
111 | self.cin = IOStream(cin,sys.stdin) |
|
113 | self.cin = IOStream(cin,sys.stdin) | |
112 | self.cout = IOStream(cout,sys.stdout) |
|
114 | self.cout = IOStream(cout,sys.stdout) | |
113 | self.cerr = IOStream(cerr,sys.stderr) |
|
115 | self.cerr = IOStream(cerr,sys.stderr) | |
114 |
|
116 | |||
115 | # Global variable to be used for all I/O |
|
117 | # Global variable to be used for all I/O | |
116 | Term = IOTerm() |
|
118 | Term = IOTerm() | |
117 |
|
119 | |||
118 | import IPython.rlineimpl as readline |
|
120 | import IPython.rlineimpl as readline | |
119 | # Remake Term to use the readline i/o facilities |
|
121 | # Remake Term to use the readline i/o facilities | |
120 | if sys.platform == 'win32' and readline.have_readline: |
|
122 | if sys.platform == 'win32' and readline.have_readline: | |
121 |
|
123 | |||
122 | Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile) |
|
124 | Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile) | |
123 |
|
125 | |||
124 |
|
126 | |||
125 | #**************************************************************************** |
|
127 | #**************************************************************************** | |
126 | # Generic warning/error printer, used by everything else |
|
128 | # Generic warning/error printer, used by everything else | |
127 | def warn(msg,level=2,exit_val=1): |
|
129 | def warn(msg,level=2,exit_val=1): | |
128 | """Standard warning printer. Gives formatting consistency. |
|
130 | """Standard warning printer. Gives formatting consistency. | |
129 |
|
131 | |||
130 | Output is sent to Term.cerr (sys.stderr by default). |
|
132 | Output is sent to Term.cerr (sys.stderr by default). | |
131 |
|
133 | |||
132 | Options: |
|
134 | Options: | |
133 |
|
135 | |||
134 | -level(2): allows finer control: |
|
136 | -level(2): allows finer control: | |
135 | 0 -> Do nothing, dummy function. |
|
137 | 0 -> Do nothing, dummy function. | |
136 | 1 -> Print message. |
|
138 | 1 -> Print message. | |
137 | 2 -> Print 'WARNING:' + message. (Default level). |
|
139 | 2 -> Print 'WARNING:' + message. (Default level). | |
138 | 3 -> Print 'ERROR:' + message. |
|
140 | 3 -> Print 'ERROR:' + message. | |
139 | 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val). |
|
141 | 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val). | |
140 |
|
142 | |||
141 | -exit_val (1): exit value returned by sys.exit() for a level 4 |
|
143 | -exit_val (1): exit value returned by sys.exit() for a level 4 | |
142 | warning. Ignored for all other levels.""" |
|
144 | warning. Ignored for all other levels.""" | |
143 |
|
145 | |||
144 | if level>0: |
|
146 | if level>0: | |
145 | header = ['','','WARNING: ','ERROR: ','FATAL ERROR: '] |
|
147 | header = ['','','WARNING: ','ERROR: ','FATAL ERROR: '] | |
146 | print >> Term.cerr, '%s%s' % (header[level],msg) |
|
148 | print >> Term.cerr, '%s%s' % (header[level],msg) | |
147 | if level == 4: |
|
149 | if level == 4: | |
148 | print >> Term.cerr,'Exiting.\n' |
|
150 | print >> Term.cerr,'Exiting.\n' | |
149 | sys.exit(exit_val) |
|
151 | sys.exit(exit_val) | |
150 |
|
152 | |||
151 | def info(msg): |
|
153 | def info(msg): | |
152 | """Equivalent to warn(msg,level=1).""" |
|
154 | """Equivalent to warn(msg,level=1).""" | |
153 |
|
155 | |||
154 | warn(msg,level=1) |
|
156 | warn(msg,level=1) | |
155 |
|
157 | |||
156 | def error(msg): |
|
158 | def error(msg): | |
157 | """Equivalent to warn(msg,level=3).""" |
|
159 | """Equivalent to warn(msg,level=3).""" | |
158 |
|
160 | |||
159 | warn(msg,level=3) |
|
161 | warn(msg,level=3) | |
160 |
|
162 | |||
161 | def fatal(msg,exit_val=1): |
|
163 | def fatal(msg,exit_val=1): | |
162 | """Equivalent to warn(msg,exit_val=exit_val,level=4).""" |
|
164 | """Equivalent to warn(msg,exit_val=exit_val,level=4).""" | |
163 |
|
165 | |||
164 | warn(msg,exit_val=exit_val,level=4) |
|
166 | warn(msg,exit_val=exit_val,level=4) | |
165 |
|
167 | |||
166 | #--------------------------------------------------------------------------- |
|
168 | #--------------------------------------------------------------------------- | |
167 | # Debugging routines |
|
169 | # Debugging routines | |
168 | # |
|
170 | # | |
169 | def debugx(expr,pre_msg=''): |
|
171 | def debugx(expr,pre_msg=''): | |
170 | """Print the value of an expression from the caller's frame. |
|
172 | """Print the value of an expression from the caller's frame. | |
171 |
|
173 | |||
172 | Takes an expression, evaluates it in the caller's frame and prints both |
|
174 | Takes an expression, evaluates it in the caller's frame and prints both | |
173 | the given expression and the resulting value (as well as a debug mark |
|
175 | the given expression and the resulting value (as well as a debug mark | |
174 | indicating the name of the calling function. The input must be of a form |
|
176 | indicating the name of the calling function. The input must be of a form | |
175 | suitable for eval(). |
|
177 | suitable for eval(). | |
176 |
|
178 | |||
177 | An optional message can be passed, which will be prepended to the printed |
|
179 | An optional message can be passed, which will be prepended to the printed | |
178 | expr->value pair.""" |
|
180 | expr->value pair.""" | |
179 |
|
181 | |||
180 | cf = sys._getframe(1) |
|
182 | cf = sys._getframe(1) | |
181 | print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr, |
|
183 | print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr, | |
182 | eval(expr,cf.f_globals,cf.f_locals)) |
|
184 | eval(expr,cf.f_globals,cf.f_locals)) | |
183 |
|
185 | |||
184 | # deactivate it by uncommenting the following line, which makes it a no-op |
|
186 | # deactivate it by uncommenting the following line, which makes it a no-op | |
185 | #def debugx(expr,pre_msg=''): pass |
|
187 | #def debugx(expr,pre_msg=''): pass | |
186 |
|
188 | |||
187 | #---------------------------------------------------------------------------- |
|
189 | #---------------------------------------------------------------------------- | |
188 | StringTypes = types.StringTypes |
|
190 | StringTypes = types.StringTypes | |
189 |
|
191 | |||
190 | # Basic timing functionality |
|
192 | # Basic timing functionality | |
191 |
|
193 | |||
192 | # If possible (Unix), use the resource module instead of time.clock() |
|
194 | # If possible (Unix), use the resource module instead of time.clock() | |
193 | try: |
|
195 | try: | |
194 | import resource |
|
196 | import resource | |
195 | def clocku(): |
|
197 | def clocku(): | |
196 | """clocku() -> floating point number |
|
198 | """clocku() -> floating point number | |
197 |
|
199 | |||
198 | Return the *USER* CPU time in seconds since the start of the process. |
|
200 | Return the *USER* CPU time in seconds since the start of the process. | |
199 | This is done via a call to resource.getrusage, so it avoids the |
|
201 | This is done via a call to resource.getrusage, so it avoids the | |
200 | wraparound problems in time.clock().""" |
|
202 | wraparound problems in time.clock().""" | |
201 |
|
203 | |||
202 | return resource.getrusage(resource.RUSAGE_SELF)[0] |
|
204 | return resource.getrusage(resource.RUSAGE_SELF)[0] | |
203 |
|
205 | |||
204 | def clocks(): |
|
206 | def clocks(): | |
205 | """clocks() -> floating point number |
|
207 | """clocks() -> floating point number | |
206 |
|
208 | |||
207 | Return the *SYSTEM* CPU time in seconds since the start of the process. |
|
209 | Return the *SYSTEM* CPU time in seconds since the start of the process. | |
208 | This is done via a call to resource.getrusage, so it avoids the |
|
210 | This is done via a call to resource.getrusage, so it avoids the | |
209 | wraparound problems in time.clock().""" |
|
211 | wraparound problems in time.clock().""" | |
210 |
|
212 | |||
211 | return resource.getrusage(resource.RUSAGE_SELF)[1] |
|
213 | return resource.getrusage(resource.RUSAGE_SELF)[1] | |
212 |
|
214 | |||
213 | def clock(): |
|
215 | def clock(): | |
214 | """clock() -> floating point number |
|
216 | """clock() -> floating point number | |
215 |
|
217 | |||
216 | Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of |
|
218 | Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of | |
217 | the process. This is done via a call to resource.getrusage, so it |
|
219 | the process. This is done via a call to resource.getrusage, so it | |
218 | avoids the wraparound problems in time.clock().""" |
|
220 | avoids the wraparound problems in time.clock().""" | |
219 |
|
221 | |||
220 | u,s = resource.getrusage(resource.RUSAGE_SELF)[:2] |
|
222 | u,s = resource.getrusage(resource.RUSAGE_SELF)[:2] | |
221 | return u+s |
|
223 | return u+s | |
222 |
|
224 | |||
223 | def clock2(): |
|
225 | def clock2(): | |
224 | """clock2() -> (t_user,t_system) |
|
226 | """clock2() -> (t_user,t_system) | |
225 |
|
227 | |||
226 | Similar to clock(), but return a tuple of user/system times.""" |
|
228 | Similar to clock(), but return a tuple of user/system times.""" | |
227 | return resource.getrusage(resource.RUSAGE_SELF)[:2] |
|
229 | return resource.getrusage(resource.RUSAGE_SELF)[:2] | |
228 |
|
230 | |||
229 | except ImportError: |
|
231 | except ImportError: | |
230 | # There is no distinction of user/system time under windows, so we just use |
|
232 | # There is no distinction of user/system time under windows, so we just use | |
231 | # time.clock() for everything... |
|
233 | # time.clock() for everything... | |
232 | clocku = clocks = clock = time.clock |
|
234 | clocku = clocks = clock = time.clock | |
233 | def clock2(): |
|
235 | def clock2(): | |
234 | """Under windows, system CPU time can't be measured. |
|
236 | """Under windows, system CPU time can't be measured. | |
235 |
|
237 | |||
236 | This just returns clock() and zero.""" |
|
238 | This just returns clock() and zero.""" | |
237 | return time.clock(),0.0 |
|
239 | return time.clock(),0.0 | |
238 |
|
240 | |||
239 | def timings_out(reps,func,*args,**kw): |
|
241 | def timings_out(reps,func,*args,**kw): | |
240 | """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output) |
|
242 | """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output) | |
241 |
|
243 | |||
242 | Execute a function reps times, return a tuple with the elapsed total |
|
244 | Execute a function reps times, return a tuple with the elapsed total | |
243 | CPU time in seconds, the time per call and the function's output. |
|
245 | CPU time in seconds, the time per call and the function's output. | |
244 |
|
246 | |||
245 | Under Unix, the return value is the sum of user+system time consumed by |
|
247 | Under Unix, the return value is the sum of user+system time consumed by | |
246 | the process, computed via the resource module. This prevents problems |
|
248 | the process, computed via the resource module. This prevents problems | |
247 | related to the wraparound effect which the time.clock() function has. |
|
249 | related to the wraparound effect which the time.clock() function has. | |
248 |
|
250 | |||
249 | Under Windows the return value is in wall clock seconds. See the |
|
251 | Under Windows the return value is in wall clock seconds. See the | |
250 | documentation for the time module for more details.""" |
|
252 | documentation for the time module for more details.""" | |
251 |
|
253 | |||
252 | reps = int(reps) |
|
254 | reps = int(reps) | |
253 | assert reps >=1, 'reps must be >= 1' |
|
255 | assert reps >=1, 'reps must be >= 1' | |
254 | if reps==1: |
|
256 | if reps==1: | |
255 | start = clock() |
|
257 | start = clock() | |
256 | out = func(*args,**kw) |
|
258 | out = func(*args,**kw) | |
257 | tot_time = clock()-start |
|
259 | tot_time = clock()-start | |
258 | else: |
|
260 | else: | |
259 | rng = xrange(reps-1) # the last time is executed separately to store output |
|
261 | rng = xrange(reps-1) # the last time is executed separately to store output | |
260 | start = clock() |
|
262 | start = clock() | |
261 | for dummy in rng: func(*args,**kw) |
|
263 | for dummy in rng: func(*args,**kw) | |
262 | out = func(*args,**kw) # one last time |
|
264 | out = func(*args,**kw) # one last time | |
263 | tot_time = clock()-start |
|
265 | tot_time = clock()-start | |
264 | av_time = tot_time / reps |
|
266 | av_time = tot_time / reps | |
265 | return tot_time,av_time,out |
|
267 | return tot_time,av_time,out | |
266 |
|
268 | |||
267 | def timings(reps,func,*args,**kw): |
|
269 | def timings(reps,func,*args,**kw): | |
268 | """timings(reps,func,*args,**kw) -> (t_total,t_per_call) |
|
270 | """timings(reps,func,*args,**kw) -> (t_total,t_per_call) | |
269 |
|
271 | |||
270 | Execute a function reps times, return a tuple with the elapsed total CPU |
|
272 | Execute a function reps times, return a tuple with the elapsed total CPU | |
271 | time in seconds and the time per call. These are just the first two values |
|
273 | time in seconds and the time per call. These are just the first two values | |
272 | in timings_out().""" |
|
274 | in timings_out().""" | |
273 |
|
275 | |||
274 | return timings_out(reps,func,*args,**kw)[0:2] |
|
276 | return timings_out(reps,func,*args,**kw)[0:2] | |
275 |
|
277 | |||
276 | def timing(func,*args,**kw): |
|
278 | def timing(func,*args,**kw): | |
277 | """timing(func,*args,**kw) -> t_total |
|
279 | """timing(func,*args,**kw) -> t_total | |
278 |
|
280 | |||
279 | Execute a function once, return the elapsed total CPU time in |
|
281 | Execute a function once, return the elapsed total CPU time in | |
280 | seconds. This is just the first value in timings_out().""" |
|
282 | seconds. This is just the first value in timings_out().""" | |
281 |
|
283 | |||
282 | return timings_out(1,func,*args,**kw)[0] |
|
284 | return timings_out(1,func,*args,**kw)[0] | |
283 |
|
285 | |||
284 | #**************************************************************************** |
|
286 | #**************************************************************************** | |
285 | # file and system |
|
287 | # file and system | |
286 |
|
288 | |||
287 | def arg_split(s,posix=False): |
|
289 | def arg_split(s,posix=False): | |
288 | """Split a command line's arguments in a shell-like manner. |
|
290 | """Split a command line's arguments in a shell-like manner. | |
289 |
|
291 | |||
290 | This is a modified version of the standard library's shlex.split() |
|
292 | This is a modified version of the standard library's shlex.split() | |
291 | function, but with a default of posix=False for splitting, so that quotes |
|
293 | function, but with a default of posix=False for splitting, so that quotes | |
292 | in inputs are respected.""" |
|
294 | in inputs are respected.""" | |
293 |
|
295 | |||
294 | # XXX - there may be unicode-related problems here!!! I'm not sure that |
|
296 | # XXX - there may be unicode-related problems here!!! I'm not sure that | |
295 | # shlex is truly unicode-safe, so it might be necessary to do |
|
297 | # shlex is truly unicode-safe, so it might be necessary to do | |
296 | # |
|
298 | # | |
297 | # s = s.encode(sys.stdin.encoding) |
|
299 | # s = s.encode(sys.stdin.encoding) | |
298 | # |
|
300 | # | |
299 | # first, to ensure that shlex gets a normal string. Input from anyone who |
|
301 | # first, to ensure that shlex gets a normal string. Input from anyone who | |
300 | # knows more about unicode and shlex than I would be good to have here... |
|
302 | # knows more about unicode and shlex than I would be good to have here... | |
301 | lex = shlex.shlex(s, posix=posix) |
|
303 | lex = shlex.shlex(s, posix=posix) | |
302 | lex.whitespace_split = True |
|
304 | lex.whitespace_split = True | |
303 | return list(lex) |
|
305 | return list(lex) | |
304 |
|
306 | |||
305 | def system(cmd,verbose=0,debug=0,header=''): |
|
307 | def system(cmd,verbose=0,debug=0,header=''): | |
306 | """Execute a system command, return its exit status. |
|
308 | """Execute a system command, return its exit status. | |
307 |
|
309 | |||
308 | Options: |
|
310 | Options: | |
309 |
|
311 | |||
310 | - verbose (0): print the command to be executed. |
|
312 | - verbose (0): print the command to be executed. | |
311 |
|
313 | |||
312 | - debug (0): only print, do not actually execute. |
|
314 | - debug (0): only print, do not actually execute. | |
313 |
|
315 | |||
314 | - header (''): Header to print on screen prior to the executed command (it |
|
316 | - header (''): Header to print on screen prior to the executed command (it | |
315 | is only prepended to the command, no newlines are added). |
|
317 | is only prepended to the command, no newlines are added). | |
316 |
|
318 | |||
317 | Note: a stateful version of this function is available through the |
|
319 | Note: a stateful version of this function is available through the | |
318 | SystemExec class.""" |
|
320 | SystemExec class.""" | |
319 |
|
321 | |||
320 | stat = 0 |
|
322 | stat = 0 | |
321 | if verbose or debug: print header+cmd |
|
323 | if verbose or debug: print header+cmd | |
322 | sys.stdout.flush() |
|
324 | sys.stdout.flush() | |
323 | if not debug: stat = os.system(cmd) |
|
325 | if not debug: stat = os.system(cmd) | |
324 | return stat |
|
326 | return stat | |
325 |
|
327 | |||
326 | def abbrev_cwd(): |
|
328 | def abbrev_cwd(): | |
327 | """ Return abbreviated version of cwd, e.g. d:mydir """ |
|
329 | """ Return abbreviated version of cwd, e.g. d:mydir """ | |
328 | cwd = os.getcwd().replace('\\','/') |
|
330 | cwd = os.getcwd().replace('\\','/') | |
329 | drivepart = '' |
|
331 | drivepart = '' | |
330 | tail = cwd |
|
332 | tail = cwd | |
331 | if sys.platform == 'win32': |
|
333 | if sys.platform == 'win32': | |
332 | if len(cwd) < 4: |
|
334 | if len(cwd) < 4: | |
333 | return cwd |
|
335 | return cwd | |
334 | drivepart,tail = os.path.splitdrive(cwd) |
|
336 | drivepart,tail = os.path.splitdrive(cwd) | |
335 |
|
337 | |||
336 |
|
338 | |||
337 | parts = tail.split('/') |
|
339 | parts = tail.split('/') | |
338 | if len(parts) > 2: |
|
340 | if len(parts) > 2: | |
339 | tail = '/'.join(parts[-2:]) |
|
341 | tail = '/'.join(parts[-2:]) | |
340 |
|
342 | |||
341 | return (drivepart + ( |
|
343 | return (drivepart + ( | |
342 | cwd == '/' and '/' or tail)) |
|
344 | cwd == '/' and '/' or tail)) | |
343 |
|
345 | |||
344 |
|
346 | |||
345 | # This function is used by ipython in a lot of places to make system calls. |
|
347 | # This function is used by ipython in a lot of places to make system calls. | |
346 | # We need it to be slightly different under win32, due to the vagaries of |
|
348 | # We need it to be slightly different under win32, due to the vagaries of | |
347 | # 'network shares'. A win32 override is below. |
|
349 | # 'network shares'. A win32 override is below. | |
348 |
|
350 | |||
349 | def shell(cmd,verbose=0,debug=0,header=''): |
|
351 | def shell(cmd,verbose=0,debug=0,header=''): | |
350 | """Execute a command in the system shell, always return None. |
|
352 | """Execute a command in the system shell, always return None. | |
351 |
|
353 | |||
352 | Options: |
|
354 | Options: | |
353 |
|
355 | |||
354 | - verbose (0): print the command to be executed. |
|
356 | - verbose (0): print the command to be executed. | |
355 |
|
357 | |||
356 | - debug (0): only print, do not actually execute. |
|
358 | - debug (0): only print, do not actually execute. | |
357 |
|
359 | |||
358 | - header (''): Header to print on screen prior to the executed command (it |
|
360 | - header (''): Header to print on screen prior to the executed command (it | |
359 | is only prepended to the command, no newlines are added). |
|
361 | is only prepended to the command, no newlines are added). | |
360 |
|
362 | |||
361 | Note: this is similar to genutils.system(), but it returns None so it can |
|
363 | Note: this is similar to genutils.system(), but it returns None so it can | |
362 | be conveniently used in interactive loops without getting the return value |
|
364 | be conveniently used in interactive loops without getting the return value | |
363 | (typically 0) printed many times.""" |
|
365 | (typically 0) printed many times.""" | |
364 |
|
366 | |||
365 | stat = 0 |
|
367 | stat = 0 | |
366 | if verbose or debug: print header+cmd |
|
368 | if verbose or debug: print header+cmd | |
367 | # flush stdout so we don't mangle python's buffering |
|
369 | # flush stdout so we don't mangle python's buffering | |
368 | sys.stdout.flush() |
|
370 | sys.stdout.flush() | |
369 |
|
371 | |||
370 | if not debug: |
|
372 | if not debug: | |
371 | platutils.set_term_title("IPy " + cmd) |
|
373 | platutils.set_term_title("IPy " + cmd) | |
372 | os.system(cmd) |
|
374 | os.system(cmd) | |
373 | platutils.set_term_title("IPy " + abbrev_cwd()) |
|
375 | platutils.set_term_title("IPy " + abbrev_cwd()) | |
374 |
|
376 | |||
375 | # override shell() for win32 to deal with network shares |
|
377 | # override shell() for win32 to deal with network shares | |
376 | if os.name in ('nt','dos'): |
|
378 | if os.name in ('nt','dos'): | |
377 |
|
379 | |||
378 | shell_ori = shell |
|
380 | shell_ori = shell | |
379 |
|
381 | |||
380 | def shell(cmd,verbose=0,debug=0,header=''): |
|
382 | def shell(cmd,verbose=0,debug=0,header=''): | |
381 | if os.getcwd().startswith(r"\\"): |
|
383 | if os.getcwd().startswith(r"\\"): | |
382 | path = os.getcwd() |
|
384 | path = os.getcwd() | |
383 | # change to c drive (cannot be on UNC-share when issuing os.system, |
|
385 | # change to c drive (cannot be on UNC-share when issuing os.system, | |
384 | # as cmd.exe cannot handle UNC addresses) |
|
386 | # as cmd.exe cannot handle UNC addresses) | |
385 | os.chdir("c:") |
|
387 | os.chdir("c:") | |
386 | # issue pushd to the UNC-share and then run the command |
|
388 | # issue pushd to the UNC-share and then run the command | |
387 | try: |
|
389 | try: | |
388 | shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header) |
|
390 | shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header) | |
389 | finally: |
|
391 | finally: | |
390 | os.chdir(path) |
|
392 | os.chdir(path) | |
391 | else: |
|
393 | else: | |
392 | shell_ori(cmd,verbose,debug,header) |
|
394 | shell_ori(cmd,verbose,debug,header) | |
393 |
|
395 | |||
394 | shell.__doc__ = shell_ori.__doc__ |
|
396 | shell.__doc__ = shell_ori.__doc__ | |
395 |
|
397 | |||
396 | def getoutput(cmd,verbose=0,debug=0,header='',split=0): |
|
398 | def getoutput(cmd,verbose=0,debug=0,header='',split=0): | |
397 | """Dummy substitute for perl's backquotes. |
|
399 | """Dummy substitute for perl's backquotes. | |
398 |
|
400 | |||
399 | Executes a command and returns the output. |
|
401 | Executes a command and returns the output. | |
400 |
|
402 | |||
401 | Accepts the same arguments as system(), plus: |
|
403 | Accepts the same arguments as system(), plus: | |
402 |
|
404 | |||
403 | - split(0): if true, the output is returned as a list split on newlines. |
|
405 | - split(0): if true, the output is returned as a list split on newlines. | |
404 |
|
406 | |||
405 | Note: a stateful version of this function is available through the |
|
407 | Note: a stateful version of this function is available through the | |
406 | SystemExec class. |
|
408 | SystemExec class. | |
407 |
|
409 | |||
408 | This is pretty much deprecated and rarely used, |
|
410 | This is pretty much deprecated and rarely used, | |
409 | genutils.getoutputerror may be what you need. |
|
411 | genutils.getoutputerror may be what you need. | |
410 |
|
412 | |||
411 | """ |
|
413 | """ | |
412 |
|
414 | |||
413 | if verbose or debug: print header+cmd |
|
415 | if verbose or debug: print header+cmd | |
414 | if not debug: |
|
416 | if not debug: | |
415 | output = os.popen(cmd).read() |
|
417 | output = os.popen(cmd).read() | |
416 | # stipping last \n is here for backwards compat. |
|
418 | # stipping last \n is here for backwards compat. | |
417 | if output.endswith('\n'): |
|
419 | if output.endswith('\n'): | |
418 | output = output[:-1] |
|
420 | output = output[:-1] | |
419 | if split: |
|
421 | if split: | |
420 | return output.split('\n') |
|
422 | return output.split('\n') | |
421 | else: |
|
423 | else: | |
422 | return output |
|
424 | return output | |
423 |
|
425 | |||
424 | def getoutputerror(cmd,verbose=0,debug=0,header='',split=0): |
|
426 | def getoutputerror(cmd,verbose=0,debug=0,header='',split=0): | |
425 | """Return (standard output,standard error) of executing cmd in a shell. |
|
427 | """Return (standard output,standard error) of executing cmd in a shell. | |
426 |
|
428 | |||
427 | Accepts the same arguments as system(), plus: |
|
429 | Accepts the same arguments as system(), plus: | |
428 |
|
430 | |||
429 | - split(0): if true, each of stdout/err is returned as a list split on |
|
431 | - split(0): if true, each of stdout/err is returned as a list split on | |
430 | newlines. |
|
432 | newlines. | |
431 |
|
433 | |||
432 | Note: a stateful version of this function is available through the |
|
434 | Note: a stateful version of this function is available through the | |
433 | SystemExec class.""" |
|
435 | SystemExec class.""" | |
434 |
|
436 | |||
435 | if verbose or debug: print header+cmd |
|
437 | if verbose or debug: print header+cmd | |
436 | if not cmd: |
|
438 | if not cmd: | |
437 | if split: |
|
439 | if split: | |
438 | return [],[] |
|
440 | return [],[] | |
439 | else: |
|
441 | else: | |
440 | return '','' |
|
442 | return '','' | |
441 | if not debug: |
|
443 | if not debug: | |
442 | pin,pout,perr = os.popen3(cmd) |
|
444 | pin,pout,perr = os.popen3(cmd) | |
443 | tout = pout.read().rstrip() |
|
445 | tout = pout.read().rstrip() | |
444 | terr = perr.read().rstrip() |
|
446 | terr = perr.read().rstrip() | |
445 | pin.close() |
|
447 | pin.close() | |
446 | pout.close() |
|
448 | pout.close() | |
447 | perr.close() |
|
449 | perr.close() | |
448 | if split: |
|
450 | if split: | |
449 | return tout.split('\n'),terr.split('\n') |
|
451 | return tout.split('\n'),terr.split('\n') | |
450 | else: |
|
452 | else: | |
451 | return tout,terr |
|
453 | return tout,terr | |
452 |
|
454 | |||
453 | # for compatibility with older naming conventions |
|
455 | # for compatibility with older naming conventions | |
454 | xsys = system |
|
456 | xsys = system | |
455 | bq = getoutput |
|
457 | bq = getoutput | |
456 |
|
458 | |||
457 | class SystemExec: |
|
459 | class SystemExec: | |
458 | """Access the system and getoutput functions through a stateful interface. |
|
460 | """Access the system and getoutput functions through a stateful interface. | |
459 |
|
461 | |||
460 | Note: here we refer to the system and getoutput functions from this |
|
462 | Note: here we refer to the system and getoutput functions from this | |
461 | library, not the ones from the standard python library. |
|
463 | library, not the ones from the standard python library. | |
462 |
|
464 | |||
463 | This class offers the system and getoutput functions as methods, but the |
|
465 | This class offers the system and getoutput functions as methods, but the | |
464 | verbose, debug and header parameters can be set for the instance (at |
|
466 | verbose, debug and header parameters can be set for the instance (at | |
465 | creation time or later) so that they don't need to be specified on each |
|
467 | creation time or later) so that they don't need to be specified on each | |
466 | call. |
|
468 | call. | |
467 |
|
469 | |||
468 | For efficiency reasons, there's no way to override the parameters on a |
|
470 | For efficiency reasons, there's no way to override the parameters on a | |
469 | per-call basis other than by setting instance attributes. If you need |
|
471 | per-call basis other than by setting instance attributes. If you need | |
470 | local overrides, it's best to directly call system() or getoutput(). |
|
472 | local overrides, it's best to directly call system() or getoutput(). | |
471 |
|
473 | |||
472 | The following names are provided as alternate options: |
|
474 | The following names are provided as alternate options: | |
473 | - xsys: alias to system |
|
475 | - xsys: alias to system | |
474 | - bq: alias to getoutput |
|
476 | - bq: alias to getoutput | |
475 |
|
477 | |||
476 | An instance can then be created as: |
|
478 | An instance can then be created as: | |
477 | >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ') |
|
479 | >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ') | |
478 |
|
480 | |||
479 | And used as: |
|
481 | And used as: | |
480 | >>> sysexec.xsys('pwd') |
|
482 | >>> sysexec.xsys('pwd') | |
481 | >>> dirlist = sysexec.bq('ls -l') |
|
483 | >>> dirlist = sysexec.bq('ls -l') | |
482 | """ |
|
484 | """ | |
483 |
|
485 | |||
484 | def __init__(self,verbose=0,debug=0,header='',split=0): |
|
486 | def __init__(self,verbose=0,debug=0,header='',split=0): | |
485 | """Specify the instance's values for verbose, debug and header.""" |
|
487 | """Specify the instance's values for verbose, debug and header.""" | |
486 | setattr_list(self,'verbose debug header split') |
|
488 | setattr_list(self,'verbose debug header split') | |
487 |
|
489 | |||
488 | def system(self,cmd): |
|
490 | def system(self,cmd): | |
489 | """Stateful interface to system(), with the same keyword parameters.""" |
|
491 | """Stateful interface to system(), with the same keyword parameters.""" | |
490 |
|
492 | |||
491 | system(cmd,self.verbose,self.debug,self.header) |
|
493 | system(cmd,self.verbose,self.debug,self.header) | |
492 |
|
494 | |||
493 | def shell(self,cmd): |
|
495 | def shell(self,cmd): | |
494 | """Stateful interface to shell(), with the same keyword parameters.""" |
|
496 | """Stateful interface to shell(), with the same keyword parameters.""" | |
495 |
|
497 | |||
496 | shell(cmd,self.verbose,self.debug,self.header) |
|
498 | shell(cmd,self.verbose,self.debug,self.header) | |
497 |
|
499 | |||
498 | xsys = system # alias |
|
500 | xsys = system # alias | |
499 |
|
501 | |||
500 | def getoutput(self,cmd): |
|
502 | def getoutput(self,cmd): | |
501 | """Stateful interface to getoutput().""" |
|
503 | """Stateful interface to getoutput().""" | |
502 |
|
504 | |||
503 | return getoutput(cmd,self.verbose,self.debug,self.header,self.split) |
|
505 | return getoutput(cmd,self.verbose,self.debug,self.header,self.split) | |
504 |
|
506 | |||
505 | def getoutputerror(self,cmd): |
|
507 | def getoutputerror(self,cmd): | |
506 | """Stateful interface to getoutputerror().""" |
|
508 | """Stateful interface to getoutputerror().""" | |
507 |
|
509 | |||
508 | return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split) |
|
510 | return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split) | |
509 |
|
511 | |||
510 | bq = getoutput # alias |
|
512 | bq = getoutput # alias | |
511 |
|
513 | |||
512 | #----------------------------------------------------------------------------- |
|
514 | #----------------------------------------------------------------------------- | |
513 | def mutex_opts(dict,ex_op): |
|
515 | def mutex_opts(dict,ex_op): | |
514 | """Check for presence of mutually exclusive keys in a dict. |
|
516 | """Check for presence of mutually exclusive keys in a dict. | |
515 |
|
517 | |||
516 | Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]""" |
|
518 | Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]""" | |
517 | for op1,op2 in ex_op: |
|
519 | for op1,op2 in ex_op: | |
518 | if op1 in dict and op2 in dict: |
|
520 | if op1 in dict and op2 in dict: | |
519 | raise ValueError,'\n*** ERROR in Arguments *** '\ |
|
521 | raise ValueError,'\n*** ERROR in Arguments *** '\ | |
520 | 'Options '+op1+' and '+op2+' are mutually exclusive.' |
|
522 | 'Options '+op1+' and '+op2+' are mutually exclusive.' | |
521 |
|
523 | |||
522 | #----------------------------------------------------------------------------- |
|
524 | #----------------------------------------------------------------------------- | |
523 | def get_py_filename(name): |
|
525 | def get_py_filename(name): | |
524 | """Return a valid python filename in the current directory. |
|
526 | """Return a valid python filename in the current directory. | |
525 |
|
527 | |||
526 | If the given name is not a file, it adds '.py' and searches again. |
|
528 | If the given name is not a file, it adds '.py' and searches again. | |
527 | Raises IOError with an informative message if the file isn't found.""" |
|
529 | Raises IOError with an informative message if the file isn't found.""" | |
528 |
|
530 | |||
529 | name = os.path.expanduser(name) |
|
531 | name = os.path.expanduser(name) | |
530 | if not os.path.isfile(name) and not name.endswith('.py'): |
|
532 | if not os.path.isfile(name) and not name.endswith('.py'): | |
531 | name += '.py' |
|
533 | name += '.py' | |
532 | if os.path.isfile(name): |
|
534 | if os.path.isfile(name): | |
533 | return name |
|
535 | return name | |
534 | else: |
|
536 | else: | |
535 | raise IOError,'File `%s` not found.' % name |
|
537 | raise IOError,'File `%s` not found.' % name | |
536 |
|
538 | |||
537 | #----------------------------------------------------------------------------- |
|
539 | #----------------------------------------------------------------------------- | |
538 | def filefind(fname,alt_dirs = None): |
|
540 | def filefind(fname,alt_dirs = None): | |
539 | """Return the given filename either in the current directory, if it |
|
541 | """Return the given filename either in the current directory, if it | |
540 | exists, or in a specified list of directories. |
|
542 | exists, or in a specified list of directories. | |
541 |
|
543 | |||
542 | ~ expansion is done on all file and directory names. |
|
544 | ~ expansion is done on all file and directory names. | |
543 |
|
545 | |||
544 | Upon an unsuccessful search, raise an IOError exception.""" |
|
546 | Upon an unsuccessful search, raise an IOError exception.""" | |
545 |
|
547 | |||
546 | if alt_dirs is None: |
|
548 | if alt_dirs is None: | |
547 | try: |
|
549 | try: | |
548 | alt_dirs = get_home_dir() |
|
550 | alt_dirs = get_home_dir() | |
549 | except HomeDirError: |
|
551 | except HomeDirError: | |
550 | alt_dirs = os.getcwd() |
|
552 | alt_dirs = os.getcwd() | |
551 | search = [fname] + list_strings(alt_dirs) |
|
553 | search = [fname] + list_strings(alt_dirs) | |
552 | search = map(os.path.expanduser,search) |
|
554 | search = map(os.path.expanduser,search) | |
553 | #print 'search list for',fname,'list:',search # dbg |
|
555 | #print 'search list for',fname,'list:',search # dbg | |
554 | fname = search[0] |
|
556 | fname = search[0] | |
555 | if os.path.isfile(fname): |
|
557 | if os.path.isfile(fname): | |
556 | return fname |
|
558 | return fname | |
557 | for direc in search[1:]: |
|
559 | for direc in search[1:]: | |
558 | testname = os.path.join(direc,fname) |
|
560 | testname = os.path.join(direc,fname) | |
559 | #print 'testname',testname # dbg |
|
561 | #print 'testname',testname # dbg | |
560 | if os.path.isfile(testname): |
|
562 | if os.path.isfile(testname): | |
561 | return testname |
|
563 | return testname | |
562 | raise IOError,'File' + `fname` + \ |
|
564 | raise IOError,'File' + `fname` + \ | |
563 | ' not found in current or supplied directories:' + `alt_dirs` |
|
565 | ' not found in current or supplied directories:' + `alt_dirs` | |
564 |
|
566 | |||
565 | #---------------------------------------------------------------------------- |
|
567 | #---------------------------------------------------------------------------- | |
566 | def file_read(filename): |
|
568 | def file_read(filename): | |
567 | """Read a file and close it. Returns the file source.""" |
|
569 | """Read a file and close it. Returns the file source.""" | |
568 | fobj = open(filename,'r'); |
|
570 | fobj = open(filename,'r'); | |
569 | source = fobj.read(); |
|
571 | source = fobj.read(); | |
570 | fobj.close() |
|
572 | fobj.close() | |
571 | return source |
|
573 | return source | |
572 |
|
574 | |||
573 | def file_readlines(filename): |
|
575 | def file_readlines(filename): | |
574 | """Read a file and close it. Returns the file source using readlines().""" |
|
576 | """Read a file and close it. Returns the file source using readlines().""" | |
575 | fobj = open(filename,'r'); |
|
577 | fobj = open(filename,'r'); | |
576 | lines = fobj.readlines(); |
|
578 | lines = fobj.readlines(); | |
577 | fobj.close() |
|
579 | fobj.close() | |
578 | return lines |
|
580 | return lines | |
579 |
|
581 | |||
580 | #---------------------------------------------------------------------------- |
|
582 | #---------------------------------------------------------------------------- | |
581 | def target_outdated(target,deps): |
|
583 | def target_outdated(target,deps): | |
582 | """Determine whether a target is out of date. |
|
584 | """Determine whether a target is out of date. | |
583 |
|
585 | |||
584 | target_outdated(target,deps) -> 1/0 |
|
586 | target_outdated(target,deps) -> 1/0 | |
585 |
|
587 | |||
586 | deps: list of filenames which MUST exist. |
|
588 | deps: list of filenames which MUST exist. | |
587 | target: single filename which may or may not exist. |
|
589 | target: single filename which may or may not exist. | |
588 |
|
590 | |||
589 | If target doesn't exist or is older than any file listed in deps, return |
|
591 | If target doesn't exist or is older than any file listed in deps, return | |
590 | true, otherwise return false. |
|
592 | true, otherwise return false. | |
591 | """ |
|
593 | """ | |
592 | try: |
|
594 | try: | |
593 | target_time = os.path.getmtime(target) |
|
595 | target_time = os.path.getmtime(target) | |
594 | except os.error: |
|
596 | except os.error: | |
595 | return 1 |
|
597 | return 1 | |
596 | for dep in deps: |
|
598 | for dep in deps: | |
597 | dep_time = os.path.getmtime(dep) |
|
599 | dep_time = os.path.getmtime(dep) | |
598 | if dep_time > target_time: |
|
600 | if dep_time > target_time: | |
599 | #print "For target",target,"Dep failed:",dep # dbg |
|
601 | #print "For target",target,"Dep failed:",dep # dbg | |
600 | #print "times (dep,tar):",dep_time,target_time # dbg |
|
602 | #print "times (dep,tar):",dep_time,target_time # dbg | |
601 | return 1 |
|
603 | return 1 | |
602 | return 0 |
|
604 | return 0 | |
603 |
|
605 | |||
604 | #----------------------------------------------------------------------------- |
|
606 | #----------------------------------------------------------------------------- | |
605 | def target_update(target,deps,cmd): |
|
607 | def target_update(target,deps,cmd): | |
606 | """Update a target with a given command given a list of dependencies. |
|
608 | """Update a target with a given command given a list of dependencies. | |
607 |
|
609 | |||
608 | target_update(target,deps,cmd) -> runs cmd if target is outdated. |
|
610 | target_update(target,deps,cmd) -> runs cmd if target is outdated. | |
609 |
|
611 | |||
610 | This is just a wrapper around target_outdated() which calls the given |
|
612 | This is just a wrapper around target_outdated() which calls the given | |
611 | command if target is outdated.""" |
|
613 | command if target is outdated.""" | |
612 |
|
614 | |||
613 | if target_outdated(target,deps): |
|
615 | if target_outdated(target,deps): | |
614 | xsys(cmd) |
|
616 | xsys(cmd) | |
615 |
|
617 | |||
616 | #---------------------------------------------------------------------------- |
|
618 | #---------------------------------------------------------------------------- | |
617 | def unquote_ends(istr): |
|
619 | def unquote_ends(istr): | |
618 | """Remove a single pair of quotes from the endpoints of a string.""" |
|
620 | """Remove a single pair of quotes from the endpoints of a string.""" | |
619 |
|
621 | |||
620 | if not istr: |
|
622 | if not istr: | |
621 | return istr |
|
623 | return istr | |
622 | if (istr[0]=="'" and istr[-1]=="'") or \ |
|
624 | if (istr[0]=="'" and istr[-1]=="'") or \ | |
623 | (istr[0]=='"' and istr[-1]=='"'): |
|
625 | (istr[0]=='"' and istr[-1]=='"'): | |
624 | return istr[1:-1] |
|
626 | return istr[1:-1] | |
625 | else: |
|
627 | else: | |
626 | return istr |
|
628 | return istr | |
627 |
|
629 | |||
628 | #---------------------------------------------------------------------------- |
|
630 | #---------------------------------------------------------------------------- | |
629 | def process_cmdline(argv,names=[],defaults={},usage=''): |
|
631 | def process_cmdline(argv,names=[],defaults={},usage=''): | |
630 | """ Process command-line options and arguments. |
|
632 | """ Process command-line options and arguments. | |
631 |
|
633 | |||
632 | Arguments: |
|
634 | Arguments: | |
633 |
|
635 | |||
634 | - argv: list of arguments, typically sys.argv. |
|
636 | - argv: list of arguments, typically sys.argv. | |
635 |
|
637 | |||
636 | - names: list of option names. See DPyGetOpt docs for details on options |
|
638 | - names: list of option names. See DPyGetOpt docs for details on options | |
637 | syntax. |
|
639 | syntax. | |
638 |
|
640 | |||
639 | - defaults: dict of default values. |
|
641 | - defaults: dict of default values. | |
640 |
|
642 | |||
641 | - usage: optional usage notice to print if a wrong argument is passed. |
|
643 | - usage: optional usage notice to print if a wrong argument is passed. | |
642 |
|
644 | |||
643 | Return a dict of options and a list of free arguments.""" |
|
645 | Return a dict of options and a list of free arguments.""" | |
644 |
|
646 | |||
645 | getopt = DPyGetOpt.DPyGetOpt() |
|
647 | getopt = DPyGetOpt.DPyGetOpt() | |
646 | getopt.setIgnoreCase(0) |
|
648 | getopt.setIgnoreCase(0) | |
647 | getopt.parseConfiguration(names) |
|
649 | getopt.parseConfiguration(names) | |
648 |
|
650 | |||
649 | try: |
|
651 | try: | |
650 | getopt.processArguments(argv) |
|
652 | getopt.processArguments(argv) | |
651 | except DPyGetOpt.ArgumentError, exc: |
|
653 | except DPyGetOpt.ArgumentError, exc: | |
652 | print usage |
|
654 | print usage | |
653 | warn('"%s"' % exc,level=4) |
|
655 | warn('"%s"' % exc,level=4) | |
654 |
|
656 | |||
655 | defaults.update(getopt.optionValues) |
|
657 | defaults.update(getopt.optionValues) | |
656 | args = getopt.freeValues |
|
658 | args = getopt.freeValues | |
657 |
|
659 | |||
658 | return defaults,args |
|
660 | return defaults,args | |
659 |
|
661 | |||
660 | #---------------------------------------------------------------------------- |
|
662 | #---------------------------------------------------------------------------- | |
661 | def optstr2types(ostr): |
|
663 | def optstr2types(ostr): | |
662 | """Convert a string of option names to a dict of type mappings. |
|
664 | """Convert a string of option names to a dict of type mappings. | |
663 |
|
665 | |||
664 | optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'} |
|
666 | optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'} | |
665 |
|
667 | |||
666 | This is used to get the types of all the options in a string formatted |
|
668 | This is used to get the types of all the options in a string formatted | |
667 | with the conventions of DPyGetOpt. The 'type' None is used for options |
|
669 | with the conventions of DPyGetOpt. The 'type' None is used for options | |
668 | which are strings (they need no further conversion). This function's main |
|
670 | which are strings (they need no further conversion). This function's main | |
669 | use is to get a typemap for use with read_dict(). |
|
671 | use is to get a typemap for use with read_dict(). | |
670 | """ |
|
672 | """ | |
671 |
|
673 | |||
672 | typeconv = {None:'',int:'',float:''} |
|
674 | typeconv = {None:'',int:'',float:''} | |
673 | typemap = {'s':None,'i':int,'f':float} |
|
675 | typemap = {'s':None,'i':int,'f':float} | |
674 | opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)') |
|
676 | opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)') | |
675 |
|
677 | |||
676 | for w in ostr.split(): |
|
678 | for w in ostr.split(): | |
677 | oname,alias,otype = opt_re.match(w).groups() |
|
679 | oname,alias,otype = opt_re.match(w).groups() | |
678 | if otype == '' or alias == '!': # simple switches are integers too |
|
680 | if otype == '' or alias == '!': # simple switches are integers too | |
679 | otype = 'i' |
|
681 | otype = 'i' | |
680 | typeconv[typemap[otype]] += oname + ' ' |
|
682 | typeconv[typemap[otype]] += oname + ' ' | |
681 | return typeconv |
|
683 | return typeconv | |
682 |
|
684 | |||
683 | #---------------------------------------------------------------------------- |
|
685 | #---------------------------------------------------------------------------- | |
684 | def read_dict(filename,type_conv=None,**opt): |
|
686 | def read_dict(filename,type_conv=None,**opt): | |
685 |
|
687 | |||
686 | """Read a dictionary of key=value pairs from an input file, optionally |
|
688 | """Read a dictionary of key=value pairs from an input file, optionally | |
687 | performing conversions on the resulting values. |
|
689 | performing conversions on the resulting values. | |
688 |
|
690 | |||
689 | read_dict(filename,type_conv,**opt) -> dict |
|
691 | read_dict(filename,type_conv,**opt) -> dict | |
690 |
|
692 | |||
691 | Only one value per line is accepted, the format should be |
|
693 | Only one value per line is accepted, the format should be | |
692 | # optional comments are ignored |
|
694 | # optional comments are ignored | |
693 | key value\n |
|
695 | key value\n | |
694 |
|
696 | |||
695 | Args: |
|
697 | Args: | |
696 |
|
698 | |||
697 | - type_conv: A dictionary specifying which keys need to be converted to |
|
699 | - type_conv: A dictionary specifying which keys need to be converted to | |
698 | which types. By default all keys are read as strings. This dictionary |
|
700 | which types. By default all keys are read as strings. This dictionary | |
699 | should have as its keys valid conversion functions for strings |
|
701 | should have as its keys valid conversion functions for strings | |
700 | (int,long,float,complex, or your own). The value for each key |
|
702 | (int,long,float,complex, or your own). The value for each key | |
701 | (converter) should be a whitespace separated string containing the names |
|
703 | (converter) should be a whitespace separated string containing the names | |
702 | of all the entries in the file to be converted using that function. For |
|
704 | of all the entries in the file to be converted using that function. For | |
703 | keys to be left alone, use None as the conversion function (only needed |
|
705 | keys to be left alone, use None as the conversion function (only needed | |
704 | with purge=1, see below). |
|
706 | with purge=1, see below). | |
705 |
|
707 | |||
706 | - opt: dictionary with extra options as below (default in parens) |
|
708 | - opt: dictionary with extra options as below (default in parens) | |
707 |
|
709 | |||
708 | purge(0): if set to 1, all keys *not* listed in type_conv are purged out |
|
710 | purge(0): if set to 1, all keys *not* listed in type_conv are purged out | |
709 | of the dictionary to be returned. If purge is going to be used, the |
|
711 | of the dictionary to be returned. If purge is going to be used, the | |
710 | set of keys to be left as strings also has to be explicitly specified |
|
712 | set of keys to be left as strings also has to be explicitly specified | |
711 | using the (non-existent) conversion function None. |
|
713 | using the (non-existent) conversion function None. | |
712 |
|
714 | |||
713 | fs(None): field separator. This is the key/value separator to be used |
|
715 | fs(None): field separator. This is the key/value separator to be used | |
714 | when parsing the file. The None default means any whitespace [behavior |
|
716 | when parsing the file. The None default means any whitespace [behavior | |
715 | of string.split()]. |
|
717 | of string.split()]. | |
716 |
|
718 | |||
717 | strip(0): if 1, strip string values of leading/trailinig whitespace. |
|
719 | strip(0): if 1, strip string values of leading/trailinig whitespace. | |
718 |
|
720 | |||
719 | warn(1): warning level if requested keys are not found in file. |
|
721 | warn(1): warning level if requested keys are not found in file. | |
720 | - 0: silently ignore. |
|
722 | - 0: silently ignore. | |
721 | - 1: inform but proceed. |
|
723 | - 1: inform but proceed. | |
722 | - 2: raise KeyError exception. |
|
724 | - 2: raise KeyError exception. | |
723 |
|
725 | |||
724 | no_empty(0): if 1, remove keys with whitespace strings as a value. |
|
726 | no_empty(0): if 1, remove keys with whitespace strings as a value. | |
725 |
|
727 | |||
726 | unique([]): list of keys (or space separated string) which can't be |
|
728 | unique([]): list of keys (or space separated string) which can't be | |
727 | repeated. If one such key is found in the file, each new instance |
|
729 | repeated. If one such key is found in the file, each new instance | |
728 | overwrites the previous one. For keys not listed here, the behavior is |
|
730 | overwrites the previous one. For keys not listed here, the behavior is | |
729 | to make a list of all appearances. |
|
731 | to make a list of all appearances. | |
730 |
|
732 | |||
731 | Example: |
|
733 | Example: | |
732 | If the input file test.ini has: |
|
734 | If the input file test.ini has: | |
733 | i 3 |
|
735 | i 3 | |
734 | x 4.5 |
|
736 | x 4.5 | |
735 | y 5.5 |
|
737 | y 5.5 | |
736 | s hi ho |
|
738 | s hi ho | |
737 | Then: |
|
739 | Then: | |
738 |
|
740 | |||
739 | >>> type_conv={int:'i',float:'x',None:'s'} |
|
741 | >>> type_conv={int:'i',float:'x',None:'s'} | |
740 | >>> read_dict('test.ini') |
|
742 | >>> read_dict('test.ini') | |
741 | {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'} |
|
743 | {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'} | |
742 | >>> read_dict('test.ini',type_conv) |
|
744 | >>> read_dict('test.ini',type_conv) | |
743 | {'i': 3, 's': 'hi ho', 'x': 4.5, 'y': '5.5'} |
|
745 | {'i': 3, 's': 'hi ho', 'x': 4.5, 'y': '5.5'} | |
744 | >>> read_dict('test.ini',type_conv,purge=1) |
|
746 | >>> read_dict('test.ini',type_conv,purge=1) | |
745 | {'i': 3, 's': 'hi ho', 'x': 4.5} |
|
747 | {'i': 3, 's': 'hi ho', 'x': 4.5} | |
746 | """ |
|
748 | """ | |
747 |
|
749 | |||
748 | # starting config |
|
750 | # starting config | |
749 | opt.setdefault('purge',0) |
|
751 | opt.setdefault('purge',0) | |
750 | opt.setdefault('fs',None) # field sep defaults to any whitespace |
|
752 | opt.setdefault('fs',None) # field sep defaults to any whitespace | |
751 | opt.setdefault('strip',0) |
|
753 | opt.setdefault('strip',0) | |
752 | opt.setdefault('warn',1) |
|
754 | opt.setdefault('warn',1) | |
753 | opt.setdefault('no_empty',0) |
|
755 | opt.setdefault('no_empty',0) | |
754 | opt.setdefault('unique','') |
|
756 | opt.setdefault('unique','') | |
755 | if type(opt['unique']) in StringTypes: |
|
757 | if type(opt['unique']) in StringTypes: | |
756 | unique_keys = qw(opt['unique']) |
|
758 | unique_keys = qw(opt['unique']) | |
757 | elif type(opt['unique']) in (types.TupleType,types.ListType): |
|
759 | elif type(opt['unique']) in (types.TupleType,types.ListType): | |
758 | unique_keys = opt['unique'] |
|
760 | unique_keys = opt['unique'] | |
759 | else: |
|
761 | else: | |
760 | raise ValueError, 'Unique keys must be given as a string, List or Tuple' |
|
762 | raise ValueError, 'Unique keys must be given as a string, List or Tuple' | |
761 |
|
763 | |||
762 | dict = {} |
|
764 | dict = {} | |
763 | # first read in table of values as strings |
|
765 | # first read in table of values as strings | |
764 | file = open(filename,'r') |
|
766 | file = open(filename,'r') | |
765 | for line in file.readlines(): |
|
767 | for line in file.readlines(): | |
766 | line = line.strip() |
|
768 | line = line.strip() | |
767 | if len(line) and line[0]=='#': continue |
|
769 | if len(line) and line[0]=='#': continue | |
768 | if len(line)>0: |
|
770 | if len(line)>0: | |
769 | lsplit = line.split(opt['fs'],1) |
|
771 | lsplit = line.split(opt['fs'],1) | |
770 | try: |
|
772 | try: | |
771 | key,val = lsplit |
|
773 | key,val = lsplit | |
772 | except ValueError: |
|
774 | except ValueError: | |
773 | key,val = lsplit[0],'' |
|
775 | key,val = lsplit[0],'' | |
774 | key = key.strip() |
|
776 | key = key.strip() | |
775 | if opt['strip']: val = val.strip() |
|
777 | if opt['strip']: val = val.strip() | |
776 | if val == "''" or val == '""': val = '' |
|
778 | if val == "''" or val == '""': val = '' | |
777 | if opt['no_empty'] and (val=='' or val.isspace()): |
|
779 | if opt['no_empty'] and (val=='' or val.isspace()): | |
778 | continue |
|
780 | continue | |
779 | # if a key is found more than once in the file, build a list |
|
781 | # if a key is found more than once in the file, build a list | |
780 | # unless it's in the 'unique' list. In that case, last found in file |
|
782 | # unless it's in the 'unique' list. In that case, last found in file | |
781 | # takes precedence. User beware. |
|
783 | # takes precedence. User beware. | |
782 | try: |
|
784 | try: | |
783 | if dict[key] and key in unique_keys: |
|
785 | if dict[key] and key in unique_keys: | |
784 | dict[key] = val |
|
786 | dict[key] = val | |
785 | elif type(dict[key]) is types.ListType: |
|
787 | elif type(dict[key]) is types.ListType: | |
786 | dict[key].append(val) |
|
788 | dict[key].append(val) | |
787 | else: |
|
789 | else: | |
788 | dict[key] = [dict[key],val] |
|
790 | dict[key] = [dict[key],val] | |
789 | except KeyError: |
|
791 | except KeyError: | |
790 | dict[key] = val |
|
792 | dict[key] = val | |
791 | # purge if requested |
|
793 | # purge if requested | |
792 | if opt['purge']: |
|
794 | if opt['purge']: | |
793 | accepted_keys = qwflat(type_conv.values()) |
|
795 | accepted_keys = qwflat(type_conv.values()) | |
794 | for key in dict.keys(): |
|
796 | for key in dict.keys(): | |
795 | if key in accepted_keys: continue |
|
797 | if key in accepted_keys: continue | |
796 | del(dict[key]) |
|
798 | del(dict[key]) | |
797 | # now convert if requested |
|
799 | # now convert if requested | |
798 | if type_conv==None: return dict |
|
800 | if type_conv==None: return dict | |
799 | conversions = type_conv.keys() |
|
801 | conversions = type_conv.keys() | |
800 | try: conversions.remove(None) |
|
802 | try: conversions.remove(None) | |
801 | except: pass |
|
803 | except: pass | |
802 | for convert in conversions: |
|
804 | for convert in conversions: | |
803 | for val in qw(type_conv[convert]): |
|
805 | for val in qw(type_conv[convert]): | |
804 | try: |
|
806 | try: | |
805 | dict[val] = convert(dict[val]) |
|
807 | dict[val] = convert(dict[val]) | |
806 | except KeyError,e: |
|
808 | except KeyError,e: | |
807 | if opt['warn'] == 0: |
|
809 | if opt['warn'] == 0: | |
808 | pass |
|
810 | pass | |
809 | elif opt['warn'] == 1: |
|
811 | elif opt['warn'] == 1: | |
810 | print >>sys.stderr, 'Warning: key',val,\ |
|
812 | print >>sys.stderr, 'Warning: key',val,\ | |
811 | 'not found in file',filename |
|
813 | 'not found in file',filename | |
812 | elif opt['warn'] == 2: |
|
814 | elif opt['warn'] == 2: | |
813 | raise KeyError,e |
|
815 | raise KeyError,e | |
814 | else: |
|
816 | else: | |
815 | raise ValueError,'Warning level must be 0,1 or 2' |
|
817 | raise ValueError,'Warning level must be 0,1 or 2' | |
816 |
|
818 | |||
817 | return dict |
|
819 | return dict | |
818 |
|
820 | |||
819 | #---------------------------------------------------------------------------- |
|
821 | #---------------------------------------------------------------------------- | |
820 | def flag_calls(func): |
|
822 | def flag_calls(func): | |
821 | """Wrap a function to detect and flag when it gets called. |
|
823 | """Wrap a function to detect and flag when it gets called. | |
822 |
|
824 | |||
823 | This is a decorator which takes a function and wraps it in a function with |
|
825 | This is a decorator which takes a function and wraps it in a function with | |
824 | a 'called' attribute. wrapper.called is initialized to False. |
|
826 | a 'called' attribute. wrapper.called is initialized to False. | |
825 |
|
827 | |||
826 | The wrapper.called attribute is set to False right before each call to the |
|
828 | The wrapper.called attribute is set to False right before each call to the | |
827 | wrapped function, so if the call fails it remains False. After the call |
|
829 | wrapped function, so if the call fails it remains False. After the call | |
828 | completes, wrapper.called is set to True and the output is returned. |
|
830 | completes, wrapper.called is set to True and the output is returned. | |
829 |
|
831 | |||
830 | Testing for truth in wrapper.called allows you to determine if a call to |
|
832 | Testing for truth in wrapper.called allows you to determine if a call to | |
831 | func() was attempted and succeeded.""" |
|
833 | func() was attempted and succeeded.""" | |
832 |
|
834 | |||
833 | def wrapper(*args,**kw): |
|
835 | def wrapper(*args,**kw): | |
834 | wrapper.called = False |
|
836 | wrapper.called = False | |
835 | out = func(*args,**kw) |
|
837 | out = func(*args,**kw) | |
836 | wrapper.called = True |
|
838 | wrapper.called = True | |
837 | return out |
|
839 | return out | |
838 |
|
840 | |||
839 | wrapper.called = False |
|
841 | wrapper.called = False | |
840 | wrapper.__doc__ = func.__doc__ |
|
842 | wrapper.__doc__ = func.__doc__ | |
841 | return wrapper |
|
843 | return wrapper | |
842 |
|
844 | |||
843 | #---------------------------------------------------------------------------- |
|
845 | #---------------------------------------------------------------------------- | |
844 | def dhook_wrap(func,*a,**k): |
|
846 | def dhook_wrap(func,*a,**k): | |
845 | """Wrap a function call in a sys.displayhook controller. |
|
847 | """Wrap a function call in a sys.displayhook controller. | |
846 |
|
848 | |||
847 | Returns a wrapper around func which calls func, with all its arguments and |
|
849 | Returns a wrapper around func which calls func, with all its arguments and | |
848 | keywords unmodified, using the default sys.displayhook. Since IPython |
|
850 | keywords unmodified, using the default sys.displayhook. Since IPython | |
849 | modifies sys.displayhook, it breaks the behavior of certain systems that |
|
851 | modifies sys.displayhook, it breaks the behavior of certain systems that | |
850 | rely on the default behavior, notably doctest. |
|
852 | rely on the default behavior, notably doctest. | |
851 | """ |
|
853 | """ | |
852 |
|
854 | |||
853 | def f(*a,**k): |
|
855 | def f(*a,**k): | |
854 |
|
856 | |||
855 | dhook_s = sys.displayhook |
|
857 | dhook_s = sys.displayhook | |
856 | sys.displayhook = sys.__displayhook__ |
|
858 | sys.displayhook = sys.__displayhook__ | |
857 | try: |
|
859 | try: | |
858 | out = func(*a,**k) |
|
860 | out = func(*a,**k) | |
859 | finally: |
|
861 | finally: | |
860 | sys.displayhook = dhook_s |
|
862 | sys.displayhook = dhook_s | |
861 |
|
863 | |||
862 | return out |
|
864 | return out | |
863 |
|
865 | |||
864 | f.__doc__ = func.__doc__ |
|
866 | f.__doc__ = func.__doc__ | |
865 | return f |
|
867 | return f | |
866 |
|
868 | |||
867 | #---------------------------------------------------------------------------- |
|
869 | #---------------------------------------------------------------------------- | |
868 | def doctest_reload(): |
|
870 | def doctest_reload(): | |
869 | """Properly reload doctest to reuse it interactively. |
|
871 | """Properly reload doctest to reuse it interactively. | |
870 |
|
872 | |||
871 | This routine: |
|
873 | This routine: | |
872 |
|
874 | |||
873 | - reloads doctest |
|
875 | - reloads doctest | |
874 |
|
876 | |||
875 | - resets its global 'master' attribute to None, so that multiple uses of |
|
877 | - resets its global 'master' attribute to None, so that multiple uses of | |
876 | the module interactively don't produce cumulative reports. |
|
878 | the module interactively don't produce cumulative reports. | |
877 |
|
879 | |||
878 | - Monkeypatches its core test runner method to protect it from IPython's |
|
880 | - Monkeypatches its core test runner method to protect it from IPython's | |
879 | modified displayhook. Doctest expects the default displayhook behavior |
|
881 | modified displayhook. Doctest expects the default displayhook behavior | |
880 | deep down, so our modification breaks it completely. For this reason, a |
|
882 | deep down, so our modification breaks it completely. For this reason, a | |
881 | hard monkeypatch seems like a reasonable solution rather than asking |
|
883 | hard monkeypatch seems like a reasonable solution rather than asking | |
882 | users to manually use a different doctest runner when under IPython.""" |
|
884 | users to manually use a different doctest runner when under IPython.""" | |
883 |
|
885 | |||
884 | import doctest |
|
886 | import doctest | |
885 | reload(doctest) |
|
887 | reload(doctest) | |
886 | doctest.master=None |
|
888 | doctest.master=None | |
887 |
|
889 | |||
888 | try: |
|
890 | try: | |
889 | doctest.DocTestRunner |
|
891 | doctest.DocTestRunner | |
890 | except AttributeError: |
|
892 | except AttributeError: | |
891 | # This is only for python 2.3 compatibility, remove once we move to |
|
893 | # This is only for python 2.3 compatibility, remove once we move to | |
892 | # 2.4 only. |
|
894 | # 2.4 only. | |
893 | pass |
|
895 | pass | |
894 | else: |
|
896 | else: | |
895 | doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run) |
|
897 | doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run) | |
896 |
|
898 | |||
897 | #---------------------------------------------------------------------------- |
|
899 | #---------------------------------------------------------------------------- | |
898 | class HomeDirError(Error): |
|
900 | class HomeDirError(Error): | |
899 | pass |
|
901 | pass | |
900 |
|
902 | |||
901 | def get_home_dir(): |
|
903 | def get_home_dir(): | |
902 | """Return the closest possible equivalent to a 'home' directory. |
|
904 | """Return the closest possible equivalent to a 'home' directory. | |
903 |
|
905 | |||
904 | We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH. |
|
906 | We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH. | |
905 |
|
907 | |||
906 | Currently only Posix and NT are implemented, a HomeDirError exception is |
|
908 | Currently only Posix and NT are implemented, a HomeDirError exception is | |
907 | raised for all other OSes. """ |
|
909 | raised for all other OSes. """ | |
908 |
|
910 | |||
909 | isdir = os.path.isdir |
|
911 | isdir = os.path.isdir | |
910 | env = os.environ |
|
912 | env = os.environ | |
911 |
|
913 | |||
912 | # first, check py2exe distribution root directory for _ipython. |
|
914 | # first, check py2exe distribution root directory for _ipython. | |
913 | # This overrides all. Normally does not exist. |
|
915 | # This overrides all. Normally does not exist. | |
914 |
|
916 | |||
915 | if '\\library.zip\\' in IPython.__file__.lower(): |
|
917 | if '\\library.zip\\' in IPython.__file__.lower(): | |
916 | root, rest = IPython.__file__.lower().split('library.zip') |
|
918 | root, rest = IPython.__file__.lower().split('library.zip') | |
917 | if isdir(root + '_ipython'): |
|
919 | if isdir(root + '_ipython'): | |
918 | os.environ["IPYKITROOT"] = root.rstrip('\\') |
|
920 | os.environ["IPYKITROOT"] = root.rstrip('\\') | |
919 | return root |
|
921 | return root | |
920 |
|
922 | |||
921 | try: |
|
923 | try: | |
922 | homedir = env['HOME'] |
|
924 | homedir = env['HOME'] | |
923 | if not isdir(homedir): |
|
925 | if not isdir(homedir): | |
924 | # in case a user stuck some string which does NOT resolve to a |
|
926 | # in case a user stuck some string which does NOT resolve to a | |
925 | # valid path, it's as good as if we hadn't foud it |
|
927 | # valid path, it's as good as if we hadn't foud it | |
926 | raise KeyError |
|
928 | raise KeyError | |
927 | return homedir |
|
929 | return homedir | |
928 | except KeyError: |
|
930 | except KeyError: | |
929 | if os.name == 'posix': |
|
931 | if os.name == 'posix': | |
930 | raise HomeDirError,'undefined $HOME, IPython can not proceed.' |
|
932 | raise HomeDirError,'undefined $HOME, IPython can not proceed.' | |
931 | elif os.name == 'nt': |
|
933 | elif os.name == 'nt': | |
932 | # For some strange reason, win9x returns 'nt' for os.name. |
|
934 | # For some strange reason, win9x returns 'nt' for os.name. | |
933 | try: |
|
935 | try: | |
934 | homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH']) |
|
936 | homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH']) | |
935 | if not isdir(homedir): |
|
937 | if not isdir(homedir): | |
936 | homedir = os.path.join(env['USERPROFILE']) |
|
938 | homedir = os.path.join(env['USERPROFILE']) | |
937 | if not isdir(homedir): |
|
939 | if not isdir(homedir): | |
938 | raise HomeDirError |
|
940 | raise HomeDirError | |
939 | return homedir |
|
941 | return homedir | |
940 | except: |
|
942 | except: | |
941 | try: |
|
943 | try: | |
942 | # Use the registry to get the 'My Documents' folder. |
|
944 | # Use the registry to get the 'My Documents' folder. | |
943 | import _winreg as wreg |
|
945 | import _winreg as wreg | |
944 | key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, |
|
946 | key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, | |
945 | "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") |
|
947 | "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") | |
946 | homedir = wreg.QueryValueEx(key,'Personal')[0] |
|
948 | homedir = wreg.QueryValueEx(key,'Personal')[0] | |
947 | key.Close() |
|
949 | key.Close() | |
948 | if not isdir(homedir): |
|
950 | if not isdir(homedir): | |
949 | e = ('Invalid "Personal" folder registry key ' |
|
951 | e = ('Invalid "Personal" folder registry key ' | |
950 | 'typically "My Documents".\n' |
|
952 | 'typically "My Documents".\n' | |
951 | 'Value: %s\n' |
|
953 | 'Value: %s\n' | |
952 | 'This is not a valid directory on your system.' % |
|
954 | 'This is not a valid directory on your system.' % | |
953 | homedir) |
|
955 | homedir) | |
954 | raise HomeDirError(e) |
|
956 | raise HomeDirError(e) | |
955 | return homedir |
|
957 | return homedir | |
956 | except HomeDirError: |
|
958 | except HomeDirError: | |
957 | raise |
|
959 | raise | |
958 | except: |
|
960 | except: | |
959 | return 'C:\\' |
|
961 | return 'C:\\' | |
960 | elif os.name == 'dos': |
|
962 | elif os.name == 'dos': | |
961 | # Desperate, may do absurd things in classic MacOS. May work under DOS. |
|
963 | # Desperate, may do absurd things in classic MacOS. May work under DOS. | |
962 | return 'C:\\' |
|
964 | return 'C:\\' | |
963 | else: |
|
965 | else: | |
964 | raise HomeDirError,'support for your operating system not implemented.' |
|
966 | raise HomeDirError,'support for your operating system not implemented.' | |
965 |
|
967 | |||
966 | #**************************************************************************** |
|
968 | #**************************************************************************** | |
967 | # strings and text |
|
969 | # strings and text | |
968 |
|
970 | |||
969 | class LSString(str): |
|
971 | class LSString(str): | |
970 | """String derivative with a special access attributes. |
|
972 | """String derivative with a special access attributes. | |
971 |
|
973 | |||
972 | These are normal strings, but with the special attributes: |
|
974 | These are normal strings, but with the special attributes: | |
973 |
|
975 | |||
974 | .l (or .list) : value as list (split on newlines). |
|
976 | .l (or .list) : value as list (split on newlines). | |
975 | .n (or .nlstr): original value (the string itself). |
|
977 | .n (or .nlstr): original value (the string itself). | |
976 | .s (or .spstr): value as whitespace-separated string. |
|
978 | .s (or .spstr): value as whitespace-separated string. | |
977 | .p (or .paths): list of path objects |
|
979 | .p (or .paths): list of path objects | |
978 |
|
980 | |||
979 | Any values which require transformations are computed only once and |
|
981 | Any values which require transformations are computed only once and | |
980 | cached. |
|
982 | cached. | |
981 |
|
983 | |||
982 | Such strings are very useful to efficiently interact with the shell, which |
|
984 | Such strings are very useful to efficiently interact with the shell, which | |
983 | typically only understands whitespace-separated options for commands.""" |
|
985 | typically only understands whitespace-separated options for commands.""" | |
984 |
|
986 | |||
985 | def get_list(self): |
|
987 | def get_list(self): | |
986 | try: |
|
988 | try: | |
987 | return self.__list |
|
989 | return self.__list | |
988 | except AttributeError: |
|
990 | except AttributeError: | |
989 | self.__list = self.split('\n') |
|
991 | self.__list = self.split('\n') | |
990 | return self.__list |
|
992 | return self.__list | |
991 |
|
993 | |||
992 | l = list = property(get_list) |
|
994 | l = list = property(get_list) | |
993 |
|
995 | |||
994 | def get_spstr(self): |
|
996 | def get_spstr(self): | |
995 | try: |
|
997 | try: | |
996 | return self.__spstr |
|
998 | return self.__spstr | |
997 | except AttributeError: |
|
999 | except AttributeError: | |
998 | self.__spstr = self.replace('\n',' ') |
|
1000 | self.__spstr = self.replace('\n',' ') | |
999 | return self.__spstr |
|
1001 | return self.__spstr | |
1000 |
|
1002 | |||
1001 | s = spstr = property(get_spstr) |
|
1003 | s = spstr = property(get_spstr) | |
1002 |
|
1004 | |||
1003 | def get_nlstr(self): |
|
1005 | def get_nlstr(self): | |
1004 | return self |
|
1006 | return self | |
1005 |
|
1007 | |||
1006 | n = nlstr = property(get_nlstr) |
|
1008 | n = nlstr = property(get_nlstr) | |
1007 |
|
1009 | |||
1008 | def get_paths(self): |
|
1010 | def get_paths(self): | |
1009 | try: |
|
1011 | try: | |
1010 | return self.__paths |
|
1012 | return self.__paths | |
1011 | except AttributeError: |
|
1013 | except AttributeError: | |
1012 | self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)] |
|
1014 | self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)] | |
1013 | return self.__paths |
|
1015 | return self.__paths | |
1014 |
|
1016 | |||
1015 | p = paths = property(get_paths) |
|
1017 | p = paths = property(get_paths) | |
1016 |
|
1018 | |||
1017 | def print_lsstring(arg): |
|
1019 | def print_lsstring(arg): | |
1018 | """ Prettier (non-repr-like) and more informative printer for LSString """ |
|
1020 | """ Prettier (non-repr-like) and more informative printer for LSString """ | |
1019 | print "LSString (.p, .n, .l, .s available). Value:" |
|
1021 | print "LSString (.p, .n, .l, .s available). Value:" | |
1020 | print arg |
|
1022 | print arg | |
1021 |
|
1023 | |||
1022 | print_lsstring = result_display.when_type(LSString)(print_lsstring) |
|
1024 | print_lsstring = result_display.when_type(LSString)(print_lsstring) | |
1023 |
|
1025 | |||
1024 | #---------------------------------------------------------------------------- |
|
1026 | #---------------------------------------------------------------------------- | |
1025 | class SList(list): |
|
1027 | class SList(list): | |
1026 | """List derivative with a special access attributes. |
|
1028 | """List derivative with a special access attributes. | |
1027 |
|
1029 | |||
1028 | These are normal lists, but with the special attributes: |
|
1030 | These are normal lists, but with the special attributes: | |
1029 |
|
1031 | |||
1030 | .l (or .list) : value as list (the list itself). |
|
1032 | .l (or .list) : value as list (the list itself). | |
1031 | .n (or .nlstr): value as a string, joined on newlines. |
|
1033 | .n (or .nlstr): value as a string, joined on newlines. | |
1032 | .s (or .spstr): value as a string, joined on spaces. |
|
1034 | .s (or .spstr): value as a string, joined on spaces. | |
1033 | .p (or .paths): list of path objects |
|
1035 | .p (or .paths): list of path objects | |
1034 |
|
1036 | |||
1035 | Any values which require transformations are computed only once and |
|
1037 | Any values which require transformations are computed only once and | |
1036 | cached.""" |
|
1038 | cached.""" | |
1037 |
|
1039 | |||
1038 | def get_list(self): |
|
1040 | def get_list(self): | |
1039 | return self |
|
1041 | return self | |
1040 |
|
1042 | |||
1041 | l = list = property(get_list) |
|
1043 | l = list = property(get_list) | |
1042 |
|
1044 | |||
1043 | def get_spstr(self): |
|
1045 | def get_spstr(self): | |
1044 | try: |
|
1046 | try: | |
1045 | return self.__spstr |
|
1047 | return self.__spstr | |
1046 | except AttributeError: |
|
1048 | except AttributeError: | |
1047 | self.__spstr = ' '.join(self) |
|
1049 | self.__spstr = ' '.join(self) | |
1048 | return self.__spstr |
|
1050 | return self.__spstr | |
1049 |
|
1051 | |||
1050 | s = spstr = property(get_spstr) |
|
1052 | s = spstr = property(get_spstr) | |
1051 |
|
1053 | |||
1052 | def get_nlstr(self): |
|
1054 | def get_nlstr(self): | |
1053 | try: |
|
1055 | try: | |
1054 | return self.__nlstr |
|
1056 | return self.__nlstr | |
1055 | except AttributeError: |
|
1057 | except AttributeError: | |
1056 | self.__nlstr = '\n'.join(self) |
|
1058 | self.__nlstr = '\n'.join(self) | |
1057 | return self.__nlstr |
|
1059 | return self.__nlstr | |
1058 |
|
1060 | |||
1059 | n = nlstr = property(get_nlstr) |
|
1061 | n = nlstr = property(get_nlstr) | |
1060 |
|
1062 | |||
1061 | def get_paths(self): |
|
1063 | def get_paths(self): | |
1062 | try: |
|
1064 | try: | |
1063 | return self.__paths |
|
1065 | return self.__paths | |
1064 | except AttributeError: |
|
1066 | except AttributeError: | |
1065 | self.__paths = [path(p) for p in self if os.path.exists(p)] |
|
1067 | self.__paths = [path(p) for p in self if os.path.exists(p)] | |
1066 | return self.__paths |
|
1068 | return self.__paths | |
1067 |
|
1069 | |||
1068 | p = paths = property(get_paths) |
|
1070 | p = paths = property(get_paths) | |
1069 |
|
1071 | |||
1070 | def grep(self, pattern, prune = False, field = None): |
|
1072 | def grep(self, pattern, prune = False, field = None): | |
1071 | """ Return all strings matching 'pattern' (a regex or callable) |
|
1073 | """ Return all strings matching 'pattern' (a regex or callable) | |
1072 |
|
1074 | |||
1073 | This is case-insensitive. If prune is true, return all items |
|
1075 | This is case-insensitive. If prune is true, return all items | |
1074 | NOT matching the pattern. |
|
1076 | NOT matching the pattern. | |
1075 |
|
1077 | |||
1076 | If field is specified, the match must occur in the specified |
|
1078 | If field is specified, the match must occur in the specified | |
1077 | whitespace-separated field. |
|
1079 | whitespace-separated field. | |
1078 |
|
1080 | |||
1079 | Examples:: |
|
1081 | Examples:: | |
1080 |
|
1082 | |||
1081 | a.grep( lambda x: x.startswith('C') ) |
|
1083 | a.grep( lambda x: x.startswith('C') ) | |
1082 | a.grep('Cha.*log', prune=1) |
|
1084 | a.grep('Cha.*log', prune=1) | |
1083 | a.grep('chm', field=-1) |
|
1085 | a.grep('chm', field=-1) | |
1084 | """ |
|
1086 | """ | |
1085 |
|
1087 | |||
1086 | def match_target(s): |
|
1088 | def match_target(s): | |
1087 | if field is None: |
|
1089 | if field is None: | |
1088 | return s |
|
1090 | return s | |
1089 | parts = s.split() |
|
1091 | parts = s.split() | |
1090 | try: |
|
1092 | try: | |
1091 | tgt = parts[field] |
|
1093 | tgt = parts[field] | |
1092 | return tgt |
|
1094 | return tgt | |
1093 | except IndexError: |
|
1095 | except IndexError: | |
1094 | return "" |
|
1096 | return "" | |
1095 |
|
1097 | |||
1096 | if isinstance(pattern, basestring): |
|
1098 | if isinstance(pattern, basestring): | |
1097 | pred = lambda x : re.search(pattern, x, re.IGNORECASE) |
|
1099 | pred = lambda x : re.search(pattern, x, re.IGNORECASE) | |
1098 | else: |
|
1100 | else: | |
1099 | pred = pattern |
|
1101 | pred = pattern | |
1100 | if not prune: |
|
1102 | if not prune: | |
1101 | return SList([el for el in self if pred(match_target(el))]) |
|
1103 | return SList([el for el in self if pred(match_target(el))]) | |
1102 | else: |
|
1104 | else: | |
1103 | return SList([el for el in self if not pred(match_target(el))]) |
|
1105 | return SList([el for el in self if not pred(match_target(el))]) | |
1104 | def fields(self, *fields): |
|
1106 | def fields(self, *fields): | |
1105 | """ Collect whitespace-separated fields from string list |
|
1107 | """ Collect whitespace-separated fields from string list | |
1106 |
|
1108 | |||
1107 | Allows quick awk-like usage of string lists. |
|
1109 | Allows quick awk-like usage of string lists. | |
1108 |
|
1110 | |||
1109 | Example data (in var a, created by 'a = !ls -l'):: |
|
1111 | Example data (in var a, created by 'a = !ls -l'):: | |
1110 | -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog |
|
1112 | -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog | |
1111 | drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython |
|
1113 | drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython | |
1112 |
|
1114 | |||
1113 | a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+'] |
|
1115 | a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+'] | |
1114 | a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+'] |
|
1116 | a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+'] | |
1115 | (note the joining by space). |
|
1117 | (note the joining by space). | |
1116 | a.fields(-1) is ['ChangeLog', 'IPython'] |
|
1118 | a.fields(-1) is ['ChangeLog', 'IPython'] | |
1117 |
|
1119 | |||
1118 | IndexErrors are ignored. |
|
1120 | IndexErrors are ignored. | |
1119 |
|
1121 | |||
1120 | Without args, fields() just split()'s the strings. |
|
1122 | Without args, fields() just split()'s the strings. | |
1121 | """ |
|
1123 | """ | |
1122 | if len(fields) == 0: |
|
1124 | if len(fields) == 0: | |
1123 | return [el.split() for el in self] |
|
1125 | return [el.split() for el in self] | |
1124 |
|
1126 | |||
1125 | res = SList() |
|
1127 | res = SList() | |
1126 | for el in [f.split() for f in self]: |
|
1128 | for el in [f.split() for f in self]: | |
1127 | lineparts = [] |
|
1129 | lineparts = [] | |
1128 |
|
1130 | |||
1129 | for fd in fields: |
|
1131 | for fd in fields: | |
1130 | try: |
|
1132 | try: | |
1131 | lineparts.append(el[fd]) |
|
1133 | lineparts.append(el[fd]) | |
1132 | except IndexError: |
|
1134 | except IndexError: | |
1133 | pass |
|
1135 | pass | |
1134 | if lineparts: |
|
1136 | if lineparts: | |
1135 | res.append(" ".join(lineparts)) |
|
1137 | res.append(" ".join(lineparts)) | |
1136 |
|
1138 | |||
1137 | return res |
|
1139 | return res | |
1138 |
|
1140 | |||
1139 |
|
1141 | |||
1140 |
|
1142 | |||
1141 |
|
1143 | |||
1142 |
|
1144 | |||
1143 | def print_slist(arg): |
|
1145 | def print_slist(arg): | |
1144 | """ Prettier (non-repr-like) and more informative printer for SList """ |
|
1146 | """ Prettier (non-repr-like) and more informative printer for SList """ | |
1145 | print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:" |
|
1147 | print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:" | |
1146 | nlprint(arg) |
|
1148 | nlprint(arg) | |
1147 |
|
1149 | |||
1148 | print_slist = result_display.when_type(SList)(print_slist) |
|
1150 | print_slist = result_display.when_type(SList)(print_slist) | |
1149 |
|
1151 | |||
1150 |
|
1152 | |||
1151 |
|
1153 | |||
1152 | #---------------------------------------------------------------------------- |
|
1154 | #---------------------------------------------------------------------------- | |
1153 | def esc_quotes(strng): |
|
1155 | def esc_quotes(strng): | |
1154 | """Return the input string with single and double quotes escaped out""" |
|
1156 | """Return the input string with single and double quotes escaped out""" | |
1155 |
|
1157 | |||
1156 | return strng.replace('"','\\"').replace("'","\\'") |
|
1158 | return strng.replace('"','\\"').replace("'","\\'") | |
1157 |
|
1159 | |||
1158 | #---------------------------------------------------------------------------- |
|
1160 | #---------------------------------------------------------------------------- | |
1159 | def make_quoted_expr(s): |
|
1161 | def make_quoted_expr(s): | |
1160 | """Return string s in appropriate quotes, using raw string if possible. |
|
1162 | """Return string s in appropriate quotes, using raw string if possible. | |
1161 |
|
1163 | |||
1162 | Effectively this turns string: cd \ao\ao\ |
|
1164 | Effectively this turns string: cd \ao\ao\ | |
1163 | to: r"cd \ao\ao\_"[:-1] |
|
1165 | to: r"cd \ao\ao\_"[:-1] | |
1164 |
|
1166 | |||
1165 | Note the use of raw string and padding at the end to allow trailing backslash. |
|
1167 | Note the use of raw string and padding at the end to allow trailing backslash. | |
1166 |
|
1168 | |||
1167 | """ |
|
1169 | """ | |
1168 |
|
1170 | |||
1169 | tail = '' |
|
1171 | tail = '' | |
1170 | tailpadding = '' |
|
1172 | tailpadding = '' | |
1171 | raw = '' |
|
1173 | raw = '' | |
1172 | if "\\" in s: |
|
1174 | if "\\" in s: | |
1173 | raw = 'r' |
|
1175 | raw = 'r' | |
1174 | if s.endswith('\\'): |
|
1176 | if s.endswith('\\'): | |
1175 | tail = '[:-1]' |
|
1177 | tail = '[:-1]' | |
1176 | tailpadding = '_' |
|
1178 | tailpadding = '_' | |
1177 | if '"' not in s: |
|
1179 | if '"' not in s: | |
1178 | quote = '"' |
|
1180 | quote = '"' | |
1179 | elif "'" not in s: |
|
1181 | elif "'" not in s: | |
1180 | quote = "'" |
|
1182 | quote = "'" | |
1181 | elif '"""' not in s and not s.endswith('"'): |
|
1183 | elif '"""' not in s and not s.endswith('"'): | |
1182 | quote = '"""' |
|
1184 | quote = '"""' | |
1183 | elif "'''" not in s and not s.endswith("'"): |
|
1185 | elif "'''" not in s and not s.endswith("'"): | |
1184 | quote = "'''" |
|
1186 | quote = "'''" | |
1185 | else: |
|
1187 | else: | |
1186 | # give up, backslash-escaped string will do |
|
1188 | # give up, backslash-escaped string will do | |
1187 | return '"%s"' % esc_quotes(s) |
|
1189 | return '"%s"' % esc_quotes(s) | |
1188 | res = raw + quote + s + tailpadding + quote + tail |
|
1190 | res = raw + quote + s + tailpadding + quote + tail | |
1189 | return res |
|
1191 | return res | |
1190 |
|
1192 | |||
1191 |
|
1193 | |||
1192 | #---------------------------------------------------------------------------- |
|
1194 | #---------------------------------------------------------------------------- | |
1193 | def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'): |
|
1195 | def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'): | |
1194 | """Take multiple lines of input. |
|
1196 | """Take multiple lines of input. | |
1195 |
|
1197 | |||
1196 | A list with each line of input as a separate element is returned when a |
|
1198 | A list with each line of input as a separate element is returned when a | |
1197 | termination string is entered (defaults to a single '.'). Input can also |
|
1199 | termination string is entered (defaults to a single '.'). Input can also | |
1198 | terminate via EOF (^D in Unix, ^Z-RET in Windows). |
|
1200 | terminate via EOF (^D in Unix, ^Z-RET in Windows). | |
1199 |
|
1201 | |||
1200 | Lines of input which end in \\ are joined into single entries (and a |
|
1202 | Lines of input which end in \\ are joined into single entries (and a | |
1201 | secondary continuation prompt is issued as long as the user terminates |
|
1203 | secondary continuation prompt is issued as long as the user terminates | |
1202 | lines with \\). This allows entering very long strings which are still |
|
1204 | lines with \\). This allows entering very long strings which are still | |
1203 | meant to be treated as single entities. |
|
1205 | meant to be treated as single entities. | |
1204 | """ |
|
1206 | """ | |
1205 |
|
1207 | |||
1206 | try: |
|
1208 | try: | |
1207 | if header: |
|
1209 | if header: | |
1208 | header += '\n' |
|
1210 | header += '\n' | |
1209 | lines = [raw_input(header + ps1)] |
|
1211 | lines = [raw_input(header + ps1)] | |
1210 | except EOFError: |
|
1212 | except EOFError: | |
1211 | return [] |
|
1213 | return [] | |
1212 | terminate = [terminate_str] |
|
1214 | terminate = [terminate_str] | |
1213 | try: |
|
1215 | try: | |
1214 | while lines[-1:] != terminate: |
|
1216 | while lines[-1:] != terminate: | |
1215 | new_line = raw_input(ps1) |
|
1217 | new_line = raw_input(ps1) | |
1216 | while new_line.endswith('\\'): |
|
1218 | while new_line.endswith('\\'): | |
1217 | new_line = new_line[:-1] + raw_input(ps2) |
|
1219 | new_line = new_line[:-1] + raw_input(ps2) | |
1218 | lines.append(new_line) |
|
1220 | lines.append(new_line) | |
1219 |
|
1221 | |||
1220 | return lines[:-1] # don't return the termination command |
|
1222 | return lines[:-1] # don't return the termination command | |
1221 | except EOFError: |
|
1223 | except EOFError: | |
1222 |
|
1224 | |||
1223 | return lines |
|
1225 | return lines | |
1224 |
|
1226 | |||
1225 | #---------------------------------------------------------------------------- |
|
1227 | #---------------------------------------------------------------------------- | |
1226 | def raw_input_ext(prompt='', ps2='... '): |
|
1228 | def raw_input_ext(prompt='', ps2='... '): | |
1227 | """Similar to raw_input(), but accepts extended lines if input ends with \\.""" |
|
1229 | """Similar to raw_input(), but accepts extended lines if input ends with \\.""" | |
1228 |
|
1230 | |||
1229 | line = raw_input(prompt) |
|
1231 | line = raw_input(prompt) | |
1230 | while line.endswith('\\'): |
|
1232 | while line.endswith('\\'): | |
1231 | line = line[:-1] + raw_input(ps2) |
|
1233 | line = line[:-1] + raw_input(ps2) | |
1232 | return line |
|
1234 | return line | |
1233 |
|
1235 | |||
1234 | #---------------------------------------------------------------------------- |
|
1236 | #---------------------------------------------------------------------------- | |
1235 | def ask_yes_no(prompt,default=None): |
|
1237 | def ask_yes_no(prompt,default=None): | |
1236 | """Asks a question and returns a boolean (y/n) answer. |
|
1238 | """Asks a question and returns a boolean (y/n) answer. | |
1237 |
|
1239 | |||
1238 | If default is given (one of 'y','n'), it is used if the user input is |
|
1240 | If default is given (one of 'y','n'), it is used if the user input is | |
1239 | empty. Otherwise the question is repeated until an answer is given. |
|
1241 | empty. Otherwise the question is repeated until an answer is given. | |
1240 |
|
1242 | |||
1241 | An EOF is treated as the default answer. If there is no default, an |
|
1243 | An EOF is treated as the default answer. If there is no default, an | |
1242 | exception is raised to prevent infinite loops. |
|
1244 | exception is raised to prevent infinite loops. | |
1243 |
|
1245 | |||
1244 | Valid answers are: y/yes/n/no (match is not case sensitive).""" |
|
1246 | Valid answers are: y/yes/n/no (match is not case sensitive).""" | |
1245 |
|
1247 | |||
1246 | answers = {'y':True,'n':False,'yes':True,'no':False} |
|
1248 | answers = {'y':True,'n':False,'yes':True,'no':False} | |
1247 | ans = None |
|
1249 | ans = None | |
1248 | while ans not in answers.keys(): |
|
1250 | while ans not in answers.keys(): | |
1249 | try: |
|
1251 | try: | |
1250 | ans = raw_input(prompt+' ').lower() |
|
1252 | ans = raw_input(prompt+' ').lower() | |
1251 | if not ans: # response was an empty string |
|
1253 | if not ans: # response was an empty string | |
1252 | ans = default |
|
1254 | ans = default | |
1253 | except KeyboardInterrupt: |
|
1255 | except KeyboardInterrupt: | |
1254 | pass |
|
1256 | pass | |
1255 | except EOFError: |
|
1257 | except EOFError: | |
1256 | if default in answers.keys(): |
|
1258 | if default in answers.keys(): | |
1257 | ans = default |
|
1259 | ans = default | |
1258 |
|
1260 | |||
1259 | else: |
|
1261 | else: | |
1260 | raise |
|
1262 | raise | |
1261 |
|
1263 | |||
1262 | return answers[ans] |
|
1264 | return answers[ans] | |
1263 |
|
1265 | |||
1264 | #---------------------------------------------------------------------------- |
|
1266 | #---------------------------------------------------------------------------- | |
1265 | def marquee(txt='',width=78,mark='*'): |
|
1267 | def marquee(txt='',width=78,mark='*'): | |
1266 | """Return the input string centered in a 'marquee'.""" |
|
1268 | """Return the input string centered in a 'marquee'.""" | |
1267 | if not txt: |
|
1269 | if not txt: | |
1268 | return (mark*width)[:width] |
|
1270 | return (mark*width)[:width] | |
1269 | nmark = (width-len(txt)-2)/len(mark)/2 |
|
1271 | nmark = (width-len(txt)-2)/len(mark)/2 | |
1270 | if nmark < 0: nmark =0 |
|
1272 | if nmark < 0: nmark =0 | |
1271 | marks = mark*nmark |
|
1273 | marks = mark*nmark | |
1272 | return '%s %s %s' % (marks,txt,marks) |
|
1274 | return '%s %s %s' % (marks,txt,marks) | |
1273 |
|
1275 | |||
1274 | #---------------------------------------------------------------------------- |
|
1276 | #---------------------------------------------------------------------------- | |
1275 | class EvalDict: |
|
1277 | class EvalDict: | |
1276 | """ |
|
1278 | """ | |
1277 | Emulate a dict which evaluates its contents in the caller's frame. |
|
1279 | Emulate a dict which evaluates its contents in the caller's frame. | |
1278 |
|
1280 | |||
1279 | Usage: |
|
1281 | Usage: | |
1280 | >>>number = 19 |
|
1282 | >>>number = 19 | |
1281 | >>>text = "python" |
|
1283 | >>>text = "python" | |
1282 | >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict() |
|
1284 | >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict() | |
1283 | """ |
|
1285 | """ | |
1284 |
|
1286 | |||
1285 | # This version is due to sismex01@hebmex.com on c.l.py, and is basically a |
|
1287 | # This version is due to sismex01@hebmex.com on c.l.py, and is basically a | |
1286 | # modified (shorter) version of: |
|
1288 | # modified (shorter) version of: | |
1287 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by |
|
1289 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by | |
1288 | # Skip Montanaro (skip@pobox.com). |
|
1290 | # Skip Montanaro (skip@pobox.com). | |
1289 |
|
1291 | |||
1290 | def __getitem__(self, name): |
|
1292 | def __getitem__(self, name): | |
1291 | frame = sys._getframe(1) |
|
1293 | frame = sys._getframe(1) | |
1292 | return eval(name, frame.f_globals, frame.f_locals) |
|
1294 | return eval(name, frame.f_globals, frame.f_locals) | |
1293 |
|
1295 | |||
1294 | EvalString = EvalDict # for backwards compatibility |
|
1296 | EvalString = EvalDict # for backwards compatibility | |
1295 | #---------------------------------------------------------------------------- |
|
1297 | #---------------------------------------------------------------------------- | |
1296 | def qw(words,flat=0,sep=None,maxsplit=-1): |
|
1298 | def qw(words,flat=0,sep=None,maxsplit=-1): | |
1297 | """Similar to Perl's qw() operator, but with some more options. |
|
1299 | """Similar to Perl's qw() operator, but with some more options. | |
1298 |
|
1300 | |||
1299 | qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit) |
|
1301 | qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit) | |
1300 |
|
1302 | |||
1301 | words can also be a list itself, and with flat=1, the output will be |
|
1303 | words can also be a list itself, and with flat=1, the output will be | |
1302 | recursively flattened. Examples: |
|
1304 | recursively flattened. Examples: | |
1303 |
|
1305 | |||
1304 | >>> qw('1 2') |
|
1306 | >>> qw('1 2') | |
1305 | ['1', '2'] |
|
1307 | ['1', '2'] | |
1306 | >>> qw(['a b','1 2',['m n','p q']]) |
|
1308 | >>> qw(['a b','1 2',['m n','p q']]) | |
1307 | [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]] |
|
1309 | [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]] | |
1308 | >>> qw(['a b','1 2',['m n','p q']],flat=1) |
|
1310 | >>> qw(['a b','1 2',['m n','p q']],flat=1) | |
1309 | ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """ |
|
1311 | ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """ | |
1310 |
|
1312 | |||
1311 | if type(words) in StringTypes: |
|
1313 | if type(words) in StringTypes: | |
1312 | return [word.strip() for word in words.split(sep,maxsplit) |
|
1314 | return [word.strip() for word in words.split(sep,maxsplit) | |
1313 | if word and not word.isspace() ] |
|
1315 | if word and not word.isspace() ] | |
1314 | if flat: |
|
1316 | if flat: | |
1315 | return flatten(map(qw,words,[1]*len(words))) |
|
1317 | return flatten(map(qw,words,[1]*len(words))) | |
1316 | return map(qw,words) |
|
1318 | return map(qw,words) | |
1317 |
|
1319 | |||
1318 | #---------------------------------------------------------------------------- |
|
1320 | #---------------------------------------------------------------------------- | |
1319 | def qwflat(words,sep=None,maxsplit=-1): |
|
1321 | def qwflat(words,sep=None,maxsplit=-1): | |
1320 | """Calls qw(words) in flat mode. It's just a convenient shorthand.""" |
|
1322 | """Calls qw(words) in flat mode. It's just a convenient shorthand.""" | |
1321 | return qw(words,1,sep,maxsplit) |
|
1323 | return qw(words,1,sep,maxsplit) | |
1322 |
|
1324 | |||
1323 | #---------------------------------------------------------------------------- |
|
1325 | #---------------------------------------------------------------------------- | |
1324 | def qw_lol(indata): |
|
1326 | def qw_lol(indata): | |
1325 | """qw_lol('a b') -> [['a','b']], |
|
1327 | """qw_lol('a b') -> [['a','b']], | |
1326 | otherwise it's just a call to qw(). |
|
1328 | otherwise it's just a call to qw(). | |
1327 |
|
1329 | |||
1328 | We need this to make sure the modules_some keys *always* end up as a |
|
1330 | We need this to make sure the modules_some keys *always* end up as a | |
1329 | list of lists.""" |
|
1331 | list of lists.""" | |
1330 |
|
1332 | |||
1331 | if type(indata) in StringTypes: |
|
1333 | if type(indata) in StringTypes: | |
1332 | return [qw(indata)] |
|
1334 | return [qw(indata)] | |
1333 | else: |
|
1335 | else: | |
1334 | return qw(indata) |
|
1336 | return qw(indata) | |
1335 |
|
1337 | |||
1336 | #----------------------------------------------------------------------------- |
|
1338 | #----------------------------------------------------------------------------- | |
1337 | def list_strings(arg): |
|
1339 | def list_strings(arg): | |
1338 | """Always return a list of strings, given a string or list of strings |
|
1340 | """Always return a list of strings, given a string or list of strings | |
1339 | as input.""" |
|
1341 | as input.""" | |
1340 |
|
1342 | |||
1341 | if type(arg) in StringTypes: return [arg] |
|
1343 | if type(arg) in StringTypes: return [arg] | |
1342 | else: return arg |
|
1344 | else: return arg | |
1343 |
|
1345 | |||
1344 | #---------------------------------------------------------------------------- |
|
1346 | #---------------------------------------------------------------------------- | |
1345 | def grep(pat,list,case=1): |
|
1347 | def grep(pat,list,case=1): | |
1346 | """Simple minded grep-like function. |
|
1348 | """Simple minded grep-like function. | |
1347 | grep(pat,list) returns occurrences of pat in list, None on failure. |
|
1349 | grep(pat,list) returns occurrences of pat in list, None on failure. | |
1348 |
|
1350 | |||
1349 | It only does simple string matching, with no support for regexps. Use the |
|
1351 | It only does simple string matching, with no support for regexps. Use the | |
1350 | option case=0 for case-insensitive matching.""" |
|
1352 | option case=0 for case-insensitive matching.""" | |
1351 |
|
1353 | |||
1352 | # This is pretty crude. At least it should implement copying only references |
|
1354 | # This is pretty crude. At least it should implement copying only references | |
1353 | # to the original data in case it's big. Now it copies the data for output. |
|
1355 | # to the original data in case it's big. Now it copies the data for output. | |
1354 | out=[] |
|
1356 | out=[] | |
1355 | if case: |
|
1357 | if case: | |
1356 | for term in list: |
|
1358 | for term in list: | |
1357 | if term.find(pat)>-1: out.append(term) |
|
1359 | if term.find(pat)>-1: out.append(term) | |
1358 | else: |
|
1360 | else: | |
1359 | lpat=pat.lower() |
|
1361 | lpat=pat.lower() | |
1360 | for term in list: |
|
1362 | for term in list: | |
1361 | if term.lower().find(lpat)>-1: out.append(term) |
|
1363 | if term.lower().find(lpat)>-1: out.append(term) | |
1362 |
|
1364 | |||
1363 | if len(out): return out |
|
1365 | if len(out): return out | |
1364 | else: return None |
|
1366 | else: return None | |
1365 |
|
1367 | |||
1366 | #---------------------------------------------------------------------------- |
|
1368 | #---------------------------------------------------------------------------- | |
1367 | def dgrep(pat,*opts): |
|
1369 | def dgrep(pat,*opts): | |
1368 | """Return grep() on dir()+dir(__builtins__). |
|
1370 | """Return grep() on dir()+dir(__builtins__). | |
1369 |
|
1371 | |||
1370 | A very common use of grep() when working interactively.""" |
|
1372 | A very common use of grep() when working interactively.""" | |
1371 |
|
1373 | |||
1372 | return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts) |
|
1374 | return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts) | |
1373 |
|
1375 | |||
1374 | #---------------------------------------------------------------------------- |
|
1376 | #---------------------------------------------------------------------------- | |
1375 | def idgrep(pat): |
|
1377 | def idgrep(pat): | |
1376 | """Case-insensitive dgrep()""" |
|
1378 | """Case-insensitive dgrep()""" | |
1377 |
|
1379 | |||
1378 | return dgrep(pat,0) |
|
1380 | return dgrep(pat,0) | |
1379 |
|
1381 | |||
1380 | #---------------------------------------------------------------------------- |
|
1382 | #---------------------------------------------------------------------------- | |
1381 | def igrep(pat,list): |
|
1383 | def igrep(pat,list): | |
1382 | """Synonym for case-insensitive grep.""" |
|
1384 | """Synonym for case-insensitive grep.""" | |
1383 |
|
1385 | |||
1384 | return grep(pat,list,case=0) |
|
1386 | return grep(pat,list,case=0) | |
1385 |
|
1387 | |||
1386 | #---------------------------------------------------------------------------- |
|
1388 | #---------------------------------------------------------------------------- | |
1387 | def indent(str,nspaces=4,ntabs=0): |
|
1389 | def indent(str,nspaces=4,ntabs=0): | |
1388 | """Indent a string a given number of spaces or tabstops. |
|
1390 | """Indent a string a given number of spaces or tabstops. | |
1389 |
|
1391 | |||
1390 | indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. |
|
1392 | indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. | |
1391 | """ |
|
1393 | """ | |
1392 | if str is None: |
|
1394 | if str is None: | |
1393 | return |
|
1395 | return | |
1394 | ind = '\t'*ntabs+' '*nspaces |
|
1396 | ind = '\t'*ntabs+' '*nspaces | |
1395 | outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind)) |
|
1397 | outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind)) | |
1396 | if outstr.endswith(os.linesep+ind): |
|
1398 | if outstr.endswith(os.linesep+ind): | |
1397 | return outstr[:-len(ind)] |
|
1399 | return outstr[:-len(ind)] | |
1398 | else: |
|
1400 | else: | |
1399 | return outstr |
|
1401 | return outstr | |
1400 |
|
1402 | |||
1401 | #----------------------------------------------------------------------------- |
|
1403 | #----------------------------------------------------------------------------- | |
1402 | def native_line_ends(filename,backup=1): |
|
1404 | def native_line_ends(filename,backup=1): | |
1403 | """Convert (in-place) a file to line-ends native to the current OS. |
|
1405 | """Convert (in-place) a file to line-ends native to the current OS. | |
1404 |
|
1406 | |||
1405 | If the optional backup argument is given as false, no backup of the |
|
1407 | If the optional backup argument is given as false, no backup of the | |
1406 | original file is left. """ |
|
1408 | original file is left. """ | |
1407 |
|
1409 | |||
1408 | backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'} |
|
1410 | backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'} | |
1409 |
|
1411 | |||
1410 | bak_filename = filename + backup_suffixes[os.name] |
|
1412 | bak_filename = filename + backup_suffixes[os.name] | |
1411 |
|
1413 | |||
1412 | original = open(filename).read() |
|
1414 | original = open(filename).read() | |
1413 | shutil.copy2(filename,bak_filename) |
|
1415 | shutil.copy2(filename,bak_filename) | |
1414 | try: |
|
1416 | try: | |
1415 | new = open(filename,'wb') |
|
1417 | new = open(filename,'wb') | |
1416 | new.write(os.linesep.join(original.splitlines())) |
|
1418 | new.write(os.linesep.join(original.splitlines())) | |
1417 | new.write(os.linesep) # ALWAYS put an eol at the end of the file |
|
1419 | new.write(os.linesep) # ALWAYS put an eol at the end of the file | |
1418 | new.close() |
|
1420 | new.close() | |
1419 | except: |
|
1421 | except: | |
1420 | os.rename(bak_filename,filename) |
|
1422 | os.rename(bak_filename,filename) | |
1421 | if not backup: |
|
1423 | if not backup: | |
1422 | try: |
|
1424 | try: | |
1423 | os.remove(bak_filename) |
|
1425 | os.remove(bak_filename) | |
1424 | except: |
|
1426 | except: | |
1425 | pass |
|
1427 | pass | |
1426 |
|
1428 | |||
1427 | #---------------------------------------------------------------------------- |
|
1429 | #---------------------------------------------------------------------------- | |
1428 | def get_pager_cmd(pager_cmd = None): |
|
1430 | def get_pager_cmd(pager_cmd = None): | |
1429 | """Return a pager command. |
|
1431 | """Return a pager command. | |
1430 |
|
1432 | |||
1431 | Makes some attempts at finding an OS-correct one.""" |
|
1433 | Makes some attempts at finding an OS-correct one.""" | |
1432 |
|
1434 | |||
1433 | if os.name == 'posix': |
|
1435 | if os.name == 'posix': | |
1434 | default_pager_cmd = 'less -r' # -r for color control sequences |
|
1436 | default_pager_cmd = 'less -r' # -r for color control sequences | |
1435 | elif os.name in ['nt','dos']: |
|
1437 | elif os.name in ['nt','dos']: | |
1436 | default_pager_cmd = 'type' |
|
1438 | default_pager_cmd = 'type' | |
1437 |
|
1439 | |||
1438 | if pager_cmd is None: |
|
1440 | if pager_cmd is None: | |
1439 | try: |
|
1441 | try: | |
1440 | pager_cmd = os.environ['PAGER'] |
|
1442 | pager_cmd = os.environ['PAGER'] | |
1441 | except: |
|
1443 | except: | |
1442 | pager_cmd = default_pager_cmd |
|
1444 | pager_cmd = default_pager_cmd | |
1443 | return pager_cmd |
|
1445 | return pager_cmd | |
1444 |
|
1446 | |||
1445 | #----------------------------------------------------------------------------- |
|
1447 | #----------------------------------------------------------------------------- | |
1446 | def get_pager_start(pager,start): |
|
1448 | def get_pager_start(pager,start): | |
1447 | """Return the string for paging files with an offset. |
|
1449 | """Return the string for paging files with an offset. | |
1448 |
|
1450 | |||
1449 | This is the '+N' argument which less and more (under Unix) accept. |
|
1451 | This is the '+N' argument which less and more (under Unix) accept. | |
1450 | """ |
|
1452 | """ | |
1451 |
|
1453 | |||
1452 | if pager in ['less','more']: |
|
1454 | if pager in ['less','more']: | |
1453 | if start: |
|
1455 | if start: | |
1454 | start_string = '+' + str(start) |
|
1456 | start_string = '+' + str(start) | |
1455 | else: |
|
1457 | else: | |
1456 | start_string = '' |
|
1458 | start_string = '' | |
1457 | else: |
|
1459 | else: | |
1458 | start_string = '' |
|
1460 | start_string = '' | |
1459 | return start_string |
|
1461 | return start_string | |
1460 |
|
1462 | |||
1461 | #---------------------------------------------------------------------------- |
|
1463 | #---------------------------------------------------------------------------- | |
1462 | # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch() |
|
1464 | # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch() | |
1463 | if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs': |
|
1465 | if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs': | |
1464 | import msvcrt |
|
1466 | import msvcrt | |
1465 | def page_more(): |
|
1467 | def page_more(): | |
1466 | """ Smart pausing between pages |
|
1468 | """ Smart pausing between pages | |
1467 |
|
1469 | |||
1468 | @return: True if need print more lines, False if quit |
|
1470 | @return: True if need print more lines, False if quit | |
1469 | """ |
|
1471 | """ | |
1470 | Term.cout.write('---Return to continue, q to quit--- ') |
|
1472 | Term.cout.write('---Return to continue, q to quit--- ') | |
1471 | ans = msvcrt.getch() |
|
1473 | ans = msvcrt.getch() | |
1472 | if ans in ("q", "Q"): |
|
1474 | if ans in ("q", "Q"): | |
1473 | result = False |
|
1475 | result = False | |
1474 | else: |
|
1476 | else: | |
1475 | result = True |
|
1477 | result = True | |
1476 | Term.cout.write("\b"*37 + " "*37 + "\b"*37) |
|
1478 | Term.cout.write("\b"*37 + " "*37 + "\b"*37) | |
1477 | return result |
|
1479 | return result | |
1478 | else: |
|
1480 | else: | |
1479 | def page_more(): |
|
1481 | def page_more(): | |
1480 | ans = raw_input('---Return to continue, q to quit--- ') |
|
1482 | ans = raw_input('---Return to continue, q to quit--- ') | |
1481 | if ans.lower().startswith('q'): |
|
1483 | if ans.lower().startswith('q'): | |
1482 | return False |
|
1484 | return False | |
1483 | else: |
|
1485 | else: | |
1484 | return True |
|
1486 | return True | |
1485 |
|
1487 | |||
1486 | esc_re = re.compile(r"(\x1b[^m]+m)") |
|
1488 | esc_re = re.compile(r"(\x1b[^m]+m)") | |
1487 |
|
1489 | |||
1488 | def page_dumb(strng,start=0,screen_lines=25): |
|
1490 | def page_dumb(strng,start=0,screen_lines=25): | |
1489 | """Very dumb 'pager' in Python, for when nothing else works. |
|
1491 | """Very dumb 'pager' in Python, for when nothing else works. | |
1490 |
|
1492 | |||
1491 | Only moves forward, same interface as page(), except for pager_cmd and |
|
1493 | Only moves forward, same interface as page(), except for pager_cmd and | |
1492 | mode.""" |
|
1494 | mode.""" | |
1493 |
|
1495 | |||
1494 | out_ln = strng.splitlines()[start:] |
|
1496 | out_ln = strng.splitlines()[start:] | |
1495 | screens = chop(out_ln,screen_lines-1) |
|
1497 | screens = chop(out_ln,screen_lines-1) | |
1496 | if len(screens) == 1: |
|
1498 | if len(screens) == 1: | |
1497 | print >>Term.cout, os.linesep.join(screens[0]) |
|
1499 | print >>Term.cout, os.linesep.join(screens[0]) | |
1498 | else: |
|
1500 | else: | |
1499 | last_escape = "" |
|
1501 | last_escape = "" | |
1500 | for scr in screens[0:-1]: |
|
1502 | for scr in screens[0:-1]: | |
1501 | hunk = os.linesep.join(scr) |
|
1503 | hunk = os.linesep.join(scr) | |
1502 | print >>Term.cout, last_escape + hunk |
|
1504 | print >>Term.cout, last_escape + hunk | |
1503 | if not page_more(): |
|
1505 | if not page_more(): | |
1504 | return |
|
1506 | return | |
1505 | esc_list = esc_re.findall(hunk) |
|
1507 | esc_list = esc_re.findall(hunk) | |
1506 | if len(esc_list) > 0: |
|
1508 | if len(esc_list) > 0: | |
1507 | last_escape = esc_list[-1] |
|
1509 | last_escape = esc_list[-1] | |
1508 | print >>Term.cout, last_escape + os.linesep.join(screens[-1]) |
|
1510 | print >>Term.cout, last_escape + os.linesep.join(screens[-1]) | |
1509 |
|
1511 | |||
1510 | #---------------------------------------------------------------------------- |
|
1512 | #---------------------------------------------------------------------------- | |
1511 | def page(strng,start=0,screen_lines=0,pager_cmd = None): |
|
1513 | def page(strng,start=0,screen_lines=0,pager_cmd = None): | |
1512 | """Print a string, piping through a pager after a certain length. |
|
1514 | """Print a string, piping through a pager after a certain length. | |
1513 |
|
1515 | |||
1514 | The screen_lines parameter specifies the number of *usable* lines of your |
|
1516 | The screen_lines parameter specifies the number of *usable* lines of your | |
1515 | terminal screen (total lines minus lines you need to reserve to show other |
|
1517 | terminal screen (total lines minus lines you need to reserve to show other | |
1516 | information). |
|
1518 | information). | |
1517 |
|
1519 | |||
1518 | If you set screen_lines to a number <=0, page() will try to auto-determine |
|
1520 | If you set screen_lines to a number <=0, page() will try to auto-determine | |
1519 | your screen size and will only use up to (screen_size+screen_lines) for |
|
1521 | your screen size and will only use up to (screen_size+screen_lines) for | |
1520 | printing, paging after that. That is, if you want auto-detection but need |
|
1522 | printing, paging after that. That is, if you want auto-detection but need | |
1521 | to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for |
|
1523 | to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for | |
1522 | auto-detection without any lines reserved simply use screen_lines = 0. |
|
1524 | auto-detection without any lines reserved simply use screen_lines = 0. | |
1523 |
|
1525 | |||
1524 | If a string won't fit in the allowed lines, it is sent through the |
|
1526 | If a string won't fit in the allowed lines, it is sent through the | |
1525 | specified pager command. If none given, look for PAGER in the environment, |
|
1527 | specified pager command. If none given, look for PAGER in the environment, | |
1526 | and ultimately default to less. |
|
1528 | and ultimately default to less. | |
1527 |
|
1529 | |||
1528 | If no system pager works, the string is sent through a 'dumb pager' |
|
1530 | If no system pager works, the string is sent through a 'dumb pager' | |
1529 | written in python, very simplistic. |
|
1531 | written in python, very simplistic. | |
1530 | """ |
|
1532 | """ | |
1531 |
|
1533 | |||
1532 | # Some routines may auto-compute start offsets incorrectly and pass a |
|
1534 | # Some routines may auto-compute start offsets incorrectly and pass a | |
1533 | # negative value. Offset to 0 for robustness. |
|
1535 | # negative value. Offset to 0 for robustness. | |
1534 | start = max(0,start) |
|
1536 | start = max(0,start) | |
1535 |
|
1537 | |||
1536 | # first, try the hook |
|
1538 | # first, try the hook | |
1537 | ip = IPython.ipapi.get() |
|
1539 | ip = IPython.ipapi.get() | |
1538 | if ip: |
|
1540 | if ip: | |
1539 | try: |
|
1541 | try: | |
1540 | ip.IP.hooks.show_in_pager(strng) |
|
1542 | ip.IP.hooks.show_in_pager(strng) | |
1541 | return |
|
1543 | return | |
1542 | except IPython.ipapi.TryNext: |
|
1544 | except IPython.ipapi.TryNext: | |
1543 | pass |
|
1545 | pass | |
1544 |
|
1546 | |||
1545 | # Ugly kludge, but calling curses.initscr() flat out crashes in emacs |
|
1547 | # Ugly kludge, but calling curses.initscr() flat out crashes in emacs | |
1546 | TERM = os.environ.get('TERM','dumb') |
|
1548 | TERM = os.environ.get('TERM','dumb') | |
1547 | if TERM in ['dumb','emacs'] and os.name != 'nt': |
|
1549 | if TERM in ['dumb','emacs'] and os.name != 'nt': | |
1548 | print strng |
|
1550 | print strng | |
1549 | return |
|
1551 | return | |
1550 | # chop off the topmost part of the string we don't want to see |
|
1552 | # chop off the topmost part of the string we don't want to see | |
1551 | str_lines = strng.split(os.linesep)[start:] |
|
1553 | str_lines = strng.split(os.linesep)[start:] | |
1552 | str_toprint = os.linesep.join(str_lines) |
|
1554 | str_toprint = os.linesep.join(str_lines) | |
1553 | num_newlines = len(str_lines) |
|
1555 | num_newlines = len(str_lines) | |
1554 | len_str = len(str_toprint) |
|
1556 | len_str = len(str_toprint) | |
1555 |
|
1557 | |||
1556 | # Dumb heuristics to guesstimate number of on-screen lines the string |
|
1558 | # Dumb heuristics to guesstimate number of on-screen lines the string | |
1557 | # takes. Very basic, but good enough for docstrings in reasonable |
|
1559 | # takes. Very basic, but good enough for docstrings in reasonable | |
1558 | # terminals. If someone later feels like refining it, it's not hard. |
|
1560 | # terminals. If someone later feels like refining it, it's not hard. | |
1559 | numlines = max(num_newlines,int(len_str/80)+1) |
|
1561 | numlines = max(num_newlines,int(len_str/80)+1) | |
1560 |
|
1562 | |||
1561 | if os.name == "nt": |
|
1563 | if os.name == "nt": | |
1562 | screen_lines_def = get_console_size(defaulty=25)[1] |
|
1564 | screen_lines_def = get_console_size(defaulty=25)[1] | |
1563 | else: |
|
1565 | else: | |
1564 | screen_lines_def = 25 # default value if we can't auto-determine |
|
1566 | screen_lines_def = 25 # default value if we can't auto-determine | |
1565 |
|
1567 | |||
1566 | # auto-determine screen size |
|
1568 | # auto-determine screen size | |
1567 | if screen_lines <= 0: |
|
1569 | if screen_lines <= 0: | |
1568 | if TERM=='xterm': |
|
1570 | if TERM=='xterm': | |
1569 | use_curses = USE_CURSES |
|
1571 | use_curses = USE_CURSES | |
1570 | else: |
|
1572 | else: | |
1571 | # curses causes problems on many terminals other than xterm. |
|
1573 | # curses causes problems on many terminals other than xterm. | |
1572 | use_curses = False |
|
1574 | use_curses = False | |
1573 | if use_curses: |
|
1575 | if use_curses: | |
1574 | # There is a bug in curses, where *sometimes* it fails to properly |
|
1576 | # There is a bug in curses, where *sometimes* it fails to properly | |
1575 | # initialize, and then after the endwin() call is made, the |
|
1577 | # initialize, and then after the endwin() call is made, the | |
1576 | # terminal is left in an unusable state. Rather than trying to |
|
1578 | # terminal is left in an unusable state. Rather than trying to | |
1577 | # check everytime for this (by requesting and comparing termios |
|
1579 | # check everytime for this (by requesting and comparing termios | |
1578 | # flags each time), we just save the initial terminal state and |
|
1580 | # flags each time), we just save the initial terminal state and | |
1579 | # unconditionally reset it every time. It's cheaper than making |
|
1581 | # unconditionally reset it every time. It's cheaper than making | |
1580 | # the checks. |
|
1582 | # the checks. | |
1581 | term_flags = termios.tcgetattr(sys.stdout) |
|
1583 | term_flags = termios.tcgetattr(sys.stdout) | |
1582 | scr = curses.initscr() |
|
1584 | scr = curses.initscr() | |
1583 | screen_lines_real,screen_cols = scr.getmaxyx() |
|
1585 | screen_lines_real,screen_cols = scr.getmaxyx() | |
1584 | curses.endwin() |
|
1586 | curses.endwin() | |
1585 | # Restore terminal state in case endwin() didn't. |
|
1587 | # Restore terminal state in case endwin() didn't. | |
1586 | termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags) |
|
1588 | termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags) | |
1587 | # Now we have what we needed: the screen size in rows/columns |
|
1589 | # Now we have what we needed: the screen size in rows/columns | |
1588 | screen_lines += screen_lines_real |
|
1590 | screen_lines += screen_lines_real | |
1589 | #print '***Screen size:',screen_lines_real,'lines x',\ |
|
1591 | #print '***Screen size:',screen_lines_real,'lines x',\ | |
1590 | #screen_cols,'columns.' # dbg |
|
1592 | #screen_cols,'columns.' # dbg | |
1591 | else: |
|
1593 | else: | |
1592 | screen_lines += screen_lines_def |
|
1594 | screen_lines += screen_lines_def | |
1593 |
|
1595 | |||
1594 | #print 'numlines',numlines,'screenlines',screen_lines # dbg |
|
1596 | #print 'numlines',numlines,'screenlines',screen_lines # dbg | |
1595 | if numlines <= screen_lines : |
|
1597 | if numlines <= screen_lines : | |
1596 | #print '*** normal print' # dbg |
|
1598 | #print '*** normal print' # dbg | |
1597 | print >>Term.cout, str_toprint |
|
1599 | print >>Term.cout, str_toprint | |
1598 | else: |
|
1600 | else: | |
1599 | # Try to open pager and default to internal one if that fails. |
|
1601 | # Try to open pager and default to internal one if that fails. | |
1600 | # All failure modes are tagged as 'retval=1', to match the return |
|
1602 | # All failure modes are tagged as 'retval=1', to match the return | |
1601 | # value of a failed system command. If any intermediate attempt |
|
1603 | # value of a failed system command. If any intermediate attempt | |
1602 | # sets retval to 1, at the end we resort to our own page_dumb() pager. |
|
1604 | # sets retval to 1, at the end we resort to our own page_dumb() pager. | |
1603 | pager_cmd = get_pager_cmd(pager_cmd) |
|
1605 | pager_cmd = get_pager_cmd(pager_cmd) | |
1604 | pager_cmd += ' ' + get_pager_start(pager_cmd,start) |
|
1606 | pager_cmd += ' ' + get_pager_start(pager_cmd,start) | |
1605 | if os.name == 'nt': |
|
1607 | if os.name == 'nt': | |
1606 | if pager_cmd.startswith('type'): |
|
1608 | if pager_cmd.startswith('type'): | |
1607 | # The default WinXP 'type' command is failing on complex strings. |
|
1609 | # The default WinXP 'type' command is failing on complex strings. | |
1608 | retval = 1 |
|
1610 | retval = 1 | |
1609 | else: |
|
1611 | else: | |
1610 | tmpname = tempfile.mktemp('.txt') |
|
1612 | tmpname = tempfile.mktemp('.txt') | |
1611 | tmpfile = file(tmpname,'wt') |
|
1613 | tmpfile = file(tmpname,'wt') | |
1612 | tmpfile.write(strng) |
|
1614 | tmpfile.write(strng) | |
1613 | tmpfile.close() |
|
1615 | tmpfile.close() | |
1614 | cmd = "%s < %s" % (pager_cmd,tmpname) |
|
1616 | cmd = "%s < %s" % (pager_cmd,tmpname) | |
1615 | if os.system(cmd): |
|
1617 | if os.system(cmd): | |
1616 | retval = 1 |
|
1618 | retval = 1 | |
1617 | else: |
|
1619 | else: | |
1618 | retval = None |
|
1620 | retval = None | |
1619 | os.remove(tmpname) |
|
1621 | os.remove(tmpname) | |
1620 | else: |
|
1622 | else: | |
1621 | try: |
|
1623 | try: | |
1622 | retval = None |
|
1624 | retval = None | |
1623 | # if I use popen4, things hang. No idea why. |
|
1625 | # if I use popen4, things hang. No idea why. | |
1624 | #pager,shell_out = os.popen4(pager_cmd) |
|
1626 | #pager,shell_out = os.popen4(pager_cmd) | |
1625 | pager = os.popen(pager_cmd,'w') |
|
1627 | pager = os.popen(pager_cmd,'w') | |
1626 | pager.write(strng) |
|
1628 | pager.write(strng) | |
1627 | pager.close() |
|
1629 | pager.close() | |
1628 | retval = pager.close() # success returns None |
|
1630 | retval = pager.close() # success returns None | |
1629 | except IOError,msg: # broken pipe when user quits |
|
1631 | except IOError,msg: # broken pipe when user quits | |
1630 | if msg.args == (32,'Broken pipe'): |
|
1632 | if msg.args == (32,'Broken pipe'): | |
1631 | retval = None |
|
1633 | retval = None | |
1632 | else: |
|
1634 | else: | |
1633 | retval = 1 |
|
1635 | retval = 1 | |
1634 | except OSError: |
|
1636 | except OSError: | |
1635 | # Other strange problems, sometimes seen in Win2k/cygwin |
|
1637 | # Other strange problems, sometimes seen in Win2k/cygwin | |
1636 | retval = 1 |
|
1638 | retval = 1 | |
1637 | if retval is not None: |
|
1639 | if retval is not None: | |
1638 | page_dumb(strng,screen_lines=screen_lines) |
|
1640 | page_dumb(strng,screen_lines=screen_lines) | |
1639 |
|
1641 | |||
1640 | #---------------------------------------------------------------------------- |
|
1642 | #---------------------------------------------------------------------------- | |
1641 | def page_file(fname,start = 0, pager_cmd = None): |
|
1643 | def page_file(fname,start = 0, pager_cmd = None): | |
1642 | """Page a file, using an optional pager command and starting line. |
|
1644 | """Page a file, using an optional pager command and starting line. | |
1643 | """ |
|
1645 | """ | |
1644 |
|
1646 | |||
1645 | pager_cmd = get_pager_cmd(pager_cmd) |
|
1647 | pager_cmd = get_pager_cmd(pager_cmd) | |
1646 | pager_cmd += ' ' + get_pager_start(pager_cmd,start) |
|
1648 | pager_cmd += ' ' + get_pager_start(pager_cmd,start) | |
1647 |
|
1649 | |||
1648 | try: |
|
1650 | try: | |
1649 | if os.environ['TERM'] in ['emacs','dumb']: |
|
1651 | if os.environ['TERM'] in ['emacs','dumb']: | |
1650 | raise EnvironmentError |
|
1652 | raise EnvironmentError | |
1651 | xsys(pager_cmd + ' ' + fname) |
|
1653 | xsys(pager_cmd + ' ' + fname) | |
1652 | except: |
|
1654 | except: | |
1653 | try: |
|
1655 | try: | |
1654 | if start > 0: |
|
1656 | if start > 0: | |
1655 | start -= 1 |
|
1657 | start -= 1 | |
1656 | page(open(fname).read(),start) |
|
1658 | page(open(fname).read(),start) | |
1657 | except: |
|
1659 | except: | |
1658 | print 'Unable to show file',`fname` |
|
1660 | print 'Unable to show file',`fname` | |
1659 |
|
1661 | |||
1660 |
|
1662 | |||
1661 | #---------------------------------------------------------------------------- |
|
1663 | #---------------------------------------------------------------------------- | |
1662 | def snip_print(str,width = 75,print_full = 0,header = ''): |
|
1664 | def snip_print(str,width = 75,print_full = 0,header = ''): | |
1663 | """Print a string snipping the midsection to fit in width. |
|
1665 | """Print a string snipping the midsection to fit in width. | |
1664 |
|
1666 | |||
1665 | print_full: mode control: |
|
1667 | print_full: mode control: | |
1666 | - 0: only snip long strings |
|
1668 | - 0: only snip long strings | |
1667 | - 1: send to page() directly. |
|
1669 | - 1: send to page() directly. | |
1668 | - 2: snip long strings and ask for full length viewing with page() |
|
1670 | - 2: snip long strings and ask for full length viewing with page() | |
1669 | Return 1 if snipping was necessary, 0 otherwise.""" |
|
1671 | Return 1 if snipping was necessary, 0 otherwise.""" | |
1670 |
|
1672 | |||
1671 | if print_full == 1: |
|
1673 | if print_full == 1: | |
1672 | page(header+str) |
|
1674 | page(header+str) | |
1673 | return 0 |
|
1675 | return 0 | |
1674 |
|
1676 | |||
1675 | print header, |
|
1677 | print header, | |
1676 | if len(str) < width: |
|
1678 | if len(str) < width: | |
1677 | print str |
|
1679 | print str | |
1678 | snip = 0 |
|
1680 | snip = 0 | |
1679 | else: |
|
1681 | else: | |
1680 | whalf = int((width -5)/2) |
|
1682 | whalf = int((width -5)/2) | |
1681 | print str[:whalf] + ' <...> ' + str[-whalf:] |
|
1683 | print str[:whalf] + ' <...> ' + str[-whalf:] | |
1682 | snip = 1 |
|
1684 | snip = 1 | |
1683 | if snip and print_full == 2: |
|
1685 | if snip and print_full == 2: | |
1684 | if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y': |
|
1686 | if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y': | |
1685 | page(str) |
|
1687 | page(str) | |
1686 | return snip |
|
1688 | return snip | |
1687 |
|
1689 | |||
1688 | #**************************************************************************** |
|
1690 | #**************************************************************************** | |
1689 | # lists, dicts and structures |
|
1691 | # lists, dicts and structures | |
1690 |
|
1692 | |||
1691 | def belong(candidates,checklist): |
|
1693 | def belong(candidates,checklist): | |
1692 | """Check whether a list of items appear in a given list of options. |
|
1694 | """Check whether a list of items appear in a given list of options. | |
1693 |
|
1695 | |||
1694 | Returns a list of 1 and 0, one for each candidate given.""" |
|
1696 | Returns a list of 1 and 0, one for each candidate given.""" | |
1695 |
|
1697 | |||
1696 | return [x in checklist for x in candidates] |
|
1698 | return [x in checklist for x in candidates] | |
1697 |
|
1699 | |||
1698 | #---------------------------------------------------------------------------- |
|
1700 | #---------------------------------------------------------------------------- | |
1699 | def uniq_stable(elems): |
|
1701 | def uniq_stable(elems): | |
1700 | """uniq_stable(elems) -> list |
|
1702 | """uniq_stable(elems) -> list | |
1701 |
|
1703 | |||
1702 | Return from an iterable, a list of all the unique elements in the input, |
|
1704 | Return from an iterable, a list of all the unique elements in the input, | |
1703 | but maintaining the order in which they first appear. |
|
1705 | but maintaining the order in which they first appear. | |
1704 |
|
1706 | |||
1705 | A naive solution to this problem which just makes a dictionary with the |
|
1707 | A naive solution to this problem which just makes a dictionary with the | |
1706 | elements as keys fails to respect the stability condition, since |
|
1708 | elements as keys fails to respect the stability condition, since | |
1707 | dictionaries are unsorted by nature. |
|
1709 | dictionaries are unsorted by nature. | |
1708 |
|
1710 | |||
1709 | Note: All elements in the input must be valid dictionary keys for this |
|
1711 | Note: All elements in the input must be valid dictionary keys for this | |
1710 | routine to work, as it internally uses a dictionary for efficiency |
|
1712 | routine to work, as it internally uses a dictionary for efficiency | |
1711 | reasons.""" |
|
1713 | reasons.""" | |
1712 |
|
1714 | |||
1713 | unique = [] |
|
1715 | unique = [] | |
1714 | unique_dict = {} |
|
1716 | unique_dict = {} | |
1715 | for nn in elems: |
|
1717 | for nn in elems: | |
1716 | if nn not in unique_dict: |
|
1718 | if nn not in unique_dict: | |
1717 | unique.append(nn) |
|
1719 | unique.append(nn) | |
1718 | unique_dict[nn] = None |
|
1720 | unique_dict[nn] = None | |
1719 | return unique |
|
1721 | return unique | |
1720 |
|
1722 | |||
1721 | #---------------------------------------------------------------------------- |
|
1723 | #---------------------------------------------------------------------------- | |
1722 | class NLprinter: |
|
1724 | class NLprinter: | |
1723 | """Print an arbitrarily nested list, indicating index numbers. |
|
1725 | """Print an arbitrarily nested list, indicating index numbers. | |
1724 |
|
1726 | |||
1725 | An instance of this class called nlprint is available and callable as a |
|
1727 | An instance of this class called nlprint is available and callable as a | |
1726 | function. |
|
1728 | function. | |
1727 |
|
1729 | |||
1728 | nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent' |
|
1730 | nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent' | |
1729 | and using 'sep' to separate the index from the value. """ |
|
1731 | and using 'sep' to separate the index from the value. """ | |
1730 |
|
1732 | |||
1731 | def __init__(self): |
|
1733 | def __init__(self): | |
1732 | self.depth = 0 |
|
1734 | self.depth = 0 | |
1733 |
|
1735 | |||
1734 | def __call__(self,lst,pos='',**kw): |
|
1736 | def __call__(self,lst,pos='',**kw): | |
1735 | """Prints the nested list numbering levels.""" |
|
1737 | """Prints the nested list numbering levels.""" | |
1736 | kw.setdefault('indent',' ') |
|
1738 | kw.setdefault('indent',' ') | |
1737 | kw.setdefault('sep',': ') |
|
1739 | kw.setdefault('sep',': ') | |
1738 | kw.setdefault('start',0) |
|
1740 | kw.setdefault('start',0) | |
1739 | kw.setdefault('stop',len(lst)) |
|
1741 | kw.setdefault('stop',len(lst)) | |
1740 | # we need to remove start and stop from kw so they don't propagate |
|
1742 | # we need to remove start and stop from kw so they don't propagate | |
1741 | # into a recursive call for a nested list. |
|
1743 | # into a recursive call for a nested list. | |
1742 | start = kw['start']; del kw['start'] |
|
1744 | start = kw['start']; del kw['start'] | |
1743 | stop = kw['stop']; del kw['stop'] |
|
1745 | stop = kw['stop']; del kw['stop'] | |
1744 | if self.depth == 0 and 'header' in kw.keys(): |
|
1746 | if self.depth == 0 and 'header' in kw.keys(): | |
1745 | print kw['header'] |
|
1747 | print kw['header'] | |
1746 |
|
1748 | |||
1747 | for idx in range(start,stop): |
|
1749 | for idx in range(start,stop): | |
1748 | elem = lst[idx] |
|
1750 | elem = lst[idx] | |
1749 | if type(elem)==type([]): |
|
1751 | if type(elem)==type([]): | |
1750 | self.depth += 1 |
|
1752 | self.depth += 1 | |
1751 | self.__call__(elem,itpl('$pos$idx,'),**kw) |
|
1753 | self.__call__(elem,itpl('$pos$idx,'),**kw) | |
1752 | self.depth -= 1 |
|
1754 | self.depth -= 1 | |
1753 | else: |
|
1755 | else: | |
1754 | printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem') |
|
1756 | printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem') | |
1755 |
|
1757 | |||
1756 | nlprint = NLprinter() |
|
1758 | nlprint = NLprinter() | |
1757 | #---------------------------------------------------------------------------- |
|
1759 | #---------------------------------------------------------------------------- | |
1758 | def all_belong(candidates,checklist): |
|
1760 | def all_belong(candidates,checklist): | |
1759 | """Check whether a list of items ALL appear in a given list of options. |
|
1761 | """Check whether a list of items ALL appear in a given list of options. | |
1760 |
|
1762 | |||
1761 | Returns a single 1 or 0 value.""" |
|
1763 | Returns a single 1 or 0 value.""" | |
1762 |
|
1764 | |||
1763 | return 1-(0 in [x in checklist for x in candidates]) |
|
1765 | return 1-(0 in [x in checklist for x in candidates]) | |
1764 |
|
1766 | |||
1765 | #---------------------------------------------------------------------------- |
|
1767 | #---------------------------------------------------------------------------- | |
1766 | def sort_compare(lst1,lst2,inplace = 1): |
|
1768 | def sort_compare(lst1,lst2,inplace = 1): | |
1767 | """Sort and compare two lists. |
|
1769 | """Sort and compare two lists. | |
1768 |
|
1770 | |||
1769 | By default it does it in place, thus modifying the lists. Use inplace = 0 |
|
1771 | By default it does it in place, thus modifying the lists. Use inplace = 0 | |
1770 | to avoid that (at the cost of temporary copy creation).""" |
|
1772 | to avoid that (at the cost of temporary copy creation).""" | |
1771 | if not inplace: |
|
1773 | if not inplace: | |
1772 | lst1 = lst1[:] |
|
1774 | lst1 = lst1[:] | |
1773 | lst2 = lst2[:] |
|
1775 | lst2 = lst2[:] | |
1774 | lst1.sort(); lst2.sort() |
|
1776 | lst1.sort(); lst2.sort() | |
1775 | return lst1 == lst2 |
|
1777 | return lst1 == lst2 | |
1776 |
|
1778 | |||
1777 | #---------------------------------------------------------------------------- |
|
1779 | #---------------------------------------------------------------------------- | |
1778 | def mkdict(**kwargs): |
|
1780 | def mkdict(**kwargs): | |
1779 | """Return a dict from a keyword list. |
|
1781 | """Return a dict from a keyword list. | |
1780 |
|
1782 | |||
1781 | It's just syntactic sugar for making ditcionary creation more convenient: |
|
1783 | It's just syntactic sugar for making ditcionary creation more convenient: | |
1782 | # the standard way |
|
1784 | # the standard way | |
1783 | >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 } |
|
1785 | >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 } | |
1784 | # a cleaner way |
|
1786 | # a cleaner way | |
1785 | >>>data = dict(red=1, green=2, blue=3) |
|
1787 | >>>data = dict(red=1, green=2, blue=3) | |
1786 |
|
1788 | |||
1787 | If you need more than this, look at the Struct() class.""" |
|
1789 | If you need more than this, look at the Struct() class.""" | |
1788 |
|
1790 | |||
1789 | return kwargs |
|
1791 | return kwargs | |
1790 |
|
1792 | |||
1791 | #---------------------------------------------------------------------------- |
|
1793 | #---------------------------------------------------------------------------- | |
1792 | def list2dict(lst): |
|
1794 | def list2dict(lst): | |
1793 | """Takes a list of (key,value) pairs and turns it into a dict.""" |
|
1795 | """Takes a list of (key,value) pairs and turns it into a dict.""" | |
1794 |
|
1796 | |||
1795 | dic = {} |
|
1797 | dic = {} | |
1796 | for k,v in lst: dic[k] = v |
|
1798 | for k,v in lst: dic[k] = v | |
1797 | return dic |
|
1799 | return dic | |
1798 |
|
1800 | |||
1799 | #---------------------------------------------------------------------------- |
|
1801 | #---------------------------------------------------------------------------- | |
1800 | def list2dict2(lst,default=''): |
|
1802 | def list2dict2(lst,default=''): | |
1801 | """Takes a list and turns it into a dict. |
|
1803 | """Takes a list and turns it into a dict. | |
1802 | Much slower than list2dict, but more versatile. This version can take |
|
1804 | Much slower than list2dict, but more versatile. This version can take | |
1803 | lists with sublists of arbitrary length (including sclars).""" |
|
1805 | lists with sublists of arbitrary length (including sclars).""" | |
1804 |
|
1806 | |||
1805 | dic = {} |
|
1807 | dic = {} | |
1806 | for elem in lst: |
|
1808 | for elem in lst: | |
1807 | if type(elem) in (types.ListType,types.TupleType): |
|
1809 | if type(elem) in (types.ListType,types.TupleType): | |
1808 | size = len(elem) |
|
1810 | size = len(elem) | |
1809 | if size == 0: |
|
1811 | if size == 0: | |
1810 | pass |
|
1812 | pass | |
1811 | elif size == 1: |
|
1813 | elif size == 1: | |
1812 | dic[elem] = default |
|
1814 | dic[elem] = default | |
1813 | else: |
|
1815 | else: | |
1814 | k,v = elem[0], elem[1:] |
|
1816 | k,v = elem[0], elem[1:] | |
1815 | if len(v) == 1: v = v[0] |
|
1817 | if len(v) == 1: v = v[0] | |
1816 | dic[k] = v |
|
1818 | dic[k] = v | |
1817 | else: |
|
1819 | else: | |
1818 | dic[elem] = default |
|
1820 | dic[elem] = default | |
1819 | return dic |
|
1821 | return dic | |
1820 |
|
1822 | |||
1821 | #---------------------------------------------------------------------------- |
|
1823 | #---------------------------------------------------------------------------- | |
1822 | def flatten(seq): |
|
1824 | def flatten(seq): | |
1823 | """Flatten a list of lists (NOT recursive, only works for 2d lists).""" |
|
1825 | """Flatten a list of lists (NOT recursive, only works for 2d lists).""" | |
1824 |
|
1826 | |||
1825 | return [x for subseq in seq for x in subseq] |
|
1827 | return [x for subseq in seq for x in subseq] | |
1826 |
|
1828 | |||
1827 | #---------------------------------------------------------------------------- |
|
1829 | #---------------------------------------------------------------------------- | |
1828 | def get_slice(seq,start=0,stop=None,step=1): |
|
1830 | def get_slice(seq,start=0,stop=None,step=1): | |
1829 | """Get a slice of a sequence with variable step. Specify start,stop,step.""" |
|
1831 | """Get a slice of a sequence with variable step. Specify start,stop,step.""" | |
1830 | if stop == None: |
|
1832 | if stop == None: | |
1831 | stop = len(seq) |
|
1833 | stop = len(seq) | |
1832 | item = lambda i: seq[i] |
|
1834 | item = lambda i: seq[i] | |
1833 | return map(item,xrange(start,stop,step)) |
|
1835 | return map(item,xrange(start,stop,step)) | |
1834 |
|
1836 | |||
1835 | #---------------------------------------------------------------------------- |
|
1837 | #---------------------------------------------------------------------------- | |
1836 | def chop(seq,size): |
|
1838 | def chop(seq,size): | |
1837 | """Chop a sequence into chunks of the given size.""" |
|
1839 | """Chop a sequence into chunks of the given size.""" | |
1838 | chunk = lambda i: seq[i:i+size] |
|
1840 | chunk = lambda i: seq[i:i+size] | |
1839 | return map(chunk,xrange(0,len(seq),size)) |
|
1841 | return map(chunk,xrange(0,len(seq),size)) | |
1840 |
|
1842 | |||
1841 | #---------------------------------------------------------------------------- |
|
1843 | #---------------------------------------------------------------------------- | |
1842 | # with is a keyword as of python 2.5, so this function is renamed to withobj |
|
1844 | # with is a keyword as of python 2.5, so this function is renamed to withobj | |
1843 | # from its old 'with' name. |
|
1845 | # from its old 'with' name. | |
1844 | def with_obj(object, **args): |
|
1846 | def with_obj(object, **args): | |
1845 | """Set multiple attributes for an object, similar to Pascal's with. |
|
1847 | """Set multiple attributes for an object, similar to Pascal's with. | |
1846 |
|
1848 | |||
1847 | Example: |
|
1849 | Example: | |
1848 | with_obj(jim, |
|
1850 | with_obj(jim, | |
1849 | born = 1960, |
|
1851 | born = 1960, | |
1850 | haircolour = 'Brown', |
|
1852 | haircolour = 'Brown', | |
1851 | eyecolour = 'Green') |
|
1853 | eyecolour = 'Green') | |
1852 |
|
1854 | |||
1853 | Credit: Greg Ewing, in |
|
1855 | Credit: Greg Ewing, in | |
1854 | http://mail.python.org/pipermail/python-list/2001-May/040703.html. |
|
1856 | http://mail.python.org/pipermail/python-list/2001-May/040703.html. | |
1855 |
|
1857 | |||
1856 | NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with' |
|
1858 | NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with' | |
1857 | has become a keyword for Python 2.5, so we had to rename it.""" |
|
1859 | has become a keyword for Python 2.5, so we had to rename it.""" | |
1858 |
|
1860 | |||
1859 | object.__dict__.update(args) |
|
1861 | object.__dict__.update(args) | |
1860 |
|
1862 | |||
1861 | #---------------------------------------------------------------------------- |
|
1863 | #---------------------------------------------------------------------------- | |
1862 | def setattr_list(obj,alist,nspace = None): |
|
1864 | def setattr_list(obj,alist,nspace = None): | |
1863 | """Set a list of attributes for an object taken from a namespace. |
|
1865 | """Set a list of attributes for an object taken from a namespace. | |
1864 |
|
1866 | |||
1865 | setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in |
|
1867 | setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in | |
1866 | alist with their values taken from nspace, which must be a dict (something |
|
1868 | alist with their values taken from nspace, which must be a dict (something | |
1867 | like locals() will often do) If nspace isn't given, locals() of the |
|
1869 | like locals() will often do) If nspace isn't given, locals() of the | |
1868 | *caller* is used, so in most cases you can omit it. |
|
1870 | *caller* is used, so in most cases you can omit it. | |
1869 |
|
1871 | |||
1870 | Note that alist can be given as a string, which will be automatically |
|
1872 | Note that alist can be given as a string, which will be automatically | |
1871 | split into a list on whitespace. If given as a list, it must be a list of |
|
1873 | split into a list on whitespace. If given as a list, it must be a list of | |
1872 | *strings* (the variable names themselves), not of variables.""" |
|
1874 | *strings* (the variable names themselves), not of variables.""" | |
1873 |
|
1875 | |||
1874 | # this grabs the local variables from the *previous* call frame -- that is |
|
1876 | # this grabs the local variables from the *previous* call frame -- that is | |
1875 | # the locals from the function that called setattr_list(). |
|
1877 | # the locals from the function that called setattr_list(). | |
1876 | # - snipped from weave.inline() |
|
1878 | # - snipped from weave.inline() | |
1877 | if nspace is None: |
|
1879 | if nspace is None: | |
1878 | call_frame = sys._getframe().f_back |
|
1880 | call_frame = sys._getframe().f_back | |
1879 | nspace = call_frame.f_locals |
|
1881 | nspace = call_frame.f_locals | |
1880 |
|
1882 | |||
1881 | if type(alist) in StringTypes: |
|
1883 | if type(alist) in StringTypes: | |
1882 | alist = alist.split() |
|
1884 | alist = alist.split() | |
1883 | for attr in alist: |
|
1885 | for attr in alist: | |
1884 | val = eval(attr,nspace) |
|
1886 | val = eval(attr,nspace) | |
1885 | setattr(obj,attr,val) |
|
1887 | setattr(obj,attr,val) | |
1886 |
|
1888 | |||
1887 | #---------------------------------------------------------------------------- |
|
1889 | #---------------------------------------------------------------------------- | |
1888 | def getattr_list(obj,alist,*args): |
|
1890 | def getattr_list(obj,alist,*args): | |
1889 | """getattr_list(obj,alist[, default]) -> attribute list. |
|
1891 | """getattr_list(obj,alist[, default]) -> attribute list. | |
1890 |
|
1892 | |||
1891 | Get a list of named attributes for an object. When a default argument is |
|
1893 | Get a list of named attributes for an object. When a default argument is | |
1892 | given, it is returned when the attribute doesn't exist; without it, an |
|
1894 | given, it is returned when the attribute doesn't exist; without it, an | |
1893 | exception is raised in that case. |
|
1895 | exception is raised in that case. | |
1894 |
|
1896 | |||
1895 | Note that alist can be given as a string, which will be automatically |
|
1897 | Note that alist can be given as a string, which will be automatically | |
1896 | split into a list on whitespace. If given as a list, it must be a list of |
|
1898 | split into a list on whitespace. If given as a list, it must be a list of | |
1897 | *strings* (the variable names themselves), not of variables.""" |
|
1899 | *strings* (the variable names themselves), not of variables.""" | |
1898 |
|
1900 | |||
1899 | if type(alist) in StringTypes: |
|
1901 | if type(alist) in StringTypes: | |
1900 | alist = alist.split() |
|
1902 | alist = alist.split() | |
1901 | if args: |
|
1903 | if args: | |
1902 | if len(args)==1: |
|
1904 | if len(args)==1: | |
1903 | default = args[0] |
|
1905 | default = args[0] | |
1904 | return map(lambda attr: getattr(obj,attr,default),alist) |
|
1906 | return map(lambda attr: getattr(obj,attr,default),alist) | |
1905 | else: |
|
1907 | else: | |
1906 | raise ValueError,'getattr_list() takes only one optional argument' |
|
1908 | raise ValueError,'getattr_list() takes only one optional argument' | |
1907 | else: |
|
1909 | else: | |
1908 | return map(lambda attr: getattr(obj,attr),alist) |
|
1910 | return map(lambda attr: getattr(obj,attr),alist) | |
1909 |
|
1911 | |||
1910 | #---------------------------------------------------------------------------- |
|
1912 | #---------------------------------------------------------------------------- | |
1911 | def map_method(method,object_list,*argseq,**kw): |
|
1913 | def map_method(method,object_list,*argseq,**kw): | |
1912 | """map_method(method,object_list,*args,**kw) -> list |
|
1914 | """map_method(method,object_list,*args,**kw) -> list | |
1913 |
|
1915 | |||
1914 | Return a list of the results of applying the methods to the items of the |
|
1916 | Return a list of the results of applying the methods to the items of the | |
1915 | argument sequence(s). If more than one sequence is given, the method is |
|
1917 | argument sequence(s). If more than one sequence is given, the method is | |
1916 | called with an argument list consisting of the corresponding item of each |
|
1918 | called with an argument list consisting of the corresponding item of each | |
1917 | sequence. All sequences must be of the same length. |
|
1919 | sequence. All sequences must be of the same length. | |
1918 |
|
1920 | |||
1919 | Keyword arguments are passed verbatim to all objects called. |
|
1921 | Keyword arguments are passed verbatim to all objects called. | |
1920 |
|
1922 | |||
1921 | This is Python code, so it's not nearly as fast as the builtin map().""" |
|
1923 | This is Python code, so it's not nearly as fast as the builtin map().""" | |
1922 |
|
1924 | |||
1923 | out_list = [] |
|
1925 | out_list = [] | |
1924 | idx = 0 |
|
1926 | idx = 0 | |
1925 | for object in object_list: |
|
1927 | for object in object_list: | |
1926 | try: |
|
1928 | try: | |
1927 | handler = getattr(object, method) |
|
1929 | handler = getattr(object, method) | |
1928 | except AttributeError: |
|
1930 | except AttributeError: | |
1929 | out_list.append(None) |
|
1931 | out_list.append(None) | |
1930 | else: |
|
1932 | else: | |
1931 | if argseq: |
|
1933 | if argseq: | |
1932 | args = map(lambda lst:lst[idx],argseq) |
|
1934 | args = map(lambda lst:lst[idx],argseq) | |
1933 | #print 'ob',object,'hand',handler,'ar',args # dbg |
|
1935 | #print 'ob',object,'hand',handler,'ar',args # dbg | |
1934 | out_list.append(handler(args,**kw)) |
|
1936 | out_list.append(handler(args,**kw)) | |
1935 | else: |
|
1937 | else: | |
1936 | out_list.append(handler(**kw)) |
|
1938 | out_list.append(handler(**kw)) | |
1937 | idx += 1 |
|
1939 | idx += 1 | |
1938 | return out_list |
|
1940 | return out_list | |
1939 |
|
1941 | |||
1940 | #---------------------------------------------------------------------------- |
|
1942 | #---------------------------------------------------------------------------- | |
1941 | def get_class_members(cls): |
|
1943 | def get_class_members(cls): | |
1942 | ret = dir(cls) |
|
1944 | ret = dir(cls) | |
1943 | if hasattr(cls,'__bases__'): |
|
1945 | if hasattr(cls,'__bases__'): | |
1944 | for base in cls.__bases__: |
|
1946 | for base in cls.__bases__: | |
1945 | ret.extend(get_class_members(base)) |
|
1947 | ret.extend(get_class_members(base)) | |
1946 | return ret |
|
1948 | return ret | |
1947 |
|
1949 | |||
1948 | #---------------------------------------------------------------------------- |
|
1950 | #---------------------------------------------------------------------------- | |
1949 | def dir2(obj): |
|
1951 | def dir2(obj): | |
1950 | """dir2(obj) -> list of strings |
|
1952 | """dir2(obj) -> list of strings | |
1951 |
|
1953 | |||
1952 | Extended version of the Python builtin dir(), which does a few extra |
|
1954 | Extended version of the Python builtin dir(), which does a few extra | |
1953 | checks, and supports common objects with unusual internals that confuse |
|
1955 | checks, and supports common objects with unusual internals that confuse | |
1954 | dir(), such as Traits and PyCrust. |
|
1956 | dir(), such as Traits and PyCrust. | |
1955 |
|
1957 | |||
1956 | This version is guaranteed to return only a list of true strings, whereas |
|
1958 | This version is guaranteed to return only a list of true strings, whereas | |
1957 | dir() returns anything that objects inject into themselves, even if they |
|
1959 | dir() returns anything that objects inject into themselves, even if they | |
1958 | are later not really valid for attribute access (many extension libraries |
|
1960 | are later not really valid for attribute access (many extension libraries | |
1959 | have such bugs). |
|
1961 | have such bugs). | |
1960 | """ |
|
1962 | """ | |
1961 |
|
1963 | |||
1962 | # Start building the attribute list via dir(), and then complete it |
|
1964 | # Start building the attribute list via dir(), and then complete it | |
1963 | # with a few extra special-purpose calls. |
|
1965 | # with a few extra special-purpose calls. | |
1964 | words = dir(obj) |
|
1966 | words = dir(obj) | |
1965 |
|
1967 | |||
1966 | if hasattr(obj,'__class__'): |
|
1968 | if hasattr(obj,'__class__'): | |
1967 | words.append('__class__') |
|
1969 | words.append('__class__') | |
1968 | words.extend(get_class_members(obj.__class__)) |
|
1970 | words.extend(get_class_members(obj.__class__)) | |
1969 | #if '__base__' in words: 1/0 |
|
1971 | #if '__base__' in words: 1/0 | |
1970 |
|
1972 | |||
1971 | # Some libraries (such as traits) may introduce duplicates, we want to |
|
1973 | # Some libraries (such as traits) may introduce duplicates, we want to | |
1972 | # track and clean this up if it happens |
|
1974 | # track and clean this up if it happens | |
1973 | may_have_dupes = False |
|
1975 | may_have_dupes = False | |
1974 |
|
1976 | |||
1975 | # this is the 'dir' function for objects with Enthought's traits |
|
1977 | # this is the 'dir' function for objects with Enthought's traits | |
1976 | if hasattr(obj, 'trait_names'): |
|
1978 | if hasattr(obj, 'trait_names'): | |
1977 | try: |
|
1979 | try: | |
1978 | words.extend(obj.trait_names()) |
|
1980 | words.extend(obj.trait_names()) | |
1979 | may_have_dupes = True |
|
1981 | may_have_dupes = True | |
1980 | except TypeError: |
|
1982 | except TypeError: | |
1981 | # This will happen if `obj` is a class and not an instance. |
|
1983 | # This will happen if `obj` is a class and not an instance. | |
1982 | pass |
|
1984 | pass | |
1983 |
|
1985 | |||
1984 | # Support for PyCrust-style _getAttributeNames magic method. |
|
1986 | # Support for PyCrust-style _getAttributeNames magic method. | |
1985 | if hasattr(obj, '_getAttributeNames'): |
|
1987 | if hasattr(obj, '_getAttributeNames'): | |
1986 | try: |
|
1988 | try: | |
1987 | words.extend(obj._getAttributeNames()) |
|
1989 | words.extend(obj._getAttributeNames()) | |
1988 | may_have_dupes = True |
|
1990 | may_have_dupes = True | |
1989 | except TypeError: |
|
1991 | except TypeError: | |
1990 | # `obj` is a class and not an instance. Ignore |
|
1992 | # `obj` is a class and not an instance. Ignore | |
1991 | # this error. |
|
1993 | # this error. | |
1992 | pass |
|
1994 | pass | |
1993 |
|
1995 | |||
1994 | if may_have_dupes: |
|
1996 | if may_have_dupes: | |
1995 | # eliminate possible duplicates, as some traits may also |
|
1997 | # eliminate possible duplicates, as some traits may also | |
1996 | # appear as normal attributes in the dir() call. |
|
1998 | # appear as normal attributes in the dir() call. | |
1997 | words = list(set(words)) |
|
1999 | words = list(set(words)) | |
1998 | words.sort() |
|
2000 | words.sort() | |
1999 |
|
2001 | |||
2000 | # filter out non-string attributes which may be stuffed by dir() calls |
|
2002 | # filter out non-string attributes which may be stuffed by dir() calls | |
2001 | # and poor coding in third-party modules |
|
2003 | # and poor coding in third-party modules | |
2002 | return [w for w in words if isinstance(w, basestring)] |
|
2004 | return [w for w in words if isinstance(w, basestring)] | |
2003 |
|
2005 | |||
2004 | #---------------------------------------------------------------------------- |
|
2006 | #---------------------------------------------------------------------------- | |
2005 | def import_fail_info(mod_name,fns=None): |
|
2007 | def import_fail_info(mod_name,fns=None): | |
2006 | """Inform load failure for a module.""" |
|
2008 | """Inform load failure for a module.""" | |
2007 |
|
2009 | |||
2008 | if fns == None: |
|
2010 | if fns == None: | |
2009 | warn("Loading of %s failed.\n" % (mod_name,)) |
|
2011 | warn("Loading of %s failed.\n" % (mod_name,)) | |
2010 | else: |
|
2012 | else: | |
2011 | warn("Loading of %s from %s failed.\n" % (fns,mod_name)) |
|
2013 | warn("Loading of %s from %s failed.\n" % (fns,mod_name)) | |
2012 |
|
2014 | |||
2013 | #---------------------------------------------------------------------------- |
|
2015 | #---------------------------------------------------------------------------- | |
2014 | # Proposed popitem() extension, written as a method |
|
2016 | # Proposed popitem() extension, written as a method | |
2015 |
|
2017 | |||
2016 |
|
2018 | |||
2017 | class NotGiven: pass |
|
2019 | class NotGiven: pass | |
2018 |
|
2020 | |||
2019 | def popkey(dct,key,default=NotGiven): |
|
2021 | def popkey(dct,key,default=NotGiven): | |
2020 | """Return dct[key] and delete dct[key]. |
|
2022 | """Return dct[key] and delete dct[key]. | |
2021 |
|
2023 | |||
2022 | If default is given, return it if dct[key] doesn't exist, otherwise raise |
|
2024 | If default is given, return it if dct[key] doesn't exist, otherwise raise | |
2023 | KeyError. """ |
|
2025 | KeyError. """ | |
2024 |
|
2026 | |||
2025 | try: |
|
2027 | try: | |
2026 | val = dct[key] |
|
2028 | val = dct[key] | |
2027 | except KeyError: |
|
2029 | except KeyError: | |
2028 | if default is NotGiven: |
|
2030 | if default is NotGiven: | |
2029 | raise |
|
2031 | raise | |
2030 | else: |
|
2032 | else: | |
2031 | return default |
|
2033 | return default | |
2032 | else: |
|
2034 | else: | |
2033 | del dct[key] |
|
2035 | del dct[key] | |
2034 | return val |
|
2036 | return val | |
2035 |
|
2037 | |||
2036 | def wrap_deprecated(func, suggest = '<nothing>'): |
|
2038 | def wrap_deprecated(func, suggest = '<nothing>'): | |
2037 | def newFunc(*args, **kwargs): |
|
2039 | def newFunc(*args, **kwargs): | |
2038 | warnings.warn("Call to deprecated function %s, use %s instead" % |
|
2040 | warnings.warn("Call to deprecated function %s, use %s instead" % | |
2039 | ( func.__name__, suggest), |
|
2041 | ( func.__name__, suggest), | |
2040 | category=DeprecationWarning, |
|
2042 | category=DeprecationWarning, | |
2041 | stacklevel = 2) |
|
2043 | stacklevel = 2) | |
2042 | return func(*args, **kwargs) |
|
2044 | return func(*args, **kwargs) | |
2043 | return newFunc |
|
2045 | return newFunc | |
2044 |
|
||||
2045 | #*************************** end of file <genutils.py> ********************** |
|
|||
2046 |
|
2046 | |||
|
2047 | ||||
|
2048 | def _num_cpus_unix(): | |||
|
2049 | """Return the number of active CPUs on a Unix system.""" | |||
|
2050 | return os.sysconf("SC_NPROCESSORS_ONLN") | |||
|
2051 | ||||
|
2052 | ||||
|
2053 | def _num_cpus_darwin(): | |||
|
2054 | """Return the number of active CPUs on a Darwin system.""" | |||
|
2055 | p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE) | |||
|
2056 | return p.stdout.read() | |||
|
2057 | ||||
|
2058 | ||||
|
2059 | def _num_cpus_windows(): | |||
|
2060 | """Return the number of active CPUs on a Windows system.""" | |||
|
2061 | return os.environ.get("NUMBER_OF_PROCESSORS") | |||
|
2062 | ||||
|
2063 | ||||
|
2064 | def num_cpus(): | |||
|
2065 | """Return the effective number of CPUs in the system as an integer. | |||
|
2066 | ||||
|
2067 | This cross-platform function makes an attempt at finding the total number of | |||
|
2068 | available CPUs in the system, as returned by various underlying system and | |||
|
2069 | python calls. | |||
|
2070 | ||||
|
2071 | If it can't find a sensible answer, it returns 1 (though an error *may* make | |||
|
2072 | it return a large positive number that's actually incorrect). | |||
|
2073 | """ | |||
|
2074 | ||||
|
2075 | # Many thanks to the Parallel Python project (http://www.parallelpython.com) | |||
|
2076 | # for the names of the keys we needed to look up for this function. This | |||
|
2077 | # code was inspired by their equivalent function. | |||
|
2078 | ||||
|
2079 | ncpufuncs = {'Linux':_num_cpus_unix, | |||
|
2080 | 'Darwin':_num_cpus_darwin, | |||
|
2081 | 'Windows':_num_cpus_windows, | |||
|
2082 | # On Vista, python < 2.5.2 has a bug and returns 'Microsoft' | |||
|
2083 | # See http://bugs.python.org/issue1082 for details. | |||
|
2084 | 'Microsoft':_num_cpus_windows, | |||
|
2085 | } | |||
|
2086 | ||||
|
2087 | ncpufunc = ncpufuncs.get(platform.system(), | |||
|
2088 | # default to unix version (Solaris, AIX, etc) | |||
|
2089 | _num_cpus_unix) | |||
|
2090 | ||||
|
2091 | try: | |||
|
2092 | ncpus = max(1,int(ncpufunc())) | |||
|
2093 | except: | |||
|
2094 | ncpus = 1 | |||
|
2095 | return ncpus | |||
|
2096 | ||||
|
2097 | #*************************** end of file <genutils.py> ********************** |
@@ -1,394 +1,406 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Mimic C structs with lots of extra functionality. |
|
2 | """Mimic C structs with lots of extra functionality. | |
3 |
|
3 | |||
4 | $Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $""" |
|
4 | $Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $""" | |
5 |
|
5 | |||
6 | #***************************************************************************** |
|
6 | #***************************************************************************** | |
7 | # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> |
|
7 | # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> | |
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 | from IPython import Release |
|
13 | from IPython import Release | |
14 | __author__ = '%s <%s>' % Release.authors['Fernando'] |
|
14 | __author__ = '%s <%s>' % Release.authors['Fernando'] | |
15 | __license__ = Release.license |
|
15 | __license__ = Release.license | |
16 |
|
16 | |||
17 | __all__ = ['Struct'] |
|
17 | __all__ = ['Struct'] | |
18 |
|
18 | |||
19 | import types |
|
19 | import types | |
20 | import pprint |
|
20 | import pprint | |
21 |
|
21 | |||
22 | from IPython.genutils import list2dict2 |
|
22 | from IPython.genutils import list2dict2 | |
23 |
|
23 | |||
24 | class Struct: |
|
24 | class Struct: | |
25 | """Class to mimic C structs but also provide convenient dictionary-like |
|
25 | """Class to mimic C structs but also provide convenient dictionary-like | |
26 | functionality. |
|
26 | functionality. | |
27 |
|
27 | |||
28 | Instances can be initialized with a dictionary, a list of key=value pairs |
|
28 | Instances can be initialized with a dictionary, a list of key=value pairs | |
29 | or both. If both are present, the dictionary must come first. |
|
29 | or both. If both are present, the dictionary must come first. | |
30 |
|
30 | |||
31 | Because Python classes provide direct assignment to their members, it's |
|
31 | Because Python classes provide direct assignment to their members, it's | |
32 | easy to overwrite normal methods (S.copy = 1 would destroy access to |
|
32 | easy to overwrite normal methods (S.copy = 1 would destroy access to | |
33 | S.copy()). For this reason, all builtin method names are protected and |
|
33 | S.copy()). For this reason, all builtin method names are protected and | |
34 | can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise |
|
34 | can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise | |
35 | a KeyError exception. If you really want to, you can bypass this |
|
35 | a KeyError exception. If you really want to, you can bypass this | |
36 | protection by directly assigning to __dict__: s.__dict__['copy']=1 will |
|
36 | protection by directly assigning to __dict__: s.__dict__['copy']=1 will | |
37 | still work. Doing this will break functionality, though. As in most of |
|
37 | still work. Doing this will break functionality, though. As in most of | |
38 | Python, namespace protection is weakly enforced, so feel free to shoot |
|
38 | Python, namespace protection is weakly enforced, so feel free to shoot | |
39 | yourself if you really want to. |
|
39 | yourself if you really want to. | |
40 |
|
40 | |||
41 | Note that this class uses more memory and is *much* slower than a regular |
|
41 | Note that this class uses more memory and is *much* slower than a regular | |
42 | dictionary, so be careful in situations where memory or performance are |
|
42 | dictionary, so be careful in situations where memory or performance are | |
43 | critical. But for day to day use it should behave fine. It is particularly |
|
43 | critical. But for day to day use it should behave fine. It is particularly | |
44 | convenient for storing configuration data in programs. |
|
44 | convenient for storing configuration data in programs. | |
45 |
|
45 | |||
46 | +,+=,- and -= are implemented. +/+= do merges (non-destructive updates), |
|
46 | +,+=,- and -= are implemented. +/+= do merges (non-destructive updates), | |
47 | -/-= remove keys from the original. See the method descripitions. |
|
47 | -/-= remove keys from the original. See the method descripitions. | |
48 |
|
48 | |||
49 | This class allows a quick access syntax: both s.key and s['key'] are |
|
49 | This class allows a quick access syntax: both s.key and s['key'] are | |
50 | valid. This syntax has a limitation: each 'key' has to be explicitly |
|
50 | valid. This syntax has a limitation: each 'key' has to be explicitly | |
51 | accessed by its original name. The normal s.key syntax doesn't provide |
|
51 | accessed by its original name. The normal s.key syntax doesn't provide | |
52 | access to the keys via variables whose values evaluate to the desired |
|
52 | access to the keys via variables whose values evaluate to the desired | |
53 | keys. An example should clarify this: |
|
53 | keys. An example should clarify this: | |
54 |
|
54 | |||
55 | Define a dictionary and initialize both with dict and k=v pairs: |
|
55 | Define a dictionary and initialize both with dict and k=v pairs: | |
56 | >>> d={'a':1,'b':2} |
|
56 | >>> d={'a':1,'b':2} | |
57 | >>> s=Struct(d,hi=10,ho=20) |
|
57 | >>> s=Struct(d,hi=10,ho=20) | |
|
58 | ||||
58 | The return of __repr__ can be used to create a new instance: |
|
59 | The return of __repr__ can be used to create a new instance: | |
59 | >>> s |
|
60 | >>> s | |
60 |
Struct({' |
|
61 | Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20}) | |
|
62 | ||||
|
63 | Note: the special '__allownew' key is used for internal purposes. | |||
|
64 | ||||
61 | __str__ (called by print) shows it's not quite a regular dictionary: |
|
65 | __str__ (called by print) shows it's not quite a regular dictionary: | |
62 | >>> print s |
|
66 | >>> print s | |
63 |
Struct |
|
67 | Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20}) | |
|
68 | ||||
64 | Access by explicitly named key with dot notation: |
|
69 | Access by explicitly named key with dot notation: | |
65 | >>> s.a |
|
70 | >>> s.a | |
66 | 1 |
|
71 | 1 | |
|
72 | ||||
67 | Or like a dictionary: |
|
73 | Or like a dictionary: | |
68 | >>> s['a'] |
|
74 | >>> s['a'] | |
69 | 1 |
|
75 | 1 | |
|
76 | ||||
70 | If you want a variable to hold the key value, only dictionary access works: |
|
77 | If you want a variable to hold the key value, only dictionary access works: | |
71 | >>> key='hi' |
|
78 | >>> key='hi' | |
72 | >>> s.key |
|
79 | >>> s.key | |
73 | Traceback (most recent call last): |
|
80 | Traceback (most recent call last): | |
74 | File "<stdin>", line 1, in ? |
|
81 | File "<stdin>", line 1, in ? | |
75 | AttributeError: Struct instance has no attribute 'key' |
|
82 | AttributeError: Struct instance has no attribute 'key' | |
|
83 | ||||
76 | >>> s[key] |
|
84 | >>> s[key] | |
77 | 10 |
|
85 | 10 | |
78 |
|
86 | |||
79 | Another limitation of the s.key syntax (and Struct(key=val) |
|
87 | Another limitation of the s.key syntax (and Struct(key=val) | |
80 | initialization): keys can't be numbers. But numeric keys can be used and |
|
88 | initialization): keys can't be numbers. But numeric keys can be used and | |
81 | accessed using the dictionary syntax. Again, an example: |
|
89 | accessed using the dictionary syntax. Again, an example: | |
82 |
|
90 | |||
83 | This doesn't work: |
|
91 | This doesn't work: | |
84 | >>> s=Struct(4='hi') |
|
92 | >>> s=Struct(4='hi') #doctest: +IGNORE_EXCEPTION_DETAIL | |
|
93 | Traceback (most recent call last): | |||
|
94 | ... | |||
85 | SyntaxError: keyword can't be an expression |
|
95 | SyntaxError: keyword can't be an expression | |
|
96 | ||||
86 | But this does: |
|
97 | But this does: | |
87 | >>> s=Struct() |
|
98 | >>> s=Struct() | |
88 | >>> s[4]='hi' |
|
99 | >>> s[4]='hi' | |
89 | >>> s |
|
100 | >>> s | |
90 | Struct({4: 'hi'}) |
|
101 | Struct({4: 'hi', '__allownew': True}) | |
91 | >>> s[4] |
|
102 | >>> s[4] | |
92 | 'hi' |
|
103 | 'hi' | |
93 | """ |
|
104 | """ | |
94 |
|
105 | |||
95 | # Attributes to which __setitem__ and __setattr__ will block access. |
|
106 | # Attributes to which __setitem__ and __setattr__ will block access. | |
96 | # Note: much of this will be moot in Python 2.2 and will be done in a much |
|
107 | # Note: much of this will be moot in Python 2.2 and will be done in a much | |
97 | # cleaner way. |
|
108 | # cleaner way. | |
98 | __protected = ('copy dict dictcopy get has_attr has_key items keys ' |
|
109 | __protected = ('copy dict dictcopy get has_attr has_key items keys ' | |
99 | 'merge popitem setdefault update values ' |
|
110 | 'merge popitem setdefault update values ' | |
100 | '__make_dict __dict_invert ').split() |
|
111 | '__make_dict __dict_invert ').split() | |
101 |
|
112 | |||
102 | def __init__(self,dict=None,**kw): |
|
113 | def __init__(self,dict=None,**kw): | |
103 | """Initialize with a dictionary, another Struct, or by giving |
|
114 | """Initialize with a dictionary, another Struct, or by giving | |
104 | explicitly the list of attributes. |
|
115 | explicitly the list of attributes. | |
105 |
|
116 | |||
106 | Both can be used, but the dictionary must come first: |
|
117 | Both can be used, but the dictionary must come first: | |
107 | Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2). |
|
118 | Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2). | |
108 | """ |
|
119 | """ | |
109 | self.__dict__['__allownew'] = True |
|
120 | self.__dict__['__allownew'] = True | |
110 | if dict is None: |
|
121 | if dict is None: | |
111 | dict = {} |
|
122 | dict = {} | |
112 | if isinstance(dict,Struct): |
|
123 | if isinstance(dict,Struct): | |
113 | dict = dict.dict() |
|
124 | dict = dict.dict() | |
114 | elif dict and type(dict) is not types.DictType: |
|
125 | elif dict and type(dict) is not types.DictType: | |
115 | raise TypeError,\ |
|
126 | raise TypeError,\ | |
116 | 'Initialize with a dictionary or key=val pairs.' |
|
127 | 'Initialize with a dictionary or key=val pairs.' | |
117 | dict.update(kw) |
|
128 | dict.update(kw) | |
118 | # do the updating by hand to guarantee that we go through the |
|
129 | # do the updating by hand to guarantee that we go through the | |
119 | # safety-checked __setitem__ |
|
130 | # safety-checked __setitem__ | |
120 | for k,v in dict.items(): |
|
131 | for k,v in dict.items(): | |
121 | self[k] = v |
|
132 | self[k] = v | |
122 |
|
133 | |||
123 |
|
134 | |||
124 | def __setitem__(self,key,value): |
|
135 | def __setitem__(self,key,value): | |
125 | """Used when struct[key] = val calls are made.""" |
|
136 | """Used when struct[key] = val calls are made.""" | |
126 | if key in Struct.__protected: |
|
137 | if key in Struct.__protected: | |
127 | raise KeyError,'Key '+`key`+' is a protected key of class Struct.' |
|
138 | raise KeyError,'Key '+`key`+' is a protected key of class Struct.' | |
128 | if not self['__allownew'] and key not in self.__dict__: |
|
139 | if not self['__allownew'] and key not in self.__dict__: | |
129 | raise KeyError( |
|
140 | raise KeyError( | |
130 | "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" % |
|
141 | "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" % | |
131 | key) |
|
142 | key) | |
132 |
|
143 | |||
133 | self.__dict__[key] = value |
|
144 | self.__dict__[key] = value | |
134 |
|
145 | |||
135 | def __setattr__(self, key, value): |
|
146 | def __setattr__(self, key, value): | |
136 | """Used when struct.key = val calls are made.""" |
|
147 | """Used when struct.key = val calls are made.""" | |
137 | self.__setitem__(key,value) |
|
148 | self.__setitem__(key,value) | |
138 |
|
149 | |||
139 | def __str__(self): |
|
150 | def __str__(self): | |
140 | """Gets called by print.""" |
|
151 | """Gets called by print.""" | |
141 |
|
152 | |||
142 | return 'Struct('+ pprint.pformat(self.__dict__)+')' |
|
153 | return 'Struct('+ pprint.pformat(self.__dict__)+')' | |
143 |
|
154 | |||
144 | def __repr__(self): |
|
155 | def __repr__(self): | |
145 | """Gets called by repr. |
|
156 | """Gets called by repr. | |
146 |
|
157 | |||
147 | A Struct can be recreated with S_new=eval(repr(S_old)).""" |
|
158 | A Struct can be recreated with S_new=eval(repr(S_old)).""" | |
148 | return self.__str__() |
|
159 | return self.__str__() | |
149 |
|
160 | |||
150 | def __getitem__(self,key): |
|
161 | def __getitem__(self,key): | |
151 | """Allows struct[key] access.""" |
|
162 | """Allows struct[key] access.""" | |
152 | return self.__dict__[key] |
|
163 | return self.__dict__[key] | |
153 |
|
164 | |||
154 | def __contains__(self,key): |
|
165 | def __contains__(self,key): | |
155 | """Allows use of the 'in' operator.""" |
|
166 | """Allows use of the 'in' operator.""" | |
156 | return self.__dict__.has_key(key) |
|
167 | return self.__dict__.has_key(key) | |
157 |
|
168 | |||
158 | def __iadd__(self,other): |
|
169 | def __iadd__(self,other): | |
159 | """S += S2 is a shorthand for S.merge(S2).""" |
|
170 | """S += S2 is a shorthand for S.merge(S2).""" | |
160 | self.merge(other) |
|
171 | self.merge(other) | |
161 | return self |
|
172 | return self | |
162 |
|
173 | |||
163 | def __add__(self,other): |
|
174 | def __add__(self,other): | |
164 | """S + S2 -> New Struct made form S and S.merge(S2)""" |
|
175 | """S + S2 -> New Struct made form S and S.merge(S2)""" | |
165 | Sout = self.copy() |
|
176 | Sout = self.copy() | |
166 | Sout.merge(other) |
|
177 | Sout.merge(other) | |
167 | return Sout |
|
178 | return Sout | |
168 |
|
179 | |||
169 | def __sub__(self,other): |
|
180 | def __sub__(self,other): | |
170 | """Return S1-S2, where all keys in S2 have been deleted (if present) |
|
181 | """Return S1-S2, where all keys in S2 have been deleted (if present) | |
171 | from S1.""" |
|
182 | from S1.""" | |
172 | Sout = self.copy() |
|
183 | Sout = self.copy() | |
173 | Sout -= other |
|
184 | Sout -= other | |
174 | return Sout |
|
185 | return Sout | |
175 |
|
186 | |||
176 | def __isub__(self,other): |
|
187 | def __isub__(self,other): | |
177 | """Do in place S = S - S2, meaning all keys in S2 have been deleted |
|
188 | """Do in place S = S - S2, meaning all keys in S2 have been deleted | |
178 | (if present) from S1.""" |
|
189 | (if present) from S1.""" | |
179 |
|
190 | |||
180 | for k in other.keys(): |
|
191 | for k in other.keys(): | |
181 | if self.has_key(k): |
|
192 | if self.has_key(k): | |
182 | del self.__dict__[k] |
|
193 | del self.__dict__[k] | |
183 |
|
194 | |||
184 | def __make_dict(self,__loc_data__,**kw): |
|
195 | def __make_dict(self,__loc_data__,**kw): | |
185 | "Helper function for update and merge. Return a dict from data." |
|
196 | "Helper function for update and merge. Return a dict from data." | |
186 |
|
197 | |||
187 | if __loc_data__ == None: |
|
198 | if __loc_data__ == None: | |
188 | dict = {} |
|
199 | dict = {} | |
189 | elif type(__loc_data__) is types.DictType: |
|
200 | elif type(__loc_data__) is types.DictType: | |
190 | dict = __loc_data__ |
|
201 | dict = __loc_data__ | |
191 | elif isinstance(__loc_data__,Struct): |
|
202 | elif isinstance(__loc_data__,Struct): | |
192 | dict = __loc_data__.__dict__ |
|
203 | dict = __loc_data__.__dict__ | |
193 | else: |
|
204 | else: | |
194 | raise TypeError, 'Update with a dict, a Struct or key=val pairs.' |
|
205 | raise TypeError, 'Update with a dict, a Struct or key=val pairs.' | |
195 | if kw: |
|
206 | if kw: | |
196 | dict.update(kw) |
|
207 | dict.update(kw) | |
197 | return dict |
|
208 | return dict | |
198 |
|
209 | |||
199 | def __dict_invert(self,dict): |
|
210 | def __dict_invert(self,dict): | |
200 | """Helper function for merge. Takes a dictionary whose values are |
|
211 | """Helper function for merge. Takes a dictionary whose values are | |
201 | lists and returns a dict. with the elements of each list as keys and |
|
212 | lists and returns a dict. with the elements of each list as keys and | |
202 | the original keys as values.""" |
|
213 | the original keys as values.""" | |
203 |
|
214 | |||
204 | outdict = {} |
|
215 | outdict = {} | |
205 | for k,lst in dict.items(): |
|
216 | for k,lst in dict.items(): | |
206 | if type(lst) is types.StringType: |
|
217 | if type(lst) is types.StringType: | |
207 | lst = lst.split() |
|
218 | lst = lst.split() | |
208 | for entry in lst: |
|
219 | for entry in lst: | |
209 | outdict[entry] = k |
|
220 | outdict[entry] = k | |
210 | return outdict |
|
221 | return outdict | |
211 |
|
222 | |||
212 | def clear(self): |
|
223 | def clear(self): | |
213 | """Clear all attributes.""" |
|
224 | """Clear all attributes.""" | |
214 | self.__dict__.clear() |
|
225 | self.__dict__.clear() | |
215 |
|
226 | |||
216 | def copy(self): |
|
227 | def copy(self): | |
217 | """Return a (shallow) copy of a Struct.""" |
|
228 | """Return a (shallow) copy of a Struct.""" | |
218 | return Struct(self.__dict__.copy()) |
|
229 | return Struct(self.__dict__.copy()) | |
219 |
|
230 | |||
220 | def dict(self): |
|
231 | def dict(self): | |
221 | """Return the Struct's dictionary.""" |
|
232 | """Return the Struct's dictionary.""" | |
222 | return self.__dict__ |
|
233 | return self.__dict__ | |
223 |
|
234 | |||
224 | def dictcopy(self): |
|
235 | def dictcopy(self): | |
225 | """Return a (shallow) copy of the Struct's dictionary.""" |
|
236 | """Return a (shallow) copy of the Struct's dictionary.""" | |
226 | return self.__dict__.copy() |
|
237 | return self.__dict__.copy() | |
227 |
|
238 | |||
228 | def popitem(self): |
|
239 | def popitem(self): | |
229 | """S.popitem() -> (k, v), remove and return some (key, value) pair as |
|
240 | """S.popitem() -> (k, v), remove and return some (key, value) pair as | |
230 | a 2-tuple; but raise KeyError if S is empty.""" |
|
241 | a 2-tuple; but raise KeyError if S is empty.""" | |
231 | return self.__dict__.popitem() |
|
242 | return self.__dict__.popitem() | |
232 |
|
243 | |||
233 | def update(self,__loc_data__=None,**kw): |
|
244 | def update(self,__loc_data__=None,**kw): | |
234 | """Update (merge) with data from another Struct or from a dictionary. |
|
245 | """Update (merge) with data from another Struct or from a dictionary. | |
235 | Optionally, one or more key=value pairs can be given at the end for |
|
246 | Optionally, one or more key=value pairs can be given at the end for | |
236 | direct update.""" |
|
247 | direct update.""" | |
237 |
|
248 | |||
238 | # The funny name __loc_data__ is to prevent a common variable name which |
|
249 | # The funny name __loc_data__ is to prevent a common variable name which | |
239 | # could be a fieled of a Struct to collide with this parameter. The problem |
|
250 | # could be a fieled of a Struct to collide with this parameter. The problem | |
240 | # would arise if the function is called with a keyword with this same name |
|
251 | # would arise if the function is called with a keyword with this same name | |
241 | # that a user means to add as a Struct field. |
|
252 | # that a user means to add as a Struct field. | |
242 | newdict = Struct.__make_dict(self,__loc_data__,**kw) |
|
253 | newdict = Struct.__make_dict(self,__loc_data__,**kw) | |
243 | for k,v in newdict.items(): |
|
254 | for k,v in newdict.items(): | |
244 | self[k] = v |
|
255 | self[k] = v | |
245 |
|
256 | |||
246 | def merge(self,__loc_data__=None,__conflict_solve=None,**kw): |
|
257 | def merge(self,__loc_data__=None,__conflict_solve=None,**kw): | |
247 | """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S. |
|
258 | """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S. | |
248 |
|
259 | |||
249 | This is similar to update(), but much more flexible. First, a dict is |
|
260 | This is similar to update(), but much more flexible. First, a dict is | |
250 | made from data+key=value pairs. When merging this dict with the Struct |
|
261 | made from data+key=value pairs. When merging this dict with the Struct | |
251 | S, the optional dictionary 'conflict' is used to decide what to do. |
|
262 | S, the optional dictionary 'conflict' is used to decide what to do. | |
252 |
|
263 | |||
253 | If conflict is not given, the default behavior is to preserve any keys |
|
264 | If conflict is not given, the default behavior is to preserve any keys | |
254 | with their current value (the opposite of the update method's |
|
265 | with their current value (the opposite of the update method's | |
255 | behavior). |
|
266 | behavior). | |
256 |
|
267 | |||
257 | conflict is a dictionary of binary functions which will be used to |
|
268 | conflict is a dictionary of binary functions which will be used to | |
258 | solve key conflicts. It must have the following structure: |
|
269 | solve key conflicts. It must have the following structure: | |
259 |
|
270 | |||
260 | conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc } |
|
271 | conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc } | |
261 |
|
272 | |||
262 | Values must be lists or whitespace separated strings which are |
|
273 | Values must be lists or whitespace separated strings which are | |
263 | automatically converted to lists of strings by calling string.split(). |
|
274 | automatically converted to lists of strings by calling string.split(). | |
264 |
|
275 | |||
265 | Each key of conflict is a function which defines a policy for |
|
276 | Each key of conflict is a function which defines a policy for | |
266 | resolving conflicts when merging with the input data. Each fn must be |
|
277 | resolving conflicts when merging with the input data. Each fn must be | |
267 | a binary function which returns the desired outcome for a key |
|
278 | a binary function which returns the desired outcome for a key | |
268 | conflict. These functions will be called as fn(old,new). |
|
279 | conflict. These functions will be called as fn(old,new). | |
269 |
|
280 | |||
270 | An example is probably in order. Suppose you are merging the struct S |
|
281 | An example is probably in order. Suppose you are merging the struct S | |
271 | with a dict D and the following conflict policy dict: |
|
282 | with a dict D and the following conflict policy dict: | |
272 |
|
283 | |||
273 | S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'}) |
|
284 | S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'}) | |
274 |
|
285 | |||
275 | If the key 'a' is found in both S and D, the merge method will call: |
|
286 | If the key 'a' is found in both S and D, the merge method will call: | |
276 |
|
287 | |||
277 | S['a'] = fn1(S['a'],D['a']) |
|
288 | S['a'] = fn1(S['a'],D['a']) | |
278 |
|
289 | |||
279 | As a convenience, merge() provides five (the most commonly needed) |
|
290 | As a convenience, merge() provides five (the most commonly needed) | |
280 | pre-defined policies: preserve, update, add, add_flip and add_s. The |
|
291 | pre-defined policies: preserve, update, add, add_flip and add_s. The | |
281 | easiest explanation is their implementation: |
|
292 | easiest explanation is their implementation: | |
282 |
|
293 | |||
283 | preserve = lambda old,new: old |
|
294 | preserve = lambda old,new: old | |
284 | update = lambda old,new: new |
|
295 | update = lambda old,new: new | |
285 | add = lambda old,new: old + new |
|
296 | add = lambda old,new: old + new | |
286 | add_flip = lambda old,new: new + old # note change of order! |
|
297 | add_flip = lambda old,new: new + old # note change of order! | |
287 | add_s = lambda old,new: old + ' ' + new # only works for strings! |
|
298 | add_s = lambda old,new: old + ' ' + new # only works for strings! | |
288 |
|
299 | |||
289 | You can use those four words (as strings) as keys in conflict instead |
|
300 | You can use those four words (as strings) as keys in conflict instead | |
290 | of defining them as functions, and the merge method will substitute |
|
301 | of defining them as functions, and the merge method will substitute | |
291 | the appropriate functions for you. That is, the call |
|
302 | the appropriate functions for you. That is, the call | |
292 |
|
303 | |||
293 | S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]}) |
|
304 | S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]}) | |
294 |
|
305 | |||
295 | will automatically substitute the functions preserve and add for the |
|
306 | will automatically substitute the functions preserve and add for the | |
296 | names 'preserve' and 'add' before making any function calls. |
|
307 | names 'preserve' and 'add' before making any function calls. | |
297 |
|
308 | |||
298 | For more complicated conflict resolution policies, you still need to |
|
309 | For more complicated conflict resolution policies, you still need to | |
299 | construct your own functions. """ |
|
310 | construct your own functions. """ | |
300 |
|
311 | |||
301 | data_dict = Struct.__make_dict(self,__loc_data__,**kw) |
|
312 | data_dict = Struct.__make_dict(self,__loc_data__,**kw) | |
302 |
|
313 | |||
303 | # policies for conflict resolution: two argument functions which return |
|
314 | # policies for conflict resolution: two argument functions which return | |
304 | # the value that will go in the new struct |
|
315 | # the value that will go in the new struct | |
305 | preserve = lambda old,new: old |
|
316 | preserve = lambda old,new: old | |
306 | update = lambda old,new: new |
|
317 | update = lambda old,new: new | |
307 | add = lambda old,new: old + new |
|
318 | add = lambda old,new: old + new | |
308 | add_flip = lambda old,new: new + old # note change of order! |
|
319 | add_flip = lambda old,new: new + old # note change of order! | |
309 | add_s = lambda old,new: old + ' ' + new |
|
320 | add_s = lambda old,new: old + ' ' + new | |
310 |
|
321 | |||
311 | # default policy is to keep current keys when there's a conflict |
|
322 | # default policy is to keep current keys when there's a conflict | |
312 | conflict_solve = list2dict2(self.keys(),default = preserve) |
|
323 | conflict_solve = list2dict2(self.keys(),default = preserve) | |
313 |
|
324 | |||
314 | # the conflict_solve dictionary is given by the user 'inverted': we |
|
325 | # the conflict_solve dictionary is given by the user 'inverted': we | |
315 | # need a name-function mapping, it comes as a function -> names |
|
326 | # need a name-function mapping, it comes as a function -> names | |
316 | # dict. Make a local copy (b/c we'll make changes), replace user |
|
327 | # dict. Make a local copy (b/c we'll make changes), replace user | |
317 | # strings for the three builtin policies and invert it. |
|
328 | # strings for the three builtin policies and invert it. | |
318 | if __conflict_solve: |
|
329 | if __conflict_solve: | |
319 | inv_conflict_solve_user = __conflict_solve.copy() |
|
330 | inv_conflict_solve_user = __conflict_solve.copy() | |
320 | for name, func in [('preserve',preserve), ('update',update), |
|
331 | for name, func in [('preserve',preserve), ('update',update), | |
321 |
('add',add), ('add_flip',add_flip), |
|
332 | ('add',add), ('add_flip',add_flip), | |
|
333 | ('add_s',add_s)]: | |||
322 | if name in inv_conflict_solve_user.keys(): |
|
334 | if name in inv_conflict_solve_user.keys(): | |
323 | inv_conflict_solve_user[func] = inv_conflict_solve_user[name] |
|
335 | inv_conflict_solve_user[func] = inv_conflict_solve_user[name] | |
324 | del inv_conflict_solve_user[name] |
|
336 | del inv_conflict_solve_user[name] | |
325 | conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user)) |
|
337 | conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user)) | |
326 | #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg |
|
338 | #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg | |
327 | #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve) |
|
339 | #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve) | |
328 | for key in data_dict: |
|
340 | for key in data_dict: | |
329 | if key not in self: |
|
341 | if key not in self: | |
330 | self[key] = data_dict[key] |
|
342 | self[key] = data_dict[key] | |
331 | else: |
|
343 | else: | |
332 | self[key] = conflict_solve[key](self[key],data_dict[key]) |
|
344 | self[key] = conflict_solve[key](self[key],data_dict[key]) | |
333 |
|
345 | |||
334 | def has_key(self,key): |
|
346 | def has_key(self,key): | |
335 | """Like has_key() dictionary method.""" |
|
347 | """Like has_key() dictionary method.""" | |
336 | return self.__dict__.has_key(key) |
|
348 | return self.__dict__.has_key(key) | |
337 |
|
349 | |||
338 | def hasattr(self,key): |
|
350 | def hasattr(self,key): | |
339 | """hasattr function available as a method. |
|
351 | """hasattr function available as a method. | |
340 |
|
352 | |||
341 | Implemented like has_key, to make sure that all available keys in the |
|
353 | Implemented like has_key, to make sure that all available keys in the | |
342 | internal dictionary of the Struct appear also as attributes (even |
|
354 | internal dictionary of the Struct appear also as attributes (even | |
343 | numeric keys).""" |
|
355 | numeric keys).""" | |
344 | return self.__dict__.has_key(key) |
|
356 | return self.__dict__.has_key(key) | |
345 |
|
357 | |||
346 | def items(self): |
|
358 | def items(self): | |
347 | """Return the items in the Struct's dictionary, in the same format |
|
359 | """Return the items in the Struct's dictionary, in the same format | |
348 | as a call to {}.items().""" |
|
360 | as a call to {}.items().""" | |
349 | return self.__dict__.items() |
|
361 | return self.__dict__.items() | |
350 |
|
362 | |||
351 | def keys(self): |
|
363 | def keys(self): | |
352 | """Return the keys in the Struct's dictionary, in the same format |
|
364 | """Return the keys in the Struct's dictionary, in the same format | |
353 | as a call to {}.keys().""" |
|
365 | as a call to {}.keys().""" | |
354 | return self.__dict__.keys() |
|
366 | return self.__dict__.keys() | |
355 |
|
367 | |||
356 | def values(self,keys=None): |
|
368 | def values(self,keys=None): | |
357 | """Return the values in the Struct's dictionary, in the same format |
|
369 | """Return the values in the Struct's dictionary, in the same format | |
358 | as a call to {}.values(). |
|
370 | as a call to {}.values(). | |
359 |
|
371 | |||
360 | Can be called with an optional argument keys, which must be a list or |
|
372 | Can be called with an optional argument keys, which must be a list or | |
361 | tuple of keys. In this case it returns only the values corresponding |
|
373 | tuple of keys. In this case it returns only the values corresponding | |
362 | to those keys (allowing a form of 'slicing' for Structs).""" |
|
374 | to those keys (allowing a form of 'slicing' for Structs).""" | |
363 | if not keys: |
|
375 | if not keys: | |
364 | return self.__dict__.values() |
|
376 | return self.__dict__.values() | |
365 | else: |
|
377 | else: | |
366 | ret=[] |
|
378 | ret=[] | |
367 | for k in keys: |
|
379 | for k in keys: | |
368 | ret.append(self[k]) |
|
380 | ret.append(self[k]) | |
369 | return ret |
|
381 | return ret | |
370 |
|
382 | |||
371 | def get(self,attr,val=None): |
|
383 | def get(self,attr,val=None): | |
372 |
"""S.get(k[,d]) -> S[k] if |
|
384 | """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None.""" | |
373 | try: |
|
385 | try: | |
374 | return self[attr] |
|
386 | return self[attr] | |
375 | except KeyError: |
|
387 | except KeyError: | |
376 | return val |
|
388 | return val | |
377 |
|
389 | |||
378 | def setdefault(self,attr,val=None): |
|
390 | def setdefault(self,attr,val=None): | |
379 |
"""S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if not S |
|
391 | """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S""" | |
380 | if not self.has_key(attr): |
|
392 | if not self.has_key(attr): | |
381 | self[attr] = val |
|
393 | self[attr] = val | |
382 | return self.get(attr,val) |
|
394 | return self.get(attr,val) | |
383 |
|
395 | |||
384 | def allow_new_attr(self, allow = True): |
|
396 | def allow_new_attr(self, allow = True): | |
385 | """ Set whether new attributes can be created inside struct |
|
397 | """ Set whether new attributes can be created inside struct | |
386 |
|
398 | |||
387 |
This can be used to catch typos by verifying that the attribute user |
|
399 | This can be used to catch typos by verifying that the attribute user | |
388 | change already exists in this Struct. |
|
400 | tries to change already exists in this Struct. | |
389 | """ |
|
401 | """ | |
390 | self['__allownew'] = allow |
|
402 | self['__allownew'] = allow | |
391 |
|
403 | |||
392 |
|
404 | |||
393 | # end class Struct |
|
405 | # end class Struct | |
394 |
|
406 |
@@ -1,874 +1,876 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | # -*- test-case-name: IPython.kernel.tests.test_engineservice -*- |
|
2 | # -*- test-case-name: IPython.kernel.tests.test_engineservice -*- | |
3 |
|
3 | |||
4 | """A Twisted Service Representation of the IPython core. |
|
4 | """A Twisted Service Representation of the IPython core. | |
5 |
|
5 | |||
6 | The IPython Core exposed to the network is called the Engine. Its |
|
6 | The IPython Core exposed to the network is called the Engine. Its | |
7 | representation in Twisted in the EngineService. Interfaces and adapters |
|
7 | representation in Twisted in the EngineService. Interfaces and adapters | |
8 | are used to abstract out the details of the actual network protocol used. |
|
8 | are used to abstract out the details of the actual network protocol used. | |
9 | The EngineService is an Engine that knows nothing about the actual protocol |
|
9 | The EngineService is an Engine that knows nothing about the actual protocol | |
10 | used. |
|
10 | used. | |
11 |
|
11 | |||
12 | The EngineService is exposed with various network protocols in modules like: |
|
12 | The EngineService is exposed with various network protocols in modules like: | |
13 |
|
13 | |||
14 | enginepb.py |
|
14 | enginepb.py | |
15 | enginevanilla.py |
|
15 | enginevanilla.py | |
16 |
|
16 | |||
17 | As of 12/12/06 the classes in this module have been simplified greatly. It was |
|
17 | As of 12/12/06 the classes in this module have been simplified greatly. It was | |
18 | felt that we had over-engineered things. To improve the maintainability of the |
|
18 | felt that we had over-engineered things. To improve the maintainability of the | |
19 | code we have taken out the ICompleteEngine interface and the completeEngine |
|
19 | code we have taken out the ICompleteEngine interface and the completeEngine | |
20 | method that automatically added methods to engines. |
|
20 | method that automatically added methods to engines. | |
21 |
|
21 | |||
22 | """ |
|
22 | """ | |
23 |
|
23 | |||
24 | __docformat__ = "restructuredtext en" |
|
24 | __docformat__ = "restructuredtext en" | |
25 |
|
25 | |||
26 | #------------------------------------------------------------------------------- |
|
26 | #------------------------------------------------------------------------------- | |
27 | # Copyright (C) 2008 The IPython Development Team |
|
27 | # Copyright (C) 2008 The IPython Development Team | |
28 | # |
|
28 | # | |
29 | # Distributed under the terms of the BSD License. The full license is in |
|
29 | # Distributed under the terms of the BSD License. The full license is in | |
30 | # the file COPYING, distributed as part of this software. |
|
30 | # the file COPYING, distributed as part of this software. | |
31 | #------------------------------------------------------------------------------- |
|
31 | #------------------------------------------------------------------------------- | |
32 |
|
32 | |||
33 | #------------------------------------------------------------------------------- |
|
33 | #------------------------------------------------------------------------------- | |
34 | # Imports |
|
34 | # Imports | |
35 | #------------------------------------------------------------------------------- |
|
35 | #------------------------------------------------------------------------------- | |
36 |
|
36 | |||
37 | import os, sys, copy |
|
37 | import os, sys, copy | |
38 | import cPickle as pickle |
|
38 | import cPickle as pickle | |
39 | from new import instancemethod |
|
39 | from new import instancemethod | |
40 |
|
40 | |||
41 | from twisted.application import service |
|
41 | from twisted.application import service | |
42 | from twisted.internet import defer, reactor |
|
42 | from twisted.internet import defer, reactor | |
43 | from twisted.python import log, failure, components |
|
43 | from twisted.python import log, failure, components | |
44 | import zope.interface as zi |
|
44 | import zope.interface as zi | |
45 |
|
45 | |||
46 | from IPython.kernel.core.interpreter import Interpreter |
|
46 | from IPython.kernel.core.interpreter import Interpreter | |
47 | from IPython.kernel import newserialized, error, util |
|
47 | from IPython.kernel import newserialized, error, util | |
48 | from IPython.kernel.util import printer |
|
48 | from IPython.kernel.util import printer | |
49 | from IPython.kernel.twistedutil import gatherBoth, DeferredList |
|
49 | from IPython.kernel.twistedutil import gatherBoth, DeferredList | |
50 | from IPython.kernel import codeutil |
|
50 | from IPython.kernel import codeutil | |
51 |
|
51 | |||
52 |
|
52 | |||
53 | #------------------------------------------------------------------------------- |
|
53 | #------------------------------------------------------------------------------- | |
54 | # Interface specification for the Engine |
|
54 | # Interface specification for the Engine | |
55 | #------------------------------------------------------------------------------- |
|
55 | #------------------------------------------------------------------------------- | |
56 |
|
56 | |||
57 | class IEngineCore(zi.Interface): |
|
57 | class IEngineCore(zi.Interface): | |
58 | """The minimal required interface for the IPython Engine. |
|
58 | """The minimal required interface for the IPython Engine. | |
59 |
|
59 | |||
60 | This interface provides a formal specification of the IPython core. |
|
60 | This interface provides a formal specification of the IPython core. | |
61 | All these methods should return deferreds regardless of what side of a |
|
61 | All these methods should return deferreds regardless of what side of a | |
62 | network connection they are on. |
|
62 | network connection they are on. | |
63 |
|
63 | |||
64 | In general, this class simply wraps a shell class and wraps its return |
|
64 | In general, this class simply wraps a shell class and wraps its return | |
65 | values as Deferred objects. If the underlying shell class method raises |
|
65 | values as Deferred objects. If the underlying shell class method raises | |
66 | an exception, this class should convert it to a twisted.failure.Failure |
|
66 | an exception, this class should convert it to a twisted.failure.Failure | |
67 | that will be propagated along the Deferred's errback chain. |
|
67 | that will be propagated along the Deferred's errback chain. | |
68 |
|
68 | |||
69 | In addition, Failures are aggressive. By this, we mean that if a method |
|
69 | In addition, Failures are aggressive. By this, we mean that if a method | |
70 | is performing multiple actions (like pulling multiple object) if any |
|
70 | is performing multiple actions (like pulling multiple object) if any | |
71 | single one fails, the entire method will fail with that Failure. It is |
|
71 | single one fails, the entire method will fail with that Failure. It is | |
72 | all or nothing. |
|
72 | all or nothing. | |
73 | """ |
|
73 | """ | |
74 |
|
74 | |||
75 | id = zi.interface.Attribute("the id of the Engine object") |
|
75 | id = zi.interface.Attribute("the id of the Engine object") | |
76 | properties = zi.interface.Attribute("A dict of properties of the Engine") |
|
76 | properties = zi.interface.Attribute("A dict of properties of the Engine") | |
77 |
|
77 | |||
78 | def execute(lines): |
|
78 | def execute(lines): | |
79 | """Execute lines of Python code. |
|
79 | """Execute lines of Python code. | |
80 |
|
80 | |||
81 | Returns a dictionary with keys (id, number, stdin, stdout, stderr) |
|
81 | Returns a dictionary with keys (id, number, stdin, stdout, stderr) | |
82 | upon success. |
|
82 | upon success. | |
83 |
|
83 | |||
84 | Returns a failure object if the execution of lines raises an exception. |
|
84 | Returns a failure object if the execution of lines raises an exception. | |
85 | """ |
|
85 | """ | |
86 |
|
86 | |||
87 | def push(namespace): |
|
87 | def push(namespace): | |
88 | """Push dict namespace into the user's namespace. |
|
88 | """Push dict namespace into the user's namespace. | |
89 |
|
89 | |||
90 | Returns a deferred to None or a failure. |
|
90 | Returns a deferred to None or a failure. | |
91 | """ |
|
91 | """ | |
92 |
|
92 | |||
93 | def pull(keys): |
|
93 | def pull(keys): | |
94 | """Pulls values out of the user's namespace by keys. |
|
94 | """Pulls values out of the user's namespace by keys. | |
95 |
|
95 | |||
96 | Returns a deferred to a tuple objects or a single object. |
|
96 | Returns a deferred to a tuple objects or a single object. | |
97 |
|
97 | |||
98 | Raises NameError if any one of objects doess not exist. |
|
98 | Raises NameError if any one of objects doess not exist. | |
99 | """ |
|
99 | """ | |
100 |
|
100 | |||
101 | def push_function(namespace): |
|
101 | def push_function(namespace): | |
102 | """Push a dict of key, function pairs into the user's namespace. |
|
102 | """Push a dict of key, function pairs into the user's namespace. | |
103 |
|
103 | |||
104 | Returns a deferred to None or a failure.""" |
|
104 | Returns a deferred to None or a failure.""" | |
105 |
|
105 | |||
106 | def pull_function(keys): |
|
106 | def pull_function(keys): | |
107 | """Pulls functions out of the user's namespace by keys. |
|
107 | """Pulls functions out of the user's namespace by keys. | |
108 |
|
108 | |||
109 | Returns a deferred to a tuple of functions or a single function. |
|
109 | Returns a deferred to a tuple of functions or a single function. | |
110 |
|
110 | |||
111 | Raises NameError if any one of the functions does not exist. |
|
111 | Raises NameError if any one of the functions does not exist. | |
112 | """ |
|
112 | """ | |
113 |
|
113 | |||
114 | def get_result(i=None): |
|
114 | def get_result(i=None): | |
115 | """Get the stdin/stdout/stderr of command i. |
|
115 | """Get the stdin/stdout/stderr of command i. | |
116 |
|
116 | |||
117 | Returns a deferred to a dict with keys |
|
117 | Returns a deferred to a dict with keys | |
118 | (id, number, stdin, stdout, stderr). |
|
118 | (id, number, stdin, stdout, stderr). | |
119 |
|
119 | |||
120 | Raises IndexError if command i does not exist. |
|
120 | Raises IndexError if command i does not exist. | |
121 | Raises TypeError if i in not an int. |
|
121 | Raises TypeError if i in not an int. | |
122 | """ |
|
122 | """ | |
123 |
|
123 | |||
124 | def reset(): |
|
124 | def reset(): | |
125 | """Reset the shell. |
|
125 | """Reset the shell. | |
126 |
|
126 | |||
127 | This clears the users namespace. Won't cause modules to be |
|
127 | This clears the users namespace. Won't cause modules to be | |
128 | reloaded. Should also re-initialize certain variables like id. |
|
128 | reloaded. Should also re-initialize certain variables like id. | |
129 | """ |
|
129 | """ | |
130 |
|
130 | |||
131 | def kill(): |
|
131 | def kill(): | |
132 | """Kill the engine by stopping the reactor.""" |
|
132 | """Kill the engine by stopping the reactor.""" | |
133 |
|
133 | |||
134 | def keys(): |
|
134 | def keys(): | |
135 | """Return the top level variables in the users namspace. |
|
135 | """Return the top level variables in the users namspace. | |
136 |
|
136 | |||
137 | Returns a deferred to a dict.""" |
|
137 | Returns a deferred to a dict.""" | |
138 |
|
138 | |||
139 |
|
139 | |||
140 | class IEngineSerialized(zi.Interface): |
|
140 | class IEngineSerialized(zi.Interface): | |
141 | """Push/Pull methods that take Serialized objects. |
|
141 | """Push/Pull methods that take Serialized objects. | |
142 |
|
142 | |||
143 | All methods should return deferreds. |
|
143 | All methods should return deferreds. | |
144 | """ |
|
144 | """ | |
145 |
|
145 | |||
146 | def push_serialized(namespace): |
|
146 | def push_serialized(namespace): | |
147 | """Push a dict of keys and Serialized objects into the user's namespace.""" |
|
147 | """Push a dict of keys and Serialized objects into the user's namespace.""" | |
148 |
|
148 | |||
149 | def pull_serialized(keys): |
|
149 | def pull_serialized(keys): | |
150 | """Pull objects by key from the user's namespace as Serialized. |
|
150 | """Pull objects by key from the user's namespace as Serialized. | |
151 |
|
151 | |||
152 | Returns a list of or one Serialized. |
|
152 | Returns a list of or one Serialized. | |
153 |
|
153 | |||
154 | Raises NameError is any one of the objects does not exist. |
|
154 | Raises NameError is any one of the objects does not exist. | |
155 | """ |
|
155 | """ | |
156 |
|
156 | |||
157 |
|
157 | |||
158 | class IEngineProperties(zi.Interface): |
|
158 | class IEngineProperties(zi.Interface): | |
159 | """Methods for access to the properties object of an Engine""" |
|
159 | """Methods for access to the properties object of an Engine""" | |
160 |
|
160 | |||
161 | properties = zi.Attribute("A StrictDict object, containing the properties") |
|
161 | properties = zi.Attribute("A StrictDict object, containing the properties") | |
162 |
|
162 | |||
163 | def set_properties(properties): |
|
163 | def set_properties(properties): | |
164 | """set properties by key and value""" |
|
164 | """set properties by key and value""" | |
165 |
|
165 | |||
166 | def get_properties(keys=None): |
|
166 | def get_properties(keys=None): | |
167 | """get a list of properties by `keys`, if no keys specified, get all""" |
|
167 | """get a list of properties by `keys`, if no keys specified, get all""" | |
168 |
|
168 | |||
169 | def del_properties(keys): |
|
169 | def del_properties(keys): | |
170 | """delete properties by `keys`""" |
|
170 | """delete properties by `keys`""" | |
171 |
|
171 | |||
172 | def has_properties(keys): |
|
172 | def has_properties(keys): | |
173 | """get a list of bool values for whether `properties` has `keys`""" |
|
173 | """get a list of bool values for whether `properties` has `keys`""" | |
174 |
|
174 | |||
175 | def clear_properties(): |
|
175 | def clear_properties(): | |
176 | """clear the properties dict""" |
|
176 | """clear the properties dict""" | |
177 |
|
177 | |||
178 | class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties): |
|
178 | class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties): | |
179 | """The basic engine interface that EngineService will implement. |
|
179 | """The basic engine interface that EngineService will implement. | |
180 |
|
180 | |||
181 | This exists so it is easy to specify adapters that adapt to and from the |
|
181 | This exists so it is easy to specify adapters that adapt to and from the | |
182 | API that the basic EngineService implements. |
|
182 | API that the basic EngineService implements. | |
183 | """ |
|
183 | """ | |
184 | pass |
|
184 | pass | |
185 |
|
185 | |||
186 | class IEngineQueued(IEngineBase): |
|
186 | class IEngineQueued(IEngineBase): | |
187 | """Interface for adding a queue to an IEngineBase. |
|
187 | """Interface for adding a queue to an IEngineBase. | |
188 |
|
188 | |||
189 | This interface extends the IEngineBase interface to add methods for managing |
|
189 | This interface extends the IEngineBase interface to add methods for managing | |
190 | the engine's queue. The implicit details of this interface are that the |
|
190 | the engine's queue. The implicit details of this interface are that the | |
191 | execution of all methods declared in IEngineBase should appropriately be |
|
191 | execution of all methods declared in IEngineBase should appropriately be | |
192 | put through a queue before execution. |
|
192 | put through a queue before execution. | |
193 |
|
193 | |||
194 | All methods should return deferreds. |
|
194 | All methods should return deferreds. | |
195 | """ |
|
195 | """ | |
196 |
|
196 | |||
197 | def clear_queue(): |
|
197 | def clear_queue(): | |
198 | """Clear the queue.""" |
|
198 | """Clear the queue.""" | |
199 |
|
199 | |||
200 | def queue_status(): |
|
200 | def queue_status(): | |
201 | """Get the queued and pending commands in the queue.""" |
|
201 | """Get the queued and pending commands in the queue.""" | |
202 |
|
202 | |||
203 | def register_failure_observer(obs): |
|
203 | def register_failure_observer(obs): | |
204 | """Register an observer of pending Failures. |
|
204 | """Register an observer of pending Failures. | |
205 |
|
205 | |||
206 | The observer must implement IFailureObserver. |
|
206 | The observer must implement IFailureObserver. | |
207 | """ |
|
207 | """ | |
208 |
|
208 | |||
209 | def unregister_failure_observer(obs): |
|
209 | def unregister_failure_observer(obs): | |
210 | """Unregister an observer of pending Failures.""" |
|
210 | """Unregister an observer of pending Failures.""" | |
211 |
|
211 | |||
212 |
|
212 | |||
213 | class IEngineThreaded(zi.Interface): |
|
213 | class IEngineThreaded(zi.Interface): | |
214 | """A place holder for threaded commands. |
|
214 | """A place holder for threaded commands. | |
215 |
|
215 | |||
216 | All methods should return deferreds. |
|
216 | All methods should return deferreds. | |
217 | """ |
|
217 | """ | |
218 | pass |
|
218 | pass | |
219 |
|
219 | |||
220 |
|
220 | |||
221 | #------------------------------------------------------------------------------- |
|
221 | #------------------------------------------------------------------------------- | |
222 | # Functions and classes to implement the EngineService |
|
222 | # Functions and classes to implement the EngineService | |
223 | #------------------------------------------------------------------------------- |
|
223 | #------------------------------------------------------------------------------- | |
224 |
|
224 | |||
225 |
|
225 | |||
226 | class StrictDict(dict): |
|
226 | class StrictDict(dict): | |
227 | """This is a strict copying dictionary for use as the interface to the |
|
227 | """This is a strict copying dictionary for use as the interface to the | |
228 | properties of an Engine. |
|
228 | properties of an Engine. | |
229 | :IMPORTANT: |
|
229 | :IMPORTANT: | |
230 | This object copies the values you set to it, and returns copies to you |
|
230 | This object copies the values you set to it, and returns copies to you | |
231 | when you request them. The only way to change properties os explicitly |
|
231 | when you request them. The only way to change properties os explicitly | |
232 | through the setitem and getitem of the dictionary interface. |
|
232 | through the setitem and getitem of the dictionary interface. | |
233 | Example: |
|
233 | Example: | |
234 | >>> e = kernel.get_engine(id) |
|
234 | >>> e = kernel.get_engine(id) | |
235 | >>> L = someList |
|
235 | >>> L = someList | |
236 | >>> e.properties['L'] = L |
|
236 | >>> e.properties['L'] = L | |
237 | >>> L == e.properties['L'] |
|
237 | >>> L == e.properties['L'] | |
238 | ... True |
|
238 | ... True | |
239 | >>> L.append(something Else) |
|
239 | >>> L.append(something Else) | |
240 | >>> L == e.properties['L'] |
|
240 | >>> L == e.properties['L'] | |
241 | ... False |
|
241 | ... False | |
242 |
|
242 | |||
243 | getitem copies, so calls to methods of objects do not affect the |
|
243 | getitem copies, so calls to methods of objects do not affect the | |
244 | properties, as in the following example: |
|
244 | properties, as in the following example: | |
245 | >>> e.properties[1] = range(2) |
|
245 | >>> e.properties[1] = range(2) | |
246 | >>> print e.properties[1] |
|
246 | >>> print e.properties[1] | |
247 | ... [0, 1] |
|
247 | ... [0, 1] | |
248 | >>> e.properties[1].append(2) |
|
248 | >>> e.properties[1].append(2) | |
249 | >>> print e.properties[1] |
|
249 | >>> print e.properties[1] | |
250 | ... [0, 1] |
|
250 | ... [0, 1] | |
251 |
|
251 | |||
252 | """ |
|
252 | """ | |
253 | def __init__(self, *args, **kwargs): |
|
253 | def __init__(self, *args, **kwargs): | |
254 | dict.__init__(self, *args, **kwargs) |
|
254 | dict.__init__(self, *args, **kwargs) | |
255 | self.modified = True |
|
255 | self.modified = True | |
256 |
|
256 | |||
257 | def __getitem__(self, key): |
|
257 | def __getitem__(self, key): | |
258 | return copy.deepcopy(dict.__getitem__(self, key)) |
|
258 | return copy.deepcopy(dict.__getitem__(self, key)) | |
259 |
|
259 | |||
260 | def __setitem__(self, key, value): |
|
260 | def __setitem__(self, key, value): | |
261 | # check if this entry is valid for transport around the network |
|
261 | # check if this entry is valid for transport around the network | |
262 | # and copying |
|
262 | # and copying | |
263 | try: |
|
263 | try: | |
264 | pickle.dumps(key, 2) |
|
264 | pickle.dumps(key, 2) | |
265 | pickle.dumps(value, 2) |
|
265 | pickle.dumps(value, 2) | |
266 | newvalue = copy.deepcopy(value) |
|
266 | newvalue = copy.deepcopy(value) | |
267 | except: |
|
267 | except: | |
268 | raise error.InvalidProperty(value) |
|
268 | raise error.InvalidProperty(value) | |
269 | dict.__setitem__(self, key, newvalue) |
|
269 | dict.__setitem__(self, key, newvalue) | |
270 | self.modified = True |
|
270 | self.modified = True | |
271 |
|
271 | |||
272 | def __delitem__(self, key): |
|
272 | def __delitem__(self, key): | |
273 | dict.__delitem__(self, key) |
|
273 | dict.__delitem__(self, key) | |
274 | self.modified = True |
|
274 | self.modified = True | |
275 |
|
275 | |||
276 | def update(self, dikt): |
|
276 | def update(self, dikt): | |
277 | for k,v in dikt.iteritems(): |
|
277 | for k,v in dikt.iteritems(): | |
278 | self[k] = v |
|
278 | self[k] = v | |
279 |
|
279 | |||
280 | def pop(self, key): |
|
280 | def pop(self, key): | |
281 | self.modified = True |
|
281 | self.modified = True | |
282 | return dict.pop(self, key) |
|
282 | return dict.pop(self, key) | |
283 |
|
283 | |||
284 | def popitem(self): |
|
284 | def popitem(self): | |
285 | self.modified = True |
|
285 | self.modified = True | |
286 | return dict.popitem(self) |
|
286 | return dict.popitem(self) | |
287 |
|
287 | |||
288 | def clear(self): |
|
288 | def clear(self): | |
289 | self.modified = True |
|
289 | self.modified = True | |
290 | dict.clear(self) |
|
290 | dict.clear(self) | |
291 |
|
291 | |||
292 | def subDict(self, *keys): |
|
292 | def subDict(self, *keys): | |
293 | d = {} |
|
293 | d = {} | |
294 | for key in keys: |
|
294 | for key in keys: | |
295 | d[key] = self[key] |
|
295 | d[key] = self[key] | |
296 | return d |
|
296 | return d | |
297 |
|
297 | |||
298 |
|
298 | |||
299 |
|
299 | |||
300 | class EngineAPI(object): |
|
300 | class EngineAPI(object): | |
301 | """This is the object through which the user can edit the `properties` |
|
301 | """This is the object through which the user can edit the `properties` | |
302 | attribute of an Engine. |
|
302 | attribute of an Engine. | |
303 | The Engine Properties object copies all object in and out of itself. |
|
303 | The Engine Properties object copies all object in and out of itself. | |
304 | See the EngineProperties object for details. |
|
304 | See the EngineProperties object for details. | |
305 | """ |
|
305 | """ | |
306 | _fix=False |
|
306 | _fix=False | |
307 | def __init__(self, id): |
|
307 | def __init__(self, id): | |
308 | self.id = id |
|
308 | self.id = id | |
309 | self.properties = StrictDict() |
|
309 | self.properties = StrictDict() | |
310 | self._fix=True |
|
310 | self._fix=True | |
311 |
|
311 | |||
312 | def __setattr__(self, k,v): |
|
312 | def __setattr__(self, k,v): | |
313 | if self._fix: |
|
313 | if self._fix: | |
314 | raise error.KernelError("I am protected!") |
|
314 | raise error.KernelError("I am protected!") | |
315 | else: |
|
315 | else: | |
316 | object.__setattr__(self, k, v) |
|
316 | object.__setattr__(self, k, v) | |
317 |
|
317 | |||
318 | def __delattr__(self, key): |
|
318 | def __delattr__(self, key): | |
319 | raise error.KernelError("I am protected!") |
|
319 | raise error.KernelError("I am protected!") | |
320 |
|
320 | |||
321 |
|
321 | |||
322 | _apiDict = {} |
|
322 | _apiDict = {} | |
323 |
|
323 | |||
324 | def get_engine(id): |
|
324 | def get_engine(id): | |
325 | """Get the Engine API object, whcih currently just provides the properties |
|
325 | """Get the Engine API object, whcih currently just provides the properties | |
326 | object, by ID""" |
|
326 | object, by ID""" | |
327 | global _apiDict |
|
327 | global _apiDict | |
328 | if not _apiDict.get(id): |
|
328 | if not _apiDict.get(id): | |
329 | _apiDict[id] = EngineAPI(id) |
|
329 | _apiDict[id] = EngineAPI(id) | |
330 | return _apiDict[id] |
|
330 | return _apiDict[id] | |
331 |
|
331 | |||
332 | def drop_engine(id): |
|
332 | def drop_engine(id): | |
333 | """remove an engine""" |
|
333 | """remove an engine""" | |
334 | global _apiDict |
|
334 | global _apiDict | |
335 | if _apiDict.has_key(id): |
|
335 | if _apiDict.has_key(id): | |
336 | del _apiDict[id] |
|
336 | del _apiDict[id] | |
337 |
|
337 | |||
338 | class EngineService(object, service.Service): |
|
338 | class EngineService(object, service.Service): | |
339 | """Adapt a IPython shell into a IEngine implementing Twisted Service.""" |
|
339 | """Adapt a IPython shell into a IEngine implementing Twisted Service.""" | |
340 |
|
340 | |||
341 | zi.implements(IEngineBase) |
|
341 | zi.implements(IEngineBase) | |
342 | name = 'EngineService' |
|
342 | name = 'EngineService' | |
343 |
|
343 | |||
344 | def __init__(self, shellClass=Interpreter, mpi=None): |
|
344 | def __init__(self, shellClass=Interpreter, mpi=None): | |
345 | """Create an EngineService. |
|
345 | """Create an EngineService. | |
346 |
|
346 | |||
347 | shellClass: something that implements IInterpreter or core1 |
|
347 | shellClass: something that implements IInterpreter or core1 | |
348 | mpi: an mpi module that has rank and size attributes |
|
348 | mpi: an mpi module that has rank and size attributes | |
349 | """ |
|
349 | """ | |
350 | self.shellClass = shellClass |
|
350 | self.shellClass = shellClass | |
351 | self.shell = self.shellClass() |
|
351 | self.shell = self.shellClass() | |
352 | self.mpi = mpi |
|
352 | self.mpi = mpi | |
353 | self.id = None |
|
353 | self.id = None | |
354 | self.properties = get_engine(self.id).properties |
|
354 | self.properties = get_engine(self.id).properties | |
355 | if self.mpi is not None: |
|
355 | if self.mpi is not None: | |
356 | log.msg("MPI started with rank = %i and size = %i" % |
|
356 | log.msg("MPI started with rank = %i and size = %i" % | |
357 | (self.mpi.rank, self.mpi.size)) |
|
357 | (self.mpi.rank, self.mpi.size)) | |
358 | self.id = self.mpi.rank |
|
358 | self.id = self.mpi.rank | |
359 | self._seedNamespace() |
|
359 | self._seedNamespace() | |
360 |
|
360 | |||
361 | # Make id a property so that the shell can get the updated id |
|
361 | # Make id a property so that the shell can get the updated id | |
362 |
|
362 | |||
363 | def _setID(self, id): |
|
363 | def _setID(self, id): | |
364 | self._id = id |
|
364 | self._id = id | |
365 | self.properties = get_engine(id).properties |
|
365 | self.properties = get_engine(id).properties | |
366 | self.shell.push({'id': id}) |
|
366 | self.shell.push({'id': id}) | |
367 |
|
367 | |||
368 | def _getID(self): |
|
368 | def _getID(self): | |
369 | return self._id |
|
369 | return self._id | |
370 |
|
370 | |||
371 | id = property(_getID, _setID) |
|
371 | id = property(_getID, _setID) | |
372 |
|
372 | |||
373 | def _seedNamespace(self): |
|
373 | def _seedNamespace(self): | |
374 | self.shell.push({'mpi': self.mpi, 'id' : self.id}) |
|
374 | self.shell.push({'mpi': self.mpi, 'id' : self.id}) | |
375 |
|
375 | |||
376 | def executeAndRaise(self, msg, callable, *args, **kwargs): |
|
376 | def executeAndRaise(self, msg, callable, *args, **kwargs): | |
377 | """Call a method of self.shell and wrap any exception.""" |
|
377 | """Call a method of self.shell and wrap any exception.""" | |
378 | d = defer.Deferred() |
|
378 | d = defer.Deferred() | |
379 | try: |
|
379 | try: | |
380 | result = callable(*args, **kwargs) |
|
380 | result = callable(*args, **kwargs) | |
381 | except: |
|
381 | except: | |
382 | # This gives the following: |
|
382 | # This gives the following: | |
383 | # et=exception class |
|
383 | # et=exception class | |
384 | # ev=exception class instance |
|
384 | # ev=exception class instance | |
385 | # tb=traceback object |
|
385 | # tb=traceback object | |
386 | et,ev,tb = sys.exc_info() |
|
386 | et,ev,tb = sys.exc_info() | |
387 | # This call adds attributes to the exception value |
|
387 | # This call adds attributes to the exception value | |
388 | et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg) |
|
388 | et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg) | |
389 | # Add another attribute |
|
389 | # Add another attribute | |
390 | ev._ipython_engine_info = msg |
|
390 | ev._ipython_engine_info = msg | |
391 | f = failure.Failure(ev,et,None) |
|
391 | f = failure.Failure(ev,et,None) | |
392 | d.errback(f) |
|
392 | d.errback(f) | |
393 | else: |
|
393 | else: | |
394 | d.callback(result) |
|
394 | d.callback(result) | |
395 |
|
395 | |||
396 | return d |
|
396 | return d | |
397 |
|
397 | |||
398 | # The IEngine methods. See the interface for documentation. |
|
398 | # The IEngine methods. See the interface for documentation. | |
399 |
|
399 | |||
400 | def execute(self, lines): |
|
400 | def execute(self, lines): | |
401 | msg = {'engineid':self.id, |
|
401 | msg = {'engineid':self.id, | |
402 | 'method':'execute', |
|
402 | 'method':'execute', | |
403 | 'args':[lines]} |
|
403 | 'args':[lines]} | |
404 | d = self.executeAndRaise(msg, self.shell.execute, lines) |
|
404 | d = self.executeAndRaise(msg, self.shell.execute, lines) | |
405 | d.addCallback(self.addIDToResult) |
|
405 | d.addCallback(self.addIDToResult) | |
406 | return d |
|
406 | return d | |
407 |
|
407 | |||
408 | def addIDToResult(self, result): |
|
408 | def addIDToResult(self, result): | |
409 | result['id'] = self.id |
|
409 | result['id'] = self.id | |
410 | return result |
|
410 | return result | |
411 |
|
411 | |||
412 | def push(self, namespace): |
|
412 | def push(self, namespace): | |
413 | msg = {'engineid':self.id, |
|
413 | msg = {'engineid':self.id, | |
414 | 'method':'push', |
|
414 | 'method':'push', | |
415 | 'args':[repr(namespace.keys())]} |
|
415 | 'args':[repr(namespace.keys())]} | |
416 | d = self.executeAndRaise(msg, self.shell.push, namespace) |
|
416 | d = self.executeAndRaise(msg, self.shell.push, namespace) | |
417 | return d |
|
417 | return d | |
418 |
|
418 | |||
419 | def pull(self, keys): |
|
419 | def pull(self, keys): | |
420 | msg = {'engineid':self.id, |
|
420 | msg = {'engineid':self.id, | |
421 | 'method':'pull', |
|
421 | 'method':'pull', | |
422 | 'args':[repr(keys)]} |
|
422 | 'args':[repr(keys)]} | |
423 | d = self.executeAndRaise(msg, self.shell.pull, keys) |
|
423 | d = self.executeAndRaise(msg, self.shell.pull, keys) | |
424 | return d |
|
424 | return d | |
425 |
|
425 | |||
426 | def push_function(self, namespace): |
|
426 | def push_function(self, namespace): | |
427 | msg = {'engineid':self.id, |
|
427 | msg = {'engineid':self.id, | |
428 | 'method':'push_function', |
|
428 | 'method':'push_function', | |
429 | 'args':[repr(namespace.keys())]} |
|
429 | 'args':[repr(namespace.keys())]} | |
430 | d = self.executeAndRaise(msg, self.shell.push_function, namespace) |
|
430 | d = self.executeAndRaise(msg, self.shell.push_function, namespace) | |
431 | return d |
|
431 | return d | |
432 |
|
432 | |||
433 | def pull_function(self, keys): |
|
433 | def pull_function(self, keys): | |
434 | msg = {'engineid':self.id, |
|
434 | msg = {'engineid':self.id, | |
435 | 'method':'pull_function', |
|
435 | 'method':'pull_function', | |
436 | 'args':[repr(keys)]} |
|
436 | 'args':[repr(keys)]} | |
437 | d = self.executeAndRaise(msg, self.shell.pull_function, keys) |
|
437 | d = self.executeAndRaise(msg, self.shell.pull_function, keys) | |
438 | return d |
|
438 | return d | |
439 |
|
439 | |||
440 | def get_result(self, i=None): |
|
440 | def get_result(self, i=None): | |
441 | msg = {'engineid':self.id, |
|
441 | msg = {'engineid':self.id, | |
442 | 'method':'get_result', |
|
442 | 'method':'get_result', | |
443 | 'args':[repr(i)]} |
|
443 | 'args':[repr(i)]} | |
444 | d = self.executeAndRaise(msg, self.shell.getCommand, i) |
|
444 | d = self.executeAndRaise(msg, self.shell.getCommand, i) | |
445 | d.addCallback(self.addIDToResult) |
|
445 | d.addCallback(self.addIDToResult) | |
446 | return d |
|
446 | return d | |
447 |
|
447 | |||
448 | def reset(self): |
|
448 | def reset(self): | |
449 | msg = {'engineid':self.id, |
|
449 | msg = {'engineid':self.id, | |
450 | 'method':'reset', |
|
450 | 'method':'reset', | |
451 | 'args':[]} |
|
451 | 'args':[]} | |
452 | del self.shell |
|
452 | del self.shell | |
453 | self.shell = self.shellClass() |
|
453 | self.shell = self.shellClass() | |
454 | self.properties.clear() |
|
454 | self.properties.clear() | |
455 | d = self.executeAndRaise(msg, self._seedNamespace) |
|
455 | d = self.executeAndRaise(msg, self._seedNamespace) | |
456 | return d |
|
456 | return d | |
457 |
|
457 | |||
458 | def kill(self): |
|
458 | def kill(self): | |
459 | drop_engine(self.id) |
|
459 | drop_engine(self.id) | |
460 | try: |
|
460 | try: | |
461 | reactor.stop() |
|
461 | reactor.stop() | |
462 | except RuntimeError: |
|
462 | except RuntimeError: | |
463 | log.msg('The reactor was not running apparently.') |
|
463 | log.msg('The reactor was not running apparently.') | |
464 | return defer.fail() |
|
464 | return defer.fail() | |
465 | else: |
|
465 | else: | |
466 | return defer.succeed(None) |
|
466 | return defer.succeed(None) | |
467 |
|
467 | |||
468 | def keys(self): |
|
468 | def keys(self): | |
469 | """Return a list of variables names in the users top level namespace. |
|
469 | """Return a list of variables names in the users top level namespace. | |
470 |
|
470 | |||
471 | This used to return a dict of all the keys/repr(values) in the |
|
471 | This used to return a dict of all the keys/repr(values) in the | |
472 | user's namespace. This was too much info for the ControllerService |
|
472 | user's namespace. This was too much info for the ControllerService | |
473 | to handle so it is now just a list of keys. |
|
473 | to handle so it is now just a list of keys. | |
474 | """ |
|
474 | """ | |
475 |
|
475 | |||
476 | remotes = [] |
|
476 | remotes = [] | |
477 | for k in self.shell.user_ns.iterkeys(): |
|
477 | for k in self.shell.user_ns.iterkeys(): | |
478 | if k not in ['__name__', '_ih', '_oh', '__builtins__', |
|
478 | if k not in ['__name__', '_ih', '_oh', '__builtins__', | |
479 | 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']: |
|
479 | 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']: | |
480 | remotes.append(k) |
|
480 | remotes.append(k) | |
481 | return defer.succeed(remotes) |
|
481 | return defer.succeed(remotes) | |
482 |
|
482 | |||
483 | def set_properties(self, properties): |
|
483 | def set_properties(self, properties): | |
484 | msg = {'engineid':self.id, |
|
484 | msg = {'engineid':self.id, | |
485 | 'method':'set_properties', |
|
485 | 'method':'set_properties', | |
486 | 'args':[repr(properties.keys())]} |
|
486 | 'args':[repr(properties.keys())]} | |
487 | return self.executeAndRaise(msg, self.properties.update, properties) |
|
487 | return self.executeAndRaise(msg, self.properties.update, properties) | |
488 |
|
488 | |||
489 | def get_properties(self, keys=None): |
|
489 | def get_properties(self, keys=None): | |
490 | msg = {'engineid':self.id, |
|
490 | msg = {'engineid':self.id, | |
491 | 'method':'get_properties', |
|
491 | 'method':'get_properties', | |
492 | 'args':[repr(keys)]} |
|
492 | 'args':[repr(keys)]} | |
493 | if keys is None: |
|
493 | if keys is None: | |
494 | keys = self.properties.keys() |
|
494 | keys = self.properties.keys() | |
495 | return self.executeAndRaise(msg, self.properties.subDict, *keys) |
|
495 | return self.executeAndRaise(msg, self.properties.subDict, *keys) | |
496 |
|
496 | |||
497 | def _doDel(self, keys): |
|
497 | def _doDel(self, keys): | |
498 | for key in keys: |
|
498 | for key in keys: | |
499 | del self.properties[key] |
|
499 | del self.properties[key] | |
500 |
|
500 | |||
501 | def del_properties(self, keys): |
|
501 | def del_properties(self, keys): | |
502 | msg = {'engineid':self.id, |
|
502 | msg = {'engineid':self.id, | |
503 | 'method':'del_properties', |
|
503 | 'method':'del_properties', | |
504 | 'args':[repr(keys)]} |
|
504 | 'args':[repr(keys)]} | |
505 | return self.executeAndRaise(msg, self._doDel, keys) |
|
505 | return self.executeAndRaise(msg, self._doDel, keys) | |
506 |
|
506 | |||
507 | def _doHas(self, keys): |
|
507 | def _doHas(self, keys): | |
508 | return [self.properties.has_key(key) for key in keys] |
|
508 | return [self.properties.has_key(key) for key in keys] | |
509 |
|
509 | |||
510 | def has_properties(self, keys): |
|
510 | def has_properties(self, keys): | |
511 | msg = {'engineid':self.id, |
|
511 | msg = {'engineid':self.id, | |
512 | 'method':'has_properties', |
|
512 | 'method':'has_properties', | |
513 | 'args':[repr(keys)]} |
|
513 | 'args':[repr(keys)]} | |
514 | return self.executeAndRaise(msg, self._doHas, keys) |
|
514 | return self.executeAndRaise(msg, self._doHas, keys) | |
515 |
|
515 | |||
516 | def clear_properties(self): |
|
516 | def clear_properties(self): | |
517 | msg = {'engineid':self.id, |
|
517 | msg = {'engineid':self.id, | |
518 | 'method':'clear_properties', |
|
518 | 'method':'clear_properties', | |
519 | 'args':[]} |
|
519 | 'args':[]} | |
520 | return self.executeAndRaise(msg, self.properties.clear) |
|
520 | return self.executeAndRaise(msg, self.properties.clear) | |
521 |
|
521 | |||
522 | def push_serialized(self, sNamespace): |
|
522 | def push_serialized(self, sNamespace): | |
523 | msg = {'engineid':self.id, |
|
523 | msg = {'engineid':self.id, | |
524 | 'method':'push_serialized', |
|
524 | 'method':'push_serialized', | |
525 | 'args':[repr(sNamespace.keys())]} |
|
525 | 'args':[repr(sNamespace.keys())]} | |
526 | ns = {} |
|
526 | ns = {} | |
527 | for k,v in sNamespace.iteritems(): |
|
527 | for k,v in sNamespace.iteritems(): | |
528 | try: |
|
528 | try: | |
529 | unserialized = newserialized.IUnSerialized(v) |
|
529 | unserialized = newserialized.IUnSerialized(v) | |
530 | ns[k] = unserialized.getObject() |
|
530 | ns[k] = unserialized.getObject() | |
531 | except: |
|
531 | except: | |
532 | return defer.fail() |
|
532 | return defer.fail() | |
533 | return self.executeAndRaise(msg, self.shell.push, ns) |
|
533 | return self.executeAndRaise(msg, self.shell.push, ns) | |
534 |
|
534 | |||
535 | def pull_serialized(self, keys): |
|
535 | def pull_serialized(self, keys): | |
536 | msg = {'engineid':self.id, |
|
536 | msg = {'engineid':self.id, | |
537 | 'method':'pull_serialized', |
|
537 | 'method':'pull_serialized', | |
538 | 'args':[repr(keys)]} |
|
538 | 'args':[repr(keys)]} | |
539 | if isinstance(keys, str): |
|
539 | if isinstance(keys, str): | |
540 | keys = [keys] |
|
540 | keys = [keys] | |
541 | if len(keys)==1: |
|
541 | if len(keys)==1: | |
542 | d = self.executeAndRaise(msg, self.shell.pull, keys) |
|
542 | d = self.executeAndRaise(msg, self.shell.pull, keys) | |
543 | d.addCallback(newserialized.serialize) |
|
543 | d.addCallback(newserialized.serialize) | |
544 | return d |
|
544 | return d | |
545 | elif len(keys)>1: |
|
545 | elif len(keys)>1: | |
546 | d = self.executeAndRaise(msg, self.shell.pull, keys) |
|
546 | d = self.executeAndRaise(msg, self.shell.pull, keys) | |
547 | @d.addCallback |
|
547 | @d.addCallback | |
548 | def packThemUp(values): |
|
548 | def packThemUp(values): | |
549 | serials = [] |
|
549 | serials = [] | |
550 | for v in values: |
|
550 | for v in values: | |
551 | try: |
|
551 | try: | |
552 | serials.append(newserialized.serialize(v)) |
|
552 | serials.append(newserialized.serialize(v)) | |
553 | except: |
|
553 | except: | |
554 | return defer.fail(failure.Failure()) |
|
554 | return defer.fail(failure.Failure()) | |
555 | return serials |
|
555 | return serials | |
556 | return packThemUp |
|
556 | return packThemUp | |
557 |
|
557 | |||
558 |
|
558 | |||
559 | def queue(methodToQueue): |
|
559 | def queue(methodToQueue): | |
560 | def queuedMethod(this, *args, **kwargs): |
|
560 | def queuedMethod(this, *args, **kwargs): | |
561 | name = methodToQueue.__name__ |
|
561 | name = methodToQueue.__name__ | |
562 | return this.submitCommand(Command(name, *args, **kwargs)) |
|
562 | return this.submitCommand(Command(name, *args, **kwargs)) | |
563 | return queuedMethod |
|
563 | return queuedMethod | |
564 |
|
564 | |||
565 | class QueuedEngine(object): |
|
565 | class QueuedEngine(object): | |
566 | """Adapt an IEngineBase to an IEngineQueued by wrapping it. |
|
566 | """Adapt an IEngineBase to an IEngineQueued by wrapping it. | |
567 |
|
567 | |||
568 | The resulting object will implement IEngineQueued which extends |
|
568 | The resulting object will implement IEngineQueued which extends | |
569 | IEngineCore which extends (IEngineBase, IEngineSerialized). |
|
569 | IEngineCore which extends (IEngineBase, IEngineSerialized). | |
570 |
|
570 | |||
571 | This seems like the best way of handling it, but I am not sure. The |
|
571 | This seems like the best way of handling it, but I am not sure. The | |
572 | other option is to have the various base interfaces be used like |
|
572 | other option is to have the various base interfaces be used like | |
573 | mix-in intefaces. The problem I have with this is adpatation is |
|
573 | mix-in intefaces. The problem I have with this is adpatation is | |
574 | more difficult and complicated because there can be can multiple |
|
574 | more difficult and complicated because there can be can multiple | |
575 | original and final Interfaces. |
|
575 | original and final Interfaces. | |
576 | """ |
|
576 | """ | |
577 |
|
577 | |||
578 | zi.implements(IEngineQueued) |
|
578 | zi.implements(IEngineQueued) | |
579 |
|
579 | |||
580 | def __init__(self, engine): |
|
580 | def __init__(self, engine): | |
581 | """Create a QueuedEngine object from an engine |
|
581 | """Create a QueuedEngine object from an engine | |
582 |
|
582 | |||
583 | engine: An implementor of IEngineCore and IEngineSerialized |
|
583 | engine: An implementor of IEngineCore and IEngineSerialized | |
584 | keepUpToDate: whether to update the remote status when the |
|
584 | keepUpToDate: whether to update the remote status when the | |
585 | queue is empty. Defaults to False. |
|
585 | queue is empty. Defaults to False. | |
586 | """ |
|
586 | """ | |
587 |
|
587 | |||
588 | # This is the right way to do these tests rather than |
|
588 | # This is the right way to do these tests rather than | |
589 | # IEngineCore in list(zi.providedBy(engine)) which will only |
|
589 | # IEngineCore in list(zi.providedBy(engine)) which will only | |
590 | # picks of the interfaces that are directly declared by engine. |
|
590 | # picks of the interfaces that are directly declared by engine. | |
591 | assert IEngineBase.providedBy(engine), \ |
|
591 | assert IEngineBase.providedBy(engine), \ | |
592 | "engine passed to QueuedEngine doesn't provide IEngineBase" |
|
592 | "engine passed to QueuedEngine doesn't provide IEngineBase" | |
593 |
|
593 | |||
594 | self.engine = engine |
|
594 | self.engine = engine | |
595 | self.id = engine.id |
|
595 | self.id = engine.id | |
596 | self.queued = [] |
|
596 | self.queued = [] | |
597 | self.history = {} |
|
597 | self.history = {} | |
598 | self.engineStatus = {} |
|
598 | self.engineStatus = {} | |
599 | self.currentCommand = None |
|
599 | self.currentCommand = None | |
600 | self.failureObservers = [] |
|
600 | self.failureObservers = [] | |
601 |
|
601 | |||
602 | def _get_properties(self): |
|
602 | def _get_properties(self): | |
603 | return self.engine.properties |
|
603 | return self.engine.properties | |
604 |
|
604 | |||
605 | properties = property(_get_properties, lambda self, _: None) |
|
605 | properties = property(_get_properties, lambda self, _: None) | |
606 | # Queue management methods. You should not call these directly |
|
606 | # Queue management methods. You should not call these directly | |
607 |
|
607 | |||
608 | def submitCommand(self, cmd): |
|
608 | def submitCommand(self, cmd): | |
609 | """Submit command to queue.""" |
|
609 | """Submit command to queue.""" | |
610 |
|
610 | |||
611 | d = defer.Deferred() |
|
611 | d = defer.Deferred() | |
612 | cmd.setDeferred(d) |
|
612 | cmd.setDeferred(d) | |
613 | if self.currentCommand is not None: |
|
613 | if self.currentCommand is not None: | |
614 | if self.currentCommand.finished: |
|
614 | if self.currentCommand.finished: | |
615 | # log.msg("Running command immediately: %r" % cmd) |
|
615 | # log.msg("Running command immediately: %r" % cmd) | |
616 | self.currentCommand = cmd |
|
616 | self.currentCommand = cmd | |
617 | self.runCurrentCommand() |
|
617 | self.runCurrentCommand() | |
618 | else: # command is still running |
|
618 | else: # command is still running | |
619 | # log.msg("Command is running: %r" % self.currentCommand) |
|
619 | # log.msg("Command is running: %r" % self.currentCommand) | |
620 | # log.msg("Queueing: %r" % cmd) |
|
620 | # log.msg("Queueing: %r" % cmd) | |
621 | self.queued.append(cmd) |
|
621 | self.queued.append(cmd) | |
622 | else: |
|
622 | else: | |
623 | # log.msg("No current commands, running: %r" % cmd) |
|
623 | # log.msg("No current commands, running: %r" % cmd) | |
624 | self.currentCommand = cmd |
|
624 | self.currentCommand = cmd | |
625 | self.runCurrentCommand() |
|
625 | self.runCurrentCommand() | |
626 | return d |
|
626 | return d | |
627 |
|
627 | |||
628 | def runCurrentCommand(self): |
|
628 | def runCurrentCommand(self): | |
629 | """Run current command.""" |
|
629 | """Run current command.""" | |
630 |
|
630 | |||
631 | cmd = self.currentCommand |
|
631 | cmd = self.currentCommand | |
632 | f = getattr(self.engine, cmd.remoteMethod, None) |
|
632 | f = getattr(self.engine, cmd.remoteMethod, None) | |
633 | if f: |
|
633 | if f: | |
634 | d = f(*cmd.args, **cmd.kwargs) |
|
634 | d = f(*cmd.args, **cmd.kwargs) | |
635 | if cmd.remoteMethod is 'execute': |
|
635 | if cmd.remoteMethod is 'execute': | |
636 | d.addCallback(self.saveResult) |
|
636 | d.addCallback(self.saveResult) | |
637 | d.addCallback(self.finishCommand) |
|
637 | d.addCallback(self.finishCommand) | |
638 | d.addErrback(self.abortCommand) |
|
638 | d.addErrback(self.abortCommand) | |
639 | else: |
|
639 | else: | |
640 | return defer.fail(AttributeError(cmd.remoteMethod)) |
|
640 | return defer.fail(AttributeError(cmd.remoteMethod)) | |
641 |
|
641 | |||
642 | def _flushQueue(self): |
|
642 | def _flushQueue(self): | |
643 | """Pop next command in queue and run it.""" |
|
643 | """Pop next command in queue and run it.""" | |
644 |
|
644 | |||
645 | if len(self.queued) > 0: |
|
645 | if len(self.queued) > 0: | |
646 | self.currentCommand = self.queued.pop(0) |
|
646 | self.currentCommand = self.queued.pop(0) | |
647 | self.runCurrentCommand() |
|
647 | self.runCurrentCommand() | |
648 |
|
648 | |||
649 | def saveResult(self, result): |
|
649 | def saveResult(self, result): | |
650 | """Put the result in the history.""" |
|
650 | """Put the result in the history.""" | |
651 | self.history[result['number']] = result |
|
651 | self.history[result['number']] = result | |
652 | return result |
|
652 | return result | |
653 |
|
653 | |||
654 | def finishCommand(self, result): |
|
654 | def finishCommand(self, result): | |
655 | """Finish currrent command.""" |
|
655 | """Finish currrent command.""" | |
656 |
|
656 | |||
657 | # The order of these commands is absolutely critical. |
|
657 | # The order of these commands is absolutely critical. | |
658 | self.currentCommand.handleResult(result) |
|
658 | self.currentCommand.handleResult(result) | |
659 | self.currentCommand.finished = True |
|
659 | self.currentCommand.finished = True | |
660 | self._flushQueue() |
|
660 | self._flushQueue() | |
661 | return result |
|
661 | return result | |
662 |
|
662 | |||
663 | def abortCommand(self, reason): |
|
663 | def abortCommand(self, reason): | |
664 | """Abort current command. |
|
664 | """Abort current command. | |
665 |
|
665 | |||
666 | This eats the Failure but first passes it onto the Deferred that the |
|
666 | This eats the Failure but first passes it onto the Deferred that the | |
667 | user has. |
|
667 | user has. | |
668 |
|
668 | |||
669 | It also clear out the queue so subsequence commands don't run. |
|
669 | It also clear out the queue so subsequence commands don't run. | |
670 | """ |
|
670 | """ | |
671 |
|
671 | |||
672 | # The order of these 3 commands is absolutely critical. The currentCommand |
|
672 | # The order of these 3 commands is absolutely critical. The currentCommand | |
673 | # must first be marked as finished BEFORE the queue is cleared and before |
|
673 | # must first be marked as finished BEFORE the queue is cleared and before | |
674 | # the current command is sent the failure. |
|
674 | # the current command is sent the failure. | |
675 | # Also, the queue must be cleared BEFORE the current command is sent the Failure |
|
675 | # Also, the queue must be cleared BEFORE the current command is sent the Failure | |
676 | # otherwise the errback chain could trigger new commands to be added to the |
|
676 | # otherwise the errback chain could trigger new commands to be added to the | |
677 | # queue before we clear it. We should clear ONLY the commands that were in |
|
677 | # queue before we clear it. We should clear ONLY the commands that were in | |
678 | # the queue when the error occured. |
|
678 | # the queue when the error occured. | |
679 | self.currentCommand.finished = True |
|
679 | self.currentCommand.finished = True | |
680 | s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs) |
|
680 | s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs) | |
681 | self.clear_queue(msg=s) |
|
681 | self.clear_queue(msg=s) | |
682 | self.currentCommand.handleError(reason) |
|
682 | self.currentCommand.handleError(reason) | |
683 |
|
683 | |||
684 | return None |
|
684 | return None | |
685 |
|
685 | |||
686 | #--------------------------------------------------------------------------- |
|
686 | #--------------------------------------------------------------------------- | |
687 | # IEngineCore methods |
|
687 | # IEngineCore methods | |
688 | #--------------------------------------------------------------------------- |
|
688 | #--------------------------------------------------------------------------- | |
689 |
|
689 | |||
690 | @queue |
|
690 | @queue | |
691 | def execute(self, lines): |
|
691 | def execute(self, lines): | |
692 | pass |
|
692 | pass | |
693 |
|
693 | |||
694 | @queue |
|
694 | @queue | |
695 | def push(self, namespace): |
|
695 | def push(self, namespace): | |
696 | pass |
|
696 | pass | |
697 |
|
697 | |||
698 | @queue |
|
698 | @queue | |
699 | def pull(self, keys): |
|
699 | def pull(self, keys): | |
700 | pass |
|
700 | pass | |
701 |
|
701 | |||
702 | @queue |
|
702 | @queue | |
703 | def push_function(self, namespace): |
|
703 | def push_function(self, namespace): | |
704 | pass |
|
704 | pass | |
705 |
|
705 | |||
706 | @queue |
|
706 | @queue | |
707 | def pull_function(self, keys): |
|
707 | def pull_function(self, keys): | |
708 | pass |
|
708 | pass | |
709 |
|
709 | |||
710 | def get_result(self, i=None): |
|
710 | def get_result(self, i=None): | |
711 | if i is None: |
|
711 | if i is None: | |
712 | i = max(self.history.keys()+[None]) |
|
712 | i = max(self.history.keys()+[None]) | |
713 |
|
713 | |||
714 | cmd = self.history.get(i, None) |
|
714 | cmd = self.history.get(i, None) | |
715 | # Uncomment this line to disable chaching of results |
|
715 | # Uncomment this line to disable chaching of results | |
716 | #cmd = None |
|
716 | #cmd = None | |
717 | if cmd is None: |
|
717 | if cmd is None: | |
718 | return self.submitCommand(Command('get_result', i)) |
|
718 | return self.submitCommand(Command('get_result', i)) | |
719 | else: |
|
719 | else: | |
720 | return defer.succeed(cmd) |
|
720 | return defer.succeed(cmd) | |
721 |
|
721 | |||
722 | def reset(self): |
|
722 | def reset(self): | |
723 | self.clear_queue() |
|
723 | self.clear_queue() | |
724 | self.history = {} # reset the cache - I am not sure we should do this |
|
724 | self.history = {} # reset the cache - I am not sure we should do this | |
725 | return self.submitCommand(Command('reset')) |
|
725 | return self.submitCommand(Command('reset')) | |
726 |
|
726 | |||
727 | def kill(self): |
|
727 | def kill(self): | |
728 | self.clear_queue() |
|
728 | self.clear_queue() | |
729 | return self.submitCommand(Command('kill')) |
|
729 | return self.submitCommand(Command('kill')) | |
730 |
|
730 | |||
731 | @queue |
|
731 | @queue | |
732 | def keys(self): |
|
732 | def keys(self): | |
733 | pass |
|
733 | pass | |
734 |
|
734 | |||
735 | #--------------------------------------------------------------------------- |
|
735 | #--------------------------------------------------------------------------- | |
736 | # IEngineSerialized methods |
|
736 | # IEngineSerialized methods | |
737 | #--------------------------------------------------------------------------- |
|
737 | #--------------------------------------------------------------------------- | |
738 |
|
738 | |||
739 | @queue |
|
739 | @queue | |
740 | def push_serialized(self, namespace): |
|
740 | def push_serialized(self, namespace): | |
741 | pass |
|
741 | pass | |
742 |
|
742 | |||
743 | @queue |
|
743 | @queue | |
744 | def pull_serialized(self, keys): |
|
744 | def pull_serialized(self, keys): | |
745 | pass |
|
745 | pass | |
746 |
|
746 | |||
747 | #--------------------------------------------------------------------------- |
|
747 | #--------------------------------------------------------------------------- | |
748 | # IEngineProperties methods |
|
748 | # IEngineProperties methods | |
749 | #--------------------------------------------------------------------------- |
|
749 | #--------------------------------------------------------------------------- | |
750 |
|
750 | |||
751 | @queue |
|
751 | @queue | |
752 | def set_properties(self, namespace): |
|
752 | def set_properties(self, namespace): | |
753 | pass |
|
753 | pass | |
754 |
|
754 | |||
755 | @queue |
|
755 | @queue | |
756 | def get_properties(self, keys=None): |
|
756 | def get_properties(self, keys=None): | |
757 | pass |
|
757 | pass | |
758 |
|
758 | |||
759 | @queue |
|
759 | @queue | |
760 | def del_properties(self, keys): |
|
760 | def del_properties(self, keys): | |
761 | pass |
|
761 | pass | |
762 |
|
762 | |||
763 | @queue |
|
763 | @queue | |
764 | def has_properties(self, keys): |
|
764 | def has_properties(self, keys): | |
765 | pass |
|
765 | pass | |
766 |
|
766 | |||
767 | @queue |
|
767 | @queue | |
768 | def clear_properties(self): |
|
768 | def clear_properties(self): | |
769 | pass |
|
769 | pass | |
770 |
|
770 | |||
771 | #--------------------------------------------------------------------------- |
|
771 | #--------------------------------------------------------------------------- | |
772 | # IQueuedEngine methods |
|
772 | # IQueuedEngine methods | |
773 | #--------------------------------------------------------------------------- |
|
773 | #--------------------------------------------------------------------------- | |
774 |
|
774 | |||
775 | def clear_queue(self, msg=''): |
|
775 | def clear_queue(self, msg=''): | |
776 | """Clear the queue, but doesn't cancel the currently running commmand.""" |
|
776 | """Clear the queue, but doesn't cancel the currently running commmand.""" | |
777 |
|
777 | |||
778 | for cmd in self.queued: |
|
778 | for cmd in self.queued: | |
779 | cmd.deferred.errback(failure.Failure(error.QueueCleared(msg))) |
|
779 | cmd.deferred.errback(failure.Failure(error.QueueCleared(msg))) | |
780 | self.queued = [] |
|
780 | self.queued = [] | |
781 | return defer.succeed(None) |
|
781 | return defer.succeed(None) | |
782 |
|
782 | |||
783 | def queue_status(self): |
|
783 | def queue_status(self): | |
784 | if self.currentCommand is not None: |
|
784 | if self.currentCommand is not None: | |
785 | if self.currentCommand.finished: |
|
785 | if self.currentCommand.finished: | |
786 | pending = repr(None) |
|
786 | pending = repr(None) | |
787 | else: |
|
787 | else: | |
788 | pending = repr(self.currentCommand) |
|
788 | pending = repr(self.currentCommand) | |
789 | else: |
|
789 | else: | |
790 | pending = repr(None) |
|
790 | pending = repr(None) | |
791 | dikt = {'queue':map(repr,self.queued), 'pending':pending} |
|
791 | dikt = {'queue':map(repr,self.queued), 'pending':pending} | |
792 | return defer.succeed(dikt) |
|
792 | return defer.succeed(dikt) | |
793 |
|
793 | |||
794 | def register_failure_observer(self, obs): |
|
794 | def register_failure_observer(self, obs): | |
795 | self.failureObservers.append(obs) |
|
795 | self.failureObservers.append(obs) | |
796 |
|
796 | |||
797 | def unregister_failure_observer(self, obs): |
|
797 | def unregister_failure_observer(self, obs): | |
798 | self.failureObservers.remove(obs) |
|
798 | self.failureObservers.remove(obs) | |
799 |
|
799 | |||
800 |
|
800 | |||
801 | # Now register QueuedEngine as an adpater class that makes an IEngineBase into a |
|
801 | # Now register QueuedEngine as an adpater class that makes an IEngineBase into a | |
802 | # IEngineQueued. |
|
802 | # IEngineQueued. | |
803 | components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued) |
|
803 | components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued) | |
804 |
|
804 | |||
805 |
|
805 | |||
806 | class Command(object): |
|
806 | class Command(object): | |
807 | """A command object that encapslates queued commands. |
|
807 | """A command object that encapslates queued commands. | |
808 |
|
808 | |||
809 | This class basically keeps track of a command that has been queued |
|
809 | This class basically keeps track of a command that has been queued | |
810 | in a QueuedEngine. It manages the deferreds and hold the method to be called |
|
810 | in a QueuedEngine. It manages the deferreds and hold the method to be called | |
811 | and the arguments to that method. |
|
811 | and the arguments to that method. | |
812 | """ |
|
812 | """ | |
813 |
|
813 | |||
814 |
|
814 | |||
815 | def __init__(self, remoteMethod, *args, **kwargs): |
|
815 | def __init__(self, remoteMethod, *args, **kwargs): | |
816 | """Build a new Command object.""" |
|
816 | """Build a new Command object.""" | |
817 |
|
817 | |||
818 | self.remoteMethod = remoteMethod |
|
818 | self.remoteMethod = remoteMethod | |
819 | self.args = args |
|
819 | self.args = args | |
820 | self.kwargs = kwargs |
|
820 | self.kwargs = kwargs | |
821 | self.finished = False |
|
821 | self.finished = False | |
822 |
|
822 | |||
823 | def setDeferred(self, d): |
|
823 | def setDeferred(self, d): | |
824 | """Sets the deferred attribute of the Command.""" |
|
824 | """Sets the deferred attribute of the Command.""" | |
825 |
|
825 | |||
826 | self.deferred = d |
|
826 | self.deferred = d | |
827 |
|
827 | |||
828 | def __repr__(self): |
|
828 | def __repr__(self): | |
829 | if not self.args: |
|
829 | if not self.args: | |
830 | args = '' |
|
830 | args = '' | |
831 | else: |
|
831 | else: | |
832 | args = str(self.args)[1:-2] #cut off (...,) |
|
832 | args = str(self.args)[1:-2] #cut off (...,) | |
833 | for k,v in self.kwargs.iteritems(): |
|
833 | for k,v in self.kwargs.iteritems(): | |
834 | if args: |
|
834 | if args: | |
835 | args += ', ' |
|
835 | args += ', ' | |
836 | args += '%s=%r' %(k,v) |
|
836 | args += '%s=%r' %(k,v) | |
837 | return "%s(%s)" %(self.remoteMethod, args) |
|
837 | return "%s(%s)" %(self.remoteMethod, args) | |
838 |
|
838 | |||
839 | def handleResult(self, result): |
|
839 | def handleResult(self, result): | |
840 | """When the result is ready, relay it to self.deferred.""" |
|
840 | """When the result is ready, relay it to self.deferred.""" | |
841 |
|
841 | |||
842 | self.deferred.callback(result) |
|
842 | self.deferred.callback(result) | |
843 |
|
843 | |||
844 | def handleError(self, reason): |
|
844 | def handleError(self, reason): | |
845 | """When an error has occured, relay it to self.deferred.""" |
|
845 | """When an error has occured, relay it to self.deferred.""" | |
846 |
|
846 | |||
847 | self.deferred.errback(reason) |
|
847 | self.deferred.errback(reason) | |
848 |
|
848 | |||
849 | class ThreadedEngineService(EngineService): |
|
849 | class ThreadedEngineService(EngineService): | |
850 |
"""An EngineService subclass that defers execute commands to a separate |
|
850 | """An EngineService subclass that defers execute commands to a separate | |
|
851 | thread. | |||
851 |
|
852 | |||
852 |
ThreadedEngineService uses twisted.internet.threads.deferToThread to |
|
853 | ThreadedEngineService uses twisted.internet.threads.deferToThread to | |
853 |
requests to a separate thread. GUI frontends may want to |
|
854 | defer execute requests to a separate thread. GUI frontends may want to | |
854 | the engine in an IPython.frontend.frontendbase.FrontEndBase subclass to prevent |
|
855 | use ThreadedEngineService as the engine in an | |
|
856 | IPython.frontend.frontendbase.FrontEndBase subclass to prevent | |||
855 | block execution from blocking the GUI thread. |
|
857 | block execution from blocking the GUI thread. | |
856 | """ |
|
858 | """ | |
857 |
|
859 | |||
858 | zi.implements(IEngineBase) |
|
860 | zi.implements(IEngineBase) | |
859 |
|
861 | |||
860 | def __init__(self, shellClass=Interpreter, mpi=None): |
|
862 | def __init__(self, shellClass=Interpreter, mpi=None): | |
861 | EngineService.__init__(self, shellClass, mpi) |
|
863 | EngineService.__init__(self, shellClass, mpi) | |
862 |
|
864 | |||
863 |
|
865 | |||
864 | def execute(self, lines): |
|
866 | def execute(self, lines): | |
865 | # Only import this if we are going to use this class |
|
867 | # Only import this if we are going to use this class | |
866 | from twisted.internet import threads |
|
868 | from twisted.internet import threads | |
867 |
|
869 | |||
868 | msg = {'engineid':self.id, |
|
870 | msg = {'engineid':self.id, | |
869 | 'method':'execute', |
|
871 | 'method':'execute', | |
870 | 'args':[lines]} |
|
872 | 'args':[lines]} | |
871 |
|
873 | |||
872 | d = threads.deferToThread(self.shell.execute, lines) |
|
874 | d = threads.deferToThread(self.shell.execute, lines) | |
873 | d.addCallback(self.addIDToResult) |
|
875 | d.addCallback(self.addIDToResult) | |
874 | return d |
|
876 | return d |
1 | NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog |
|
NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog |
@@ -1,161 +1,162 b'' | |||||
1 | .. _changes: |
|
1 | .. _changes: | |
2 |
|
2 | |||
3 | ========== |
|
3 | ========== | |
4 | What's new |
|
4 | What's new | |
5 | ========== |
|
5 | ========== | |
6 |
|
6 | |||
7 | .. contents:: |
|
7 | .. contents:: | |
8 |
|
8 | |||
9 | Release 0.9 |
|
9 | Release 0.9 | |
10 | =========== |
|
10 | =========== | |
11 |
|
11 | |||
12 | New features |
|
12 | New features | |
13 | ------------ |
|
13 | ------------ | |
14 |
|
14 | |||
15 | * All of the parallel computing capabilities from `ipython1-dev` have been merged into |
|
15 | * All of the parallel computing capabilities from `ipython1-dev` have been merged into | |
16 | IPython proper. This resulted in the following new subpackages: |
|
16 | IPython proper. This resulted in the following new subpackages: | |
17 | :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`IPython.config`, |
|
17 | :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`IPython.config`, | |
18 | :mod:`IPython.tools` and :mod:`IPython.testing`. |
|
18 | :mod:`IPython.tools` and :mod:`IPython.testing`. | |
19 | * As part of merging in the `ipython1-dev` stuff, the `setup.py` script and friends |
|
19 | * As part of merging in the `ipython1-dev` stuff, the `setup.py` script and friends | |
20 | have been completely refactored. Now we are checking for dependencies using |
|
20 | have been completely refactored. Now we are checking for dependencies using | |
21 | the approach that matplotlib uses. |
|
21 | the approach that matplotlib uses. | |
22 | * The documentation has been completely reorganized to accept the documentation |
|
22 | * The documentation has been completely reorganized to accept the documentation | |
23 | from `ipython1-dev`. |
|
23 | from `ipython1-dev`. | |
24 | * We have switched to using Foolscap for all of our network protocols in |
|
24 | * We have switched to using Foolscap for all of our network protocols in | |
25 | :mod:`IPython.kernel`. This gives us secure connections that are both encrypted |
|
25 | :mod:`IPython.kernel`. This gives us secure connections that are both encrypted | |
26 | and authenticated. |
|
26 | and authenticated. | |
27 | * We have a brand new `COPYING.txt` files that describes the IPython license |
|
27 | * We have a brand new `COPYING.txt` files that describes the IPython license | |
28 | and copyright. The biggest change is that we are putting "The IPython |
|
28 | and copyright. The biggest change is that we are putting "The IPython | |
29 | Development Team" as the copyright holder. We give more details about exactly |
|
29 | Development Team" as the copyright holder. We give more details about exactly | |
30 | what this means in this file. All developer should read this and use the new |
|
30 | what this means in this file. All developer should read this and use the new | |
31 | banner in all IPython source code files. |
|
31 | banner in all IPython source code files. | |
|
32 | * sh profile: ./foo runs foo as system command, no need to do !./foo anymore | |||
32 |
|
33 | |||
33 | Bug fixes |
|
34 | Bug fixes | |
34 | --------- |
|
35 | --------- | |
35 |
|
36 | |||
36 | * A few subpackages has missing `__init__.py` files. |
|
37 | * A few subpackages has missing `__init__.py` files. | |
37 | * The documentation is only created is Sphinx is found. Previously, the `setup.py` |
|
38 | * The documentation is only created is Sphinx is found. Previously, the `setup.py` | |
38 | script would fail if it was missing. |
|
39 | script would fail if it was missing. | |
39 |
|
40 | |||
40 | Backwards incompatible changes |
|
41 | Backwards incompatible changes | |
41 | ------------------------------ |
|
42 | ------------------------------ | |
42 |
|
43 | |||
43 | * IPython has a larger set of dependencies if you want all of its capabilities. |
|
44 | * IPython has a larger set of dependencies if you want all of its capabilities. | |
44 | See the `setup.py` script for details. |
|
45 | See the `setup.py` script for details. | |
45 | * The constructors for :class:`IPython.kernel.client.MultiEngineClient` and |
|
46 | * The constructors for :class:`IPython.kernel.client.MultiEngineClient` and | |
46 | :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple. |
|
47 | :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple. | |
47 | Instead they take the filename of a file that contains the FURL for that |
|
48 | Instead they take the filename of a file that contains the FURL for that | |
48 | client. If the FURL file is in your IPYTHONDIR, it will be found automatically |
|
49 | client. If the FURL file is in your IPYTHONDIR, it will be found automatically | |
49 | and the constructor can be left empty. |
|
50 | and the constructor can be left empty. | |
50 | * The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created |
|
51 | * The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created | |
51 | using the factory functions :func:`get_multiengine_client` and |
|
52 | using the factory functions :func:`get_multiengine_client` and | |
52 | :func:`get_task_client`. These return a `Deferred` to the actual client. |
|
53 | :func:`get_task_client`. These return a `Deferred` to the actual client. | |
53 | * The command line options to `ipcontroller` and `ipengine` have changed to |
|
54 | * The command line options to `ipcontroller` and `ipengine` have changed to | |
54 | reflect the new Foolscap network protocol and the FURL files. Please see the |
|
55 | reflect the new Foolscap network protocol and the FURL files. Please see the | |
55 | help for these scripts for details. |
|
56 | help for these scripts for details. | |
56 | * The configuration files for the kernel have changed because of the Foolscap stuff. |
|
57 | * The configuration files for the kernel have changed because of the Foolscap stuff. | |
57 | If you were using custom config files before, you should delete them and regenerate |
|
58 | If you were using custom config files before, you should delete them and regenerate | |
58 | new ones. |
|
59 | new ones. | |
59 |
|
60 | |||
60 | Changes merged in from IPython1 |
|
61 | Changes merged in from IPython1 | |
61 | ------------------------------- |
|
62 | ------------------------------- | |
62 |
|
63 | |||
63 | New features |
|
64 | New features | |
64 | ............ |
|
65 | ............ | |
65 |
|
66 | |||
66 | * Much improved ``setup.py`` and ``setupegg.py`` scripts. Because Twisted |
|
67 | * Much improved ``setup.py`` and ``setupegg.py`` scripts. Because Twisted | |
67 | and zope.interface are now easy installable, we can declare them as dependencies |
|
68 | and zope.interface are now easy installable, we can declare them as dependencies | |
68 | in our setupegg.py script. |
|
69 | in our setupegg.py script. | |
69 | * IPython is now compatible with Twisted 2.5.0 and 8.x. |
|
70 | * IPython is now compatible with Twisted 2.5.0 and 8.x. | |
70 | * Added a new example of how to use :mod:`ipython1.kernel.asynclient`. |
|
71 | * Added a new example of how to use :mod:`ipython1.kernel.asynclient`. | |
71 | * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not |
|
72 | * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not | |
72 | been merged into IPython and is still in `ipython1-dev`. |
|
73 | been merged into IPython and is still in `ipython1-dev`. | |
73 | * The ``TaskController`` now has methods for getting the queue status. |
|
74 | * The ``TaskController`` now has methods for getting the queue status. | |
74 | * The ``TaskResult`` objects not have information about how long the task |
|
75 | * The ``TaskResult`` objects not have information about how long the task | |
75 | took to run. |
|
76 | took to run. | |
76 | * We are attaching additional attributes to exceptions ``(_ipython_*)`` that |
|
77 | * We are attaching additional attributes to exceptions ``(_ipython_*)`` that | |
77 | we use to carry additional info around. |
|
78 | we use to carry additional info around. | |
78 | * New top-level module :mod:`asyncclient` that has asynchronous versions (that |
|
79 | * New top-level module :mod:`asyncclient` that has asynchronous versions (that | |
79 | return deferreds) of the client classes. This is designed to users who want |
|
80 | return deferreds) of the client classes. This is designed to users who want | |
80 | to run their own Twisted reactor |
|
81 | to run their own Twisted reactor | |
81 | * All the clients in :mod:`client` are now based on Twisted. This is done by |
|
82 | * All the clients in :mod:`client` are now based on Twisted. This is done by | |
82 | running the Twisted reactor in a separate thread and using the |
|
83 | running the Twisted reactor in a separate thread and using the | |
83 | :func:`blockingCallFromThread` function that is in recent versions of Twisted. |
|
84 | :func:`blockingCallFromThread` function that is in recent versions of Twisted. | |
84 | * Functions can now be pushed/pulled to/from engines using |
|
85 | * Functions can now be pushed/pulled to/from engines using | |
85 | :meth:`MultiEngineClient.push_function` and :meth:`MultiEngineClient.pull_function`. |
|
86 | :meth:`MultiEngineClient.push_function` and :meth:`MultiEngineClient.pull_function`. | |
86 | * Gather/scatter are now implemented in the client to reduce the work load |
|
87 | * Gather/scatter are now implemented in the client to reduce the work load | |
87 | of the controller and improve performance. |
|
88 | of the controller and improve performance. | |
88 | * Complete rewrite of the IPython docuementation. All of the documentation |
|
89 | * Complete rewrite of the IPython docuementation. All of the documentation | |
89 | from the IPython website has been moved into docs/source as restructured |
|
90 | from the IPython website has been moved into docs/source as restructured | |
90 | text documents. PDF and HTML documentation are being generated using |
|
91 | text documents. PDF and HTML documentation are being generated using | |
91 | Sphinx. |
|
92 | Sphinx. | |
92 | * New developer oriented documentation: development guidelines and roadmap. |
|
93 | * New developer oriented documentation: development guidelines and roadmap. | |
93 | * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt`` file |
|
94 | * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt`` file | |
94 | that is organized by release and is meant to provide something more relevant |
|
95 | that is organized by release and is meant to provide something more relevant | |
95 | for users. |
|
96 | for users. | |
96 |
|
97 | |||
97 | Bug fixes |
|
98 | Bug fixes | |
98 | ......... |
|
99 | ......... | |
99 |
|
100 | |||
100 | * Created a proper ``MANIFEST.in`` file to create source distributions. |
|
101 | * Created a proper ``MANIFEST.in`` file to create source distributions. | |
101 | * Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine |
|
102 | * Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine | |
102 | actions were being collected with a :class:`DeferredList` with |
|
103 | actions were being collected with a :class:`DeferredList` with | |
103 | ``fireononeerrback=1``. This meant that methods were returning |
|
104 | ``fireononeerrback=1``. This meant that methods were returning | |
104 | before all engines had given their results. This was causing extremely odd |
|
105 | before all engines had given their results. This was causing extremely odd | |
105 | bugs in certain cases. To fix this problem, we have 1) set |
|
106 | bugs in certain cases. To fix this problem, we have 1) set | |
106 | ``fireononeerrback=0`` to make sure all results (or exceptions) are in |
|
107 | ``fireononeerrback=0`` to make sure all results (or exceptions) are in | |
107 | before returning and 2) introduced a :exc:`CompositeError` exception |
|
108 | before returning and 2) introduced a :exc:`CompositeError` exception | |
108 | that wraps all of the engine exceptions. This is a huge change as it means |
|
109 | that wraps all of the engine exceptions. This is a huge change as it means | |
109 | that users will have to catch :exc:`CompositeError` rather than the actual |
|
110 | that users will have to catch :exc:`CompositeError` rather than the actual | |
110 | exception. |
|
111 | exception. | |
111 |
|
112 | |||
112 | Backwards incompatible changes |
|
113 | Backwards incompatible changes | |
113 | .............................. |
|
114 | .............................. | |
114 |
|
115 | |||
115 | * All names have been renamed to conform to the lowercase_with_underscore |
|
116 | * All names have been renamed to conform to the lowercase_with_underscore | |
116 | convention. This will require users to change references to all names like |
|
117 | convention. This will require users to change references to all names like | |
117 | ``queueStatus`` to ``queue_status``. |
|
118 | ``queueStatus`` to ``queue_status``. | |
118 | * Previously, methods like :meth:`MultiEngineClient.push` and |
|
119 | * Previously, methods like :meth:`MultiEngineClient.push` and | |
119 | :meth:`MultiEngineClient.push` used ``*args`` and ``**kwargs``. This was |
|
120 | :meth:`MultiEngineClient.push` used ``*args`` and ``**kwargs``. This was | |
120 | becoming a problem as we weren't able to introduce new keyword arguments into |
|
121 | becoming a problem as we weren't able to introduce new keyword arguments into | |
121 | the API. Now these methods simple take a dict or sequence. This has also allowed |
|
122 | the API. Now these methods simple take a dict or sequence. This has also allowed | |
122 | us to get rid of the ``*All`` methods like :meth:`pushAll` and :meth:`pullAll`. |
|
123 | us to get rid of the ``*All`` methods like :meth:`pushAll` and :meth:`pullAll`. | |
123 | These things are now handled with the ``targets`` keyword argument that defaults |
|
124 | These things are now handled with the ``targets`` keyword argument that defaults | |
124 | to ``'all'``. |
|
125 | to ``'all'``. | |
125 | * The :attr:`MultiEngineClient.magicTargets` has been renamed to |
|
126 | * The :attr:`MultiEngineClient.magicTargets` has been renamed to | |
126 | :attr:`MultiEngineClient.targets`. |
|
127 | :attr:`MultiEngineClient.targets`. | |
127 | * All methods in the MultiEngine interface now accept the optional keyword argument |
|
128 | * All methods in the MultiEngine interface now accept the optional keyword argument | |
128 | ``block``. |
|
129 | ``block``. | |
129 | * Renamed :class:`RemoteController` to :class:`MultiEngineClient` and |
|
130 | * Renamed :class:`RemoteController` to :class:`MultiEngineClient` and | |
130 | :class:`TaskController` to :class:`TaskClient`. |
|
131 | :class:`TaskController` to :class:`TaskClient`. | |
131 | * Renamed the top-level module from :mod:`api` to :mod:`client`. |
|
132 | * Renamed the top-level module from :mod:`api` to :mod:`client`. | |
132 | * Most methods in the multiengine interface now raise a :exc:`CompositeError` exception |
|
133 | * Most methods in the multiengine interface now raise a :exc:`CompositeError` exception | |
133 | that wraps the user's exceptions, rather than just raising the raw user's exception. |
|
134 | that wraps the user's exceptions, rather than just raising the raw user's exception. | |
134 | * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push`` |
|
135 | * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push`` | |
135 | and ``pull``. |
|
136 | and ``pull``. | |
136 |
|
137 | |||
137 | Release 0.8.4 |
|
138 | Release 0.8.4 | |
138 | ============= |
|
139 | ============= | |
139 |
|
140 | |||
140 | Someone needs to describe what went into 0.8.4. |
|
141 | Someone needs to describe what went into 0.8.4. | |
141 |
|
142 | |||
142 | Release 0.8.2 |
|
143 | Release 0.8.2 | |
143 | ============= |
|
144 | ============= | |
144 |
|
145 | |||
145 | * %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory |
|
146 | * %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory | |
146 | and jumps to /foo. The current behaviour is closer to the documented |
|
147 | and jumps to /foo. The current behaviour is closer to the documented | |
147 | behaviour, and should not trip anyone. |
|
148 | behaviour, and should not trip anyone. | |
148 |
|
149 | |||
149 | Release 0.8.3 |
|
150 | Release 0.8.3 | |
150 | ============= |
|
151 | ============= | |
151 |
|
152 | |||
152 | * pydb is now disabled by default (due to %run -d problems). You can enable |
|
153 | * pydb is now disabled by default (due to %run -d problems). You can enable | |
153 | it by passing -pydb command line argument to IPython. Note that setting |
|
154 | it by passing -pydb command line argument to IPython. Note that setting | |
154 | it in config file won't work. |
|
155 | it in config file won't work. | |
155 |
|
156 | |||
156 | Older releases |
|
157 | Older releases | |
157 | ============== |
|
158 | ============== | |
158 |
|
159 | |||
159 | Changes in earlier releases of IPython are described in the older file ``ChangeLog``. |
|
160 | Changes in earlier releases of IPython are described in the older file ``ChangeLog``. | |
160 | Please refer to this document for details. |
|
161 | Please refer to this document for details. | |
161 |
|
162 |
@@ -1,315 +1,360 b'' | |||||
1 | .. _development: |
|
1 | .. _development: | |
2 |
|
2 | |||
3 | ================================== |
|
3 | ================================== | |
4 | IPython development guidelines |
|
4 | IPython development guidelines | |
5 | ================================== |
|
5 | ================================== | |
6 |
|
6 | |||
7 | .. contents:: |
|
7 | .. contents:: | |
8 | .. |
|
|||
9 | 1 Overview |
|
|||
10 | 2 Project organization |
|
|||
11 | 2.1 Subpackages |
|
|||
12 | 2.2 Installation and dependencies |
|
|||
13 | 2.3 Specific subpackages |
|
|||
14 | 3 Version control |
|
|||
15 | 4 Documentation |
|
|||
16 | 4.1 Standalone documentation |
|
|||
17 | 4.2 Docstring format |
|
|||
18 | 5 Coding conventions |
|
|||
19 | 5.1 General |
|
|||
20 | 5.2 Naming conventions |
|
|||
21 | 6 Testing |
|
|||
22 | 7 Configuration |
|
|||
23 | .. |
|
|||
24 |
|
8 | |||
25 |
|
9 | |||
26 | Overview |
|
10 | Overview | |
27 | ======== |
|
11 | ======== | |
28 |
|
12 | |||
29 | IPython is the next generation of IPython. It is named such for two reasons: |
|
13 | IPython is the next generation of IPython. It is named such for two reasons: | |
30 |
|
14 | |||
31 | - Eventually, IPython will become IPython version 1.0. |
|
15 | - Eventually, IPython will become IPython version 1.0. | |
32 | - This new code base needs to be able to co-exist with the existing IPython until |
|
16 | - This new code base needs to be able to co-exist with the existing IPython until | |
33 | it is a full replacement for it. Thus we needed a different name. We couldn't |
|
17 | it is a full replacement for it. Thus we needed a different name. We couldn't | |
34 | use ``ipython`` (lowercase) as some files systems are case insensitive. |
|
18 | use ``ipython`` (lowercase) as some files systems are case insensitive. | |
35 |
|
19 | |||
36 | There are two, no three, main goals of the IPython effort: |
|
20 | There are two, no three, main goals of the IPython effort: | |
37 |
|
21 | |||
38 | 1. Clean up the existing codebase and write lots of tests. |
|
22 | 1. Clean up the existing codebase and write lots of tests. | |
39 | 2. Separate the core functionality of IPython from the terminal to enable IPython |
|
23 | 2. Separate the core functionality of IPython from the terminal to enable IPython | |
40 | to be used from within a variety of GUI applications. |
|
24 | to be used from within a variety of GUI applications. | |
41 | 3. Implement a system for interactive parallel computing. |
|
25 | 3. Implement a system for interactive parallel computing. | |
42 |
|
26 | |||
43 | While the third goal may seem a bit unrelated to the main focus of IPython, it turns |
|
27 | While the third goal may seem a bit unrelated to the main focus of IPython, it turns | |
44 | out that the technologies required for this goal are nearly identical with those |
|
28 | out that the technologies required for this goal are nearly identical with those | |
45 | required for goal two. This is the main reason the interactive parallel computing |
|
29 | required for goal two. This is the main reason the interactive parallel computing | |
46 | capabilities are being put into IPython proper. Currently the third of these goals is |
|
30 | capabilities are being put into IPython proper. Currently the third of these goals is | |
47 | furthest along. |
|
31 | furthest along. | |
48 |
|
32 | |||
49 | This document describes IPython from the perspective of developers. |
|
33 | This document describes IPython from the perspective of developers. | |
50 |
|
34 | |||
51 |
|
35 | |||
52 | Project organization |
|
36 | Project organization | |
53 | ==================== |
|
37 | ==================== | |
54 |
|
38 | |||
55 | Subpackages |
|
39 | Subpackages | |
56 | ----------- |
|
40 | ----------- | |
57 |
|
41 | |||
58 | IPython is organized into semi self-contained subpackages. Each of the subpackages will have its own: |
|
42 | IPython is organized into semi self-contained subpackages. Each of the subpackages will have its own: | |
59 |
|
43 | |||
60 | - **Dependencies**. One of the most important things to keep in mind in |
|
44 | - **Dependencies**. One of the most important things to keep in mind in | |
61 | partitioning code amongst subpackages, is that they should be used to cleanly |
|
45 | partitioning code amongst subpackages, is that they should be used to cleanly | |
62 | encapsulate dependencies. |
|
46 | encapsulate dependencies. | |
63 | - **Tests**. Each subpackage shoud have its own ``tests`` subdirectory that |
|
47 | - **Tests**. Each subpackage shoud have its own ``tests`` subdirectory that | |
64 | contains all of the tests for that package. For information about writing tests |
|
48 | contains all of the tests for that package. For information about writing tests | |
65 | for IPython, see the `Testing System`_ section of this document. |
|
49 | for IPython, see the `Testing System`_ section of this document. | |
66 | - **Configuration**. Each subpackage should have its own ``config`` subdirectory |
|
50 | - **Configuration**. Each subpackage should have its own ``config`` subdirectory | |
67 | that contains the configuration information for the components of the |
|
51 | that contains the configuration information for the components of the | |
68 | subpackage. For information about how the IPython configuration system |
|
52 | subpackage. For information about how the IPython configuration system | |
69 | works, see the `Configuration System`_ section of this document. |
|
53 | works, see the `Configuration System`_ section of this document. | |
70 | - **Scripts**. Each subpackage should have its own ``scripts`` subdirectory that |
|
54 | - **Scripts**. Each subpackage should have its own ``scripts`` subdirectory that | |
71 | contains all of the command line scripts associated with the subpackage. |
|
55 | contains all of the command line scripts associated with the subpackage. | |
72 |
|
56 | |||
73 | Installation and dependencies |
|
57 | Installation and dependencies | |
74 | ----------------------------- |
|
58 | ----------------------------- | |
75 |
|
59 | |||
76 | IPython will not use `setuptools`_ for installation. Instead, we will use standard |
|
60 | IPython will not use `setuptools`_ for installation. Instead, we will use standard | |
77 | ``setup.py`` scripts that use `distutils`_. While there are a number a extremely nice |
|
61 | ``setup.py`` scripts that use `distutils`_. While there are a number a extremely nice | |
78 | features that `setuptools`_ has (like namespace packages), the current implementation |
|
62 | features that `setuptools`_ has (like namespace packages), the current implementation | |
79 | of `setuptools`_ has performance problems, particularly on shared file systems. In |
|
63 | of `setuptools`_ has performance problems, particularly on shared file systems. In | |
80 | particular, when Python packages are installed on NSF file systems, import times |
|
64 | particular, when Python packages are installed on NSF file systems, import times | |
81 | become much too long (up towards 10 seconds). |
|
65 | become much too long (up towards 10 seconds). | |
82 |
|
66 | |||
83 | Because IPython is being used extensively in the context of high performance |
|
67 | Because IPython is being used extensively in the context of high performance | |
84 | computing, where performance is critical but shared file systems are common, we feel |
|
68 | computing, where performance is critical but shared file systems are common, we feel | |
85 | these performance hits are not acceptable. Thus, until the performance problems |
|
69 | these performance hits are not acceptable. Thus, until the performance problems | |
86 | associated with `setuptools`_ are addressed, we will stick with plain `distutils`_. We |
|
70 | associated with `setuptools`_ are addressed, we will stick with plain `distutils`_. We | |
87 | are hopeful that these problems will be addressed and that we will eventually begin |
|
71 | are hopeful that these problems will be addressed and that we will eventually begin | |
88 | using `setuptools`_. Because of this, we are trying to organize IPython in a way that |
|
72 | using `setuptools`_. Because of this, we are trying to organize IPython in a way that | |
89 | will make the eventual transition to `setuptools`_ as painless as possible. |
|
73 | will make the eventual transition to `setuptools`_ as painless as possible. | |
90 |
|
74 | |||
91 | Because we will be using `distutils`_, there will be no method for automatically installing dependencies. Instead, we are following the approach of `Matplotlib`_ which can be summarized as follows: |
|
75 | Because we will be using `distutils`_, there will be no method for automatically installing dependencies. Instead, we are following the approach of `Matplotlib`_ which can be summarized as follows: | |
92 |
|
76 | |||
93 | - Distinguish between required and optional dependencies. However, the required |
|
77 | - Distinguish between required and optional dependencies. However, the required | |
94 | dependencies for IPython should be only the Python standard library. |
|
78 | dependencies for IPython should be only the Python standard library. | |
95 | - Upon installation check to see which optional dependencies are present and tell |
|
79 | - Upon installation check to see which optional dependencies are present and tell | |
96 | the user which parts of IPython need which optional dependencies. |
|
80 | the user which parts of IPython need which optional dependencies. | |
97 |
|
81 | |||
98 | It is absolutely critical that each subpackage of IPython has a clearly specified set |
|
82 | It is absolutely critical that each subpackage of IPython has a clearly specified set | |
99 | of dependencies and that dependencies are not carelessly inherited from other IPython |
|
83 | of dependencies and that dependencies are not carelessly inherited from other IPython | |
100 | subpackages. Furthermore, tests that have certain dependencies should not fail if |
|
84 | subpackages. Furthermore, tests that have certain dependencies should not fail if | |
101 | those dependencies are not present. Instead they should be skipped and print a |
|
85 | those dependencies are not present. Instead they should be skipped and print a | |
102 | message. |
|
86 | message. | |
103 |
|
87 | |||
104 | .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools |
|
88 | .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools | |
105 | .. _distutils: http://docs.python.org/lib/module-distutils.html |
|
89 | .. _distutils: http://docs.python.org/lib/module-distutils.html | |
106 | .. _Matplotlib: http://matplotlib.sourceforge.net/ |
|
90 | .. _Matplotlib: http://matplotlib.sourceforge.net/ | |
107 |
|
91 | |||
108 | Specific subpackages |
|
92 | Specific subpackages | |
109 | -------------------- |
|
93 | -------------------- | |
110 |
|
94 | |||
111 | ``core`` |
|
95 | ``core`` | |
112 | This is the core functionality of IPython that is independent of the |
|
96 | This is the core functionality of IPython that is independent of the | |
113 | terminal, network and GUIs. Most of the code that is in the current |
|
97 | terminal, network and GUIs. Most of the code that is in the current | |
114 | IPython trunk will be refactored, cleaned up and moved here. |
|
98 | IPython trunk will be refactored, cleaned up and moved here. | |
115 |
|
99 | |||
116 | ``kernel`` |
|
100 | ``kernel`` | |
117 | The enables the IPython core to be expose to a the network. This is |
|
101 | The enables the IPython core to be expose to a the network. This is | |
118 | also where all of the parallel computing capabilities are to be found. |
|
102 | also where all of the parallel computing capabilities are to be found. | |
119 |
|
103 | |||
120 | ``config`` |
|
104 | ``config`` | |
121 | The configuration package used by IPython. |
|
105 | The configuration package used by IPython. | |
122 |
|
106 | |||
123 | ``frontends`` |
|
107 | ``frontends`` | |
124 | The various frontends for IPython. A frontend is the end-user application |
|
108 | The various frontends for IPython. A frontend is the end-user application | |
125 | that exposes the capabilities of IPython to the user. The most basic frontend |
|
109 | that exposes the capabilities of IPython to the user. The most basic frontend | |
126 | will simply be a terminal based application that looks just like today 's |
|
110 | will simply be a terminal based application that looks just like today 's | |
127 | IPython. Other frontends will likely be more powerful and based on GUI toolkits. |
|
111 | IPython. Other frontends will likely be more powerful and based on GUI toolkits. | |
128 |
|
112 | |||
129 | ``notebook`` |
|
113 | ``notebook`` | |
130 | An application that allows users to work with IPython notebooks. |
|
114 | An application that allows users to work with IPython notebooks. | |
131 |
|
115 | |||
132 | ``tools`` |
|
116 | ``tools`` | |
133 | This is where general utilities go. |
|
117 | This is where general utilities go. | |
134 |
|
118 | |||
135 |
|
119 | |||
136 | Version control |
|
120 | Version control | |
137 | =============== |
|
121 | =============== | |
138 |
|
122 | |||
139 |
In the past, IPython development has been done using `Subversion`__. |
|
123 | In the past, IPython development has been done using `Subversion`__. Recently, we made the transition to using `Bazaar`__ and `Launchpad`__. This makes it much easier for people | |
|
124 | to contribute code to IPython. Here is a sketch of how to use Bazaar for IPython | |||
|
125 | development. First, you should install Bazaar. After you have done that, make | |||
|
126 | sure that it is working by getting the latest main branch of IPython:: | |||
|
127 | ||||
|
128 | $ bzr branch lp:ipython | |||
|
129 | ||||
|
130 | Now you can create a new branch for you to do your work in:: | |||
|
131 | ||||
|
132 | $ bzr branch ipython ipython-mybranch | |||
|
133 | ||||
|
134 | The typical work cycle in this branch will be to make changes in `ipython-mybranch` | |||
|
135 | and then commit those changes using the commit command:: | |||
|
136 | ||||
|
137 | $ ...do work in ipython-mybranch... | |||
|
138 | $ bzr ci -m "the commit message goes here" | |||
|
139 | ||||
|
140 | Please note that since we now don't use an old-style linear ChangeLog | |||
|
141 | (that tends to cause problems with distributed version control | |||
|
142 | systems), you should ensure that your log messages are reasonably | |||
|
143 | detailed. Use a docstring-like approach in the commit messages | |||
|
144 | (including the second line being left *blank*):: | |||
|
145 | ||||
|
146 | Single line summary of changes being committed. | |||
|
147 | ||||
|
148 | - more details when warranted ... | |||
|
149 | - including crediting outside contributors if they sent the | |||
|
150 | code/bug/idea! | |||
|
151 | ||||
|
152 | If we couple this with a policy of making single commits for each | |||
|
153 | reasonably atomic change, the bzr log should give an excellent view of | |||
|
154 | the project, and the `--short` log option becomes a nice summary. | |||
|
155 | ||||
|
156 | While working with this branch, it is a good idea to merge in changes that have been | |||
|
157 | made upstream in the parent branch. This can be done by doing:: | |||
|
158 | ||||
|
159 | $ bzr pull | |||
|
160 | ||||
|
161 | If this command shows that the branches have diverged, then you should do a merge | |||
|
162 | instead:: | |||
|
163 | ||||
|
164 | $ bzr merge lp:ipython | |||
|
165 | ||||
|
166 | If you want others to be able to see your branch, you can create an account with | |||
|
167 | launchpad and push the branch to your own workspace:: | |||
|
168 | ||||
|
169 | $ bzr push bzr+ssh://<me>@bazaar.launchpad.net/~<me>/+junk/ipython-mybranch | |||
|
170 | ||||
|
171 | Finally, once the work in your branch is done, you can merge your changes back into | |||
|
172 | the `ipython` branch by using merge:: | |||
|
173 | ||||
|
174 | $ cd ipython | |||
|
175 | $ merge ../ipython-mybranch | |||
|
176 | [resolve any conflicts] | |||
|
177 | $ bzr ci -m "Fixing that bug" | |||
|
178 | $ bzr push | |||
|
179 | ||||
|
180 | But this will require you to have write permissions to the `ipython` branch. It you don't | |||
|
181 | you can tell one of the IPython devs about your branch and they can do the merge for you. | |||
|
182 | ||||
|
183 | More information about Bazaar workflows can be found `here`__. | |||
140 |
|
184 | |||
141 | .. __: http://subversion.tigris.org/ |
|
185 | .. __: http://subversion.tigris.org/ | |
142 | .. __: http://bazaar-vcs.org/ |
|
186 | .. __: http://bazaar-vcs.org/ | |
143 | .. __: http://www.launchpad.net/ipython |
|
187 | .. __: http://www.launchpad.net/ipython | |
|
188 | .. __: http://doc.bazaar-vcs.org/bzr.dev/en/user-guide/index.html | |||
144 |
|
189 | |||
145 | Documentation |
|
190 | Documentation | |
146 | ============= |
|
191 | ============= | |
147 |
|
192 | |||
148 | Standalone documentation |
|
193 | Standalone documentation | |
149 | ------------------------ |
|
194 | ------------------------ | |
150 |
|
195 | |||
151 | All standalone documentation should be written in plain text (``.txt``) files using |
|
196 | All standalone documentation should be written in plain text (``.txt``) files using | |
152 | `reStructuredText`_ for markup and formatting. All such documentation should be placed |
|
197 | `reStructuredText`_ for markup and formatting. All such documentation should be placed | |
153 | in the top level directory ``docs`` of the IPython source tree. Or, when appropriate, |
|
198 | in the top level directory ``docs`` of the IPython source tree. Or, when appropriate, | |
154 | a suitably named subdirectory should be used. The documentation in this location will |
|
199 | a suitably named subdirectory should be used. The documentation in this location will | |
155 | serve as the main source for IPython documentation and all existing documentation |
|
200 | serve as the main source for IPython documentation and all existing documentation | |
156 | should be converted to this format. |
|
201 | should be converted to this format. | |
157 |
|
202 | |||
158 | In the future, the text files in the ``docs`` directory will be used to generate all |
|
203 | In the future, the text files in the ``docs`` directory will be used to generate all | |
159 | forms of documentation for IPython. This include documentation on the IPython website |
|
204 | forms of documentation for IPython. This include documentation on the IPython website | |
160 | as well as *pdf* documentation. |
|
205 | as well as *pdf* documentation. | |
161 |
|
206 | |||
162 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html |
|
207 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html | |
163 |
|
208 | |||
164 | Docstring format |
|
209 | Docstring format | |
165 | ---------------- |
|
210 | ---------------- | |
166 |
|
211 | |||
167 | Good docstrings are very important. All new code will use `Epydoc`_ for generating API |
|
212 | Good docstrings are very important. All new code will use `Epydoc`_ for generating API | |
168 | docs, so we will follow the `Epydoc`_ conventions. More specifically, we will use |
|
213 | docs, so we will follow the `Epydoc`_ conventions. More specifically, we will use | |
169 | `reStructuredText`_ for markup and formatting, since it is understood by a wide |
|
214 | `reStructuredText`_ for markup and formatting, since it is understood by a wide | |
170 | variety of tools. This means that if in the future we have any reason to change from |
|
215 | variety of tools. This means that if in the future we have any reason to change from | |
171 | `Epydoc`_ to something else, we'll have fewer transition pains. |
|
216 | `Epydoc`_ to something else, we'll have fewer transition pains. | |
172 |
|
217 | |||
173 | Details about using `reStructuredText`_ for docstrings can be found `here |
|
218 | Details about using `reStructuredText`_ for docstrings can be found `here | |
174 | <http://epydoc.sourceforge.net/manual-othermarkup.html>`_. |
|
219 | <http://epydoc.sourceforge.net/manual-othermarkup.html>`_. | |
175 |
|
220 | |||
176 | .. _Epydoc: http://epydoc.sourceforge.net/ |
|
221 | .. _Epydoc: http://epydoc.sourceforge.net/ | |
177 |
|
222 | |||
178 | Additional PEPs of interest regarding documentation of code: |
|
223 | Additional PEPs of interest regarding documentation of code: | |
179 |
|
224 | |||
180 | - `Docstring Conventions <http://www.python.org/peps/pep-0257.html>`_ |
|
225 | - `Docstring Conventions <http://www.python.org/peps/pep-0257.html>`_ | |
181 | - `Docstring Processing System Framework <http://www.python.org/peps/pep-0256.html>`_ |
|
226 | - `Docstring Processing System Framework <http://www.python.org/peps/pep-0256.html>`_ | |
182 | - `Docutils Design Specification <http://www.python.org/peps/pep-0258.html>`_ |
|
227 | - `Docutils Design Specification <http://www.python.org/peps/pep-0258.html>`_ | |
183 |
|
228 | |||
184 |
|
229 | |||
185 | Coding conventions |
|
230 | Coding conventions | |
186 | ================== |
|
231 | ================== | |
187 |
|
232 | |||
188 | General |
|
233 | General | |
189 | ------- |
|
234 | ------- | |
190 |
|
235 | |||
191 | In general, we'll try to follow the standard Python style conventions as described here: |
|
236 | In general, we'll try to follow the standard Python style conventions as described here: | |
192 |
|
237 | |||
193 | - `Style Guide for Python Code <http://www.python.org/peps/pep-0008.html>`_ |
|
238 | - `Style Guide for Python Code <http://www.python.org/peps/pep-0008.html>`_ | |
194 |
|
239 | |||
195 |
|
240 | |||
196 | Other comments: |
|
241 | Other comments: | |
197 |
|
242 | |||
198 | - In a large file, top level classes and functions should be |
|
243 | - In a large file, top level classes and functions should be | |
199 | separated by 2-3 lines to make it easier to separate them visually. |
|
244 | separated by 2-3 lines to make it easier to separate them visually. | |
200 | - Use 4 spaces for indentation. |
|
245 | - Use 4 spaces for indentation. | |
201 | - Keep the ordering of methods the same in classes that have the same |
|
246 | - Keep the ordering of methods the same in classes that have the same | |
202 | methods. This is particularly true for classes that implement |
|
247 | methods. This is particularly true for classes that implement | |
203 | similar interfaces and for interfaces that are similar. |
|
248 | similar interfaces and for interfaces that are similar. | |
204 |
|
249 | |||
205 | Naming conventions |
|
250 | Naming conventions | |
206 | ------------------ |
|
251 | ------------------ | |
207 |
|
252 | |||
208 | In terms of naming conventions, we'll follow the guidelines from the `Style Guide for |
|
253 | In terms of naming conventions, we'll follow the guidelines from the `Style Guide for | |
209 | Python Code`_. |
|
254 | Python Code`_. | |
210 |
|
255 | |||
211 | For all new IPython code (and much existing code is being refactored), we'll use: |
|
256 | For all new IPython code (and much existing code is being refactored), we'll use: | |
212 |
|
257 | |||
213 | - All ``lowercase`` module names. |
|
258 | - All ``lowercase`` module names. | |
214 |
|
259 | |||
215 | - ``CamelCase`` for class names. |
|
260 | - ``CamelCase`` for class names. | |
216 |
|
261 | |||
217 | - ``lowercase_with_underscores`` for methods, functions, variables and attributes. |
|
262 | - ``lowercase_with_underscores`` for methods, functions, variables and attributes. | |
218 |
|
263 | |||
219 | This may be confusing as most of the existing IPython codebase uses a different convention (``lowerCamelCase`` for methods and attributes). Slowly, we will move IPython over to the new |
|
264 | This may be confusing as most of the existing IPython codebase uses a different convention (``lowerCamelCase`` for methods and attributes). Slowly, we will move IPython over to the new | |
220 | convention, providing shadow names for backward compatibility in public interfaces. |
|
265 | convention, providing shadow names for backward compatibility in public interfaces. | |
221 |
|
266 | |||
222 | There are, however, some important exceptions to these rules. In some cases, IPython |
|
267 | There are, however, some important exceptions to these rules. In some cases, IPython | |
223 | code will interface with packages (Twisted, Wx, Qt) that use other conventions. At some level this makes it impossible to adhere to our own standards at all times. In particular, when subclassing classes that use other naming conventions, you must follow their naming conventions. To deal with cases like this, we propose the following policy: |
|
268 | code will interface with packages (Twisted, Wx, Qt) that use other conventions. At some level this makes it impossible to adhere to our own standards at all times. In particular, when subclassing classes that use other naming conventions, you must follow their naming conventions. To deal with cases like this, we propose the following policy: | |
224 |
|
269 | |||
225 | - If you are subclassing a class that uses different conventions, use its |
|
270 | - If you are subclassing a class that uses different conventions, use its | |
226 | naming conventions throughout your subclass. Thus, if you are creating a |
|
271 | naming conventions throughout your subclass. Thus, if you are creating a | |
227 | Twisted Protocol class, used Twisted's ``namingSchemeForMethodsAndAttributes.`` |
|
272 | Twisted Protocol class, used Twisted's ``namingSchemeForMethodsAndAttributes.`` | |
228 |
|
273 | |||
229 | - All IPython's official interfaces should use our conventions. In some cases |
|
274 | - All IPython's official interfaces should use our conventions. In some cases | |
230 | this will mean that you need to provide shadow names (first implement ``fooBar`` |
|
275 | this will mean that you need to provide shadow names (first implement ``fooBar`` | |
231 | and then ``foo_bar = fooBar``). We want to avoid this at all costs, but it |
|
276 | and then ``foo_bar = fooBar``). We want to avoid this at all costs, but it | |
232 | will probably be necessary at times. But, please use this sparingly! |
|
277 | will probably be necessary at times. But, please use this sparingly! | |
233 |
|
278 | |||
234 | Implementation-specific *private* methods will use ``_single_underscore_prefix``. |
|
279 | Implementation-specific *private* methods will use ``_single_underscore_prefix``. | |
235 | Names with a leading double underscore will *only* be used in special cases, as they |
|
280 | Names with a leading double underscore will *only* be used in special cases, as they | |
236 | makes subclassing difficult (such names are not easily seen by child classes). |
|
281 | makes subclassing difficult (such names are not easily seen by child classes). | |
237 |
|
282 | |||
238 | Occasionally some run-in lowercase names are used, but mostly for very short names or |
|
283 | Occasionally some run-in lowercase names are used, but mostly for very short names or | |
239 | where we are implementing methods very similar to existing ones in a base class (like |
|
284 | where we are implementing methods very similar to existing ones in a base class (like | |
240 | ``runlines()`` where ``runsource()`` and ``runcode()`` had established precedent). |
|
285 | ``runlines()`` where ``runsource()`` and ``runcode()`` had established precedent). | |
241 |
|
286 | |||
242 | The old IPython codebase has a big mix of classes and modules prefixed with an |
|
287 | The old IPython codebase has a big mix of classes and modules prefixed with an | |
243 | explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned upon, as |
|
288 | explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned upon, as | |
244 | namespaces offer cleaner prefixing. The only case where this approach is justified is |
|
289 | namespaces offer cleaner prefixing. The only case where this approach is justified is | |
245 | for classes which are expected to be imported into external namespaces and a very |
|
290 | for classes which are expected to be imported into external namespaces and a very | |
246 | generic name (like Shell) is too likely to clash with something else. We'll need to |
|
291 | generic name (like Shell) is too likely to clash with something else. We'll need to | |
247 | revisit this issue as we clean up and refactor the code, but in general we should |
|
292 | revisit this issue as we clean up and refactor the code, but in general we should | |
248 | remove as many unnecessary ``IP``/``ip`` prefixes as possible. However, if a prefix |
|
293 | remove as many unnecessary ``IP``/``ip`` prefixes as possible. However, if a prefix | |
249 | seems absolutely necessary the more specific ``IPY`` or ``ipy`` are preferred. |
|
294 | seems absolutely necessary the more specific ``IPY`` or ``ipy`` are preferred. | |
250 |
|
295 | |||
251 | .. _devel_testing: |
|
296 | .. _devel_testing: | |
252 |
|
297 | |||
253 | Testing system |
|
298 | Testing system | |
254 | ============== |
|
299 | ============== | |
255 |
|
300 | |||
256 | It is extremely important that all code contributed to IPython has tests. Tests should |
|
301 | It is extremely important that all code contributed to IPython has tests. Tests should | |
257 | be written as unittests, doctests or as entities that the `Nose`_ testing package will |
|
302 | be written as unittests, doctests or as entities that the `Nose`_ testing package will | |
258 | find. Regardless of how the tests are written, we will use `Nose`_ for discovering and |
|
303 | find. Regardless of how the tests are written, we will use `Nose`_ for discovering and | |
259 | running the tests. `Nose`_ will be required to run the IPython test suite, but will |
|
304 | running the tests. `Nose`_ will be required to run the IPython test suite, but will | |
260 | not be required to simply use IPython. |
|
305 | not be required to simply use IPython. | |
261 |
|
306 | |||
262 | .. _Nose: http://code.google.com/p/python-nose/ |
|
307 | .. _Nose: http://code.google.com/p/python-nose/ | |
263 |
|
308 | |||
264 | Tests of `Twisted`__ using code should be written by subclassing the ``TestCase`` class |
|
309 | Tests of `Twisted`__ using code should be written by subclassing the ``TestCase`` class | |
265 | that comes with ``twisted.trial.unittest``. When this is done, `Nose`_ will be able to |
|
310 | that comes with ``twisted.trial.unittest``. When this is done, `Nose`_ will be able to | |
266 | run the tests and the twisted reactor will be handled correctly. |
|
311 | run the tests and the twisted reactor will be handled correctly. | |
267 |
|
312 | |||
268 | .. __: http://www.twistedmatrix.com |
|
313 | .. __: http://www.twistedmatrix.com | |
269 |
|
314 | |||
270 | Each subpackage in IPython should have its own ``tests`` directory that contains all |
|
315 | Each subpackage in IPython should have its own ``tests`` directory that contains all | |
271 | of the tests for that subpackage. This allows each subpackage to be self-contained. If |
|
316 | of the tests for that subpackage. This allows each subpackage to be self-contained. If | |
272 | a subpackage has any dependencies beyond the Python standard library, the tests for |
|
317 | a subpackage has any dependencies beyond the Python standard library, the tests for | |
273 | that subpackage should be skipped if the dependencies are not found. This is very |
|
318 | that subpackage should be skipped if the dependencies are not found. This is very | |
274 | important so users don't get tests failing simply because they don't have dependencies. |
|
319 | important so users don't get tests failing simply because they don't have dependencies. | |
275 |
|
320 | |||
276 | We also need to look into use Noses ability to tag tests to allow a more modular |
|
321 | We also need to look into use Noses ability to tag tests to allow a more modular | |
277 | approach of running tests. |
|
322 | approach of running tests. | |
278 |
|
323 | |||
279 | .. _devel_config: |
|
324 | .. _devel_config: | |
280 |
|
325 | |||
281 | Configuration system |
|
326 | Configuration system | |
282 | ==================== |
|
327 | ==================== | |
283 |
|
328 | |||
284 | IPython uses `.ini`_ files for configuration purposes. This represents a huge |
|
329 | IPython uses `.ini`_ files for configuration purposes. This represents a huge | |
285 | improvement over the configuration system used in IPython. IPython works with these |
|
330 | improvement over the configuration system used in IPython. IPython works with these | |
286 | files using the `ConfigObj`_ package, which IPython includes as |
|
331 | files using the `ConfigObj`_ package, which IPython includes as | |
287 | ``ipython1/external/configobj.py``. |
|
332 | ``ipython1/external/configobj.py``. | |
288 |
|
333 | |||
289 | Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of IPython |
|
334 | Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of IPython | |
290 | should contain a ``config`` subdirectory that contains all of the configuration |
|
335 | should contain a ``config`` subdirectory that contains all of the configuration | |
291 | information for the subpackage. To see how configuration information is defined (along |
|
336 | information for the subpackage. To see how configuration information is defined (along | |
292 | with defaults) see at the examples in ``ipython1/kernel/config`` and |
|
337 | with defaults) see at the examples in ``ipython1/kernel/config`` and | |
293 | ``ipython1/core/config``. Likewise, to see how the configuration information is used, |
|
338 | ``ipython1/core/config``. Likewise, to see how the configuration information is used, | |
294 | see examples in ``ipython1/kernel/scripts/ipengine.py``. |
|
339 | see examples in ``ipython1/kernel/scripts/ipengine.py``. | |
295 |
|
340 | |||
296 | Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We are |
|
341 | Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We are | |
297 | calling this new layer, ``tconfig``, as it will use a `Traits`_-like validation model. |
|
342 | calling this new layer, ``tconfig``, as it will use a `Traits`_-like validation model. | |
298 | We won't actually use `Traits`_, but will implement something similar in pure Python. |
|
343 | We won't actually use `Traits`_, but will implement something similar in pure Python. | |
299 | But, even in this new system, we will still use `ConfigObj`_ and `.ini`_ files |
|
344 | But, even in this new system, we will still use `ConfigObj`_ and `.ini`_ files | |
300 | underneath the hood. Talk to Fernando if you are interested in working on this part of |
|
345 | underneath the hood. Talk to Fernando if you are interested in working on this part of | |
301 | IPython. The current prototype of ``tconfig`` is located in the IPython sandbox. |
|
346 | IPython. The current prototype of ``tconfig`` is located in the IPython sandbox. | |
302 |
|
347 | |||
303 | .. _.ini: http://docs.python.org/lib/module-ConfigParser.html |
|
348 | .. _.ini: http://docs.python.org/lib/module-ConfigParser.html | |
304 | .. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html |
|
349 | .. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html | |
305 | .. _Traits: http://code.enthought.com/traits/ |
|
350 | .. _Traits: http://code.enthought.com/traits/ | |
306 |
|
351 | |||
307 |
|
352 | |||
308 |
|
353 | |||
309 |
|
354 | |||
310 |
|
355 | |||
311 |
|
356 | |||
312 |
|
357 | |||
313 |
|
358 | |||
314 |
|
359 | |||
315 |
|
360 |
@@ -1,226 +1,228 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 |
|
2 | |||
3 | """ |
|
3 | """ | |
4 | This module defines the things that are used in setup.py for building IPython |
|
4 | This module defines the things that are used in setup.py for building IPython | |
5 |
|
5 | |||
6 | This includes: |
|
6 | This includes: | |
7 |
|
7 | |||
8 | * The basic arguments to setup |
|
8 | * The basic arguments to setup | |
9 | * Functions for finding things like packages, package data, etc. |
|
9 | * Functions for finding things like packages, package data, etc. | |
10 | * A function for checking dependencies. |
|
10 | * A function for checking dependencies. | |
11 | """ |
|
11 | """ | |
12 |
|
12 | |||
13 | __docformat__ = "restructuredtext en" |
|
13 | __docformat__ = "restructuredtext en" | |
14 |
|
14 | |||
15 | #------------------------------------------------------------------------------- |
|
15 | #------------------------------------------------------------------------------- | |
16 | # Copyright (C) 2008 The IPython Development Team |
|
16 | # Copyright (C) 2008 The IPython Development Team | |
17 | # |
|
17 | # | |
18 | # Distributed under the terms of the BSD License. The full license is in |
|
18 | # Distributed under the terms of the BSD License. The full license is in | |
19 | # the file COPYING, distributed as part of this software. |
|
19 | # the file COPYING, distributed as part of this software. | |
20 | #------------------------------------------------------------------------------- |
|
20 | #------------------------------------------------------------------------------- | |
21 |
|
21 | |||
22 | #------------------------------------------------------------------------------- |
|
22 | #------------------------------------------------------------------------------- | |
23 | # Imports |
|
23 | # Imports | |
24 | #------------------------------------------------------------------------------- |
|
24 | #------------------------------------------------------------------------------- | |
25 |
|
25 | |||
26 | import os, sys |
|
26 | import os, sys | |
27 |
|
27 | |||
28 | from glob import glob |
|
28 | from glob import glob | |
29 |
|
29 | |||
30 | from setupext import install_data_ext |
|
30 | from setupext import install_data_ext | |
31 |
|
31 | |||
32 | #------------------------------------------------------------------------------- |
|
32 | #------------------------------------------------------------------------------- | |
33 | # Useful globals and utility functions |
|
33 | # Useful globals and utility functions | |
34 | #------------------------------------------------------------------------------- |
|
34 | #------------------------------------------------------------------------------- | |
35 |
|
35 | |||
36 | # A few handy globals |
|
36 | # A few handy globals | |
37 | isfile = os.path.isfile |
|
37 | isfile = os.path.isfile | |
38 | pjoin = os.path.join |
|
38 | pjoin = os.path.join | |
39 |
|
39 | |||
40 | def oscmd(s): |
|
40 | def oscmd(s): | |
41 | print ">", s |
|
41 | print ">", s | |
42 | os.system(s) |
|
42 | os.system(s) | |
43 |
|
43 | |||
44 | # A little utility we'll need below, since glob() does NOT allow you to do |
|
44 | # A little utility we'll need below, since glob() does NOT allow you to do | |
45 | # exclusion on multiple endings! |
|
45 | # exclusion on multiple endings! | |
46 | def file_doesnt_endwith(test,endings): |
|
46 | def file_doesnt_endwith(test,endings): | |
47 | """Return true if test is a file and its name does NOT end with any |
|
47 | """Return true if test is a file and its name does NOT end with any | |
48 | of the strings listed in endings.""" |
|
48 | of the strings listed in endings.""" | |
49 | if not isfile(test): |
|
49 | if not isfile(test): | |
50 | return False |
|
50 | return False | |
51 | for e in endings: |
|
51 | for e in endings: | |
52 | if test.endswith(e): |
|
52 | if test.endswith(e): | |
53 | return False |
|
53 | return False | |
54 | return True |
|
54 | return True | |
55 |
|
55 | |||
56 | #--------------------------------------------------------------------------- |
|
56 | #--------------------------------------------------------------------------- | |
57 | # Basic project information |
|
57 | # Basic project information | |
58 | #--------------------------------------------------------------------------- |
|
58 | #--------------------------------------------------------------------------- | |
59 |
|
59 | |||
60 | # Release.py contains version, authors, license, url, keywords, etc. |
|
60 | # Release.py contains version, authors, license, url, keywords, etc. | |
61 | execfile(pjoin('IPython','Release.py')) |
|
61 | execfile(pjoin('IPython','Release.py')) | |
62 |
|
62 | |||
63 | # Create a dict with the basic information |
|
63 | # Create a dict with the basic information | |
64 | # This dict is eventually passed to setup after additional keys are added. |
|
64 | # This dict is eventually passed to setup after additional keys are added. | |
65 | setup_args = dict( |
|
65 | setup_args = dict( | |
66 | name = name, |
|
66 | name = name, | |
67 | version = version, |
|
67 | version = version, | |
68 | description = description, |
|
68 | description = description, | |
69 | long_description = long_description, |
|
69 | long_description = long_description, | |
70 | author = author, |
|
70 | author = author, | |
71 | author_email = author_email, |
|
71 | author_email = author_email, | |
72 | url = url, |
|
72 | url = url, | |
73 | download_url = download_url, |
|
73 | download_url = download_url, | |
74 | license = license, |
|
74 | license = license, | |
75 | platforms = platforms, |
|
75 | platforms = platforms, | |
76 | keywords = keywords, |
|
76 | keywords = keywords, | |
77 | cmdclass = {'install_data': install_data_ext}, |
|
77 | cmdclass = {'install_data': install_data_ext}, | |
78 | ) |
|
78 | ) | |
79 |
|
79 | |||
80 |
|
80 | |||
81 | #--------------------------------------------------------------------------- |
|
81 | #--------------------------------------------------------------------------- | |
82 | # Find packages |
|
82 | # Find packages | |
83 | #--------------------------------------------------------------------------- |
|
83 | #--------------------------------------------------------------------------- | |
84 |
|
84 | |||
85 | def add_package(packages, pname, config=False, tests=False, scripts=False, others=None): |
|
85 | def add_package(packages, pname, config=False, tests=False, scripts=False, others=None): | |
86 | """ |
|
86 | """ | |
87 | Add a package to the list of packages, including certain subpackages. |
|
87 | Add a package to the list of packages, including certain subpackages. | |
88 | """ |
|
88 | """ | |
89 | packages.append('.'.join(['IPython',pname])) |
|
89 | packages.append('.'.join(['IPython',pname])) | |
90 | if config: |
|
90 | if config: | |
91 | packages.append('.'.join(['IPython',pname,'config'])) |
|
91 | packages.append('.'.join(['IPython',pname,'config'])) | |
92 | if tests: |
|
92 | if tests: | |
93 | packages.append('.'.join(['IPython',pname,'tests'])) |
|
93 | packages.append('.'.join(['IPython',pname,'tests'])) | |
94 | if scripts: |
|
94 | if scripts: | |
95 | packages.append('.'.join(['IPython',pname,'scripts'])) |
|
95 | packages.append('.'.join(['IPython',pname,'scripts'])) | |
96 | if others is not None: |
|
96 | if others is not None: | |
97 | for o in others: |
|
97 | for o in others: | |
98 | packages.append('.'.join(['IPython',pname,o])) |
|
98 | packages.append('.'.join(['IPython',pname,o])) | |
99 |
|
99 | |||
100 | def find_packages(): |
|
100 | def find_packages(): | |
101 | """ |
|
101 | """ | |
102 | Find all of IPython's packages. |
|
102 | Find all of IPython's packages. | |
103 | """ |
|
103 | """ | |
104 | packages = ['IPython'] |
|
104 | packages = ['IPython'] | |
105 | add_package(packages, 'config', tests=True) |
|
105 | add_package(packages, 'config', tests=True) | |
106 | add_package(packages , 'Extensions') |
|
106 | add_package(packages , 'Extensions') | |
107 | add_package(packages, 'external') |
|
107 | add_package(packages, 'external') | |
108 | add_package(packages, 'gui') |
|
108 | add_package(packages, 'gui') | |
109 | add_package(packages, 'gui.wx') |
|
109 | add_package(packages, 'gui.wx') | |
110 | add_package(packages, 'kernel', config=True, tests=True, scripts=True) |
|
110 | add_package(packages, 'kernel', config=True, tests=True, scripts=True) | |
111 | add_package(packages, 'kernel.core', config=True, tests=True) |
|
111 | add_package(packages, 'kernel.core', config=True, tests=True) | |
112 | add_package(packages, 'testing', tests=True) |
|
112 | add_package(packages, 'testing', tests=True) | |
113 | add_package(packages, 'tools', tests=True) |
|
113 | add_package(packages, 'tools', tests=True) | |
114 | add_package(packages, 'UserConfig') |
|
114 | add_package(packages, 'UserConfig') | |
115 | return packages |
|
115 | return packages | |
116 |
|
116 | |||
117 | #--------------------------------------------------------------------------- |
|
117 | #--------------------------------------------------------------------------- | |
118 | # Find package data |
|
118 | # Find package data | |
119 | #--------------------------------------------------------------------------- |
|
119 | #--------------------------------------------------------------------------- | |
120 |
|
120 | |||
121 | def find_package_data(): |
|
121 | def find_package_data(): | |
122 | """ |
|
122 | """ | |
123 | Find IPython's package_data. |
|
123 | Find IPython's package_data. | |
124 | """ |
|
124 | """ | |
125 | # This is not enough for these things to appear in an sdist. |
|
125 | # This is not enough for these things to appear in an sdist. | |
126 | # We need to muck with the MANIFEST to get this to work |
|
126 | # We need to muck with the MANIFEST to get this to work | |
127 | package_data = {'IPython.UserConfig' : ['*'] } |
|
127 | package_data = {'IPython.UserConfig' : ['*'] } | |
128 | return package_data |
|
128 | return package_data | |
129 |
|
129 | |||
130 |
|
130 | |||
131 | #--------------------------------------------------------------------------- |
|
131 | #--------------------------------------------------------------------------- | |
132 | # Find data files |
|
132 | # Find data files | |
133 | #--------------------------------------------------------------------------- |
|
133 | #--------------------------------------------------------------------------- | |
134 |
|
134 | |||
135 | def find_data_files(): |
|
135 | def find_data_files(): | |
136 | """ |
|
136 | """ | |
137 | Find IPython's data_files. |
|
137 | Find IPython's data_files. | |
138 | """ |
|
138 | """ | |
139 |
|
139 | |||
140 | # I can't find how to make distutils create a nested dir. structure, so |
|
140 | # I can't find how to make distutils create a nested dir. structure, so | |
141 | # in the meantime do it manually. Butt ugly. |
|
141 | # in the meantime do it manually. Butt ugly. | |
142 | # Note that http://www.redbrick.dcu.ie/~noel/distutils.html, ex. 2/3, contain |
|
142 | # Note that http://www.redbrick.dcu.ie/~noel/distutils.html, ex. 2/3, contain | |
143 | # information on how to do this more cleanly once python 2.4 can be assumed. |
|
143 | # information on how to do this more cleanly once python 2.4 can be assumed. | |
144 | # Thanks to Noel for the tip. |
|
144 | # Thanks to Noel for the tip. | |
145 | docdirbase = 'share/doc/ipython' |
|
145 | docdirbase = 'share/doc/ipython' | |
146 | manpagebase = 'share/man/man1' |
|
146 | manpagebase = 'share/man/man1' | |
147 |
|
147 | |||
148 | # We only need to exclude from this things NOT already excluded in the |
|
148 | # We only need to exclude from this things NOT already excluded in the | |
149 | # MANIFEST.in file. |
|
149 | # MANIFEST.in file. | |
150 | exclude = ('.sh','.1.gz') |
|
150 | exclude = ('.sh','.1.gz') | |
151 | # We need to figure out how we want to package all of our rst docs? |
|
151 | # We need to figure out how we want to package all of our rst docs? | |
152 | # docfiles = filter(lambda f:file_doesnt_endwith(f,exclude),glob('docs/*')) |
|
152 | # docfiles = filter(lambda f:file_doesnt_endwith(f,exclude),glob('docs/*')) | |
153 | examfiles = filter(isfile, glob('docs/examples/core/*.py')) |
|
153 | examfiles = filter(isfile, glob('docs/examples/core/*.py')) | |
154 | examfiles.append(filter(isfile, glob('docs/examples/kernel/*.py'))) |
|
154 | examfiles.append(filter(isfile, glob('docs/examples/kernel/*.py'))) | |
155 | manpages = filter(isfile, glob('docs/man/*.1.gz')) |
|
155 | manpages = filter(isfile, glob('docs/man/*.1.gz')) | |
156 | igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*')) |
|
156 | igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*')) | |
157 |
|
157 | |||
158 | data_files = [#('data', docdirbase, docfiles), |
|
158 | data_files = [#('data', docdirbase, docfiles), | |
159 | ('data', pjoin(docdirbase, 'examples'),examfiles), |
|
159 | ('data', pjoin(docdirbase, 'examples'),examfiles), | |
160 | ('data', manpagebase, manpages), |
|
160 | ('data', manpagebase, manpages), | |
161 | ('data',pjoin(docdirbase, 'extensions'),igridhelpfiles), |
|
161 | ('data',pjoin(docdirbase, 'extensions'),igridhelpfiles), | |
162 | ] |
|
162 | ] | |
163 | return data_files |
|
163 | # import pprint | |
|
164 | # pprint.pprint(data_files) | |||
|
165 | return [] | |||
164 |
|
166 | |||
165 | #--------------------------------------------------------------------------- |
|
167 | #--------------------------------------------------------------------------- | |
166 | # Find scripts |
|
168 | # Find scripts | |
167 | #--------------------------------------------------------------------------- |
|
169 | #--------------------------------------------------------------------------- | |
168 |
|
170 | |||
169 | def find_scripts(): |
|
171 | def find_scripts(): | |
170 | """ |
|
172 | """ | |
171 | Find IPython's scripts. |
|
173 | Find IPython's scripts. | |
172 | """ |
|
174 | """ | |
173 | scripts = [] |
|
175 | scripts = [] | |
174 | scripts.append('IPython/kernel/scripts/ipengine') |
|
176 | scripts.append('IPython/kernel/scripts/ipengine') | |
175 | scripts.append('IPython/kernel/scripts/ipcontroller') |
|
177 | scripts.append('IPython/kernel/scripts/ipcontroller') | |
176 | scripts.append('IPython/kernel/scripts/ipcluster') |
|
178 | scripts.append('IPython/kernel/scripts/ipcluster') | |
177 | scripts.append('scripts/ipython') |
|
179 | scripts.append('scripts/ipython') | |
178 | scripts.append('scripts/pycolor') |
|
180 | scripts.append('scripts/pycolor') | |
179 | scripts.append('scripts/irunner') |
|
181 | scripts.append('scripts/irunner') | |
180 |
|
182 | |||
181 | # Script to be run by the windows binary installer after the default setup |
|
183 | # Script to be run by the windows binary installer after the default setup | |
182 | # routine, to add shortcuts and similar windows-only things. Windows |
|
184 | # routine, to add shortcuts and similar windows-only things. Windows | |
183 | # post-install scripts MUST reside in the scripts/ dir, otherwise distutils |
|
185 | # post-install scripts MUST reside in the scripts/ dir, otherwise distutils | |
184 | # doesn't find them. |
|
186 | # doesn't find them. | |
185 | if 'bdist_wininst' in sys.argv: |
|
187 | if 'bdist_wininst' in sys.argv: | |
186 | if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv): |
|
188 | if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv): | |
187 | print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting." |
|
189 | print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting." | |
188 | sys.exit(1) |
|
190 | sys.exit(1) | |
189 | scripts.append('scripts/ipython_win_post_install.py') |
|
191 | scripts.append('scripts/ipython_win_post_install.py') | |
190 |
|
192 | |||
191 | return scripts |
|
193 | return scripts | |
192 |
|
194 | |||
193 | #--------------------------------------------------------------------------- |
|
195 | #--------------------------------------------------------------------------- | |
194 | # Find scripts |
|
196 | # Find scripts | |
195 | #--------------------------------------------------------------------------- |
|
197 | #--------------------------------------------------------------------------- | |
196 |
|
198 | |||
197 | def check_for_dependencies(): |
|
199 | def check_for_dependencies(): | |
198 | """Check for IPython's dependencies. |
|
200 | """Check for IPython's dependencies. | |
199 |
|
201 | |||
200 | This function should NOT be called if running under setuptools! |
|
202 | This function should NOT be called if running under setuptools! | |
201 | """ |
|
203 | """ | |
202 | from setupext.setupext import ( |
|
204 | from setupext.setupext import ( | |
203 | print_line, print_raw, print_status, print_message, |
|
205 | print_line, print_raw, print_status, print_message, | |
204 | check_for_zopeinterface, check_for_twisted, |
|
206 | check_for_zopeinterface, check_for_twisted, | |
205 | check_for_foolscap, check_for_pyopenssl, |
|
207 | check_for_foolscap, check_for_pyopenssl, | |
206 | check_for_sphinx, check_for_pygments, |
|
208 | check_for_sphinx, check_for_pygments, | |
207 | check_for_nose, check_for_pexpect |
|
209 | check_for_nose, check_for_pexpect | |
208 | ) |
|
210 | ) | |
209 | print_line() |
|
211 | print_line() | |
210 | print_raw("BUILDING IPYTHON") |
|
212 | print_raw("BUILDING IPYTHON") | |
211 | print_status('python', sys.version) |
|
213 | print_status('python', sys.version) | |
212 | print_status('platform', sys.platform) |
|
214 | print_status('platform', sys.platform) | |
213 | if sys.platform == 'win32': |
|
215 | if sys.platform == 'win32': | |
214 | print_status('Windows version', sys.getwindowsversion()) |
|
216 | print_status('Windows version', sys.getwindowsversion()) | |
215 |
|
217 | |||
216 | print_raw("") |
|
218 | print_raw("") | |
217 | print_raw("OPTIONAL DEPENDENCIES") |
|
219 | print_raw("OPTIONAL DEPENDENCIES") | |
218 |
|
220 | |||
219 | check_for_zopeinterface() |
|
221 | check_for_zopeinterface() | |
220 | check_for_twisted() |
|
222 | check_for_twisted() | |
221 | check_for_foolscap() |
|
223 | check_for_foolscap() | |
222 | check_for_pyopenssl() |
|
224 | check_for_pyopenssl() | |
223 | check_for_sphinx() |
|
225 | check_for_sphinx() | |
224 | check_for_pygments() |
|
226 | check_for_pygments() | |
225 | check_for_nose() |
|
227 | check_for_nose() | |
226 | check_for_pexpect() No newline at end of file |
|
228 | check_for_pexpect() |
General Comments 0
You need to be logged in to leave comments.
Login now