utils.py 15.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Copyright 2011 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
33

34
from sys import stdout, stdin
35
from re import compile as regex_compile
36
from time import sleep
37
from os import walk, path
38
from json import dumps
39

40
from kamaki.cli.errors import raiseCLIError
41

42
suggest = dict(ansicolors=dict(
43
44
        active=False,
        url='#install-ansicolors-progress',
45
        description='Add colors to console responses'))
46

47
48
49
try:
    from colors import magenta, red, yellow, bold
except ImportError:
50
    # No colours? No worries, use dummy foo instead
Stavros Sachtouris's avatar
Stavros Sachtouris committed
51
    def dummy(val):
52
        return val
Stavros Sachtouris's avatar
Stavros Sachtouris committed
53
    red = yellow = magenta = bold = dummy
54
55
    #from kamaki.cli import _colors
    #if _colors.lower() == 'on':
56
57
58
59
60
61
62
63
    suggest['ansicolors']['active'] = True

try:
    from progress.bar import ShadyBar
except ImportError:
    suggest['progress']['active'] = True


64
def suggest_missing(miss=None, exclude=[]):
65
    global suggest
66
67
68
69
70
71
    sgs = dict(suggest)
    for exc in exclude:
        try:
            sgs.pop(exc)
        except KeyError:
            pass
72
    kamaki_docs = 'http://www.synnefo.org/docs/kamaki/latest'
73
    for k, v in (miss, sgs[miss]) if miss else sgs.items():
74
75
76
77
78
79
        if v['active'] and stdout.isatty():
            print('Suggestion: for better user experience install %s' % k)
            print('\t%s' % v['description'])
            print('\tIt is easy, here are the instructions:')
            print('\t%s/installation.html%s' % (kamaki_docs, v['url']))
            print('')
80

81

Stavros Sachtouris's avatar
Stavros Sachtouris committed
82
83
def remove_colors():
    global bold
84
85
86
    global red
    global yellow
    global magenta
87

Stavros Sachtouris's avatar
Stavros Sachtouris committed
88
89
    def dummy(val):
        return val
90
    red = yellow = magenta = bold = dummy
91

92

93
def pretty_keys(d, delim='_', recurcive=False):
94
    """<term>delim<term> to <term> <term> transformation
95
96
97
98
99
    """
    new_d = {}
    for key, val in d.items():
        new_key = key.split(delim)[-1]
        if recurcive and isinstance(val, dict):
100
            new_val = pretty_keys(val, delim, recurcive)
101
102
103
104
105
        else:
            new_val = val
        new_d[new_key] = new_val
    return new_d

106

107
108
109
110
111
112
113
114
def print_json(data):
    """Print a list or dict as json in console

    :param data: json-dumpable data
    """
    print(dumps(data, indent=2))


115
116
117
118
def pretty_dict(d, *args, **kwargs):
    print_dict(pretty_keys(d, *args, **kwargs))


119
def print_dict(
120
121
        d, exclude=(), ident=0,
        with_enumeration=False, recursive_enumeration=False):
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
    """
    Pretty-print a dictionary object

    :param d: (dict) the input

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

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

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

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

    :raises CLIError: (TypeError wrapper) non-dict input
    """
138
    if not isinstance(d, dict):
139
        raiseCLIError(TypeError('Cannot dict_print a non-dict object'))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
140

Stavros Sachtouris's avatar
Stavros Sachtouris committed
141
    if d:
142
143
        margin = max(len(('%s' % key).strip()) for key in d.keys() if (
            key not in exclude))
144

145
    counter = 1
146
    for key, val in sorted(d.items()):
147
        key = '%s' % key
148
149
        if key in exclude:
            continue
150
151
152
153
154
        print_str = ''
        if with_enumeration:
            print_str = '%s. ' % counter
            counter += 1
        print_str = '%s%s' % (' ' * (ident - len(print_str)), print_str)
155
        print_str += key.strip()
156
        print_str += ':'
157
        print_str += ' ' * (margin - len(key.strip()))
158
        #print_str += ':'
159
        if isinstance(val, dict):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
160
            print(print_str)
161
162
            print_dict(
                val,
163
164
165
166
                exclude=exclude,
                ident=margin + ident,
                with_enumeration=recursive_enumeration,
                recursive_enumeration=recursive_enumeration)
167
        elif isinstance(val, list):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
168
            print(print_str)
169
170
            print_list(
                val,
171
172
173
174
                exclude=exclude,
                ident=margin + ident,
                with_enumeration=recursive_enumeration,
                recursive_enumeration=recursive_enumeration)
175
        else:
176
            print print_str + ' ' + ('%s' % val).strip()
177

178

179
def print_list(
180
181
        l, exclude=(), ident=0,
        with_enumeration=False, recursive_enumeration=False):
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
    """
    Pretty-print a list object

    :param l: (list) the input

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

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

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

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

    :raises CLIError: (TypeError wrapper) non-list input
    """
198
    if not isinstance(l, list):
199
        raiseCLIError(TypeError('Cannot list_print a non-list object'))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
200

