Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / command_tree / __init__.py @ eb46e9a1

History | View | Annotate | Download (7.1 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

    
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
        assert path, 'Cannot initialize a command without a command path'
45
        self.path = path
46
        self.help = help or ''
47
        self.subcommands = dict(subcommands) if subcommands else {}
48
        self.cmd_class = cmd_class or None
49

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

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

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

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

    
72
    @property
73
    def is_command(self):
74
        return len(self.subcommands) == 0 if self.cmd_class else False
75

    
76
    @property
77
    def parent_path(self):
78
        try:
79
            return self.path[self.path.rindex('_') + 1:]
80
        except ValueError:
81
            return ''
82

    
83
    def parse_out(self, args):
84
        cmd = self
85
        index = 0
86
        for term in args:
87
            try:
88
                cmd = cmd.subcommands[term]
89
            except KeyError:
90
                break
91
            index += 1
92
        return cmd, args[index:]
93

    
94
    def pretty_print(self, recursive=False):
95
        print('Path: %s (Name: %s) is_cmd: %s\n\thelp: %s' % (
96
            self.path,
97
            self.name,
98
            self.is_command,
99
            self.help))
100
        for cmd in self.subcommands.values():
101
            cmd.pretty_print(recursive)
102

    
103

    
104
class CommandTree(object):
105

    
106
    groups = {}
107
    _all_commands = {}
108
    name = None
109
    description = None
110

    
111
    def __init__(self, name, description=''):
112
        self.name = name
113
        self.description = description
114

    
115
    def exclude(self, groups_to_exclude=[]):
116
        for group in groups_to_exclude:
117
            self.groups.pop(group, None)
118

    
119
    def add_command(self, command_path, description=None, cmd_class=None):
120
        terms = command_path.split('_')
121
        try:
122
            cmd = self.groups[terms[0]]
123
        except KeyError:
124
            cmd = Command(terms[0])
125
            self.groups[terms[0]] = cmd
126
            self._all_commands[terms[0]] = cmd
127
        path = terms[0]
128
        for term in terms[1:]:
129
            path += '_' + term
130
            try:
131
                cmd = cmd.subcommands[term]
132
            except KeyError:
133
                new_cmd = Command(path)
134
                self._all_commands[path] = new_cmd
135
                cmd.add_subcmd(new_cmd)
136
                cmd = new_cmd
137
        if cmd_class:
138
            cmd.cmd_class = cmd_class
139
        if description is not None:
140
            cmd.help = description
141

    
142
    def find_best_match(self, terms):
143
        """Find a command that best matches a given list of terms
144

145
        :param terms: (list of str) match them against paths in cmd_tree
146

147
        :returns: (Command, list) the matching command, the remaining terms
148
        """
149
        path = []
150
        for term in terms:
151
            check_path = path + [term]
152
            if '_'.join(check_path) not in self._all_commands:
153
                break
154
            path = check_path
155
        if path:
156
            return (self._all_commands['_'.join(path)], terms[len(path):])
157
        return (None, terms)
158

    
159
    def add_tree(self, new_tree):
160
        tname = new_tree.name
161
        tdesc = new_tree.description
162
        self.groups.update(new_tree.groups)
163
        self._all_commands.update(new_tree._all_commands)
164
        self.set_description(tname, tdesc)
165

    
166
    def has_command(self, path):
167
        return path in self._all_commands
168

    
169
    def get_command(self, path):
170
        return self._all_commands[path]
171

    
172
    def get_groups(self):
173
        return self.groups.values()
174

    
175
    def get_group_names(self):
176
        return self.groups.keys()
177

    
178
    def set_description(self, path, description):
179
        self._all_commands[path].help = description
180

    
181
    def get_description(self, path):
182
        return self._all_commands[path].help
183

    
184
    def get_subnames(self, path=None):
185
        if path in (None, ''):
186
            return self.get_group_names()
187
        return self._all_commands[path].subcommands.keys()
188

    
189
    def get_subcommands(self, path=None):
190
        return self._all_commands[path].subcommands.values() if (
191
            path) else self.get_groups()
192

    
193
    def get_parent(self, path):
194
        if '_' not in path:
195
            return None
196
        terms = path.split('_')
197
        parent_path = '_'.join(terms[:-1])
198
        return self._all_commands[parent_path]
199

    
200
    def get_closest_ancestor_command(self, path):
201
        path, sep, name = path.rpartition('_')
202
        while len(path) > 0:
203
            cmd = self._all_commands[path]
204
            if cmd.is_command:
205
                return cmd
206
            path, sep, name = path.rpartition('_')
207
        return None
208

    
209
        if '_' not in path:
210
            return None
211
        terms = path.split()[:-1]
212
        while len(terms) > 0:
213
            tmp_path = '_'.join(terms)
214
            cmd = self._all_commands[tmp_path]
215
            if cmd.is_command:
216
                return cmd
217
            terms = terms[:-1]
218
        raise KeyError('No ancestor commands')
219

    
220
    def pretty_print(self, group=None):
221
        if group is None:
222
            for group in self.groups:
223
                self.pretty_print(group)
224
        else:
225
            self.groups[group].pretty_print(recursive=True)