1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
34 from colors import magenta, red, yellow, bold
36 #No colours? No worries, use dummy foo instead
39 red = yellow = magenta = bold
41 from .errors import CLIUnknownCommand, CLICmdIncompleteError, CLICmdSpecError, CLIError
43 class CommandTree(object):
44 """A tree of command terms usefull for fast commands checking
47 def __init__(self, run_class=None, description='', commands={}):
48 self.run_class = run_class
49 self.description = description
50 self.commands = commands
52 def get_command_names(self, prefix=[]):
53 cmd = self.get_command(prefix)
54 return cmd.commands.keys()
56 def get_terminal_commands(self, prefix=''):
57 cmd = self.get_command(prefix)
58 terminal_cmds = [prefix] if cmd.is_command() else []
59 prefix = '' if len(prefix) == 0 else '%s_'%prefix
60 for term, tree in cmd.commands.items():
61 xtra = self.get_terminal_commands(prefix+term)
62 terminal_cmds.append(*xtra)
65 def add_path(self, command, description):
66 path = get_pathlist_from_prefix(command)
70 tmp = tmp.get_command(term)
71 except CLIUnknownCommand:
73 tmp = tmp.get_command(term)
74 tmp.description = description
76 def add_command(self, new_command, new_descr='', new_class=None):
77 cmd_list = new_command.split('_')
78 cmd = self.get_command(cmd_list[:-1])
80 existing = cmd.get_command(cmd_list[-1])
81 if new_class is not None:
82 existing.run_class = new_class
83 if new_descr not in (None, ''):
84 existing.description = new_descr
85 except CLIUnknownCommand:
86 cmd.commands[new_command] = CommandTree(new_class,new_descr,{})
88 def is_command(self, command=''):
89 if self.get_command(command).run_class is None:
93 def get_class(self, command=''):
94 cmd = self.get_command(command)
96 def set_class(self, command, new_class):
97 cmd = self.get_command(command)
98 cmd.run_class = new_class
100 def get_description(self, command):
101 cmd = self.get_command(command)
102 return cmd.description
103 def set_description(self, command, new_descr):
104 cmd = self.get_command(command)
105 cmd.description = new_descr
107 def closest_complete_command(self, command):
108 path = get_pathlist_from_prefix(command)
112 tmp = tmp.get_command(term)
117 def closest_description(self, command):
118 path = get_pathlist_from_prefix(command)
119 desc = self.description
122 tmp = tmp.get_command(term)
123 if tmp.description not in [None, '']:
124 desc = tmp.description
127 def copy_command(self, prefix=''):
128 cmd = self.get_command(prefix)
129 from copy import deepcopy
132 def get_command(self, command):
134 @return a tuple of the form (cls_object, 'description text', {term1':(...), 'term2':(...)})
136 path = get_pathlist_from_prefix(command)
140 cmd = cmd.commands[term]
142 error_index = path.index(term)
143 details='Command term %s not in path %s'%(unicode(term), path[:error_index])
144 raise CLIUnknownCommand('Unknown command', details=details)
147 def print_tree(self, command=[], level = 0, tabs=0):
148 cmd = self.get_command(command)
149 command_str = '_'.join(command) if isinstance(command, list) else command
150 print(' '*tabs+command_str+': '+cmd.description)
152 for name in cmd.get_command_names():
153 new_level = level if level < 0 else (level-1)
154 cmd.print_tree(name, new_level, tabs+1)
156 def get_pathlist_from_prefix(prefix):
157 if isinstance(prefix, list):
161 return unicode(prefix).split('_')
163 def pretty_keys(d, delim='_', recurcive=False):
164 """Transform keys of a dict from the form
165 str1_str2_..._strN to the form strN
166 where _ is the delimeter
169 for key, val in d.items():
170 new_key = key.split(delim)[-1]
171 if recurcive and isinstance(val, dict):
172 new_val = pretty_keys(val, delim, recurcive)
175 new_d[new_key] = new_val
178 def print_dict(d, exclude=(), ident= 0):
179 if not isinstance(d, dict):
180 raise CLIError(message='Cannot dict_print a non-dict object')
183 1 + max(len(unicode(key).strip()) for key in d.keys() \
184 if not isinstance(key, dict) and not isinstance(key, list)),
189 for key, val in sorted(d.items()):
192 print_str = '%s:' % unicode(key).strip()
193 if isinstance(val, dict):
194 print(print_str.rjust(margin)+' {')
195 print_dict(val, exclude = exclude, ident = margin + 6)
196 print '}'.rjust(margin)
197 elif isinstance(val, list):
198 print(print_str.rjust(margin)+' [')
199 print_list(val, exclude = exclude, ident = margin + 6)
200 print ']'.rjust(margin)
202 print print_str.rjust(margin)+' '+unicode(val).strip()
204 def print_list(l, exclude=(), ident = 0):
205 if not isinstance(l, list):
206 raise CLIError(message='Cannot list_print a non-list object')
209 1 + max(len(unicode(item).strip()) for item in l \
210 if not isinstance(item, dict) and not isinstance(item, list)),
215 for item in sorted(l):
218 if isinstance(item, dict):
219 print('{'.rjust(margin))
220 print_dict(item, exclude = exclude, ident = margin + 6)
221 print '}'.rjust(margin)
222 elif isinstance(item, list):
223 print '['.rjust(margin)
224 print_list(item, exclude = exclude, ident = margin + 6)
225 print ']'.rjust(margin)
227 print unicode(item).rjust(margin)
229 def print_items(items, title=('id', 'name')):
231 if isinstance(item, dict) or isinstance(item, list):
232 print ' '.join(unicode(item.pop(key)) for key in title if key in item)
233 if isinstance(item, dict):
236 def format_size(size):
237 units = ('B', 'K', 'M', 'G', 'T')
241 raise CLIError(message='Cannot format %s in bytes'%size)
251 def dict2file(d, f, depth = 0):
252 for k, v in d.items():
253 f.write('%s%s: '%('\t'*depth, k))
254 if isinstance(v,dict):
256 dict2file(v, f, depth+1)
257 elif isinstance(v,list):
259 list2file(v, f, depth+1)
261 f.write(' %s\n'%unicode(v))
263 def list2file(l, f, depth = 1):
265 if isinstance(item,dict):
266 dict2file(item, f, depth+1)
267 elif isinstance(item,list):
268 list2file(item, f, depth+1)
270 f.write('%s%s\n'%('\t'*depth, unicode(item)))