|
18 | 18 | if True:
|
19 | 19 | import _thread as thread
|
20 | 20 | import urllib.request as urllib2
|
21 |
| - from io import FileIO as file |
| 21 | + from io import FileIO as file, BytesIO |
| 22 | + |
| 23 | + import inspect |
| 24 | + import glob |
| 25 | + import shlex |
| 26 | + import tokenize |
| 27 | + from enum import Enum |
| 28 | + from functools import wraps |
| 29 | + from pathlib import Path |
| 30 | + from textwrap import dedent |
| 31 | + from typing import Tuple, Iterable, get_args, Optional, Union, Any, NewType, List, get_origin |
| 32 | + |
22 | 33 |
|
23 | 34 | import re
|
24 | 35 | import os
|
25 | 36 | import time
|
| 37 | + import builtins |
26 | 38 | import threading
|
27 | 39 | import traceback
|
28 | 40 | from . import colorprinting
|
@@ -529,6 +541,168 @@ def delete(name, *, _self=cmd):
|
529 | 541 | if _self._raising(r,_self): raise pymol.CmdException
|
530 | 542 | return r
|
531 | 543 |
|
| 544 | + # Selection = NewType('Selection', str) |
| 545 | + |
| 546 | + def _into_types(type, value): |
| 547 | + if repr(type) == 'typing.Any': |
| 548 | + return value |
| 549 | + elif type is bool: |
| 550 | + if isinstance(value, bool): |
| 551 | + return value |
| 552 | + if value.lower() in ["yes", "1", "true", "on", "y"]: |
| 553 | + return True |
| 554 | + elif value.lower() in ["no", "0", "false", "off", "n"]: |
| 555 | + return False |
| 556 | + else: |
| 557 | + raise pymol.CmdException("Invalid boolean value: %s" % value) |
| 558 | + |
| 559 | + elif isinstance(type, builtins.type): |
| 560 | + return type(value) |
| 561 | + |
| 562 | + if origin := get_origin(type): |
| 563 | + if not repr(origin).startswith('typing.') and issubclass(origin, tuple): |
| 564 | + args = get_args(type) |
| 565 | + new_values = [] |
| 566 | + for i, new_value in enumerate(shlex.split(value)): |
| 567 | + new_values.append(_into_types(args[i], new_value)) |
| 568 | + return tuple(new_values) |
| 569 | + |
| 570 | + elif origin == Union: |
| 571 | + args = get_args(type) |
| 572 | + found = False |
| 573 | + for i, arg in enumerate(args): |
| 574 | + try: |
| 575 | + found = True |
| 576 | + return _into_types(arg, value) |
| 577 | + except: |
| 578 | + found = False |
| 579 | + if not found: |
| 580 | + raise pymol.CmdException(f"Union was not able to cast %s" % value) |
| 581 | + |
| 582 | + elif issubclass(list, origin): |
| 583 | + args = get_args(type) |
| 584 | + if len(args) > 0: |
| 585 | + f = args[0] |
| 586 | + else: |
| 587 | + f = lambda x: x |
| 588 | + return [f(i) for i in shlex.split(value)] |
| 589 | + |
| 590 | + |
| 591 | + # elif value is None: |
| 592 | + # origin = get_origin(type) |
| 593 | + # if origin is None: |
| 594 | + # return None |
| 595 | + # else: |
| 596 | + # return _into_types(origin) |
| 597 | + # for arg in get_args(origin): |
| 598 | + # return _into_types(get_args(origin), value) |
| 599 | + |
| 600 | + elif isinstance(type, str): |
| 601 | + return str(value) |
| 602 | + |
| 603 | + raise pymol.CmdException(f"Unsupported argument type {type}") |
| 604 | + |
| 605 | + def parse_documentation(func): |
| 606 | + source = inspect.getsource(func) |
| 607 | + tokens = tokenize.tokenize(BytesIO(source.encode('utf-8')).readline) |
| 608 | + tokens = list(tokens) |
| 609 | + comments = [] |
| 610 | + params = {} |
| 611 | + i = -1 |
| 612 | + started = False |
| 613 | + while True: |
| 614 | + i += 1 |
| 615 | + if tokens[i].string == "def": |
| 616 | + while tokens[i].string == "(": |
| 617 | + i += 1 |
| 618 | + started = True |
| 619 | + continue |
| 620 | + if not started: |
| 621 | + continue |
| 622 | + if tokens[i].string == "->": |
| 623 | + break |
| 624 | + if tokens[i].type == tokenize.NEWLINE: |
| 625 | + break |
| 626 | + if tokens[i].string == ")": |
| 627 | + break |
| 628 | + if tokens[i].type == tokenize.COMMENT: |
| 629 | + comments.append(tokens[i].string) |
| 630 | + continue |
| 631 | + if tokens[i].type == tokenize.NAME and tokens[i+1].string == ":": |
| 632 | + name = tokens[i].string |
| 633 | + name_line = tokens[i].line |
| 634 | + i += 1 |
| 635 | + while not (tokens[i].type == tokenize.NAME and tokens[i+1].string == ":"): |
| 636 | + if tokens[i].type == tokenize.COMMENT and tokens[i].line == name_line: |
| 637 | + comments.append(tokens[i].string) |
| 638 | + break |
| 639 | + elif tokens[i].type == tokenize.NEWLINE: |
| 640 | + break |
| 641 | + i += 1 |
| 642 | + else: |
| 643 | + i -= 3 |
| 644 | + docs = ' '.join(c[1:].strip() for c in comments) |
| 645 | + params[name] = docs |
| 646 | + comments = [] |
| 647 | + return params |
| 648 | + |
| 649 | + |
| 650 | + def declare_command(name, function=None, _self=cmd): |
| 651 | + |
| 652 | + if function is None: |
| 653 | + name, function = name.__name__, name |
| 654 | + |
| 655 | + # docstring text, if present, should be dedented |
| 656 | + if function.__doc__ is not None: |
| 657 | + function.__doc__ = dedent(function.__doc__) |
| 658 | + |
| 659 | + # Analysing arguments |
| 660 | + spec = inspect.getfullargspec(function) |
| 661 | + kwargs_ = {} |
| 662 | + args_ = spec.args[:] |
| 663 | + defaults = list(spec.defaults or []) |
| 664 | + |
| 665 | + args2_ = args_[:] |
| 666 | + while args_ and defaults: |
| 667 | + kwargs_[args_.pop(-1)] = defaults.pop(-1) |
| 668 | + |
| 669 | + funcs = {} |
| 670 | + for idx, (var, func) in enumerate(spec.annotations.items()): |
| 671 | + funcs[var] = func |
| 672 | + |
| 673 | + # Inner function that will be callable every time the command is executed |
| 674 | + @wraps(function) |
| 675 | + def inner(*args, **kwargs): |
| 676 | + frame = traceback.format_stack()[-2] |
| 677 | + caller = frame.split("\"", maxsplit=2)[1] |
| 678 | + # It was called from command line or pml script, so parse arguments |
| 679 | + if caller.endswith("pymol/parser.py"): |
| 680 | + kwargs = {**kwargs, **dict(zip(args2_, args))} |
| 681 | + kwargs.pop("_self", None) |
| 682 | + new_kwargs = {} |
| 683 | + for var, type in funcs.items(): |
| 684 | + if var in kwargs: |
| 685 | + value = kwargs[var] |
| 686 | + new_kwargs[var] = _into_types(type, value) |
| 687 | + final_kwargs = {} |
| 688 | + for k, v in kwargs_.items(): |
| 689 | + final_kwargs[k] = v |
| 690 | + for k, v in new_kwargs.items(): |
| 691 | + if k not in final_kwargs: |
| 692 | + final_kwargs[k] = v |
| 693 | + return function(**final_kwargs) |
| 694 | + |
| 695 | + # It was called from Python, so pass the arguments as is |
| 696 | + else: |
| 697 | + return function(*args, **kwargs) |
| 698 | + inner.__arg_docs = parse_documentation(function) |
| 699 | + |
| 700 | + _self.keyword[name] = [inner, 0,0,',',parsing.STRICT] |
| 701 | + _self.kwhash.append(name) |
| 702 | + _self.help_sc.append(name) |
| 703 | + return inner |
| 704 | + |
| 705 | + |
532 | 706 | def extend(name, function=None, _self=cmd):
|
533 | 707 |
|
534 | 708 | '''
|
|
0 commit comments