612 lines
30 KiB
Python
612 lines
30 KiB
Python
#A* -------------------------------------------------------------------
|
|||
|
|
#B* This file contains source code for the PyMOL computer program
|
||
|
|
#C* Copyright (c) Schrodinger, LLC.
|
||
|
|
#D* -------------------------------------------------------------------
|
||
|
|
#E* It is unlawful to modify or remove this copyright notice.
|
||
|
|
#F* -------------------------------------------------------------------
|
||
|
|
#G* Please see the accompanying LICENSE file for further information.
|
||
|
|
#H* -------------------------------------------------------------------
|
||
|
|
#I* Additional authors of this source file include:
|
||
|
|
#-*
|
||
|
|
#-*
|
||
|
|
#-*
|
||
|
|
#Z* -------------------------------------------------------------------
|
||
|
|
|
||
|
|
# parser.py
|
||
|
|
# Python parser module for PyMol
|
||
|
|
#
|
||
|
|
|
||
|
|
class SecurityException(Exception):
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
SCRIPT_TOPLEVEL = 'toplevel'
|
||
|
|
|
||
|
|
|
||
|
|
if True:
|
||
|
|
|
||
|
|
import pymol
|
||
|
|
import traceback
|
||
|
|
import collections
|
||
|
|
import re
|
||
|
|
import glob
|
||
|
|
import sys
|
||
|
|
import os
|
||
|
|
|
||
|
|
from . import parsing
|
||
|
|
from . import colorprinting
|
||
|
|
from .cmd import _feedback,fb_module,fb_mask,exp_path
|
||
|
|
|
||
|
|
QuietException = parsing.QuietException
|
||
|
|
CmdException = pymol.CmdException
|
||
|
|
|
||
|
|
py_delims = { '=' : 1, '+=' : 1, '-=' : 1, '*=' : 1,
|
||
|
|
'/=' :1, '//=' : 1, '%=' : 1, '&=' : 1,
|
||
|
|
'|=' :1, '^=' : 1, '>>=' : 1,'<<=' : 1,
|
||
|
|
'**=':1 }
|
||
|
|
|
||
|
|
remove_lists_re = re.compile(r"\[[^\]]*\]")
|
||
|
|
|
||
|
|
def complete_sc(st,sc,type_name,postfix, mode=0):
|
||
|
|
result = None
|
||
|
|
|
||
|
|
try:
|
||
|
|
sc=sc() # invoke lambda functions (if any)
|
||
|
|
except:
|
||
|
|
traceback.print_exc()
|
||
|
|
amb = sc.interpret(st, mode)
|
||
|
|
if amb is None:
|
||
|
|
colorprinting.warning(" parser: no matching %s."%type_name)
|
||
|
|
elif isinstance(amb, str):
|
||
|
|
result = amb+postfix
|
||
|
|
else:
|
||
|
|
amb.sort()
|
||
|
|
colorprinting.suggest(" parser: matching %s:"%type_name)
|
||
|
|
flist = [x for x in amb if x[0]!='_']
|
||
|
|
lst = parsing.list_to_str_list(flist)
|
||
|
|
for a in lst:
|
||
|
|
colorprinting.suggest(a)
|
||
|
|
# now append up to point of ambiguity
|
||
|
|
if not len(flist):
|
||
|
|
css = []
|
||
|
|
else:
|
||
|
|
css = list(flist[0]) # common sub-string (css)
|
||
|
|
for a in flist:
|
||
|
|
ac = list(a)
|
||
|
|
tmp = css
|
||
|
|
css = []
|
||
|
|
for c in range(len(tmp)):
|
||
|
|
if tmp[c]!=ac[c]:
|
||
|
|
break
|
||
|
|
css.append(tmp[c])
|
||
|
|
css = [_f for _f in css if _f]
|
||
|
|
css = ''.join(css)
|
||
|
|
if len(css)>len(st):
|
||
|
|
result = css
|
||
|
|
return result
|
||
|
|
|
||
|
|
class NestLayer:
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
|
||
|
|
self.cont = ""
|
||
|
|
self.com0 = ""
|
||
|
|
self.sc_path = SCRIPT_TOPLEVEL
|
||
|
|
self.lineno = 0
|
||
|
|
self.literal_python_fallback = False
|
||
|
|
self.embed_sentinel = None
|
||
|
|
self.embed_dict = {}
|
||
|
|
self.next = []
|
||
|
|
|
||
|
|
class Parser:
|
||
|
|
|
||
|
|
def __init__(self,cmd):
|
||
|
|
cmd = cmd._weakrefproxy
|
||
|
|
|
||
|
|
self.cmd = cmd
|
||
|
|
self.nest = 0
|
||
|
|
self.layer = collections.defaultdict(NestLayer)
|
||
|
|
self.pymol_names = self.cmd._pymol.__dict__
|
||
|
|
|
||
|
|
# parsing state implemented with dictionaries to enable safe recursion
|
||
|
|
# to arbitrary depths
|
||
|
|
|
||
|
|
#com0 = {} # verbose line, as read in
|
||
|
|
#com1 = {} # line w/o trailing whitespace
|
||
|
|
#com2 = {} # non-compound command
|
||
|
|
#cont = {} # continued characters from previous lines (i.e., before \ )
|
||
|
|
#script = {} # file handles
|
||
|
|
#sc_path = {} # file paths
|
||
|
|
#kw = {} # row in the keyword table for the current command
|
||
|
|
#input = {} # list of length two - command and unparsed arguments string
|
||
|
|
#next = {} # characters for next command (i.e., after ; )
|
||
|
|
#args = {} # parsed non-keyword argument string
|
||
|
|
#kw_args = {} # parser keyword argument string
|
||
|
|
#embed_dict = {}
|
||
|
|
#embed_list = {}
|
||
|
|
#embed_sentinel = {}
|
||
|
|
#embed_type = {}
|
||
|
|
#embed_line = {}
|
||
|
|
|
||
|
|
# The resulting value from a pymol command (if any) is stored in the
|
||
|
|
# parser.result global variable. However, script developers will
|
||
|
|
# geerally want to switch to the Python API for any of this kind of
|
||
|
|
# stuff.
|
||
|
|
|
||
|
|
self.result = None
|
||
|
|
|
||
|
|
# initialize parser
|
||
|
|
|
||
|
|
self.cmd._pymol.__script__ = SCRIPT_TOPLEVEL
|
||
|
|
|
||
|
|
def exec_python(self, s, secure=False, fallback=False):
|
||
|
|
"""Execute a python expression.
|
||
|
|
|
||
|
|
:param s: Python expression
|
||
|
|
:param secure: Must be false, otherwise raise an exception.
|
||
|
|
:param fallback: True if this is a fallback attempt and s may or
|
||
|
|
may not be a valid Python expression (used for better error
|
||
|
|
reporting).
|
||
|
|
"""
|
||
|
|
if secure:
|
||
|
|
raise SecurityException('Python expressions disallowed in this file')
|
||
|
|
|
||
|
|
layer = self.layer[self.nest]
|
||
|
|
layer.literal_python_fallback = fallback
|
||
|
|
|
||
|
|
# for meaningful line number in error messages
|
||
|
|
blanklines = layer.lineno - 1 - s.count('\n')
|
||
|
|
|
||
|
|
s = '\n' * blanklines + s + '\n'
|
||
|
|
s = compile(s, layer.sc_path, 'exec')
|
||
|
|
exec(s, self.pymol_names, self.pymol_names)
|
||
|
|
|
||
|
|
# main parser routine
|
||
|
|
|
||
|
|
def parse(self,s,secure=0):
|
||
|
|
"""Parse (and execute) commands from a single line. Multiple
|
||
|
|
commands can be concatenated with semicolon, but line feeds are
|
||
|
|
ignored.
|
||
|
|
|
||
|
|
:param s: Line to parse
|
||
|
|
:param secure: If true, then Python code and nested "embed" are not
|
||
|
|
allowed and will raise an exception.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
self.nest += 1
|
||
|
|
return self._parse(s, secure)
|
||
|
|
finally:
|
||
|
|
self.nest -= 1
|
||
|
|
|
||
|
|
def _parse(self, s, secure):
|
||
|
|
layer = self.layer[self.nest]
|
||
|
|
self.result = None
|
||
|
|
# report any uncaught errors...
|
||
|
|
# WLD: this is problematic if parse is called inside an exception...removed.
|
||
|
|
# if sys.exc_info()!=(None,None,None):
|
||
|
|
# traceback.print_exc()
|
||
|
|
# sys.exc_clear()
|
||
|
|
def parse_embed():
|
||
|
|
if s.strip() == layer.embed_sentinel:
|
||
|
|
etn = layer.embed_type
|
||
|
|
if etn == 0: # embedded data
|
||
|
|
colorprinting.parrot(" Embed: read %d lines."%(len(layer.embed_list)))
|
||
|
|
layer.embed_sentinel=None
|
||
|
|
elif etn == 1: # python block
|
||
|
|
colorprinting.parrot("PyMOL>"+s.rstrip())
|
||
|
|
py_block = ''.join(layer.embed_list)
|
||
|
|
del layer.embed_list
|
||
|
|
layer.embed_sentinel=None
|
||
|
|
self.exec_python(py_block)
|
||
|
|
elif etn == 2: # skip block
|
||
|
|
colorprinting.parrot(" Skip: skipped %d lines."%(layer.embed_line))
|
||
|
|
layer.embed_sentinel=None
|
||
|
|
else:
|
||
|
|
etn = layer.embed_type
|
||
|
|
if etn == 0: # normal embedded data
|
||
|
|
layer.embed_list.append(s.rstrip()+"\n")
|
||
|
|
elif etn == 1: # python block
|
||
|
|
el = layer.embed_line + 1
|
||
|
|
colorprinting.parrot("%5d:%s"%(el,s.rstrip()))
|
||
|
|
layer.embed_line = el
|
||
|
|
layer.embed_list.append(s.rstrip()+"\n")
|
||
|
|
elif etn == 2:
|
||
|
|
layer.embed_line = layer.embed_line + 1
|
||
|
|
|
||
|
|
p_result = 1
|
||
|
|
layer.com0 = s
|
||
|
|
try:
|
||
|
|
if layer.embed_sentinel is not None:
|
||
|
|
parse_embed()
|
||
|
|
return 1
|
||
|
|
|
||
|
|
layer.com1 = layer.com0.rstrip() # strips trailing whitespace
|
||
|
|
if len(layer.com1) > 0:
|
||
|
|
if str(layer.com1[-1]) == "\\":
|
||
|
|
# prepend leftovers
|
||
|
|
if layer.cont != '':
|
||
|
|
layer.cont = layer.cont + "\n" + layer.com1[:-1]
|
||
|
|
else:
|
||
|
|
layer.cont = layer.com1[:-1]
|
||
|
|
else:
|
||
|
|
# prepend leftovers
|
||
|
|
if layer.cont != '':
|
||
|
|
layer.com1 = layer.cont + "\n" + layer.com1
|
||
|
|
layer.cont = ''
|
||
|
|
# this routine splits up the line first based on semicolon
|
||
|
|
|
||
|
|
layer.next = parsing.split(layer.com1,';',1) + layer.next[1:]
|
||
|
|
|
||
|
|
# layer.com2 now a full non-compound command
|
||
|
|
layer.com2 = layer.next[0]
|
||
|
|
layer.input = layer.com2.split(' ',1)
|
||
|
|
lin = len(layer.input)
|
||
|
|
if lin:
|
||
|
|
layer.input[0] = layer.input[0].strip()
|
||
|
|
com = layer.input[0]
|
||
|
|
if (com[0:1]=='/'):
|
||
|
|
# explicit literal python
|
||
|
|
layer.com2 = layer.com2[1:].strip()
|
||
|
|
if len(layer.com2)>0:
|
||
|
|
self.exec_python(layer.com2, secure)
|
||
|
|
elif lin>1 and layer.input[-1:][0].split(' ',1)[0] in py_delims:
|
||
|
|
self.exec_python(layer.com2, secure)
|
||
|
|
else:
|
||
|
|
# try to find a keyword which matches
|
||
|
|
if com in self.cmd.kwhash:
|
||
|
|
amb = self.cmd.kwhash.interpret(com)
|
||
|
|
if amb is None:
|
||
|
|
com = self.cmd.kwhash[com]
|
||
|
|
elif not isinstance(amb, str):
|
||
|
|
colorprinting.warning('Error: ambiguous command: ')
|
||
|
|
amb.sort()
|
||
|
|
amb = parsing.list_to_str_list(amb)
|
||
|
|
for a in amb:
|
||
|
|
colorprinting.warning(a)
|
||
|
|
raise QuietException
|
||
|
|
com = amb
|
||
|
|
if com in self.cmd.keyword:
|
||
|
|
# here is the command and argument handling section
|
||
|
|
layer.kw = self.cmd.keyword[com]
|
||
|
|
if layer.kw[4]>=parsing.NO_CHECK:
|
||
|
|
# stricter, Python-based argument parsing
|
||
|
|
# remove line breaks (only important for Python expressions)
|
||
|
|
layer.com2=layer.com2.replace('\n','')
|
||
|
|
|
||
|
|
if layer.kw[4]>=parsing.LITERAL: # treat literally
|
||
|
|
layer.next = []
|
||
|
|
if not secure:
|
||
|
|
layer.com2=layer.com1
|
||
|
|
else:
|
||
|
|
raise SecurityException('Python expressions disallowed in this file')
|
||
|
|
if secure and (layer.kw[4]==parsing.SECURE):
|
||
|
|
layer.next = []
|
||
|
|
raise SecurityException('Command disallowed in this file')
|
||
|
|
else:
|
||
|
|
(layer.args, layer.kw_args) = \
|
||
|
|
parsing.prepare_call(
|
||
|
|
layer.kw[0],
|
||
|
|
parsing.parse_arg(layer.com2,mode=layer.kw[4],_self=self.cmd),
|
||
|
|
layer.kw[4], com, _self=self.cmd) # will raise exception on failure
|
||
|
|
self.result=layer.kw[0](*layer.args, **layer.kw_args)
|
||
|
|
elif layer.kw[4]==parsing.PYTHON:
|
||
|
|
# handle python keyword
|
||
|
|
layer.com2 = layer.com2.strip()
|
||
|
|
if len(layer.com2)>0:
|
||
|
|
self.exec_python(layer.com2, secure)
|
||
|
|
else:
|
||
|
|
# remove line breaks (only important for Python expressions)
|
||
|
|
layer.com2=layer.com2.replace('\n','')
|
||
|
|
# old parsing style, being phased out
|
||
|
|
if layer.kw[4]==parsing.ABORT:
|
||
|
|
return None # SCRIPT ABORT EXIT POINT
|
||
|
|
if layer.kw[4]==parsing.MOVIE: # copy literal single line, no breaks
|
||
|
|
layer.next = []
|
||
|
|
if not secure:
|
||
|
|
layer.input = layer.com1.split(' ',1)
|
||
|
|
else:
|
||
|
|
raise SecurityException('Movie commands disallowed in this file')
|
||
|
|
if len(layer.input)>1:
|
||
|
|
layer.args = parsing.split(layer.input[1],layer.kw[3])
|
||
|
|
while 1:
|
||
|
|
nArg = len(layer.args) - 1
|
||
|
|
c = 0
|
||
|
|
while c < nArg:
|
||
|
|
if ( layer.args[c].count('(')!=
|
||
|
|
layer.args[c].count(')')):
|
||
|
|
tmp=layer.args[c+1]
|
||
|
|
layer.args.remove(tmp)
|
||
|
|
layer.args[c]=layer.args[c].strip()+\
|
||
|
|
','+tmp.strip()
|
||
|
|
nArg = nArg-1
|
||
|
|
break;
|
||
|
|
c = c + 1
|
||
|
|
if c == nArg:
|
||
|
|
break;
|
||
|
|
if len(layer.args)==1 and len(layer.args[0])==0:
|
||
|
|
layer.args = []
|
||
|
|
else:
|
||
|
|
layer.args = []
|
||
|
|
if layer.kw[1]<= len(layer.args) <= layer.kw[2]:
|
||
|
|
layer.args = [a.strip() for a in layer.args]
|
||
|
|
if layer.kw[4]<parsing.RUN:
|
||
|
|
#
|
||
|
|
# this is where old-style commands are invoked
|
||
|
|
#
|
||
|
|
self.result=layer.kw[0](*layer.args)
|
||
|
|
#
|
||
|
|
elif (layer.kw[4]==parsing.EMBED):
|
||
|
|
layer.next = []
|
||
|
|
if secure or self.nest==0: # only legal on top level and p1m files
|
||
|
|
l = len(layer.args)
|
||
|
|
if l>0:
|
||
|
|
key = layer.args[0]
|
||
|
|
else:
|
||
|
|
key = self.get_default_key()
|
||
|
|
if l>1:
|
||
|
|
format = layer.args[1]
|
||
|
|
else:
|
||
|
|
format = 'pdb'
|
||
|
|
if l>2:
|
||
|
|
layer.embed_sentinel = layer.args[2]
|
||
|
|
else:
|
||
|
|
layer.embed_sentinel = "embed end"
|
||
|
|
list = []
|
||
|
|
layer.embed_dict[key] = ( format, list )
|
||
|
|
layer.embed_list = list
|
||
|
|
layer.embed_type = 0 # not a python block
|
||
|
|
else:
|
||
|
|
print('Error: embed only legal in special files (e.g. p1m)')
|
||
|
|
raise None
|
||
|
|
elif (layer.kw[4]==parsing.SKIP):
|
||
|
|
layer.next = []
|
||
|
|
arg = parsing.apply_arg(
|
||
|
|
parsing.parse_arg(layer.com2,_self=self.cmd),
|
||
|
|
('sentinel',),
|
||
|
|
{'sentinel':'skip end'})
|
||
|
|
print(arg) # ???
|
||
|
|
if len(layer.args):
|
||
|
|
if layer.args[0]=='end': # probable 'skip end' to ignore
|
||
|
|
arg = []
|
||
|
|
if len(arg):
|
||
|
|
layer.embed_sentinel = arg[0]
|
||
|
|
layer.embed_type = 2 # skip block
|
||
|
|
layer.embed_line = 0
|
||
|
|
elif (layer.kw[4]==parsing.PYTHON_BLOCK):
|
||
|
|
layer.next = []
|
||
|
|
if not secure:
|
||
|
|
arg = parsing.apply_arg(
|
||
|
|
parsing.parse_arg(layer.com2,_self=self.cmd),
|
||
|
|
('sentinel','skip'),
|
||
|
|
{'sentinel':'python end','skip':0})
|
||
|
|
layer.embed_sentinel = arg[0]
|
||
|
|
list = []
|
||
|
|
layer.embed_list = list
|
||
|
|
if arg[1]:
|
||
|
|
layer.embed_type = 2 # skip block
|
||
|
|
else:
|
||
|
|
layer.embed_type = 1 # python block
|
||
|
|
layer.embed_line = 0
|
||
|
|
else:
|
||
|
|
print('Error: Python blocks disallowed in this file.')
|
||
|
|
raise None
|
||
|
|
else:
|
||
|
|
print('Error: unknown keyword mode: '+str(layer.kw[4]))
|
||
|
|
raise QuietException
|
||
|
|
else:
|
||
|
|
print('Error: invalid arguments for %s command.' % com)
|
||
|
|
#
|
||
|
|
# non-keyword command handling
|
||
|
|
#
|
||
|
|
elif len(layer.input[0]):
|
||
|
|
if layer.input[0][0]=='@':
|
||
|
|
path = exp_path(layer.com2[1:].strip())
|
||
|
|
if path[-3:].lower()=='p1m':
|
||
|
|
nest_securely = 1
|
||
|
|
else:
|
||
|
|
nest_securely = secure
|
||
|
|
if re.search(r"\.py$|\.pym$",path) is not None:
|
||
|
|
if self.cmd._feedback(fb_module.parser,fb_mask.warnings):
|
||
|
|
print("Warning: use 'run' instead of '@' with Python files?")
|
||
|
|
layer.script = open(path,'r')
|
||
|
|
self.cmd._pymol.__script__ = path
|
||
|
|
self.nest=self.nest+1
|
||
|
|
self.layer[self.nest] = NestLayer()
|
||
|
|
layer = self.layer[self.nest]
|
||
|
|
layer.cont=''
|
||
|
|
layer.sc_path=path
|
||
|
|
layer.embed_sentinel=None
|
||
|
|
while 1:
|
||
|
|
layer.com0 = self.layer[self.nest-1].script.readline()
|
||
|
|
self.layer[self.nest].lineno += 1
|
||
|
|
if not layer.com0: break
|
||
|
|
inp_cmd = layer.com0
|
||
|
|
tmp_cmd = inp_cmd.strip()
|
||
|
|
if len(tmp_cmd):
|
||
|
|
if tmp_cmd[0] not in ['#','_','/']: # suppress comments, internals, python
|
||
|
|
if layer.embed_sentinel is None:
|
||
|
|
colorprinting.parrot("PyMOL>"+tmp_cmd)
|
||
|
|
elif tmp_cmd[0]=='_' and \
|
||
|
|
tmp_cmd[1:2] in [' ','']: # "_ " remove echo suppression signal
|
||
|
|
inp_cmd=inp_cmd[2:]
|
||
|
|
pp_result = self.parse(inp_cmd,nest_securely)
|
||
|
|
if pp_result is None: # RECURSION
|
||
|
|
break # abort command gets us out
|
||
|
|
elif pp_result==0: # QuietException
|
||
|
|
if self.cmd.get_setting_boolean("stop_on_exceptions"):
|
||
|
|
p_result = 0 # signal an error occurred
|
||
|
|
colorprinting.error("PyMOL: stopped on exception.")
|
||
|
|
break;
|
||
|
|
self.nest=self.nest-1
|
||
|
|
layer=self.layer[self.nest]
|
||
|
|
|
||
|
|
layer.script.close()
|
||
|
|
self.cmd._pymol.__script__ = layer.sc_path
|
||
|
|
else: # nothing found, try literal python
|
||
|
|
layer.com2 = layer.com2.strip()
|
||
|
|
if len(layer.com2)>0:
|
||
|
|
if not secure:
|
||
|
|
self.exec_python(layer.com2, fallback=True)
|
||
|
|
elif layer.input[0][0:1]!='#':
|
||
|
|
colorprinting.error('Error: unrecognized keyword: '+layer.input[0])
|
||
|
|
if (len(layer.next)>1) and p_result:
|
||
|
|
# continue parsing if no error or break has occurred
|
||
|
|
self.nest=self.nest+1
|
||
|
|
self.layer[self.nest] = NestLayer()
|
||
|
|
layer=self.layer[self.nest]
|
||
|
|
layer.com0 = self.layer[self.nest-1].next[1]
|
||
|
|
self.layer[self.nest-1].next=[]
|
||
|
|
layer.cont=''
|
||
|
|
layer.embed_sentinel=None
|
||
|
|
p_result = self.parse(layer.com0,secure) # RECURSION
|
||
|
|
self.nest=self.nest-1
|
||
|
|
layer=self.layer[self.nest]
|
||
|
|
except (QuietException, CmdException) as e:
|
||
|
|
if e.args:
|
||
|
|
colorprinting.error(e)
|
||
|
|
if self.cmd._feedback(fb_module.parser,fb_mask.blather):
|
||
|
|
print("Parser: caught " + type(e).__name__)
|
||
|
|
p_result = 0
|
||
|
|
except SecurityException as e:
|
||
|
|
colorprinting.error('Error: %s' % (e,))
|
||
|
|
p_result = None
|
||
|
|
except:
|
||
|
|
exc_type, exc_value, tb = colorprinting.print_exc(
|
||
|
|
[__file__, SCRIPT_TOPLEVEL])
|
||
|
|
|
||
|
|
p_result = 0 # notify caller that an error was encountered
|
||
|
|
if not p_result and self.cmd._pymol.invocation.options.exit_on_error:
|
||
|
|
self.cmd.quit(1)
|
||
|
|
return p_result # 0 = Exception, None = abort, 1 = ok
|
||
|
|
|
||
|
|
def get_embedded(self,key=None):
|
||
|
|
layer = self.layer[self.nest]
|
||
|
|
dict = layer.embed_dict
|
||
|
|
if key is None:
|
||
|
|
key = self.get_default_key()
|
||
|
|
return dict.get(key,None)
|
||
|
|
|
||
|
|
def get_default_key(self):
|
||
|
|
layer = self.layer[self.nest]
|
||
|
|
return os.path.splitext(os.path.basename(layer.sc_path))[0]
|
||
|
|
|
||
|
|
def parse_stream(self, stream):
|
||
|
|
"""Parse commands line-by-line from an input stream.
|
||
|
|
"""
|
||
|
|
for line in stream:
|
||
|
|
# For embedded input on stdin bypass PyMOL C code altogether
|
||
|
|
if self.nest == 0 and self.layer[0].embed_sentinel is not None:
|
||
|
|
self.parse(line)
|
||
|
|
else:
|
||
|
|
self.cmd.do(line, flush=True)
|
||
|
|
|
||
|
|
def stdin_reader(self):
|
||
|
|
"""Parse commands line-by-line from standard input. Run this
|
||
|
|
function in a daemon thread, otherwise it would lock up the GUI.
|
||
|
|
"""
|
||
|
|
import sys
|
||
|
|
|
||
|
|
if sys.stdin is None:
|
||
|
|
if "pythonw" in sys.executable:
|
||
|
|
msg = "Cannot listen to STDIN with pythonw.exe, use python.exe instead."
|
||
|
|
else:
|
||
|
|
msg = "Cannot listen to STDIN, sys.stdin is None."
|
||
|
|
raise RuntimeError(msg)
|
||
|
|
|
||
|
|
self.parse_stream(sys.stdin)
|
||
|
|
|
||
|
|
if not self.cmd._pymol.invocation.options.keep_thread_alive:
|
||
|
|
self.cmd.quit()
|
||
|
|
|
||
|
|
self.cmd._pymol._stdin_reader_thread = None
|
||
|
|
|
||
|
|
def complete(self,st):
|
||
|
|
with self.cmd.lockcm:
|
||
|
|
return self._complete(st)
|
||
|
|
|
||
|
|
def _complete(self,st):
|
||
|
|
result = None
|
||
|
|
pre = ''
|
||
|
|
flag = 0
|
||
|
|
if not (' ' in st or '@' in st):
|
||
|
|
try:
|
||
|
|
result = complete_sc(st, self.cmd.kwhash, 'commands',' ', 1)
|
||
|
|
except:
|
||
|
|
traceback.print_exc()
|
||
|
|
else:
|
||
|
|
full = self.cmd.kwhash.interpret(re.sub(r" .*","",st))
|
||
|
|
st_no_lists = remove_lists_re.sub("",st)
|
||
|
|
count = st_no_lists.count(',') # which argument are we on
|
||
|
|
if self.cmd.is_string(full):
|
||
|
|
try:
|
||
|
|
if count<len(self.cmd.auto_arg):
|
||
|
|
if full in self.cmd.auto_arg[count]: # autocomplete arguments
|
||
|
|
flag = 1
|
||
|
|
pre = re.sub(r"^[^ ]* ",' ',st,count=1) # trim command
|
||
|
|
if re.search(r",",pre) is not None:
|
||
|
|
pre = re.sub(r"[^\, ]*$","",pre,count=1)
|
||
|
|
pre = re.sub(r",\s*[^\, ]*$",", ",pre,count=1) # trim 1 arg
|
||
|
|
else:
|
||
|
|
pre = re.sub("[^ ]*$","",pre,count=1) # trim 1 arg
|
||
|
|
pre = re.sub(r"^ *",'',pre)
|
||
|
|
pre = full+' '+pre
|
||
|
|
pat = re.sub(r".*[\, ]",'',st)
|
||
|
|
# print ":"+pre+":"+pat+":"
|
||
|
|
# print tuple([pat] + self.cmd.auto_arg[count][full])
|
||
|
|
result = complete_sc(*tuple([pat] + self.cmd.auto_arg[count][full]), **{})
|
||
|
|
except:
|
||
|
|
traceback.print_exc()
|
||
|
|
if not flag: # otherwise fallback onto filename completion
|
||
|
|
st = self.cmd.as_pathstr(st)
|
||
|
|
loc = 1 + max(map(st.rfind, ',@'))
|
||
|
|
if not loc:
|
||
|
|
loc = 1 + st.find(' ')
|
||
|
|
pre = st[:loc]
|
||
|
|
st3 = st[loc:].lstrip()
|
||
|
|
flist = glob.glob(exp_path(st3)+"*")
|
||
|
|
|
||
|
|
# environment variable completion
|
||
|
|
if not flist and st3.startswith('$'):
|
||
|
|
flist = ['$' + var for var in os.environ
|
||
|
|
if var.startswith(st3[1:])]
|
||
|
|
|
||
|
|
lf = len(flist)
|
||
|
|
if lf == 0:
|
||
|
|
print(" parser: no matching files.")
|
||
|
|
elif lf==1:
|
||
|
|
result = flist[0]
|
||
|
|
if os.path.isdir(flist[0]):
|
||
|
|
result += '/' # do not use os.path.sep here
|
||
|
|
else:
|
||
|
|
flist.sort()
|
||
|
|
print(" parser: matching files:")
|
||
|
|
lst = parsing.list_to_str_list(flist)
|
||
|
|
for a in lst:
|
||
|
|
print(a)
|
||
|
|
# now append as much up to point of ambiguity
|
||
|
|
css = os.path.commonprefix(flist)
|
||
|
|
if len(css)>len(st3):
|
||
|
|
result = css
|
||
|
|
if result is not None:
|
||
|
|
result = pre+result
|
||
|
|
return result
|
||
|
|
|
||
|
|
def new_parse_closure(self_cmd): # create parser and return an instance-specific parse function closure
|
||
|
|
try:
|
||
|
|
p = Parser(self_cmd)
|
||
|
|
except:
|
||
|
|
traceback.print_exc()
|
||
|
|
self_cmd._parser = p
|
||
|
|
return lambda s,secure,p=p:p.parse(s,secure)
|
||
|
|
|
||
|
|
def new_complete_closure(self_cmd): # return an instance-specific complete function closure
|
||
|
|
return lambda st,p=self_cmd._parser:p.complete(st)
|
||
|
|
|
||
|
|
# unused code?
|
||
|
|
#
|
||
|
|
# def _same_(a,b):
|
||
|
|
# if a==b:
|
||
|
|
# return a
|
||
|
|
# else:
|
||
|
|
# return None
|