progress 2.4.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/progress.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'singleton'
2
4
  require 'thread'
3
5
 
@@ -35,51 +37,76 @@ require 'thread'
35
37
  class Progress
36
38
  include Singleton
37
39
 
38
- attr_accessor :title, :current, :total, :note
39
- attr_reader :current_step
40
- def initialize(title, total)
41
- if title.is_a?(Numeric) && total.nil?
42
- title, total = nil, title
43
- elsif total.nil?
44
- total = 1
40
+ attr_reader :total
41
+ attr_reader :current
42
+ attr_reader :title
43
+ attr_accessor :note
44
+ def initialize(total, title)
45
+ if !total.kind_of?(Numeric) && (title.nil? || title.kind_of?(Numeric))
46
+ total, title = title, total
45
47
  end
46
- @title = title
48
+ total = total && total != 0 ? Float(total) : 1.0
49
+
50
+ @total = total
47
51
  @current = 0.0
48
- @total = total == 0.0 ? 1.0 : Float(total)
52
+ @title = title
49
53
  end
50
54
 
51
- def step_if_blank
52
- if current == 0.0 && total == 1.0
53
- self.current = 1.0
54
- end
55
+ def to_f(inner)
56
+ inner = 1.0 if inner > 1.0
57
+ inner *= @step if @step
58
+ (current + inner) / total
55
59
  end
56
60
 
57
- def to_f(inner)
58
- inner = [inner, 1.0].min
59
- if current_step
60
- inner *= current_step
61
+ def step(step, note)
62
+ if !step.kind_of?(Numeric)
63
+ step, note = nil, step
61
64
  end
62
- (current + inner) / total
65
+ step = 1 if step.nil?
66
+
67
+ @step = step
68
+ @note = note
69
+ ret = yield if block_given?
70
+ Thread.exclusive do
71
+ @current += step
72
+ end
73
+ ret
63
74
  end
64
75
 
65
- def step(steps)
66
- @current_step = steps
67
- yield
68
- ensure
69
- @current_step = nil
76
+ def set(new_current, note)
77
+ @step = new_current - @current
78
+ @note = note
79
+ ret = yield if block_given?
80
+ Thread.exclusive do
81
+ @current = new_current
82
+ end
83
+ ret
70
84
  end
71
85
 
86
+ @lock = Mutex.new
72
87
  class << self
88
+
73
89
  # start progress indication
74
- def start(title = nil, total = nil)
75
- if levels.empty?
76
- @started_at = Time.now
77
- @eta = nil
78
- @semaphore = Mutex.new
79
- start_beeper
90
+ def start(total = nil, title = nil)
91
+ lock do
92
+ if running?
93
+ unless @started_in == Thread.current
94
+ warn 'Can\'t start inner progress in different thread'
95
+ if block_given?
96
+ return yield
97
+ else
98
+ return
99
+ end
100
+ end
101
+ else
102
+ @started_in = Thread.current
103
+ @eta = Eta.new
104
+ start_beeper
105
+ end
106
+ @levels ||= []
107
+ @levels.push new(total, title)
80
108
  end
81
- levels << new(title, total)
82
- print_message true
109
+ print_message :force => true
83
110
  if block_given?
84
111
  begin
85
112
  yield
@@ -89,26 +116,22 @@ class Progress
89
116
  end
90
117
  end
91
118
 
92
- # step current progress by `num / den`
93
- def step(num = 1, den = 1, &block)
94
- if levels.last
95
- set(levels.last.current + Float(num) / den, &block)
119
+ # step current progress
120
+ def step(step = nil, note = nil, &block)
121
+ if running?
122
+ ret = @levels.last.step(step, note, &block)
123
+ print_message
124
+ ret
96
125
  elsif block
97
126
  block.call
98
127
  end
99
128
  end
100
129
 
101
- # set current progress to `value`
102
- def set(value, &block)
103
- if levels.last
104
- ret = if block
105
- levels.last.step(value - levels.last.current, &block)
106
- end
107
- if levels.last
108
- levels.last.current = Float(value)
109
- end
130
+ # set value of current progress
131
+ def set(new_current, note = nil, &block)
132
+ if running?
133
+ ret = @levels.last.set(new_current, note, &block)
110
134
  print_message
111
- self.note = nil
112
135
  ret
113
136
  elsif block
114
137
  block.call
@@ -117,176 +140,165 @@ class Progress
117
140
 
