Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / utils.py @ 7147e1ca

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

    
34
from sys import stdout, stdin
35
from re import compile as regex_compile
36
from kamaki.cli.errors import raiseCLIError
37

    
38
try:
39
    from colors import magenta, red, yellow, bold
40
except ImportError:
41
    # No colours? No worries, use dummy foo instead
42
    def dummy(val):
43
        return val
44
    red = yellow = magenta = bold = dummy
45

    
46

    
47
def remove_colors():
48
    global bold
49
    global red
50
    global yellow
51
    global magenta
52

    
53
    def dummy(val):
54
        return val
55
    red = yellow = magenta = bold = dummy
56

    
57

    
58
def pretty_keys(d, delim='_', recurcive=False):
59
    """<term>delim<term> to <term> <term> transformation
60
    """
61
    new_d = {}
62
    for key, val in d.items():
63
        new_key = key.split(delim)[-1]
64
        if recurcive and isinstance(val, dict):
65
            new_val = pretty_keys(val, delim, recurcive)
66
        else:
67
            new_val = val
68
        new_d[new_key] = new_val
69
    return new_d
70

    
71

    
72
def print_dict(d,
73
    exclude=(),
74
    ident=0,
75
    with_enumeration=False,
76
    recursive_enumeration=False):
77
    """
78
    Pretty-print a dictionary object
79

80
    :param d: (dict) the input
81

82
    :param excelude: (set or list) keys to exclude from printing
83

84
    :param ident: (int) initial indentation (recursive)
85

86
    :param with_enumeration: (bool) enumerate each 1st level key if true
87

88
    :recursive_enumeration: (bool) recursively enumerate dicts and lists of
89
        2nd level or deeper
90

91
    :raises CLIError: (TypeError wrapper) non-dict input
92
    """
93
    if not isinstance(d, dict):
94
        raiseCLIError(TypeError('Cannot dict_print a non-dict object'))
95

    
96
    if d:
97
        margin = max(len(unicode(key).strip())\
98
            for key in d.keys() if key not in exclude)
99

    
100
    counter = 1
101
    for key, val in sorted(d.items()):
102
        if key in exclude:
103
            continue
104
        print_str = ''
105
        if with_enumeration:
106
            print_str = '%s. ' % counter
107
            counter += 1
108
        print_str = '%s%s' % (' ' * (ident - len(print_str)), print_str)
109
        print_str += ('%s' % key).strip()
110
        print_str += ' ' * (margin - len(unicode(key).strip()))
111
        print_str += ': '
112
        if isinstance(val, dict):
113
            print(print_str)
114
            print_dict(val,
115
                exclude=exclude,
116
                ident=margin + ident,
117
                with_enumeration=recursive_enumeration,
118
                recursive_enumeration=recursive_enumeration)
119
        elif isinstance(val, list):
120
            print(print_str)
121
            print_list(val,
122
                exclude=exclude,
123
                ident=margin + ident,
124
                with_enumeration=recursive_enumeration,
125
                recursive_enumeration=recursive_enumeration)
126
        else:
127
            print print_str + ' ' + unicode(val).strip()
128

    
129

    
130
def print_list(l,
131
    exclude=(),
132
    ident=0,
133
    with_enumeration=False,
134
    recursive_enumeration=False):
135
    """
136
    Pretty-print a list object
137

138
    :param l: (list) the input
139

140
    :param excelude: (object - anytype) values to exclude from printing
141

142
    :param ident: (int) initial indentation (recursive)
143

144
    :param with_enumeration: (bool) enumerate each 1st level value if true
145

146
    :recursive_enumeration: (bool) recursively enumerate dicts and lists of
147
        2nd level or deeper
148

149
    :raises CLIError: (TypeError wrapper) non-list input
150
    """
151
    if not isinstance(l, list):
152
        raiseCLIError(TypeError('Cannot list_print a non-list object'))
153

    
154
    if l:
155
        try:
156
            margin = max(len(unicode(item).strip()) for item in l\
157
                if not (isinstance(item, dict)\
158
                or isinstance(item, list)\
159
                or item in exclude))
160
        except ValueError:
161
            margin = (2 + len(unicode(len(l)))) if enumerate else 1
162

    
163
    counter = 1
164
    prefix = ''
165
    for item in sorted(l):
166
        if item in exclude:
167
            continue
168
        elif with_enumeration:
169
            prefix = '%s. ' % counter
170
            counter += 1
171
            prefix = '%s%s' % (' ' * (ident - len(prefix)), prefix)
172
        else:
173
            prefix = ' ' * ident
174
        if isinstance(item, dict):
175
            if with_enumeration:
176
                print(prefix)
177
            print_dict(item,
178
                exclude=exclude,
179
                ident=margin + ident,
180
                with_enumeration=recursive_enumeration,
181
                recursive_enumeration=recursive_enumeration)
182
        elif isinstance(item, list):
183
            if with_enumeration:
184
                print(prefix)
185
            print_list(item,
186
                exclude=exclude,
187
                ident=margin + ident,
188
                with_enumeration=recursive_enumeration,
189
                recursive_enumeration=recursive_enumeration)
190
        else:
191
            print('%s%s' % (prefix, item))
