cli-ui 1.2.2 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +45 -1
- data/lib/cli/ui.rb +75 -29
- data/lib/cli/ui/ansi.rb +10 -6
- data/lib/cli/ui/color.rb +12 -7
- data/lib/cli/ui/formatter.rb +34 -21
- data/lib/cli/ui/frame.rb +111 -152
- data/lib/cli/ui/frame/frame_stack.rb +98 -0
- data/lib/cli/ui/frame/frame_style.rb +120 -0
- data/lib/cli/ui/frame/frame_style/box.rb +166 -0
- data/lib/cli/ui/frame/frame_style/bracket.rb +139 -0
- data/lib/cli/ui/glyph.rb +23 -17
- data/lib/cli/ui/os.rb +67 -0
- data/lib/cli/ui/printer.rb +59 -0
- data/lib/cli/ui/progress.rb +9 -7
- data/lib/cli/ui/prompt.rb +97 -21
- data/lib/cli/ui/prompt/interactive_options.rb +75 -61
- data/lib/cli/ui/prompt/options_handler.rb +7 -2
- data/lib/cli/ui/spinner.rb +23 -5
- data/lib/cli/ui/spinner/spin_group.rb +34 -12
- data/lib/cli/ui/stdout_router.rb +13 -8
- data/lib/cli/ui/terminal.rb +26 -16
- data/lib/cli/ui/truncater.rb +4 -4
- data/lib/cli/ui/version.rb +1 -1
- data/lib/cli/ui/widgets.rb +77 -0
- data/lib/cli/ui/widgets/base.rb +27 -0
- data/lib/cli/ui/widgets/status.rb +61 -0
- data/lib/cli/ui/wrap.rb +56 -0
- metadata +17 -16
- data/.gitignore +0 -15
- data/.rubocop.yml +0 -17
- data/.travis.yml +0 -5
- data/Gemfile +0 -16
- data/Rakefile +0 -20
- data/bin/console +0 -14
- data/cli-ui.gemspec +0 -27
- data/dev.yml +0 -14
- data/lib/cli/ui/box.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e28f7cb02b8647ee32e91b11f2b25b450c33a245514a520783d50cc6d5f7d84
|
4
|
+
data.tar.gz: 894e2a52591d18c5210efa36beab81d761c18734a42f8362d8af0c97e3bd4ae2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af8bd2b36c0d4c65e93aa966062e040109f5a10347404b112331975f04eb9cfe2a8601a9a9d1d4c1787f92f5a306b81f68e21d1e4f6728705fdfc81dd58fc25c
|
7
|
+
data.tar.gz: a94fa31ff9991a26202f8c4d4d85f87d9efdd23489ed47c5f719b0389a6106be4eee4ee86075fcddafe367d847b8299f69e6fe014fdffce629b0c9df43e156d1
|
data/README.md
CHANGED
@@ -108,13 +108,26 @@ puts CLI::UI.fmt "{{red:Red}} {{green:Green}}"
|
|
108
108
|
e.g. `{{*}}` => a yellow ⭑
|
109
109
|
|
110
110
|
```ruby
|
111
|
-
puts CLI::UI.fmt "{{*}} {{
|
111
|
+
puts CLI::UI.fmt "{{*}} {{v}} {{?}} {{x}}"
|
112
112
|
```
|
113
113
|
|
114
114
|
![Symbol Formatting](https://user-images.githubusercontent.com/3074765/33799847-9ec03fd0-dd01-11e7-93f7-5f5cc540e61e.png)
|
115
115
|
|
116
116
|
---
|
117
117
|
|
118
|
+
### Status Widget
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
CLI::UI::Spinner.spin("building packages: {{@widget/status:1:2:3:4}}") do |spinner|
|
122
|
+
# spinner.update_title(...)
|
123
|
+
sleep(3)
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
![Status Widget](https://user-images.githubusercontent.com/1284/61405142-11042580-a8a7-11e9-9885-46ba44c46358.gif)
|
128
|
+
|
129
|
+
---
|
130
|
+
|
118
131
|
### Progress Bar
|
119
132
|
|
120
133
|
Show progress of a process or operation.
|
@@ -131,6 +144,37 @@ end
|
|
131
144
|
|
132
145
|
---
|
133
146
|
|
147
|
+
### Frame Styles
|
148
|
+
|
149
|
+
Modify the appearance of CLI::UI both globally and on an individual frame level.
|
150
|
+
|
151
|
+
To set the default style:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
CLI::UI.frame_style = :box
|
155
|
+
```
|
156
|
+
|
157
|
+
To style an individual frame:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
CLI::UI.frame('New Style!', frame_style: :bracket) { puts 'It's pretty cool!' }
|
161
|
+
```
|
162
|
+
|
163
|
+
The default style - `:box` - is what has been used up until now. The other style - `:bracket` - looks like this:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
CLI::UI.frame_style = :bracket
|
167
|
+
CLI::UI::StdoutRouter.enable
|
168
|
+
CLI::UI::Frame.open('Frame 1') do
|
169
|
+
CLI::UI::Frame.open('Frame 2') { puts "inside frame 2" }
|
170
|
+
puts "inside frame 1"
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
![Frame Style](https://user-images.githubusercontent.com/315948/65287373-9a82de80-db08-11e9-94fb-20f4b7561c07.png)
|
175
|
+
|
176
|
+
---
|
177
|
+
|
134
178
|
## Example Usage
|
135
179
|
|
136
180
|
The following code makes use of nested-framing, multi-threaded spinners, formatted text, and more.
|
data/lib/cli/ui.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
module CLI
|
2
2
|
module UI
|
3
|
-
autoload :ANSI,
|
4
|
-
autoload :Glyph,
|
5
|
-
autoload :Color,
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
autoload :
|
9
|
-
autoload :
|
10
|
-
autoload :
|
11
|
-
autoload :
|
12
|
-
autoload :
|
13
|
-
autoload :
|
3
|
+
autoload :ANSI, 'cli/ui/ansi'
|
4
|
+
autoload :Glyph, 'cli/ui/glyph'
|
5
|
+
autoload :Color, 'cli/ui/color'
|
6
|
+
autoload :Frame, 'cli/ui/frame'
|
7
|
+
autoload :OS, 'cli/ui/os'
|
8
|
+
autoload :Printer, 'cli/ui/printer'
|
9
|
+
autoload :Progress, 'cli/ui/progress'
|
10
|
+
autoload :Prompt, 'cli/ui/prompt'
|
11
|
+
autoload :Terminal, 'cli/ui/terminal'
|
12
|
+
autoload :Truncater, 'cli/ui/truncater'
|
13
|
+
autoload :Formatter, 'cli/ui/formatter'
|
14
|
+
autoload :Spinner, 'cli/ui/spinner'
|
15
|
+
autoload :Widgets, 'cli/ui/widgets'
|
16
|
+
autoload :Wrap, 'cli/ui/wrap'
|
14
17
|
|
15
18
|
# Convenience accessor to +CLI::UI::Spinner::SpinGroup+
|
16
19
|
SpinGroup = Spinner::SpinGroup
|
@@ -27,7 +30,7 @@ module CLI
|
|
27
30
|
end
|
28
31
|
|
29
32
|
# Color resolution using +CLI::UI::Color.lookup+
|
30
|
-
# Will lookup using +Color.lookup+
|
33
|
+
# Will lookup using +Color.lookup+ unless it's already a CLI::UI::Color (or nil)
|
31
34
|
#
|
32
35
|
# ==== Attributes
|
33
36
|
#
|
@@ -35,14 +38,29 @@ module CLI
|
|
35
38
|
#
|
36
39
|
def self.resolve_color(input)
|
37
40
|
case input
|
38
|
-
when
|
39
|
-
|
41
|
+
when CLI::UI::Color, nil
|
42
|
+
input
|
40
43
|
else
|
44
|
+
CLI::UI::Color.lookup(input)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Frame style resolution using +CLI::UI::Frame::FrameStyle.lookup+.
|
49
|
+
# Will lookup using +FrameStyle.lookup+ unless it's already a CLI::UI::Frame::FrameStyle(or nil)
|
50
|
+
#
|
51
|
+
# ==== Attributes
|
52
|
+
#
|
53
|
+
# * +input+ - frame style to resolve
|
54
|
+
def self.resolve_style(input)
|
55
|
+
case input
|
56
|
+
when CLI::UI::Frame::FrameStyle, nil
|
41
57
|
input
|
58
|
+
else
|
59
|
+
CLI::UI::Frame::FrameStyle.lookup(input)
|
42
60
|
end
|
43
61
|
end
|
44
62
|
|
45
|
-
#
|
63
|
+
# Convenience Method for +CLI::UI::Prompt.confirm+
|
46
64
|
#
|
47
65
|
# ==== Attributes
|
48
66
|
#
|
@@ -52,18 +70,18 @@ module CLI
|
|
52
70
|
CLI::UI::Prompt.confirm(question, **kwargs)
|
53
71
|
end
|
54
72
|
|
55
|
-
#
|
73
|
+
# Convenience Method for +CLI::UI::Prompt.ask+
|
56
74
|
#
|
57
75
|
# ==== Attributes
|
58
76
|
#
|
59
77
|
# * +question+ - question to ask
|
60
|
-
# * +kwargs+ -
|
78
|
+
# * +kwargs+ - arguments for +Prompt.ask+
|
61
79
|
#
|
62
80
|
def self.ask(question, **kwargs)
|
63
81
|
CLI::UI::Prompt.ask(question, **kwargs)
|
64
82
|
end
|
65
83
|
|
66
|
-
#
|
84
|
+
# Convenience Method to resolve text using +CLI::UI::Formatter.format+
|
67
85
|
# Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
|
68
86
|
#
|
69
87
|
# ==== Attributes
|
@@ -75,10 +93,10 @@ module CLI
|
|
75
93
|
return input if input.nil?
|
76
94
|
formatted = CLI::UI::Formatter.new(input).format
|
77
95
|
return formatted unless truncate_to
|
78
|
-
|
96
|
+
CLI::UI::Truncater.call(formatted, truncate_to)
|
79
97
|
end
|
80
98
|
|
81
|
-
#
|
99
|
+
# Convenience Method to format text using +CLI::UI::Formatter.format+
|
82
100
|
# Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
|
83
101
|
#
|
84
102
|
# https://user-images.githubusercontent.com/3074765/33799827-6d0721a2-dd01-11e7-9ab5-c3d455264afe.png
|
@@ -96,29 +114,44 @@ module CLI
|
|
96
114
|
CLI::UI::Formatter.new(input).format(enable_color: enable_color)
|
97
115
|
end
|
98
116
|
|
99
|
-
|
117
|
+
def self.wrap(input)
|
118
|
+
CLI::UI::Wrap.new(input).wrap
|
119
|
+
end
|
120
|
+
|
121
|
+
# Convenience Method for +CLI::UI::Printer.puts+
|
122
|
+
#
|
123
|
+
# ==== Attributes
|
124
|
+
#
|
125
|
+
# * +msg+ - Message to print
|
126
|
+
# * +kwargs+ - keyword arguments for +Printer.puts+
|
127
|
+
#
|
128
|
+
def self.puts(msg, **kwargs)
|
129
|
+
CLI::UI::Printer.puts(msg, **kwargs)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Convenience Method for +CLI::UI::Frame.open+
|
100
133
|
#
|
101
134
|
# ==== Attributes
|
102
135
|
#
|
103
136
|
# * +args+ - arguments for +Frame.open+
|
104
137
|
# * +block+ - block for +Frame.open+
|
105
138
|
#
|
106
|
-
def self.frame(*args, &block)
|
107
|
-
CLI::UI::Frame.open(*args, &block)
|
139
|
+
def self.frame(*args, **kwargs, &block)
|
140
|
+
CLI::UI::Frame.open(*args, **kwargs, &block)
|
108
141
|
end
|
109
142
|
|
110
|
-
#
|
143
|
+
# Convenience Method for +CLI::UI::Spinner.spin+
|
111
144
|
#
|
112
145
|
# ==== Attributes
|
113
146
|
#
|
114
147
|
# * +args+ - arguments for +Spinner.open+
|
115
148
|
# * +block+ - block for +Spinner.open+
|
116
149
|
#
|
117
|
-
def self.spinner(*args, &block)
|
118
|
-
CLI::UI::Spinner.spin(*args, &block)
|
150
|
+
def self.spinner(*args, **kwargs, &block)
|
151
|
+
CLI::UI::Spinner.spin(*args, **kwargs, &block)
|
119
152
|
end
|
120
153
|
|
121
|
-
#
|
154
|
+
# Convenience Method to override frame color using +CLI::UI::Frame.with_frame_color+
|
122
155
|
#
|
123
156
|
# ==== Attributes
|
124
157
|
#
|
@@ -137,12 +170,12 @@ module CLI
|
|
137
170
|
#
|
138
171
|
def self.log_output_to(path)
|
139
172
|
if CLI::UI::StdoutRouter.duplicate_output_to
|
140
|
-
raise
|
173
|
+
raise 'multiple logs not allowed'
|
141
174
|
end
|
142
175
|
CLI::UI::StdoutRouter.duplicate_output_to = File.open(path, 'w')
|
143
176
|
yield
|
144
177
|
ensure
|
145
|
-
if file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to
|
178
|
+
if (file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to)
|
146
179
|
file_descriptor.close
|
147
180
|
CLI::UI::StdoutRouter.duplicate_output_to = nil
|
148
181
|
end
|
@@ -181,6 +214,19 @@ module CLI
|
|
181
214
|
end
|
182
215
|
|
183
216
|
self.enable_color = $stdout.tty?
|
217
|
+
|
218
|
+
# Set the default frame style.
|
219
|
+
# Convenience method for setting the default frame style with +CLI::UI::Frame.frame_style=+
|
220
|
+
#
|
221
|
+
# Raises ArgumentError if +frame_style+ is not valid
|
222
|
+
#
|
223
|
+
# ==== Attributes
|
224
|
+
#
|
225
|
+
# * +symbol+ - the default frame style to use for frames
|
226
|
+
#
|
227
|
+
def self.frame_style=(frame_style)
|
228
|
+
Frame.frame_style = frame_style.to_sym
|
229
|
+
end
|
184
230
|
end
|
185
231
|
end
|
186
232
|
|
data/lib/cli/ui/ansi.rb
CHANGED
@@ -21,6 +21,8 @@ module CLI
|
|
21
21
|
when 0x200d # zero-width joiner
|
22
22
|
zwj = true
|
23
23
|
acc
|
24
|
+
when "\n"
|
25
|
+
acc
|
24
26
|
else
|
25
27
|
acc + 1
|
26
28
|
end
|
@@ -45,7 +47,7 @@ module CLI
|
|
45
47
|
# - +cmd+ - ANSI control sequence Command
|
46
48
|
#
|
47
49
|
def self.control(args, cmd)
|
48
|
-
ESC +
|
50
|
+
ESC + '[' + args + cmd
|
49
51
|
end
|
50
52
|
|
51
53
|
# https://en.wikipedia.org/wiki/ANSI_escape_code#graphics
|
@@ -106,19 +108,21 @@ module CLI
|
|
106
108
|
# * +n+ - The column to move to
|
107
109
|
#
|
108
110
|
def self.cursor_horizontal_absolute(n = 1)
|
109
|
-
control(n.to_s, 'G')
|
111
|
+
cmd = control(n.to_s, 'G')
|
112
|
+
cmd += control('1', 'D') if CLI::UI::OS.current.shift_cursor_on_line_reset?
|
113
|
+
cmd
|
110
114
|
end
|
111
115
|
|
112
116
|
# Show the cursor
|
113
117
|
#
|
114
118
|
def self.show_cursor
|
115
|
-
control('',
|
119
|
+
control('', '?25h')
|
116
120
|
end
|
117
121
|
|
118
122
|
# Hide the cursor
|
119
123
|
#
|
120
124
|
def self.hide_cursor
|
121
|
-
control('',
|
125
|
+
control('', '?25l')
|
122
126
|
end
|
123
127
|
|
124
128
|
# Save the cursor position
|
@@ -136,13 +140,13 @@ module CLI
|
|
136
140
|
# Move to the next line
|
137
141
|
#
|
138
142
|
def self.next_line
|
139
|
-
cursor_down +
|
143
|
+
cursor_down + cursor_horizontal_absolute
|
140
144
|
end
|
141
145
|
|
142
146
|
# Move to the previous line
|
143
147
|
#
|
144
148
|
def self.previous_line
|
145
|
-
cursor_up +
|
149
|
+
cursor_up + cursor_horizontal_absolute
|
146
150
|
end
|
147
151
|
|
148
152
|
def self.clear_to_end_of_line
|
data/lib/cli/ui/color.rb
CHANGED
@@ -31,19 +31,24 @@ module CLI
|
|
31
31
|
BOLD = new('1', :bold)
|
32
32
|
WHITE = new('97', :white)
|
33
33
|
|
34
|
+
# 240 is very dark gray; 255 is very light gray. 244 is somewhat dark.
|
35
|
+
GRAY = new('38;5;244', :grey)
|
36
|
+
|
34
37
|
MAP = {
|
35
|
-
red:
|
36
|
-
green:
|
37
|
-
yellow:
|
38
|
-
blue:
|
38
|
+
red: RED,
|
39
|
+
green: GREEN,
|
40
|
+
yellow: YELLOW,
|
41
|
+
blue: BLUE,
|
39
42
|
magenta: MAGENTA,
|
40
|
-
cyan:
|
41
|
-
reset:
|
42
|
-
bold:
|
43
|
+
cyan: CYAN,
|
44
|
+
reset: RESET,
|
45
|
+
bold: BOLD,
|
46
|
+
gray: GRAY,
|
43
47
|
}.freeze
|
44
48
|
|
45
49
|
class InvalidColorName < ArgumentError
|
46
50
|
def initialize(name)
|
51
|
+
super
|
47
52
|
@name = name
|
48
53
|
end
|
49
54
|
|
data/lib/cli/ui/formatter.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require 'strscan'
|
2
|
+
require('cli/ui')
|
3
|
+
require('strscan')
|
5
4
|
|
6
5
|
module CLI
|
7
6
|
module UI
|
@@ -13,39 +12,40 @@ module CLI
|
|
13
12
|
#
|
14
13
|
SGR_MAP = {
|
15
14
|
# presentational
|
16
|
-
'red'
|
17
|
-
'green'
|
18
|
-
'yellow'
|
19
|
-
|
20
|
-
'blue'
|
21
|
-
'magenta'
|
22
|
-
'cyan'
|
23
|
-
'bold'
|
24
|
-
'italic'
|
15
|
+
'red' => '31',
|
16
|
+
'green' => '32',
|
17
|
+
'yellow' => '33',
|
18
|
+
# default blue is low-contrast against black in some default terminal color scheme
|
19
|
+
'blue' => '94', # 9x = high-intensity fg color x
|
20
|
+
'magenta' => '35',
|
21
|
+
'cyan' => '36',
|
22
|
+
'bold' => '1',
|
23
|
+
'italic' => '3',
|
25
24
|
'underline' => '4',
|
26
|
-
'reset'
|
25
|
+
'reset' => '0',
|
27
26
|
|
28
27
|
# semantic
|
29
|
-
'error'
|
28
|
+
'error' => '31', # red
|
30
29
|
'success' => '32', # success
|
31
30
|
'warning' => '33', # yellow
|
32
|
-
'info'
|
31
|
+
'info' => '94', # bright blue
|
33
32
|
'command' => '36', # cyan
|
34
33
|
}.freeze
|
35
34
|
|
36
35
|
BEGIN_EXPR = '{{'
|
37
36
|
END_EXPR = '}}'
|
38
37
|
|
38
|
+
SCAN_WIDGET = %r[@widget/(?<handle>\w+):(?<args>.*?)}}]
|
39
39
|
SCAN_FUNCNAME = /\w+:/
|
40
40
|
SCAN_GLYPH = /.}}/
|
41
|
-
SCAN_BODY =
|
41
|
+
SCAN_BODY = %r{
|
42
42
|
.*?
|
43
43
|
(
|
44
44
|
#{BEGIN_EXPR} |
|
45
45
|
#{END_EXPR} |
|
46
46
|
\z
|
47
47
|
)
|
48
|
-
|
48
|
+
}mx
|
49
49
|
|
50
50
|
DISCARD_BRACES = 0..-3
|
51
51
|
|
@@ -123,7 +123,7 @@ module CLI
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def parse_expr(sc, stack)
|
126
|
-
if match = sc.scan(SCAN_GLYPH)
|
126
|
+
if (match = sc.scan(SCAN_GLYPH))
|
127
127
|
glyph_handle = match[0]
|
128
128
|
begin
|
129
129
|
glyph = Glyph.lookup(glyph_handle)
|
@@ -136,7 +136,20 @@ module CLI
|
|
136
136
|
index
|
137
137
|
)
|
138
138
|
end
|
139
|
-
elsif match = sc.scan(
|
139
|
+
elsif (match = sc.scan(SCAN_WIDGET))
|
140
|
+
match_data = SCAN_WIDGET.match(match) # Regexp.last_match doesn't work here
|
141
|
+
widget_handle = match_data['handle']
|
142
|
+
begin
|
143
|
+
widget = Widgets.lookup(widget_handle)
|
144
|
+
emit(widget.call(match_data['args']), stack)
|
145
|
+
rescue Widgets::InvalidWidgetHandle
|
146
|
+
index = sc.pos - 2 # rewind past '}}'
|
147
|
+
raise(FormatError.new(
|
148
|
+
"invalid widget handle at index #{index}: '#{widget_handle}'",
|
149
|
+
@text, index,
|
150
|
+
))
|
151
|
+
end
|
152
|
+
elsif (match = sc.scan(SCAN_FUNCNAME))
|
140
153
|
funcname = match.chop
|
141
154
|
stack.push(funcname)
|
142
155
|
else
|
@@ -153,10 +166,10 @@ module CLI
|
|
153
166
|
|
154
167
|
def parse_body(sc, stack = [])
|
155
168
|
match = sc.scan(SCAN_BODY)
|
156
|
-
if match
|
169
|
+
if match&.end_with?(BEGIN_EXPR)
|
157
170
|
emit(match[DISCARD_BRACES], stack)
|
158
171
|
parse_expr(sc, stack)
|
159
|
-
elsif match
|
172
|
+
elsif match&.end_with?(END_EXPR)
|
160
173
|
emit(match[DISCARD_BRACES], stack)
|
161
174
|
if stack.pop == LITERAL_BRACES
|
162
175
|
emit('}}', stack)
|