-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathipython.py
302 lines (220 loc) · 7.64 KB
/
ipython.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
import re
from functools import wraps
from IPython.core.magic import register_line_magic
from IPython import get_ipython # @UnusedImport (used by register_line_magic)
from diffcalc.gdasupport.scannable.hkl import Hkl
"""
For wrapping functions:
In [1]: import diffcmd.ipython
In [2]: diffcmd.ipython.GLOBAL_NAMESPACE_DICT = globals()
In [3]: from IPython.core.magic import register_line_magic
In [4]: from diffcmd.ipython import parse_line
In [5]: @register_line_magic
...: @parse_line
...: def check_parser(*args):
...: return args
...:
In [6]: check_parser
Out[6]: <function __main__.check_parser>
In [7]: del check_parser
In [8]: check_parser
Out[8]: ()
In [9]: check_parser 1
Out[9]: (1,)
In [10]: check_parser 1 2
Out[10]: (1, 2)
In [11]: check_parser 1 2 [3]
Out[11]: (1, 2, [3])
In [12]: b='bbb'
In [13]: check_parser 1 2 [3] b
Out[13]: (1, 2, [3], 'bbb')
And to create something dynamically from a function:
In [28]: def f(a, b, c):
....: ....: return a, b, c
....:
In [29]: register_line_magic(parse_line(f))
Out[29]: <function __main__.f>
In [30]: del f
In [31]: f 'a' -2 [1 3 -4]
Out[31]: ('a', -2, [1, 3, -4])
And from a list of functions:
In [32]: def one(a):
....: return a
....:
In [33]: def two(a, b):
....: return a, b
....:
In [34]: functions = one, two
In [35]: del one, two
In [36]: for f in functions:
....: register_line_magic(parse_line(f))
....:
In [37]: one 1
Out[37]: 1
In [39]: two 1 2
Out[39]: (1, 2)
And to check if we are running in iPython:
In [47]: 'get_ipython' in globals()
Out[47]: True
def in_ipython():
try:
get_ipython()
return True
except NameError:
return False
"""
GLOBAL_NAMESPACE_DICT = {}
MATH_OPERATORS = set(['-', '+', '/', '*'])
# Keep a copy of python's original help as we may remove it later
if 'help' in __builtins__:
ORIGINAL_PYTHON_HELP = __builtins__['help']
COMMA_USAGE_HELP = \
'''
| When calling a function without brackets, whitespace must be used in
| place of commas. For example:
|
| >>> function a b [1 2 3] 'c'
|
| is equivalent to:
|
| >>> function(a, b, [1, 2, 3], 'c')
|
'''
MATH_OPERATOR_USAGE_HELP = \
'''
| When calling a function without brackets, whitespace is used in place of
| commas. Therefore terms which require evaluation must contain no space.
| These will fail for example:
|
| >>> function - 1
| >>> function a() * 2
| But this
| >>> function -1 1-1 +1 a()+1 [-1 0+1 b()] c+1
|
| is okay and equivalent to:
|
| >>> function(-1, 0, 1, a() + 1, [-1, 1, b()], c + 1)
|
'''
comma_finder = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''')
space_finder = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''')
hash_finder = re.compile(r'''((?:[^#"']|"[^"]*"|'[^']*')+)''')
open_square_finder = re.compile(r'''((?:[^["']|"[^"]*"|'[^']*')+)''')
close_square_finder = re.compile(r'''((?:[^]"']|"[^"]*"|'[^']*')+)''')
def tokenify(s):
# Don't accept commas outside strings.
# Users are frustrated by not knowing when commas _are_ required.
# Making it clear when they are not helps them understand the
# difference.
if ',' in comma_finder.split(s):
print COMMA_USAGE_HELP
print "(string was: %s)" % s
raise SyntaxError('unexpected comma')
# ignore comment
hash_split = hash_finder.split(s)
if '#' in hash_split:
s = '' if hash_split[0] == '#' else hash_split[1]
# surround square brackets with spaces to simplify token extraction
s = ''.join(' [ ' if e == '[' else e for e in open_square_finder.split(s))
s = ''.join(' ] ' if e == ']' else e for e in close_square_finder.split(s))
# tokens are now separated by spaces
tokens = space_finder.split(s)[1::2]
tokens = [tok for tok in tokens if tok != '']
return tokens
def parse(s, d):
s = str(s)
tokens = tokenify(s)
for tok in tokens:
if tok in MATH_OPERATORS:
print MATH_OPERATOR_USAGE_HELP
raise SyntaxError('could not evaluate: "%s"' % tok)
s = ', '.join(tokens)
s = s.replace('[, ', '[')
s = s.replace(',]', ']')
s = s.replace(', ]', ']')
try:
args = eval('[' + s + ']', d)
except SyntaxError:
raise SyntaxError('could not evaluate: "%s"' % s)
return args
def parse_line(f, global_namespace_dict=None):
'''A decorator that parses a single string argument into a list of arguments
and calls the wrapped function with these.
'''
if not global_namespace_dict:
global_namespace_dict = GLOBAL_NAMESPACE_DICT
@wraps(f)
def wrapper(line):
args = parse(line, global_namespace_dict)
return f(*args)
return wrapper
_DEFAULT_HELP = \
"""
For help with diffcalc's orientation phase try:
>>> help ub
For help with moving in reciprocal lattice space try:
>>> help hkl
For more detailed help try for example:
>>> help newub
For help with driving axes or scanning:
>>> help pos
>>> help scan
For help with regular python try for example:
>>> help list
For more detailed help with diffcalc go to:
https://diffcalc.readthedocs.io
"""
def magic_commands(global_namespace_dict):
"""Magic commands left in global_namespace_dict by previous import from
diffcalc.
Also creates a help command. NOTE that calling this will
remove the original commands from the global namespace as otherwise these
would shadow the ipython magiced versions.
Depends on hkl_commands_for_help & ub_commands_for_help list having been
left in the global namespace and assumes there is pos and scan command.
"""
gnd = global_namespace_dict
global GLOBAL_NAMESPACE_DICT
GLOBAL_NAMESPACE_DICT = gnd
### Magic commands in namespace ###
commands = list(gnd['hkl_commands_for_help'])
commands += gnd['ub_commands_for_help']
commands.append(gnd['pos'])
commands.append(gnd['scan'])
command_map = {}
for f in commands:
# Skip section headers like 'Motion'
if not hasattr(f, '__call__'):
continue
# magic the function and remove from namespace (otherwise it would
# shadow the magiced command)
register_line_magic(parse_line(f, gnd))
del gnd[f.__name__]
command_map[f.__name__] = f
### Create help function ###
#Expects python's original help to be named pythons_help and to be
#available in the top-level global namespace (where non-diffcalc
#objects may have help called from).
def help(s): # @ReservedAssignment
"""Diffcalc help for iPython
"""
if s == '':
print _DEFAULT_HELP
elif s == 'hkl':
# Use help injected into hkl object
print Hkl.dynamic_docstring
elif s == 'ub':
# Use help injected into ub command
print command_map['ub'].__doc__
elif s in command_map:
print "%s (diffcalc command):" %s
print command_map[s].__doc__
else:
exec('pythons_help(%s)' %s, gnd)
### Setup help command ###
gnd['pythons_help'] = ORIGINAL_PYTHON_HELP
register_line_magic(help)
# Remove builtin help
# (otherwise it would shadow magiced command
if 'help' in __builtins__:
del __builtins__['help']