cli-ui 1.5.1 → 2.2.3

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/cli/ui/frame.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  # coding: utf-8
2
+
3
+ # typed: true
4
+
2
5
  require 'cli/ui'
3
6
  require 'cli/ui/frame/frame_stack'
4
7
  require 'cli/ui/frame/frame_style'
@@ -7,9 +10,12 @@ module CLI
7
10
  module UI
8
11
  module Frame
9
12
  class UnnestedFrameException < StandardError; end
13
+ DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
14
+
10
15
  class << self
11
- DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
16
+ extend T::Sig
12
17
 
18
+ sig { returns(FrameStyle) }
13
19
  def frame_style
14
20
  @frame_style ||= FrameStyle::Box
15
21
  end
@@ -22,6 +28,7 @@ module CLI
22
28
  #
23
29
  # * +symbol+ or +FrameStyle+ - the default frame style to use for frames
24
30
  #
31
+ sig { params(frame_style: FrameStylable).void }
25
32
  def frame_style=(frame_style)
26
33
  @frame_style = CLI::UI.resolve_style(frame_style)
27
34
  end
@@ -47,6 +54,8 @@ module CLI
47
54
  # * +:success_text+ - If the block succeeds, what do we output? Defaults to nil
48
55
  # * +:timing+ - How long did the frame content take? Invalid for blockless. Defaults to true for the block form
49
56
  # * +frame_style+ - The frame style to use for this frame
57
+ # * +:to+ - Target stream, like $stdout or $stderr. Can be anything with print and puts methods,
58
+ # or under Sorbet, IO or StringIO. Defaults to $stdout.
50
59
  #
51
60
  # ==== Example
52
61
  #
@@ -67,13 +76,27 @@ module CLI
67
76
  # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68
77
  #
69
78
  #
79
+ sig do
80
+ type_parameters(:T).params(
81
+ text: String,
82
+ color: Colorable,
83
+ failure_text: T.nilable(String),
84
+ success_text: T.nilable(String),
85
+ timing: T.any(T::Boolean, Numeric),
86
+ frame_style: FrameStylable,
87
+ to: IOLike,
88
+ block: T.nilable(T.proc.returns(T.type_parameter(:T))),
89
+ ).returns(T.nilable(T.type_parameter(:T)))
90
+ end
70
91
  def open(
71
92
  text,
72
93
  color: DEFAULT_FRAME_COLOR,
73
94
  failure_text: nil,
74
95
  success_text: nil,
75
- timing: nil,
76
- frame_style: self.frame_style
96
+ timing: block_given?,
97
+ frame_style: self.frame_style,
98
+ to: $stdout,
99
+ &block
77
100
  )
78
101
  frame_style = CLI::UI.resolve_style(frame_style)
79
102
  color = CLI::UI.resolve_color(color)
@@ -90,8 +113,8 @@ module CLI
90
113
 
91
114
  t_start = Time.now
92
115
  CLI::UI.raw do
93
- print(prefix.chop)
94
- puts frame_style.open(text, color: color)
116
+ to.print(prefix.chop)
117
+ to.puts(frame_style.start(text, color: color))
95
118
  end
96
119
  FrameStack.push(color: color, style: frame_style)
97
120
 
@@ -103,18 +126,18 @@ module CLI
103
126
  success = yield
104
127
  rescue
105
128
  closed = true
106
- t_diff = elasped(t_start, timing)
107
- close(failure_text, color: :red, elapsed: t_diff)
129
+ t_diff = elapsed(t_start, timing)
130
+ close(failure_text, color: :red, elapsed: t_diff, to: to)
108
131
  raise
109
132
  else
110
133
  success
111
134
  ensure
112
135
  unless closed
113
- t_diff = elasped(t_start, timing)
114
- if success != false
115
- close(success_text, color: color, elapsed: t_diff)
136
+ t_diff = elapsed(t_start, timing)
137
+ if T.unsafe(success) != false
138
+ close(success_text, color: color, elapsed: t_diff, to: to)
116
139
  else
