2020 if True :
2121 import _thread as thread
2222 import urllib .request as urllib2
23- from io import FileIO as file
23+ from io import FileIO as file , BytesIO
24+
25+ import inspect
26+ import glob
27+ import shlex
28+ import tokenize
29+ from enum import Enum
30+ from functools import wraps
31+ from pathlib import Path
32+ from textwrap import dedent
33+ from typing import Tuple , Iterable , get_args , Optional , Union , Any , NewType , List , get_origin
34+
2435
2536 import inspect
2637 import glob
3445 import re
3546 import os
3647 import time
48+ import builtins
3749 import threading
3850 import traceback
3951 from . import colorprinting
@@ -599,45 +611,119 @@ def get_state_list(states_str):
599611 states_list = sorted (set (map (int , output )))
600612 return _cmd .delete_states (_self ._COb , name , states_list )
601613
602- class Selection (str ):
603- pass
604-
605-
606- def _parse_bool (value : str ):
607- if isinstance (value , str ):
614+ # Selection = NewType('Selection', str)
615+
616+ def _into_types (type , value ):
617+ if repr (type ) == 'typing.Any' :
618+ return value
619+ elif type is bool :
620+ if isinstance (value , bool ):
621+ return value
608622 if value .lower () in ["yes" , "1" , "true" , "on" , "y" ]:
609623 return True
610624 elif value .lower () in ["no" , "0" , "false" , "off" , "n" ]:
611625 return False
612626 else :
613- raise Exception ("Invalid boolean value: %s" % value )
614- elif isinstance (value , bool ):
615- return value
616- else :
617- raise Exception (f"Unsuported boolean flag { value } " )
618-
619- def _parse_list_str (value ):
620- return shlex .split (value )
621-
622- def _parse_list_int (value ):
623- return list (map (int , shlex .split (value )))
627+ raise pymol .CmdException ("Invalid boolean value: %s" % value )
628+
629+ elif isinstance (type , builtins .type ):
630+ return type (value )
631+
632+ if origin := get_origin (type ):
633+ if not repr (origin ).startswith ('typing.' ) and issubclass (origin , tuple ):
634+ args = get_args (type )
635+ new_values = []
636+ for i , new_value in enumerate (shlex .split (value )):
637+ new_values .append (_into_types (args [i ], new_value ))
638+ return tuple (new_values )
639+
640+ elif origin == Union :
641+ args = get_args (type )
642+ found = False
643+ for i , arg in enumerate (args ):
644+ try :
645+ found = True
646+ return _into_types (arg , value )
647+ except :
648+ found = False
649+ if not found :
650+ raise pymol .CmdException (f"Union was not able to cast %s" % value )
651+
652+ elif issubclass (list , origin ):
653+ args = get_args (type )
654+ if len (args ) > 0 :
655+ f = args [0 ]
656+ else :
657+ f = lambda x : x
658+ return [f (i ) for i in shlex .split (value )]
659+
660+ # elif value is None:
661+ # origin = get_origin(type)
662+ # if origin is None:
663+ # return None
664+ # else:
665+ # return _into_types(origin)
666+ # for arg in get_args(origin):
667+ # return _into_types(get_args(origin), value)
668+
669+ elif isinstance (type , str ):
670+ return str (value )
671+
672+ raise pymol .CmdException (f"Unsupported argument type { type } " )
673+
674+ def parse_documentation (func ):
675+ source = inspect .getsource (func )
676+ tokens = tokenize .tokenize (BytesIO (source .encode ('utf-8' )).readline )
677+ tokens = list (tokens )
678+ comments = []
679+ params = {}
680+ i = - 1
681+ started = False
682+ while True :
683+ i += 1
684+ if tokens [i ].string == "def" :
685+ while tokens [i ].string == "(" :
686+ i += 1
687+ started = True
688+ continue
689+ if not started :
690+ continue
691+ if tokens [i ].string == "->" :
692+ break
693+ if tokens [i ].type == tokenize .NEWLINE :
694+ break
695+ if tokens [i ].string == ")" :
696+ break
697+ if tokens [i ].type == tokenize .COMMENT :
698+ comments .append (tokens [i ].string )
699+ continue
700+ if tokens [i ].type == tokenize .NAME and tokens [i + 1 ].string == ":" :
701+ name = tokens [i ].string
702+ name_line = tokens [i ].line
703+ i += 1
704+ while not (tokens [i ].type == tokenize .NAME and tokens [i + 1 ].string == ":" ):
705+ if tokens [i ].type == tokenize .COMMENT and tokens [i ].line == name_line :
706+ comments .append (tokens [i ].string )
707+ break
708+ elif tokens [i ].type == tokenize .NEWLINE :
709+ break
710+ i += 1
711+ else :
712+ i -= 3
713+ docs = ' ' .join (c [1 :].strip () for c in comments )
714+ params [name ] = docs
715+ comments = []
716+ return params
624717
625- def _parse_list_float (value ):
626- return list (map (float , shlex .split (value )))
627718
628719 def declare_command (name , function = None , _self = cmd ):
720+
629721 if function is None :
630722 name , function = name .__name__ , name
631723
632- # new style commands should have annotations
633- annotations = [a for a in function .__annotations__ if a != "return" ]
634- if function .__code__ .co_argcount != len (annotations ):
635- raise Exception ("Messy annotations" )
636-
637724 # docstring text, if present, should be dedented
638725 if function .__doc__ is not None :
639- function .__doc__ = dedent (function .__doc__ ).strip ()
640-
726+ function .__doc__ = dedent (function .__doc__ )
641727
642728 # Analysing arguments
643729 spec = inspect .getfullargspec (function )
@@ -658,35 +744,29 @@ def declare_command(name, function=None, _self=cmd):
658744 def inner (* args , ** kwargs ):
659745 frame = traceback .format_stack ()[- 2 ]
660746 caller = frame .split ("\" " , maxsplit = 2 )[1 ]
661-
662747 # It was called from command line or pml script, so parse arguments
663748 if caller .endswith ("pymol/parser.py" ):
664- kwargs = {** kwargs_ , ** kwargs , ** dict (zip (args2_ , args ))}
749+ kwargs = {** kwargs , ** dict (zip (args2_ , args ))}
665750 kwargs .pop ("_self" , None )
666- for arg in kwargs .copy ():
667- if funcs [arg ] == bool :
668- funcs [arg ] = _parse_bool
669- elif funcs [arg ] == List [str ]:
670- funcs [arg ] = _parse_list_str
671- elif funcs [arg ] == List [int ]:
672- funcs [arg ] = _parse_list_int
673- elif funcs [arg ] == List [float ]:
674- funcs [arg ] = _parse_list_float
675- else :
676- # Assume it's a literal supported type
677- pass
678- # Convert the argument to the correct type
679- kwargs [arg ] = funcs [arg ](kwargs [arg ])
680- return function (** kwargs )
751+ new_kwargs = {}
752+ for var , type in funcs .items ():
753+ if var in kwargs :
754+ value = kwargs [var ]
755+ new_kwargs [var ] = _into_types (type , value )
756+ final_kwargs = {}
757+ for k , v in kwargs_ .items ():
758+ final_kwargs [k ] = v
759+ for k , v in new_kwargs .items ():
760+ if k not in final_kwargs :
761+ final_kwargs [k ] = v
762+ return function (** final_kwargs )
681763
682764 # It was called from Python, so pass the arguments as is
683765 else :
684766 return function (* args , ** kwargs )
767+ inner .__arg_docs = parse_documentation (function )
685768
686- name = function .__name__
687- _self .keyword [name ] = [inner , 0 , 0 , "," , parsing .STRICT ]
688- _self .kwhash .append (name )
689- _self .help_sc .append (name )
769+ _self .keyword [name ] = [inner , 0 ,0 ,',' ,parsing .STRICT ]
690770 return inner
691771
692772 def extend (name , function = None , _self = cmd ):
0 commit comments