Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / command_tree.py @ 451a7992

History | View | Annotate | Download (7.5 kB)

1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34

    
35
class Command(object):
36
    """Store a command and the next-level (2 levels)"""
37
    _name = None
38
    path = None
39
    cmd_class = None
40
    subcommands = {}
41
    help = ' '
42

    
43
    def __init__(self, path, help=' ', subcommands={}, cmd_class=None):
44
        self.path = path
45
        self.help = help
46
        self.subcommands = dict(subcommands)
47
        self.cmd_class = cmd_class
48

    
49
    @property
50
    def name(self):
51
        if self._name is None:
52
            self._name = self.path.split('_')[-1]
53
        return str(self._name)
54

    
55
    def add_subcmd(self, subcmd):
56
        if subcmd.path == '%s_%s' % (self.path, subcmd.name):
57
            self.subcommands[subcmd.name] = subcmd
58
            return True
59
        return False
60

    
61
    def get_subcmd(self, name):
62
        try:
63
            return self.subcommands[name]
64
        except KeyError:
65
            return None
66

    
67
    def contains(self, name):
68
        """Check if a name is a direct child of self"""
69
        return name in self.subcommands
70

    
71
    @property
72
    def is_command(self):
73
        return self.cmd_class is not None
74

    
75
    @property
76
    def has_description(self):
77
        return len(self.help.strip()) > 0
78

    
79
    @property
80
    def description(self):
81
        return self.help
82

    
83
    @property
84
    def parent_path(self):
85
        parentpath, sep, name = self.path.rpartition('_')
86
        return parentpath
87

    
88
    def set_class(self, cmd_class):
89
        self.cmd_class = cmd_class
90

    
91
    def get_class(self):
92
        return self.cmd_class
93

    
94
    def has_subname(self, subname):
95
        return subname in self.subcommands
96

    
97
    def get_subnames(self):
98
        return self.subcommands.keys()
99

    
100
    def get_subcommands(self):
101
        return self.subcommands.values()
102

    
103
    def sublen(self):
104
        return len(self.subcommands)
105

    
106
    def parse_out(self, args):
107
        cmd = self
108
        index = 0
109
        for term in args:
110
            try:
111
                cmd = cmd.subcommands[term]
112
            except KeyError:
113
                break
114
            index += 1
115
        return cmd, args[index:]
116

    
117
    def pretty_print(self, recursive=False):
118
        print('Path: %s (Name: %s) is_cmd: %s\n\thelp: %s'\
119
            % (self.path, self.name, self.is_command, self.help))
120
        for cmd in self.get_subcommands():
121
            cmd.pretty_print(recursive)
122

    
123

    
124
class CommandTree(object):
125

    
126
    groups = {}
127
    _all_commands = {}
128
    name = None
129
    description = None
130

    
131
    def __init__(self, name, description=''):
132
        self.name = name
133
        self.description = description
134

    
135
    def add_command(self, command_path, description=None, cmd_class=None):
136
        terms = command_path.split('_')
137
        try:
138
            cmd = self.groups[terms[0]]
139
        except KeyError:
140
            cmd = Command(terms[0])
141
            self.groups[terms[0]] = cmd
142
            self._all_commands[terms[0]] = cmd
143
        path = terms[0]
144
        for term in terms[1:]:
145
            path += '_' + term
146
            try:
147
                cmd = cmd.subcommands[term]
148
            except KeyError:
149
                new_cmd = Command(path)
150
                self._all_commands[path] = new_cmd
151
                cmd.add_subcmd(new_cmd)
152
                cmd = new_cmd
153
        if cmd_class is not None:
154
            cmd.set_class(cmd_class)
155
        if description is not None:
156
            cmd.help = description
157

    
158
    def find_best_match(self, terms):
159
        """Find a command that best matches a given list of terms
160

161
        :param terms: (list of str) match them against paths in cmd_tree
162

163
        :returns: (Command, list) the matching command, the remaining terms
164
        """
165
        path = []
166
        for term in terms:
167
            check_path = path + [term]
168
            if '_'.join(check_path) not in self._all_commands:
169
                break
170
            path = check_path
171
        if path:
172
            return (self._all_commands['_'.join(path)], terms[len(path):])
173
        return (None, terms)
174

    
175
    def add_tree(self, new_tree):
176
        tname = new_tree.name
177
        tdesc = new_tree.description
178
        self.groups.update(new_tree.groups)
179
        self._all_commands.update(new_tree._all_commands)
180
        self.set_description(tname, tdesc)
181

    
182
    def has_command(self, path):
183
        return path in self._all_commands
184

    
185
    def get_command(self, path):
186
        return self._all_commands[path]
187

    
188
    def get_groups(self):
189
        return self.groups.values()
190

    
191
    def get_group_names(self):
192
        return self.groups.keys()
193

    
194
    def set_description(self, path, description):
195
        self._all_commands[path].help = description
196

    
197
    def get_description(self, path):
198
        return self._all_commands[path].help
199

    
200
    def set_class(self, path, cmd_class):
201
        self._all_commands[path].set_class(cmd_class)
202

    
203
    def get_class(self, path):
204
        return self._all_commands[path].get_class()
205

    
206
    def get_subnames(self, path=None):
207
        return self.get_group_names() if path in (None, '') \
208
        else self._all_commands[path].get_subnames()
209

    
210
    def get_subcommands(self, path=None):
211
        return self.get_groups() if path in (None, '') \
212
        else self._all_commands[path].get_subcommands()
213

    
214
    def get_parent(self, path):
215
        if '_' not in path:
216
            return None
217
        terms = path.split('_')
218
        parent_path = '_'.join(terms[:-1])
219
        return self._all_commands[parent_path]
220

    
221
    def get_closest_ancestor_command(self, path):
222
        path, sep, name = path.rpartition('_')
223
        while len(path) > 0:
224
            cmd = self._all_commands[path]
225
            if cmd.is_command:
226
                return cmd
227
            path, sep, name = path.rpartition('_')
228
        return None
229

    
230
        if '_' not in path:
231
            return None
232
        terms = path.split()[:-1]
233
        while len(terms) > 0:
234
            tmp_path = '_'.join(terms)
235
            cmd = self._all_commands[tmp_path]
236
            if cmd.is_command:
237
                return cmd
238
            terms = terms[:-1]
239
        raise KeyError('No ancestor commands')
240

    
241
    def pretty_print(self, group=None):
242
        if group is None:
243
            for group in self.groups:
244
                self.pretty_print(group)
245
        else:
246
            self.groups[group].pretty_print(recursive=True)