117
- close(failure_text, color: :red, elapsed: t_diff)
140
+ close(failure_text, color: :red, elapsed: t_diff, to: to)
118
141
  end
119
142
  end
120
143
  end
@@ -131,6 +154,8 @@ module CLI
131
154
  #
132
155
  # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
133
156
  # * +frame_style+ - The frame style to use for this frame
157
+ # * +:to+ - Target stream, like $stdout or $stderr. Can be anything with print and puts methods,
158
+ # or under Sorbet, IO or StringIO. Defaults to $stdout.
134
159
  #
135
160
  # ==== Example
136
161
  #
@@ -145,16 +170,24 @@ module CLI
145
170
  #
146
171
  # MUST be inside an open frame or it raises a +UnnestedFrameException+
147
172
  #
148
- def divider(text, color: nil, frame_style: nil)
173
+ sig do
174
+ params(
175
+ text: T.nilable(String),
176
+ color: T.nilable(Colorable),
177
+ frame_style: T.nilable(FrameStylable),
178
+ to: IOLike,
179
+ ).void
180
+ end
181
+ def divider(text, color: nil, frame_style: nil, to: $stdout)
149
182
  fs_item = FrameStack.pop
150
183
  raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
151
184
 
152
- color = CLI::UI.resolve_color(color) || fs_item.color
153
- frame_style = CLI::UI.resolve_style(frame_style) || fs_item.frame_style
185
+ divider_color = CLI::UI.resolve_color(color || fs_item.color)
186
+ frame_style = CLI::UI.resolve_style(frame_style || fs_item.frame_style)
154
187
 
155
188
  CLI::UI.raw do
156
- print(prefix.chop)
157
- puts frame_style.divider(text, color: color)
189
+ to.print(prefix.chop)
190
+ to.puts(frame_style.divider(text.to_s, color: divider_color))
158
191
  end
159
192
 
160
193
  FrameStack.push(fs_item)
@@ -172,6 +205,8 @@ module CLI
172
205
  # * +:color+ - The color of the frame. Defaults to nil
173
206
  # * +:elapsed+ - How long did the frame take? Defaults to nil
174
207
  # * +frame_style+ - The frame style to use for this frame. Defaults to nil
208
+ # * +:to+ - Target stream, like $stdout or $stderr. Can be anything with print and puts methods,
209
+ # or under Sorbet, IO or StringIO. Defaults to $stdout.
175
210
  #
176
211
  # ==== Example
177
212
  #
@@ -184,21 +219,26 @@ module CLI
184
219
  #
185
220
  # MUST be inside an open frame or it raises a +UnnestedFrameException+
186
221
  #
187
- def close(text, color: nil, elapsed: nil, frame_style: nil)
222
+ sig do
223
+ params(
224
+ text: T.nilable(String),
225
+ color: T.nilable(Colorable),
226
+ elapsed: T.nilable(Numeric),
227
+ frame_style: T.nilable(FrameStylable),
228
+ to: IOLike,
229
+ ).void
230
+ end
231
+ def close(text, color: nil, elapsed: nil, frame_style: nil, to: $stdout)
188
232
  fs_item = FrameStack.pop
189
233
  raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
190
234
 
191
- color = CLI::UI.resolve_color(color) || fs_item.color
192
- frame_style = CLI::UI.resolve_style(frame_style) || fs_item.frame_style
193
-
194
- kwargs = {}
195
- if elapsed
196
- kwargs[:right_text] = "(#{elapsed.round(2)}s)"
197
- end
235
+ close_color = CLI::UI.resolve_color(color || fs_item.color)
236
+ frame_style = CLI::UI.resolve_style(frame_style || fs_item.frame_style)
237
+ elapsed_string = elapsed ? "(#{elapsed.round(2)}s)" : nil
198
238
 
199
239
  CLI::UI.raw do