118
141
  # stop progress
119
142
  def stop
120
- if levels.last
121
- if levels.last.step_if_blank || levels.length == 1
122
- print_message true
123
- set_title nil
124
- end
125
- levels.pop
126
- if levels.empty?
143
+ if running?
144
+ if @levels.length == 1
145
+ print_message :force => true, :finish => true
127
146
  stop_beeper
128
- io.puts
129
147
  end
148
+ @levels.pop
130
149
  end
131
150
  end
132
151
 
133
- # check in block of showing progress
152
+ # check if progress was started
134
153
  def running?
135
- !levels.empty?
154
+ @levels && !@levels.empty?
136
155
  end
137
156
 
138
157
  # set note
139
- def note=(s)
140
- if levels.last
141
- levels.last.note = s
158
+ def note=(note)
159
+ if running?
160
+ @levels.last.note = note
142
161
  end
143
162
  end
144
163
 
145
- # output progress as lines (not trying to stay on line)
146
- # Progress.lines = true
147
- attr_writer :lines
148
-
149
- # force highlight
150
- # Progress.highlight = true
151
- attr_writer :highlight
152
-
153
- private
154
-
155
- def levels
156
- @levels ||= []
157
- end
158
-
159
- def io
160
- @io || $stderr
161
- end
162
-
163
- def io_tty?
164
- io.tty? || ENV['PROGRESS_TTY']
164
+ # stay on one line
165
+ def stay_on_line?
166
+ @stay_on_line.nil? ? io_tty? : @stay_on_line
165
167
  end
166
168
 
167
- def lines?
168
- @lines.nil? ? !io_tty? : @lines
169
+ # explicitly set staying on one line [true/false/nil]
170
+ def stay_on_line=(value)
171
+ @stay_on_line = true && value
169
172
  end
170
173
 
174
+ # highlight output using control characters
171
175
  def highlight?
172
176
  @highlight.nil? ? io_tty? : @highlight
173
177
  end
174
178
 
175
- def time_to_print?
176
- if !@previous || @previous < Time.now - 0.3
177
- @previous = Time.now
178
- true
179
- end
179
+ # explicitly set highlighting [true/false/nil]
180
+ def highlight=(value)
181
+ @highlight = true && value
180
182
  end
181
183
 
182
- def eta(completed)
183
- now = Time.now
184
- if now > @started_at && completed > 0
185
- current_eta = @started_at + (now - @started_at) / completed
186
- @eta = @eta ? @eta + (current_eta - @eta) * (1 + completed) * 0.5 : current_eta
187
- seconds = @eta - now
188
- if seconds > 0
189
- left = case seconds
190
- when 0...60
191
- '%.0fs' % seconds
192
- when 60...3600
193
- '%.1fm' % (seconds / 60)
194
- when 3600...86400
195
- '%.1fh' % (seconds / 3600)
196
- else
197
- '%.1fd' % (seconds / 86400)
198
- end
199
- eta_string = " (ETA: #{left})"
200
- end
201
- end
184
+ # show progerss in terminal title
185
+ def set_terminal_title?
186
+ @set_terminal_title.nil? ? io_tty? : @set_terminal_title
202
187
  end
203
188
 
204
- def set_title(title)
205
- if io_tty?
206
- io.print "\e]0;#{title}\a"
207
- end
189
+ # explicitly set showing progress in terminal title [true/false/nil]
190
+ def set_terminal_title=(value)
191
+ @set_terminal_title = true && value
208
192
  end
209
193
 
210
- def lock(force)
211
- if force ? @semaphore.lock : @semaphore.try_lock
194
+ private
195
+
196
+ def lock(force = true)
197
+ if force ? @lock.lock : @lock.try_lock
212
198
  begin
213
199
  yield
214
200
  ensure
215
- @semaphore.unlock
201
+ @lock.unlock
216
202
  end
217
203
  end
218
204
  end
219
205
 
206
+ def io
207
+ @io || $stderr
208
+ end
209
+
210
+ def io_tty?
211
+ io.tty? || ENV['PROGRESS_TTY']
212
+ end
213
+
220
214
  def start_beeper
221
- @beeper = Thread.new do
222
- loop do
223
- sleep 10
224
- print_message unless Thread.current[:skip]
225
- end
215
+ @beeper = Beeper.new(10) do
216
+ print_message
226
217
  end
227
218
  end
228
219
 
229
220
  def stop_beeper