Stavros Sachtouris's avatar
Stavros Sachtouris committed
201
    if l:
202
        try:
203
204
205
206
            margin = max(len(('%s' % item).strip()) for item in l if not (
                isinstance(item, dict) or
                isinstance(item, list) or
                item in exclude))
207
        except ValueError:
208
            margin = (2 + len(('%s' % len(l)))) if enumerate else 1
209

210
211
    counter = 1
    prefix = ''
212
    item_sep = False
213
214
215
    for item in sorted(l):
        if item in exclude:
            continue
216
        elif with_enumeration:
217
218
219
220
221
            prefix = '%s. ' % counter
            counter += 1
            prefix = '%s%s' % (' ' * (ident - len(prefix)), prefix)
        else:
            prefix = ' ' * ident
222
223
224
225
        if item_sep:
            print '%s. . . . . . .' % prefix
        else:
            item_sep = True
226
        if isinstance(item, dict):
227
228
            if with_enumeration:
                print(prefix)
229
230
            print_dict(
                item,
231
232
233
234
                exclude=exclude,
                ident=margin + ident,
                with_enumeration=recursive_enumeration,
                recursive_enumeration=recursive_enumeration)
235
        elif isinstance(item, list):
236
237
            if with_enumeration:
                print(prefix)
238
239
            print_list(
                item,
240
241
242
243
                exclude=exclude,
                ident=margin + ident,
                with_enumeration=recursive_enumeration,
                recursive_enumeration=recursive_enumeration)
244
        else:
245
            print('%s%s' % (prefix, item))
246

247

248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def page_hold(index, limit, maxlen):
    """Check if there are results to show, and hold the page when needed
    :param index: (int) > 0
    :param limit: (int) 0 < limit <= max, page hold if limit mod index == 0
    :param maxlen: (int) Don't hold if index reaches maxlen

    :returns: True if there are more to show, False if all results are shown
    """
    if index >= limit and index % limit == 0:
        if index >= maxlen:
            return False
        else:
            print('(%s listed - %s more - "enter" to continue)' % (
                index,
                maxlen - index))
            c = ' '
            while c != '\n':
                c = stdin.read(1)
    return True


269
def print_items(
270
271
272
        items, title=('id', 'name'),
        with_enumeration=False, with_redundancy=False,
        page_size=0):
273
274
275
276
277
278
279
    """print dict or list items in a list, using some values as title
    Objects of next level don't inherit enumeration (default: off) or titles

    :param items: (list) items are lists or dict
    :param title: (tuple) keys to use their values as title
    :param with_enumeration: (boolean) enumerate items (order id on title)
    :param with_redundancy: (boolean) values in title also appear on body
280
281
    :param page_size: (int) show results in pages of page_size items, enter to
        continue
282
    """
283
284
    if not items:
        return
285
286
287
288
289
290
    try:
        page_size = int(page_size) if int(page_size) > 0 else len(items)
    except:
        page_size = len(items)
    num_of_pages = len(items) // page_size
    num_of_pages += 1 if len(items) % page_size else 0
291
    for i, item in enumerate(items):
292
293
294
295
296
        if with_enumeration:
            stdout.write('%s. ' % (i + 1))
        if isinstance(item, dict):
            title = sorted(set(title).intersection(item.keys()))
            if with_redundancy:
297
                header = ' '.join('%s' % item[key] for key in title)
298
            else:
299
                header = ' '.join('%s' % item.pop(key) for key in title)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
300
            print(bold(header))
301
        if isinstance(item, dict):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
302
            print_dict(item, ident=1)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
303
        elif isinstance(item, list):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
304
            print_list(item, ident=1)
305
306
        else:
            print(' %s' % item)
307
        page_hold(i + 1, page_size, len(items))
308

309

310
def format_size(size):
311
    units = ('B', 'KiB', 'MiB', 'GiB', 'TiB')
312
313
    try:
        size = float(size)
314
315
    except ValueError as err:
        raiseCLIError(err, 'Cannot format %s in bytes' % size)
316
317
318
    for unit in units:
        if size < 1024:
            break
319
320
321
322
        size /= 1024.0
    s = ('%.2f' % size)
    while '.' in s and s[-1] in ('0', '.'):
        s = s[:-1]
323
324
    return s + unit

325

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
def to_bytes(size, format):
    """
    :param size: (float) the size in the given format
    :param format: (case insensitive) KiB, KB, MiB, MB, GiB, GB, TiB, TB

    :returns: (int) the size in bytes
    """
    format = format.upper()
    if format == 'B':
        return int(size)
    size = float(size)
    units_dc = ('KB', 'MB', 'GB', 'TB')
    units_bi = ('KIB', 'MIB', 'GIB', 'TIB')

    factor = 1024 if format in units_bi else 1000 if format in units_dc else 0
    if not factor:
        raise ValueError('Invalid data size format %s' % format)
    for prefix in ('K', 'M', 'G', 'T'):
        size *= factor
        if format.startswith(prefix):
            break
    return int(size)


350
def dict2file(d, f, depth=0):
351
    for k, v in d.items():
