__init__.py 15.2 KB
Newer Older
1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
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
#
# 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
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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
42

43
INDENT_TAB = 4
Stavros Sachtouris's avatar
Stavros Sachtouris committed
44
45


46
suggest = dict(ansicolors=dict(
47
48
        active=False,
        url='#install-ansicolors-progress',
49
        description='Add colors to console responses'))
50

51
52
53
try:
    from colors import magenta, red, yellow, bold
except ImportError:
Stavros Sachtouris's avatar
Stavros Sachtouris committed
54
    def dummy(val):
55
        return val
Stavros Sachtouris's avatar
Stavros Sachtouris committed
56
    red = yellow = magenta = bold = dummy
57
58
59
    suggest['ansicolors']['active'] = True


60
61
62
63
64
def _print(w):
    """Print wrapper is used to help unittests check what is printed"""
    print w


65
66
67
68
69
def _write(w):
    """stdout.write wrapper is used to help unittests check what is printed"""
    stdout.write(w)


70
71
72
73
74
75
def _flush():
    """stdout.flush wrapper is used to help unittests check what is called"""
    stdout.flush()


def _readline():
76
77
    """raw_input wrapper is used to help unittests"""
    return raw_input()
78
79


80
def suggest_missing(miss=None, exclude=[]):
81
    global suggest
82
83
84
85
86
87
    sgs = dict(suggest)
    for exc in exclude:
        try:
            sgs.pop(exc)
        except KeyError:
            pass
88
    kamaki_docs = 'http://www.synnefo.org/docs/kamaki/latest'
89
    for k, v in (miss, sgs[miss]) if miss else sgs.items():
90
91
92
93
94
95
        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('')
96

97

98
99
100
101
102
103
104
105
106
107
108
109
110
111
def guess_mime_type(
        filename,
        default_content_type='application/octet-stream',
        default_encoding=None):
    assert filename, 'Cannot guess mimetype for empty filename'
    try:
        from mimetypes import guess_type
        ctype, cenc = guess_type(filename)
        return ctype or default_content_type, cenc or default_encoding
    except ImportError:
        print 'WARNING: Cannot import mimetypes, using defaults'
        return (default_content_type, default_encoding)


Stavros Sachtouris's avatar
Stavros Sachtouris committed
112
113
def remove_colors():
    global bold
114
115
116
    global red
    global yellow
    global magenta
117

Stavros Sachtouris's avatar
Stavros Sachtouris committed
118
119
    def dummy(val):
        return val
120
    red = yellow = magenta = bold = dummy
121

122

123
124
125
126
127
128
129
130
def pretty_keys(d, delim='_', recursive=False):
    """<term>delim<term> to <term> <term> transformation"""
    new_d = dict(d)
    for k, v in d.items():
        new_v = new_d.pop(k)
        new_d[k.replace(delim, ' ').strip()] = pretty_keys(
            new_v, delim, True) if (
                recursive and isinstance(v, dict)) else new_v
131
132
    return new_d

133

134
135
136
137
138
def print_json(data):
    """Print a list or dict as json in console

    :param data: json-dumpable data
    """
139
    _print(dumps(data, indent=INDENT_TAB))
140
141


142
143
144
145
def pretty_dict(d, *args, **kwargs):
    print_dict(pretty_keys(d, *args, **kwargs))


146
def print_dict(
Stavros Sachtouris's avatar
Stavros Sachtouris committed
147
148
        d,
        exclude=(), indent=0,
149
        with_enumeration=False, recursive_enumeration=False):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
