File size: 6,309 Bytes
33ba306
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import sys


class Progress(object):
    def __init__(self,
                 first: int = 1,  # the initial iteration
                 last: int = 10,  # the final iteration
                 string: str = 'Processing iteration',  # the string prefix that is returned
                 fraction: bool = True,  # whether to output the fraction of things completed
                 rng: bool = False,  # whether to output the range that the iterations span
                 percent: bool = True,  # whether to show percent completion
                 hash: bool = False,  # whether to have a hash bar for progress
                 hashnum: int = 20,  # width of the hash progress bar
                 endmsg: str = 'DONE',  # end message
                 writeevery: int = 1,  # write output every n calls
                 ):
        """
        A progress output object for use when performing a large number of
        repititions of the same process and informing the user of progress
        is desired.

        :param first: The first iteration of the process.
        :param last: The last iteration of the process.
        :param string: The string prefix that is written (this is usually information about
            the process.
        :param fraction: Whether the fractional progress should be written in the printed string. e.g. 1/10.
        :param rng: Whether the iteration range should be written in the printed string.
            e.g. (1-10)
        :param percent: Whether the percent progress should be written in the printed string.
            e.g. 10.0%
        :param hash: Whether a hash-type progress bar should be written in the printed
            string. e.g. |#####       | Default: False
        :param hashnum: If *hashes* is True, this is the width of the hash-type progress bar.
        :param endmsg: The message that is printed when the `fin()` method is called.
        :param writeevery: Write an output every n calls. This can be used if an iteration is
            rapid and printing every output is not particularly useful.
        
        **Examples**
        
        ::
        
            >>> prog = Progress()
            >>> prog.write(1)
            Processing iteration #1/10 0.0%
            
            >>> prog.write(7)
            Processing iteration #7/10 66.7%
            
            >>> for i in range(1,11):
                    prog.write(i)
                prog.fin()
            Processing iteration #10/10 100.0% DONE
                
        
        """
        self.first = first
        self.last = last
        self.string = string
        self.fraction = fraction
        self.rng = rng
        self.percent = percent
        self.hash = hash
        self.hashnum = hashnum
        self.endmsg = endmsg
        self.writeevery = writeevery
        self.wr = sys.stdout.write
        self.fl = sys.stdout.flush
        self.count = 0
        self.strlen = 0
        self.current = 0  # current state
        self.spinner = ['|', '/', '-', '\\']

    def __str__(self):
        """returns the progress string at the current iteration"""
        return self.write(self.current, True)

    def __repr__(self):
        return f'{self.__class__.__name__}({self.string} {self.first}-{self.last})'

    def __getitem__(self, x):
        """
        Prints and returns the progress string at iteration x
        This accomplishes the same thing as write()
        """
        return self.write(x)

    @property
    def perc(self):
        try:
            return round(
                (float(self.current) - self.first)
                / float(self.last - self.first)
                * 100.,
                1
            )
        except ZeroDivisionError:
            return 0.

    def write(self, current, suppress=False):
        """
        Writes the progress of the iteration

        :param current: current iteration
        :param suppress: suppress output
        :return: formatted output string
        """
        self.count += 1  # keep count
        if self.writeevery != 1:
            # if the counter does not match the write, bail out
            if self.count != self.last and self.count % self.writeevery != 0:
                return None
        self.current = current  # saves the current state
        string = f'{self.string}'  # begin the string
        if self.fraction is True:
            string += f' #{current - self.first + 1}/{self.last - self.first + 1}'
        if self.rng is True:
            string += f' ({self.first}-{self.last})'
        if self.percent is True:
            string += f' {self.perc}%'
        if self.hash is True:
            string += self.hashes()
        # create space filler if output has somehow shrunk below the length of the previous output
        if len(string) < self.strlen:
            string += ' ' * (self.strlen - len(string))
        self.strlen = len(string)
        # does not write string to terminal, instead returns the generated progress string
        if suppress is True:
            return string
        try:
            self.wr(f'\r{string}')
        # a catch for I/O errors that sometimes pop up for large iteration processes
        except ValueError:
            pass
        return string

    def hashes(self):
        """generates the hash-type progress bar if called for"""
        out = ' |'
        num = int(self.perc / 100. * self.hashnum)
        out += '#' * num  # add completed
        if num < self.hashnum:  # add spinner
            out += self.spinner[self.count % 4]
        out += ' ' * (self.hashnum - len(out) + 2)  # add still to go
        out += '|'
        return out

    def fin(self, msg=None):
        """
        writes the completion message of the object and starts a new line
        if msg is specified, that message will be written instead of the object's 
        completion message
        """
        if msg is None:
           msg = self.endmsg
        self.wr(f' {msg}\n')
        self.fl()  # flush output


if __name__ == '__main__':  # for testing and troubleshooting
    first = 1
    finish = 255
    prog = Progress(
        first=first,
        last=finish,
        # hashes = True,
    )
    import time

    for i in range(first, finish + 1):
        prog.write(i)
        time.sleep(0.01)
    prog.fin()