cli-ui 1.2.3 → 1.3.0
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/.github/CODEOWNERS +1 -0
- data/README.md +13 -0
- data/lib/cli/ui.rb +1 -0
- data/lib/cli/ui/color.rb +4 -0
- data/lib/cli/ui/formatter.rb +21 -8
- data/lib/cli/ui/frame.rb +20 -18
- data/lib/cli/ui/glyph.rb +9 -15
- data/lib/cli/ui/prompt.rb +27 -0
- data/lib/cli/ui/prompt/interactive_options.rb +2 -0
- data/lib/cli/ui/spinner.rb +23 -3
- data/lib/cli/ui/spinner/spin_group.rb +19 -3
- data/lib/cli/ui/version.rb +1 -1
- data/lib/cli/ui/widgets.rb +75 -0
- data/lib/cli/ui/widgets/base.rb +27 -0
- data/lib/cli/ui/widgets/status.rb +61 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48913d44cb2cdae4f699a6f456cce15ca8b50b450317e4734f4bcd8062982d39
|
4
|
+
data.tar.gz: ae1d337eb7b280001153a25a5e92ee9b2e534b47d5e95c79a2b9a20371bdadf0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e87d0b821c5911d1e10680619522b5f36a19213042e187cc54097850dd2ccb986db4a6dbf800234c43102377d168d6a5dda573f577763dff754e4cee58fc50b
|
7
|
+
data.tar.gz: 2cfb1d76a7b6f49f1f14eadf7237c644a2c2f1896dfff8dcaff9c5b68d354194df0da34339817d8153ef6815ff2898d15b6bb7bd6bb68198bdc614505072aaf3
|
data/.github/CODEOWNERS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* @Shopify/dev-infra
|
data/README.md
CHANGED
@@ -115,6 +115,19 @@ puts CLI::UI.fmt "{{*}} {{x}} {{?}} {{v}}"
|
|
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.
|
data/lib/cli/ui.rb
CHANGED
@@ -11,6 +11,7 @@ module CLI
|
|
11
11
|
autoload :Truncater, 'cli/ui/truncater'
|
12
12
|
autoload :Formatter, 'cli/ui/formatter'
|
13
13
|
autoload :Spinner, 'cli/ui/spinner'
|
14
|
+
autoload :Widgets, 'cli/ui/widgets'
|
14
15
|
|
15
16
|
# Convenience accessor to +CLI::UI::Spinner::SpinGroup+
|
16
17
|
SpinGroup = Spinner::SpinGroup
|
data/lib/cli/ui/color.rb
CHANGED
@@ -31,6 +31,9 @@ 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
38
|
red: RED,
|
36
39
|
green: GREEN,
|
@@ -40,6 +43,7 @@ module CLI
|
|
40
43
|
cyan: CYAN,
|
41
44
|
reset: RESET,
|
42
45
|
bold: BOLD,
|
46
|
+
gray: GRAY,
|
43
47
|
}.freeze
|
44
48
|
|
45
49
|
class InvalidColorName < ArgumentError
|
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
|
@@ -16,7 +15,7 @@ module CLI
|
|
16
15
|
'red' => '31',
|
17
16
|
'green' => '32',
|
18
17
|
'yellow' => '33',
|
19
|
-
|
18
|
+
# default blue is low-contrast against black in some default terminal color scheme
|
20
19
|
'blue' => '94', # 9x = high-intensity fg color x
|
21
20
|
'magenta' => '35',
|
22
21
|
'cyan' => '36',
|
@@ -36,16 +35,17 @@ module CLI
|
|
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
|
data/lib/cli/ui/frame.rb
CHANGED
@@ -249,26 +249,32 @@ module CLI
|
|
249
249
|
|
250
250
|
o = +''
|
251
251
|
|
252
|
-
|
252
|
+
# Shopify's CI system supports terminal emulation, but not some of
|
253
|
+
# the fancier features that we normally use to draw frames
|
254
|
+
# extra-reliably, so we fall back to a less foolproof strategy. This
|
255
|
+
# is probably better in general for cases with impoverished terminal
|
256
|
+
# emulators and no active user.
|
257
|
+
if (is_ci = ![0, '', nil].include?(ENV['CI']))
|
258
|
+
linewidth = [0, termwidth - (prefix_width + suffix_width)].max
|
259
|
+
|
260
|
+
o << color.code << prefix
|
261
|
+
o << color.code << (CLI::UI::Box::Heavy::HORZ * linewidth)
|
262
|
+
o << color.code << suffix
|
263
|
+
o << CLI::UI::Color::RESET.code << "\n"
|
264
|
+
return o
|
265
|
+
end
|
253
266
|
|
254
267
|
# Jumping around the line can cause some unwanted flashes
|
255
268
|
o << CLI::UI::ANSI.hide_cursor
|
256
269
|
|
257
|
-
|
258
|
-
|
259
|
-
# So we move around the line by offset from this cursor position.
|
260
|
-
CLI::UI::ANSI.cursor_save
|
261
|
-
else
|
262
|
-
# Outside of CI, we reset to column 1 so that things like ^C don't
|
263
|
-
# cause output misformatting.
|
264
|
-
"\r"
|
265
|
-
end
|
270
|
+
# reset to column 1 so that things like ^C don't ruin formatting
|
271
|
+
o << "\r"
|
266
272
|
|
267
273
|
o << color.code
|
268
274
|
o << CLI::UI::Box::Heavy::HORZ * termwidth # draw a full line
|
269
|
-
o << print_at_x(prefix_start, prefix
|
275
|
+
o << print_at_x(prefix_start, prefix)
|
270
276
|
o << color.code
|
271
|
-
o << print_at_x(suffix_start, suffix
|
277
|
+
o << print_at_x(suffix_start, suffix)
|
272
278
|
o << CLI::UI::Color::RESET.code
|
273
279
|
o << CLI::UI::ANSI.show_cursor
|
274
280
|
o << "\n"
|
@@ -276,12 +282,8 @@ module CLI
|
|
276
282
|
o
|
277
283
|
end
|
278
284
|
|
279
|
-
def print_at_x(x, str
|
280
|
-
|
281
|
-
CLI::UI::ANSI.cursor_restore + CLI::UI::ANSI.cursor_forward(x) + str
|
282
|
-
else
|
283
|
-
CLI::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
|
284
|
-
end
|
285
|
+
def print_at_x(x, str)
|
286
|
+
CLI::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
|
285
287
|
end
|
286
288
|
|
287
289
|
module FrameStack
|
data/lib/cli/ui/glyph.rb
CHANGED
@@ -29,7 +29,7 @@ module CLI
|
|
29
29
|
@handle = handle
|
30
30
|
@codepoint = codepoint
|
31
31
|
@color = color
|
32
|
-
@char =
|
32
|
+
@char = Array(codepoint).pack('U*')
|
33
33
|
@to_s = color.code + char + Color::RESET.code
|
34
34
|
@fmt = "{{#{color.name}:#{char}}}"
|
35
35
|
|
@@ -38,20 +38,14 @@ module CLI
|
|
38
38
|
|
39
39
|
# Mapping of glyphs to terminal output
|
40
40
|
MAP = {}
|
41
|
-
#
|
42
|
-
|
43
|
-
# BLUE
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
# RED BALLOT X (✗)
|
50
|
-
X = new('x', 0x2717, Color::RED)
|
51
|
-
# Bug emoji (🐛)
|
52
|
-
BUG = new('b', 0x1f41b, Color::WHITE)
|
53
|
-
# RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (»)
|
54
|
-
CHEVRON = new('>', 0xbb, Color::YELLOW)
|
41
|
+
STAR = new('*', 0x2b51, Color::YELLOW) # YELLOW SMALL STAR (⭑)
|
42
|
+
INFO = new('i', 0x1d4be, Color::BLUE) # BLUE MATHEMATICAL SCRIPT SMALL i (𝒾)
|
43
|
+
QUESTION = new('?', 0x003f, Color::BLUE) # BLUE QUESTION MARK (?)
|
44
|
+
CHECK = new('v', 0x2713, Color::GREEN) # GREEN CHECK MARK (✓)
|
45
|
+
X = new('x', 0x2717, Color::RED) # RED BALLOT X (✗)
|
46
|
+
BUG = new('b', 0x1f41b, Color::WHITE) # Bug emoji (🐛)
|
47
|
+
CHEVRON = new('>', 0xbb, Color::YELLOW) # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (»)
|
48
|
+
HOURGLASS = new('H', [0x231b, 0xfe0e], Color::BLUE) # HOURGLASS + VARIATION SELECTOR 15 (⌛︎)
|
55
49
|
|
56
50
|
# Looks up a glyph by name
|
57
51
|
#
|
data/lib/cli/ui/prompt.rb
CHANGED
@@ -88,6 +88,33 @@ module CLI
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
# Asks the user for a single-line answer, without displaying the characters while typing.
|
92
|
+
# Typically used for password prompts
|
93
|
+
#
|
94
|
+
# ==== Return Value
|
95
|
+
#
|
96
|
+
# The password, without a trailing newline.
|
97
|
+
# If the user simply presses "Enter" without typing any password, this will return an empty string.
|
98
|
+
def ask_password(question)
|
99
|
+
require 'io/console'
|
100
|
+
|
101
|
+
CLI::UI.with_frame_color(:blue) do
|
102
|
+
STDOUT.print(CLI::UI.fmt('{{?}} ' + question)) # Do not use puts_question to avoid the new line.
|
103
|
+
|
104
|
+
# noecho interacts poorly with Readline under system Ruby, so do a manual `gets` here.
|
105
|
+
# No fancy Readline integration (like echoing back) is required for a password prompt anyway.
|
106
|
+
password = STDIN.noecho do
|
107
|
+
# Chomp will remove the one new line character added by `gets`, without touching potential extra spaces:
|
108
|
+
# " 123 \n".chomp => " 123 "
|
109
|
+
STDIN.gets.chomp
|
110
|
+
end
|
111
|
+
|
112
|
+
STDOUT.puts # Complete the line
|
113
|
+
|
114
|
+
password
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
91
118
|
# Asks the user a yes/no question.
|
92
119
|
# Can use arrows, y/n, numbers (1/2), and vim bindings to control
|
93
120
|
#
|
data/lib/cli/ui/spinner.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen-string-literal: true
|
1
2
|
require 'cli/ui'
|
2
3
|
|
3
4
|
module CLI
|
@@ -9,11 +10,30 @@ module CLI
|
|
9
10
|
PERIOD = 0.1 # seconds
|
10
11
|
TASK_FAILED = :task_failed
|
11
12
|
|
13
|
+
RUNES = %w(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏).freeze
|
14
|
+
|
12
15
|
begin
|
13
|
-
runes = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
14
16
|
colors = [CLI::UI::Color::CYAN.code] * 5 + [CLI::UI::Color::MAGENTA.code] * 5
|
15
|
-
raise unless
|
16
|
-
GLYPHS = colors.zip(
|
17
|
+
raise unless RUNES.size == colors.size
|
18
|
+
GLYPHS = colors.zip(RUNES).map(&:join)
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
attr_accessor(:index)
|
23
|
+
|
24
|
+
# We use this from CLI::UI::Widgets::Status to render an additional
|
25
|
+
# spinner next to the "working" element. While this global state looks
|
26
|
+
# a bit repulsive at first, it's worth realizing that:
|
27
|
+
#
|
28
|
+
# * It's managed by the SpinGroup#wait method, not individual tasks; and
|
29
|
+
# * It would be complete insanity to run two separate but concurrent SpinGroups.
|
30
|
+
#
|
31
|
+
# While it would be possible to stitch through some connection between
|
32
|
+
# the SpinGroup and the Widgets included in its title, this is simpler
|
33
|
+
# in practice and seems unlikely to cause issues in practice.
|
34
|
+
def current_rune
|
35
|
+
RUNES[index || 0]
|
36
|
+
end
|
17
37
|
end
|
18
38
|
|
19
39
|
# Adds a single spinner
|
@@ -40,6 +40,7 @@ module CLI
|
|
40
40
|
#
|
41
41
|
def initialize(title, &block)
|
42
42
|
@title = title
|
43
|
+
@always_full_render = title =~ Formatter::SCAN_WIDGET
|
43
44
|
@thread = Thread.new do
|
44
45
|
cap = CLI::UI::StdoutRouter::Capture.new(self, with_frame_inset: false, &block)
|
45
46
|
begin
|
@@ -75,7 +76,17 @@ module CLI
|
|
75
76
|
@done
|
76
77
|
end
|
77
78
|
|
78
|
-
# Re-renders the task if required
|
79
|
+
# Re-renders the task if required:
|
80
|
+
#
|
81
|
+
# We try to be as lazy as possible in re-rendering the full line. The
|
82
|
+
# spinner rune will change on each render for the most part, but the
|
83
|
+
# body text will rarely have changed. If the body text *has* changed,
|
84
|
+
# we set @force_full_render.
|
85
|
+
#
|
86
|
+
# Further, if the title string includes any CLI::UI::Widgets, we
|
87
|
+
# assume that it may change from render to render, since those
|
88
|
+
# evaluate more dynamically than the rest of our format codes, which
|
89
|
+
# are just text formatters. This is controlled by @always_full_render.
|
79
90
|
#
|
80
91
|
# ==== Attributes
|
81
92
|
#
|
@@ -84,8 +95,11 @@ module CLI
|
|
84
95
|
# * +width+ - current terminal width to format for
|
85
96
|
#
|
86
97
|
def render(index, force = true, width: CLI::UI::Terminal.width)
|
87
|
-
|
88
|
-
|
98
|
+
if force || @always_full_render || @force_full_render
|
99
|
+
full_render(index, width)
|
100
|
+
else
|
101
|
+
partial_render(index)
|
102
|
+
end
|
89
103
|
ensure
|
90
104
|
@force_full_render = false
|
91
105
|
end
|
@@ -97,6 +111,7 @@ module CLI
|
|
97
111
|
# * +title+ - title to change the spinner to
|
98
112
|
#
|
99
113
|
def update_title(new_title)
|
114
|
+
@always_full_render = new_title =~ Formatter::SCAN_WIDGET
|
100
115
|
@title = new_title
|
101
116
|
@force_full_render = true
|
102
117
|
end
|
@@ -194,6 +209,7 @@ module CLI
|
|
194
209
|
break if all_done
|
195
210
|
|
196
211
|
idx = (idx + 1) % GLYPHS.size
|
212
|
+
Spinner.index = idx
|
197
213
|
sleep(PERIOD)
|
198
214
|
end
|
199
215
|
|
data/lib/cli/ui/version.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
require('cli/ui')
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
# Widgets are formatter objects with more custom implementations than the
|
6
|
+
# other features, which all center around formatting text with colours,
|
7
|
+
# etc.
|
8
|
+
#
|
9
|
+
# If you want to extend CLI::UI with your own widgets, you may want to do
|
10
|
+
# something like this:
|
11
|
+
#
|
12
|
+
# require('cli/ui')
|
13
|
+
# class MyWidget < CLI::UI::Widgets::Base
|
14
|
+
# # ...
|
15
|
+
# end
|
16
|
+
# CLI::UI::Widgets.register('my-widget') { MyWidget }
|
17
|
+
# puts(CLI::UI.fmt("{{@widget/my-widget:args}}"))
|
18
|
+
module Widgets
|
19
|
+
MAP = {}
|
20
|
+
|
21
|
+
autoload(:Base, 'cli/ui/widgets/base')
|
22
|
+
|
23
|
+
def self.register(name, &cb)
|
24
|
+
MAP[name] = cb
|
25
|
+
end
|
26
|
+
|
27
|
+
autoload(:Status, 'cli/ui/widgets/status')
|
28
|
+
register('status') { Widgets::Status }
|
29
|
+
|
30
|
+
# Looks up a widget by handle
|
31
|
+
#
|
32
|
+
# ==== Raises
|
33
|
+
# Raises InvalidWidgetHandle if the widget is not available.
|
34
|
+
#
|
35
|
+
# ==== Returns
|
36
|
+
# A callable widget, to be invoked like `.call(argstring)`
|
37
|
+
#
|
38
|
+
def self.lookup(handle)
|
39
|
+
MAP.fetch(handle.to_s).call
|
40
|
+
rescue KeyError, NameError
|
41
|
+
raise(InvalidWidgetHandle, handle)
|
42
|
+
end
|
43
|
+
|
44
|
+
# All available widgets by name
|
45
|
+
#
|
46
|
+
def self.available
|
47
|
+
MAP.keys
|
48
|
+
end
|
49
|
+
|
50
|
+
class InvalidWidgetHandle < ArgumentError
|
51
|
+
def initialize(handle)
|
52
|
+
@handle = handle
|
53
|
+
end
|
54
|
+
|
55
|
+
def message
|
56
|
+
keys = Widget.available.join(',')
|
57
|
+
"invalid widget handle: #{@handle} " \
|
58
|
+
"-- must be one of CLI::UI::Widgets.available (#{keys})"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class InvalidWidgetArguments < ArgumentError
|
63
|
+
def initialize(argstring, pattern)
|
64
|
+
@argstring = argstring
|
65
|
+
@pattern = pattern
|
66
|
+
end
|
67
|
+
|
68
|
+
def message
|
69
|
+
"invalid widget arguments: #{@argstring} " \
|
70
|
+
"-- must match pattern: #{@pattern.inspect}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require('cli/ui')
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
module Widgets
|
6
|
+
class Base
|
7
|
+
def self.call(argstring)
|
8
|
+
new(argstring).render
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(argstring)
|
12
|
+
pat = self.class.argparse_pattern
|
13
|
+
unless (@match_data = pat.match(argstring))
|
14
|
+
raise(Widgets::InvalidWidgetArguments.new(argstring, pat))
|
15
|
+
end
|
16
|
+
@match_data.names.each do |name|
|
17
|
+
instance_variable_set(:"@#{name}", @match_data[name])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.argparse_pattern
|
22
|
+
const_get(:ARGPARSE_PATTERN)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
require('cli/ui')
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
module Widgets
|
7
|
+
class Status < Widgets::Base
|
8
|
+
ARGPARSE_PATTERN = %r{
|
9
|
+
\A (?<succeeded> \d+)
|
10
|
+
: (?<failed> \d+)
|
11
|
+
: (?<working> \d+)
|
12
|
+
: (?<pending> \d+) \z
|
13
|
+
}x # e.g. "1:23:3:404"
|
14
|
+
OPEN = Color::RESET.code + Color::BOLD.code + '[' + Color::RESET.code
|
15
|
+
CLOSE = Color::RESET.code + Color::BOLD.code + ']' + Color::RESET.code
|
16
|
+
ARROW = Color::RESET.code + Color::GRAY.code + '◂' + Color::RESET.code
|
17
|
+
COMMA = Color::RESET.code + Color::GRAY.code + ',' + Color::RESET.code
|
18
|
+
|
19
|
+
SPINNER_STOPPED = '⠿'
|
20
|
+
EMPTY_SET = '∅'
|
21
|
+
|
22
|
+
def render
|
23
|
+
if zero?(@succeeded) && zero?(@failed) && zero?(@working) && zero?(@pending)
|
24
|
+
Color::RESET.code + Color::BOLD.code + EMPTY_SET + Color::RESET.code
|
25
|
+
else
|
26
|
+
# [ 0✓ , 2✗ ◂ 3⠼ ◂ 4⌛︎ ]
|
27
|
+
"#{OPEN}#{succeeded_part}#{COMMA}#{failed_part}#{ARROW}#{working_part}#{ARROW}#{pending_part}#{CLOSE}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def zero?(num_str)
|
34
|
+
num_str == '0'
|
35
|
+
end
|
36
|
+
|
37
|
+
def colorize_if_nonzero(num_str, rune, color)
|
38
|
+
color = Color::GRAY if zero?(num_str)
|
39
|
+
color.code + num_str + rune
|
40
|
+
end
|
41
|
+
|
42
|
+
def succeeded_part
|
43
|
+
colorize_if_nonzero(@succeeded, Glyph::CHECK.char, Color::GREEN)
|
44
|
+
end
|
45
|
+
|
46
|
+
def failed_part
|
47
|
+
colorize_if_nonzero(@failed, Glyph::X.char, Color::RED)
|
48
|
+
end
|
49
|
+
|
50
|
+
def working_part
|
51
|
+
rune = zero?(@working) ? SPINNER_STOPPED : Spinner.current_rune
|
52
|
+
colorize_if_nonzero(@working, rune, Color::BLUE)
|
53
|
+
end
|
54
|
+
|
55
|
+
def pending_part
|
56
|
+
colorize_if_nonzero(@pending, Glyph::HOURGLASS.char, Color::WHITE)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli-ui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2019-
|
13
|
+
date: 2019-08-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
@@ -49,6 +49,7 @@ executables: []
|
|
49
49
|
extensions: []
|
50
50
|
extra_rdoc_files: []
|
51
51
|
files:
|
52
|
+
- ".github/CODEOWNERS"
|
52
53
|
- ".github/probots.yml"
|
53
54
|
- ".gitignore"
|
54
55
|
- ".rubocop.yml"
|
@@ -78,6 +79,9 @@ files:
|
|
78
79
|
- lib/cli/ui/terminal.rb
|
79
80
|
- lib/cli/ui/truncater.rb
|
80
81
|
- lib/cli/ui/version.rb
|
82
|
+
- lib/cli/ui/widgets.rb
|
83
|
+
- lib/cli/ui/widgets/base.rb
|
84
|
+
- lib/cli/ui/widgets/status.rb
|
81
85
|
homepage: https://github.com/shopify/cli-ui
|
82
86
|
licenses:
|
83
87
|
- MIT
|
@@ -97,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
101
|
- !ruby/object:Gem::Version
|
98
102
|
version: '0'
|
99
103
|
requirements: []
|
100
|
-
rubygems_version: 3.0.
|
104
|
+
rubygems_version: 3.0.3
|
101
105
|
signing_key:
|
102
106
|
specification_version: 4
|
103
107
|
summary: Terminal UI framework
|