200
- print(prefix.chop)
201
- puts frame_style.close(text, color: color, **kwargs)
240
+ to.print(prefix.chop)
241
+ to.puts(frame_style.close(text.to_s, color: close_color, right_text: elapsed_string))
202
242
  end
203
243
  end
204
244
 
@@ -208,11 +248,12 @@ module CLI
208
248
  #
209
249
  # * +:color+ - The color of the prefix. Defaults to +Thread.current[:cliui_frame_color_override]+
210
250
  #
251
+ sig { params(color: T.nilable(Colorable)).returns(String) }
211
252
  def prefix(color: Thread.current[:cliui_frame_color_override])
212
253
  +''.tap do |output|
213
254
  items = FrameStack.items
214
255
 
215
- items[0..-2].each do |item|
256
+ items[0..-2].to_a.each do |item|
216
257
  output << item.color.code << item.frame_style.prefix
217
258
  end
218
259
 
@@ -227,6 +268,7 @@ module CLI
227
268
  end
228
269
 
229
270
  # The width of a prefix given the number of Frames in the stack
271
+ sig { returns(Integer) }
230
272
  def prefix_width
231
273
  w = FrameStack.items.reduce(0) do |width, item|
232
274
  width + item.frame_style.prefix_width
@@ -241,7 +283,12 @@ module CLI
241
283
  #
242
284
  # * +color+ - The color to override to
243
285
  #
244
- def with_frame_color_override(color)
286
+ sig do
287
+ type_parameters(:T)
288
+ .params(color: Colorable, block: T.proc.returns(T.type_parameter(:T)))
289
+ .returns(T.type_parameter(:T))
290
+ end
291
+ def with_frame_color_override(color, &block)
245
292
  prev = Thread.current[:cliui_frame_color_override]
246
293
  Thread.current[:cliui_frame_color_override] = color
247
294
  yield
@@ -254,13 +301,13 @@ module CLI
254
301
  # If timing is:
255
302
  # Numeric: return it
256
303
  # false: return nil
257
- # true or nil: defaults to Time.new
258
- # Time: return the difference with start
259
- def elasped(start, timing)
304
+ # true: defaults to Time.new
305
+ sig { params(start: Time, timing: T.any(Numeric, T::Boolean)).returns(T.nilable(Numeric)) }
306
+ def elapsed(start, timing)
260
307
  return timing if timing.is_a?(Numeric)
261
308
  return if timing.is_a?(FalseClass)
262
309
 
263
- timing = Time.new if timing.is_a?(TrueClass) || timing.nil?
310
+ timing = Time.new
264
311
  timing - start
265
312
  end
266
313
  end
data/lib/cli/ui/glyph.rb CHANGED
@@ -1,14 +1,22 @@
1
+ # typed: true
2
+
1
3
  require 'cli/ui'
2
4
 
3
5
  module CLI
4
6
  module UI
5
7
  class Glyph
8
+ extend T::Sig
9
+
6
10
  class InvalidGlyphHandle < ArgumentError
11
+ extend T::Sig
12
+
13
+ sig { params(handle: String).void }
7
14
  def initialize(handle)
8
15
  super
9
16
  @handle = handle
10
17
  end
11
18
 
19
+ sig { returns(String) }
12
20
  def message
13
21
  keys = Glyph.available.join(',')
14
22
  "invalid glyph handle: #{@handle} " \
@@ -16,7 +24,14 @@ module CLI
16
24
  end
17
25
  end
18
26
 
19
- attr_reader :handle, :codepoint, :color, :to_s, :fmt
27
+ sig { returns(String) }
28
+ attr_reader :handle, :to_s, :fmt, :char
29
+
30
+ sig { returns(T.any(Integer, T::Array[Integer])) }
31
+ attr_reader :codepoint
32
+
33
+ sig { returns(Color) }
34
+ attr_reader :color
20
35
 
21
36
  # Creates a new glyph
