Skip to content

Commit 7c9f7ff

Browse files
committed
Git is strange
1 parent 33c5c7c commit 7c9f7ff

File tree

3 files changed

+224
-165
lines changed

3 files changed

+224
-165
lines changed

modules/pymol/commanding.py

Lines changed: 128 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@
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
@@ -34,6 +45,7 @@
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

Comments
 (0)