summaryrefslogtreecommitdiff
path: root/cmd2/pyscript_bridge.py
blob: 88e12bfb0af681396f6e058c52283b70d9436459 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""Bridges calls made inside of pyscript with the Cmd2 host app while maintaining a reasonable
degree of isolation between the two"""

import argparse

class ArgparseFunctor:
    def __init__(self, cmd2_app, item, parser):
        self._cmd2_app = cmd2_app
        self._item = item
        self._parser = parser

        self._args = {}
        self.__current_subcommand_parser = parser

    def __getattr__(self, item):
        # look for sub-command
        for action in self.__current_subcommand_parser._actions:
            if not action.option_strings and isinstance(action, argparse._SubParsersAction):
                if item in action.choices:
                    # item matches the a sub-command, save our position in argparse,
                    # save the sub-command, return self to allow next level of traversal
                    self.__current_subcommand_parser = action.choices[item]
                    self._args[action.dest] = item
                    return self
        return super().__getatttr__(item)

    def __call__(self, *args, **kwargs):
        next_pos_index = 0

        has_subcommand = False
        consumed_kw = []
        for action in self.__current_subcommand_parser._actions:
            # is this a flag option?
            if action.option_strings:
                if action.dest in kwargs:
                    self._args[action.dest] = kwargs[action.dest]
                    consumed_kw.append(action.dest)
            else:
                if not isinstance(action, argparse._SubParsersAction):
                    if next_pos_index < len(args):
                        self._args[action.dest] = args[next_pos_index]
                        next_pos_index += 1
                else:
                    has_subcommand = True

        for kw in kwargs:
            if kw not in consumed_kw:
                raise TypeError('{}() got an unexpected keyword argument \'{}\''.format(
                    self.__current_subcommand_parser.prog, kw))

        if has_subcommand:
            return self
        else:
            return self._run()

    def _run(self):
        # look up command function
        func = getattr(self._cmd2_app, 'do_' + self._item)

        # reconstruct the cmd2 command from the python call
        cmd_str = ['']

        def traverse_parser(parser):
            for action in parser._actions:
                # was something provided for the argument
                if action.dest in self._args:
                    # was the argument a flag?
                    # TODO: Handle 'narg' and 'append' options
                    if action.option_strings:
                        cmd_str[0] += '"{}" "{}" '.format(action.option_strings[0], self._args[action.dest])
                    else:
                        cmd_str[0] += '"{}" '.format(self._args[action.dest])

                        if isinstance(action, argparse._SubParsersAction):
                            traverse_parser(action.choices[self._args[action.dest]])
        traverse_parser(self._parser)

        func(cmd_str[0])
        return self._cmd2_app._last_result


class PyscriptBridge(object):
    def __init__(self, cmd2_app):
        self._cmd2_app = cmd2_app
        self._last_result = None

    def __getattr__(self, item: str):
        commands = self._cmd2_app.get_all_commands()
        if item in commands:
            func = getattr(self._cmd2_app, 'do_' + item)

            try:
                parser = getattr(func, 'argparser')
            except AttributeError:
                def wrap_func(args=''):
                    func(args)
                    return self._cmd2_app._last_result
                return wrap_func
            else:
                return ArgparseFunctor(self._cmd2_app, item, parser)

        return super().__getattr__(item)

    def __call__(self, args):
        self._cmd2_app.onecmd_plus_hooks(args + '\n')
        self._last_result = self._cmd2_app._last_result
        return self._cmd2_app._last_result