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