gorails 0.1.0 → 0.1.3
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/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,107 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'cli/ui'
|
5
|
+
|
6
|
+
module CLI
|
7
|
+
module UI
|
8
|
+
# Truncater truncates a string to a provided printable width.
|
9
|
+
module Truncater
|
10
|
+
PARSE_ROOT = :root
|
11
|
+
PARSE_ANSI = :ansi
|
12
|
+
PARSE_ESC = :esc
|
13
|
+
PARSE_ZWJ = :zwj
|
14
|
+
|
15
|
+
ESC = 0x1b
|
16
|
+
LEFT_SQUARE_BRACKET = 0x5b
|
17
|
+
ZWJ = 0x200d # emojipedia.org/emoji-zwj-sequences
|
18
|
+
SEMICOLON = 0x3b
|
19
|
+
|
20
|
+
# EMOJI_RANGE in particular is super inaccurate. This is best-effort.
|
21
|
+
# If you need this to be more accurate, we'll almost certainly accept a
|
22
|
+
# PR improving it.
|
23
|
+
EMOJI_RANGE = 0x1f300..0x1f5ff
|
24
|
+
NUMERIC_RANGE = 0x30..0x39
|
25
|
+
LC_ALPHA_RANGE = 0x40..0x5a
|
26
|
+
UC_ALPHA_RANGE = 0x60..0x71
|
27
|
+
|
28
|
+
TRUNCATED = "\x1b[0m…"
|
29
|
+
|
30
|
+
class << self
|
31
|
+
extend T::Sig
|
32
|
+
|
33
|
+
sig { params(text: String, printing_width: Integer).returns(String) }
|
34
|
+
def call(text, printing_width)
|
35
|
+
return text if text.size <= printing_width
|
36
|
+
|
37
|
+
width = 0
|
38
|
+
mode = PARSE_ROOT
|
39
|
+
truncation_index = T.let(nil, T.nilable(Integer))
|
40
|
+
|
41
|
+
codepoints = text.codepoints
|
42
|
+
codepoints.each.with_index do |cp, index|
|
43
|
+
case mode
|
44
|
+
when PARSE_ROOT
|
45
|
+
case cp
|
46
|
+
when ESC # non-printable, followed by some more non-printables.
|
47
|
+
mode = PARSE_ESC
|
48
|
+
when ZWJ # non-printable, followed by another non-printable.
|
49
|
+
mode = PARSE_ZWJ
|
50
|
+
else
|
51
|
+
width += width(cp)
|
52
|
+
if width >= printing_width
|
53
|
+
truncation_index ||= index
|
54
|
+
# it looks like we could break here but we still want the
|
55
|
+
# width calculation for the rest of the characters.
|
56
|
+
end
|
57
|
+
end
|
58
|
+
when PARSE_ESC
|
59
|
+
mode = case cp
|
60
|
+
when LEFT_SQUARE_BRACKET
|
61
|
+
PARSE_ANSI
|
62
|
+
else
|
63
|
+
PARSE_ROOT
|
64
|
+
end
|
65
|
+
when PARSE_ANSI
|
66
|
+
# ANSI escape codes preeeetty much have the format of:
|
67
|
+
# \x1b[0-9;]+[A-Za-z]
|
68
|
+
case cp
|
69
|
+
when NUMERIC_RANGE, SEMICOLON
|
70
|
+
when LC_ALPHA_RANGE, UC_ALPHA_RANGE
|
71
|
+
mode = PARSE_ROOT
|
72
|
+
else
|
73
|
+
# unexpected. let's just go back to the root state I guess?
|
74
|
+
mode = PARSE_ROOT
|
75
|
+
end
|
76
|
+
when PARSE_ZWJ
|
77
|
+
# consume any character and consider it as having no width
|
78
|
+
# width(x+ZWJ+y) = width(x).
|
79
|
+
mode = PARSE_ROOT
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Without the `width <= printing_width` check, we truncate
|
84
|
+
# "foo\x1b[0m" for a width of 3, but it should not be truncated.
|
85
|
+
# It's specifically for the case where we decided "Yes, this is the
|
86
|
+
# point at which we'd have to add a truncation!" but it's actually
|
87
|
+
# the end of the string.
|
88
|
+
return text if !truncation_index || width <= printing_width
|
89
|
+
|
90
|
+
T.must(codepoints[0...truncation_index]).pack('U*') + TRUNCATED
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
sig { params(printable_codepoint: Integer).returns(Integer) }
|
96
|
+
def width(printable_codepoint)
|
97
|
+
case printable_codepoint
|
98
|
+
when EMOJI_RANGE
|
99
|
+
2
|
100
|
+
else
|
101
|
+
1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# typed: true
|
2
|
+
require('cli/ui')
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
module Widgets
|
7
|
+
class Base
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
abstract!
|
11
|
+
|
12
|
+
sig { params(argstring: String).returns(String) }
|
13
|
+
def self.call(argstring)
|
14
|
+
new(argstring).render
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(argstring: String).void }
|
18
|
+
def initialize(argstring)
|
19
|
+
pat = self.class.argparse_pattern
|
20
|
+
unless (@match_data = pat.match(argstring))
|
21
|
+
raise(Widgets::InvalidWidgetArguments.new(argstring, pat))
|
22
|
+
end
|
23
|
+
|
24
|
+
@match_data.names.each do |name|
|
25
|
+
instance_variable_set(:"@#{name}", @match_data[name])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { abstract.returns(Regexp) }
|
30
|
+
def self.argparse_pattern; end
|
31
|
+
|
32
|
+
sig { abstract.returns(String) }
|
33
|
+
def render; end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen-string-literal: true
|
3
|
+
|
4
|
+
require('cli/ui')
|
5
|
+
|
6
|
+
module CLI
|
7
|
+
module UI
|
8
|
+
module Widgets
|
9
|
+
class Status < Widgets::Base
|
10
|
+
ARGPARSE_PATTERN = %r{
|
11
|
+
\A (?<succeeded> \d+)
|
12
|
+
: (?<failed> \d+)
|
13
|
+
: (?<working> \d+)
|
14
|
+
: (?<pending> \d+) \z
|
15
|
+
}x # e.g. "1:23:3:404"
|
16
|
+
OPEN = Color::RESET.code + Color::BOLD.code + '[' + Color::RESET.code
|
17
|
+
CLOSE = Color::RESET.code + Color::BOLD.code + ']' + Color::RESET.code
|
18
|
+
ARROW = Color::RESET.code + Color::GRAY.code + '◂' + Color::RESET.code
|
19
|
+
COMMA = Color::RESET.code + Color::GRAY.code + ',' + Color::RESET.code
|
20
|
+
|
21
|
+
SPINNER_STOPPED = '⠿'
|
22
|
+
EMPTY_SET = '∅'
|
23
|
+
|
24
|
+
sig { override.returns(Regexp) }
|
25
|
+
def self.argparse_pattern
|
26
|
+
ARGPARSE_PATTERN
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { override.returns(String) }
|
30
|
+
def render
|
31
|
+
if zero?(@succeeded) && zero?(@failed) && zero?(@working) && zero?(@pending)
|
32
|
+
Color::RESET.code + Color::BOLD.code + EMPTY_SET + Color::RESET.code
|
33
|
+
else
|
34
|
+
# [ 0✓ , 2✗ ◂ 3⠼ ◂ 4⌛︎ ]
|
35
|
+
"#{OPEN}#{succeeded_part}#{COMMA}#{failed_part}#{ARROW}#{working_part}#{ARROW}#{pending_part}#{CLOSE}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
sig { params(num_str: String).returns(T::Boolean) }
|
42
|
+
def zero?(num_str)
|
43
|
+
num_str == '0'
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { params(num_str: String, rune: String, color: Color).returns(String) }
|
47
|
+
def colorize_if_nonzero(num_str, rune, color)
|
48
|
+
color = Color::GRAY if zero?(num_str)
|
49
|
+
color.code + num_str + rune
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(String) }
|
53
|
+
def succeeded_part
|
54
|
+
colorize_if_nonzero(@succeeded, Glyph::CHECK.char, Color::GREEN)
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { returns(String) }
|
58
|
+
def failed_part
|
59
|
+
colorize_if_nonzero(@failed, Glyph::X.char, Color::RED)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { returns(String) }
|
63
|
+
def working_part
|
64
|
+
rune = zero?(@working) ? SPINNER_STOPPED : Spinner.current_rune
|
65
|
+
colorize_if_nonzero(@working, rune, Color::BLUE)
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { returns(String) }
|
69
|
+
def pending_part
|
70
|
+
colorize_if_nonzero(@pending, Glyph::HOURGLASS.char, Color::WHITE)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# typed: true
|
2
|
+
require('cli/ui')
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
# Widgets are formatter objects with more custom implementations than the
|
7
|
+
# other features, which all center around formatting text with colours,
|
8
|
+
# etc.
|
9
|
+
#
|
10
|
+
# If you want to extend CLI::UI with your own widgets, you may want to do
|
11
|
+
# something like this:
|
12
|
+
#
|
13
|
+
# require('cli/ui')
|
14
|
+
# class MyWidget < CLI::UI::Widgets::Base
|
15
|
+
# # ...
|
16
|
+
# end
|
17
|
+
# CLI::UI::Widgets.register('my-widget') { MyWidget }
|
18
|
+
# puts(CLI::UI.fmt("{{@widget/my-widget:args}}"))
|
19
|
+
module Widgets
|
20
|
+
extend T::Sig
|
21
|
+
|
22
|
+
MAP = {}
|
23
|
+
|
24
|
+
autoload(:Base, 'cli/ui/widgets/base')
|
25
|
+
|
26
|
+
sig { params(name: String, cb: T.proc.returns(T.class_of(Widgets::Base))).void }
|
27
|
+
def self.register(name, &cb)
|
28
|
+
MAP[name] = cb
|
29
|
+
end
|
30
|
+
|
31
|
+
autoload(:Status, 'cli/ui/widgets/status')
|
32
|
+
register('status') { Widgets::Status }
|
33
|
+
|
34
|
+
# Looks up a widget by handle
|
35
|
+
#
|
36
|
+
# ==== Raises
|
37
|
+
# Raises InvalidWidgetHandle if the widget is not available.
|
38
|
+
#
|
39
|
+
# ==== Returns
|
40
|
+
# A callable widget, to be invoked like `.call(argstring)`
|
41
|
+
#
|
42
|
+
sig { params(handle: String).returns(T.class_of(Widgets::Base)) }
|
43
|
+
def self.lookup(handle)
|
44
|
+
MAP.fetch(handle).call
|
45
|
+
rescue KeyError, NameError
|
46
|
+
raise(InvalidWidgetHandle, handle)
|
47
|
+
end
|
48
|
+
|
49
|
+
# All available widgets by name
|
50
|
+
#
|
51
|
+
sig { returns(T::Array[String]) }
|
52
|
+
def self.available
|
53
|
+
MAP.keys
|
54
|
+
end
|
55
|
+
|
56
|
+
class InvalidWidgetHandle < ArgumentError
|
57
|
+
extend T::Sig
|
58
|
+
|
59
|
+
sig { params(handle: String).void }
|
60
|
+
def initialize(handle)
|
61
|
+
super
|
62
|
+
@handle = handle
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { returns(String) }
|
66
|
+
def message
|
67
|
+
keys = Widgets.available.join(',')
|
68
|
+
"invalid widget handle: #{@handle} " \
|
69
|
+
"-- must be one of CLI::UI::Widgets.available (#{keys})"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class InvalidWidgetArguments < ArgumentError
|
74
|
+
extend T::Sig
|
75
|
+
|
76
|
+
sig { params(argstring: String, pattern: Regexp).void }
|
77
|
+
def initialize(argstring, pattern)
|
78
|
+
super
|
79
|
+
@argstring = argstring
|
80
|
+
@pattern = pattern
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { returns(String) }
|
84
|
+
def message
|
85
|
+
"invalid widget arguments: #{@argstring} " \
|
86
|
+
"-- must match pattern: #{@pattern.inspect}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,63 @@
|
|
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
|
+
class Wrap
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(input: String).void }
|
15
|
+
def initialize(input)
|
16
|
+
@input = input
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(String) }
|
20
|
+
def wrap
|
21
|
+
max_width = Terminal.width - Frame.prefix_width
|
22
|
+
width = T.let(0, Integer)
|
23
|
+
final = []
|
24
|
+
# Create an alternation of format codes of parameter lengths 1-20, since + and {1,n} not allowed in lookbehind
|
25
|
+
format_codes = (1..20).map { |n| /\x1b\[[\d;]{#{n}}m/ }.join('|')
|
26
|
+
codes = ''
|
27
|
+
@input.split(/(?=\s|\x1b\[[\d;]+m|\r)|(?<=\s|#{format_codes})/).each do |token|
|
28
|
+
case token
|
29
|
+
when '\x1B[0?m'
|
30
|
+
codes = ''
|
31
|
+
final << token
|
32
|
+
when /\x1b\[[\d;]+m/
|
33
|
+
codes += token # Track in use format codes so that they are resent after frame coloring
|
34
|
+
final << token
|
35
|
+
when "\n"
|
36
|
+
final << "\n#{codes}"
|
37
|
+
width = 0
|
38
|
+
when /\s/
|
39
|
+
token_width = ANSI.printing_width(token)
|
40
|
+
if width + token_width <= max_width
|
41
|
+
final << token
|
42
|
+
width += token_width
|
43
|
+
else
|
44
|
+
final << "\n#{codes}"
|
45
|
+
width = 0
|
46
|
+
end
|
47
|
+
else
|
48
|
+
token_width = ANSI.printing_width(token)
|
49
|
+
if width + token_width <= max_width
|
50
|
+
final << token
|
51
|
+
width += token_width
|
52
|
+
else
|
53
|
+
final << "\n#{codes}"
|
54
|
+
final << token
|
55
|
+
width = token_width
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
final.join
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|