progress 2.4.0 → 3.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/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']