File: bs_calc.py.in

package info (click to toggle)
libbytesize 2.11-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 784 kB
  • sloc: python: 1,850; ansic: 994; sh: 666; makefile: 258
file content (151 lines) | stat: -rw-r--r-- 5,283 bytes parent folder | download | duplicates (4)
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/python3

import sys
from argparse import ArgumentParser
from decimal import Decimal, InvalidOperation
from bytesize import Size, ZeroDivisionError

b_units = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB")
d_units = ("KB", "MB", "GB", "TB", "PB", "EB")

_WHITESPACE = {" ", "\t", "\n"}
_OPERATORS = {"+", "-", "*", "/", "%", "(", ")"}  # TODO: add divmod
_DOUBLE_OPERATORS = {"/", "*"}  # // and ** are valid operators too
_EXP_OPERATORS = {"-", "+"}     # 1e+2, 1e-2


def _get_operand(op_str):
    try:
        return int(op_str)
    except ValueError:
        try:
            return Decimal(op_str)
        except InvalidOperation:
            return Size(op_str)


def _tokenize(expression):
    tokens = []
    operand_str_start = -1
    for i, char in enumerate(expression):
        if char in _WHITESPACE:
            continue
        elif char in _OPERATORS:
            if char in _EXP_OPERATORS and expression[i - 1] in ("e", "E"):
                continue
            if char in _DOUBLE_OPERATORS and expression[i - 1] == char:
                # see _DOUBLE_OPERATORS above
                tokens[-1] = tokens[-1] + char
                continue
            if operand_str_start != -1:
                op = _get_operand(expression[operand_str_start:i])
                tokens.append(op)
                operand_str_start = -1
            tokens.append(expression[i])
        elif operand_str_start == -1:
            operand_str_start = i
    if operand_str_start != -1:
        op = _get_operand(expression[operand_str_start:])
        tokens.append(op)
    return tokens


def _tokens_to_eval_str(tokens):
    eval_tokens = []
    for token in tokens:
        if isinstance(token, int):
            eval_tokens.append("%d" % token)
        elif isinstance(token, Decimal):
            eval_tokens.append("Decimal('%s')" % token)
        elif isinstance(token, Size):
            eval_tokens.append("Size(%d)" % token)
        else:
            eval_tokens.append(token)

    return " ".join(eval_tokens)


def _print_result(result, args):
    # TODO: support configurable n_places (aligned printing is not so easy then)
    n_places = 2
    if isinstance(result, Size):
        if args.human_readable:
            print(result.human_readable())
        elif args.unit is not None:
            value = result.convert_to(args.unit)
            if int(value) == value:
                print("%d %s" % (int(value), args.unit))
            else:
                print(("%0." + str(n_places) + "f" + " %s") % (value, args.unit))
        else:
            in_bytes = "%d B" % int(result)
            print(in_bytes)

            format_str = "%" + str(len(in_bytes) - n_places) + "." + str(n_places) + "f"
            for b_unit in b_units[1:]:
                converted = (format_str % result.convert_to(b_unit))
                # don't print "0.00 CRAZY_BIG_UNIT"
                if converted.strip() not in ("0." + n_places * "0", "-0." + n_places * "0"):
                    print("%s %s" % (converted, b_unit))
    else:
        print(str(result))


def _main():
    ap = ArgumentParser(epilog="Report issues at https://github.com/storaged-project/libbytesize/issues")
    ap.add_argument("--version", action="version", version="@VERSION@")
    ap.add_argument("-u", "--unit", choices=(b_units + d_units),
                    help="Unit to show the result in")
    ap.add_argument("-b", "-B", dest="unit", const="B",
                    help="Show result in bytes", action="store_const")
    for b_unit in b_units[1:]:
        ap.add_argument("-" + b_unit[0].lower(), "-" + b_unit[0], "--" + b_unit,
                        dest="unit", const=b_unit,
                        help="Show result in " + b_unit, action="store_const")
    for d_unit in d_units:
        ap.add_argument("--" + d_unit,
                        dest="unit", const=d_unit,
                        help="Show result in " + d_unit, action="store_const")
    ap.add_argument("-H", "--human-readable", dest="human_readable",
                    help="Show only single 'best' result", action="store_true")
    ap.add_argument(metavar="EXPRESSION_PART", dest="expressions", nargs="+")

    if not sys.stdin.isatty():
        stdin = sys.stdin.read().splitlines()
        args = ap.parse_args(sys.argv[1:] + stdin)
    else:
        args = ap.parse_args()

    try:
        tokens = _tokenize(" ".join(args.expressions))
    except ValueError as e:
        print("Error while parsing expression: %s" % e)
        return 1

    any_size = any(isinstance(token, Size) for token in tokens)

    eval_str = _tokens_to_eval_str(tokens)
    try:
        result = eval(eval_str)
    except (TypeError, ValueError, ZeroDivisionError) as e:
        print("Error during evaluation: %s" % e)
        return 1
    except SyntaxError as e:
        print("Error during evaluation: %s" % e.msg)
        print(eval_str)
        print(" " * (e.offset - 1) + "^")
        return 1

    # The given expression contained no Size, just numbers. By default we just
    # assume the whole expression was in bytes so let's convert the result
    # before printing it.
    if not any_size:
        result = Size(result)

    _print_result(result, args)

    return 0


if __name__ == "__main__":
    sys.exit(_main())