22
37
  #
@@ -27,26 +42,18 @@ module CLI
27
42
  # * +plain+ - A fallback plain string to be used in case glyphs are disabled
28
43
  # * +color+ - What color to output the glyph. Check +CLI::UI::Color+ for options.
29
44
  #
45
+ sig { params(handle: String, codepoint: T.any(Integer, T::Array[Integer]), plain: String, color: Color).void }
30
46
  def initialize(handle, codepoint, plain, color)
31
47
  @handle = handle
32
48
  @codepoint = codepoint
33
49
  @color = color
34
- @plain = plain
35
- @char = Array(codepoint).pack('U*')
36
- @to_s = color.code + char + Color::RESET.code
37
- @fmt = "{{#{color.name}:#{char}}}"
50
+ @char = CLI::UI::OS.current.use_emoji? ? Array(codepoint).pack('U*') : plain
51
+ @to_s = color.code + @char + Color::RESET.code
52
+ @fmt = "{{#{color.name}:#{@char}}}"
38
53
 
39
54
  MAP[handle] = self
40
55
  end
41
56
 
42
- # Fetches the actual character(s) to be displayed for a glyph, based on the current OS support
43
- #
44
- # ==== Returns
45
- # Returns the glyph string
46
- def char
47
- CLI::UI::OS.current.supports_emoji? ? @char : @plain
48
- end
49
-
50
57
  # Mapping of glyphs to terminal output
51
58
  MAP = {}
52
59
  STAR = new('*', 0x2b51, '*', Color::YELLOW) # YELLOW SMALL STAR (⭑)
@@ -59,25 +66,31 @@ module CLI
59
66
  HOURGLASS = new('H', [0x231b, 0xfe0e], 'H', Color::BLUE) # HOURGLASS + VARIATION SELECTOR 15 (⌛︎)
60
67
  WARNING = new('!', [0x26a0, 0xfe0f], '!', Color::YELLOW) # WARNING SIGN + VARIATION SELECTOR 16 (⚠️ )
61
68
 
62
- # Looks up a glyph by name
63
- #
64
- # ==== Raises
65
- # Raises a InvalidGlyphHandle if the glyph is not available
66
- # You likely need to create it with +.new+ or you made a typo
67
- #
68
- # ==== Returns
69
- # Returns a terminal output-capable string
70
- #
71
- def self.lookup(name)
72
- MAP.fetch(name.to_s)
73
- rescue KeyError
74
- raise InvalidGlyphHandle, name
75
- end
69
+ class << self
70
+ extend T::Sig
76
71
 
77
- # All available glyphs by name
78
- #
79
- def self.available
80
- MAP.keys
72
+ # Looks up a glyph by name
73
+ #
74
+ # ==== Raises
75
+ # Raises a InvalidGlyphHandle if the glyph is not available
76
+ # You likely need to create it with +.new+ or you made a typo
77
+ #
78
+ # ==== Returns
79
+ # Returns a terminal output-capable string
80
+ #
81
+ sig { params(name: String).returns(Glyph) }
82
+ def lookup(name)
83
+ MAP.fetch(name.to_s)
84
+ rescue KeyError
85
+ raise InvalidGlyphHandle, name
86
+ end
87
+
88
+ # All available glyphs by name
89
+ #
90
+ sig { returns(T::Array[String]) }
91
+ def available
92
+ MAP.keys
93
+ end
81
94
  end
82
95
  end
83
96
  end
data/lib/cli/ui/os.rb CHANGED
@@ -1,67 +1,63 @@
1
+ # typed: true
2
+
1
3
  require 'rbconfig'
2
4
 
3
5
  module CLI
4
6
  module UI
