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.
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
@@ -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
- # Add a progress bar to the terminal output
12
- #
13
- # https://user-images.githubusercontent.com/3074765/33799794-cc4c940e-dd00-11e7-9bdc-90f77ec9167c.gif
14
- #
15
- # ==== Example Usage:
16
- #
17
- # Set the percent to X
18
- # CLI::UI::Progress.progress do |bar|
19
- # bar.tick(set_percent: percent)
20
- # end
21
- #
22
- # Increase the percent by 1 percent
23
- # CLI::UI::Progress.progress do |bar|
24
- # bar.tick
25
- # end
26
- #
27
- # Increase the percent by X
28
- # CLI::UI::Progress.progress do |bar|
29
- # bar.tick(percent: 0.05)
30
- # end
31
- def self.progress(width: Terminal.width)
32
- bar = Progress.new(width: width)
33
- print(CLI::UI::ANSI.hide_cursor)
34
- yield(bar)
35
- ensure
36
- puts bar.to_s
37
- CLI::UI.raw do
38
- print(ANSI.show_cursor)
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
- def tick(percent: 0.01, set_percent: nil)
65
- raise ArgumentError, 'percent and set_percent cannot both be specified' if percent != 0.01 && set_percent
66
- @percent_done += percent
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