powerbar 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.rdoc +83 -0
- data/Rakefile +1 -0
- data/ass/screenshot.png +0 -0
- data/bin/powerbar-demo +236 -0
- data/lib/powerbar.rb +379 -0
- data/lib/powerbar/version.rb +3 -0
- data/powerbar.gemspec +24 -0
- metadata +78 -0
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
= PowerBar
|
2
|
+
|
3
|
+
This is PowerBar - The last progressbar-library you'll ever need.
|
4
|
+
|
5
|
+
== Features
|
6
|
+
|
7
|
+
* Detects when stdout is not a terminal and automatically falls back to logging
|
8
|
+
* Does not clutter your log-files with ansi-codes!
|
9
|
+
* If your CLI-app can run interactively and non-interactively (e.g. cronjob)
|
10
|
+
you will automatically get reasonable progress-output in both modes.
|
11
|
+
* By default prints to stderr but can call any output-method
|
12
|
+
of your choice (e.g. your favorite Logger).
|
13
|
+
|
14
|
+
* Fully customizable; all output is template-driven.
|
15
|
+
|
16
|
+
* All output is optional. You may use PowerBar to silently collect progress
|
17
|
+
information (percentage-done, throughput, ETA, etc.) and then use the
|
18
|
+
computed values elsewhere in your app.
|
19
|
+
|
20
|
+
* All state can be updated at any time. If you're monitoring a
|
21
|
+
multi-part operation you can change the status-message of a running
|
22
|
+
PowerBar at any time.
|
23
|
+
|
24
|
+
== Screenshot
|
25
|
+
|
26
|
+
{screenshot}[http://github.com/busyloop/powerbar/raw/master/ass/screenshot.png]
|
27
|
+
|
28
|
+
|
29
|
+
== Installation
|
30
|
+
|
31
|
+
gem install powerbar
|
32
|
+
|
33
|
+
== Getting Started
|
34
|
+
|
35
|
+
Watch the demo that was installed along with the gem:
|
36
|
+
|
37
|
+
powerbar-demo
|
38
|
+
|
39
|
+
Then look at the {source-code}[https://github.com/busyloop/powerbar/blob/master/bin/powerbar-demo] of the demo. Pretty much all use-cases are covered in there, including templates and how to hook in your own logger.
|
40
|
+
|
41
|
+
|
42
|
+
== Example (for the impatient)
|
43
|
+
|
44
|
+
#!/usr/bin/env ruby
|
45
|
+
|
46
|
+
require 'powerbar'
|
47
|
+
|
48
|
+
total = 100000
|
49
|
+
step = 1000
|
50
|
+
|
51
|
+
p = PowerBar.new
|
52
|
+
(0..total).step(step).each do |i|
|
53
|
+
p.show({:msg => 'DEMO 1 - Ten seconds of progress', :done => i, :total => total})
|
54
|
+
sleep 0.1
|
55
|
+
end
|
56
|
+
p.close
|
57
|
+
|
58
|
+
== Documentation?
|
59
|
+
|
60
|
+
Use the {source}[https://github.com/busyloop/powerbar/blob/master/lib/powerbar.rb], Luke!
|
61
|
+
|
62
|
+
== License (MIT)
|
63
|
+
|
64
|
+
Copyright (C) 2011 by moe@busyloop.net
|
65
|
+
|
66
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
67
|
+
of this software and associated documentation files (the "Software"), to deal
|
68
|
+
in the Software without restriction, including without limitation the rights
|
69
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
70
|
+
copies of the Software, and to permit persons to whom the Software is
|
71
|
+
furnished to do so, subject to the following conditions:
|
72
|
+
|
73
|
+
The above copyright notice and this permission notice shall be included in
|
74
|
+
all copies or substantial portions of the Software.
|
75
|
+
|
76
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
77
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
78
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
79
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
80
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
81
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
82
|
+
THE SOFTWARE.
|
83
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/ass/screenshot.png
ADDED
Binary file
|
data/bin/powerbar-demo
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'powerbar'
|
4
|
+
|
5
|
+
# DEMO 1
|
6
|
+
def demo_1
|
7
|
+
total = 100000
|
8
|
+
step = 1000
|
9
|
+
|
10
|
+
p = PowerBar.new
|
11
|
+
(0..total).step(step).each do |i|
|
12
|
+
p.show({:msg => 'DEMO 1 - Ten seconds of progress', :done => i, :total => total})
|
13
|
+
sleep 0.1
|
14
|
+
end
|
15
|
+
p.close
|
16
|
+
end
|
17
|
+
|
18
|
+
# DEMO 2
|
19
|
+
def demo_2
|
20
|
+
total = 100000
|
21
|
+
step = 1000
|
22
|
+
text = " "*30 + "Need to change text in mid-progress? No Problemo!" +
|
23
|
+
" "*5 + "As seen in: DEMO 2 - Scrolling madness"
|
24
|
+
p = PowerBar.new
|
25
|
+
j = 0
|
26
|
+
(0..total).step(step).each do |i|
|
27
|
+
woah = sprintf('%-26s', text[j%text.length..j%text.length+25])
|
28
|
+
p.show({:msg => woah, :done => i, :total => total})
|
29
|
+
j += 1
|
30
|
+
sleep 0.1
|
31
|
+
end
|
32
|
+
p.close
|
33
|
+
end
|
34
|
+
|
35
|
+
# DEMO 3
|
36
|
+
def demo_3
|
37
|
+
total = 100000
|
38
|
+
step = 1000
|
39
|
+
text = "DEMO 3 - Don't sweat your message width too much, we can squeeze it some"
|
40
|
+
p = PowerBar.new
|
41
|
+
j = 0
|
42
|
+
(0..total).step(step).each do |i|
|
43
|
+
woah = text[0..j]
|
44
|
+
p.show({:msg => woah, :done => i, :total => total})
|
45
|
+
j += 1
|
46
|
+
sleep 0.1
|
47
|
+
end
|
48
|
+
p.close
|
49
|
+
end
|
50
|
+
|
51
|
+
# DEMO 4
|
52
|
+
def demo_4
|
53
|
+
total = 100000
|
54
|
+
step = 1000
|
55
|
+
text = "\e[0mDEMO 4 - Colors!"
|
56
|
+
p = PowerBar.new
|
57
|
+
p.settings.tty.finite.template.main = \
|
58
|
+
"${<msg>} ${<bar> }\e[0m${<rate>/s} \e[33;1m${<percent>%} " +
|
59
|
+
"\e[36;1m${<elapsed>}\e[31;1m${ ETA: <eta>}"
|
60
|
+
p.settings.tty.finite.template.padchar = "\e[30;1m\u2589"
|
61
|
+
p.settings.tty.finite.template.barchar = "\e[34;1m\u2589"
|
62
|
+
p.settings.tty.finite.template.exit = "\e[?25h\e[0m" # clean up after us
|
63
|
+
p.settings.tty.finite.template.close = "\e[?25h\e[0m\n" # clean up after us
|
64
|
+
p.settings.tty.finite.output = Proc.new{ |s|
|
65
|
+
# The default output function truncates our
|
66
|
+
# string to to enable the "squeezing" as seen in the
|
67
|
+
# previous demo. This doesn't mix so well with ANSI-colors,
|
68
|
+
# so if you want to use colors you'll have to make the output
|
69
|
+
# a little more naive. Like this:
|
70
|
+
$stderr.print s
|
71
|
+
}
|
72
|
+
j = 0
|
73
|
+
(0..total).step(step).each do |i|
|
74
|
+
p.show({:msg => text, :done => i, :total => total})
|
75
|
+
j += 1
|
76
|
+
sleep 0.1
|
77
|
+
end
|
78
|
+
p.close
|
79
|
+
end
|
80
|
+
|
81
|
+
# DEMO 5
|
82
|
+
def demo_5
|
83
|
+
total = 100000
|
84
|
+
step = 1000
|
85
|
+
text = "DEMO 5 - When total is :unknown then we only display what we know"
|
86
|
+
p = PowerBar.new
|
87
|
+
j = 0
|
88
|
+
(0..total).step(step).each do |i|
|
89
|
+
p.show({:msg => text, :done => i, :total => :unknown})
|
90
|
+
j += 1
|
91
|
+
sleep 0.1
|
92
|
+
end
|
93
|
+
p.close
|
94
|
+
end
|
95
|
+
|
96
|
+
# DEMO 6
|
97
|
+
def demo_6
|
98
|
+
total = 100000
|
99
|
+
step = 1000
|
100
|
+
text = "DEMO 6 - Still :unknown, now with a different template"
|
101
|
+
p = PowerBar.new
|
102
|
+
p.settings.tty.infinite.template.main = \
|
103
|
+
'${<msg>} > ${Rate: <rate>/s, }elapsed: ${<elapsed>}'
|
104
|
+
j = 0
|
105
|
+
(0..total).step(step).each do |i|
|
106
|
+
p.show({:msg => text, :done => i, :total => :unknown})
|
107
|
+
j += 1
|
108
|
+
sleep 0.1
|
109
|
+
end
|
110
|
+
p.close
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# DEMO 7
|
115
|
+
def demo_7
|
116
|
+
total = 100000
|
117
|
+
step = 1000
|
118
|
+
text = "DEMO 7 - Forcing notty mode"
|
119
|
+
p = PowerBar.new
|
120
|
+
p.settings.force_mode = :notty
|
121
|
+
j = 0
|
122
|
+
(0..total).step(step).each do |i|
|
123
|
+
p.show({:msg => text, :done => i, :total => total})
|
124
|
+
j += 1
|
125
|
+
sleep 0.1
|
126
|
+
end
|
127
|
+
p.close
|
128
|
+
end
|
129
|
+
|
130
|
+
# DEMO 8
|
131
|
+
def demo_8
|
132
|
+
total = 100000
|
133
|
+
step = 1000
|
134
|
+
log = Logger.new(STDOUT)
|
135
|
+
text = "DEMO 8 - Forcing notty mode and using a logger"
|
136
|
+
p = PowerBar.new
|
137
|
+
p.settings.force_mode = :notty
|
138
|
+
p.settings.notty.finite.output = Proc.new { |s|
|
139
|
+
log.debug(s) unless s == ''
|
140
|
+
}
|
141
|
+
j = 0
|
142
|
+
(0..total).step(step).each do |i|
|
143
|
+
p.show({:msg => text, :done => i, :total => total})
|
144
|
+
j += 1
|
145
|
+
sleep 0.1
|
146
|
+
end
|
147
|
+
p.close
|
148
|
+
end
|
149
|
+
|
150
|
+
# DEMO 9
|
151
|
+
def demo_9
|
152
|
+
total = 100000
|
153
|
+
step = 1000
|
154
|
+
|
155
|
+
puts "DEMO 9 - No output by PowerBar."
|
156
|
+
puts "We only use it to collect stats and then roll our own output...\n---"
|
157
|
+
p = PowerBar.new
|
158
|
+
j = 0
|
159
|
+
(0..total).step(step).each do |i|
|
160
|
+
# update() does not output anything, it only updates (who would've guessed!)
|
161
|
+
p.update({:done => i, :total => total})
|
162
|
+
j += 1
|
163
|
+
if 0 == j % 50
|
164
|
+
puts "elapsed is: #{p.elapsed}, humanized elapsed is: #{p.h_elapsed}"
|
165
|
+
puts "percent is: #{p.percent}, humanized percent is: #{p.h_percent}"
|
166
|
+
puts "eta is: #{p.eta}, humanized eta is: #{p.h_eta}"
|
167
|
+
puts "done is: #{p.done}, humanized done is: #{p.h_done}"
|
168
|
+
puts "total is: #{p.total}, humanized done is: #{p.h_total}"
|
169
|
+
puts "total is: #{p.total}, humanized done is: #{p.h_total}"
|
170
|
+
puts "bar is: #{p.bar}"
|
171
|
+
puts "---"
|
172
|
+
end
|
173
|
+
sleep 0.1
|
174
|
+
end
|
175
|
+
#p.close # no need to close this one
|
176
|
+
end
|
177
|
+
|
178
|
+
# DEMO 10
|
179
|
+
def demo_10
|
180
|
+
total = 100000
|
181
|
+
step = 1000
|
182
|
+
text = " " * 30 + "DEMO 10 - OMGWTFBBQ!"
|
183
|
+
p = PowerBar.new
|
184
|
+
p.settings.tty.finite.template.padchar = "\e[30;1m\u2589"
|
185
|
+
p.settings.tty.finite.template.barchar = "\e[34;1m\u2589"
|
186
|
+
p.settings.tty.finite.output = Proc.new{ |s| $stderr.print s }
|
187
|
+
j = 0
|
188
|
+
total = 300000000
|
189
|
+
step = 1000000
|
190
|
+
|
191
|
+
spin = ')|(|'
|
192
|
+
spun = ',.oO^`^Oo'
|
193
|
+
|
194
|
+
(0..total).step(step).each do |i|
|
195
|
+
p.send(:state).scope = nil # omghax, don't try this at home!
|
196
|
+
wtf = sprintf('%-26s', text[j%text.length..j%text.length+25])
|
197
|
+
.gsub(/./) { |char| char.send(rand(2).zero? ? :downcase : :upcase) }
|
198
|
+
p.show(
|
199
|
+
{
|
200
|
+
:msg => wtf,
|
201
|
+
:done => i,
|
202
|
+
:total => total,
|
203
|
+
:settings => {
|
204
|
+
:tty => {
|
205
|
+
:finite => {
|
206
|
+
:template => {
|
207
|
+
:barchar => "\e[36;1m" + spin[j%spin.length],
|
208
|
+
:padchar => "\e[0;34m" + spun[j%spun.length],
|
209
|
+
:main => "\e[#{rand(1)};3#{rand(7)}m${<msg>}\e[0m "+
|
210
|
+
"\e[0m${<bar> }\e[0m${<rate>/s} "+
|
211
|
+
"\e[33;1m${<percent>%} " +
|
212
|
+
"\e[36;1m${<elapsed>}\e[31;1m${ ETA: <eta>}",
|
213
|
+
:exit => "\e[?25h\e[0m",
|
214
|
+
:close => "\e[?25h\e[0m\n"
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
} # chrr..
|
219
|
+
}
|
220
|
+
)
|
221
|
+
j += 1
|
222
|
+
sleep 0.1
|
223
|
+
end
|
224
|
+
p.wipe
|
225
|
+
p.close
|
226
|
+
end
|
227
|
+
|
228
|
+
begin
|
229
|
+
method("demo_#{ARGV[0]}".to_sym).call
|
230
|
+
rescue
|
231
|
+
(1..10).each do |i|
|
232
|
+
method("demo_#{i}".to_sym).call
|
233
|
+
puts "---"
|
234
|
+
sleep 1
|
235
|
+
end
|
236
|
+
end
|
data/lib/powerbar.rb
ADDED
@@ -0,0 +1,379 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2011 by moe@busyloop.net
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'powerbar/version'
|
24
|
+
require 'ansi'
|
25
|
+
require 'hashie/mash'
|
26
|
+
|
27
|
+
class PowerBar
|
28
|
+
#
|
29
|
+
# This is PowerBar - The last progressbar-library you'll ever need.
|
30
|
+
#
|
31
|
+
|
32
|
+
STRIP_ANSI = Regexp.compile '\e\[(\d+)(;\d+)?(;\d+)?[m|K]', nil
|
33
|
+
|
34
|
+
def initialize(opts={})
|
35
|
+
@@exit_hooked = false
|
36
|
+
@state = Hashie::Mash.new( {
|
37
|
+
:time_last_show => Time.at(0), # <- don't mess with us
|
38
|
+
:time_last_update => Time.at(0), # <- unless you know
|
39
|
+
:time_start => nil, # <- what you're doing!
|
40
|
+
:time_now => nil, # <-
|
41
|
+
:msg => 'PowerBar!', # <--- this one is safe to mess with ;)
|
42
|
+
:settings => {
|
43
|
+
:rate_sample_max_interval => 10, # See PowerBar::Rate
|
44
|
+
:rate_sample_window => 6, # See PowerBar::Rate
|
45
|
+
:force_mode => nil, # set to :tty or :notty to force either mode
|
46
|
+
:tty => { # <== Settings when stdout is a tty
|
47
|
+
:finite => { # <== Settings for a finite progress bar (when total != :unknown)
|
48
|
+
# The :output Proc is called to draw on the screen --------------------.
|
49
|
+
:output => Proc.new{ |s| $stderr.print s[0..terminal_width()-1] }, # <-'
|
50
|
+
:interval => 0.1, # Minimum interval between screen refreshes (in seconds)
|
51
|
+
:template => { # <== template for a finite progress bar on a tty
|
52
|
+
:pre => "\e[1000D\e[?25l", # printed before the progress-bar
|
53
|
+
#
|
54
|
+
# :main is the progressbar template
|
55
|
+
#
|
56
|
+
# The following tokens are available:
|
57
|
+
# msg, bar, rate, percent, elapsed, eta, done, total
|
58
|
+
#
|
59
|
+
# Tokens may be used like so:
|
60
|
+
# ${<foo>}
|
61
|
+
# OR:
|
62
|
+
# ${surrounding <foo> text}
|
63
|
+
#
|
64
|
+
# The surrounding text is only rendered when <foo>
|
65
|
+
# evaluates to something other than nil.
|
66
|
+
:main => '${<msg>}: ${[<bar>] }${<rate>/s }${<percent>% }${<elapsed>}${, ETA: <eta>}',
|
67
|
+
:post => '', # printed after the progressbar
|
68
|
+
:wipe => "\e[1000D\e[K", # printed when 'wipe' is called
|
69
|
+
:close => "\e[?25h\n", # printed when 'close' is called
|
70
|
+
:exit => "\e[?25h", # printed if the process exits unexpectedly
|
71
|
+
:barchar => "\u2588", # fill-char for the progress-bar
|
72
|
+
:padchar => "\u2022" # padding-char for the progress-bar
|
73
|
+
},
|
74
|
+
},
|
75
|
+
:infinite => { # <== Settings for an infinite progress "bar" (when total is :unknown)
|
76
|
+
:output => Proc.new{ |s| $stderr.print s[0..terminal_width()-1] },
|
77
|
+
:interval => 0.1,
|
78
|
+
:template => {
|
79
|
+
:pre => "\e[1000D\e[?25l",
|
80
|
+
:main => "${<msg>}: ${<done> }${<rate>/s }${<elapsed>}",
|
81
|
+
:post => "\e[K",
|
82
|
+
:wipe => "\e[1000D\e[K",
|
83
|
+
:close => "\e[?25h\n",
|
84
|
+
:exit => "\e[?25h",
|
85
|
+
:barchar => "\u2588",
|
86
|
+
:padchar => "\u2022"
|
87
|
+
},
|
88
|
+
}
|
89
|
+
},
|
90
|
+
:notty => { # <== Settings when stdout is not a tty
|
91
|
+
:finite => {
|
92
|
+
# You may want to hook in your favorite Logger-Library here. ---.
|
93
|
+
:output => Proc.new{ |s| $stderr.print s }, # <----------------'
|
94
|
+
:interval => 1,
|
95
|
+
:line_width => 78, # Maximum output line width
|
96
|
+
:template => {
|
97
|
+
:pre => '',
|
98
|
+
:main => "${<msg>}: ${<done>}/${<total>}, ${<percent>%}${, <rate>/s}${, elapsed: <elapsed>}${, ETA: <eta>}\n",
|
99
|
+
:post => '',
|
100
|
+
:wipe => '',
|
101
|
+
:close => nil,
|
102
|
+
:exit => nil,
|
103
|
+
:barchar => "#",
|
104
|
+
:padchar => "."
|
105
|
+
},
|
106
|
+
},
|
107
|
+
:infinite => {
|
108
|
+
:output => Proc.new{ |s| $stderr.print s },
|
109
|
+
:interval => 1,
|
110
|
+
:line_width => 78,
|
111
|
+
:template => {
|
112
|
+
:pre => "",
|
113
|
+
:main => "${<msg>}: ${<done> }${<rate>/s }${<elapsed>}\n",
|
114
|
+
:post => "",
|
115
|
+
:wipe => "",
|
116
|
+
:close => nil,
|
117
|
+
:exit => nil,
|
118
|
+
:barchar => "#",
|
119
|
+
:padchar => "."
|
120
|
+
},
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}.merge(opts) )
|
125
|
+
end
|
126
|
+
|
127
|
+
# Access the settings-hash
|
128
|
+
def settings
|
129
|
+
@state.settings
|
130
|
+
end
|
131
|
+
|
132
|
+
# Access settings under current scope (e.g. tty.infinite)
|
133
|
+
def scope
|
134
|
+
scope_hash = [settings.force_mode,state.total].hash
|
135
|
+
return @state.scope unless @state.scope.nil? or scope_hash != @state.scope_hash
|
136
|
+
state.scope_at = [
|
137
|
+
settings.force_mode || ($stdout.isatty ? :tty : :notty),
|
138
|
+
:unknown == state.total ? :infinite : :finite
|
139
|
+
]
|
140
|
+
state.scope = state.settings
|
141
|
+
state.scope_at.each do |s|
|
142
|
+
begin
|
143
|
+
state.scope = state.scope[s]
|
144
|
+
rescue NoMethodError
|
145
|
+
raise StandardError, "Invalid configuration: #{state.scope_at.join('.')} "+
|
146
|
+
"(Can't resolve: #{state.scope_at[state.scope_at.index(s)-1]})"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
state.scope_hash = scope_hash
|
150
|
+
state.scope
|
151
|
+
end
|
152
|
+
|
153
|
+
# Hook at_exit to ensure cleanup when we get interrupted
|
154
|
+
def hook_exit
|
155
|
+
return if @@exit_hooked
|
156
|
+
if scope.template.exit
|
157
|
+
at_exit do
|
158
|
+
exit!
|
159
|
+
end
|
160
|
+
end
|
161
|
+
@@exit_hooked = true
|
162
|
+
end
|
163
|
+
|
164
|
+
# This prints the close-template which normally prints a newline.
|
165
|
+
# Be a good citizen, always close your PowerBars!
|
166
|
+
def close
|
167
|
+
scope.output.call(scope.template.close) unless scope.template.close.nil?
|
168
|
+
state.closed = true
|
169
|
+
end
|
170
|
+
|
171
|
+
# Update state (and settings) without printing anything.
|
172
|
+
def update(opts={})
|
173
|
+
state.merge!(opts)
|
174
|
+
state.time_start ||= Time.now
|
175
|
+
state.time_now = Time.now
|
176
|
+
|
177
|
+
@rate ||= PowerBar::Rate.new(state.time_now,
|
178
|
+
state.settings.rate_sample_window,
|
179
|
+
state.settings.rate_sample_max_interval)
|
180
|
+
@rate.append(state.time_now, state.done)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Display the PowerBar.
|
184
|
+
def show(opts={})
|
185
|
+
if scope.interval <= Time.now - state.time_last_show
|
186
|
+
update(opts)
|
187
|
+
hook_exit
|
188
|
+
|
189
|
+
state.time_last_show = Time.now
|
190
|
+
state.closed = false
|
191
|
+
scope.output.call(scope.template.pre)
|
192
|
+
scope.output.call(render)
|
193
|
+
scope.output.call(scope.template.post)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Render the PowerBar and return as a string.
|
198
|
+
def render(opts={})
|
199
|
+
update(opts)
|
200
|
+
render_template
|
201
|
+
end
|
202
|
+
|
203
|
+
# Remove the PowerBar from the screen.
|
204
|
+
def wipe
|
205
|
+
scope.output.call(scope.template.wipe)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Render the actual bar-portion of the PowerBar.
|
209
|
+
# The length of the bar is determined from the template.
|
210
|
+
# Returns nil if the bar-length would be == 0.
|
211
|
+
def bar
|
212
|
+
return nil if state.total.is_a? Symbol
|
213
|
+
blank = render_template(:main, skip=[:bar])
|
214
|
+
twid = state.scope_at[0] == :tty ? terminal_width() : scope.line_width
|
215
|
+
barlen = [twid - blank.gsub(STRIP_ANSI, '').length, 0].max
|
216
|
+
done = state.done
|
217
|
+
total = state.total
|
218
|
+
barchar = scope.template.barchar
|
219
|
+
padchar = scope.template.padchar
|
220
|
+
fill = [0,[(done.to_f/total*barlen).to_i,barlen].min].max
|
221
|
+
thebar = barchar * fill + padchar * [barlen - fill,0].max
|
222
|
+
thebar.length == 0 ? nil : thebar
|
223
|
+
end
|
224
|
+
|
225
|
+
def h_bar
|
226
|
+
bar
|
227
|
+
end
|
228
|
+
|
229
|
+
def msg
|
230
|
+
state.msg
|
231
|
+
end
|
232
|
+
|
233
|
+
def h_msg
|
234
|
+
msg
|
235
|
+
end
|
236
|
+
|
237
|
+
def eta
|
238
|
+
(state.total - state.done) / rate
|
239
|
+
end
|
240
|
+
|
241
|
+
# returns nil when eta is < 1 second
|
242
|
+
def h_eta
|
243
|
+
1 < eta ? humanize_interval(eta) : nil
|
244
|
+
end
|
245
|
+
|
246
|
+
def elapsed
|
247
|
+
e = (state.time_now - state.time_start).to_f
|
248
|
+
end
|
249
|
+
|
250
|
+
def h_elapsed
|
251
|
+
humanize_interval(elapsed)
|
252
|
+
end
|
253
|
+
|
254
|
+
def percent
|
255
|
+
return 0.0 if state.total.is_a? Symbol
|
256
|
+
state.done.to_f/state.total*100
|
257
|
+
end
|
258
|
+
|
259
|
+
def h_percent
|
260
|
+
sprintf "%d", percent
|
261
|
+
end
|
262
|
+
|
263
|
+
def rate
|
264
|
+
@rate.avg
|
265
|
+
end
|
266
|
+
|
267
|
+
def h_rate
|
268
|
+
humanize_quantity(rate.round(1))
|
269
|
+
end
|
270
|
+
|
271
|
+
def total
|
272
|
+
state.total
|
273
|
+
end
|
274
|
+
|
275
|
+
def h_total
|
276
|
+
humanize_quantity(state.total)
|
277
|
+
end
|
278
|
+
|
279
|
+
def done
|
280
|
+
state.done
|
281
|
+
end
|
282
|
+
|
283
|
+
def h_done
|
284
|
+
humanize_quantity(state.done)
|
285
|
+
end
|
286
|
+
|
287
|
+
def terminal_width
|
288
|
+
ANSI::Terminal.terminal_width
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
def state
|
293
|
+
@state
|
294
|
+
end
|
295
|
+
|
296
|
+
# Cap'n Hook
|
297
|
+
def exit!
|
298
|
+
return if state.closed
|
299
|
+
scope.output.call(scope.template.exit) unless scope.template.exit.nil?
|
300
|
+
end
|
301
|
+
|
302
|
+
def render_template(tplid=:main, skip=[])
|
303
|
+
tpl = scope.template[tplid]
|
304
|
+
skip.each do |s|
|
305
|
+
tpl = tpl.gsub(/\$\{([^<]*)<#{s}>([^}]*)\}/, '\1\2')
|
306
|
+
end
|
307
|
+
tpl.gsub(/\${[^}]+}/) do |var|
|
308
|
+
sub = nil
|
309
|
+
r = var.gsub(/<[^>]+>/) do |t|
|
310
|
+
t = t[1..-2]
|
311
|
+
begin
|
312
|
+
sub = self.send(('h_'+t).to_sym)
|
313
|
+
rescue NoMethodError => e
|
314
|
+
raise NameError, "Invalid token '#{t}' in template '#{tplid}'"
|
315
|
+
end
|
316
|
+
end[2..-2]
|
317
|
+
sub.nil? ? '' : r
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
HQ_UNITS = %w(b k M G T).freeze
|
322
|
+
def humanize_quantity(number, format='%n%u', base=1024)
|
323
|
+
return nil if number.nil?
|
324
|
+
return nil if number.is_a? Float and (number.nan? or number.infinite?)
|
325
|
+
return number if number.to_i < base
|
326
|
+
|
327
|
+
max_exp = HQ_UNITS.size - 1
|
328
|
+
number = Float(number)
|
329
|
+
exponent = (Math.log(number) / Math.log(base)).to_i
|
330
|
+
exponent = max_exp if exponent > max_exp
|
331
|
+
number /= base ** exponent
|
332
|
+
|
333
|
+
unit = HQ_UNITS[exponent]
|
334
|
+
return format.gsub(/%n/, number.round(1).to_s).gsub(/%u/, unit)
|
335
|
+
end
|
336
|
+
|
337
|
+
def humanize_interval(s)
|
338
|
+
return nil if s.nil? or s.infinite?
|
339
|
+
sprintf("%02d:%02d:%02d", s / 3600, s / 60 % 60, s % 60)
|
340
|
+
end
|
341
|
+
|
342
|
+
class Rate < Array
|
343
|
+
attr_reader :last_sample_at
|
344
|
+
def initialize(at, len, max_interval=10, interval_step=0.1)
|
345
|
+
super([])
|
346
|
+
@last_sample_at = at
|
347
|
+
@sample_interval = 0
|
348
|
+
@sample_interval_step = interval_step
|
349
|
+
@sample_interval_max = max_interval
|
350
|
+
@counter = 0
|
351
|
+
@len = len
|
352
|
+
end
|
353
|
+
|
354
|
+
def append(at, v)
|
355
|
+
return if @sample_interval > at - @last_sample_at
|
356
|
+
@sample_interval += @sample_interval_step if @sample_interval < @sample_interval_max
|
357
|
+
|
358
|
+
rate = (v - @counter) / (at - @last_sample_at).to_f
|
359
|
+
return if rate.nan?
|
360
|
+
|
361
|
+
@last_sample_at = at
|
362
|
+
@counter = v
|
363
|
+
|
364
|
+
self << rate
|
365
|
+
if length > @len
|
366
|
+
shift
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def sum
|
371
|
+
inject(:+).to_f
|
372
|
+
end
|
373
|
+
|
374
|
+
def avg
|
375
|
+
sum / size
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
data/powerbar.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "powerbar/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "powerbar"
|
7
|
+
s.version = Powerbar::VERSION
|
8
|
+
s.authors = ["Moe"]
|
9
|
+
s.email = ["moe@busyloop.net"]
|
10
|
+
s.homepage = "https://github.com/busyloop/powerbar"
|
11
|
+
s.summary = %q{The last progressbar-library you'll ever need}
|
12
|
+
s.description = %q{The last progressbar-library you'll ever need}
|
13
|
+
|
14
|
+
s.add_dependency "ansi", "~> 1.4.0"
|
15
|
+
s.add_dependency "hashie", "~> 1.1.0"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: powerbar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Moe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-07 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ansi
|
16
|
+
requirement: &17384200 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.4.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *17384200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hashie
|
27
|
+
requirement: &17383420 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *17383420
|
36
|
+
description: The last progressbar-library you'll ever need
|
37
|
+
email:
|
38
|
+
- moe@busyloop.net
|
39
|
+
executables:
|
40
|
+
- powerbar-demo
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- README.rdoc
|
47
|
+
- Rakefile
|
48
|
+
- ass/screenshot.png
|
49
|
+
- bin/powerbar-demo
|
50
|
+
- lib/powerbar.rb
|
51
|
+
- lib/powerbar/version.rb
|
52
|
+
- powerbar.gemspec
|
53
|
+
homepage: https://github.com/busyloop/powerbar
|
54
|
+
licenses: []
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.8.10
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: The last progressbar-library you'll ever need
|
77
|
+
test_files: []
|
78
|
+
has_rdoc:
|