230
- @beeper.kill
231
- @beeper = nil
221
+ @beeper.stop if @beeper
232
222
  end
233
223
 
234
224
  def restart_beeper
235
- if @beeper
236
- @beeper[:skip] = true
237
- @beeper.run
238
- @beeper[:skip] = false
239
- end
225
+ @beeper.restart if @beeper
226
+ end
227
+
228
+ def time_to_print?
229
+ !@next_time_to_print || @next_time_to_print <= Time.now
230
+ end
231
+
232
+ def eta(current)
233
+ @eta.left(current)
240
234
  end
241
235
 
242
- def print_message(force = false)
236
+ def elapsed
237
+ @eta.elapsed
238
+ end
239
+
240
+ def print_message(options = {})
241
+ force = options[:force]
243
242
  lock force do
244
- restart_beeper
245
243
  if force || time_to_print?
246
- inner = 0
247
- parts, parts_cl = [], []
248
- levels.reverse.each do |level|
249
- inner = current = level.to_f(inner)
250
- value = current.zero? ? '......' : "#{'%5.1f' % (current * 100.0)}%"
251
-
252
- title = level.title ? "#{level.title}: " : nil
253
- if !highlight? || value == '100.0%'
254
- parts << "#{title}#{value}"
244
+ @next_time_to_print = Time.now + 0.3
245
+ restart_beeper
246
+
247
+ current = 0
248
+ parts = []
249
+ title_parts = []
250
+ @levels.reverse.each do |level|
251
+ current = level.to_f(current)
252
+
253
+ percent = current == 0 ? '......' : "#{'%5.1f' % (current * 100.0)}%"
254
+ title = level.title && "#{level.title}: "
255
+ if !highlight? || percent == '100.0%'
256
+ parts << "#{title}#{percent}"
255
257
  else
256
- parts << "#{title}\e[1m#{value}\e[0m"
258
+ parts << "#{title}\e[1m#{percent}\e[0m"
257
259
  end
258
- parts_cl << "#{title}#{value}"
260
+ title_parts << "#{title}#{percent}"
261
+ end
262
+
263
+ timing = if options[:finish]
264
+ " (elapsed: #{elapsed})"
265
+ elsif eta_ = eta(current)
266
+ " (ETA: #{eta_})"
259
267
  end
260
268
 
261
- eta_string = eta(inner)
262
- message = "#{parts.reverse * ' > '}#{eta_string}"
263
- message_cl = "#{parts_cl.reverse * ' > '}#{eta_string}"
269
+ message = "#{parts.reverse * ' > '}#{timing}"
270
+ text_message = "#{title_parts.reverse * ' > '}#{timing}"
264
271
 
265
- if note = levels.last && levels.last.note
272
+ if note = running? && @levels.last.note
266
273
  message << " - #{note}"
267
- message_cl << " - #{note}"
274
+ text_message << " - #{note}"
268
275
  end
269
276
 
270
- if lines?
271
- io.puts message
272
- else
273
- io << message << "\e[K\r"
274
- end
277
+ message = "\r#{message}\e[K" if stay_on_line?
278
+ message << "\n" if !stay_on_line? || options[:finish]
279
+ io << message
275
280
 
276
- set_title message_cl
281
+ if set_terminal_title?
282
+ title = options[:finish] ? nil : text_message.to_s.gsub("\a", '␇')
283
+ io << "\e]0;#{title}\a"
284
+ end
277
285
  end
278
286
  end
279
287
  end
288
+
280
289
  end
281
290
  end
282
291
 
292
+ require 'progress/beeper'
293
+ require 'progress/eta'
294
+
283
295
  require 'progress/enumerable'
284
296
  require 'progress/integer'
285
- require 'progress/active_record'
297
+ require 'progress/active_record' if defined?(ActiveRecord::Base)
286
298
 
287
299
  module Kernel
288
- def Progress(title = nil, total = nil, &block)
289
- Progress.start(title, total, &block)
300
+ def Progress(*args, &block)
301
+ Progress.start(*args, &block)
290
302
  end
291
303
  private :Progress
292
304
  end
data/progress.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'progress'
5
- s.version = '2.4.0'
5
+ s.version = '3.0.0'
6
6
  s.summary = %q{Show progress of long running tasks}
7
7
  s.homepage = "http://github.com/toy/#{s.name}"
8
8
  s.authors = ['Ivan Kuchin']