dry-cli-ui 0.3.1 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -2
- data/README.md +595 -35
- data/examples/.envrc +1 -0
- data/examples/.gitignore +1 -0
- data/examples/Gemfile +1 -1
- data/examples/Gemfile.lock +5 -5
- data/examples/README.md +24 -10
- data/examples/bin/mycli +270 -94
- data/lib/dry/cli/ui/configuration.rb +160 -0
- data/lib/dry/cli/ui/console.rb +83 -8
- data/lib/dry/cli/ui/status_bar.rb +314 -0
- data/lib/dry/cli/ui/terminal.rb +38 -2
- data/lib/dry/cli/ui/theme.rb +21 -6
- data/lib/dry/cli/ui/version.rb +1 -1
- data/lib/dry/cli/ui/widgets/multi.rb +300 -0
- data/lib/dry/cli/ui/widgets/multi_progress.rb +123 -0
- data/lib/dry/cli/ui/widgets/multi_spinner.rb +69 -0
- data/lib/dry/cli/ui/widgets/outcome.rb +1 -1
- data/lib/dry/cli/ui/widgets/pool.rb +82 -0
- data/lib/dry/cli/ui/widgets/progress.rb +71 -12
- data/lib/dry/cli/ui/widgets/spinner.rb +11 -3
- data/lib/dry/cli/ui/widgets/tasks.rb +17 -61
- data/lib/dry/cli/ui/widgets.rb +4 -0
- data/lib/dry/cli/ui.rb +32 -0
- data/sig/dry/cli/ui.rbs +26 -1
- metadata +9 -2
- data/SPECIFICATION.md +0 -351
|
@@ -16,15 +16,21 @@ module Dry
|
|
|
16
16
|
class Handle
|
|
17
17
|
# @param total [Integer]
|
|
18
18
|
# @param bar [TTY::ProgressBar, nil]
|
|
19
|
-
|
|
19
|
+
# @param color [Symbol, nil] see {Progress.color}
|
|
20
|
+
def initialize(total, bar, color: nil)
|
|
20
21
|
@total = total
|
|
21
22
|
@bar = bar
|
|
23
|
+
@color = color
|
|
22
24
|
@current = 0
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
# @return [Integer] the number of units the operation has
|
|
26
28
|
attr_reader :total
|
|
27
29
|
|
|
30
|
+
# @return [Symbol, nil] the Pastel style of the bar's finished part;
|
|
31
|
+
# nil for {Configuration#bar_color}
|
|
32
|
+
attr_reader :color
|
|
33
|
+
|
|
28
34
|
# @return [Integer] the number of units completed so far
|
|
29
35
|
attr_reader :current
|
|
30
36
|
|
|
@@ -46,32 +52,80 @@ module Dry
|
|
|
46
52
|
attr_writer :current
|
|
47
53
|
end
|
|
48
54
|
|
|
49
|
-
# Columns kept for the
|
|
50
|
-
CHROME =
|
|
55
|
+
# Columns kept for the brackets, percentage, count and ETA around the bar.
|
|
56
|
+
CHROME = 36
|
|
51
57
|
|
|
52
58
|
# Narrowest bar drawn.
|
|
53
59
|
MIN_BAR = 10
|
|
54
60
|
|
|
61
|
+
# Checks a bar's own colour.
|
|
62
|
+
#
|
|
63
|
+
# @param value [Symbol, nil] any Pastel style, or nil for {Configuration#bar_color}
|
|
64
|
+
# @return [Symbol, nil] the value
|
|
65
|
+
# @raise [ArgumentError] for anything else
|
|
66
|
+
def self.color(value)
|
|
67
|
+
return value if value.nil? || Configuration::STYLES.include?(value)
|
|
68
|
+
|
|
69
|
+
raise ArgumentError, "color must be a Pastel style or nil, got #{value.inspect}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A bar between brackets, painted as the configuration says: the
|
|
73
|
+
# finished part in {Configuration#bar_color}, or in the bar's own
|
|
74
|
+
# colour when it has one, and all of it on {Configuration#bar_background}.
|
|
75
|
+
#
|
|
76
|
+
# @param pastel [Pastel::Delegator] a no-op when colour is off
|
|
77
|
+
# @param config [Configuration]
|
|
78
|
+
# @param ratio [Float] how much is finished, from 0 to 1
|
|
79
|
+
# @param columns [Integer] the bar's width inside the brackets
|
|
80
|
+
# @param color [Symbol, nil] the finished part's style; nil for {Configuration#bar_color}
|
|
81
|
+
# @return [String]
|
|
82
|
+
def self.bar(pastel, config, ratio, columns, color: nil)
|
|
83
|
+
filled = (ratio * columns).floor
|
|
84
|
+
"[#{complete(pastel, config, color) * filled}#{incomplete(pastel, config) * (columns - filled)}]"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @param pastel [Pastel::Delegator]
|
|
88
|
+
# @param config [Configuration]
|
|
89
|
+
# @param color [Symbol, nil] the style; nil for {Configuration#bar_color}
|
|
90
|
+
# @return [String] one finished cell, painted
|
|
91
|
+
def self.complete(pastel, config, color = nil)
|
|
92
|
+
pastel.decorate(config.bar_complete, *[color || config.bar_color, config.bar_background].compact)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param pastel [Pastel::Delegator]
|
|
96
|
+
# @param config [Configuration]
|
|
97
|
+
# @return [String] one unfinished cell, painted
|
|
98
|
+
def self.incomplete(pastel, config)
|
|
99
|
+
pastel.decorate(config.bar_incomplete, *[config.bar_background].compact)
|
|
100
|
+
end
|
|
101
|
+
|
|
55
102
|
# @param terminal [Terminal]
|
|
56
103
|
# @param clock [#call] returns monotonic seconds
|
|
57
|
-
|
|
104
|
+
# @param config [Configuration] where the bar's characters come from
|
|
105
|
+
def initialize(terminal, clock:, config: UI.config)
|
|
58
106
|
@terminal = terminal
|
|
59
107
|
@clock = clock
|
|
108
|
+
@config = config
|
|
60
109
|
end
|
|
61
110
|
|
|
62
111
|
# Runs the block with a progress bar.
|
|
63
112
|
#
|
|
64
113
|
# @param label [String]
|
|
65
114
|
# @param total [Integer] the number of units of work
|
|
115
|
+
# @param color [Symbol, nil] the finished part's Pastel style; nil for
|
|
116
|
+
# {Configuration#bar_color}
|
|
66
117
|
# @yieldparam progress [Handle]
|
|
67
118
|
# @return [Object] whatever the block returns
|
|
68
|
-
# @raise [ArgumentError] when total is not a non-negative Integer
|
|
69
|
-
|
|
119
|
+
# @raise [ArgumentError] when total is not a non-negative Integer, or
|
|
120
|
+
# color is not a Pastel style
|
|
121
|
+
def run(label, total:, color: nil)
|
|
70
122
|
raise ArgumentError, "total must be a non-negative Integer, got #{total.inspect}" unless total.is_a?(Integer) && total >= 0
|
|
71
123
|
|
|
124
|
+
Progress.color(color)
|
|
72
125
|
started = clock.call
|
|
73
|
-
bar = start(label, total)
|
|
74
|
-
handle = Handle.new(total, bar)
|
|
126
|
+
bar = start(label, total, color)
|
|
127
|
+
handle = Handle.new(total, bar, color: color)
|
|
128
|
+
terminal.started(handle, label, progress: handle)
|
|
75
129
|
ok = false
|
|
76
130
|
result = yield handle
|
|
77
131
|
ok = true
|
|
@@ -79,6 +133,7 @@ module Dry
|
|
|
79
133
|
ensure
|
|
80
134
|
if handle
|
|
81
135
|
bar&.stop
|
|
136
|
+
terminal.finished(handle, ok)
|
|
82
137
|
summary = "#{label} #{handle.current}/#{total}"
|
|
83
138
|
terminal.puts(Outcome.line(terminal, ok ? :done : :failed, summary, clock.call - started))
|
|
84
139
|
end
|
|
@@ -92,22 +147,26 @@ module Dry
|
|
|
92
147
|
# @return [#call]
|
|
93
148
|
attr_reader :clock
|
|
94
149
|
|
|
150
|
+
# @return [Configuration]
|
|
151
|
+
attr_reader :config
|
|
152
|
+
|
|
95
153
|
# @param label [String]
|
|
96
154
|
# @param total [Integer]
|
|
155
|
+
# @param color [Symbol, nil]
|
|
97
156
|
# @return [TTY::ProgressBar, nil]
|
|
98
|
-
def start(label, total)
|
|
157
|
+
def start(label, total, color)
|
|
99
158
|
unless terminal.animated? && total.positive?
|
|
100
159
|
terminal.puts("#{label}...")
|
|
101
160
|
return
|
|
102
161
|
end
|
|
103
162
|
|
|
104
163
|
TTY::ProgressBar.new(
|
|
105
|
-
"#{label} :bar :percent :current/:total ETA :eta",
|
|
164
|
+
"#{label} [:bar] :percent :current/:total ETA :eta",
|
|
106
165
|
total: total,
|
|
107
166
|
width: [terminal.width - label.length - CHROME, MIN_BAR].max,
|
|
108
167
|
output: terminal.io,
|
|
109
|
-
complete:
|
|
110
|
-
incomplete:
|
|
168
|
+
complete: Progress.complete(terminal.pastel, config, color),
|
|
169
|
+
incomplete: Progress.incomplete(terminal.pastel, config),
|
|
111
170
|
clear: true,
|
|
112
171
|
hide_cursor: true
|
|
113
172
|
)
|
|
@@ -18,9 +18,11 @@ module Dry
|
|
|
18
18
|
class Spinner
|
|
19
19
|
# @param terminal [Terminal]
|
|
20
20
|
# @param clock [#call] returns monotonic seconds
|
|
21
|
-
|
|
21
|
+
# @param config [Configuration] where the frames come from
|
|
22
|
+
def initialize(terminal, clock:, config: UI.config)
|
|
22
23
|
@terminal = terminal
|
|
23
24
|
@clock = clock
|
|
25
|
+
@config = config
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
# Runs the block under a spinner.
|
|
@@ -33,6 +35,7 @@ module Dry
|
|
|
33
35
|
spinner = nil
|
|
34
36
|
line = Line.new { |text| spinner&.update(detail: text.empty? ? "" : " #{text}") }
|
|
35
37
|
started = clock.call
|
|
38
|
+
terminal.started(line, label)
|
|
36
39
|
spinner = start(label)
|
|
37
40
|
ok = false
|
|
38
41
|
result = Line.call(job, line)
|
|
@@ -40,6 +43,7 @@ module Dry
|
|
|
40
43
|
result
|
|
41
44
|
ensure
|
|
42
45
|
spinner&.stop
|
|
46
|
+
terminal.finished(line, ok)
|
|
43
47
|
terminal.puts(Outcome.line(terminal, ok ? :done : :failed, line.summary(label), clock.call - started))
|
|
44
48
|
end
|
|
45
49
|
|
|
@@ -51,6 +55,9 @@ module Dry
|
|
|
51
55
|
# @return [#call]
|
|
52
56
|
attr_reader :clock
|
|
53
57
|
|
|
58
|
+
# @return [Configuration]
|
|
59
|
+
attr_reader :config
|
|
60
|
+
|
|
54
61
|
# @param label [String]
|
|
55
62
|
# @return [TTY::Spinner, nil] the running spinner, or nil when not animating
|
|
56
63
|
def start(label)
|
|
@@ -59,8 +66,9 @@ module Dry
|
|
|
59
66
|
return
|
|
60
67
|
end
|
|
61
68
|
|
|
62
|
-
TTY::Spinner.new(":spinner #{label}:detail", output: terminal.io,
|
|
63
|
-
|
|
69
|
+
TTY::Spinner.new(":spinner #{label}:detail", output: terminal.io, frames: config.spinner_frames,
|
|
70
|
+
interval: 1.0 / config.spinner_frame_seconds,
|
|
71
|
+
hide_cursor: true, clear: true)
|
|
64
72
|
.tap { |spinner| spinner.update(detail: "") }
|
|
65
73
|
.tap(&:auto_spin)
|
|
66
74
|
end
|
|
@@ -37,23 +37,12 @@ module Dry
|
|
|
37
37
|
# end
|
|
38
38
|
# end
|
|
39
39
|
class Tasks
|
|
40
|
-
#
|
|
41
|
-
FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
42
|
-
|
|
43
|
-
# Seconds between spinner frames.
|
|
44
|
-
INTERVAL = 0.1
|
|
45
|
-
|
|
46
|
-
# Checks a `concurrent:` setting.
|
|
40
|
+
# Checks a `concurrent:` setting. See {Pool.concurrency}.
|
|
47
41
|
#
|
|
48
|
-
# @param value [Boolean, Integer]
|
|
49
|
-
# at a time, or the most that may run at once
|
|
42
|
+
# @param value [Boolean, Integer]
|
|
50
43
|
# @return [Boolean, Integer] the value
|
|
51
44
|
# @raise [ArgumentError] for anything else
|
|
52
|
-
def self.concurrency(value)
|
|
53
|
-
return value if [true, false].include?(value) || (value.is_a?(Integer) && value.positive?)
|
|
54
|
-
|
|
55
|
-
raise ArgumentError, "concurrent must be true, false or a positive Integer, got #{value.inspect}"
|
|
56
|
-
end
|
|
45
|
+
def self.concurrency(value) = Pool.concurrency(value)
|
|
57
46
|
|
|
58
47
|
# One task, or a group of them.
|
|
59
48
|
class Node
|
|
@@ -152,9 +141,11 @@ module Dry
|
|
|
152
141
|
|
|
153
142
|
# @param terminal [Terminal]
|
|
154
143
|
# @param clock [#call] returns monotonic seconds
|
|
155
|
-
|
|
144
|
+
# @param config [Configuration] where the spinner frames come from
|
|
145
|
+
def initialize(terminal, clock:, config: UI.config)
|
|
156
146
|
@terminal = terminal
|
|
157
147
|
@clock = clock
|
|
148
|
+
@config = config
|
|
158
149
|
@roots = []
|
|
159
150
|
@live = nil
|
|
160
151
|
@lock = Mutex.new
|
|
@@ -192,6 +183,9 @@ module Dry
|
|
|
192
183
|
# @return [#call]
|
|
193
184
|
attr_reader :clock
|
|
194
185
|
|
|
186
|
+
# @return [Configuration]
|
|
187
|
+
attr_reader :config
|
|
188
|
+
|
|
195
189
|
# @return [Array<Node>]
|
|
196
190
|
attr_reader :roots
|
|
197
191
|
|
|
@@ -205,57 +199,20 @@ module Dry
|
|
|
205
199
|
# @param concurrent [Boolean, Integer]
|
|
206
200
|
# @return [void]
|
|
207
201
|
def run_all(nodes, concurrent)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
futures = concurrent == true ? all_at_once(nodes) : at_most(concurrent, nodes)
|
|
211
|
-
futures.each(&:wait)
|
|
212
|
-
failed = futures.find(&:rejected?)
|
|
213
|
-
raise failed.reason if failed
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# @param nodes [Array<Node>]
|
|
217
|
-
# @return [Array<Concurrent::Promises::Future>] one per node
|
|
218
|
-
def all_at_once(nodes)
|
|
219
|
-
nodes.map { |node| Concurrent::Promises.future(node) { |each| execute(each) } }
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Workers that take nodes off a queue until it is empty, or until
|
|
223
|
-
# one of them raises. A node no worker took stays pending, and is
|
|
224
|
-
# marked skipped once the tree ends.
|
|
225
|
-
#
|
|
226
|
-
# @param limit [Integer]
|
|
227
|
-
# @param nodes [Array<Node>]
|
|
228
|
-
# @return [Array<Concurrent::Promises::Future>] one per worker
|
|
229
|
-
def at_most(limit, nodes)
|
|
230
|
-
queue = Queue.new
|
|
231
|
-
nodes.each { |node| queue << node }
|
|
232
|
-
queue.close
|
|
233
|
-
stop = Concurrent::AtomicBoolean.new
|
|
234
|
-
Array.new([limit, nodes.size].min) { Concurrent::Promises.future { work(queue, stop) } }
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# @param queue [Queue] closed, so `pop` returns nil once it is empty
|
|
238
|
-
# @param stop [Concurrent::AtomicBoolean] set once any worker raises
|
|
239
|
-
# @return [void]
|
|
240
|
-
def work(queue, stop)
|
|
241
|
-
ok = false
|
|
242
|
-
while (node = queue.pop) && stop.false?
|
|
243
|
-
execute(node)
|
|
244
|
-
end
|
|
245
|
-
ok = true
|
|
246
|
-
ensure
|
|
247
|
-
stop.make_true unless ok
|
|
202
|
+
Pool.run(nodes, concurrent) { |node| execute(node) }
|
|
248
203
|
end
|
|
249
204
|
|
|
250
205
|
# @param node [Node]
|
|
251
206
|
# @return [void]
|
|
252
207
|
def execute(node)
|
|
253
208
|
started = clock.call
|
|
209
|
+
terminal.started(node, node.name) unless node.group?
|
|
254
210
|
change(node, :running)
|
|
255
211
|
ok = false
|
|
256
212
|
node.group? ? run_all(node.children, node.concurrent) : Line.call(node.job, node.line)
|
|
257
213
|
ok = !node.failed?
|
|
258
214
|
ensure
|
|
215
|
+
terminal.finished(node, ok) unless node.group?
|
|
259
216
|
change(node, ok ? :done : :failed, seconds: clock.call - started)
|
|
260
217
|
end
|
|
261
218
|
|
|
@@ -308,7 +265,7 @@ module Dry
|
|
|
308
265
|
return unless live?
|
|
309
266
|
|
|
310
267
|
rows.each_key { |node| terminal.puts(line(node)) }
|
|
311
|
-
Concurrent::TimerTask.new(execution_interval:
|
|
268
|
+
Concurrent::TimerTask.new(execution_interval: config.spinner_frame_seconds) { tick }.tap(&:execute)
|
|
312
269
|
end
|
|
313
270
|
|
|
314
271
|
# @return [void]
|
|
@@ -321,18 +278,17 @@ module Dry
|
|
|
321
278
|
|
|
322
279
|
# @return [void]
|
|
323
280
|
def redraw
|
|
324
|
-
terminal.print(terminal.cursor.up(rows.size))
|
|
325
|
-
rows.each_key { |node| terminal.print("#{terminal.cursor.clear_line}#{line(node)}\n") }
|
|
281
|
+
terminal.print(terminal.cursor.up(rows.size) + rows.each_key.map { |node| "#{terminal.cursor.clear_line}#{line(node)}\n" }.join)
|
|
326
282
|
end
|
|
327
283
|
|
|
328
284
|
# @param node [Node]
|
|
329
285
|
# @return [String]
|
|
330
286
|
def line(node)
|
|
331
287
|
pastel = terminal.pastel
|
|
332
|
-
|
|
333
|
-
glyph =
|
|
288
|
+
frames = config.spinner_frames
|
|
289
|
+
glyph = frames[frame % frames.size] if node.state == :running && live?
|
|
334
290
|
elapsed = " #{pastel.bright_black("(#{Duration.format(node.seconds)})")}" if node.seconds
|
|
335
|
-
"#{pastel.bright_black(rows.fetch(node))}#{
|
|
291
|
+
"#{pastel.bright_black(rows.fetch(node))}#{Theme.marker(pastel, node.state, glyph)} #{text(node)}#{elapsed}"
|
|
336
292
|
end
|
|
337
293
|
|
|
338
294
|
# What follows the glyph: the name, then the detail while the task
|
data/lib/dry/cli/ui/widgets.rb
CHANGED
|
@@ -8,6 +8,10 @@ module Dry
|
|
|
8
8
|
# namespace touches a TTY toolkit class.
|
|
9
9
|
module Widgets
|
|
10
10
|
autoload :Box, File.expand_path("widgets/box", __dir__)
|
|
11
|
+
autoload :Multi, File.expand_path("widgets/multi", __dir__)
|
|
12
|
+
autoload :MultiProgress, File.expand_path("widgets/multi_progress", __dir__)
|
|
13
|
+
autoload :MultiSpinner, File.expand_path("widgets/multi_spinner", __dir__)
|
|
14
|
+
autoload :Pool, File.expand_path("widgets/pool", __dir__)
|
|
11
15
|
autoload :Outcome, File.expand_path("widgets/outcome", __dir__)
|
|
12
16
|
autoload :Progress, File.expand_path("widgets/progress", __dir__)
|
|
13
17
|
autoload :Prompt, File.expand_path("widgets/prompt", __dir__)
|
data/lib/dry/cli/ui.rb
CHANGED
|
@@ -31,13 +31,45 @@ module Dry
|
|
|
31
31
|
# input stream is exhausted.
|
|
32
32
|
class NonInteractiveError < Error; end
|
|
33
33
|
|
|
34
|
+
autoload :Configuration, File.expand_path("ui/configuration", __dir__)
|
|
34
35
|
autoload :Console, File.expand_path("ui/console", __dir__)
|
|
35
36
|
autoload :Duration, File.expand_path("ui/duration", __dir__)
|
|
36
37
|
autoload :Line, File.expand_path("ui/line", __dir__)
|
|
38
|
+
autoload :StatusBar, File.expand_path("ui/status_bar", __dir__)
|
|
37
39
|
autoload :Terminal, File.expand_path("ui/terminal", __dir__)
|
|
38
40
|
autoload :Theme, File.expand_path("ui/theme", __dir__)
|
|
39
41
|
autoload :Widgets, File.expand_path("ui/widgets", __dir__)
|
|
40
42
|
|
|
43
|
+
class << self
|
|
44
|
+
# Make process-wide settings. A block taking an argument receives the
|
|
45
|
+
# configuration; any other block runs against it.
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# Dry::CLI::UI.configure do
|
|
49
|
+
# spinner_format :dots
|
|
50
|
+
# bar_format :box
|
|
51
|
+
# bar_color :cyan
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# @return [Configuration]
|
|
55
|
+
def configure(&block)
|
|
56
|
+
block.arity == 1 ? yield(config) : config.instance_eval(&block)
|
|
57
|
+
config
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Configuration] the process-wide settings
|
|
61
|
+
def config
|
|
62
|
+
@config ||= Configuration.new
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Forget every process-wide setting.
|
|
66
|
+
#
|
|
67
|
+
# @return [void]
|
|
68
|
+
def reset!
|
|
69
|
+
@config = nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
41
73
|
# The console this command presents through. Writes to the command's own
|
|
42
74
|
# `out` and `err` when dry-cli has set them, and to `$stdout` and
|
|
43
75
|
# `$stderr` otherwise.
|
data/sig/dry/cli/ui.rbs
CHANGED
|
@@ -11,6 +11,28 @@ module Dry
|
|
|
11
11
|
|
|
12
12
|
def ui: () -> Console
|
|
13
13
|
|
|
14
|
+
def self.configure: () { (Configuration config) -> void } -> Configuration
|
|
15
|
+
def self.config: () -> Configuration
|
|
16
|
+
def self.reset!: () -> void
|
|
17
|
+
|
|
18
|
+
class Configuration
|
|
19
|
+
type spinner_definition = Symbol | { interval: Numeric, frames: Array[String] | String }
|
|
20
|
+
type bar_definition = Symbol | { complete: String, incomplete: String }
|
|
21
|
+
|
|
22
|
+
def spinner_format: (?spinner_definition value) -> spinner_definition
|
|
23
|
+
def spinner_format=: (spinner_definition value) -> spinner_definition
|
|
24
|
+
def bar_format: (?bar_definition value) -> bar_definition
|
|
25
|
+
def bar_format=: (bar_definition value) -> bar_definition
|
|
26
|
+
def bar_color: (?Symbol? value) -> Symbol?
|
|
27
|
+
def bar_color=: (Symbol? value) -> Symbol?
|
|
28
|
+
def bar_background: (?Symbol? value) -> Symbol?
|
|
29
|
+
def bar_background=: (Symbol? value) -> Symbol?
|
|
30
|
+
def spinner_frames: () -> Array[String]
|
|
31
|
+
def spinner_frame_seconds: () -> Float
|
|
32
|
+
def bar_complete: () -> String
|
|
33
|
+
def bar_incomplete: () -> String
|
|
34
|
+
end
|
|
35
|
+
|
|
14
36
|
class Line
|
|
15
37
|
def self.call: [T] (^(Line) -> T job, Line line) -> T
|
|
16
38
|
def initialize: () ?{ (String detail) -> void } -> void
|
|
@@ -33,7 +55,10 @@ module Dry
|
|
|
33
55
|
def status: (*_ToS words, ?level: Symbol) -> nil
|
|
34
56
|
def popup: (*_ToS paragraphs, ?title: String?, ?width: Integer?) -> nil
|
|
35
57
|
def spinner: [T] (String label) { (Line line) -> T } -> T
|
|
36
|
-
def
|
|
58
|
+
def multi_spinner: (String title, ?concurrent: (bool | Integer)) { (untyped spinners) -> void } -> Array[untyped]
|
|
59
|
+
def progress: [T] (String label, total: Integer, ?color: Symbol?) { (untyped progress) -> T } -> T
|
|
60
|
+
def multi_progress: (String title, ?concurrent: (bool | Integer)) { (untyped bars) -> void } -> Array[untyped]
|
|
61
|
+
def status_bar: [T] (?String? title, ?hints: Array[String] | String) { () -> T } -> T
|
|
37
62
|
def tasks: (?String? title, ?concurrent: (bool | Integer)) { (untyped tasks) -> void } -> nil
|
|
38
63
|
def table: (Array[Array[_ToS]] rows, ?header: Array[_ToS]?) -> nil
|
|
39
64
|
def prompt: (String question, ?default: untyped, ?choices: (Array[String] | Hash[String, untyped])?) -> untyped
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dry-cli-ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Konstantin Gredeskoul
|
|
@@ -176,22 +176,29 @@ files:
|
|
|
176
176
|
- CHANGELOG.md
|
|
177
177
|
- LICENSE.txt
|
|
178
178
|
- README.md
|
|
179
|
-
-
|
|
179
|
+
- examples/.envrc
|
|
180
|
+
- examples/.gitignore
|
|
180
181
|
- examples/Gemfile
|
|
181
182
|
- examples/Gemfile.lock
|
|
182
183
|
- examples/README.md
|
|
183
184
|
- examples/bin/mycli
|
|
184
185
|
- lib/dry-cli-ui.rb
|
|
185
186
|
- lib/dry/cli/ui.rb
|
|
187
|
+
- lib/dry/cli/ui/configuration.rb
|
|
186
188
|
- lib/dry/cli/ui/console.rb
|
|
187
189
|
- lib/dry/cli/ui/duration.rb
|
|
188
190
|
- lib/dry/cli/ui/line.rb
|
|
191
|
+
- lib/dry/cli/ui/status_bar.rb
|
|
189
192
|
- lib/dry/cli/ui/terminal.rb
|
|
190
193
|
- lib/dry/cli/ui/theme.rb
|
|
191
194
|
- lib/dry/cli/ui/version.rb
|
|
192
195
|
- lib/dry/cli/ui/widgets.rb
|
|
193
196
|
- lib/dry/cli/ui/widgets/box.rb
|
|
197
|
+
- lib/dry/cli/ui/widgets/multi.rb
|
|
198
|
+
- lib/dry/cli/ui/widgets/multi_progress.rb
|
|
199
|
+
- lib/dry/cli/ui/widgets/multi_spinner.rb
|
|
194
200
|
- lib/dry/cli/ui/widgets/outcome.rb
|
|
201
|
+
- lib/dry/cli/ui/widgets/pool.rb
|
|
195
202
|
- lib/dry/cli/ui/widgets/progress.rb
|
|
196
203
|
- lib/dry/cli/ui/widgets/prompt.rb
|
|
197
204
|
- lib/dry/cli/ui/widgets/spinner.rb
|