gorails 0.1.1 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/Gemfile.lock +1 -6
- data/README.md +41 -12
- data/bin/update-deps +95 -0
- data/exe/gorails +2 -1
- data/gorails.gemspec +0 -2
- data/lib/gorails/commands/railsbytes.rb +45 -4
- data/lib/gorails/commands/version.rb +15 -0
- data/lib/gorails/commands.rb +2 -5
- data/lib/gorails/version.rb +1 -1
- data/lib/gorails.rb +11 -20
- 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 +59 -30
@@ -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
|