cli-ui 1.5.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e28f7cb02b8647ee32e91b11f2b25b450c33a245514a520783d50cc6d5f7d84
4
- data.tar.gz: 894e2a52591d18c5210efa36beab81d761c18734a42f8362d8af0c97e3bd4ae2
3
+ metadata.gz: eb23f8ab0044a484b7f6f8dcb57240d5b5ba9a3db6caedb6090186c3d7a55143
4
+ data.tar.gz: 9c3e0d024f05ed428a0afa190fbc440008e58d8f58cf244c452259722883dd50
5
5
  SHA512:
6
- metadata.gz: af8bd2b36c0d4c65e93aa966062e040109f5a10347404b112331975f04eb9cfe2a8601a9a9d1d4c1787f92f5a306b81f68e21d1e4f6728705fdfc81dd58fc25c
7
- data.tar.gz: a94fa31ff9991a26202f8c4d4d85f87d9efdd23489ed47c5f719b0389a6106be4eee4ee86075fcddafe367d847b8299f69e6fe014fdffce629b0c9df43e156d1
6
+ metadata.gz: 36dccad36f8cc8eeff6bc9ad2e7deb2e37427ccf5887fb543d27485ff5cfc8e441355edc9cb92f954eef9e1a45fb5d2d78e8b84b510dec12809e3e1df2071cea
7
+ data.tar.gz: 31c99ff115d4061d4f9441063177784f9f6656cafee9bd6b85fc6d48e30ab855e4cf83138d8f305fb86542edd4fbe2226db26cf448e70b3b8677bf18ce4a388c
data/README.md CHANGED
@@ -3,7 +3,7 @@ CLI UI
3
3
 
4
4
  CLI UI is a small framework for generating nice command-line user interfaces
5
5
 
