dry-cli-ui 0.6.0 → 0.7.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/dry/cli/ui.rb CHANGED
@@ -34,14 +34,25 @@ module Dry
34
34
  autoload :Configuration, File.expand_path("ui/configuration", __dir__)
35
35
  autoload :Console, File.expand_path("ui/console", __dir__)
36
36
  autoload :Duration, File.expand_path("ui/duration", __dir__)
37
+ autoload :Flags, File.expand_path("ui/flags", __dir__)
38
+ autoload :Invocation, File.expand_path("ui/invocation", __dir__)
37
39
  autoload :Line, File.expand_path("ui/line", __dir__)
40
+ autoload :Logging, File.expand_path("ui/logging", __dir__)
41
+ autoload :Report, File.expand_path("ui/report", __dir__)
42
+ autoload :Reporting, File.expand_path("ui/reporting", __dir__)
38
43
  autoload :Stop, File.expand_path("ui/stop", __dir__)
39
44
  autoload :StatusBar, File.expand_path("ui/status_bar", __dir__)
40
45
  autoload :Terminal, File.expand_path("ui/terminal", __dir__)
41
46
  autoload :Theme, File.expand_path("ui/theme", __dir__)
42
47
  autoload :Widgets, File.expand_path("ui/widgets", __dir__)
43
48
 
49
+ @started_at = ::Time.now
50
+
44
51
  class << self
52
+ # @return [Time] when this gem loaded, which stands for when the process started. Stamps
53
+ # the file name of a report `-o` writes.
54
+ attr_reader :started_at
55
+
45
56
  # Make process-wide settings. A block taking an argument receives the
46
57
  # configuration; any other block runs against it.
47
58
  #
@@ -69,23 +80,78 @@ module Dry
69
80
  def reset!
70
81
  @config = nil
71
82
  end
83
+
84
+ # The IO beneath a dry-cli stream, or the stream itself when it is not one.
85
+ #
86
+ # Asks for the class rather than for `raw`, which `io/console` also defines on every IO, to
87
+ # put a terminal into raw mode.
88
+ #
89
+ # @param stream [IO, Dry::CLI::Stream]
90
+ # @return [IO]
91
+ def raw(stream)
92
+ defined?(Dry::CLI::Stream) && stream.is_a?(Dry::CLI::Stream) ? stream.raw : stream
93
+ end
72
94
  end
73
95
 
74
- # The console this command presents through. Writes to the command's own
75
- # `out` and `err` when dry-cli has set them, and to `$stdout` and
76
- # `$stderr` otherwise.
96
+ # The console this command presents through.
77
97
  #
78
- # Override it to configure the console:
98
+ # In a dry-cli command it writes to the command's own `stdout` and `stderr`, and reads prompt
99
+ # answers from its `stdin`: the streams the CLI was called with. Under dry-cli 1.4 and
100
+ # earlier, which give a command only `out` and `err`, it writes to those and reads `$stdin`.
101
+ # Anywhere else it uses `$stdout`, `$stderr` and `$stdin`.
79
102
  #
80
- # @example
81
- # def ui = @ui ||= Dry::CLI::UI::Console.new(box_width: 72)
103
+ # The console is built again whenever those streams change. A command registered as an
104
+ # instance is used for every call to its CLI, each time with that call's streams, as when a
105
+ # test suite runs the CLI in-process with a StringIO per test.
106
+ #
107
+ # Configure the console by overriding {#ui_options}.
82
108
  #
83
109
  # @return [Dry::CLI::UI::Console]
84
110
  def ui
85
- @ui ||= Console.new(
86
- out: (respond_to?(:out, true) && __send__(:out)) || $stdout,
87
- err: (respond_to?(:err, true) && __send__(:err)) || $stderr
88
- )
111
+ streams = ui_streams
112
+ @ui = nil unless @ui_streams && streams.all? { |name, io| @ui_streams[name].equal?(io) }
113
+ @ui_streams = streams
114
+ @ui ||= Console.new(**streams, invocation: Invocation.for(self), **ui_options)
115
+ end
116
+
117
+ private
118
+
119
+ # The streams {#ui} presents through, as keywords for {Console#initialize}.
120
+ #
121
+ # A dry-cli command gives its own public `stdout`, `stderr` and `stdin`. Its output streams
122
+ # render dry-cli's own styles; the console writes to the IO beneath them, since it decides
123
+ # colour for itself.
124
+ #
125
+ # @return [Hash{Symbol => IO}] `out:`, `err:` and `input:`
126
+ def ui_streams
127
+ {
128
+ out: UI.raw(ui_stream(:stdout, :out) || $stdout),
129
+ err: UI.raw(ui_stream(:stderr, :err) || $stderr),
130
+ input: respond_to?(:stdin) ? stdin : $stdin
131
+ }
132
+ end
133
+
134
+ # One of the command's output streams: the public one, or the protected one dry-cli 1.4 and
135
+ # earlier set on a command it runs.
136
+ #
137
+ # @param name [Symbol] the public reader, `:stdout` or `:stderr`
138
+ # @param legacy_name [Symbol] the protected reader, `:out` or `:err`
139
+ # @return [IO, Dry::CLI::Stream, nil] nil when the command has neither, or was never run
140
+ def ui_stream(name, legacy_name)
141
+ return public_send(name) if respond_to?(name)
142
+
143
+ __send__(legacy_name) if respond_to?(legacy_name, true)
144
+ end
145
+
146
+ # Options for {Console#initialize} other than its streams. Override it to configure the
147
+ # console {#ui} builds.
148
+ #
149
+ # @example
150
+ # def ui_options = { box_width: 72 }
151
+ #
152
+ # @return [Hash{Symbol => Object}]
153
+ def ui_options
154
+ {}
89
155
  end
