dry-cli-ui 0.6.0 ā 0.6.1
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 +11 -0
- data/README.md +39 -9
- data/examples/Gemfile.lock +2 -2
- data/lib/dry/cli/ui/version.rb +1 -1
- data/lib/dry/cli/ui/widgets/multi_progress.rb +3 -7
- data/lib/dry/cli/ui/widgets/progress.rb +29 -3
- data/lib/dry/cli/ui.rb +52 -10
- data/sig/dry/cli/ui.rbs +5 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d64ee9f8fedbc7de0fcd1875acf59d07876ef6acaffbfcc8574b197de0e8d6fd
|
|
4
|
+
data.tar.gz: 2f7e11eca1a0dff40b3df7cba685bca7f97d4884515db1107805036e746e20bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9105f75325a1a7f8282e989d4c847320fb481880a7a529041d98d4bff9457dc5235d08a5c3b311c0b3d442a6dc4c3923b6c72844bd2fbb55ec04f2824b906e3
|
|
7
|
+
data.tar.gz: d095b784083ba57db9592e51ef2d56274dd9cb187fcac350d35aaea51b8e64c519a800e3f7f899b2b248acfe8035733c4fa63930b64086cdbebea17c8af4636b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
+
## [0.6.1]
|
|
2
|
+
|
|
3
|
+
- A progress handle takes `fail(reason = nil)`, as a `Line` does: the bar ends `š
label 3/3: reason` without raising, in `ui.progress` and in a row of `ui.multi_progress`, where the jobs after it still run and the headline ends `š
`. For work that carries on after some of its units failed.
|
|
4
|
+
|
|
1
5
|
## [0.6.0]
|
|
2
6
|
|
|
3
7
|
- `m.spinner(label)` inside `ui.multi_progress` declares a row with a spinner in place of a bar, for a phase whose size is never known, so one widget can show phases of known and unknown size together. The row shows its `Line`'s detail while it runs and ends `[ā] label`; it counts as a job and as no units on the headline.
|
|
4
8
|
|
|
9
|
+
## [0.6.0]
|
|
10
|
+
|
|
11
|
+
- `ui` reads the command's public `stdout`, `stderr` and `stdin`, the streams dry-cli was called with. Before, it looked for `out` and `err`, which dry-cli no longer has, and wrote to `$stdout` and `$stderr` instead. Prompts now read from the command's `stdin`.
|
|
12
|
+
- `ui` builds its console again when those streams change, so a command registered as an instance, or run in-process by `Dry::CLI::Launcher`, writes each call to that call's streams.
|
|
13
|
+
- `ui_options` configures the console `ui` builds, in place of overriding `ui`.
|
|
14
|
+
- Needs dry-cli with public command streams and `Dry::CLI::Launcher`; the Gemfile takes it from kigster/dry-cli until it is released.
|
|
15
|
+
|
|
5
16
|
## [0.5.1]
|
|
6
17
|
|
|
7
18
|
- `ui.stoppable { |stop| ... }` makes Ctrl-C ask for a stop instead of interrupting, and `ui.multi_spinner` and `ui.multi_progress` take `stop:`. Once it is set, the jobs running finish, the rest are skipped, and the headline says `stopping`, then ends skipped. A second Ctrl-C interrupts. `Dry::CLI::UI::Stop` is the object behind it.
|
data/README.md
CHANGED
|
@@ -86,7 +86,7 @@ class Import < ApplicationCommand
|
|
|
86
86
|
end
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
`ui` writes to the command's `
|
|
89
|
+
`ui` writes to the command's `stdout` and `stderr`, and reads prompt answers from its `stdin`: the streams dry-cli was called with. Outside a dry-cli command it uses `$stdout`, `$stderr` and `$stdin`. The console is built again when those streams change, so a command registered as an instance writes each call to that call's streams.
|
|
90
90
|
|
|
91
91
|
### Without dry-cli
|
|
92
92
|
|
|
@@ -424,6 +424,30 @@ Generating text...
|
|
|
424
424
|
|
|
425
425
|
`m.spinner` without a block raises `ArgumentError`.
|
|
426
426
|
|
|
427
|
+
A job that carries on after some of its units failed ends its row with `bar.fail(reason)` rather than raising, so the jobs after it still run. Its row ends `[š
] label 3/3: reason`, the headline ends `š
`, and the call returns as usual. `ui.progress` takes the same `bar.fail`.
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
ui.multi_progress("Generating text", concurrent: false, count: :jobs) do |m|
|
|
431
|
+
m.spinner("Finding PDFs") { pdfs = find_pdfs }
|
|
432
|
+
m.progress("Extracting", total: nil) do |bar|
|
|
433
|
+
bar.total = pdfs.size
|
|
434
|
+
failed = pdfs.count { |pdf| !extract(pdf).tap { bar.advance } }
|
|
435
|
+
bar.fail("#{failed} failed") if failed.positive?
|
|
436
|
+
end
|
|
437
|
+
m.spinner("Indexing") { index }
|
|
438
|
+
end
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Piped, with one PDF that would not extract:
|
|
442
|
+
|
|
443
|
+
```text
|
|
444
|
+
Generating text...
|
|
445
|
+
[ā] Finding PDFs (0.0s)
|
|
446
|
+
[š
] Extracting 3/3: 1 failed (0.0s)
|
|
447
|
+
[ā] Indexing (0.0s)
|
|
448
|
+
š
Generating text 3/3 (0.0s)
|
|
449
|
+
```
|
|
450
|
+
|
|
427
451
|
Piped:
|
|
428
452
|
|
|
429
453
|
```text
|
|
@@ -750,7 +774,7 @@ Errors raised inside a block are never swallowed: the widget marks itself failed
|
|
|
750
774
|
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
751
775
|
| `info`, `success`, `box`, `table`, `status` at those levels | `debug`, `warn`, `error`, `fatal`, `popup`, spinners, progress bars, their multi forms, task trees, the status bar, prompts |
|
|
752
776
|
|
|
753
|
-
`mycli export > rules.csv` therefore writes only the command's results to the file, while its progress stays on the screen. `ui` writes to the streams dry-cli was called with, so `Dry::CLI.new(registry).call(
|
|
777
|
+
`mycli export > rules.csv` therefore writes only the command's results to the file, while its progress stays on the screen. `ui` writes to the streams dry-cli was called with, so `Dry::CLI.new(registry).call(stdout: io, stderr: io)` captures everything.
|
|
754
778
|
|
|
755
779
|
A stream that is not a terminal, or runs under `TERM=dumb`, gets no animation, no cursor movement and no escape codes. [`NO_COLOR`](https://no-color.org) turns colour off and leaves animation on.
|
|
756
780
|
|
|
@@ -805,20 +829,18 @@ end
|
|
|
805
829
|
|
|
806
830
|
### Console options
|
|
807
831
|
|
|
808
|
-
Override `
|
|
832
|
+
Override `ui_options` to configure the console `ui` builds. The streams come from the command:
|
|
809
833
|
|
|
810
834
|
```ruby
|
|
811
835
|
class ApplicationCommand < Dry::CLI::Command
|
|
812
836
|
include Dry::CLI::UI
|
|
813
837
|
|
|
814
|
-
def
|
|
815
|
-
|
|
816
|
-
out: out || $stdout,
|
|
817
|
-
err: err || $stderr,
|
|
838
|
+
private def ui_options
|
|
839
|
+
{
|
|
818
840
|
box_width: 72, # boxes are 72 columns rather than the whole terminal
|
|
819
841
|
color: nil, # true or false to override detection
|
|
820
842
|
animate: nil # true or false to override detection
|
|
821
|
-
|
|
843
|
+
}
|
|
822
844
|
end
|
|
823
845
|
end
|
|
824
846
|
```
|
|
@@ -862,7 +884,15 @@ err.string # => "Loading...\nā Loading (0.0s)\nContinue? (y/N) "
|
|
|
862
884
|
out.string # => the Success box
|
|
863
885
|
```
|
|
864
886
|
|
|
865
|
-
Through dry-cli, `Dry::CLI.new(registry).call(arguments: %w[import],
|
|
887
|
+
Through dry-cli, `Dry::CLI.new(registry).call(arguments: %w[import], stdin: input, stdout: out, stderr: err)` gives every command's `ui` those streams.
|
|
888
|
+
|
|
889
|
+
To run the whole CLI in the same process as its specs, as Aruba's in-process launcher does, bind a `Dry::CLI::Launcher` to it. Each run gets its own streams, and exits are recorded rather than ending the process:
|
|
890
|
+
|
|
891
|
+
```ruby
|
|
892
|
+
Launcher = Dry::CLI::Launcher[MyCLI::Commands]
|
|
893
|
+
|
|
894
|
+
Launcher.new(%w[import], StringIO.new("y\n"), out, err, kernel).execute!
|
|
895
|
+
```
|
|
866
896
|
|
|
867
897
|
## Relationship to dry-cli-help
|
|
868
898
|
|
data/examples/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
dry-cli-ui (0.6.
|
|
4
|
+
dry-cli-ui (0.6.1)
|
|
5
5
|
concurrent-ruby (~> 1.3)
|
|
6
6
|
dry-cli (>= 1.0)
|
|
7
7
|
pastel (~> 0.8)
|
|
@@ -84,7 +84,7 @@ CHECKSUMS
|
|
|
84
84
|
dry-cli (1.4.1) sha256=b8015bb76c708aa8705a36faf694973e75eeeffca39b89c8e172dc6f66a7d874
|
|
85
85
|
dry-cli-autocomplete (0.1.3) sha256=14f76447bda0000c71739b585c827dc0a0a763e30a68a8b4b2dff91f640768e4
|
|
86
86
|
dry-cli-help (0.2.1) sha256=eaaaf6edb787ffe810425b4e4bb1a675d89397ccc759102d70b5b26e7bc692da
|
|
87
|
-
dry-cli-ui (0.6.
|
|
87
|
+
dry-cli-ui (0.6.1)
|
|
88
88
|
dry-inflector (1.3.1) sha256=7fb0c2bb04f67638f25c52e7ba39ab435d922a3a5c3cd196120f63accb682dcc
|
|
89
89
|
forwardable (1.4.0) sha256=f1cd40cc9812937980e1c76f1aa053660990a7c9b6a98fc37d945468afcce838
|
|
90
90
|
pastel (0.8.0) sha256=481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75
|
data/lib/dry/cli/ui/version.rb
CHANGED
|
@@ -113,8 +113,8 @@ module Dry
|
|
|
113
113
|
def call(job) = spinner?(job) ? Line.call(job.work, job.handle) : super
|
|
114
114
|
|
|
115
115
|
# @param job [Job]
|
|
116
|
-
# @return [Boolean]
|
|
117
|
-
def reported_failure?(job) =
|
|
116
|
+
# @return [Boolean] whether its line or its bar was told to fail
|
|
117
|
+
def reported_failure?(job) = job.handle.failed?
|
|
118
118
|
|
|
119
119
|
# @param job [Job]
|
|
120
120
|
# @return [Progress::Handle, nil] nil for a spinner, which has no progress
|
|
@@ -131,11 +131,7 @@ module Dry
|
|
|
131
131
|
|
|
132
132
|
# @param job [Job]
|
|
133
133
|
# @return [String]
|
|
134
|
-
def summary(job)
|
|
135
|
-
return job.handle.summary(job.label) if spinner?(job)
|
|
136
|
-
|
|
137
|
-
"#{job.label} #{job.handle.current}/#{job.handle.total || '?'}"
|
|
138
|
-
end
|
|
134
|
+
def summary(job) = job.handle.summary(job.label)
|
|
139
135
|
|
|
140
136
|
# @param width [Integer]
|
|
141
137
|
# @return [String]
|
|
@@ -56,6 +56,33 @@ module Dry
|
|
|
56
56
|
self
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Ends the work as a failure when the block returns, without
|
|
60
|
+
# raising: for work that went on after some of its units failed.
|
|
61
|
+
#
|
|
62
|
+
# @param reason [#to_s, nil] said after the count, such as "2 failed"
|
|
63
|
+
# @return [self]
|
|
64
|
+
def fail(reason = nil)
|
|
65
|
+
@failed = true
|
|
66
|
+
@reason = reason&.to_s
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [Boolean] whether {#fail} was called
|
|
71
|
+
def failed? = @failed == true
|
|
72
|
+
|
|
73
|
+
# @return [String, nil] what {#fail} was given
|
|
74
|
+
attr_reader :reason
|
|
75
|
+
|
|
76
|
+
# The label and the count, with the failure's reason after it when
|
|
77
|
+
# there is one: `Extracting 1002/1002: 2 failed`.
|
|
78
|
+
#
|
|
79
|
+
# @param label [String]
|
|
80
|
+
# @return [String]
|
|
81
|
+
def summary(label)
|
|
82
|
+
count = "#{label} #{current}/#{total || '?'}"
|
|
83
|
+
failed? && !reason.to_s.empty? ? "#{count}: #{reason}" : count
|
|
84
|
+
end
|
|
85
|
+
|
|
59
86
|
private
|
|
60
87
|
|
|
61
88
|
# @return [TTY::ProgressBar, nil]
|
|
@@ -150,14 +177,13 @@ module Dry
|
|
|
150
177
|
terminal.started(handle, label, progress: handle)
|
|
151
178
|
ok = false
|
|
152
179
|
result = yield handle
|
|
153
|
-
ok =
|
|
180
|
+
ok = !handle.failed?
|
|
154
181
|
result
|
|
155
182
|
ensure
|
|
156
183
|
if handle
|
|
157
184
|
bar&.stop
|
|
158
185
|
terminal.finished(handle, ok)
|
|
159
|
-
|
|
160
|
-
terminal.puts(Outcome.line(terminal, ok ? :done : :failed, summary, clock.call - started))
|
|
186
|
+
terminal.puts(Outcome.line(terminal, ok ? :done : :failed, handle.summary(label), clock.call - started))
|
|
161
187
|
end
|
|
162
188
|
end
|
|
163
189
|
|
data/lib/dry/cli/ui.rb
CHANGED
|
@@ -69,23 +69,65 @@ module Dry
|
|
|
69
69
|
def reset!
|
|
70
70
|
@config = nil
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
# The IO beneath a dry-cli stream, or the stream itself when it is not one.
|
|
74
|
+
#
|
|
75
|
+
# Asks for the class rather than for `raw`, which `io/console` also defines on every IO, to
|
|
76
|
+
# put a terminal into raw mode.
|
|
77
|
+
#
|
|
78
|
+
# @param stream [IO, Dry::CLI::Stream]
|
|
79
|
+
# @return [IO]
|
|
80
|
+
def raw(stream)
|
|
81
|
+
defined?(Dry::CLI::Stream) && stream.is_a?(Dry::CLI::Stream) ? stream.raw : stream
|
|
82
|
+
end
|
|
72
83
|
end
|
|
73
84
|
|
|
74
|
-
# The console this command presents through.
|
|
75
|
-
# `out` and `err` when dry-cli has set them, and to `$stdout` and
|
|
76
|
-
# `$stderr` otherwise.
|
|
85
|
+
# The console this command presents through.
|
|
77
86
|
#
|
|
78
|
-
#
|
|
87
|
+
# In a dry-cli command it writes to the command's own `stdout` and `stderr`, and reads prompt
|
|
88
|
+
# answers from its `stdin`: the streams the CLI was called with. Anywhere else it uses
|
|
89
|
+
# `$stdout`, `$stderr` and `$stdin`.
|
|
79
90
|
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
91
|
+
# The console is built again whenever those streams change. A command registered as an
|
|
92
|
+
# instance is used for every call to its CLI, each time with that call's streams, as when a
|
|
93
|
+
# test suite runs the CLI in-process with a StringIO per test.
|
|
94
|
+
#
|
|
95
|
+
# Configure the console by overriding {#ui_options}.
|
|
82
96
|
#
|
|
83
97
|
# @return [Dry::CLI::UI::Console]
|
|
84
98
|
def ui
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
99
|
+
streams = ui_streams
|
|
100
|
+
@ui = nil unless @ui_streams && streams.all? { |name, io| @ui_streams[name].equal?(io) }
|
|
101
|
+
@ui_streams = streams
|
|
102
|
+
@ui ||= Console.new(**streams, **ui_options)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
# The streams {#ui} presents through, as keywords for {Console#initialize}.
|
|
108
|
+
#
|
|
109
|
+
# A dry-cli command gives its own public `stdout`, `stderr` and `stdin`. Its output streams
|
|
110
|
+
# render dry-cli's own styles; the console writes to the IO beneath them, since it decides
|
|
111
|
+
# colour for itself.
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash{Symbol => IO}] `out:`, `err:` and `input:`
|
|
114
|
+
def ui_streams
|
|
115
|
+
{
|
|
116
|
+
out: UI.raw(respond_to?(:stdout) ? stdout : $stdout),
|
|
117
|
+
err: UI.raw(respond_to?(:stderr) ? stderr : $stderr),
|
|
118
|
+
input: respond_to?(:stdin) ? stdin : $stdin
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Options for {Console#initialize} other than its streams. Override it to configure the
|
|
123
|
+
# console {#ui} builds.
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# def ui_options = { box_width: 72 }
|
|
127
|
+
#
|
|
128
|
+
# @return [Hash{Symbol => Object}]
|
|
129
|
+
def ui_options
|
|
130
|
+
{}
|
|
89
131
|
end
|
|
90
132
|
end
|
|
91
133
|
end
|
data/sig/dry/cli/ui.rbs
CHANGED
|
@@ -11,6 +11,11 @@ module Dry
|
|
|
11
11
|
|
|
12
12
|
def ui: () -> Console
|
|
13
13
|
|
|
14
|
+
private def ui_streams: () -> { out: untyped, err: untyped, input: untyped }
|
|
15
|
+
private def ui_options: () -> Hash[Symbol, untyped]
|
|
16
|
+
|
|
17
|
+
def self.raw: (untyped stream) -> untyped
|
|
18
|
+
|
|
14
19
|
def self.configure: () { (Configuration config) -> void } -> Configuration
|
|
15
20
|
def self.config: () -> Configuration
|
|
16
21
|
def self.reset!: () -> void
|