cli-ui 1.5.1 → 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
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