90
156
  end
91
157
  end
data/sig/dry/cli/ui.rbs CHANGED
@@ -11,9 +11,69 @@ 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_stream: (Symbol name, Symbol legacy_name) -> untyped
16
+ private def ui_options: () -> Hash[Symbol, untyped]
17
+
18
+ def self.raw: (untyped stream) -> untyped
19
+
14
20
  def self.configure: () { (Configuration config) -> void } -> Configuration
15
21
  def self.config: () -> Configuration
16
22
  def self.reset!: () -> void
23
+ def self.started_at: () -> Time
24
+
25
+ module Flags
26
+ OPTIONS: Hash[Symbol, Hash[Symbol, Hash[Symbol, untyped]]]
27
+ OPTIONAL_VALUES: Array[Symbol]
28
+ SWITCHES: Array[String]
29
+
30
+ def self.arguments: (Array[String] argv, ?switches: Array[String]) -> Array[String]
31
+ def self.switches: (untyped command) -> Array[String]
32
+ def flags: (*Symbol names) -> Array[Symbol]
33
+ end
34
+
35
+ class Invocation
36
+ attr_reader executable: String
37
+ attr_reader action: String?
38
+ attr_reader name: String
39
+ attr_reader started_at: Time
40
+ attr_reader directory: String
41
+
42
+ def self.new: (executable: String, action: String?, name: String, started_at: Time, directory: String) -> Invocation
43
+ def self.for: (?untyped command, ?program: String, ?started_at: Time, ?directory: String) -> Invocation
44
+ def self.root: (String directory) -> String
45
+ def basename: () -> String
46
+ def report_path: (String? value) -> String?
47
+ def log_path: (String? value) -> String?
48
+ end
49
+
50
+ class Report
51
+ def color?: () -> true
52
+ end
53
+
54
+ class Logging
55
+ LEVELS: Array[String]
56
+ FORMATS: Hash[String, Symbol]
57
+ INSPECT_LIMIT: Integer
58
+ FRAME_LIMIT: Integer
59
+
60
+ def self.level: (String | Symbol name) -> Symbol
61
+ def self.formatter: (String | Symbol name) -> untyped
62
+ def self.formats: () -> Array[String]
63
+ def self.logger: (String name) -> untyped
64
+ def self.locals: (Exception exception) -> Array[Hash[Symbol, untyped]]?
65
+ def self.record: (Exception exception) -> void
66
+ def initialize: (String? target, io: untyped, level: String | Symbol, format: String | Symbol) -> void
67
+ def run: [T] () { () -> T } -> T
68
+ end
69
+
70
+ module Reporting
71
+ def output: [T] (?String? value) { (untyped io) -> T } -> T
72
+ def logging: [T] (String? value, ?level: String | Symbol, ?format: String | Symbol) { () -> T } -> T
73
+ def with_flags: [T] (?output: String?, ?log: String?, ?log_level: String | Symbol, ?log_format: String | Symbol, **untyped) { (untyped io) -> T } -> T
74
+ def logger: () -> untyped
75
+ def log_exception: (Exception exception, ?String? message, ?level: String | Symbol) -> nil
76
+ end
17
77
 
18
78
  class Configuration
19
79
  type spinner_definition = Symbol | { interval: Numeric, frames: Array[String] | String }
@@ -27,6 +87,10 @@ module Dry
27
87
  def bar_color=: (Symbol? value) -> Symbol?
28
88
  def bar_background: (?Symbol? value) -> Symbol?
29
89
  def bar_background=: (Symbol? value) -> Symbol?
90
+ def bar_failed_color: (?Symbol? value) -> Symbol?
91
+ def bar_failed_color=: (Symbol? value) -> Symbol?
92
+ def bar_aux_color: (?Symbol? value) -> Symbol?
93
+ def bar_aux_color=: (Symbol? value) -> Symbol?
30
94
  def spinner_frames: () -> Array[String]
31
95
  def spinner_frame_seconds: () -> Float
32
96
  def bar_complete: () -> String
@@ -44,6 +108,28 @@ module Dry
44
108
  def summary: (String label) -> String
45
109
  end
46
110
 
