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

View File

@@ -0,0 +1,86 @@
#!/usr/bin/python
import argparse
import subprocess
import os
import getpass
DIFFCALC_BIN = os.path.split(os.path.realpath(__file__))[0]
DIFFCALC_ROOT = os.path.abspath(os.path.join(DIFFCALC_BIN, os.pardir))
MODULE_FOR_MANUALS = '_make_sixcircle_manual'
def main():
parser = argparse.ArgumentParser(description='Diffcalc: A diffraction condition calculator of x-ray and neutron crystalography')
parser.add_argument('--modules', dest='show_modules', action='store_true',
help='list available modules')
parser.add_argument('--python', dest='use_python', action='store_true',
help='run within python rather than ipython')
parser.add_argument('--debug', dest='debug', action='store_true',
help='run in debug mode')
parser.add_argument('--make-manuals-source', dest='make_manuals', action='store_true',
help='make .rst manual files by running template through sixcircle')
parser.add_argument('--non-interactive', dest='non_interactive', action='store_true',
help='do not enter interactive mode after startup')
parser.add_argument('module', type=str, nargs='?',
help='the module to startup with')
args = parser.parse_args()
# Create list of available modules
module_names = []
for module_path in os.listdir(os.path.join(DIFFCALC_ROOT, 'startup')):
if not module_path.startswith('_') and module_path.endswith('.py'):
module_names.append(module_path.split('.')[0])
module_names.sort()
if args.show_modules:
print_available_modules(module_names)
exit(0)
if not args.make_manuals and not args.module:
print "A module name should be provided. Choose one of:"
print_available_modules(module_names)
exit(0)
if args.make_manuals:
if args.module:
print "When building the manuals no module should be given"
exit(1)
args.module = MODULE_FOR_MANUALS
if not args.make_manuals and args.module not in module_names:
print "The provided argument '%s' is not one of:" % args.module
print_available_modules(module_names)
exit(1)
env = os.environ.copy()
if 'PYTHONPATH' not in env:
env['PYTHONPATH'] = ''
env['PYTHONPATH'] = DIFFCALC_ROOT + ':' + env['PYTHONPATH']
diffcmd_start_path = os.path.join(DIFFCALC_ROOT, 'diffcmd', 'start.py')
if args.use_python:
cmd = 'python'
else: # ipython
cmd = 'ipython --no-banner --HistoryManager.hist_file=/tmp/ipython_hist_%s.sqlite' % getpass.getuser()
iflag = '' if args.non_interactive else '-i'
cmd = cmd + ' ' + ' '.join([iflag, diffcmd_start_path, args.module, str(args.debug)])
print 'Running: ' + cmd
rc = subprocess.call(cmd, env=env, shell=True)
exit(rc)
def print_available_modules(module_names):
lines = []
for m in sorted(module_names):
lines.append(' ' + m)
print '\n'.join(lines)
if __name__ == '__main__':
main()
#

View File

@@ -0,0 +1,43 @@
#
# General utility functions to handle Diffcalc commands
#
from gda.jython.commands.GeneralCommands import alias
try:
import gda
GDA = True
except ImportError:
GDA = False
def alias_commands(global_namespace_dict):
"""Alias commands left in global_namespace_dict by previous import from
diffcalc.
This is the equivalent of diffcmd/ipython/magic_commands() for use
when IPython is not available
"""
gnd = global_namespace_dict
global GLOBAL_NAMESPACE_DICT
GLOBAL_NAMESPACE_DICT = gnd
print "Aliasing commands"
### Alias commands in namespace ###
commands = gnd['hkl_commands_for_help']
commands += gnd['ub_commands_for_help']
if not GDA: # TODO: encapsulation issue: this should be done outside this function!
commands.append(gnd['pos'])
commands.append(gnd['scan'])
aliased_names = []
for f in commands:
# Skip section headers like 'Motion'
if not hasattr(f, '__call__'):
continue
alias(f.__name__)
aliased_names.append(f.__name__)
print "Aliased commands: " + ' '.join(aliased_names)

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']