6
- - [Master Documentation](http://www.rubydoc.info/github/Shopify/cli-ui/master/CLI/UI)
6
+ - [Master Documentation](http://www.rubydoc.info/github/Shopify/cli-ui/main/CLI/UI)
7
7
  - [Documentation of the Rubygems version](http://www.rubydoc.info/gems/cli-ui/)
8
8
  - [Rubygems](https://rubygems.org/gems/cli-ui)
9
9
 
@@ -23,7 +23,7 @@ In your code, simply add a `require 'cli/ui'`. Most options assume `CLI::UI::Std
23
23
 
24
24
  ## Features
25
25
 
26
- This may not be an exhaustive list. Please check our [documentation](http://www.rubydoc.info/github/Shopify/cli-ui/master/CLI/UI) for more information.
26
+ This may not be an exhaustive list. Please check our [documentation](http://www.rubydoc.info/github/Shopify/cli-ui/main/CLI/UI) for more information.
27
27
 
28
28
  ---
29
29
 
@@ -83,10 +83,10 @@ CLI::UI.ask('Is CLI UI Awesome?', default: 'It is great!')
83
83
  Handle many multi-threaded processes while suppressing output unless there is an issue. Can update title to show state.
84
84
 
85
85
  ```ruby
86
- spin_group = CLI::UI::SpinGroup.new
87
- spin_group.add('Title') { |spinner| sleep 3.0 }
88
- spin_group.add('Title 2') { |spinner| sleep 3.0; spinner.update_title('New Title'); sleep 3.0 }
89
- spin_group.wait
86
+ CLI::UI::SpinGroup.new do |spin_group|
87
+ spin_group.add('Title') { |spinner| sleep 3.0 }
88
+ spin_group.add('Title 2') { |spinner| sleep 3.0; spinner.update_title('New Title'); sleep 3.0 }
89
+ end
90
90
  ```
91
91
 
92
92
  ![Spinner Group](https://user-images.githubusercontent.com/3074765/33798295-d94fd822-dce3-11e7-819b-43e5502d490e.gif)
@@ -175,6 +175,12 @@ end
175
175
 
176
176
  ---
177
177
 
178
+ ## Sorbet
179
+
180
+ We make use of [Sorbet](https://sorbet.org/) in cli-ui. We provide stubs for Sorbet so that you can use this gem even
181
+ if you aren't using Sorbet. We activate these stubs if `T` is undefined when the gem is loaded. For this reason, if you
182
+ would like to use this gem and your project _does_ use Sorbet, ensure you load Sorbet _before_ loading cli-ui.
183
+
178
184
  ## Example Usage
179
185
 
180
186
  The following code makes use of nested-framing, multi-threaded spinners, formatted text, and more.
@@ -187,22 +193,22 @@ CLI::UI::StdoutRouter.enable
187
193
  CLI::UI::Frame.open('{{*}} {{bold:a}}', color: :green) do
188
194
  CLI::UI::Frame.open('{{i}} b', color: :magenta) do
189
195
  CLI::UI::Frame.open('{{?}} c', color: :cyan) do
190
- sg = CLI::UI::SpinGroup.new
191
- sg.add('wow') do |spinner|
192
- sleep(2.5)
193
- spinner.update_title('second round!')
194
- sleep (1.0)
196
+ CLI::UI::SpinGroup.new do |sg|
197
+ sg.add('wow') do |spinner|
198
+ sleep(2.5)
199
+ spinner.update_title('second round!')
200
+ sleep (1.0)
201
+ end
202
+ sg.add('such spin') { sleep(1.6) }
203
+ sg.add('many glyph') { sleep(2.0) }
195
204
  end
196
- sg.add('such spin') { sleep(1.6) }
197
- sg.add('many glyph') { sleep(2.0) }
198
- sg.wait
199
205
  end
200
206
  end
201
207
  CLI::UI::Frame.divider('{{v}} lol')
202
208
  puts CLI::UI.fmt '{{info:words}} {{red:oh no!}} {{green:success!}}'
203
- sg = CLI::UI::SpinGroup.new
204
- sg.add('more spins') { sleep(0.5) ; raise 'oh no' }
205
- sg.wait
209
+ CLI::UI::SpinGroup.new do |sg|
210
+ sg.add('more spins') { sleep(0.5) ; raise 'oh no' }
211
+ end
206
212
  end
207
213
  ```
208
214
 
data/lib/cli/ui/ansi.rb CHANGED
@@ -1,156 +1,184 @@
1
+ # typed: true
2
+
1
3
  require 'cli/ui'
2
4
 
3
5
  module CLI
4
6
  module UI
5
7
  module ANSI
8
+ extend T::Sig
9
+
6
10
  ESC = "\x1b"
7
11
 
8
- # ANSI escape sequences (like \x1b[31m) have zero width.
9
- # when calculating the padding width, we must exclude them.
10
- # This also implements a basic version of utf8 character width calculation like
11
- # we could get for real from something like utf8proc.
12
- #
13
- def self.printing_width(str)
14
- zwj = false
15
- strip_codes(str).codepoints.reduce(0) do |acc, cp|
16
- if zwj
17
- zwj = false
18
- next acc
19
- end
20
- case cp
21
- when 0x200d # zero-width joiner
22
- zwj = true
23
- acc
24
- when "\n"
25
- acc
26
- else
27
- acc + 1
12
+ class << self
13
+ extend T::Sig
14
+
15
+ # ANSI escape sequences (like \x1b[31m) have zero width.
16
+ # when calculating the padding width, we must exclude them.
17
+ # This also implements a basic version of utf8 character width calculation like
18
+ # we could get for real from something like utf8proc.
19
+ #
20
+ sig { params(str: String).returns(Integer) }
21
+ def printing_width(str)
22
+ zwj = T.let(false, T::Boolean)
23
+ strip_codes(str).codepoints.reduce(0) do |acc, cp|
24
+ if zwj
25
+ zwj = false
26
+ next acc
27
+ end
28
+ case cp
29
+ when 0x200d # zero-width joiner
30
+ zwj = true
31
+ acc
32
+ when "\n"
33
+ acc
34
+ else
35
+ acc + 1
36
+ end
28
37
  end
29
38
  end
30
- end
31
39
 
32
- # Strips ANSI codes from a str
33
- #
34
- # ==== Attributes
35
- #
36
- # - +str+ - The string from which to strip codes
37
- #
38
- def self.strip_codes(str)
39
- str.gsub(/\x1b\[[\d;]+[A-z]|\r/, '')
40
- end
40
+ # Strips ANSI codes from a str
41
+ #
42
+ # ==== Attributes
43
+ #
44
+ # - +str+ - The string from which to strip codes
45
+ #
46
+ sig { params(str: String).returns(String) }
47
+ def strip_codes(str)
48
+ str.gsub(/\x1b\[[\d;]+[A-z]|\r/, '')
49
+ end
41
50
 
42
- # Returns an ANSI control sequence
43
- #
44
- # ==== Attributes
45
- #
46
- # - +args+ - Argument to pass to the ANSI control sequence
47
- # - +cmd+ - ANSI control sequence Command
48
- #
49
- def self.control(args, cmd)
50
- ESC + '[' + args + cmd
51
- end
51
+ # Returns an ANSI control sequence
52
+ #
53
+ # ==== Attributes
54
+ #
55
+ # - +args+ - Argument to pass to the ANSI control sequence
56
+ # - +cmd+ - ANSI control sequence Command
57
+ #
58
+ sig { params(args: String, cmd: String).returns(String) }
59
+ def control(args, cmd)
60
+ ESC + '[' + args + cmd
61
+ end
52
62
 
53
- # https://en.wikipedia.org/wiki/ANSI_escape_code#graphics
54
- def self.sgr(params)
55
- control(params.to_s, 'm')
56
- end
63
+ # https://en.wikipedia.org/wiki/ANSI_escape_code#graphics
64
+ sig { params(params: String).returns(String) }
65
+ def sgr(params)
66
+ control(params, 'm')
67
+ end
57
68
 
58
- # Cursor Movement
59
-
60
- # Move the cursor up n lines
61
- #
62
- # ==== Attributes
63
- #
64
- # * +n+ - number of lines by which to move the cursor up
65
- #
66
- def self.cursor_up(n = 1)
67
- return '' if n.zero?
68
- control(n.to_s, 'A')
69
- end
69
+ # Cursor Movement
70
70
 
71
- # Move the cursor down n lines
72
- #
73
- # ==== Attributes
74
- #
75
- # * +n+ - number of lines by which to move the cursor down
76
- #
77
- def self.cursor_down(n = 1)
78
- return '' if n.zero?
79
- control(n.to_s, 'B')
80
- end
71
+ # Move the cursor up n lines
72
+ #
73
+ # ==== Attributes
74
+ #
75
+ # * +n+ - number of lines by which to move the cursor up
76
+ #
77
+ sig { params(n: Integer).returns(String) }
78
+ def cursor_up(n = 1)
79
+ return '' if n.zero?
81
80
 
82
- # Move the cursor forward n columns
83
- #
84
- # ==== Attributes
85
- #
86
- # * +n+ - number of columns by which to move the cursor forward
87
- #
88
- def self.cursor_forward(n = 1)
89
- return '' if n.zero?
90
- control(n.to_s, 'C')
91
- end
81
+ control(n.to_s, 'A')
82
+ end
92
83
 
93
- # Move the cursor back n columns
94
- #
95
- # ==== Attributes
96
- #
97
- # * +n+ - number of columns by which to move the cursor back
98
- #
99
- def self.cursor_back(n = 1)
100
- return '' if n.zero?
101
- control(n.to_s, 'D')
102
- end
84
+ # Move the cursor down n lines
85
+ #
86
+ # ==== Attributes
87
+ #
88
+ # * +n+ - number of lines by which to move the cursor down
89
+ #
90
+ sig { params(n: Integer).returns(String) }
91
+ def cursor_down(n = 1)
92
+ return '' if n.zero?
93
+
94
+ control(n.to_s, 'B')
95
+ end
103
96
 
104
- # Move the cursor to a specific column
105
- #
106
- # ==== Attributes
107
- #
108
- # * +n+ - The column to move to
109
- #
110
- def self.cursor_horizontal_absolute(n = 1)
111
- cmd = control(n.to_s, 'G')
112
- cmd += control('1', 'D') if CLI::UI::OS.current.shift_cursor_on_line_reset?
113
- cmd
114
- end
97
+ # Move the cursor forward n columns
98
+ #
99
+ # ==== Attributes
100
+ #
101
+ # * +n+ - number of columns by which to move the cursor forward
102
+ #
103
+ sig { params(n: Integer).returns(String) }
104
+ def cursor_forward(n = 1)
105
+ return '' if n.zero?
106
+
107
+ control(n.to_s, 'C')
108
+ end
115
109
 
116
- # Show the cursor
117
- #
118
- def self.show_cursor
119
- control('', '?25h')
120
- end
110
+ # Move the cursor back n columns
111
+ #
112
+ # ==== Attributes
113
+ #
114
+ # * +n+ - number of columns by which to move the cursor back
115
+ #
116
+ sig { params(n: Integer).returns(String) }
117
+ def cursor_back(n = 1)
118
+ return '' if n.zero?
119
+
120
+ control(n.to_s, 'D')
121
+ end
121
122
 
122
- # Hide the cursor
123
- #
124
- def self.hide_cursor
125
- control('', '?25l')
126
- end
123
+ # Move the cursor to a specific column
124
+ #
125
+ # ==== Attributes
126
+ #
127
+ # * +n+ - The column to move to
128
+ #
129
+ sig { params(n: Integer).returns(String) }
130
+ def cursor_horizontal_absolute(n = 1)
131
+ cmd = control(n.to_s, 'G')
132
+ cmd += cursor_back if CLI::UI::OS.current.shift_cursor_back_on_horizontal_absolute?
133
+ cmd
134
+ end
127
135
 
128
- # Save the cursor position
129
- #
130
- def self.cursor_save
131
- control('', 's')
132
- end
136
+ # Show the cursor
137
+ #
138
+ sig { returns(String) }
139
+ def show_cursor
140
+ control('', '?25h')
141
+ end
133
142
 
134
- # Restore the saved cursor position
135
- #
136
- def self.cursor_restore
137
- control('', 'u')
138
- end
143
+ # Hide the cursor
144
+ #
145
+ sig { returns(String) }
146
+ def hide_cursor
147
+ control('', '?25l')
148
+ end
139
149
 
140
- # Move to the next line
141
- #
142
- def self.next_line
143
- cursor_down + cursor_horizontal_absolute
144
- end
150
+ # Save the cursor position
151
+ #
152
+ sig { returns(String) }
153
+ def cursor_save
154
+ control('', 's')
155
+ end
145
156
 
146
- # Move to the previous line
147
- #
148
- def self.previous_line
149
- cursor_up + cursor_horizontal_absolute
150
- end
157
+ # Restore the saved cursor position
158
+ #
159
+ sig { returns(String) }
160
+ def cursor_restore
161
+ control('', 'u')
162
+ end
163
+
164
+ # Move to the next line
165
+ #
166
+ sig { returns(String) }
167
+ def next_line
168
+ cursor_down + cursor_horizontal_absolute
169
+ end
170
+
171
+ # Move to the previous line
172
+ #
173
+ sig { returns(String) }
174
+ def previous_line
175
+ cursor_up + cursor_horizontal_absolute
176
+ end
151
177
 
152
- def self.clear_to_end_of_line
153
- control('', 'K')
178
+ sig { returns(String) }
179
+ def clear_to_end_of_line
180
+ control('', 'K')
181
+ end
154
182
  end
155
183
  end
156
184
  end
data/lib/cli/ui/color.rb CHANGED
@@ -1,9 +1,17 @@
1
+ # typed: true
2
+
1
3
  require 'cli/ui'
2
4
 
3
5
  module CLI
4
6
  module UI
5
7
  class Color
6
- attr_reader :sgr, :name, :code
8
+ extend T::Sig
9
+
10
+ sig { returns(String) }
11
+ attr_reader :sgr, :code
12
+
13
+ sig { returns(Symbol) }
14
+ attr_reader :name
7
15
 
8
16
  # Creates a new color mapping
9
17
  # Signatures can be found here:
@@ -14,6 +22,7 @@ module CLI
14
22
  # * +sgr+ - The color signature
15
23
  # * +name+ - The name of the color
16
24
  #
25
+ sig { params(sgr: String, name: Symbol).void }
17
26
  def initialize(sgr, name)
18
27
  @sgr = sgr
19
28
  @code = CLI::UI::ANSI.sgr(sgr)
@@ -32,7 +41,7 @@ module CLI
32
41
  WHITE = new('97', :white)
33
42
 
34
43
  # 240 is very dark gray; 255 is very light gray. 244 is somewhat dark.
35
- GRAY = new('38;5;244', :grey)
44
+ GRAY = new('38;5;244', :gray)
36
45
 
37
46
  MAP = {
38
47
  red: RED,
@@ -47,11 +56,15 @@ module CLI
47
56
  }.freeze
48
57
 
49
58
  class InvalidColorName < ArgumentError
59
+ extend T::Sig
60
+
61
+ sig { params(name: Symbol).void }
50
62
  def initialize(name)
51
63
  super
52
64
  @name = name
53
65
  end
54
66
 
67
+ sig { returns(String) }
55
68
  def message
56
69
  keys = Color.available.map(&:inspect).join(',')
57
70
  "invalid color: #{@name.inspect} " \
@@ -59,25 +72,31 @@ module CLI
59
72
  end
60
73
  end
61
74
 
62
- # Looks up a color code by name
63
- #
64
- # ==== Raises
65
- # Raises a InvalidColorName if the color is not available
66
- # You likely need to add it to the +MAP+ or you made a typo
67
- #
68
- # ==== Returns
69
- # Returns a color code
70
- #
71
- def self.lookup(name)
72
- MAP.fetch(name)
73
- rescue KeyError
74
- raise InvalidColorName, name
75
- end
75
+ class << self
76
+ extend T::Sig
76
77
 
77
- # All available colors by name
78
- #
79
- def self.available
80
- MAP.keys
78
+ # Looks up a color code by name
79
+ #
80
+ # ==== Raises
81
+ # Raises a InvalidColorName if the color is not available
82
+ # You likely need to add it to the +MAP+ or you made a typo
83
+ #
84
+ # ==== Returns
85
+ # Returns a color code
86
+ #
87
+ sig { params(name: T.any(Symbol, String)).returns(Color) }
88
+ def lookup(name)
89
+ MAP.fetch(name.to_sym)
90
+ rescue KeyError
91
+ raise InvalidColorName, name
92
+ end
93
+
94
+ # All available colors by name
95
+ #
96
+ sig { returns(T::Array[Symbol]) }
97
+ def available
98
+ MAP.keys
99
+ end
81
100
  end
82
101
  end
83
102
  end