Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / utils.py @ 017d37ce

History | View | Annotate | Download (10.2 kB)

1
# Copyright 2011 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
try:
34
    from colors import magenta, red, yellow, bold
35
except ImportError:
36
    #No colours? No worries, use dummy foo instead
37
    def bold(val):
38
        return val
39
    red = yellow = magenta = bold
40

    
41
from .errors import CLIUnknownCommand, CLICmdIncompleteError, CLICmdSpecError, CLIError
42

    
43
"""
44
def magenta(val):
45
    return magenta(val)
46
def red(val):
47
    return red(val)
48
def yellow(val):
49
    return yellow(val)
50
def bold(val):
51
    return bold(val)
52
"""
53

    
54
class CommandTree(object):
55
    """A tree of command terms usefull for fast commands checking
56
    None key is used to denote that its parent is a terminal symbol
57
    and also the command spec class
58
    e.g. add(store_list_all) will result to this:
59
        {'store': {
60
            'list': {
61
                'all': {
62
                    '_class':<store_list_all class>
63
                }
64
            }
65
        }
66
    then add(store_list) and store_info will create this:
67
        {'store': {
68
            'list': {
69
                '_class': <store_list class>
70
                'all': {
71
                    '_description': 'detail list of all containers in account'
72
                    '_class': <store_list_all class>
73
                },
74
            'info': {
75
                '_class': <store_info class>
76
                }
77
            }
78
        }
79
    """
80

    
81
    cmd_spec_locations = [
82
        'kamaki.cli.commands',
83
        'kamaki.commands',
84
        'kamaki.cli',
85
        'kamaki',
86
        '']
87

    
88
    def __init__(self):
89
        self._commands = {}
90

    
91
    def set_groups(self, groups):
92
        for grp in groups:
93
            self._commands[grp] = {}
94

    
95
    def get_groups(self):
96
        return self._commands.keys()
97

    
98
    def _get_commands_from_prefix(self, prefix):
99
        if len(prefix) == 0:
100
            return self._commands
101
        path = get_pathlist_from_prefix(prefix)
102
        next_list = self._commands
103
        try:
104
            for cmd in path:
105
                next_list = next_list[unicode(cmd)]
106
        except TypeError, KeyError:
107
            error_index = path.index(cmd)
108
            details='Command %s not in path %s'%(unicode(cmd), path[:error_index])
109
            raise CLIUnknownCommand('Unknown command', details=details)
110
        assert isinstance(next_list,dict)
111
        return next_list 
112

    
113
    def list(self, prefix=[]):
114
        """ List the commands after prefix
115
        @param prefix can be either cmd1_cmd2_... or ['cmd1', 'cmd2', ...]
116
        """
117
        next_list =  self._get_commands_from_prefix(prefix)
118
        ret = next_list.keys()
119
        try:
120
            ret = ret.remove('_description')
121
        except ValueError:
122
            pass
123
        try:
124
            return ret.remove('_class')
125
        except ValueError:
126
            return ret
127

    
128
    def get_class(self, command):
129
        """ Check if a command exists as a full/terminal command
130
        e.g. store_list is full, store is partial, stort is not existing
131
        @param command can either be a cmd1_cmd2_... str or a ['cmd1, cmd2, ...'] list
132
        @return True if this command is in this Command Tree, False otherwise
133
        @raise CLIUnknownCommand if command is unknown to this tree
134
        """
135
        next_level = self._get_commands_from_prefix(command)
136
        try:
137
            return next_level['_class']
138
        except KeyError:
139
            raise CLICmdIncompleteError(details='Cmd %s is not a full cmd'%command)
140

    
141
    def add(self, command, cmd_class):
142
        """Add a command_path-->cmd_class relation to the path """
143
        path_list = get_pathlist_from_prefix(command)
144
        cmds = self._commands
145
        for cmd in path_list:
146
            if not cmds.has_key(cmd):
147
                cmds[cmd] = {}
148
            cmds = cmds[cmd]
149
        cmds['_class'] = cmd_class #make it terminal
150

    
151
    def set_description(self, command, description):
152
        """Add a command_path-->description to the path"""
153
        path_list = get_pathlist_from_prefix(command)
154
        cmds = self._commands
155
        for cmd in path_list:
156
            try:
157
                cmds = cmds[cmd]
158
            except KeyError:
159
                raise CLIUnknownCommand(details='set_description to cmd %s failed: cmd not found'%command)
160
        cmds['_description'] = description
161

    
162
    def load_spec_package(self, spec_package):
163
        loaded = False
164
        for location in self.cmd_spec_locations:
165
            location += spec_package if location == '' else '.%s'%spec_package
166
            try:
167
                __import__(location) #a class decorator will put evetyrhing in place
168
                loaded = True
169
                break
170
            except ImportError:
171
                pass
172
        if not loaded:
173
            raise CLICmdSpecError(details='Cmd Spec Package %s load failed'%spec_package)
174

    
175
    def load_spec(self, spec_package, spec):
176
        """Load spec from a non nessecery loaded spec package"""
177

    
178
        loaded = False
179
        for location in self.cmd_spec_locations:
180
            location += spec_package if location == '' else '.%s'%spec_package
181
            try:
182
                __import__(location, fromlist=[spec])
183
                loaded = True
184
                break
185
            except ImportError:
186
                pass
187
        if not loaded:
188
            raise CLICmdSpecError('Cmd Spec %s load failed'%spec)
189

    
190
def get_pathlist_from_prefix(prefix):
191
    if isinstance(prefix, list):
192
        return prefix
193
    if len(prefix) == 0:
194
        return []
195
    return unicode(prefix).split('_')
196

    
197
def pretty_keys(d, delim='_', recurcive=False):
198
    """Transform keys of a dict from the form
199
    str1_str2_..._strN to the form strN
200
    where _ is the delimeter
201
    """
202
    new_d = {}
203
    for key, val in d.items():
204
        new_key = key.split(delim)[-1]
205
        if recurcive and isinstance(val, dict):
206
            new_val = pretty_keys(val, delim, recurcive) 
207
        else:
208
            new_val = val
209
        new_d[new_key] = new_val
210
    return new_d
211

    
212
def print_dict(d, exclude=(), ident= 0):
213
    if not isinstance(d, dict):
214
        raise CLIError(message='Cannot dict_print a non-dict object')
215
    try:
216
        margin = max(
217
            1 + max(len(unicode(key).strip()) for key in d.keys() \
218
                if not isinstance(key, dict) and not isinstance(key, list)),
219
            ident)
220
    except ValueError:
221
        margin = ident
222

    
223
    for key, val in sorted(d.items()):
224
        if key in exclude:
225
            continue
226
        print_str = '%s:' % unicode(key).strip()
227
        if isinstance(val, dict):
228
            print(print_str.rjust(margin)+' {')
229
            print_dict(val, exclude = exclude, ident = margin + 6)
230
            print '}'.rjust(margin)
231
        elif isinstance(val, list):
232
            print(print_str.rjust(margin)+' [')
233
            print_list(val, exclude = exclude, ident = margin + 6)
234
            print ']'.rjust(margin)
235
        else:
236
            print print_str.rjust(margin)+' '+unicode(val).strip()
237

    
238
def print_list(l, exclude=(), ident = 0):
239
    if not isinstance(l, list):
240
        raise CLIError(message='Cannot list_print a non-list object')
241
    try:
242
        margin = max(
243
            1 + max(len(unicode(item).strip()) for item in l \
244
                if not isinstance(item, dict) and not isinstance(item, list)),
245
            ident)
246
    except ValueError:
247
        margin = ident
248

    
249
    for item in sorted(l):
250
        if item in exclude:
251
            continue
252
        if isinstance(item, dict):
253
            print('{'.rjust(margin))
254
            print_dict(item, exclude = exclude, ident = margin + 6)
255
            print '}'.rjust(margin)
256
        elif isinstance(item, list):
257
            print '['.rjust(margin)
258
            print_list(item, exclude = exclude, ident = margin + 6)
259
            print ']'.rjust(margin)
260
        else:
261
            print unicode(item).rjust(margin)
262

    
263
def print_items(items, title=('id', 'name')):
264
    for item in items:
265
        if isinstance(item, dict) or isinstance(item, list):
266
            print ' '.join(unicode(item.pop(key)) for key in title if key in item)
267
        if isinstance(item, dict):
268
            print_dict(item)
269

    
270
def format_size(size):
271
    units = ('B', 'K', 'M', 'G', 'T')
272
    try:
273
        size = float(size)
274
    except ValueError:
275
        raise CLIError(message='Cannot format %s in bytes'%size)
276
    for unit in units:
277
        if size < 1024:
278
            break
279
        size /= 1024
280
    s = ('%.1f' % size)
281
    if '.0' == s[-2:]:
282
        s = s[:-2]
283
    return s + unit
284

    
285
def dict2file(d, f, depth = 0):
286
    for k, v in d.items():
287
        f.write('%s%s: '%('\t'*depth, k))
288
        if isinstance(v,dict):
289
            f.write('\n')
290
            dict2file(v, f, depth+1)
291
        elif isinstance(v,list):
292
            f.write('\n')
293
            list2file(v, f, depth+1)
294
        else:
295
            f.write(' %s\n'%unicode(v))
296

    
297
def list2file(l, f, depth = 1):
298
    for item in l:
299
        if isinstance(item,dict):
300
            dict2file(item, f, depth+1)
301
        elif isinstance(item,list):
302
            list2file(item, f, depth+1)
303
        else:
304
            f.write('%s%s\n'%('\t'*depth, unicode(item)))