Class: PowerBar

Inherits:
Object
  • Object
show all
Defined in:
lib/powerbar.rb

Overview

This is PowerBar - The last progressbar-library you'll ever need.

Defined Under Namespace

Classes: Rate

Constant Summary collapse

STRIP_ANSI =
Regexp.compile '\e\[(\d+)(;\d+)?(;\d+)?[m|K]', nil
RUBY18 =
RUBY_VERSION[0..2] == "1.8"

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ PowerBar

Returns a new instance of PowerBar.


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
# File 'lib/powerbar.rb', line 34

def initialize(opts={})
  @@exit_hooked = false
  @state = Hashie::Mash.new( {
    :time_last_show => Time.at(0),    # <- don't mess with us
    :time_last_update => Time.at(0),  # <- unless you know
    :time_start => nil,               # <- what you're doing!
    :time_now => nil,                 # <-
    :msg => 'PowerBar!',
    :done => 0,
    :total => :unknown,
    :settings => {
      :rate_sample_max_interval => 10,  # See PowerBar::Rate
      :rate_sample_window => 6,         # See PowerBar::Rate
      :force_mode => nil, # set to :tty or :notty to force either mode
      :kilo => 1024, # Change this to 1000 when measuring network traffic or such.
      :tty => {      # <== Settings when stdout is a tty
        :finite => { # <== Settings for a finite progress bar (when total != :unknown)
          # The :output Proc is called to draw on the screen --------------------.
          :output => Proc.new{ |s| $stderr.print s[0..terminal_width()-1] }, # <-'
          :interval => 0.1,  # Minimum interval between screen refreshes (in seconds)
          :show_eta => true, # Set to false if you want to hide the ETA without changing the template
          :template => { # <== template for a finite progress bar on a tty
            :pre  => "\e[1G\e[?25l",  # printed before the progress-bar
            #
            # :main is the progressbar template
            #
            # The following tokens are available:
            #   msg, bar, rate, percent, elapsed, eta, done, total
            #
            # Tokens may be used like so:
            #    ${<foo>}
            # OR:
            #    ${surrounding <foo> text}
            #
            # The surrounding text is only rendered when <foo>
            # evaluates to something other than nil.
            :main => '${<msg>}: ${[<bar>] }${<rate>/s }${<percent>% }${<elapsed>}${, ETA: <eta>}',
            :post => '',             # printed after the progressbar
            :wipe => "\e[0m\e[1G\e[K", # printed when 'wipe' is called
            :close => "\e[?25h\n",   # printed when 'close' is called
            :exit => "\e[?25h",      # printed if the process exits unexpectedly
            :barchar => RUBY18 ? '#' : "\u2588", # fill-char for the progress-bar
            :padchar => RUBY18 ? '.' : "\u2022"  # padding-char for the progress-bar
          },
        },
        :infinite => { # <== Settings for an infinite progress "bar" (when total is :unknown)
          :output => Proc.new{ |s| $stderr.print s[0..terminal_width()-1] },
          :interval => 0.1,
          :show_eta => false,
          :template => {
            :pre  => "\e[1G\e[?25l",
            :main => "${<msg>}: ${<done> }${<rate>/s }${<elapsed>}",
            :post => "\e[K",
            :wipe => "\e[0m\e[1G\e[K",
            :close => "\e[?25h\n",
            :exit => "\e[?25h",
            :barchar => RUBY18 ? '#' : "\u2588",
            :padchar => RUBY18 ? '.' : "\u2022"
          },
        }
      },
      :notty => { # <== Settings when stdout is not a tty
        :finite => {
          # You may want to hook in your favorite Logger-Library here. ---.
          :output => Proc.new{ |s| $stderr.print s },  # <----------------'
          :interval => 1,
          :show_eta => true,
          :line_width => 78, # Maximum output line width
          :template => {
            :pre  => '',
            :main => "${<msg>}: ${<done>}/${<total>}, ${<percent>%}${, <rate>/s}${, elapsed: <elapsed>}${, ETA: <eta>}\n",
            :post => '',
            :wipe => '',
            :close => nil,
            :exit => nil,
            :barchar => "#",
            :padchar => "."
          },
        },
        :infinite => {
          :output => Proc.new{ |s| $stderr.print s },
          :interval => 1,
          :show_eta => false,
          :line_width => 78,
          :template => {
            :pre  => "",
            :main => "${<msg>}: ${<done> }${<rate>/s }${<elapsed>}\n",
            :post => "",
            :wipe => "",
            :close => nil,
            :exit => nil,
            :barchar => "#",
            :padchar => "."
          },
        }
      }
    }
  }.merge(opts) )
end

Instance Method Details

#barObject

Render the actual bar-portion of the PowerBar. The length of the bar is determined from the template. Returns nil if the bar-length would be == 0.


238
239
240
241
242
243
244
245
246
# File 'lib/powerbar.rb', line 238

def bar
  return nil if state.total.is_a? Symbol
  skel   = render_template(:main, [:bar])
  lwid   = state.scope_at[0] == :tty ? terminal_width() : scope.line_width
  barlen = [lwid - skel.gsub(STRIP_ANSI, '').length, 0].max
  fill   = [0,[(state.done.to_f/state.total*barlen).to_i,barlen].min].max
  thebar = scope.template.barchar * fill + scope.template.padchar * [barlen - fill,0].max
  thebar.length == 0 ? nil : thebar
end

#close(fill = false) ⇒ Object

Print the close-template and defuse the exit-hook. Be a good citizen, always close your PowerBars!


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/powerbar.rb', line 173

