Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / command_tree.py @ 320aac17

History | View | Annotate | Download (7.7 kB)

1
# Copyright 2012-2013 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
from kamaki.clients import Client
35

    
36

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

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

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

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

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

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

    
73
    @property
74
    def is_command(self):
75
        return self.cmd_class is not None and len(self.subcommands) == 0
76

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

    
81
    @property
82
    def description(self):
83
        return self.help
84

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

    
90
    def set_class(self, cmd_class):
91
        self.cmd_class = cmd_class
92

    
93
    def get_class(self):
94
        return self.cmd_class
95

    
96
    def has_subname(self, subname):
97
        return subname in self.subcommands
98

    
99
    def get_subnames(self):
100
        return self.subcommands.keys()
101

    
102
    def get_subcommands(self):
103
        return self.subcommands.values()
104

    
105
    def sublen(self):
106
        return len(self.subcommands)
107

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

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

    
128

    
129
class CommandTree(object):
130

    
131
    groups = {}
132
    _all_commands = {}
133
    name = None
134
    description = None
135

    
136
    def __init__(self, name, description=''):
137
        self.name = name
138
        self.description = description
139

    
140
    def exclude(self, groups_to_exclude=[]):
141
        for group in groups_to_exclude:
142
            self.groups.pop(group, None)
143

    
144
    def add_command(self, command_path, description=None, cmd_class=None):
145
        terms = command_path.split('_')
146
        try:
147
            cmd = self.groups[terms[0]]
148
        except KeyError:
149
            cmd = Command(terms[0])
150
            self.groups[terms[0]] = cmd
151
            self._all_commands[terms[0]] = cmd
152
        path = terms[0]
153
        for term in terms[1:]:
154
            path += '_' + term
155
            try:
156
                cmd = cmd.subcommands[term]
157
            except KeyError:
158
                new_cmd = Command(path)
159
                self._all_commands[path] = new_cmd
160
                cmd.add_subcmd(new_cmd)
161
                cmd = new_cmd
162
        if cmd_class:
163
            cmd.set_class(cmd_class)
164
        if description is not None:
165
            cmd.help = description
166

    
167
    def find_best_match(self, terms):
168
        """Find a command that best matches a given list of terms
169

170
        :param terms: (list of str) match them against paths in cmd_tree
171

172
        :returns: (Command, list) the matching command, the remaining terms
173
        """
174
        path = []
175
        for term in terms:
176
            check_path = path + [term]
177
            if '_'.join(check_path) not in self._all_commands:
178
                break
179
            path = check_path
180
        if path:
181
            return (self._all_commands['_'.join(path)], terms[len(path):])
182
        return (None, terms)
183

    
184
    def add_tree(self, new_tree):
185
        tname = new_tree.name
186
        tdesc = new_tree.description
187
        self.groups.update(new_tree.groups)
188
        self._all_commands.update(new_tree._all_commands)
189
        self.set_description(tname, tdesc)
190

    
191
    def has_command(self, path):
192
        return path in self._all_commands
193

    
194
    def get_command(self, path):
195
        return self._all_commands[path]
196

    
197
    def get_groups(self):
198
        return self.groups.values()
199

    
200
    def get_group_names(self):
201
        return self.groups.keys()
202

    
203
    def set_description(self, path, description):
204
        self._all_commands[path].help = description
205

    
206
    def get_description(self, path):
207
        return self._all_commands[path].help
208

    
209
    def set_class(self, path, cmd_class):
210
        self._all_commands[path].set_class(cmd_class)
211

    
212
    def get_class(self, path):
213
        return self._all_commands[path].get_class()
214

    
215
    def get_subnames(self, path=None):
216
        if path in (None, ''):
217
            return self.get_group_names()
218
        return self._all_commands[path].get_subnames()
219

    
220
    def get_subcommands(self, path=None):
221
        if path in (None, ''):
222
            return self.get_groups()
223
        return self._all_commands[path].get_subcommands()
224

    
225
    def get_parent(self, path):
226
        if '_' not in path:
227
            return None
228
        terms = path.split('_')
229
        parent_path = '_'.join(terms[:-1])
230
        return self._all_commands[parent_path]
231

    
232
    def get_closest_ancestor_command(self, path):
233
        path, sep, name = path.rpartition('_')
234
        while len(path) > 0:
235
            cmd = self._all_commands[path]
236
            if cmd.is_command:
237
                return cmd
238
            path, sep, name = path.rpartition('_')
239
        return None
240

    
241
        if '_' not in path:
242
            return None
243
        terms = path.split()[:-1]
244
        while len(terms) > 0:
245
            tmp_path = '_'.join(terms)
246
            cmd = self._all_commands[tmp_path]
247
            if cmd.is_command:
248
                return cmd
249
            terms = terms[:-1]
250
        raise KeyError('No ancestor commands')
251

    
252
    def pretty_print(self, group=None):
253
        if group is None:
254
            for group in self.groups:
255
                self.pretty_print(group)
256
        else:
257
            self.groups[group].pretty_print(recursive=True)