Complete UI/cli interface refactoring, minor bugs
[kamaki] / kamaki / cli / command_tree.py
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 add_tree(self, new_tree):
159         tname = new_tree.name
160         tdesc = new_tree.description
161         self.groups.update(new_tree.groups)
162         self._all_commands.update(new_tree._all_commands)
163         self.set_description(tname, tdesc)
164
165     def has_command(self, path):
166         return path in self._all_commands
167
168     def get_command(self, path):
169         return self._all_commands[path]
170
171     def get_groups(self):
172         return self.groups.values()
173
174     def get_group_names(self):
175         return self.groups.keys()
176
177     def set_description(self, path, description):
178         self._all_commands[path].help = description
179
180     def get_description(self, path):
181         return self._all_commands[path].help
182
183     def set_class(self, path, cmd_class):
184         self._all_commands[path].set_class(cmd_class)
185
186     def get_class(self, path):
187         return self._all_commands[path].get_class()
188
189     def get_subnames(self, path=None):
190         return self.get_group_names() if path in (None, '') \
191         else self._all_commands[path].get_subnames()
192
193     def get_subcommands(self, path=None):
194         return self.get_groups() if path in (None, '') \
195         else self._all_commands[path].get_subcommands()
196
197     def get_parent(self, path):
198         if '_' not in path:
199             return None
200         terms = path.split('_')
201         parent_path = '_'.join(terms[:-1])
202         return self._all_commands[parent_path]
203
204     def get_closest_ancestor_command(self, path):
205         path, sep, name = path.rpartition('_')
206         while len(path) > 0:
207             cmd = self._all_commands[path]
208             if cmd.is_command:
209                 return cmd
210             path, sep, name = path.rpartition('_')
211         return None
212
213         if '_' not in path:
214             return None
215         terms = path.split()[:-1]
216         while len(terms) > 0:
217             tmp_path = '_'.join(terms)
218             cmd = self._all_commands[tmp_path]
219             if cmd.is_command:
220                 return cmd
221             terms = terms[:-1]
222         raise KeyError('No ancestor commands')
223
224     def pretty_print(self, group=None):
225         if group is None:
226             for group in self.groups:
227                 self.pretty_print(group)
228         else:
229             self.groups[group].pretty_print(recursive=True)