__init__.py 14.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, stdin
35
from re import compile as regex_compile
36
from os import walk, path
37
from json import dumps
38

39
from kamaki.cli.errors import raiseCLIError
40

Stavros Sachtouris's avatar
Stavros Sachtouris committed
41

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


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

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


59
def suggest_missing(miss=None, exclude=[]):
60
    global suggest
61
62
63
64
65
66
    sgs = dict(suggest)
    for exc in exclude:
        try:
            sgs.pop(exc)
        except KeyError:
            pass
67
    kamaki_docs = 'http://www.synnefo.org/docs/kamaki/latest'
68
    for k, v in (miss, sgs[miss]) if miss else sgs.items():
69
70
71
72
73
74
        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('')
75

76

77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
91
92
def remove_colors():
    global bold
93
94
95
    global red
    global yellow
    global magenta
96

Stavros Sachtouris's avatar
Stavros Sachtouris committed
97
98
    def dummy(val):
        return val
99
    red = yellow = magenta = bold = dummy
100

101

102
103
104
105
106
107
108
109
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
110
111
    return new_d

112

113
def print_json(data, out=stdout):
114
115
116
117
    """Print a list or dict as json in console

    :param data: json-dumpable data

118
119
    :param out: Input/Output stream to dump values into
    """
120
121
    out.write(unicode(dumps(data, indent=INDENT_TAB) + '\n'))
    out.flush()
122
123


124
def print_dict(
Stavros Sachtouris's avatar
Stavros Sachtouris committed
125
126
        d,
        exclude=(), indent=0,
127
        with_enumeration=False, recursive_enumeration=False, out=stdout):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
128
129
130
    """Pretty-print a dictionary object
    <indent>key: <non iterable item>
    <indent>key:
131
    <indent + INDENT_TAB><pretty-print iterable>
132

Stavros Sachtouris's avatar
Stavros Sachtouris committed
133
    :param d: (dict)
134

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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
137
    :param indent: (int) initial indentation (recursive)
138

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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
141
142
    :param recursive_enumeration: (bool) recursively enumerate iterables (does
        not enumerate 1st level keys)
143

144
145
    :param out: Input/Output stream to dump values into

Stavros Sachtouris's avatar
Stavros Sachtouris committed
146
    :raises CLIError: if preconditions fail
147
    """
Stavros Sachtouris's avatar
Stavros Sachtouris committed
148
149
    assert isinstance(d, dict), 'print_dict input must be a dict'
    assert indent >= 0, 'print_dict indent must be >= 0'
150

Stavros Sachtouris's avatar
Stavros Sachtouris committed
151
152
153
    for i, (k, v) in enumerate(d.items()):
        k = ('%s' % k).strip()
        if k in exclude:
154
            continue
155
156
157
        print_str = ' ' * indent
        print_str += '%s.' % (i + 1) if with_enumeration else ''
        print_str += '%s:' % k
Stavros Sachtouris's avatar
Stavros Sachtouris committed
158
        if isinstance(v, dict):
159
            out.write(print_str + '\n')
160
            print_dict(
161
                v, exclude, indent + INDENT_TAB,
162
                recursive_enumeration, recursive_enumeration, out)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
163
        elif isinstance(v, list) or isinstance(v, tuple):
164
            out.write(print_str + '\n')
165
            print_list(
166
                v, exclude, indent + INDENT_TAB,
167
                recursive_enumeration, recursive_enumeration, out)
168
        else:
169
            out.write('%s %s\n' % (print_str, v))
170
        out.flush()
171

172

173
def print_list(
Stavros Sachtouris's avatar
Stavros Sachtouris committed
174
175
        l,
        exclude=(), indent=0,
176
        with_enumeration=False, recursive_enumeration=False, out=stdout):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
177
178
179
    """Pretty-print a list of items
    <indent>key: <non iterable item>
    <indent>key:
180
    <indent + INDENT_TAB><pretty-print iterable>
181

Stavros Sachtouris's avatar
Stavros Sachtouris committed
182
    :param l: (list)
183

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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
186
    :param indent: (int) initial indentation (recursive)
187

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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
190
191
    :param recursive_enumeration: (bool) recursively enumerate iterables (does
        not enumerate 1st level keys)
192

193
194
    :param out: Input/Output stream to dump values into

Stavros Sachtouris's avatar
Stavros Sachtouris committed
195
    :raises CLIError: if preconditions fail
196
    """
Stavros Sachtouris's avatar
Stavros Sachtouris committed
197
198
199
    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
200

Stavros Sachtouris's avatar
Stavros Sachtouris committed
201
    for i, item in enumerate(l):
202
203
        print_str = ' ' * indent
        print_str += '%s.' % (i + 1) if with_enumeration else ''
