This commit is contained in:
2019-03-20 13:52:00 +01:00
parent 3084fe0510
commit 5db0f78aee
910 changed files with 191152 additions and 322 deletions

View File

@@ -0,0 +1,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']