File: progressbar.rb

package info (click to toggle)
ruby-ansi 1.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 404 kB
  • sloc: ruby: 1,880; makefile: 5
file content (292 lines) | stat: -rw-r--r-- 6,424 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# Copyright (C) 2009 Thomas Sawyer
#
# This library is based on the original ProgressBar
# by Satoru Takabayashi.
#
# ProgressBar Copyright (C) 2001 Satoru Takabayashi

require 'ansi/code'

module ANSI

  # = Progressbar
  #
  # Progressbar is a text-based progressbar library.
  #
  #   pbar = Progressbar.new( "Demo", 100 )
  #   100.times { pbar.inc }
  #   pbar.finish
  #
  class ProgressBar
    #
    def initialize(title, total, out=STDERR)
      @title = title
      @total = total
      @out   = out

      @bar_length = 80
      @bar_mark = "|"
      @total_overflow = true
      @current = 0
      @previous = 0
      @is_finished = false
      @start_time = Time.now
      @format = "%-14s %3d%% %s %s"
      @format_arguments = [:title, :percentage, :bar, :stat]
      @styles = {}
      #
      yield self if block_given?
      #
      show_progress
    end

    public

    attr_accessor :format
    attr_accessor :format_arguments
    attr_accessor :styles

    #
    def title=(str)
      @title = str
    end
    #
    def bar_mark=(mark)
      @bar_mark = String(mark)[0..0]
    end
    alias_method :barmark=, :bar_mark=
    alias_method :mark=,    :bar_mark=

    def total_overflow=(boolv)
      @total_overflow = boolv ? true : false
    end

    # Get rid of warning about Kenrel method being redefined.
    remove_method :format

    # Set format and format arguments.
    def format(format, *arguments)
      @format = format
      @format_arguments = *arguments unless arguments.empty?
    end

    # Set ANSI styling options.
    def style(options)
      @styles = options
    end

    #
    def standard_mode
      @format = "%-14s %3d%% %s %s"
      @format_arguments = [:title, :percentage, :bar, :stat]
    end

    #
    def transfer_mode
      @format = "%-14s %3d%% %s %s"
      @format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
    end

    # For backward compatability
    alias_method :file_transfer_mode, :transfer_mode

    def finish
      @current = @total
      @is_finished = true
      show_progress
    end

    def flush
      @out.flush
    end

    def halt
      @is_finished = true
      show_progress
    end

    def set(count)
      if count < 0
        raise "invalid count less than zero: #{count}"
      elsif count > @total
        if @total_overflow
          @total = count + 1
        else
          raise "invalid count greater than total: #{count}"
        end
      end
      @current = count
      show_progress
      @previous = @current
    end

    #
    def reset
      @current = 0
      @is_finished = false
    end

    #
    def inc(step = 1)
      @current += step
      @current = @total if @current > @total
      show_progress
      @previous = @current
    end

    #
    def clear
      @out.print(" " * get_width + eol)
    end

    def inspect
      "(ProgressBar: #{@current}/#{@total})"
    end

    private

    #
    def convert_bytes(bytes)
      if bytes < 1024
        sprintf("%6dB", bytes)
      elsif bytes < 1024 * 1000 # 1000kb
        sprintf("%5.1fKB", bytes.to_f / 1024)
      elsif bytes < 1024 * 1024 * 1000  # 1000mb
        sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
      else
        sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
      end
    end
    #
    def transfer_rate
      bytes_per_second = @current.to_f / (Time.now - @start_time)
      sprintf("%s/s", convert_bytes(bytes_per_second))
    end
    #
    def bytes
      convert_bytes(@current)
    end
    #
    def format_time(t)
      t = t.to_i
      sec = t % 60
      min  = (t / 60) % 60
      hour = t / 3600
      sprintf("%02d:%02d:%02d", hour, min, sec);
    end
    #
    # ETA stands for Estimated Time of Arrival.
    def eta
      if @current == 0
        "ETA:  --:--:--"
      else
        elapsed = Time.now - @start_time
        eta = elapsed * @total / @current - elapsed;
        sprintf("ETA:  %s", format_time(eta))
      end
    end
    #
    def elapsed
      elapsed = Time.now - @start_time
      sprintf("Time: %s", format_time(elapsed))
    end
    #
    def stat
      if @is_finished then elapsed else eta end
    end
    #
    def stat_for_file_transfer
      if @is_finished then
        sprintf("%s %s %s", bytes, transfer_rate, elapsed)
      else
        sprintf("%s %s %s", bytes, transfer_rate, eta)
      end
    end
    #
    def eol
      if @is_finished then "\n" else "\r" end
    end
    #
    def bar
      len = percentage * @bar_length / 100
      sprintf("|%s%s|", @bar_mark * len, " " *  (@bar_length - len))
    end
    #
    def percentage
      if @total.zero?
        100
      else
        @current  * 100 / @total
      end
    end
    #
    def title
      @title[0,13] + ":"
    end

    # TODO: Use Terminal.terminal_width instead.
    def get_width
      # FIXME: I don't know how portable it is.
      default_width = 80
      begin
        tiocgwinsz = 0x5413
        data = [0, 0, 0, 0].pack("SSSS")
        if @out.ioctl(tiocgwinsz, data) >= 0 then
          #rows, cols, xpixels, ypixels = data.unpack("SSSS")
          cols = data.unpack("SSSS")[1]
          if cols >= 0 then cols else default_width end
        else
          default_width
        end
      rescue Exception
        default_width
      end
    end

    #
    def show
      arguments = @format_arguments.map do |method|
        colorize(send(method), styles[method])
      end
      line      = sprintf(@format, *arguments)
      width     = get_width
      length    = ANSI::Code.uncolor{line}.length
      if length == width - 1
        @out.print(line + eol)
      elsif length >= width
        @bar_length = [@bar_length - (length - width + 1), 0].max
        @bar_length == 0 ?  @out.print(line + eol) : show
      else #line.length < width - 1
        @bar_length += width - length + 1
        show
      end
    end

    #
    def show_progress
      if @total.zero?
        cur_percentage = 100
        prev_percentage = 0
      else
        cur_percentage  = (@current  * 100 / @total).to_i
        prev_percentage = (@previous * 100 / @total).to_i
      end
      if cur_percentage > prev_percentage || @is_finished
        show
      end
    end

    #
    def colorize(part, style)
      return part unless style
      #[style].flatten.inject(part){ |pt, st| ANSI::Code.ansi(pt, *st) }
      ANSI::Code.ansi(part, *style)
    end

  end

  #
  Progressbar = ProgressBar #:nodoc:

end