View File

@@ -0,0 +1,79 @@
import diffcmd.ipython
from IPython.core.magic import register_line_magic
from diffcmd.ipython import parse_line
command_map = {}
_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
"""
# This function should be called with parameter globals()
def define_commands(dictionary):
print "Ipython detected - magicing commands"
magiced_names = []
commands = hkl_commands_for_help + ub_commands_for_help # @UndefinedVariable
commands += [pos, scan] # @UndefinedVariable
ipython.GLOBAL_NAMESPACE_DICT = dictionary
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))
del dictionary[f.__name__]
command_map[f.__name__] = f
magiced_names.append(f.__name__)
print "Magiced commands: " + ' '.join(magiced_names)
# because the functions have gone from namespace we need to override
pythons_help = __builtins__.help
del __builtins__.help
register_line_magic(help)
del help
def help(s):
"""Diffcalc help for iPython
"""
if s == '':
print _DEFAULT_HELP
elif s == 'hkl':
# Use help injected into hkl object
print hkl.__doc__
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)

View File

@@ -0,0 +1,146 @@
from StringIO import StringIO
from IPython import get_ipython
import sys
from diffcalc.dc.help import format_commands_for_rst_table
TEST_INPUT="""
Diffcalc's Scannables
=====================
Please see :ref:`moving-in-hkl-space` and :ref:`scanning-in-hkl-space` for some relevant examples.
To list and show the current positions of your beamline's scannables
use ``pos`` with no arguments::
>>> pos wl
should do nought, but this should be replaced::
==> pos wl 2
should do the thing
==> abcd
"""
def echorun(magic_cmd):
print "\n>>> " + str(magic_cmd)
def make_manual(input_file_path,
output_file_path,
ub_commands_for_help,
hkl_commands_for_help):
# Read input file (should be .rst file)
with open(input_file_path, 'r') as f:
input_string = f.read()
# Parse input string
output_lines = []
for lineno, line in enumerate(input_string.split('\n')):
process = '==>' in line
if process and 'STOP' in line:
print "'==> STOP' found on line. STOPPING", lineno + 1
return
elif process and 'UB_HELP_TABLE' in line:
print 'Creating UB help table'
output_lines_from_line = format_commands_for_rst_table(
'', ub_commands_for_help)
elif process and 'HKL_HELP_TABLE' in line:
print 'Creating HKL help table'
output_lines_from_line = format_commands_for_rst_table(
'', hkl_commands_for_help)
else:
output_lines_from_line = parse_line(
line, lineno + 1, input_file_path)
# print '\n'.join(output_lines_from_line)
output_lines.extend(output_lines_from_line)
# Write output file
if output_file_path:
with open(output_file_path, 'w') as f:
f.write('\n'.join(output_lines))
print "Wrote file:", output_file_path
# try:
# if output_file_path:
# orig_stdout = sys.stdout
# f = file(output_file_path, 'w')
# sys.stdout = f
#
#
#
# finally:
# if output_file_path:
# sys.stdout = orig_stdout
# f.close()
def parse_line(linein, lineno, filepath):
output_lines = []
if '==>' in linein:
pre, cmd = linein.split('==>')
_check_spaces_only(pre, lineno, filepath)
cmd = cmd.strip() # strip whitespace
output_lines.append(pre + ">>> " + cmd)
result_lines = _capture_magic_command_output(cmd, lineno, filepath)
# append to output
for line in result_lines:
output_lines.append(pre + line)
else:
output_lines.append(linein)
return output_lines
def _check_spaces_only(s, lineno, filepath):
for c in s:
if c != ' ':
raise Exception('Error on line %i of %s :\n text proceeding --> must be '
'spaces only' % (lineno, filepath))
def _capture_magic_command_output(magic_cmd, lineno, filepath):
orig_stdout = sys.stdout
result = StringIO()
sys.stdout = result
def log_error():
msg = "Error on line %i of %s evaluating '%s'" % (lineno, filepath, magic_cmd)
sys.stderr.write('\n' + '=' * 79 + '\n' + msg + '\n' +'v' * 79 + '\n')
return msg
try:
line_magics = get_ipython().magics_manager.magics['line']
magic = magic_cmd.split(' ')[0]
if magic not in line_magics:
msg = log_error()
raise Exception(msg + " ('%s' is not a magic command)" % magic)
get_ipython().magic(magic_cmd)
except:
log_error()
raise
finally:
sys.stdout = orig_stdout
result_lines = result.getvalue().split('\n')
# trim trailing lines which are whitespace only
while result_lines and (result_lines[-1].isspace() or not result_lines[-1]):
result_lines.pop()
return result_lines

View File

@@ -0,0 +1,88 @@
"""
start the diffcmd environemt using a script from startup.
This should normally be run by the main diffcalc.py program.
with diffcalc on PYTHONPATH
$ ipython -i -m diffcm.diffcmd module_name_string debug_bool
"""
from __future__ import absolute_import
import diffcalc
import diffcalc.settings
import os
import sys
from diffcalc.ub.persistence import UBCalculationJSONPersister
from diffcalc.ub.calcstate import UBCalcStateEncoder
from diffcalc.util import bold
import diffcalc.util
import diffcalc.gdasupport.minigda.command
DIFFCALC_ROOT = os.path.realpath(diffcalc.__file__).split('diffcalc/__init__.py')[0]
try:
__IPYTHON__ # @UndefinedVariable
IPYTHON = True
except NameError:
IPYTHON = False
module_name = sys.argv[1] #3 if IPYTHON else 1]
debug = sys.argv[2] == 'True' #4 if IPYTHON else 2])
print
print bold('-' * 34 + ' DIFFCALC ' + '-' * 35)
# configure persisentence
DIFFCALC_VAR = os.path.join(os.path.expanduser('~'), '.diffcalc', module_name)
if not os.path.exists(DIFFCALC_VAR):
print "Making diffcalc var folder:'%s'" % DIFFCALC_VAR
os.makedirs(DIFFCALC_VAR)
diffcalc.settings.ubcalc_persister = UBCalculationJSONPersister(DIFFCALC_VAR, UBCalcStateEncoder)
# configure debug
diffcalc.util.DEBUG = debug
if debug:
print "WARNING: debug mode on; help for command syntax errors disabled."
# import script
script = os.path.join(DIFFCALC_ROOT, 'startup', module_name) + '.py'
print "Startup script: '%s'" % script
namespace = {}
execfile(script, namespace)
globals().update(namespace)
diffcalc.gdasupport.minigda.command.ROOT_NAMESPACE_DICT = dict(namespace)
print bold('-' * 36 + ' Help ' + '-' * 37)
print HELP_STRING # @UndefinedVariable
if 'LOCAL_MANUAL' in locals():
print "Local: " + LOCAL_MANUAL # @UndefinedVariable
print bold('-' * 79)
print
# magic commands if IPython
if IPYTHON:
from diffcmd.ipython import magic_commands
magic_commands(globals())
if 'MANUALS_TO_MAKE' in locals():
summary_lines = ['Made manuals:']
from diffcmd.make_manual import make_manual
for source_path in MANUALS_TO_MAKE: # @UndefinedVariable
import diffcalc.ub.ub
try:
diffcalc.ub.ub.rmub('example')
except KeyError:
pass
target_path = source_path.replace('_template', '')
print '@' * 79
print "Making manual"
print " Source:", source_path
print " Target:", target_path
make_manual(source_path, target_path,
ub_commands_for_help, # @UndefinedVariable
hkl_commands_for_help) # @UndefinedVariable
summary_lines.append(' - ' + source_path + ' -- > ' + target_path)
print '\n'.join(summary_lines)