cli-ui 1.5.0 → 2.0.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/README.md +17 -17
- data/lib/cli/ui/ansi.rb +157 -129
- data/lib/cli/ui/color.rb +39 -20
- data/lib/cli/ui/formatter.rb +45 -21
- data/lib/cli/ui/frame/frame_stack.rb +32 -13
- data/lib/cli/ui/frame/frame_style/box.rb +15 -4
- data/lib/cli/ui/frame/frame_style/bracket.rb +18 -7
- data/lib/cli/ui/frame/frame_style.rb +84 -87
- data/lib/cli/ui/frame.rb +55 -24
- data/lib/cli/ui/glyph.rb +44 -31
- data/lib/cli/ui/os.rb +44 -48
- data/lib/cli/ui/printer.rb +65 -47
- data/lib/cli/ui/progress.rb +49 -32
- data/lib/cli/ui/prompt/interactive_options.rb +91 -44
- data/lib/cli/ui/prompt/options_handler.rb +8 -0
- data/lib/cli/ui/prompt.rb +84 -31
- data/lib/cli/ui/sorbet_runtime_stub.rb +157 -0
- data/lib/cli/ui/spinner/async.rb +15 -4
- data/lib/cli/ui/spinner/spin_group.rb +67 -11
- data/lib/cli/ui/spinner.rb +48 -28
- data/lib/cli/ui/stdout_router.rb +71 -34
- data/lib/cli/ui/terminal.rb +37 -25
- data/lib/cli/ui/truncater.rb +7 -2
- data/lib/cli/ui/version.rb +3 -1
- data/lib/cli/ui/widgets/base.rb +23 -4
- data/lib/cli/ui/widgets/status.rb +19 -1
- data/lib/cli/ui/widgets.rb +42 -23
- data/lib/cli/ui/wrap.rb +8 -1
- data/lib/cli/ui.rb +325 -188
- metadata +10 -21
- data/.dependabot/config.yml +0 -8
- data/.github/CODEOWNERS +0 -1
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -14
- data/.rubocop.yml +0 -41
- data/.travis.yml +0 -7
- data/Gemfile +0 -17
- data/Gemfile.lock +0 -60
- data/Rakefile +0 -20
- data/bin/console +0 -14
- data/cli-ui.gemspec +0 -27
- data/dev.yml +0 -14
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
|
-
|
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
|
-
@
|
35
|
-
@
|
36
|
-
@
|
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
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
23
|
+
sig { returns(T::Boolean) }
|
24
|
+
def use_color_prompt?
|
25
|
+
@color_prompt
|
44
26
|
end
|
45
27
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
28
|
+
sig { returns(T::Boolean) }
|
29
|
+
def suggest_arrow_keys?
|
30
|
+
@arrow_keys
|
31
|
+
end
|
51
32
|
|
52
|
-
|
53
|
-
|
54
|
-
|
33
|
+
sig { returns(T::Boolean) }
|
34
|
+
def shift_cursor_back_on_horizontal_absolute?
|
35
|
+
@shift_cursor
|
36
|
+
end
|
55
37
|
|
56
|
-
|
57
|
-
|
58
|
-
end
|
38
|
+
class << self
|
39
|
+
extend T::Sig
|
59
40
|
|
60
|
-
|
61
|
-
|
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
|
data/lib/cli/ui/printer.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
data/lib/cli/ui/progress.rb
CHANGED
@@ -1,41 +1,54 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'cli/ui'
|
2
4
|
|
3
5
|
module CLI
|
4
6
|
module UI
|
5
7
|
class Progress
|
8
|
+
extend T::Sig
|
9
|
+
|
6
10
|
# A Cyan filled block
|
7
11
|
FILLED_BAR = "\e[46m"
|
8
12
|
# A bright white block
|
9
13
|
UNFILLED_BAR = "\e[1;47m"
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
bar
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
15
|
+
class << self
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
# Add a progress bar to the terminal output
|
19
|
+
#
|
20
|
+
# https://user-images.githubusercontent.com/3074765/33799794-cc4c940e-dd00-11e7-9bdc-90f77ec9167c.gif
|
21
|
+
#
|
22
|
+
# ==== Example Usage:
|
23
|
+
#
|
24
|
+
# Set the percent to X
|
25
|
+
# CLI::UI::Progress.progress do |bar|
|
26
|
+
# bar.tick(set_percent: percent)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Increase the percent by 1 percent
|
30
|
+
# CLI::UI::Progress.progress do |bar|
|
31
|
+
# bar.tick
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# Increase the percent by X
|
35
|
+
# CLI::UI::Progress.progress do |bar|
|
36
|
+
# bar.tick(percent: 0.05)
|
37
|
+
# end
|
38
|
+
sig do
|
39
|
+
type_parameters(:T)
|
40
|
+
.params(width: Integer, block: T.proc.params(bar: Progress).returns(T.type_parameter(:T)))
|
41
|
+
.returns(T.type_parameter(:T))
|
42
|
+
end
|
43
|
+
def progress(width: Terminal.width, &block)
|
44
|
+
bar = Progress.new(width: width)
|
45
|
+
print(CLI::UI::ANSI.hide_cursor)
|
46
|
+
yield(bar)
|
47
|
+
ensure
|
48
|
+
puts bar.to_s
|
49
|
+
CLI::UI.raw do
|
50
|
+
print(ANSI.show_cursor)
|
51
|
+
end
|
39
52
|
end
|
40
53
|
end
|
41
54
|
|
@@ -46,8 +59,9 @@ module CLI
|
|
46
59
|
#
|
47
60
|
# * +:width+ - The width of the terminal
|
48
61
|
#
|
62
|
+
sig { params(width: Integer).void }
|
49
63
|
def initialize(width: Terminal.width)
|
50
|
-
@percent_done = 0
|
64
|
+
@percent_done = T.let(0, Numeric)
|
51
65
|
@max_width = width
|
52
66
|
end
|
53
67
|
|
@@ -61,9 +75,11 @@ module CLI
|
|
61
75
|
#
|
62
76
|
# *Note:* The +:percent+ and +:set_percent must be between 0.00 and 1.0
|
63
77
|
#
|
64
|
-
|
65
|
-
|
66
|
-
|
78
|
+
sig { params(percent: T.nilable(Numeric), set_percent: T.nilable(Numeric)).void }
|
79
|
+
def tick(percent: nil, set_percent: nil)
|
80
|
+
raise ArgumentError, 'percent and set_percent cannot both be specified' if percent && set_percent
|
81
|
+
|
82
|
+
@percent_done += percent || 0.01
|
67
83
|
@percent_done = set_percent if set_percent
|
68
84
|
@percent_done = [@percent_done, 1.0].min # Make sure we can't go above 1.0
|
69
85
|
|
@@ -73,6 +89,7 @@ module CLI
|
|
73
89
|
|
74
90
|
# Format the progress bar to be printed to terminal
|
75
91
|
#
|
92
|
+
sig { returns(String) }
|
76
93
|
def to_s
|
77
94
|
suffix = " #{(@percent_done * 100).floor}%".ljust(5)
|
78
95
|
workable_width = @max_width - Frame.prefix_width - suffix.size
|