##// END OF EJS Templates
Merge pull request #1932 from takluyver/i1916...
Thomas Kluyver -
r7521:b6137d28 merge
parent child Browse files
Show More
@@ -1,229 +1,230 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 45 import subprocess
46 46 import sys
47 47
48 48 from IPython.core.error import TryNext
49 49
50 50 # List here all the default hooks. For now it's just the editor functions
51 51 # but over time we'll move here all the public API for user-accessible things.
52 52
53 53 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor',
54 54 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
55 55 'show_in_pager','pre_prompt_hook',
56 56 'pre_run_code_hook', 'clipboard_get']
57 57
58 58 def editor(self, filename, linenum=None, wait=True):
59 59 """Open the default editor at the given filename and linenumber.
60 60
61 61 This is IPython's default editor hook, you can use it as an example to
62 62 write your own modified one. To set your own editor function as the
63 63 new editor hook, call ip.set_hook('editor',yourfunc)."""
64 64
65 65 # IPython configures a default editor at startup by reading $EDITOR from
66 66 # the environment, and falling back on vi (unix) or notepad (win32).
67 67 editor = self.editor
68 68
69 69 # marker for at which line to open the file (for existing objects)
70 70 if linenum is None or editor=='notepad':
71 71 linemark = ''
72 72 else:
73 73 linemark = '+%d' % int(linenum)
74 74
75 75 # Enclose in quotes if necessary and legal
76 76 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
77 77 editor = '"%s"' % editor
78 78
79 79 # Call the actual editor
80 80 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
81 81 shell=True)
82 82 if wait and proc.wait() != 0:
83 83 raise TryNext()
84 84
85 85 import tempfile
86 86 def fix_error_editor(self,filename,linenum,column,msg):
87 87 """Open the editor at the given filename, linenumber, column and
88 88 show an error message. This is used for correcting syntax errors.
89 89 The current implementation only has special support for the VIM editor,
90 90 and falls back on the 'editor' hook if VIM is not used.
91 91
92 92 Call ip.set_hook('fix_error_editor',youfunc) to use your own function,
93 93 """
94 94 def vim_quickfix_file():
95 95 t = tempfile.NamedTemporaryFile()
96 96 t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg))
97 97 t.flush()
98 98 return t
99 99 if os.path.basename(self.editor) != 'vim':
100 100 self.hooks.editor(filename,linenum)
101 101 return
102 102 t = vim_quickfix_file()
103 103 try:
104 104 if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name):
105 105 raise TryNext()
106 106 finally:
107 107 t.close()
108 108
109 109
110 110 def synchronize_with_editor(self, filename, linenum, column):
111 111 pass
112 112
113 113
114 114 class CommandChainDispatcher:
115 115 """ Dispatch calls to a chain of commands until some func can handle it
116 116
117 117 Usage: instantiate, execute "add" to add commands (with optional
118 118 priority), execute normally via f() calling mechanism.
119 119
120 120 """
121 121 def __init__(self,commands=None):
122 122 if commands is None:
123 123 self.chain = []
124 124 else:
125 125 self.chain = commands
126 126
127 127
128 128 def __call__(self,*args, **kw):
129 129 """ Command chain is called just like normal func.
130 130
131 131 This will call all funcs in chain with the same args as were given to
132 132 this function, and return the result of first func that didn't raise
133 133 TryNext"""
134 134 last_exc = TryNext()
135 135 for prio,cmd in self.chain:
136 136 #print "prio",prio,"cmd",cmd #dbg
137 137 try:
138 138 return cmd(*args, **kw)
139 139 except TryNext as exc:
140 140 last_exc = exc
141 141 # if no function will accept it, raise TryNext up to the caller
142 142 raise last_exc
143 143
144 144 def __str__(self):
145 145 return str(self.chain)
146 146
147 147 def add(self, func, priority=0):
148 148 """ Add a func to the cmd chain with given priority """
149 bisect.insort(self.chain,(priority,func))
149 self.chain.append((priority, func))
150 self.chain.sort(key=lambda x: x[0])
150 151
151 152 def __iter__(self):
152 153 """ Return all objects in chain.
153 154
154 155 Handy if the objects are not callable.
155 156 """
156 157 return iter(self.chain)
157 158
158 159
159 160 def input_prefilter(self,line):
160 161 """ Default input prefilter
161 162
162 163 This returns the line as unchanged, so that the interpreter
163 164 knows that nothing was done and proceeds with "classic" prefiltering
164 165 (%magics, !shell commands etc.).
165 166
166 167 Note that leading whitespace is not passed to this hook. Prefilter
167 168 can't alter indentation.
168 169
169 170 """
170 171 #print "attempt to rewrite",line #dbg
171 172 return line
172 173
173 174
174 175 def shutdown_hook(self):
175 176 """ default shutdown hook
176 177
177 178 Typically, shotdown hooks should raise TryNext so all shutdown ops are done
178 179 """
179 180
180 181 #print "default shutdown hook ok" # dbg
181 182 return
182 183
183 184
184 185 def late_startup_hook(self):
185 186 """ Executed after ipython has been constructed and configured
186 187
187 188 """
188 189 #print "default startup hook ok" # dbg
189 190
190 191
191 192 def show_in_pager(self,s):
192 193 """ Run a string through pager """
193 194 # raising TryNext here will use the default paging functionality
194 195 raise TryNext
195 196
196 197
197 198 def pre_prompt_hook(self):
198 199 """ Run before displaying the next prompt
199 200
200 201 Use this e.g. to display output from asynchronous operations (in order
201 202 to not mess up text entry)
202 203 """
203 204
204 205 return None
205 206
206 207
207 208 def pre_run_code_hook(self):
208 209 """ Executed before running the (prefiltered) code in IPython """
209 210 return None
210 211
211 212
212 213 def clipboard_get(self):
213 214 """ Get text from the clipboard.
214 215 """
215 216 from IPython.lib.clipboard import (
216 217 osx_clipboard_get, tkinter_clipboard_get,
217 218 win32_clipboard_get
218 219 )
219 220 if sys.platform == 'win32':
220 221 chain = [win32_clipboard_get, tkinter_clipboard_get]
221 222 elif sys.platform == 'darwin':
222 223 chain = [osx_clipboard_get, tkinter_clipboard_get]
223 224 else:
224 225 chain = [tkinter_clipboard_get]
225 226 dispatcher = CommandChainDispatcher()
226 227 for func in chain:
227 228 dispatcher.add(func)
228 229 text = dispatcher()
229 230 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