150
151
152
    """Pretty-print a dictionary object
    <indent>key: <non iterable item>
    <indent>key:
153
    <indent + INDENT_TAB><pretty-print iterable>
154

Stavros Sachtouris's avatar
Stavros Sachtouris committed
155
    :param d: (dict)
156

Stavros Sachtouris's avatar
Stavros Sachtouris committed
157
    :param exclude: (iterable of strings) keys to exclude from printing
158

Stavros Sachtouris's avatar
Stavros Sachtouris committed
159
    :param indent: (int) initial indentation (recursive)
160

Stavros Sachtouris's avatar
Stavros Sachtouris committed
161
    :param with_enumeration: (bool) enumerate 1st-level keys
162

Stavros Sachtouris's avatar
Stavros Sachtouris committed
163
164
    :param recursive_enumeration: (bool) recursively enumerate iterables (does
        not enumerate 1st level keys)
165

Stavros Sachtouris's avatar
Stavros Sachtouris committed
166
    :raises CLIError: if preconditions fail
167
    """
Stavros Sachtouris's avatar
Stavros Sachtouris committed
168
169
    assert isinstance(d, dict), 'print_dict input must be a dict'
    assert indent >= 0, 'print_dict indent must be >= 0'
170

Stavros Sachtouris's avatar
Stavros Sachtouris committed
171
172
173
    for i, (k, v) in enumerate(d.items()):
        k = ('%s' % k).strip()
        if k in exclude:
174
            continue
Stavros Sachtouris's avatar
Stavros Sachtouris committed
175
176
177
178
        print_str = ' ' * indent
        print_str += '%s.' % (i + 1) if with_enumeration else ''
        print_str += '%s:' % k
        if isinstance(v, dict):
179
            _print(print_str)
180
            print_dict(
181
                v, exclude, indent + INDENT_TAB,
Stavros Sachtouris's avatar
Stavros Sachtouris committed
182
183
                recursive_enumeration, recursive_enumeration)
        elif isinstance(v, list) or isinstance(v, tuple):
184
            _print(print_str)
185
            print_list(
186
                v, exclude, indent + INDENT_TAB,
Stavros Sachtouris's avatar
Stavros Sachtouris committed
187
                recursive_enumeration, recursive_enumeration)
188
        else:
189
            _print('%s %s' % (print_str, v))
190

191

192
def print_list(
Stavros Sachtouris's avatar
Stavros Sachtouris committed
193
194
        l,
        exclude=(), indent=0,
195
        with_enumeration=False, recursive_enumeration=False):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
196
197
198
    """Pretty-print a list of items
    <indent>key: <non iterable item>
    <indent>key:
199
    <indent + INDENT_TAB><pretty-print iterable>
200

Stavros Sachtouris's avatar
Stavros Sachtouris committed
201
    :param l: (list)
202

Stavros Sachtouris's avatar
Stavros Sachtouris committed
203
    :param exclude: (iterable of strings) items to exclude from printing
204

Stavros Sachtouris's avatar
Stavros Sachtouris committed
205
    :param indent: (int) initial indentation (recursive)
206

Stavros Sachtouris's avatar
Stavros Sachtouris committed
207
    :param with_enumeration: (bool) enumerate 1st-level items
208

Stavros Sachtouris's avatar
Stavros Sachtouris committed
209
210
    :param recursive_enumeration: (bool) recursively enumerate iterables (does
        not enumerate 1st level keys)
211

Stavros Sachtouris's avatar
Stavros Sachtouris committed
212
    :raises CLIError: if preconditions fail
213
    """
Stavros Sachtouris's avatar
Stavros Sachtouris committed
214
215
216
    assert isinstance(l, list) or isinstance(l, tuple), (
        'print_list prinbts a list or tuple')
    assert indent >= 0, 'print_list indent must be >= 0'
Stavros Sachtouris's avatar
Stavros Sachtouris committed
217

Stavros Sachtouris's avatar
Stavros Sachtouris committed
218
219
220
    for i, item in enumerate(l):
        print_str = ' ' * indent
        print_str += '%s.' % (i + 1) if with_enumeration else ''
221
        if isinstance(item, dict):
222
            if with_enumeration:
223
224
225
                _print(print_str)
            elif i and i < len(l):
                _print('')
