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