def close(fill=false)
  show(
    {
      :done => fill && !state.total.is_a?(Symbol) ? state.total : state.done,
      :tty => {
                :finite => { :show_eta => false },
                :infinite => { :show_eta => false },
              },
      :notty => {
                :finite => { :show_eta => false },
                :infinite => { :show_eta => false },
              },
    }, true)
  scope.output.call(scope.template.close) unless scope.template.close.nil?
  state.closed = true
end

#doneObject


303
304
305
# File 'lib/powerbar.rb', line 303

def done
  state.done
end

#elapsedObject


270
271
272
# File 'lib/powerbar.rb', line 270

def elapsed
  (state.time_now - state.time_start).to_f
end

#etaObject


260
261
262
# File 'lib/powerbar.rb', line 260

def eta
  (state.total - state.done) / rate
end

#h_barObject


248
249
250
# File 'lib/powerbar.rb', line 248

def h_bar
  bar
end

#h_doneObject


307
308
309
# File 'lib/powerbar.rb', line 307

def h_done
  humanize_quantity(state.done)
end

#h_elapsedObject


274
275
276
# File 'lib/powerbar.rb', line 274

def h_elapsed
  humanize_interval(elapsed)
end

#h_etaObject

returns nil when eta is < 1 second


265
266
267
268
# File 'lib/powerbar.rb', line 265

def h_eta
  return nil unless scope.show_eta
  1 < eta ? humanize_interval(eta) : nil
end

#h_msgObject


256
257
258
# File 'lib/powerbar.rb', line 256

def h_msg
  msg
end

#h_percentObject


283
284
285
# File 'lib/powerbar.rb', line 283

def h_percent
  sprintf "%d", percent
end

#h_rateObject


291
292
293
# File 'lib/powerbar.rb', line 291

def h_rate
  humanize_quantity(round(rate, 1))
end

#h_totalObject


299
300
301
# File 'lib/powerbar.rb', line 299

def h_total
  humanize_quantity(state.total)
end

#hook_exitObject

Hook at_exit to ensure cleanup if we get interrupted


161
162
163
164
165
166
167
168
169
# File 'lib/powerbar.rb', line 161

def hook_exit
  return if @@exit_hooked
  if scope.template.exit
    at_exit do
      exit!
    end
  end
  @@exit_hooked = true
end

#msgObject


252
253
254
# File 'lib/powerbar.rb', line 252

def msg
  state.msg
end

#percentObject


278
279
280
281
# File 'lib/powerbar.rb', line 278

def percent
  return 0.0 if state.total.is_a? Symbol
  state.done.to_f/state.total*100
end

Remove progress-bar, print a message


191
192
193
194
# File 'lib/powerbar.rb', line 191

def print(s)
  wipe
  scope.output.call(s)
end

#rateObject


287
288
289
# File 'lib/powerbar.rb', line 287

def rate
  @rate.avg
end

#render(opts = {}) ⇒ Object

Render the PowerBar and return as a string.


225
226
227
228
# File 'lib/powerbar.rb', line 225

def render(opts={})
  update(opts)
  render_template
end

#scopeObject

settings under current scope (e.g. tty.infinite)


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/powerbar.rb', line 140

def scope
  scope_hash = [settings.force_mode,state.total].hash
  return @state.scope unless @state.scope.nil? or scope_hash != @state.scope_hash
  state.scope_at = [
    settings.force_mode || ($stdout.isatty ? :tty : :notty),
    :unknown == state.total ? :infinite : :finite
  ]
  state.scope = state.settings
  state.scope_at.each do |s|
    begin
      state.scope = state.scope[s]
    rescue NoMethodError
      raise StandardError, "Invalid configuration: #{state.scope_at.join('.')} "+
                           "(Can't resolve: #{state.scope_at[state.scope_at.index(s)-1]})"
    end
  end
  state.scope_hash = scope_hash
  state.scope
end

#settingsObject

settings-hash


135
136
137
# File 'lib/powerbar.rb', line 135

def settings
  @state.settings
end

#show(opts = {}, force = false) ⇒ Object

Output the PowerBar. Returns true if bar was shown, false otherwise.


210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/powerbar.rb', line 210

def show(opts={}, force=false)
  return false if scope.interval > Time.now - state.time_last_show and force == false

  update(opts)
  hook_exit

  state.time_last_show = Time.now
  state.closed = false
  scope.output.call(scope.template.pre)
  scope.output.call(render)
  scope.output.call(scope.template.post)
  true
end

#terminal_widthObject


311
312
313
314
315
# File 'lib/powerbar.rb', line 311

def terminal_width
  rows, cols = IO.console.winsize
  cols -= 1 if Gem.win_platform?
  cols
end

#totalObject


295
296
297
# File 'lib/powerbar.rb', line 295

def total
  state.total
end

#update(opts = {}) ⇒ Object

Update state (and settings) without printing anything.


197
198
199
200
201
202
203
204
205
206
# File 'lib/powerbar.rb', line 197

def update(opts={})
  state.merge!(opts)
  state.time_start ||= Time.now
  state.time_now = Time.now

  @rate ||= PowerBar::Rate.new(state.time_now,
                               state.settings.rate_sample_window,
                               state.settings.rate_sample_max_interval)
  @rate.append(state.time_now, state.done)
end

#wipeObject

Remove the PowerBar from the screen.


231
232
233
# File 'lib/powerbar.rb', line 231

def wipe
  scope.output.call(scope.template.wipe)
end