5
- module OS
6
- # Determines which OS is currently running the UI, to make it easier to
7
- # adapt its behaviour to the features of the OS.
8
- def self.current
9
- @current_os ||= case RbConfig::CONFIG['host_os']
10
- when /darwin/
11
- Mac
12
- when /linux/
13
- Linux
14
- else
15
- if RUBY_PLATFORM !~ /cygwin/ && ENV['OS'] == 'Windows_NT'
16
- Windows
17
- else
18
- raise "Could not determine OS from host_os #{RbConfig::CONFIG["host_os"]}"
19
- end
20
- end
7
+ class OS
8
+ extend T::Sig
9
+
10
+ sig { params(emoji: T::Boolean, color_prompt: T::Boolean, arrow_keys: T::Boolean, shift_cursor: T::Boolean).void }
11
+ def initialize(emoji: true, color_prompt: true, arrow_keys: true, shift_cursor: false)
12
+ @emoji = emoji
13
+ @color_prompt = color_prompt
14
+ @arrow_keys = arrow_keys
15
+ @shift_cursor = shift_cursor
21
16
  end
22
17
 
23
- class Mac
24
- class << self
25
- def supports_emoji?
26
- true
27
- end
28
-
29
- def supports_color_prompt?
30
- true
31
- end
32
-
33
- def supports_arrow_keys?
34
- true
35
- end
36
-
37
- def shift_cursor_on_line_reset?
38
- false
39
- end
40
- end
18
+ sig { returns(T::Boolean) }
19
+ def use_emoji?
20
+ @emoji
41
21
  end
42
22
 
43
- class Linux < Mac
23
+ sig { returns(T::Boolean) }
24
+ def use_color_prompt?
25
+ @color_prompt
44
26
  end
45
27
 
46
- class Windows
47
- class << self
48
- def supports_emoji?
49
- false
50
- end
28
+ sig { returns(T::Boolean) }
29
+ def suggest_arrow_keys?
30
+ @arrow_keys
31
+ end
51
32
 
52
- def supports_color_prompt?
53
- false
54
- end
33
+ sig { returns(T::Boolean) }
34
+ def shift_cursor_back_on_horizontal_absolute?
35
+ @shift_cursor
36
+ end
55
37
 
56
- def supports_arrow_keys?
57
- false
58
- end
38
+ class << self
39
+ extend T::Sig
59
40
 
60
- def shift_cursor_on_line_reset?
61
- true
41
+ sig { returns(OS) }
42
+ def current
43
+ @current_os ||= case RbConfig::CONFIG['host_os']
44
+ when /darwin/
45
+ MAC
46
+ when /linux/
47
+ LINUX
48
+ else
49
+ if RUBY_PLATFORM !~ /cygwin/ && ENV['OS'] == 'Windows_NT'
50
+ WINDOWS
51
+ else
52
+ raise "Could not determine OS from host_os #{RbConfig::CONFIG["host_os"]}"
53
+ end
62
54
  end
63
55
  end
64
56
  end
57
+
58
+ MAC = OS.new
59
+ LINUX = OS.new
60
+ WINDOWS = OS.new(emoji: false, color_prompt: false, arrow_keys: false, shift_cursor: true)
65
61
  end
66
62
  end
67
63
  end
@@ -1,58 +1,76 @@
1
+ # typed: true
2
+
1
3
  require 'cli/ui'
2
4
 
3
5
  module CLI
4
6
  module UI
5
7
  class Printer