204
        if isinstance(item, dict):
205
            if with_enumeration:
206
                out.write(print_str + '\n')
207
            elif i and i < len(l):
208
                out.write('\n')
209
            print_dict(
210
211
                item, exclude,
                indent + (INDENT_TAB if with_enumeration else 0),
212
                recursive_enumeration, recursive_enumeration, out)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
213
        elif isinstance(item, list) or isinstance(item, tuple):
214
            if with_enumeration:
215
                out.write(print_str + '\n')
216
            elif i and i < len(l):
217
                out.write('\n')
218
            print_list(
219
                item, exclude, indent + INDENT_TAB,
220
                recursive_enumeration, recursive_enumeration, out)
221
        else:
Stavros Sachtouris's avatar
Stavros Sachtouris committed
222
223
224
            item = ('%s' % item).strip()
            if item in exclude:
                continue
225
            out.write('%s%s\n' % (print_str, item))
226
227
        out.flush()
    out.flush()
228
229


230
def print_items(
231
        items, title=('id', 'name'),
232
        with_enumeration=False, with_redundancy=False, out=stdout):
233
234
235
236
    """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
237

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

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

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

244
    :param out: Input/Output stream to dump values into
245
    """
Stavros Sachtouris's avatar
Stavros Sachtouris committed
246
247
    if not items:
        return
248
249
    if not (isinstance(items, dict) or isinstance(items, list) or isinstance(
                items, tuple)):
250
        out.write('%s\n' % items)
251
        out.flush()
252
253
        return

254
    for i, item in enumerate(items):
255
        if with_enumeration:
256
            out.write('%s. ' % (i + 1))
257
        if isinstance(item, dict):
258
259
260
            item = dict(item)
            title = sorted(set(title).intersection(item))
            pick = item.get if with_redundancy else item.pop
261
            header = ' '.join('%s' % pick(key) for key in title)
262
            out.write((unicode(bold(header) if header else '') + '\n'))
263
            print_dict(item, indent=INDENT_TAB, out=out)
264
        elif isinstance(item, list) or isinstance(item, tuple):
265
            print_list(item, indent=INDENT_TAB, out=out)
266
        else:
267
            out.write(' %s\n' % item)
268
269
        out.flush()
    out.flush()
270

271

272
273
274
275
276
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)
277
278
    try:
        size = float(size)
279
280
281
282
283
    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):
284
            break
285
        size /= fstep
286
    s = ('%.2f' % size)
287
    s = s.replace('%s' % step, '%s.99' % (step - 1)) if size <= fstep else s
288
289
    while '.' in s and s[-1] in ('0', '.'):
        s = s[:-1]
290
291
    return s + unit

292

293
294
295
296
297
298
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
299
300
301
    :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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
    """
    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)


320
def dict2file(d, f, depth=0):
321
    for k, v in d.items():
322
        f.write('%s%s: ' % (' ' * INDENT_TAB * depth, k))
323
        if isinstance(v, dict):
324
            f.write('\n')
325
            dict2file(v, f, depth + 1)
326
        elif isinstance(v, list) or isinstance(v, tuple):
327
            f.write('\n')
328
            list2file(v, f, depth + 1)
329
        else:
330
            f.write('%s\n' % v)
331

332

333
def list2file(l, f, depth=1):
334
    for item in l:
335
336
        if isinstance(item, dict):
            dict2file(item, f, depth + 1)
337
        elif isinstance(item, list) or isinstance(item, tuple):
338
            list2file(item, f, depth + 1)
339
        else:
340
            f.write('%s%s\n' % (' ' * INDENT_TAB * depth, item))
341
342
343
344
345
346
347
348
349

# Split input auxiliary


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


350
351
352
353
354
def _get_from_parsed(parsed_str):
    try:
        parsed_str = parsed_str.strip()
    except:
        return None
355
356
357
    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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375


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
376
377
378
379
            if tpart and not tpart[-1].endswith(' '):
                terms[-1] += ' '.join(part)
            else:
                terms += part
380
381
382
    return terms


383
def ask_user(msg, true_resp=('y', ), out=stdout, user_in=stdin):
384
385
    """Print msg and read user response

386
    :param true_resp: (tuple of chars)
387
388
389

    :returns: (bool) True if reponse in true responses, False otherwise
    """
390
391
    yep = ', '.join(true_resp)
    nope = '<not %s>' % yep if 'n' in true_resp or 'N' in true_resp else 'N'
392
    out.write('%s [%s/%s]: ' % (msg, yep, nope))
393
394
395
    out.flush()
    user_response = user_in.readline()
    return user_response[0].lower() in [s.lower() for s in true_resp]
396

397
398
399
400
401
402
403
404
405
406
407

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
408
409
410
411
412
413


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)
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447


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