##// END OF EJS Templates
Fix adding functions to CommandChainDispatcher with equal priority on Python 3....
Thomas Kluyver -
Show More
@@ -1,229 +1,231 b''
1 """hooks for IPython.
1 """hooks for IPython.
2
2
3 In Python, it is possible to overwrite any method of any object if you really
3 In Python, it is possible to overwrite any method of any object if you really
4 want to. But IPython exposes a few 'hooks', methods which are _designed_ to
4 want to. But IPython exposes a few 'hooks', methods which are _designed_ to
5 be overwritten by users for customization purposes. This module defines the
5 be overwritten by users for customization purposes. This module defines the
6 default versions of all such hooks, which get used by IPython if not
6 default versions of all such hooks, which get used by IPython if not
7 overridden by the user.
7 overridden by the user.
8
8
9 hooks are simple functions, but they should be declared with 'self' as their
9 hooks are simple functions, but they should be declared with 'self' as their
10 first argument, because when activated they are registered into IPython as
10 first argument, because when activated they are registered into IPython as
11 instance methods. The self argument will be the IPython running instance
11 instance methods. The self argument will be the IPython running instance
12 itself, so hooks have full access to the entire IPython object.
12 itself, so hooks have full access to the entire IPython object.
13
13
14 If you wish to define a new hook and activate it, you need to put the
14 If you wish to define a new hook and activate it, you need to put the
15 necessary code into a python file which can be either imported or execfile()'d
15 necessary code into a python file which can be either imported or execfile()'d
16 from within your profile's ipython_config.py configuration.
16 from within your profile's ipython_config.py configuration.
17
17
18 For example, suppose that you have a module called 'myiphooks' in your
18 For example, suppose that you have a module called 'myiphooks' in your
19 PYTHONPATH, which contains the following definition:
19 PYTHONPATH, which contains the following definition:
20
20
21 import os
21 import os
22 from IPython.core import ipapi
22 from IPython.core import ipapi
23 ip = ipapi.get()
23 ip = ipapi.get()
24
24
25 def calljed(self,filename, linenum):
25 def calljed(self,filename, linenum):
26 "My editor hook calls the jed editor directly."
26 "My editor hook calls the jed editor directly."
27 print "Calling my own editor, jed ..."
27 print "Calling my own editor, jed ..."
28 if os.system('jed +%d %s' % (linenum,filename)) != 0:
28 if os.system('jed +%d %s' % (linenum,filename)) != 0:
29 raise TryNext()
29 raise TryNext()
30
30
31 ip.set_hook('editor', calljed)
31 ip.set_hook('editor', calljed)
32
32
33 You can then enable the functionality by doing 'import myiphooks'
33 You can then enable the functionality by doing 'import myiphooks'
34 somewhere in your configuration files or ipython command line.
34 somewhere in your configuration files or ipython command line.
35 """
35 """
36
36
37 #*****************************************************************************
37 #*****************************************************************************
38 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
38 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
39 #
39 #
40 # Distributed under the terms of the BSD License. The full license is in
40 # Distributed under the terms of the BSD License. The full license is in
41 # the file COPYING, distributed as part of this software.
41 # the file COPYING, distributed as part of this software.
42 #*****************************************************************************
42 #*****************************************************************************
43
43
44 import os, bisect
44 import os
45 import operator
45 import subprocess
46 import subprocess
46 import sys
47 import sys
47
48
48 from IPython.core.error import TryNext
49 from IPython.core.error import TryNext
49
50
50 # List here all the default hooks. For now it's just the editor functions
51 # List here all the default hooks. For now it's just the editor functions
51 # but over time we'll move here all the public API for user-accessible things.
52 # but over time we'll move here all the public API for user-accessible things.
52
53
53 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor',
54 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor',
54 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
55 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
55 'show_in_pager','pre_prompt_hook',
56 'show_in_pager','pre_prompt_hook',
56 'pre_run_code_hook', 'clipboard_get']
57 'pre_run_code_hook', 'clipboard_get']
57
58
58 def editor(self, filename, linenum=None, wait=True):
59 def editor(self, filename, linenum=None, wait=True):
59 """Open the default editor at the given filename and linenumber.
60 """Open the default editor at the given filename and linenumber.
60
61
61 This is IPython's default editor hook, you can use it as an example to
62 This is IPython's default editor hook, you can use it as an example to
62 write your own modified one. To set your own editor function as the
63 write your own modified one. To set your own editor function as the
63 new editor hook, call ip.set_hook('editor',yourfunc)."""
64 new editor hook, call ip.set_hook('editor',yourfunc)."""
64
65
65 # IPython configures a default editor at startup by reading $EDITOR from
66 # IPython configures a default editor at startup by reading $EDITOR from
66 # the environment, and falling back on vi (unix) or notepad (win32).
67 # the environment, and falling back on vi (unix) or notepad (win32).
67 editor = self.editor
68 editor = self.editor
68
69
69 # marker for at which line to open the file (for existing objects)
70 # marker for at which line to open the file (for existing objects)
70 if linenum is None or editor=='notepad':
71 if linenum is None or editor=='notepad':
71 linemark = ''
72 linemark = ''
72 else:
73 else:
73 linemark = '+%d' % int(linenum)
74 linemark = '+%d' % int(linenum)
74
75
75 # Enclose in quotes if necessary and legal
76 # Enclose in quotes if necessary and legal
76 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
77 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
77 editor = '"%s"' % editor
78 editor = '"%s"' % editor
78
79
79 # Call the actual editor
80 # Call the actual editor
80 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
81 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
81 shell=True)
82 shell=True)
82 if wait and proc.wait() != 0:
83 if wait and proc.wait() != 0:
83 raise TryNext()
84 raise TryNext()
84
85
85 import tempfile
86 import tempfile
86 def fix_error_editor(self,filename,linenum,column,msg):
87 def fix_error_editor(self,filename,linenum,column,msg):
87 """Open the editor at the given filename, linenumber, column and
88 """Open the editor at the given filename, linenumber, column and
88 show an error message. This is used for correcting syntax errors.
89 show an error message. This is used for correcting syntax errors.
89 The current implementation only has special support for the VIM editor,
90 The current implementation only has special support for the VIM editor,
90 and falls back on the 'editor' hook if VIM is not used.
91 and falls back on the 'editor' hook if VIM is not used.
91
92
92 Call ip.set_hook('fix_error_editor',youfunc) to use your own function,
93 Call ip.set_hook('fix_error_editor',youfunc) to use your own function,
93 """
94 """
94 def vim_quickfix_file():
95 def vim_quickfix_file():
95 t = tempfile.NamedTemporaryFile()
96 t = tempfile.NamedTemporaryFile()
96 t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg))
97 t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg))
97 t.flush()
98 t.flush()
98 return t
99 return t
99 if os.path.basename(self.editor) != 'vim':
100 if os.path.basename(self.editor) != 'vim':
100 self.hooks.editor(filename,linenum)
101 self.hooks.editor(filename,linenum)
101 return
102 return
102 t = vim_quickfix_file()
103 t = vim_quickfix_file()
103 try:
104 try:
104 if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name):
105 if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name):
105 raise TryNext()
106 raise TryNext()
106 finally:
107 finally:
107 t.close()
108 t.close()
108
109
109
110
110 def synchronize_with_editor(self, filename, linenum, column):
111 def synchronize_with_editor(self, filename, linenum, column):
111 pass
112 pass
112
113
113
114
114 class CommandChainDispatcher:
115 class CommandChainDispatcher:
115 """ Dispatch calls to a chain of commands until some func can handle it
116 """ Dispatch calls to a chain of commands until some func can handle it
116
117
117 Usage: instantiate, execute "add" to add commands (with optional
118 Usage: instantiate, execute "add" to add commands (with optional
118 priority), execute normally via f() calling mechanism.
119 priority), execute normally via f() calling mechanism.
119
120
120 """
121 """
121 def __init__(self,commands=None):
122 def __init__(self,commands=None):
122 if commands is None:
123 if commands is None:
123 self.chain = []
124 self.chain = []
124 else:
125 else:
125 self.chain = commands
126 self.chain = commands
126
127
127
128
128 def __call__(self,*args, **kw):
129 def __call__(self,*args, **kw):
129 """ Command chain is called just like normal func.
130 """ Command chain is called just like normal func.
130
131
131 This will call all funcs in chain with the same args as were given to
132 This will call all funcs in chain with the same args as were given to
132 this function, and return the result of first func that didn't raise
133 this function, and return the result of first func that didn't raise
133 TryNext"""
134 TryNext"""
134 last_exc = TryNext()
135 last_exc = TryNext()
135 for prio,cmd in self.chain:
136 for prio,cmd in self.chain:
136 #print "prio",prio,"cmd",cmd #dbg
137 #print "prio",prio,"cmd",cmd #dbg
137 try:
138 try:
138 return cmd(*args, **kw)
139 return cmd(*args, **kw)
139 except TryNext as exc:
140 except TryNext as exc:
140 last_exc = exc
141 last_exc = exc
141 # if no function will accept it, raise TryNext up to the caller
142 # if no function will accept it, raise TryNext up to the caller
142 raise last_exc
143 raise last_exc
143
144
144 def __str__(self):
145 def __str__(self):
145 return str(self.chain)
146 return str(self.chain)
146
147
147 def add(self, func, priority=0):
148 def add(self, func, priority=0):
148 """ Add a func to the cmd chain with given priority """
149 """ Add a func to the cmd chain with given priority """
149 bisect.insort(self.chain,(priority,func))
150 self.chain.append((priority, func))
151 self.chain.sort(key=operator.itemgetter(0))
150
152
151 def __iter__(self):
153 def __iter__(self):
152 """ Return all objects in chain.
154 """ Return all objects in chain.
153
155
154 Handy if the objects are not callable.
156 Handy if the objects are not callable.
155 """
157 """
156 return iter(self.chain)
158 return iter(self.chain)
157
159
158
160
159 def input_prefilter(self,line):
161 def input_prefilter(self,line):
160 """ Default input prefilter
162 """ Default input prefilter
161
163
162 This returns the line as unchanged, so that the interpreter
164 This returns the line as unchanged, so that the interpreter
163 knows that nothing was done and proceeds with "classic" prefiltering
165 knows that nothing was done and proceeds with "classic" prefiltering
164 (%magics, !shell commands etc.).
166 (%magics, !shell commands etc.).
165
167
166 Note that leading whitespace is not passed to this hook. Prefilter
168 Note that leading whitespace is not passed to this hook. Prefilter
167 can't alter indentation.
169 can't alter indentation.
168
170
169 """
171 """
170 #print "attempt to rewrite",line #dbg
172 #print "attempt to rewrite",line #dbg
171 return line
173 return line
172
174
173
175
174 def shutdown_hook(self):
176 def shutdown_hook(self):
175 """ default shutdown hook
177 """ default shutdown hook
176
178
177 Typically, shotdown hooks should raise TryNext so all shutdown ops are done
179 Typically, shotdown hooks should raise TryNext so all shutdown ops are done
178 """
180 """
179
181
180 #print "default shutdown hook ok" # dbg
182 #print "default shutdown hook ok" # dbg
181 return
183 return
182
184
183
185
184 def late_startup_hook(self):
186 def late_startup_hook(self):
185 """ Executed after ipython has been constructed and configured
187 """ Executed after ipython has been constructed and configured
186
188
187 """
189 """
188 #print "default startup hook ok" # dbg
190 #print "default startup hook ok" # dbg
189
191
190
192
191 def show_in_pager(self,s):
193 def show_in_pager(self,s):
192 """ Run a string through pager """
194 """ Run a string through pager """
193 # raising TryNext here will use the default paging functionality
195 # raising TryNext here will use the default paging functionality
194 raise TryNext
196 raise TryNext
195
197
196
198
197 def pre_prompt_hook(self):
199 def pre_prompt_hook(self):
198 """ Run before displaying the next prompt
200 """ Run before displaying the next prompt
199
201
200 Use this e.g. to display output from asynchronous operations (in order
202 Use this e.g. to display output from asynchronous operations (in order
201 to not mess up text entry)
203 to not mess up text entry)
202 """
204 """
203
205
204 return None
206 return None
205
207
206
208
207 def pre_run_code_hook(self):
209 def pre_run_code_hook(self):
208 """ Executed before running the (prefiltered) code in IPython """
210 """ Executed before running the (prefiltered) code in IPython """
209 return None
211 return None
210
212
211
213
212 def clipboard_get(self):
214 def clipboard_get(self):
213 """ Get text from the clipboard.
215 """ Get text from the clipboard.
214 """
216 """
215 from IPython.lib.clipboard import (
217 from IPython.lib.clipboard import (
216 osx_clipboard_get, tkinter_clipboard_get,
218 osx_clipboard_get, tkinter_clipboard_get,
217 win32_clipboard_get
219 win32_clipboard_get
218 )
220 )
219 if sys.platform == 'win32':
221 if sys.platform == 'win32':
220 chain = [win32_clipboard_get, tkinter_clipboard_get]
222 chain = [win32_clipboard_get, tkinter_clipboard_get]
221 elif sys.platform == 'darwin':
223 elif sys.platform == 'darwin':
222 chain = [osx_clipboard_get, tkinter_clipboard_get]
224 chain = [osx_clipboard_get, tkinter_clipboard_get]
223 else:
225 else:
224 chain = [tkinter_clipboard_get]
226 chain = [tkinter_clipboard_get]
225 dispatcher = CommandChainDispatcher()
227 dispatcher = CommandChainDispatcher()
226 for func in chain:
228 for func in chain:
227 dispatcher.add(func)
229 dispatcher.add(func)
228 text = dispatcher()
230 text = dispatcher()
229 return text
231 return text
@@ -1,75 +1,81 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for CommandChainDispatcher."""
2 """Tests for CommandChainDispatcher."""
3
3
4 from __future__ import absolute_import
4 from __future__ import absolute_import
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Imports
7 # Imports
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import nose.tools as nt
10 import nose.tools as nt
11 from IPython.core.error import TryNext
11 from IPython.core.error import TryNext
12 from IPython.core.hooks import CommandChainDispatcher
12 from IPython.core.hooks import CommandChainDispatcher
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Local utilities
15 # Local utilities
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Define two classes, one which succeeds and one which raises TryNext. Each
18 # Define two classes, one which succeeds and one which raises TryNext. Each
19 # sets the attribute `called` to True when it is called.
19 # sets the attribute `called` to True when it is called.
20 class Okay(object):
20 class Okay(object):
21 def __init__(self, message):
21 def __init__(self, message):
22 self.message = message
22 self.message = message
23 self.called = False
23 self.called = False
24 def __call__(self):
24 def __call__(self):
25 self.called = True
25 self.called = True
26 return self.message
26 return self.message
27
27
28 class Fail(object):
28 class Fail(object):
29 def __init__(self, message):
29 def __init__(self, message):
30 self.message = message
30 self.message = message
31 self.called = False
31 self.called = False
32 def __call__(self):
32 def __call__(self):
33 self.called = True
33 self.called = True
34 raise TryNext(self.message)
34 raise TryNext(self.message)
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Test functions
37 # Test functions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 def test_command_chain_dispatcher_ff():
40 def test_command_chain_dispatcher_ff():
41 """Test two failing hooks"""
41 """Test two failing hooks"""
42 fail1 = Fail(u'fail1')
42 fail1 = Fail(u'fail1')
43 fail2 = Fail(u'fail2')
43 fail2 = Fail(u'fail2')
44 dp = CommandChainDispatcher([(0, fail1),
44 dp = CommandChainDispatcher([(0, fail1),
45 (10, fail2)])
45 (10, fail2)])
46
46
47 try:
47 try:
48 dp()
48 dp()
49 except TryNext as e:
49 except TryNext as e:
50 nt.assert_equal(str(e), u'fail2')
50 nt.assert_equal(str(e), u'fail2')
51 else:
51 else:
52 assert False, "Expected exception was not raised."
52 assert False, "Expected exception was not raised."
53
53
54 nt.assert_true(fail1.called)
54 nt.assert_true(fail1.called)
55 nt.assert_true(fail2.called)
55 nt.assert_true(fail2.called)
56
56
57 def test_command_chain_dispatcher_fofo():
57 def test_command_chain_dispatcher_fofo():
58 """Test a mixture of failing and succeeding hooks."""
58 """Test a mixture of failing and succeeding hooks."""
59 fail1 = Fail(u'fail1')
59 fail1 = Fail(u'fail1')
60 fail2 = Fail(u'fail2')
60 fail2 = Fail(u'fail2')
61 okay1 = Okay(u'okay1')
61 okay1 = Okay(u'okay1')
62 okay2 = Okay(u'okay2')
62 okay2 = Okay(u'okay2')
63
63
64 dp = CommandChainDispatcher([(0, fail1),
64 dp = CommandChainDispatcher([(0, fail1),
65 # (5, okay1), # add this later
65 # (5, okay1), # add this later
66 (10, fail2),
66 (10, fail2),
67 (15, okay2)])
67 (15, okay2)])
68 dp.add(okay1, 5)
68 dp.add(okay1, 5)
69
69
70 nt.assert_equal(dp(), u'okay1')
70 nt.assert_equal(dp(), u'okay1')
71
71
72 nt.assert_true(fail1.called)
72 nt.assert_true(fail1.called)
73 nt.assert_true(okay1.called)
73 nt.assert_true(okay1.called)
74 nt.assert_false(fail2.called)
74 nt.assert_false(fail2.called)
75 nt.assert_false(okay2.called)
75 nt.assert_false(okay2.called)
76
77 def test_command_chain_dispatcher_eq_priority():
78 okay1 = Okay(u'okay1')
79 okay2 = Okay(u'okay2')
80 dp = CommandChainDispatcher([(1, okay1)])
81 dp.add(okay2, 1)
General Comments 0
You need to be logged in to leave comments. Login now