gorails 0.1.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/Gemfile +3 -1
  4. data/Gemfile.lock +65 -0
  5. data/README.md +41 -12
  6. data/bin/update-deps +95 -0
  7. data/exe/gorails +18 -0
  8. data/gorails.gemspec +4 -3
  9. data/lib/gorails/commands/episodes.rb +25 -0
  10. data/lib/gorails/commands/example.rb +19 -0
  11. data/lib/gorails/commands/help.rb +21 -0
  12. data/lib/gorails/commands/jobs.rb +25 -0
  13. data/lib/gorails/commands/jumpstart.rb +29 -0
  14. data/lib/gorails/commands/railsbytes.rb +67 -0
  15. data/lib/gorails/commands.rb +19 -0
  16. data/lib/gorails/entry_point.rb +10 -0
  17. data/lib/gorails/version.rb +1 -1
  18. data/lib/gorails.rb +22 -1
  19. data/vendor/deps/cli-kit/REVISION +1 -0
  20. data/vendor/deps/cli-kit/lib/cli/kit/args/definition.rb +301 -0
  21. data/vendor/deps/cli-kit/lib/cli/kit/args/evaluation.rb +237 -0
  22. data/vendor/deps/cli-kit/lib/cli/kit/args/parser/node.rb +131 -0
  23. data/vendor/deps/cli-kit/lib/cli/kit/args/parser.rb +128 -0
  24. data/vendor/deps/cli-kit/lib/cli/kit/args/tokenizer.rb +132 -0
  25. data/vendor/deps/cli-kit/lib/cli/kit/args.rb +15 -0
  26. data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +29 -0
  27. data/vendor/deps/cli-kit/lib/cli/kit/command_help.rb +256 -0
  28. data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +141 -0
  29. data/vendor/deps/cli-kit/lib/cli/kit/config.rb +137 -0
  30. data/vendor/deps/cli-kit/lib/cli/kit/core_ext.rb +30 -0
  31. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +165 -0
  32. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +99 -0
  33. data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +94 -0
  34. data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +89 -0
  35. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +95 -0
  36. data/vendor/deps/cli-kit/lib/cli/kit/opts.rb +284 -0
  37. data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +67 -0
  38. data/vendor/deps/cli-kit/lib/cli/kit/sorbet_runtime_stub.rb +142 -0
  39. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +253 -0
  40. data/vendor/deps/cli-kit/lib/cli/kit/support.rb +10 -0
  41. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +350 -0
  42. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +133 -0
  43. data/vendor/deps/cli-kit/lib/cli/kit/version.rb +7 -0
  44. data/vendor/deps/cli-kit/lib/cli/kit.rb +151 -0
  45. data/vendor/deps/cli-ui/REVISION +1 -0
  46. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +180 -0
  47. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +98 -0
  48. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +216 -0
  49. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +116 -0
  50. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +176 -0
  51. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +149 -0
  52. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +112 -0
  53. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +300 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +92 -0
  55. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +58 -0
  56. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +72 -0
  57. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +102 -0
  58. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +534 -0
  59. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +36 -0
  60. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +354 -0
  61. data/vendor/deps/cli-ui/lib/cli/ui/sorbet_runtime_stub.rb +143 -0
  62. data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +46 -0
  63. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +292 -0
  64. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +82 -0
  65. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +264 -0
  66. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +53 -0
  67. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +107 -0
  68. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +6 -0
  69. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +37 -0
  70. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +75 -0
  71. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +91 -0
  72. data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +63 -0
  73. data/vendor/deps/cli-ui/lib/cli/ui.rb +356 -0
  74. 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