is-term 0.8.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/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # is-term
2
+ Terminal utilities for Ruby scripts
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boolean
4
+
5
+ include Comparable
6
+
7
+ # +false+ ⇒ +0+
8
+ # +true+ ⇒ +1+
9
+ # @return [Integer]
10
+ def to_i
11
+ if self
12
+ 1
13
+ else
14
+ 0
15
+ end
16
+ end
17
+
18
+ # +false < true+
19
+ # @return [Integer]
20
+ def <=> other
21
+ if other.is_a?(Boolean)
22
+ self.to_i <=> other.to_i
23
+ else
24
+ nil
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ class TrueClass
31
+ include Boolean
32
+ end
33
+
34
+ class FalseClass
35
+ include Boolean
36
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'info'
4
+ require_relative 'string_helpers'
5
+ require_relative 'statustable'
6
+
7
+ module IS::Term::StatusTable::Formats
8
+
9
+ class << self
10
+
11
+ using IS::Term::StringHelpers
12
+
13
+ # @group Formatters
14
+
15
+ # @return [String]
16
+ def duration value
17
+ return '' if value.nil? || value == 0
18
+ value = value.to_i
19
+ result = ''
20
+ m, s = value.divmod 60
21
+ if m == 0
22
+ result = ("%ds" % s)
23
+ else
24
+ result = ("%02ds" % s)
25
+ h, m = m.divmod 60
26
+ if h == 0
27
+ result = ("%dm" % m) + result
28
+ else
29
+ result = ("%02dm" % m) + result
30
+ d, h = h.divmod 24
31
+ if d == 0
32
+ result = ("%dh" % h) + result
33
+ else
34
+ result = ("%dd" % d) + ("%02dh" % h) + result
35
+ end
36
+ end
37
+ end
38
+ result
39
+ end
40
+
41
+ # @return [String]
42
+ def percent_bar value, width, complete: '=', incomplete: ' ', head: '>', done: '≡'
43
+ return '' if value.nil?
44
+ if value >= 100
45
+ done * (width / done.width)
46
+ elsif value == 0
47
+ incomplete * (width / incomplete.width)
48
+ else
49
+ point = 100 / width
50
+ i = (100 - value) / (point * incomplete.width)
51
+ #h = 1
52
+ c = width - i * incomplete.width - head.width
53
+ if c < 0
54
+ i += c
55
+ c = 0
56
+ end
57
+ complete * c + head + incomplete * i
58
+ end
59
+ end
60
+
61
+ # @endgroup
62
+
63
+ end
64
+
65
+ SPECIAL_FORMATS = [ :duration ]
66
+
67
+ class << self
68
+
69
+ # @group Formatter Access
70
+
71
+ # @return [Proc]
72
+ def fmt desc
73
+ case desc
74
+ when String
75
+ lambda do |value|
76
+ return '' if value.nil?
77
+ desc % value
78
+ end
79
+ when Symbol
80
+ raise NameError, "Invalid format name: #{ desc.inspect }", caller_locations unless SPECIAL_FORMATS.include?(desc)
81
+ self.method(desc).to_proc
82
+ else
83
+ raise ArgumentError, "Invalid format: #{ desc.inspect }", caller_locations
84
+ end
85
+ end
86
+
87
+ # @return [Proc]
88
+ def bar width, complete: '=', incomplete: ' ', head: '>', done: '≡'
89
+ opts = {
90
+ complete: complete,
91
+ incomplete: incomplete,
92
+ head: head,
93
+ done: done
94
+ }
95
+ lambda { |value| self.percent_bar(value, width, **opts) }
96
+ end
97
+
98
+ # @endgroup
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,476 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'info'
4
+ require_relative 'statustable'
5
+
6
+ # Provides calculation functions for status table rows and aggregates.
7
+ #
8
+ # This module contains methods for retrieving and calculating timing metrics
9
+ # (start/finish times, elapsed time, estimated completion), progress percentages,
10
+ # and aggregate statistics (sum, average, min, max, count) for rows in a
11
+ # {IS::Term::StatusTable}.
12
+ #
13
+ # Methods support polymorphic signatures accepting either a Hash row,
14
+ # an identifier, or no argument (for table-wide aggregation).
15
+ #
16
+ # @example Calculating progress for a specific row
17
+ # percent(some_row) # => 45
18
+ # elapsed(some_row) # => 123.45
19
+ #
20
+ # @example Table-wide aggregation
21
+ # percent # => 32 (percent for entire table)
22
+ # active # => 3 (count of active rows)
23
+ # sum(:size) # => 1024 (sum of :size field across all rows)
24
+ #
25
+ module IS::Term::StatusTable::Functions
26
+
27
+ protected
28
+
29
+ # @group Internal Setup
30
+
31
+ # @api private
32
+ # The status table context for function execution.
33
+ #
34
+ # Returns the singleton instance by default, but can be overridden to use
35
+ # a different table context (e.g., for testing or custom implementations).
36
+ # The setter validates that the value responds to both +row+ and +rows+ methods
37
+ # (duck typing for table interface).
38
+ #
39
+ # @return [IS::Term::StatusTable]
40
+ # @raise [ArgumentError] if assigned value does not respond to +row+ and +rows+
41
+ # @!attribute [rw] _table
42
+
43
+ def _table
44
+ @status_table ||= IS::Term::StatusTable::instance
45
+ end
46
+
47
+ def _table= value
48
+ raise ArgumentError, "Invalid value for '_table': #{ value.inspect }", caller_locations unless value.respond_to?(:row) && value.respond_to?(:rows)
49
+ @status_table = value
50
+ end
51
+
52
+ # @endgroup
53
+
54
+ public
55
+
56
+ # @group Row or Table Functions
57
+
58
+ # Return starting time of row or whole table.
59
+ # @return [Time, nil]
60
+ #
61
+ # @overload started(row)
62
+ # When this row was started.
63
+ # @param [Hash] row the row hash containing +:_started+ key
64
+ #
65
+ # @overload started(id)
66
+ # Find row by id and return when it started. Returns +nil+ if row not found.
67
+ # @param [Object] id the row identifier
68
+ #
69
+ # @overload started
70
+ # When the whole table was started (minimum start time across all rows).
71
+ # Returns +nil+ if table is empty or no rows have started.
72
+ def started row = nil
73
+ if row.is_a?(Hash)
74
+ row[:_started]
75
+ else
76
+ tbl = _table
77
+ if row.nil?
78
+ tbl.rows.map { |r| r[:_started] }.min
79
+ else
80
+ r = tbl.row row
81
+ r.nil? ? nil : started(r)
82
+ end
83
+ end
84
+ end
85
+
86
+ # Return finishing time of row or whole table.
87
+ # @return [Time, nil]
88
+ #
89
+ # @overload finished(row)
90
+ # When this row was finished.
91
+ # @param [Hash] row the row hash containing +:_finished+ key
92
+ #
93
+ # @overload finished(id)
94
+ # Find row by id and return when it finished. Returns +nil+ if row not found or not finished
95
+ # @param [Object] id the row identifier
96
+ #
97
+ # @overload finished
98
+ # When the whole table was finished (maximum finish time across all rows).
99
+ # Returns +nil+ if any row is still active (not finished) or table is empty.
100
+ def finished row = nil
101
+ if row.is_a?(Hash)
102
+ row[:_finished]
103
+ else
104
+ tbl = _table
105
+ if row.nil?
106
+ values = tbl.rows.map { |r| r[:_finished] }
107
+ if values.any? { |v| v.nil? }
108
+ nil
109
+ else
110
+ values.max
111
+ end
112
+ else
113
+ r = tbl.row row
114
+ r.nil? ? nil : finished(r)
115
+ end
116
+ end
117
+ end
118
+
119
+ # Return a percent of execution — +(0 .. 100)+.
120
+ #
121
+ # @overload percent(row)
122
+ # Execution percent of row
123
+ # @param [Hash] row
124
+ #
125
+ # @overload percent(id)
126
+ # Execution percent of row with specified id
127
+ # @param [Object] id
128
+ #
129
+ # @overload percent
130
+ # Execution percent of whole table
131
+ #
132
+ # @return [Integer, nil]
133
+ def percent row = nil
134
+ current = self.current row
135
+ total = self.total row
136
+ if current.nil? || total.nil? || total == 0
137
+ nil
138
+ else
139
+ (current * 100) / total
140
+ end
141
+ end
142
+
143
+ # Return estimated remaining execution time is seconds.
144
+ #
145
+ # @overload estimated(row)
146
+ # Estimated remaining time of row execution
147
+ # @param [Hash] row
148
+ #
149
+ # @overload estimated(id)
150
+ # Estimated remaining time of row with specified id
151
+ # @param [Object] id
152
+ #
153
+ # @overload estimated
154
+ # Estimated remaining time of whole table
155
+ #
156
+ # @return [Float, nil] Value in seconds
157
+ def estimated row = nil
158
+ elapsed = self.elapsed row
159
+ current = self.current row
160
+ total = self.total row
161
+ if elapsed.nil? || current.nil? || total.nil? || current == 0
162
+ nil
163
+ else
164
+ (elapsed.to_f / current) * (total - current)
165
+ end
166
+ end
167
+
168
+ # Return elapsed time in seconds.
169
+ #
170
+ # @overload elapsed(row)
171
+ # Elapsed time of row execution
172
+ # @param [Hash] row
173
+ #
174
+ # @overload elapsed(id)
175
+ # Elapsed time of row with specified id
176
+ # @param [Object] id
177
+ #
178
+ # @overload elapsed
179
+ # Elapsed time of whole table
180
+ #
181
+ # @return [Float, nil] Value in seconds
182
+ def elapsed row = nil
183
+ started = self.started row
184
+ finished = self.finished row
185
+ if started.nil?
186
+ nil
187
+ elsif finished.nil?
188
+ Time::now - started
189
+ else
190
+ finished - started
191
+ end
192
+ end
193
+
194
+ # Return average execution speed (steps in second).
195
+ #
196
+ # @overload speed(row)
197
+ # Speed of row execution
198
+ # @param [Hash] row
199
+ #
200
+ # @overload speed(id)
201
+ # Speed of row with specified id
202
+ # @param [Object] id
203
+ #
204
+ # @overload speed
205
+ # Execution speed of whole table
206
+ #
207
+ # @return [Float, nil]
208
+ def speed row = nil
209
+ elapsed = self.elapsed row
210
+ current = self.current row
211
+ finished = self.finished row
212
+ total = self.total row
213
+ if elapsed.nil? || current.nil?
214
+ nil
215
+ else
216
+ if finished && total
217
+ total.to_f / elapsed
218
+ else
219
+ current.to_f / elapsed
220
+ end
221
+ end
222
+ end
223
+
224
+ # Return current steps value for row or whole table.
225
+ #
226
+ # @overload current(row)
227
+ # Current step in specified row
228
+ # @param [Hash] row
229
+ #
230
+ # @overload current(id)
231
+ # Current step in row with specified id
232
+ # @param [Object] id
233
+ #
234
+ # @overload current
235
+ # Sum of current of whole table
236
+ #
237
+ # @return [Integer, nil]
238
+ def current row = nil
239
+ if row.is_a?(Hash)
240
+ row[:current]
241
+ else
242
+ tbl = _table
243
+ if row.nil?
244
+ tbl.rows.map { |r| r[:total] ? r[:current] : nil }.compact.sum
245
+ else
246
+ r = tbl.row row
247
+ r.nil? ? nil : current(r)
248
+ end
249
+ end
250
+ end
251
+
252
+ # Return total steps value of row or table.
253
+ #
254
+ # @overload total(row)
255
+ # Total step count of row
256
+ # @param [Hash] row
257
+ #
258
+ # @overload total(id)
259
+ # Total step count of row with specified id
260
+ # @param [Object] id
261
+ #
262
+ # @overload total
263
+ # Total step count of whole table
264
+ #
265
+ # @return [Integer, nil]
266
+ def total row = nil
267
+ if row.is_a?(Hash)
268
+ row[:total]
269
+ else
270
+ tbl = _table
271
+ if row.nil?
272
+ tbl.rows.map { |r| r[:total] }.compact.sum
273
+ else
274
+ r = tbl.row row
275
+ r.nil? ? nil : total(r)
276
+ end
277
+ end
278
+ end
279
+
280
+ # Is a row or table active?
281
+ #
282
+ # @overload active?(row)
283
+ # Is this row active?
284
+ # @param [Hash] row
285
+ #
286
+ # @overload active?(id)
287
+ # Is row with specified id active?
288
+ # @param [Object] id
289
+ #
290
+ # @overload active?
291
+ # Is any row of table active?
292
+ #
293
+ # @return [Boolean, nil]
294
+ def active? row = nil
295
+ if row.is_a?(Hash)
296
+ row[:_active]
297
+ else
298
+ tbl = _table
299
+ if row.nil?
300
+ tbl.rows.any? { |r| r[:_active] }
301
+ else
302
+ r = tbl.row row
303
+ r.nil? ? nil : active?(r)
304
+ end
305
+ end
306
+ end
307
+
308
+ # Is a row or table done?
309
+ #
310
+ # @overload done?(row)
311
+ # Is this row done?
312
+ # @param [Hash] row
313
+ #
314
+ # @overload done?(id)
315
+ # Is row with specified id done?
316
+ # @param [Object] id
317
+ #
318
+ # @overload done?
319
+ # Is all rows if table done?
320
+ #
321
+ # @return [Boolean, nil]
322
+ def done? row = nil
323
+ active = self.active? row
324
+ active.nil? ? nil : !active
325
+ end
326
+
327
+ # @endgroup
328
+
329
+ # @group Table-only Functions
330
+
331
+ # Count of active rows
332
+ # @return [Integer]
333
+ def active
334
+ tbl = _table
335
+ tbl.rows.select { |r| r[:_active] }.count
336
+ end
337
+
338
+ # Count of done rows
339
+ # @return [Integer]
340
+ def done
341
+ tbl = _table
342
+ tbl.rows.select { |r| !r[:_active] }.count
343
+ end
344
+
345
+ # Is table empty?
346
+ # @return [Boolean]
347
+ def empty?
348
+ self.count == 0
349
+ end
350
+
351
+ # @endgroup
352
+
353
+ # @group Aggregate Functions
354
+
355
+ # Sum of field values
356
+ # @return [Numeric]
357
+ def sum name
358
+ tbl = _table
359
+ tbl.rows.map { |r| r[name] }.compact.sum
360
+ end
361
+
362
+ # Average value of field
363
+ # @return [Numeric, nil]
364
+ def avg name
365
+ tbl = _table
366
+ vls = tbl.rows.map { |r| r[name] }.compact
367
+ sum = vls.sum
368
+ cnt = vls.count
369
+ if cnt == 0
370
+ nil
371
+ else
372
+ sum / cnt
373
+ end
374
+ end
375
+
376
+ # Minimum of field values
377
+ # @return [Comparable, nil]
378
+ def min name
379
+ tbl = _table
380
+ tbl.rows.map { |r| r[name] }.compact.min
381
+ end
382
+
383
+ # Maximum of field values
384
+ # @return [Comparable, nil]
385
+ def max name
386
+ tbl = _table
387
+ tbl.rows.map { |r| r[name] }.compact.max
388
+ end
389
+
390
+ # @endgroup
391
+
392
+ # @group Aggregate or Table Function
393
+
394
+ # Count of rows, See Details.
395
+ #
396
+ # @overload cnt(name)
397
+ # Count of unique not null values of field
398
+ # @param [Symbol] name
399
+ #
400
+ # @overload count
401
+ # Count of rows
402
+ #
403
+ # @return [Integer]
404
+ def count name = nil
405
+ rows = _table.rows
406
+ if name.nil?
407
+ rows.count
408
+ else
409
+ rows.map { |r| r[name] }.compact.uniq.count
410
+ end
411
+ end
412
+
413
+ alias :cnt :count
414
+
415
+ # @endgroup
416
+
417
+ extend self
418
+
419
+ # @api private
420
+ ROW_METHODS = [ :started, :finished, :percent, :estimated, :elapsed, :speed, :current, :total, :active?, :done? ].freeze
421
+
422
+ # @api private
423
+ TABLE_METHODS = [ :started, :finished, :percent, :estimated, :elapsed, :speed, :current, :total, :active, :active?, :done, :done?, :count, :empty? ].freeze
424
+
425
+ # @api private
426
+ AGGREGATE_METHODS = [ :sum, :avg, :min, :max, :cnt, :count ].freeze
427
+
428
+ class << self
429
+
430
+ # @group Function Access
431
+
432
+ # @return [Proc]
433
+ def row_func method_name
434
+ raise NameError, "Invalid function name: #{ method_name.inspect }", caller_locations unless ROW_METHODS.include?(method_name)
435
+ self.method(method_name).to_proc
436
+ end
437
+
438
+ # @return [Proc]
439
+ def table_func method_name
440
+ raise NameError, "Invalid function name: #{ method_name.inspect }", caller_locations unless TABLE_METHODS.include?(method_name)
441
+ meth = self.method method_name
442
+ if ROW_METHODS.include?(method_name) || AGGREGATE_METHODS.include?(method_name)
443
+ lambda { meth.call(nil) }
444
+ else
445
+ meth.to_proc
446
+ end
447
+ end
448
+
449
+ # @return [Proc]
450
+ def aggregate_func method_name, field_name
451
+ raise NameError, "Invalid function name: #{ method_name.inspect }", caller_locations unless AGGREGATE_METHODS.include?(method_name)
452
+ meth = self.method method_name
453
+ lambda { meth.call(field_name) }
454
+ end
455
+
456
+ alias :RF :row_func
457
+ alias :TF :table_func
458
+ alias :AF :aggregate_func
459
+
460
+ # @endgroup
461
+
462
+ end
463
+
464
+ end
465
+
466
+ class IS::Term::StatusTable
467
+ include IS::Term::StatusTable::Functions
468
+
469
+ protected
470
+
471
+ # @private
472
+ def _table
473
+ self
474
+ end
475
+
476
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IS; end
4
+
5
+ module IS::Term; end
6
+
7
+ module IS::Term::Info
8
+
9
+ NAME = 'is-term'
10
+ VERSION = '0.8.0'
11
+ AUTHOR = "Ivan Shikhalev"
12
+ AUTHOR_URL = "https://github.com/shikhalev"
13
+ EMAIL = "shikhalev@gmail.com"
14
+
15
+ LICENSE = "GPL-3.0-or-later"
16
+ LICENSE_URL = "https://github.com/inat-get/is-term/blob/main/LICENSE"
17
+
18
+ SUMMARY = "Terminal utilities for Ruby scripts"
19
+ DESCRIPTION = "Terminal utilities for Ruby scripts."
20
+
21
+ HOMEPAGE = "https://github.com/inat-get/is-term"
22
+
23
+ end