__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
120
    :param out: Input/Output stream to dump values into
    """
    out.writelines(unicode(dumps(data, indent=INDENT_TAB) + '\n'))
121
122


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

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

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

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

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

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

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

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

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

170

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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
180
    :param l: (list)
181

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

Stavros Sachtouris's avatar
Stavros Sachtouris committed
184
    :param indent: (int) initial indentation (recursive)
185

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

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

191
192
    :param out: Input/Output stream to dump values into

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

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


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

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

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

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

241
    :param out: Input/Output stream to dump values into
242
    """
Stavros Sachtouris's avatar
Stavros Sachtouris committed
243
244
    if not items:
        return
245
246
    if not (isinstance(items, dict) or isinstance(items, list) or isinstance(
                items, tuple)):
247
        out.writelines(u'%s\n' % items)
248
249
        return

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

265

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

286

287
288
289
290
291
292
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
293
294
295
    :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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
    """
    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)


314
def dict2file(d, f, depth=0):
315
    for k, v in d.items():
316
        f.write('%s%s: ' % (' ' * INDENT_TAB * depth, k))
317
        if isinstance(v, dict):
318
            f.write('\n')
319
            dict2file(v, f, depth + 1)
320
        elif isinstance(v, list) or isinstance(v, tuple):
321
            f.write('\n')
322
            list2file(v, f, depth + 1)
323
        else:
324
            f.write('%s\n' % v)
325

326

327
def list2file(l, f, depth=1):
328
    for item in l:
329
330
        if isinstance(item, dict):
            dict2file(item, f, depth + 1)
331
        elif isinstance(item, list) or isinstance(item, tuple):
332
            list2file(item, f, depth + 1)
333
        else:
334
            f.write('%s%s\n' % (' ' * INDENT_TAB * depth, item))
335
336
337
338
339
340
341
342
343

# Split input auxiliary


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


344
345
346
347
348
def _get_from_parsed(parsed_str):
    try:
        parsed_str = parsed_str.strip()
    except:
        return None
349
350
351
    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
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369


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
370
371
372
373
            if tpart and not tpart[-1].endswith(' '):
                terms[-1] += ' '.join(part)
            else:
                terms += part
374
375
376
    return terms


377
def ask_user(msg, true_resp=('y', ), out=stdout, user_in=stdin):
378
379
    """Print msg and read user response

380
    :param true_resp: (tuple of chars)
381
382
383

    :returns: (bool) True if reponse in true responses, False otherwise
    """
384
385
386
387
388
389
    yep = ', '.join(true_resp)
    nope = '<not %s>' % yep if 'n' in true_resp or 'N' in true_resp else 'N'
    out.write(u'%s [%s/%s]: ' % (msg, yep, nope))
    out.flush()
    user_response = user_in.readline()
    return user_response[0].lower() in [s.lower() for s in true_resp]
390

391
392
393
394
395
396
397
398
399
400
401

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
402
403
404
405
406
407


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)
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
436
437
438
439
440
441


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