gorails 0.1.0 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +65 -0
- data/README.md +41 -12
- data/bin/update-deps +95 -0
- data/exe/gorails +18 -0
- data/gorails.gemspec +4 -3
- data/lib/gorails/commands/episodes.rb +25 -0
- data/lib/gorails/commands/example.rb +19 -0
- data/lib/gorails/commands/help.rb +21 -0
- data/lib/gorails/commands/jobs.rb +25 -0
- data/lib/gorails/commands/jumpstart.rb +29 -0
- data/lib/gorails/commands/railsbytes.rb +67 -0
- data/lib/gorails/commands.rb +19 -0
- data/lib/gorails/entry_point.rb +10 -0
- data/lib/gorails/version.rb +1 -1
- data/lib/gorails.rb +22 -1
- data/vendor/deps/cli-kit/REVISION +1 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/definition.rb +301 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/evaluation.rb +237 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/parser/node.rb +131 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/parser.rb +128 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/tokenizer.rb +132 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args.rb +15 -0
- data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +29 -0
- data/vendor/deps/cli-kit/lib/cli/kit/command_help.rb +256 -0
- data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +141 -0
- data/vendor/deps/cli-kit/lib/cli/kit/config.rb +137 -0
- data/vendor/deps/cli-kit/lib/cli/kit/core_ext.rb +30 -0
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +165 -0
- data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +99 -0
- data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +94 -0
- data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +89 -0
- data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +95 -0
- data/vendor/deps/cli-kit/lib/cli/kit/opts.rb +284 -0
- data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +67 -0
- data/vendor/deps/cli-kit/lib/cli/kit/sorbet_runtime_stub.rb +142 -0
- data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +253 -0
- data/vendor/deps/cli-kit/lib/cli/kit/support.rb +10 -0
- data/vendor/deps/cli-kit/lib/cli/kit/system.rb +350 -0
- data/vendor/deps/cli-kit/lib/cli/kit/util.rb +133 -0
- data/vendor/deps/cli-kit/lib/cli/kit/version.rb +7 -0
- data/vendor/deps/cli-kit/lib/cli/kit.rb +151 -0
- data/vendor/deps/cli-ui/REVISION +1 -0
- data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +180 -0
- data/vendor/deps/cli-ui/lib/cli/ui/color.rb +98 -0
- data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +216 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +116 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +176 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +149 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +112 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +300 -0
- data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +92 -0
- data/vendor/deps/cli-ui/lib/cli/ui/os.rb +58 -0
- data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +72 -0
- data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +102 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +534 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +36 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +354 -0
- data/vendor/deps/cli-ui/lib/cli/ui/sorbet_runtime_stub.rb +143 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +46 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +292 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +82 -0
- data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +264 -0
- data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +53 -0
- data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +107 -0
- data/vendor/deps/cli-ui/lib/cli/ui/version.rb +6 -0
- data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +37 -0
- data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +75 -0
- data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +91 -0
- data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +63 -0
- data/vendor/deps/cli-ui/lib/cli/ui.rb +356 -0
- metadata +114 -5
@@ -0,0 +1,112 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/ui/frame'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
module Frame
|
7
|
+
module FrameStyle
|
8
|
+
include Kernel
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
abstract!
|
12
|
+
|
13
|
+
autoload(:Box, 'cli/ui/frame/frame_style/box')
|
14
|
+
autoload(:Bracket, 'cli/ui/frame/frame_style/bracket')
|
15
|
+
|
16
|
+
MAP = {
|
17
|
+
box: -> { FrameStyle::Box },
|
18
|
+
bracket: -> { FrameStyle::Bracket },
|
19
|
+
}
|
20
|
+
|
21
|
+
# Lookup a frame style via its name
|
22
|
+
#
|
23
|
+
# ==== Attributes
|
24
|
+
#
|
25
|
+
# * +symbol+ - frame style name to lookup
|
26
|
+
sig { params(name: T.any(String, Symbol)).returns(FrameStyle) }
|
27
|
+
def self.lookup(name)
|
28
|
+
MAP.fetch(name.to_sym).call
|
29
|
+
rescue KeyError
|
30
|
+
raise(InvalidFrameStyleName, name)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { abstract.returns(Symbol) }
|
34
|
+
def style_name; end
|
35
|
+
|
36
|
+
# Returns the character(s) that should be printed at the beginning
|
37
|
+
# of lines inside this frame
|
38
|
+
sig { abstract.returns(String) }
|
39
|
+
def prefix; end
|
40
|
+
|
41
|
+
# Returns the printing width of the prefix
|
42
|
+
sig { returns(Integer) }
|
43
|
+
def prefix_width
|
44
|
+
CLI::UI::ANSI.printing_width(prefix)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Draws the "Open" line for this frame style
|
48
|
+
#
|
49
|
+
# ==== Attributes
|
50
|
+
#
|
51
|
+
# * +text+ - (required) the text/title to output in the frame
|
52
|
+
#
|
53
|
+
# ==== Options
|
54
|
+
#
|
55
|
+
# * +:color+ - (required) The color of the frame.
|
56
|
+
#
|
57
|
+
sig { abstract.params(text: String, color: CLI::UI::Color).returns(String) }
|
58
|
+
def start(text, color:); end
|
59
|
+
|
60
|
+
# Draws the "Close" line for this frame style
|
61
|
+
#
|
62
|
+
# ==== Attributes
|
63
|
+
#
|
64
|
+
# * +text+ - (required) the text/title to output in the frame
|
65
|
+
#
|
66
|
+
# ==== Options
|
67
|
+
#
|
68
|
+
# * +:color+ - (required) The color of the frame.
|
69
|
+
# * +:right_text+ - Text to print at the right of the line. Defaults to nil
|
70
|
+
#
|
71
|
+
sig { abstract.params(text: String, color: CLI::UI::Color, right_text: T.nilable(String)).returns(String) }
|
72
|
+
def close(text, color:, right_text: nil); end
|
73
|
+
|
74
|
+
# Draws a "divider" line for the current frame style
|
75
|
+
#
|
76
|
+
# ==== Attributes
|
77
|
+
#
|
78
|
+
# * +text+ - (required) the text/title to output in the frame
|
79
|
+
#
|
80
|
+
# ==== Options
|
81
|
+
#
|
82
|
+
# * +:color+ - (required) The color of the frame.
|
83
|
+
#
|
84
|
+
sig { abstract.params(text: String, color: CLI::UI::Color).returns(String) }
|
85
|
+
def divider(text, color:); end
|
86
|
+
|
87
|
+
sig { params(x: Integer, str: String).returns(String) }
|
88
|
+
def print_at_x(x, str)
|
89
|
+
CLI::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
|
90
|
+
end
|
91
|
+
|
92
|
+
class InvalidFrameStyleName < ArgumentError
|
93
|
+
extend T::Sig
|
94
|
+
|
95
|
+
sig { params(name: T.any(String, Symbol)).void }
|
96
|
+
def initialize(name)
|
97
|
+
super
|
98
|
+
@name = name
|
99
|
+
end
|
100
|
+
|
101
|
+
sig { returns(String) }
|
102
|
+
def message
|
103
|
+
keys = FrameStyle::MAP.keys.map(&:inspect).join(', ')
|
104
|
+
"invalid frame style: #{@name.inspect}" \
|
105
|
+
' -- must be one of CLI::UI::Frame::FrameStyle::MAP ' \
|
106
|
+
"(#{keys})"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# typed: true
|
4
|
+
|
5
|
+
require 'cli/ui'
|
6
|
+
require 'cli/ui/frame/frame_stack'
|
7
|
+
require 'cli/ui/frame/frame_style'
|
8
|
+
|
9
|
+
module CLI
|
10
|
+
module UI
|
11
|
+
module Frame
|
12
|
+
class UnnestedFrameException < StandardError; end
|
13
|
+
DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
|
14
|
+
|
15
|
+
class << self
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
sig { returns(FrameStyle) }
|
19
|
+
def frame_style
|
20
|
+
@frame_style ||= FrameStyle::Box
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the default frame style.
|
24
|
+
#
|
25
|
+
# Raises ArgumentError if +frame_style+ is not valid
|
26
|
+
#
|
27
|
+
# ==== Attributes
|
28
|
+
#
|
29
|
+
# * +symbol+ or +FrameStyle+ - the default frame style to use for frames
|
30
|
+
#
|
31
|
+
sig { params(frame_style: FrameStylable).void }
|
32
|
+
def frame_style=(frame_style)
|
33
|
+
@frame_style = CLI::UI.resolve_style(frame_style)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Opens a new frame. Can be nested
|
37
|
+
# Can be invoked in two ways: block and blockless
|
38
|
+
# * In block form, the frame is closed automatically when the block returns
|
39
|
+
# * In blockless form, caller MUST call +Frame.close+ when the frame is logically done
|
40
|
+
# * Blockless form is strongly discouraged in cases where block form can be made to work
|
41
|
+
#
|
42
|
+
# https://user-images.githubusercontent.com/3074765/33799861-cb5dcb5c-dd01-11e7-977e-6fad38cee08c.png
|
43
|
+
#
|
44
|
+
# The return value of the block determines if the block is a "success" or a "failure"
|
45
|
+
#
|
46
|
+
# ==== Attributes
|
47
|
+
#
|
48
|
+
# * +text+ - (required) the text/title to output in the frame
|
49
|
+
#
|
50
|
+
# ==== Options
|
51
|
+
#
|
52
|
+
# * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
|
53
|
+
# * +:failure_text+ - If the block failed, what do we output? Defaults to nil
|
54
|
+
# * +:success_text+ - If the block succeeds, what do we output? Defaults to nil
|
55
|
+
# * +:timing+ - How long did the frame content take? Invalid for blockless. Defaults to true for the block form
|
56
|
+
# * +frame_style+ - The frame style to use for this frame
|
57
|
+
#
|
58
|
+
# ==== Example
|
59
|
+
#
|
60
|
+
# ===== Block Form (Assumes +CLI::UI::StdoutRouter.enable+ has been called)
|
61
|
+
#
|
62
|
+
# CLI::UI::Frame.open('Open') { puts 'hi' }
|
63
|
+
#
|
64
|
+
# Default Output:
|
65
|
+
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
66
|
+
# ┃ hi
|
67
|
+
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (0.0s) ━━
|
68
|
+
#
|
69
|
+
# ===== Blockless Form
|
70
|
+
#
|
71
|
+
# CLI::UI::Frame.open('Open')
|
72
|
+
#
|
73
|
+
# Default Output:
|
74
|
+
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
75
|
+
#
|
76
|
+
#
|
77
|
+
sig do
|
78
|
+
type_parameters(:T).params(
|
79
|
+
text: String,
|
80
|
+
color: Colorable,
|
81
|
+
failure_text: T.nilable(String),
|
82
|
+
success_text: T.nilable(String),
|
83
|
+
timing: T.any(T::Boolean, Numeric),
|
84
|
+
frame_style: FrameStylable,
|
85
|
+
block: T.nilable(T.proc.returns(T.type_parameter(:T)))
|
86
|
+
).returns(T.nilable(T.type_parameter(:T)))
|
87
|
+
end
|
88
|
+
def open(
|
89
|
+
text,
|
90
|
+
color: DEFAULT_FRAME_COLOR,
|
91
|
+
failure_text: nil,
|
92
|
+
success_text: nil,
|
93
|
+
timing: block_given?,
|
94
|
+
frame_style: self.frame_style,
|
95
|
+
&block
|
96
|
+
)
|
97
|
+
frame_style = CLI::UI.resolve_style(frame_style)
|
98
|
+
color = CLI::UI.resolve_color(color)
|
99
|
+
|
100
|
+
unless block_given?
|
101
|
+
if failure_text
|
102
|
+
raise ArgumentError, 'failure_text is not compatible with blockless invocation'
|
103
|
+
elsif success_text
|
104
|
+
raise ArgumentError, 'success_text is not compatible with blockless invocation'
|
105
|
+
elsif timing
|
106
|
+
raise ArgumentError, 'timing is not compatible with blockless invocation'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
t_start = Time.now
|
111
|
+
CLI::UI.raw do
|
112
|
+
print(prefix.chop)
|
113
|
+
puts frame_style.start(text, color: color)
|
114
|
+
end
|
115
|
+
FrameStack.push(color: color, style: frame_style)
|
116
|
+
|
117
|
+
return unless block_given?
|
118
|
+
|
119
|
+
closed = false
|
120
|
+
begin
|
121
|
+
success = false
|
122
|
+
success = yield
|
123
|
+
rescue
|
124
|
+
closed = true
|
125
|
+
t_diff = elapsed(t_start, timing)
|
126
|
+
close(failure_text, color: :red, elapsed: t_diff)
|
127
|
+
raise
|
128
|
+
else
|
129
|
+
success
|
130
|
+
ensure
|
131
|
+
unless closed
|
132
|
+
t_diff = elapsed(t_start, timing)
|
133
|
+
if T.unsafe(success) != false
|
134
|
+
close(success_text, color: color, elapsed: t_diff)
|
135
|
+
else
|
136
|
+
close(failure_text, color: :red, elapsed: t_diff)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Adds a divider in a frame
|
143
|
+
# Used to separate information within a single frame
|
144
|
+
#
|
145
|
+
# ==== Attributes
|
146
|
+
#
|
147
|
+
# * +text+ - (required) the text/title to output in the frame
|
148
|
+
#
|
149
|
+
# ==== Options
|
150
|
+
#
|
151
|
+
# * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
|
152
|
+
# * +frame_style+ - The frame style to use for this frame
|
153
|
+
#
|
154
|
+
# ==== Example
|
155
|
+
#
|
156
|
+
# CLI::UI::Frame.open('Open') { CLI::UI::Frame.divider('Divider') }
|
157
|
+
#
|
158
|
+
# Default Output:
|
159
|
+
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
160
|
+
# ┣━━ Divider ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
161
|
+
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
162
|
+
#
|
163
|
+
# ==== Raises
|
164
|
+
#
|
165
|
+
# MUST be inside an open frame or it raises a +UnnestedFrameException+
|
166
|
+
#
|
167
|
+
sig { params(text: T.nilable(String), color: T.nilable(Colorable), frame_style: T.nilable(FrameStylable)).void }
|
168
|
+
def divider(text, color: nil, frame_style: nil)
|
169
|
+
fs_item = FrameStack.pop
|
170
|
+
raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
|
171
|
+
|
172
|
+
divider_color = CLI::UI.resolve_color(color || fs_item.color)
|
173
|
+
frame_style = CLI::UI.resolve_style(frame_style || fs_item.frame_style)
|
174
|
+
|
175
|
+
CLI::UI.raw do
|
176
|
+
print(prefix.chop)
|
177
|
+
puts frame_style.divider(text.to_s, color: divider_color)
|
178
|
+
end
|
179
|
+
|
180
|
+
FrameStack.push(fs_item)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Closes a frame
|
184
|
+
# Automatically called for a block-form +open+
|
185
|
+
#
|
186
|
+
# ==== Attributes
|
187
|
+
#
|
188
|
+
# * +text+ - (required) the text/title to output in the frame
|
189
|
+
#
|
190
|
+
# ==== Options
|
191
|
+
#
|
192
|
+
# * +:color+ - The color of the frame. Defaults to nil
|
193
|
+
# * +:elapsed+ - How long did the frame take? Defaults to nil
|
194
|
+
# * +frame_style+ - The frame style to use for this frame. Defaults to nil
|
195
|
+
#
|
196
|
+
# ==== Example
|
197
|
+
#
|
198
|
+
# CLI::UI::Frame.close('Close')
|
199
|
+
#
|
200
|
+
# Default Output:
|
201
|
+
# ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
202
|
+
#
|
203
|
+
# ==== Raises
|
204
|
+
#
|
205
|
+
# MUST be inside an open frame or it raises a +UnnestedFrameException+
|
206
|
+
#
|
207
|
+
sig do
|
208
|
+
params(
|
209
|
+
text: T.nilable(String),
|
210
|
+
color: T.nilable(Colorable),
|
211
|
+
elapsed: T.nilable(Numeric),
|
212
|
+
frame_style: T.nilable(FrameStylable)
|
213
|
+
).void
|
214
|
+
end
|
215
|
+
def close(text, color: nil, elapsed: nil, frame_style: nil)
|
216
|
+
fs_item = FrameStack.pop
|
217
|
+
raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
|
218
|
+
|
219
|
+
close_color = CLI::UI.resolve_color(color || fs_item.color)
|
220
|
+
frame_style = CLI::UI.resolve_style(frame_style || fs_item.frame_style)
|
221
|
+
elapsed_string = elapsed ? "(#{elapsed.round(2)}s)" : nil
|
222
|
+
|
223
|
+
CLI::UI.raw do
|
224
|
+
print(prefix.chop)
|
225
|
+
puts frame_style.close(text.to_s, color: close_color, right_text: elapsed_string)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Determines the prefix of a frame entry taking multi-nested frames into account
|
230
|
+
#
|
231
|
+
# ==== Options
|
232
|
+
#
|
233
|
+
# * +:color+ - The color of the prefix. Defaults to +Thread.current[:cliui_frame_color_override]+
|
234
|
+
#
|
235
|
+
sig { params(color: T.nilable(Colorable)).returns(String) }
|
236
|
+
def prefix(color: Thread.current[:cliui_frame_color_override])
|
237
|
+
+''.tap do |output|
|
238
|
+
items = FrameStack.items
|
239
|
+
|
240
|
+
items[0..-2].to_a.each do |item|
|
241
|
+
output << item.color.code << item.frame_style.prefix
|
242
|
+
end
|
243
|
+
|
244
|
+
if (item = items.last)
|
245
|
+
final_color = color || item.color
|
246
|
+
output << CLI::UI.resolve_color(final_color).code \
|
247
|
+
<< item.frame_style.prefix \
|
248
|
+
<< ' ' \
|
249
|
+
<< CLI::UI::Color::RESET.code
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# The width of a prefix given the number of Frames in the stack
|
255
|
+
sig { returns(Integer) }
|
256
|
+
def prefix_width
|
257
|
+
w = FrameStack.items.reduce(0) do |width, item|
|
258
|
+
width + item.frame_style.prefix_width
|
259
|
+
end
|
260
|
+
|
261
|
+
w.zero? ? w : w + 1
|
262
|
+
end
|
263
|
+
|
264
|
+
# Override a color for a given thread.
|
265
|
+
#
|
266
|
+
# ==== Attributes
|
267
|
+
#
|
268
|
+
# * +color+ - The color to override to
|
269
|
+
#
|
270
|
+
sig do
|
271
|
+
type_parameters(:T)
|
272
|
+
.params(color: Colorable, block: T.proc.returns(T.type_parameter(:T)))
|
273
|
+
.returns(T.type_parameter(:T))
|
274
|
+
end
|
275
|
+
def with_frame_color_override(color, &block)
|
276
|
+
prev = Thread.current[:cliui_frame_color_override]
|
277
|
+
Thread.current[:cliui_frame_color_override] = color
|
278
|
+
yield
|
279
|
+
ensure
|
280
|
+
Thread.current[:cliui_frame_color_override] = prev
|
281
|
+
end
|
282
|
+
|
283
|
+
private
|
284
|
+
|
285
|
+
# If timing is:
|
286
|
+
# Numeric: return it
|
287
|
+
# false: return nil
|
288
|
+
# true: defaults to Time.new
|
289
|
+
sig { params(start: Time, timing: T.any(Numeric, T::Boolean)).returns(T.nilable(Numeric)) }
|
290
|
+
def elapsed(start, timing)
|
291
|
+
return timing if timing.is_a?(Numeric)
|
292
|
+
return if timing.is_a?(FalseClass)
|
293
|
+
|
294
|
+
timing = Time.new
|
295
|
+
timing - start
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/ui'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
class Glyph
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class InvalidGlyphHandle < ArgumentError
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(handle: String).void }
|
13
|
+
def initialize(handle)
|
14
|
+
super
|
15
|
+
@handle = handle
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { returns(String) }
|
19
|
+
def message
|
20
|
+
keys = Glyph.available.join(',')
|
21
|
+
"invalid glyph handle: #{@handle} " \
|
22
|
+
"-- must be one of CLI::UI::Glyph.available (#{keys})"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { returns(String) }
|
27
|
+
attr_reader :handle, :to_s, :fmt, :char
|
28
|
+
|
29
|
+
sig { returns(T.any(Integer, T::Array[Integer])) }
|
30
|
+
attr_reader :codepoint
|
31
|
+
|
32
|
+
sig { returns(Color) }
|
33
|
+
attr_reader :color
|
34
|
+
|
35
|
+
# Creates a new glyph
|
36
|
+
#
|
37
|
+
# ==== Attributes
|
38
|
+
#
|
39
|
+
# * +handle+ - The handle in the +MAP+ constant
|
40
|
+
# * +codepoint+ - The codepoint used to create the glyph (e.g. +0x2717+ for a ballot X)
|
41
|
+
# * +plain+ - A fallback plain string to be used in case glyphs are disabled
|
42
|
+
# * +color+ - What color to output the glyph. Check +CLI::UI::Color+ for options.
|
43
|
+
#
|
44
|
+
sig { params(handle: String, codepoint: T.any(Integer, T::Array[Integer]), plain: String, color: Color).void }
|
45
|
+
def initialize(handle, codepoint, plain, color)
|
46
|
+
@handle = handle
|
47
|
+
@codepoint = codepoint
|
48
|
+
@color = color
|
49
|
+
@char = CLI::UI::OS.current.use_emoji? ? Array(codepoint).pack('U*') : plain
|
50
|
+
@to_s = color.code + @char + Color::RESET.code
|
51
|
+
@fmt = "{{#{color.name}:#{@char}}}"
|
52
|
+
|
53
|
+
MAP[handle] = self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Mapping of glyphs to terminal output
|
57
|
+
MAP = {}
|
58
|
+
STAR = new('*', 0x2b51, '*', Color::YELLOW) # YELLOW SMALL STAR (⭑)
|
59
|
+
INFO = new('i', 0x1d4be, 'i', Color::BLUE) # BLUE MATHEMATICAL SCRIPT SMALL i (𝒾)
|
60
|
+
QUESTION = new('?', 0x003f, '?', Color::BLUE) # BLUE QUESTION MARK (?)
|
61
|
+
CHECK = new('v', 0x2713, '√', Color::GREEN) # GREEN CHECK MARK (✓)
|
62
|
+
X = new('x', 0x2717, 'X', Color::RED) # RED BALLOT X (✗)
|
63
|
+
BUG = new('b', 0x1f41b, '!', Color::WHITE) # Bug emoji (🐛)
|
64
|
+
CHEVRON = new('>', 0xbb, '»', Color::YELLOW) # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (»)
|
65
|
+
HOURGLASS = new('H', [0x231b, 0xfe0e], 'H', Color::BLUE) # HOURGLASS + VARIATION SELECTOR 15 (⌛︎)
|
66
|
+
WARNING = new('!', [0x26a0, 0xfe0f], '!', Color::YELLOW) # WARNING SIGN + VARIATION SELECTOR 16 (⚠️ )
|
67
|
+
|
68
|
+
# Looks up a glyph by name
|
69
|
+
#
|
70
|
+
# ==== Raises
|
71
|
+
# Raises a InvalidGlyphHandle if the glyph is not available
|
72
|
+
# You likely need to create it with +.new+ or you made a typo
|
73
|
+
#
|
74
|
+
# ==== Returns
|
75
|
+
# Returns a terminal output-capable string
|
76
|
+
#
|
77
|
+
sig { params(name: String).returns(Glyph) }
|
78
|
+
def self.lookup(name)
|
79
|
+
MAP.fetch(name.to_s)
|
80
|
+
rescue KeyError
|
81
|
+
raise InvalidGlyphHandle, name
|
82
|
+
end
|
83
|
+
|
84
|
+
# All available glyphs by name
|
85
|
+
#
|
86
|
+
sig { returns(T::Array[String]) }
|
87
|
+
def self.available
|
88
|
+
MAP.keys
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
class OS
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(emoji: T::Boolean, color_prompt: T::Boolean, arrow_keys: T::Boolean, shift_cursor: T::Boolean).void }
|
10
|
+
def initialize(emoji: true, color_prompt: true, arrow_keys: true, shift_cursor: false)
|
11
|
+
@emoji = emoji
|
12
|
+
@color_prompt = color_prompt
|
13
|
+
@arrow_keys = arrow_keys
|
14
|
+
@shift_cursor = shift_cursor
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { returns(T::Boolean) }
|
18
|
+
def use_emoji?
|
19
|
+
@emoji
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(T::Boolean) }
|
23
|
+
def use_color_prompt?
|
24
|
+
@color_prompt
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { returns(T::Boolean) }
|
28
|
+
def suggest_arrow_keys?
|
29
|
+
@arrow_keys
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { returns(T::Boolean) }
|
33
|
+
def shift_cursor_back_on_horizontal_absolute?
|
34
|
+
@shift_cursor
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { returns(OS) }
|
38
|
+
def self.current
|
39
|
+
@current_os ||= case RbConfig::CONFIG['host_os']
|
40
|
+
when /darwin/
|
41
|
+
MAC
|
42
|
+
when /linux/
|
43
|
+
LINUX
|
44
|
+
else
|
45
|
+
if RUBY_PLATFORM !~ /cygwin/ && ENV['OS'] == 'Windows_NT'
|
46
|
+
WINDOWS
|
47
|
+
else
|
48
|
+
raise "Could not determine OS from host_os #{RbConfig::CONFIG["host_os"]}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
MAC = OS.new
|
54
|
+
LINUX = OS.new
|
55
|
+
WINDOWS = OS.new(emoji: false, color_prompt: false, arrow_keys: false, shift_cursor: true)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/ui'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
class Printer
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# Print a message to a stream with common utilities.
|
10
|
+
# Allows overriding the color, encoding, and target stream.
|
11
|
+
# By default, it formats the string using CLI:UI and rescues common stream errors.
|
12
|
+
#
|
13
|
+
# ==== Attributes
|
14
|
+
#
|
15
|
+
# * +msg+ - (required) the string to output. Can be frozen.
|
16
|
+
#
|
17
|
+
# ==== Options
|
18
|
+
#
|
19
|
+
# * +:frame_color+ - Override the frame color. Defaults to nil.
|
20
|
+
# * +:to+ - Target stream, like $stdout or $stderr. Can be anything with a puts method. Defaults to $stdout.
|
21
|
+
# * +:encoding+ - Force the output to be in a certain encoding. Defaults to UTF-8.
|
22
|
+
# * +:format+ - Whether to format the string using CLI::UI.fmt. Defaults to true.
|
23
|
+
# * +:graceful+ - Whether to gracefully ignore common I/O errors. Defaults to true.
|
24
|
+
# * +:wrap+ - Whether to wrap text at word boundaries to terminal width. Defaults to true.
|
25
|
+
#
|
26
|
+
# ==== Returns
|
27
|
+
# Returns whether the message was successfully printed,
|
28
|
+
# which can be useful if +:graceful+ is set to true.
|
29
|
+
#
|
30
|
+
# ==== Example
|
31
|
+
#
|
32
|
+
# CLI::UI::Printer.puts('{{x}} Ouch', to: $stderr)
|
33
|
+
#
|
34
|
+
sig do
|
35
|
+
params(
|
36
|
+
msg: String,
|
37
|
+
frame_color: T.nilable(Colorable),
|
38
|
+
to: IOLike,
|
39
|
+
encoding: T.nilable(Encoding),
|
40
|
+
format: T::Boolean,
|
41
|
+
graceful: T::Boolean,
|
42
|
+
wrap: T::Boolean
|
43
|
+
).returns(T::Boolean)
|
44
|
+
end
|
45
|
+
def self.puts(
|
46
|
+
msg,
|
47
|
+
frame_color: nil,
|
48
|
+
to: $stdout,
|
49
|
+
encoding: Encoding::UTF_8,
|
50
|
+
format: true,
|
51
|
+
graceful: true,
|
52
|
+
wrap: true
|
53
|
+
)
|
54
|
+
msg = (+msg).force_encoding(encoding) if encoding
|
55
|
+
msg = CLI::UI.fmt(msg) if format
|
56
|
+
msg = CLI::UI.wrap(msg) if wrap
|
57
|
+
|
58
|
+
if frame_color
|
59
|
+
CLI::UI::Frame.with_frame_color_override(frame_color) { to.puts(msg) }
|
60
|
+
else
|
61
|
+
to.puts(msg)
|
62
|
+
end
|
63
|
+
|
64
|
+
true
|
65
|
+
rescue Errno::EIO, Errno::EPIPE, IOError => e
|
66
|
+
raise(e) unless graceful
|
67
|
+
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|