6
- # Print a message to a stream with common utilities.
7
- # Allows overriding the color, encoding, and target stream.
8
- # By default, it formats the string using CLI:UI and rescues common stream errors.
9
- #
10
- # ==== Attributes
11
- #
12
- # * +msg+ - (required) the string to output. Can be frozen.
13
- #
14
- # ==== Options
15
- #
16
- # * +:frame_color+ - Override the frame color. Defaults to nil.
17
- # * +:to+ - Target stream, like $stdout or $stderr. Can be anything with a puts method. Defaults to $stdout.
18
- # * +:encoding+ - Force the output to be in a certain encoding. Defaults to UTF-8.
19
- # * +:format+ - Whether to format the string using CLI::UI.fmt. Defaults to true.
20
- # * +:graceful+ - Whether to gracefully ignore common I/O errors. Defaults to true.
21
- # * +:wrap+ - Whether to wrap text at word boundaries to terminal width. Defaults to true.
22
- #
23
- # ==== Returns
24
- # Returns whether the message was successfully printed,
25
- # which can be useful if +:graceful+ is set to true.
26
- #
27
- # ==== Example
28
- #
29
- # CLI::UI::Printer.puts('{{x}} Ouch', to: $stderr)
30
- #
31
- def self.puts(
32
- msg,
33
- frame_color:
34
- nil,
35
- to:
36
- $stdout,
37
- encoding: Encoding::UTF_8,
38
- format: true,
39
- graceful: true,
40
- wrap: true
41
- )
42
- msg = (+msg).force_encoding(encoding) if encoding
43
- msg = CLI::UI.fmt(msg) if format
44
- msg = CLI::UI.wrap(msg) if wrap
8
+ extend T::Sig
9
+
10
+ class << self
11
+ extend T::Sig
45
12
 
46
- if frame_color
47
- CLI::UI::Frame.with_frame_color_override(frame_color) { to.puts(msg) }
48
- else
49
- to.puts(msg)
13
+ # Print a message to a stream with common utilities.
14
+ # Allows overriding the color, encoding, and target stream.
15
+ # By default, it formats the string using CLI:UI and rescues common stream errors.
16
+ #
17
+ # ==== Attributes
18
+ #
19
+ # * +msg+ - (required) the string to output. Can be frozen.
20
+ #
21
+ # ==== Options
22
+ #
23
+ # * +:frame_color+ - Override the frame color. Defaults to nil.
24
+ # * +:to+ - Target stream, like $stdout or $stderr. Can be anything with a puts method. Defaults to $stdout.
25
+ # * +:encoding+ - Force the output to be in a certain encoding. Defaults to UTF-8.
26
+ # * +:format+ - Whether to format the string using CLI::UI.fmt. Defaults to true.
27
+ # * +:graceful+ - Whether to gracefully ignore common I/O errors. Defaults to true.
28
+ # * +:wrap+ - Whether to wrap text at word boundaries to terminal width. Defaults to true.
29
+ #
30
+ # ==== Returns
31
+ # Returns whether the message was successfully printed,
32
+ # which can be useful if +:graceful+ is set to true.
33
+ #
34
+ # ==== Example
35
+ #
36
+ # CLI::UI::Printer.puts('{{x}} Ouch', to: $stderr)
37
+ #
38
+ sig do
39
+ params(
40
+ msg: String,
41
+ frame_color: T.nilable(Colorable),
42
+ to: IOLike,
43
+ encoding: T.nilable(Encoding),
44
+ format: T::Boolean,
45
+ graceful: T::Boolean,
46
+ wrap: T::Boolean,
47
+ ).returns(T::Boolean)
50
48
  end
49
+ def puts(
50
+ msg,
51
+ frame_color: nil,
52
+ to: $stdout,
53
+ encoding: Encoding::UTF_8,
54
+ format: true,
55
+ graceful: true,
56
+ wrap: true
57
+ )
58
+ msg = (+msg).force_encoding(encoding) if encoding
59
+ msg = CLI::UI.fmt(msg) if format
60
+ msg = CLI::UI.wrap(msg) if wrap
61
+
62
+ if frame_color
63
+ CLI::UI::Frame.with_frame_color_override(frame_color) { to.puts(msg) }
64
+ else
65
+ to.puts(msg)
66
+ end
51
67
 
52
- true
53
- rescue Errno::EIO, Errno::EPIPE, IOError => e
54
- raise(e) unless graceful
55
- false
68
+ true
69
+ rescue Errno::EIO, Errno::EPIPE, IOError => e
70
+ raise(e) unless graceful
71
+
72
+ false
73
+ end
56
74
  end
57
75
  end
58
76
  end