192

    
193

    
194
def page_hold(index, limit, maxlen):
195
    """Check if there are results to show, and hold the page when needed
196
    :param index: (int) > 0
197
    :param limit: (int) 0 < limit <= max, page hold if limit mod index == 0
198
    :param maxlen: (int) Don't hold if index reaches maxlen
199

200
    :returns: True if there are more to show, False if all results are shown
201
    """
202
    if index >= limit and index % limit == 0:
203
        if index >= maxlen:
204
            return False
205
        else:
206
            print('(%s listed - %s more - "enter" to continue)' % (
207
                index,
208
                maxlen - index))
209
            c = ' '
210
            while c != '\n':
211
                c = stdin.read(1)
212
    return True
213

    
214

    
215
def print_items(items,
216
    title=('id', 'name'),
217
    with_enumeration=False,
218
    with_redundancy=False,
219
    page_size=0):
220
    """print dict or list items in a list, using some values as title
221
    Objects of next level don't inherit enumeration (default: off) or titles
222

223
    :param items: (list) items are lists or dict
224
    :param title: (tuple) keys to use their values as title
225
    :param with_enumeration: (boolean) enumerate items (order id on title)
226
    :param with_redundancy: (boolean) values in title also appear on body
227
    :param page_size: (int) show results in pages of page_size items, enter to
228
        continue
229
    """
230
    if not items:
231
        return
232
    try:
233
        page_size = int(page_size) if int(page_size) > 0 else len(items)
234
    except:
235
        page_size = len(items)
236
    num_of_pages = len(items) // page_size
237
    num_of_pages += 1 if len(items) % page_size else 0
238
    for i, item in enumerate(items):
239
        if with_enumeration:
240
            stdout.write('%s. ' % (i + 1))
241
        if isinstance(item, dict):
242
            title = sorted(set(title).intersection(item.keys()))
243
            if with_redundancy:
244
                header = ' '.join(unicode(item[key]) for key in title)
245
            else:
246
                header = ' '.join(unicode(item.pop(key)) for key in title)
247
            print(bold(header))
248
        else:
249
            print('- - -')
250
        if isinstance(item, dict):
251
            print_dict(item, ident=1)
252
        elif isinstance(item, list):
253
            print_list(item, ident=1)
254
        else:
255
            print(' %s' % item)
256
        page_hold(i + 1, page_size, len(items))
257

    
258

    
259
def format_size(size):
260
    units = ('B', 'K', 'M', 'G', 'T')
261
    try:
262
        size = float(size)
263
    except ValueError as err:
264
        raiseCLIError(err, 'Cannot format %s in bytes' % size)
265
    for unit in units:
266
        if size < 1024:
267
            break
268
        size /= 1024
269
    s = ('%.1f' % size)
270
    if '.0' == s[-2:]:
271
        s = s[:-2]
272
    return s + unit
273

    
274

    
275
def dict2file(d, f, depth=0):
276
    for k, v in d.items():
277
        f.write('%s%s: ' % ('\t' * depth, k))
278
        if isinstance(v, dict):
279
            f.write('\n')
280
            dict2file(v, f, depth + 1)
281
        elif isinstance(v, list):
282
            f.write('\n')
283
            list2file(v, f, depth + 1)
284
        else:
285
            f.write(' %s\n' % unicode(v))
286

    
287

    
288
def list2file(l, f, depth=1):
289
    for item in l:
290
        if isinstance(item, dict):
291
            dict2file(item, f, depth + 1)
292
        elif isinstance(item, list):
293
            list2file(item, f, depth + 1)
294
        else:
295
            f.write('%s%s\n' % ('\t' * depth, unicode(item)))
296

    
297
# Split input auxiliary
298

    
299

    
300
def _parse_with_regex(line, regex):
301
    re_parser = regex_compile(regex)
302
    return (re_parser.split(line), re_parser.findall(line))
303

    
304

    
305
def _sub_split(line):
306
    terms = []
307
    (sub_trivials, sub_interesting) = _parse_with_regex(line, ' ".*?" ')
308
    for subi, subipart in enumerate(sub_interesting):
309
        terms += sub_trivials[subi].split()
310
        terms.append(subipart[2:-2])
311
    terms += sub_trivials[-1].split()
312
    return terms
313

    
314

    
315
def split_input(line):
316
    """Use regular expressions to split a line correctly
317
    """
318
    line = ' %s ' % line
319
    (trivial_parts, interesting_parts) = _parse_with_regex(line, ' \'.*?\' ')
320
    terms = []
321
    for i, ipart in enumerate(interesting_parts):
322
        terms += _sub_split(trivial_parts[i])
323
        terms.append(ipart[2:-2])
324
    terms += _sub_split(trivial_parts[-1])
325
    return terms
326

    
327

    
328
def ask_user(msg, true_responses=('Y', 'y')):
329
    """Print msg and read user response
330

331
    :param true_responses: (tuple of chars)
332

333
    :returns: (bool) True if reponse in true responses, False otherwise
334
    """
335
    stdout.write('%s (%s for yes):' % (msg, true_responses))
336
    stdout.flush()
337
    user_response = stdin.read(1)
338
    return user_response[0] in true_responses