Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / command_tree / __init__.py @ 38db356b

History | View | Annotate | Download (6.6 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__(
44
            self, path,
45
            help=' ', subcommands={}, cmd_class=None, long_help=''):
46
        assert path, 'Cannot initialize a command without a command path'
47
        self.path = path
48
        self.help = help or ''
49
        self.subcommands = dict(subcommands) if subcommands else {}
50
        self.cmd_class = cmd_class or None
51
        self.long_help = '%s' % (long_help or '')
52

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

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

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

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

    
75
    @property
76
    def is_command(self):
77
        return len(self.subcommands) == 0 if self.cmd_class else False
78

    
79
    @property
80
    def parent_path(self):
81
        try:
82
            return self.path[:self.path.rindex('_')]
83
        except ValueError:
84
            return ''
85

    
86
    def parse_out(self, args):
87
        """Find the deepest subcommand matching a series of terms
88
        but stop the first time a term doesn't match
89

90
        :param args: (list) terms to match commands against
91

92
        :returns: (parsed out command, the rest of the arguments)
93

94
        :raises TypeError: if args is not inalterable
95
        """
96
        cmd = self
97
        index = 0
98
        for term in args:
99
            try:
100
                cmd = cmd.subcommands[term]
101
            except KeyError:
102
                break
103
            index += 1
104
        return cmd, args[index:]
105

    
106
    def pretty_print(self, recursive=False):
107
        print('%s\t\t(Name: %s is_cmd: %s help: %s)' % (
108
            self.path, self.name, self.is_command, self.help))
109
        for cmd in self.subcommands.values():
110
            cmd.pretty_print(recursive)
111

    
112

    
113
class CommandTree(object):
114

    
115
    def __init__(self, name, description='', long_description=''):
116
        self.name = name
117
        self.description = description
118
        self.long_description = '%s' % (long_description or '')
119
        self.groups = dict()
120
        self._all_commands = dict()
121

    
122
    def exclude(self, groups_to_exclude=[]):
123
        for group in groups_to_exclude:
124
            self.groups.pop(group, None)
125

    
126
    def add_command(
127
            self, command_path,
128
            description=None, cmd_class=None, long_description=''):
129
        terms = command_path.split('_')
130
        try:
131
            cmd = self.groups[terms[0]]
132
        except KeyError:
133
            cmd = Command(terms[0])
134
            self.groups[terms[0]] = cmd
135
            self._all_commands[terms[0]] = cmd
136
        path = terms[0]
137
        for term in terms[1:]:
138
            path += '_' + term
139
            try:
140
                cmd = cmd.subcommands[term]
141
            except KeyError:
142
                new_cmd = Command(path)
143
                self._all_commands[path] = new_cmd
144
                cmd.add_subcmd(new_cmd)
145
                cmd = new_cmd
146
        cmd.cmd_class = cmd_class or None
147
        cmd.help = description or None
148
        cmd.long_help = long_description or cmd.long_help
149

    
150
    def find_best_match(self, terms):
151
        """Find a command that best matches a given list of terms
152

153
        :param terms: (list of str) match against paths in cmd_tree, e.g.
154
            ['aa', 'bb', 'cc'] matches aa_bb_cc
155

156
        :returns: (Command, list) the matching command, the remaining terms or
157
            None
158
        """
159
        path = []
160
        for term in terms:
161
            check_path = path + [term]
162
            if '_'.join(check_path) not in self._all_commands:
163
                break
164
            path = check_path
165
        if path:
166
            return (self._all_commands['_'.join(path)], terms[len(path):])
167
        return (None, terms)
168

    
169
    def add_tree(self, new_tree):
170
        tname = new_tree.name
171
        tdesc = new_tree.description
172
        self.groups.update(new_tree.groups)
173
        self._all_commands.update(new_tree._all_commands)
174
        try:
175
            self._all_commands[tname].help = tdesc
176
        except KeyError:
177
            self.add_command(tname, tdesc)
178

    
179
    def has_command(self, path):
180
        return path in self._all_commands
181

    
182
    def get_command(self, path):
183
        return self._all_commands[path]
184

    
185
    def subnames(self, path=None):
186
        if path in (None, ''):
187
            return self.groups.keys()
188
        return self._all_commands[path].subcommands.keys()
189

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

    
194
    def pretty_print(self, group=None):
195
        if group is None:
196
            for group in self.groups:
197
                self.pretty_print(group)
198
        else:
199
            self.groups[group].pretty_print(recursive=True)