cli-ui 1.2.2 → 1.5.1
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 +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
|

|
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
|
+

|
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
|
+

|
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)
|