226
            print_dict(
227
228
                item, exclude,
                indent + (INDENT_TAB if with_enumeration else 0),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
229
230
                recursive_enumeration, recursive_enumeration)
        elif isinstance(item, list) or isinstance(item, tuple):
231
            if with_enumeration:
232
233
234
                _print(print_str)
            elif i and i < len(l):
                _print()
235
            print_list(
236
                item, exclude, indent + INDENT_TAB,
Stavros Sachtouris's avatar
Stavros Sachtouris committed
237
                recursive_enumeration, recursive_enumeration)
238
        else:
Stavros Sachtouris's avatar
Stavros Sachtouris committed
239
240
241
            item = ('%s' % item).strip()
            if item in exclude:
                continue
242
            _print('%s%s' % (print_str, item))
243

244

245
246
def page_hold(index, limit, maxlen):
    """Check if there are results to show, and hold the page when needed
247
    :param index: (int) > 0, index of current element
248
249
250
251
252
    :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
    """
253
254
255
256
257
    if index >= maxlen:
        return False
    if index and index % limit == 0:
        raw_input('(%s listed - %s more - "enter" to continue)' % (
            index, maxlen - index))
258
259
260
    return True


261
def print_items(
262
263
264
        items, title=('id', 'name'),
        with_enumeration=False, with_redundancy=False,
        page_size=0):
265
266
267
268
    """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
Stavros Sachtouris's avatar
Stavros Sachtouris committed
269

270
    :param title: (tuple) keys to use their values as title
Stavros Sachtouris's avatar
Stavros Sachtouris committed
271

272
    :param with_enumeration: (boolean) enumerate items (order id on title)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
273

274
    :param with_redundancy: (boolean) values in title also appear on body
Stavros Sachtouris's avatar
Stavros Sachtouris committed
275

276
277
    :param page_size: (int) show results in pages of page_size items, enter to
        continue
278
    """
Stavros Sachtouris's avatar
Stavros Sachtouris committed
279
280
    if not items:
        return
281
282
    if not (isinstance(items, dict) or isinstance(items, list) or isinstance(
                items, tuple)):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
283
        _print('%s' % items)
284
285
        return

286
    page_size = int(page_size or 0)
287
    try:
288
        page_size = page_size if page_size > 0 else len(items)
289
290
291
292
    except:
        page_size = len(items)
    num_of_pages = len(items) // page_size
    num_of_pages += 1 if len(items) % page_size else 0
293
    for i, item in enumerate(items):
294
        if with_enumeration:
295
            _write('%s. ' % (i + 1))
296
        if isinstance(item, dict):
297
298
299
300
301
            item = dict(item)
            title = sorted(set(title).intersection(item))
            pick = item.get if with_redundancy else item.pop
            header = ' '.join('%s' % pick(key) for key in title)
            _print(bold(header))
302
            print_dict(item, indent=INDENT_TAB)
303
        elif isinstance(item, list) or isinstance(item, tuple):
304
            print_list(item, indent=INDENT_TAB)
305
        else:
306
            _print(' %s' % item)
307
        page_hold(i + 1, page_size, len(items))
308

309

310
311
312
313
314
def format_size(size, decimal_factors=False):
    units = ('B', 'KB', 'MB', 'GB', 'TB') if decimal_factors else (
        'B', 'KiB', 'MiB', 'GiB', 'TiB')
    step = 1000 if decimal_factors else 1024
    fstep = float(step)
315
316
    try:
        size = float(size)
317
318
319
320
321
    except (ValueError, TypeError) as err:
        raiseCLIError(err, 'Cannot format %s in bytes' % (
            ','.join(size) if isinstance(size, tuple) else size))
    for i, unit in enumerate(units):
        if size < step or i + 1 == len(units):
322
            break
323
        size /= fstep
324
    s = ('%.2f' % size)
325
    s = s.replace('%s' % step, '%s.99' % (step - 1)) if size <= fstep else s
326
327
    while '.' in s and s[-1] in ('0', '.'):
        s = s[:-1]