352
353
        f.write('%s%s: ' % ('\t' * depth, k))
        if isinstance(v, dict):
354
            f.write('\n')
355
356
            dict2file(v, f, depth + 1)
        elif isinstance(v, list):
357
            f.write('\n')
358
            list2file(v, f, depth + 1)
359
        else:
360
            f.write(' %s\n' % v)
361

362

363
def list2file(l, f, depth=1):
364
    for item in l:
365
366
367
368
        if isinstance(item, dict):
            dict2file(item, f, depth + 1)
        elif isinstance(item, list):
            list2file(item, f, depth + 1)
369
        else:
370
            f.write('%s%s\n' % ('\t' * depth, item))
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389

# Split input auxiliary


def _parse_with_regex(line, regex):
    re_parser = regex_compile(regex)
    return (re_parser.split(line), re_parser.findall(line))


def _sub_split(line):
    terms = []
    (sub_trivials, sub_interesting) = _parse_with_regex(line, ' ".*?" ')
    for subi, subipart in enumerate(sub_interesting):
        terms += sub_trivials[subi].split()
        terms.append(subipart[2:-2])
    terms += sub_trivials[-1].split()
    return terms


390
391
def old_split_input(line):
    """Use regular expressions to split a line correctly"""
392
393
394
395
396
397
398
399
    line = ' %s ' % line
    (trivial_parts, interesting_parts) = _parse_with_regex(line, ' \'.*?\' ')
    terms = []
    for i, ipart in enumerate(interesting_parts):
        terms += _sub_split(trivial_parts[i])
        terms.append(ipart[2:-2])
    terms += _sub_split(trivial_parts[-1])
    return terms
400
401


402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
def _get_from_parsed(parsed_str):
    try:
        parsed_str = parsed_str.strip()
    except:
        return None
    if parsed_str:
        if parsed_str[0] == parsed_str[-1] and parsed_str[0] in ("'", '"'):
            return [parsed_str[1:-1]]
        return parsed_str.split(' ')
    return None


def split_input(line):
    if not line:
        return []
    reg_expr = '\'.*?\'|".*?"|^[\S]*$'
    (trivial_parts, interesting_parts) = _parse_with_regex(line, reg_expr)
    assert(len(trivial_parts) == 1 + len(interesting_parts))
    #print('  [split_input] trivial_parts %s are' % trivial_parts)
    #print('  [split_input] interesting_parts %s are' % interesting_parts)
    terms = []
    for i, tpart in enumerate(trivial_parts):
        part = _get_from_parsed(tpart)
        if part:
            terms += part
        try:
            part = _get_from_parsed(interesting_parts[i])
        except IndexError:
            break
        if part:
            terms += part
    return terms


436
def ask_user(msg, true_resp=['Y', 'y']):
437
438
    """Print msg and read user response

439
    :param true_resp: (tuple of chars)
440
441
442

    :returns: (bool) True if reponse in true responses, False otherwise
    """
443
    stdout.write('%s (%s or enter for yes):' % (msg, ', '.join(true_resp)))
444
    stdout.flush()
445
446
    user_response = stdin.readline()
    return user_response[0] in true_resp + ['\n']
447
448


449
def spiner(size=None):
450
451
    spins = ('/', '-', '\\', '|')
    stdout.write(' ')
452
453
454
    size = size or -1
    i = 0
    while size - i:
455
456
457
        stdout.write('\b%s' % spins[i % len(spins)])
        stdout.flush()
        i += 1
458
        sleep(0.1)
459
        yield
460
    yield
461

462
if __name__ == '__main__':
463
464
    examples = [
        'la_la le_le li_li',
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
        '\'la la\' \'le le\' \'li li\'',
        '\'la la\' le_le \'li li\'',
        'la_la \'le le\' li_li',
        'la_la \'le le\' \'li li\'',
        '"la la" "le le" "li li"',
        '"la la" le_le "li li"',
        'la_la "le le" li_li',
        '"la_la" "le le" "li li"',
        '\'la la\' "le le" \'li li\'',
        'la_la \'le le\' "li li"',
        'la_la \'le le\' li_li',
        '\'la la\' le_le "li li"',
        '"la la" le_le \'li li\'',
        '"la la" \'le le\' li_li',
        'la_la \'le\'le\' "li\'li"',
        '"la \'le le\' la"',
        '\'la "le le" la\'',
        '\'la "la" la\' "le \'le\' le" li_"li"_li',
483
        '\'\' \'L\' "" "A"']
484
485
486
487
488

    for i, example in enumerate(examples):
        print('%s. Split this: (%s)' % (i + 1, example))
        ret = old_split_input(example)
        print('\t(%s) of size %s' % (ret, len(ret)))
489
490
491
492
493
494
495
496
497
498
499
500


def get_path_size(testpath):
    if path.isfile(testpath):
        return path.getsize(testpath)
    total_size = 0
    for top, dirs, files in walk(path.abspath(testpath)):
        for f in files:
            f = path.join(top, f)
            if path.isfile(f):
                total_size += path.getsize(f)
    return total_size