111
+ module Widgets
112
+ class Progress
113
+ class Handle
114
+ type outcome = :ok | :aux | :failed
115
+
116
+ OUTCOMES: Array[outcome]
117
+
118
+ def total: () -> Integer?
119
+ def total=: (Integer value) -> Integer
120
+ def color: () -> Symbol?
121
+ def current: () -> Integer
122
+ def counts: () -> { ok: Integer, aux: Integer, failed: Integer }
123
+ def advance: (?Integer step, ?as: outcome) -> self
124
+ def fail: (?_ToS? reason) -> self
125
+ def failed?: () -> bool
126
+ def reason: () -> String?
127
+ def finish: () -> self
128
+ def summary: (String label) -> String
129
+ end
130
+ end
131
+ end
132
+
47
133
  class Stop
48
134
  def stopped?: () -> bool
49
135
  def stop!: () -> self
@@ -51,6 +137,8 @@ module Dry
51
137
  end
52
138
 
53
139
  class Console
140
+ include Reporting
141
+
54
142
  def debug: (*_ToS paragraphs, ?width: Integer?) -> nil
55
143
  def info: (*_ToS paragraphs, ?width: Integer?) -> nil
56
144
  def success: (*_ToS paragraphs, ?width: Integer?) -> nil
@@ -62,14 +150,15 @@ module Dry
62
150
  def popup: (*_ToS paragraphs, ?title: String?, ?width: Integer?) -> nil
63
151
  def spinner: [T] (String label) { (Line line) -> T } -> T
64
152
  def multi_spinner: (String title, ?concurrent: (bool | Integer), ?stop: Stop?) { (untyped spinners) -> void } -> Array[untyped]
65
- def progress: [T] (String label, total: Integer, ?color: Symbol?) { (untyped progress) -> T } -> T
153
+ def progress: [T] (String label, total: Integer, ?color: Symbol?) { (Widgets::Progress::Handle progress) -> T } -> T
154
+ def legend: (?failed: _ToS?, ?aux: _ToS?, ?ok: _ToS?) -> nil
66
155
  def multi_progress: (String title, ?concurrent: (bool | Integer), ?count: (:units | :jobs), ?total: Integer?, ?stop: Stop?) { (untyped bars) -> void } -> Array[untyped]
67
156
  def stoppable: [T] () { (Stop stop) -> T } -> T
68
157
  def status_bar: [T] (?String? title, ?hints: Array[String] | String) { () -> T } -> T
69
158
  def tasks: (?String? title, ?concurrent: (bool | Integer)) { (untyped tasks) -> void } -> nil
70
159
  def table: (Array[Array[_ToS]] rows, ?header: Array[_ToS]?) -> nil
71
160
  def prompt: (String question, ?default: untyped, ?choices: (Array[String] | Hash[String, untyped])?) -> untyped
72
- def confirm: (String question, ?default: bool) -> bool
161
+ def confirm: (String question, ?default: bool, ?yes: bool) -> bool
73
162
  end
74
163
  end
75
164
  end
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.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: binding_of_caller
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: concurrent-ruby
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -37,6 +51,20 @@ dependencies:
37
51
  - - ">="
38
52
  - !ruby/object:Gem::Version
39
53
  version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: logger
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.6'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.6'
40
68
  - !ruby/object:Gem::Dependency
41
69
  name: pastel
42
70
  requirement: !ruby/object:Gem::Requirement
@@ -51,6 +79,20 @@ dependencies:
51
79
  - - "~>"
52
80
  - !ruby/object:Gem::Version
53
81
  version: '0.8'
82
+ - !ruby/object:Gem::Dependency
83
+ name: semantic_logger
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '5.1'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '5.1'
54
96
  - !ruby/object:Gem::Dependency
55
97
  name: strings
56
98
  requirement: !ruby/object:Gem::Requirement
@@ -187,7 +229,12 @@ files:
187
229
  - lib/dry/cli/ui/configuration.rb
188
230
  - lib/dry/cli/ui/console.rb
189
231
  - lib/dry/cli/ui/duration.rb
232
+ - lib/dry/cli/ui/flags.rb
233
+ - lib/dry/cli/ui/invocation.rb
190
234
  - lib/dry/cli/ui/line.rb
235
+ - lib/dry/cli/ui/logging.rb
236
+ - lib/dry/cli/ui/report.rb
237
+ - lib/dry/cli/ui/reporting.rb
191
238
  - lib/dry/cli/ui/status_bar.rb
192
239
  - lib/dry/cli/ui/stop.rb
193
240
  - lib/dry/cli/ui/terminal.rb
@@ -195,6 +242,7 @@ files:
195
242
  - lib/dry/cli/ui/version.rb
196
243
  - lib/dry/cli/ui/widgets.rb
197
244
  - lib/dry/cli/ui/widgets/box.rb
245
+ - lib/dry/cli/ui/widgets/legend.rb
198
246
  - lib/dry/cli/ui/widgets/multi.rb
199
247
  - lib/dry/cli/ui/widgets/multi_progress.rb
200
248
  - lib/dry/cli/ui/widgets/multi_spinner.rb