328
329
    return s + unit

330

331
332
333
334
335
336
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
337
338
339
    :raises ValueError: if invalid size or format
    :raises AttributeError: if format is not str
    :raises TypeError: if size is not arithmetic or convertible to arithmetic
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
    """
    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)


358
def dict2file(d, f, depth=0):
359
    for k, v in d.items():
360
        f.write('%s%s: ' % (' ' * INDENT_TAB * depth, k))
361
        if isinstance(v, dict):
362
            f.write('\n')
363
            dict2file(v, f, depth + 1)
364
        elif isinstance(v, list) or isinstance(v, tuple):
365
            f.write('\n')
366
            list2file(v, f, depth + 1)
367
        else:
368
            f.write('%s\n' % v)
369

370

371
def list2file(l, f, depth=1):
372
    for item in l:
373
374
        if isinstance(item, dict):
            dict2file(item, f, depth + 1)
375
        elif isinstance(item, list) or isinstance(item, tuple):
376
            list2file(item, f, depth + 1)
377
        else:
378
            f.write('%s%s\n' % (' ' * INDENT_TAB * depth, item))
379
380
381
382
383
384
385
386
387

# Split input auxiliary


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


388
389
390
391
392
def _get_from_parsed(parsed_str):
    try:
        parsed_str = parsed_str.strip()
    except:
        return None
393
394
395
    return ([parsed_str[1:-1]] if (
        parsed_str[0] == parsed_str[-1] and parsed_str[0] in ("'", '"')) else (
            parsed_str.split(' '))) if parsed_str else None
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413


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))
    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:
Stavros Sachtouris's avatar
Stavros Sachtouris committed
414
415
416
417
            if tpart and not tpart[-1].endswith(' '):
                terms[-1] += ' '.join(part)
            else:
                terms += part
418
419
420
    return terms


Stavros Sachtouris's avatar
Stavros Sachtouris committed
421
def ask_user(msg, true_resp=('y', )):
422
423
    """Print msg and read user response

424
    :param true_resp: (tuple of chars)
425
426
427

    :returns: (bool) True if reponse in true responses, False otherwise
    """
428
429
430
    _write('%s [%s/N]: ' % (msg, ', '.join(true_resp)))
    _flush()
    user_response = _readline()
Stavros Sachtouris's avatar
Stavros Sachtouris committed
431
    return user_response[0].lower() in true_resp
432
433


434
def spiner(size=None):
435
    spins = ('/', '-', '\\', '|')
436
    _write(' ')
437
438
439
    size = size or -1
    i = 0
    while size - i:
440
441
        _write('\b%s' % spins[i % len(spins)])
        _flush()
442
        i += 1
443
        sleep(0.1)
444
        yield
445
    yield
446

447
448
449
450
451
452
453
454
455
456
457

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
458
459
460
461
462
463


def remove_from_items(list_of_dicts, key_to_remove):
    for item in list_of_dicts:
        assert isinstance(item, dict), 'Item %s not a dict' % item
        item.pop(key_to_remove, None)
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497


def filter_dicts_by_dict(
    list_of_dicts, filters,
    exact_match=True, case_sensitive=False):
    """
    :param list_of_dicts: (list) each dict contains "raw" key-value pairs

    :param filters: (dict) filters in key-value form

    :param exact_match: (bool) if false, check if the filter value is part of
        the actual value

    :param case_sensitive: (bool) revers to values only (not keys)

    :returns: (list) only the dicts that match all filters
    """
    new_dicts = []
    for d in list_of_dicts:
        if set(filters).difference(d):
            continue
        match = True
        for k, v in filters.items():
            dv, v = ('%s' % d[k]), ('%s' % v)
            if not case_sensitive:
                dv, v = dv.lower(), v.lower()
            if not ((
                    exact_match and v == dv) or (
                    (not exact_match) and v in dv)):
                match = False
                break
        if match:
            new_dicts.append(d)
    return new_dicts