gorails 0.1.1 → 0.1.4
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 +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,128 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module Args
|
7
|
+
class Parser
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
autoload :Node, 'cli/kit/args/parser/node'
|
11
|
+
|
12
|
+
Error = Class.new(Args::Error)
|
13
|
+
|
14
|
+
class InvalidOptionError < Error
|
15
|
+
extend T::Sig
|
16
|
+
sig { params(option: String).void }
|
17
|
+
def initialize(option)
|
18
|
+
super("invalid option -- '#{option}'")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class OptionRequiresAnArgumentError < Error
|
23
|
+
extend T::Sig
|
24
|
+
sig { params(option: String).void }
|
25
|
+
def initialize(option)
|
26
|
+
super("option requires an argument -- '#{option}'")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { params(tokens: T::Array[Tokenizer::Token]).returns(T::Array[Node]) }
|
31
|
+
def parse(tokens)
|
32
|
+
nodes = T.let([], T::Array[Node])
|
33
|
+
args = T.let(tokens, T::Array[T.nilable(Tokenizer::Token)])
|
34
|
+
args << nil # to make each_cons pass (args.last, nil) on the final round.
|
35
|
+
state = :init
|
36
|
+
# TODO: test that "--height -- 3" is parsed correctly.
|
37
|
+
args.each_cons(2) do |(arg, next_arg)|
|
38
|
+
case state
|
39
|
+
when :skip
|
40
|
+
state = :init
|
41
|
+
when :init
|
42
|
+
state, val = parse_token(T.must(arg), next_arg)
|
43
|
+
nodes << val
|
44
|
+
when :unparsed
|
45
|
+
unless arg.is_a?(Tokenizer::Token::UnparsedArgument)
|
46
|
+
raise(Error, 'bug: non-unparsed argument after unparsed argument')
|
47
|
+
end
|
48
|
+
|
49
|
+
unparsed = nodes.last
|
50
|
+
unless unparsed.is_a?(Node::Unparsed)
|
51
|
+
# :nocov: not actually possible, in theory
|
52
|
+
raise(Error, 'bug: parser failed to recognize first unparsed argument')
|
53
|
+
# :nocov:
|
54
|
+
end
|
55
|
+
|
56
|
+
unparsed.value << arg.value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
nodes
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { params(definition: Definition).void }
|
63
|
+
def initialize(definition)
|
64
|
+
@defn = definition
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
sig do
|
70
|
+
params(token: Tokenizer::Token, next_token: T.nilable(Tokenizer::Token))
|
71
|
+
.returns([Symbol, Parser::Node])
|
72
|
+
end
|
73
|
+
def parse_token(token, next_token)
|
74
|
+
case token
|
75
|
+
when Tokenizer::Token::LongOptionName
|
76
|
+
case @defn.lookup_long(token.value)
|
77
|
+
when Definition::Option
|
78
|
+
[:skip, parse_option(token, next_token)]
|
79
|
+
when Definition::Flag
|
80
|
+
[:init, Node::LongFlag.new(token.value)]
|
81
|
+
else
|
82
|
+
raise(InvalidOptionError, token.value)
|
83
|
+
end
|
84
|
+
when Tokenizer::Token::ShortOptionName
|
85
|
+
case @defn.lookup_short(token.value)
|
86
|
+
when Definition::Option
|
87
|
+
[:skip, parse_option(token, next_token)]
|
88
|
+
when Definition::Flag
|
89
|
+
[:init, Node::ShortFlag.new(token.value)]
|
90
|
+
else
|
91
|
+
raise(InvalidOptionError, token.value)
|
92
|
+
end
|
93
|
+
when Tokenizer::Token::OptionValue
|
94
|
+
raise(Error, "bug: unexpected option value in argument parse sequence: #{token.value}")
|
95
|
+
when Tokenizer::Token::PositionalArgument
|
96
|
+
[:init, Node::Argument.new(token.value)]
|
97
|
+
when Tokenizer::Token::OptionValueOrPositionalArgument
|
98
|
+
[:init, Node::Argument.new(token.value)]
|
99
|
+
when Tokenizer::Token::UnparsedArgument
|
100
|
+
[:unparsed, Node::Unparsed.new([token.value])]
|
101
|
+
else
|
102
|
+
raise(Error, "bug: unexpected token type: #{token.class}")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { params(arg: Tokenizer::Token::OptionName, next_arg: T.nilable(Tokenizer::Token)).returns(Node) }
|
107
|
+
def parse_option(arg, next_arg)
|
108
|
+
case next_arg
|
109
|
+
when nil, Tokenizer::Token::LongOptionName,
|
110
|
+
Tokenizer::Token::ShortOptionName, Tokenizer::Token::PositionalArgument
|
111
|
+
raise(OptionRequiresAnArgumentError, arg.value)
|
112
|
+
when Tokenizer::Token::OptionValue, Tokenizer::Token::OptionValueOrPositionalArgument
|
113
|
+
case arg
|
114
|
+
when Tokenizer::Token::LongOptionName
|
115
|
+
Node::LongOption.new(arg.value, next_arg.value)
|
116
|
+
when Tokenizer::Token::ShortOptionName
|
117
|
+
Node::ShortOption.new(arg.value, next_arg.value)
|
118
|
+
else
|
119
|
+
raise(Error, "bug: unexpected token type: #{arg.class}")
|
120
|
+
end
|
121
|
+
else
|
122
|
+
raise(Error, "bug: unexpected argument type: #{next_arg.class}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module Args
|
7
|
+
module Tokenizer
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
Error = Class.new(Args::Error)
|
11
|
+
|
12
|
+
class InvalidShortOption < Error
|
13
|
+
extend T::Sig
|
14
|
+
sig { params(short_option: String).void }
|
15
|
+
def initialize(short_option)
|
16
|
+
super("invalid short option: '-#{short_option}'")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidCharInShortOption < Error
|
21
|
+
extend T::Sig
|
22
|
+
sig { params(short_option: String, char: String).void }
|
23
|
+
def initialize(short_option, char)
|
24
|
+
super("invalid character '#{char}' in short option: '-#{short_option}'")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Token
|
29
|
+
extend T::Sig
|
30
|
+
|
31
|
+
sig { returns(String) }
|
32
|
+
attr_reader :value
|
33
|
+
|
34
|
+
sig { params(value: String).void }
|
35
|
+
def initialize(value)
|
36
|
+
@value = value
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { returns(String) }
|
40
|
+
def inspect
|
41
|
+
"#<#{self.class.name} #{@value}>"
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
45
|
+
def ==(other)
|
46
|
+
self.class == other.class && @value == other.value
|
47
|
+
end
|
48
|
+
|
49
|
+
OptionName = Class.new(Token)
|
50
|
+
LongOptionName = Class.new(OptionName)
|
51
|
+
ShortOptionName = Class.new(OptionName)
|
52
|
+
|
53
|
+
OptionValue = Class.new(Token)
|
54
|
+
PositionalArgument = Class.new(Token)
|
55
|
+
OptionValueOrPositionalArgument = Class.new(Token)
|
56
|
+
UnparsedArgument = Class.new(Token)
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
extend T::Sig
|
61
|
+
|
62
|
+
sig { params(raw_args: T::Array[String]).returns(T::Array[Token]) }
|
63
|
+
def tokenize(raw_args)
|
64
|
+
args = []
|
65
|
+
|
66
|
+
mode = :init
|
67
|
+
|
68
|
+
raw_args.each do |arg|
|
69
|
+
case mode
|
70
|
+
when :unparsed
|
71
|
+
args << Token::UnparsedArgument.new(arg)
|
72
|
+
when :init
|
73
|
+
case arg
|
74
|
+
when '--'
|
75
|
+
mode = :unparsed
|
76
|
+
when /\A--/
|
77
|
+
name, value = arg.split('=', 2)
|
78
|
+
args << Token::LongOptionName.new(T.must(T.must(name)[2..-1]))
|
79
|
+
if value
|
80
|
+
args << Token::OptionValue.new(value)
|
81
|
+
end
|
82
|
+
when /\A-/
|
83
|
+
args.concat(tokenize_short_option(T.must(arg[1..-1])))
|
84
|
+
else
|
85
|
+
args << if args.last.is_a?(Token::OptionName)
|
86
|
+
Token::OptionValueOrPositionalArgument.new(arg)
|
87
|
+
else
|
88
|
+
Token::PositionalArgument.new(arg)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
args
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(arg: String).returns(T::Array[Token]) }
|
98
|
+
def tokenize_short_option(arg)
|
99
|
+
args = []
|
100
|
+
mode = :init
|
101
|
+
number = +''
|
102
|
+
arg.each_char do |char|
|
103
|
+
case mode
|
104
|
+
when :numeric
|
105
|
+
case char
|
106
|
+
when /[0-9]/
|
107
|
+
number << char
|
108
|
+
else
|
109
|
+
raise(InvalidShortOption, arg)
|
110
|
+
end
|
111
|
+
when :init
|
112
|
+
case char
|
113
|
+
when /[a-zA-Z]/
|
114
|
+
args << Token::ShortOptionName.new(char)
|
115
|
+
when /[0-9]/
|
116
|
+
mode = :numeric
|
117
|
+
number << char
|
118
|
+
else
|
119
|
+
raise(InvalidCharInShortOption.new(arg, char))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
if number != ''
|
124
|
+
args << Token::OptionValue.new(number)
|
125
|
+
end
|
126
|
+
args
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module Args
|
7
|
+
Error = Class.new(StandardError)
|
8
|
+
|
9
|
+
autoload :Definition, 'cli/kit/args/definition'
|
10
|
+
autoload :Parser, 'cli/kit/args/parser'
|
11
|
+
autoload :Evaluation, 'cli/kit/args/evaluation'
|
12
|
+
autoload :Tokenizer, 'cli/kit/args/tokenizer'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
class BaseCommand
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
include CLI::Kit::CommandHelp
|
10
|
+
extend CLI::Kit::CommandHelp::ClassMethods
|
11
|
+
abstract!
|
12
|
+
|
13
|
+
sig { returns(T::Boolean) }
|
14
|
+
def self.defined?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { params(args: T::Array[String], command_name: String).void }
|
19
|
+
def self.call(args, command_name)
|
20
|
+
new.call(args, command_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { returns(T::Boolean) }
|
24
|
+
def has_subcommands?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module CommandHelp
|
7
|
+
extend T::Sig
|
8
|
+
include Kernel # for sorbet
|
9
|
+
|
10
|
+
sig { params(args: T::Array[String], name: String).void }
|
11
|
+
def call(args, name)
|
12
|
+
begin
|
13
|
+
defn = Args::Definition.new
|
14
|
+
opts = self.class.opts_class
|
15
|
+
opts.new(defn).install_to_definition
|
16
|
+
tokens = Args::Tokenizer.tokenize(args)
|
17
|
+
parse = Args::Parser.new(defn).parse(tokens)
|
18
|
+
result = Args::Evaluation.new(defn, parse)
|
19
|
+
opts_inst = opts.new(result)
|
20
|
+
rescue Args::Evaluation::TooManyPositions, Args::Evaluation::MissingRequiredPosition => e
|
21
|
+
STDERR.puts CLI::UI.fmt("{{red:{{bold:Error: #{e.message}}}}}")
|
22
|
+
STDERR.puts
|
23
|
+
STDERR.puts self.class.build_help
|
24
|
+
raise(AbortSilent)
|
25
|
+
rescue Args::Error => e
|
26
|
+
raise(Abort, e)
|
27
|
+
end
|
28
|
+
|
29
|
+
if opts_inst.helpflag
|
30
|
+
puts self.class.build_help
|
31
|
+
else
|
32
|
+
res = begin
|
33
|
+
opts.new(result)
|
34
|
+
rescue Args::Error => e
|
35
|
+
raise(Abort, e)
|
36
|
+
end
|
37
|
+
invoke_wrapper(res, name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# use to implement error handling
|
42
|
+
sig { params(op: T.untyped, name: String).void }
|
43
|
+
def invoke_wrapper(op, name)
|
44
|
+
invoke(op, name)
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(op: T.untyped, name: String).void }
|
48
|
+
def invoke(op, name)
|
49
|
+
raise(NotImplementedError, '#invoke must be implemented, or #call overridden')
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { params(name: String).void }
|
53
|
+
def self.tool_name=(name)
|
54
|
+
@tool_name = name
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { returns(String) }
|
58
|
+
def self._tool_name
|
59
|
+
unless @tool_name
|
60
|
+
raise 'You must set CLI::Kit::CommandHelp.tool_name='
|
61
|
+
end
|
62
|
+
|
63
|
+
@tool_name
|
64
|
+
end
|
65
|
+
|
66
|
+
module ClassMethods
|
67
|
+
extend T::Sig
|
68
|
+
include Kernel # for sorbet
|
69
|
+
|
70
|
+
DEFAULT_HELP_SECTIONS = [
|
71
|
+
:desc,
|
72
|
+
:long_desc,
|
73
|
+
:usage,
|
74
|
+
:examples,
|
75
|
+
:options,
|
76
|
+
]
|
77
|
+
|
78
|
+
sig { returns(String) }
|
79
|
+
def build_help
|
80
|
+
h = (@help_sections || DEFAULT_HELP_SECTIONS).map do |section|
|
81
|
+
case section
|
82
|
+
when :desc
|
83
|
+
build_desc
|
84
|
+
when :long_desc
|
85
|
+
@long_desc
|
86
|
+
when :usage
|
87
|
+
@usage_section ||= build_usage
|
88
|
+
when :examples
|
89
|
+
@examples_section ||= build_examples
|
90
|
+
when :options
|
91
|
+
@options_section ||= build_options
|
92
|
+
else
|
93
|
+
raise "Unknown help section: #{section}"
|
94
|
+
end
|
95
|
+
end.compact.map(&:chomp).join("\n\n") + "\n"
|
96
|
+
CLI::UI.fmt(h)
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { returns(String) }
|
100
|
+
def _command_name
|
101
|
+
return @command_name if @command_name
|
102
|
+
|
103
|
+
last_camel = send(:name).split('::').last
|
104
|
+
last_camel.gsub(/([a-z])([A-Z])/, '\1-\2').downcase
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { returns(String) }
|
108
|
+
def _desc
|
109
|
+
@desc
|
110
|
+
end
|
111
|
+
|
112
|
+
sig { returns(String) }
|
113
|
+
def build_desc
|
114
|
+
out = +"{{command:#{CommandHelp._tool_name} #{_command_name}}}"
|
115
|
+
if @desc
|
116
|
+
out << ": #{@desc}"
|
117
|
+
end
|
118
|
+
"{{bold:#{out}}}"
|
119
|
+
end
|
120
|
+
|
121
|
+
sig { returns(T.untyped) }
|
122
|
+
def opts_class
|
123
|
+
T.unsafe(self).const_get(:Opts) # rubocop:disable Sorbet/ConstantsFromStrings
|
124
|
+
rescue NameError
|
125
|
+
Class.new(CLI::Kit::Opts)
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { returns(T.nilable(String)) }
|
129
|
+
def build_options
|
130
|
+
opts = opts_class
|
131
|
+
return(nil) unless opts
|
132
|
+
|
133
|
+
methods = []
|
134
|
+
loop do
|
135
|
+
methods.concat(opts.public_instance_methods(false))
|
136
|
+
break if opts.superclass == CLI::Kit::Opts
|
137
|
+
|
138
|
+
opts = opts.superclass
|
139
|
+
end
|
140
|
+
|
141
|
+
@defn = Args::Definition.new
|
142
|
+
o = opts.new(@defn)
|
143
|
+
o.install_to_definition
|
144
|
+
|
145
|
+
return nil if @defn.options.empty? && @defn.flags.empty?
|
146
|
+
|
147
|
+
merged = T.let(@defn.options, T::Array[T.any(Args::Definition::Option, Args::Definition::Flag)])
|
148
|
+
merged += @defn.flags
|
149
|
+
merged.sort_by!(&:name)
|
150
|
+
"{{bold:Options:}}\n" + merged.map do |o|
|
151
|
+
if o.is_a?(Args::Definition::Option)
|
152
|
+
z = ' ' + [o.short&.prepend('-'), o.long&.prepend('--')].compact.join(', ') + ' VALUE'
|
153
|
+
default = if o.dynamic_default?
|
154
|
+
'(generated default)'
|
155
|
+
elsif o.default.nil?
|
156
|
+
'(no default)'
|
157
|
+
else
|
158
|
+
"(default: #{o.default.inspect})"
|
159
|
+
end
|
160
|
+
z << if o.desc
|
161
|
+
" {{italic:{{gray:# #{o.desc} #{default}}}}}"
|
162
|
+
else
|
163
|
+
" {{italic:{{gray:# #{default}}}}}"
|
164
|
+
end
|
165
|
+
else
|
166
|
+
z = ' ' + [o.short&.prepend('-'), o.long&.prepend('--')].compact.join(', ')
|
167
|
+
if o.desc
|
168
|
+
z << " {{italic:{{gray:# #{o.desc}}}}}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
z
|
172
|
+
end.join("\n")
|
173
|
+
end
|
174
|
+
|
175
|
+
sig { params(sections: T::Array[Symbol]).void }
|
176
|
+
def help_sections(sections)
|
177
|
+
@help_sections = sections
|
178
|
+
end
|
179
|
+
|
180
|
+
sig { params(command_name: String).void }
|
181
|
+
def command_name(command_name)
|
182
|
+
if @command_name
|
183
|
+
raise(ArgumentError, "Command name already set to #{@command_name}")
|
184
|
+
end
|
185
|
+
|
186
|
+
@command_name = command_name
|
187
|
+
end
|
188
|
+
|
189
|
+
sig { params(desc: String).void }
|
190
|
+
def desc(desc)
|
191
|
+
if desc.size > 80
|
192
|
+
raise(ArgumentError, 'description must be 80 characters or less')
|
193
|
+
end
|
194
|
+
if @desc
|
195
|
+
raise(ArgumentError, 'description already set')
|
196
|
+
end
|
197
|
+
|
198
|
+
@desc = desc
|
199
|
+
end
|
200
|
+
|
201
|
+
sig { params(long_desc: String).void }
|
202
|
+
def long_desc(long_desc)
|
203
|
+
if @long_desc
|
204
|
+
raise(ArgumentError, 'long description already set')
|
205
|
+
end
|
206
|
+
|
207
|
+
@long_desc = long_desc
|
208
|
+
end
|
209
|
+
|
210
|
+
sig { returns(String) }
|
211
|
+
def build_usage
|
212
|
+
'{{bold:Usage:}}' + case (@usage || []).size
|
213
|
+
when 0
|
214
|
+
" {{command:#{CommandHelp._tool_name} #{_command_name}}} [options]\n"
|
215
|
+
when 1
|
216
|
+
" {{command:#{CommandHelp._tool_name} #{_command_name}}} #{@usage.first}\n"
|
217
|
+
else
|
218
|
+
"\n" + @usage.map do |usage|
|
219
|
+
" {{command:#{CommandHelp._tool_name} #{_command_name}}} #{usage}\n"
|
220
|
+
end.join
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
sig { returns(T.nilable(String)) }
|
225
|
+
def build_examples
|
226
|
+
return nil unless @examples
|
227
|
+
|
228
|
+
cmd_prefix = " {{command:#{CommandHelp._tool_name} #{_command_name}}}"
|
229
|
+
"{{bold:Examples:}}\n" + @examples.map do |command, explanation|
|
230
|
+
cmd = "#{cmd_prefix} #{command}"
|
231
|
+
exp = "{{italic:{{gray:# #{explanation}}}}}"
|
232
|
+
|
233
|
+
width = CLI::UI::ANSI.printing_width(CLI::UI.fmt("#{cmd} #{exp}"))
|
234
|
+
if width > CLI::UI::Terminal.width
|
235
|
+
" #{exp}\n#{cmd}"
|
236
|
+
else
|
237
|
+
"#{cmd} #{exp}"
|
238
|
+
end
|
239
|
+
end.join("\n\n")
|
240
|
+
end
|
241
|
+
|
242
|
+
sig { params(usage: String).void }
|
243
|
+
def usage(usage)
|
244
|
+
@usage ||= []
|
245
|
+
@usage << usage
|
246
|
+
end
|
247
|
+
|
248
|
+
sig { params(command: String, explanation: T.nilable(String)).void }
|
249
|
+
def example(command, explanation)
|
250
|
+
@examples ||= []
|
251
|
+
@examples << [command, explanation]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
class CommandRegistry
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
CommandOrProc = T.type_alias do
|
10
|
+
T.any(T.class_of(CLI::Kit::BaseCommand), T.proc.returns(T.class_of(CLI::Kit::BaseCommand)))
|
11
|
+
end
|
12
|
+
|
13
|
+
sig { returns(T::Hash[String, CommandOrProc]) }
|
14
|
+
attr_reader :commands
|
15
|
+
|
16
|
+
sig { returns(T::Hash[String, String]) }
|
17
|
+
attr_reader :aliases
|
18
|
+
|
19
|
+
module ContextualResolver
|
20
|
+
extend T::Sig
|
21
|
+
extend T::Helpers
|
22
|
+
interface!
|
23
|
+
|
24
|
+
sig { abstract.returns(T::Array[String]) }
|
25
|
+
def command_names; end
|
26
|
+
|
27
|
+
sig { abstract.returns(T::Hash[String, String]) }
|
28
|
+
def aliases; end
|
29
|
+
|
30
|
+
sig { abstract.params(_name: String).returns(T.class_of(CLI::Kit::BaseCommand)) }
|
31
|
+
def command_class(_name); end
|
32
|
+
end
|
33
|
+
|
34
|
+
module NullContextualResolver
|
35
|
+
extend T::Sig
|
36
|
+
extend ContextualResolver
|
37
|
+
|
38
|
+
sig { override.returns(T::Array[String]) }
|
39
|
+
def self.command_names
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { override.returns(T::Hash[String, String]) }
|
44
|
+
def self.aliases
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { override.params(_name: String).returns(T.class_of(CLI::Kit::BaseCommand)) }
|
49
|
+
def self.command_class(_name)
|
50
|
+
raise(CLI::Kit::Abort, 'Cannot be called on the NullContextualResolver since command_names is empty')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(default: String, contextual_resolver: ContextualResolver).void }
|
55
|
+
def initialize(default:, contextual_resolver: NullContextualResolver)
|
56
|
+
@commands = {}
|
57
|
+
@aliases = {}
|
58
|
+
@default = default
|
59
|
+
@contextual_resolver = contextual_resolver
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { returns(T::Hash[String, T.class_of(CLI::Kit::BaseCommand)]) }
|
63
|
+
def resolved_commands
|
64
|
+
@commands.each_with_object({}) do |(k, v), a|
|
65
|
+
a[k] = resolve_class(v)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { params(const: CommandOrProc, name: String).void }
|
70
|
+
def add(const, name)
|
71
|
+
commands[name] = const
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(name: T.nilable(String)).returns([T.nilable(T.class_of(CLI::Kit::BaseCommand)), String]) }
|
75
|
+
def lookup_command(name)
|
76
|
+
name = @default if name.to_s.empty?
|
77
|
+
resolve_command(T.must(name))
|
78
|
+
end
|
79
|
+
|
80
|
+
sig { params(from: String, to: String).void }
|
81
|
+
def add_alias(from, to)
|
82
|
+
aliases[from] = to unless aliases[from]
|
83
|
+
end
|
84
|
+
|
85
|
+
sig { returns(T::Array[String]) }
|
86
|
+
def command_names
|
87
|
+
@contextual_resolver.command_names + commands.keys
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { params(name: String).returns(T::Boolean) }
|
91
|
+
def exist?(name)
|
92
|
+
!resolve_command(name).first.nil?
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
sig { params(name: String).returns(String) }
|
98
|
+
def resolve_alias(name)
|
99
|
+
aliases[name] || @contextual_resolver.aliases.fetch(name, name)
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { params(name: String).returns([T.nilable(T.class_of(CLI::Kit::BaseCommand)), String]) }
|
103
|
+
def resolve_command(name)
|
104
|
+
name = resolve_alias(name)
|
105
|
+
resolve_global_command(name) || \
|
106
|
+
resolve_contextual_command(name) || \
|
107
|
+
[nil, name]
|
108
|
+
end
|
109
|
+
|
110
|
+
sig { params(name: String).returns(T.nilable([T.class_of(CLI::Kit::BaseCommand), String])) }
|
111
|
+
def resolve_global_command(name)
|
112
|
+
klass = resolve_class(commands.fetch(name, nil))
|
113
|
+
return nil unless klass
|
114
|
+
|
115
|
+
[klass, name]
|
116
|
+
rescue NameError
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
sig { params(name: String).returns(T.nilable([T.class_of(CLI::Kit::BaseCommand), String])) }
|
121
|
+
def resolve_contextual_command(name)
|
122
|
+
found = @contextual_resolver.command_names.include?(name)
|
123
|
+
return nil unless found
|
124
|
+
|
125
|
+
[@contextual_resolver.command_class(name), name]
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { params(class_or_proc: T.nilable(CommandOrProc)).returns(T.nilable(T.class_of(CLI::Kit::BaseCommand))) }
|
129
|
+
def resolve_class(class_or_proc)
|
130
|
+
case class_or_proc
|
131
|
+
when nil
|
132
|
+
nil
|
133
|
+
when Proc
|
134
|
+
class_or_proc.call
|
135
|
+
else
|
136
|
+
class_or_proc
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|