303 lines
7.6 KiB
Python
303 lines
7.6 KiB
Python
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']
|