gorails 0.1.2 → 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.lock +1 -1
- data/bin/update-deps +95 -0
- data/exe/gorails +3 -2
- data/lib/gorails/commands/railsbytes.rb +10 -10
- data/lib/gorails/commands.rb +1 -4
- 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 +57 -1
@@ -0,0 +1,237 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module Args
|
7
|
+
class Evaluation
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
Error = Class.new(Args::Error)
|
11
|
+
|
12
|
+
class MissingRequiredOption < Error
|
13
|
+
extend T::Sig
|
14
|
+
sig { params(name: String).void }
|
15
|
+
def initialize(name)
|
16
|
+
super("missing required option `#{name}'")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class MissingRequiredPosition < Error
|
21
|
+
extend T::Sig
|
22
|
+
sig { void }
|
23
|
+
def initialize
|
24
|
+
super('more arguments required')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class TooManyPositions < Error
|
29
|
+
extend T::Sig
|
30
|
+
sig { void }
|
31
|
+
def initialize
|
32
|
+
super('too many arguments')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class FlagProxy
|
37
|
+
extend T::Sig
|
38
|
+
|
39
|
+
sig { params(sym: Symbol).returns(T::Boolean) }
|
40
|
+
def method_missing(sym)
|
41
|
+
flag = @evaluation.defn.lookup_flag(sym)
|
42
|
+
unless flag
|
43
|
+
raise NoMethodError, "undefined flag `#{sym}' for #{self}"
|
44
|
+
end
|
45
|
+
|
46
|
+
@evaluation.send(:lookup_flag, flag)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(sym: Symbol, include_private: T::Boolean).returns(T::Boolean) }
|
50
|
+
def respond_to_missing?(sym, include_private = false)
|
51
|
+
!!@evaluation.defn.lookup_flag(sym)
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(evaluation: Evaluation).void }
|
55
|
+
def initialize(evaluation)
|
56
|
+
@evaluation = evaluation
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class OptionProxy
|
61
|
+
extend T::Sig
|
62
|
+
|
63
|
+
sig { params(sym: Symbol).returns(T.any(NilClass, String, T::Array[String])) }
|
64
|
+
def method_missing(sym)
|
65
|
+
opt = @evaluation.defn.lookup_option(sym)
|
66
|
+
unless opt
|
67
|
+
raise NoMethodError, "undefined option `#{sym}' for #{self}"
|
68
|
+
end
|
69
|
+
|
70
|
+
@evaluation.send(:lookup_option, opt)
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { params(sym: Symbol, include_private: T::Boolean).returns(T::Boolean) }
|
74
|
+
def respond_to_missing?(sym, include_private = false)
|
75
|
+
!!@evaluation.defn.lookup_option(sym)
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { params(evaluation: Evaluation).void }
|
79
|
+
def initialize(evaluation)
|
80
|
+
@evaluation = evaluation
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class PositionProxy
|
85
|
+
extend T::Sig
|
86
|
+
|
87
|
+
sig { params(sym: Symbol).returns(T.any(NilClass, String, T::Array[String])) }
|
88
|
+
def method_missing(sym)
|
89
|
+
position = @evaluation.defn.lookup_position(sym)
|
90
|
+
unless position
|
91
|
+
raise NoMethodError, "undefined position `#{sym}' for #{self}"
|
92
|
+
end
|
93
|
+
|
94
|
+
@evaluation.send(:lookup_position, position)
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(sym: Symbol, include_private: T::Boolean).returns(T::Boolean) }
|
98
|
+
def respond_to_missing?(sym, include_private = false)
|
99
|
+
!!@evaluation.defn.lookup_position(sym)
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { params(evaluation: Evaluation).void }
|
103
|
+
def initialize(evaluation)
|
104
|
+
@evaluation = evaluation
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { returns(FlagProxy) }
|
109
|
+
def flag
|
110
|
+
@flag_proxy ||= FlagProxy.new(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
sig { returns(OptionProxy) }
|
114
|
+
def opt
|
115
|
+
@option_proxy ||= OptionProxy.new(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
sig { returns(PositionProxy) }
|
119
|
+
def position
|
120
|
+
@position_proxy ||= PositionProxy.new(self)
|
121
|
+
end
|
122
|
+
|
123
|
+
sig { returns(Definition) }
|
124
|
+
attr_reader :defn
|
125
|
+
|
126
|
+
sig { returns(T::Array[Parser::Node]) }
|
127
|
+
attr_reader :parse
|
128
|
+
|
129
|
+
sig { returns(T::Array[String]) }
|
130
|
+
def unparsed
|
131
|
+
@unparsed ||= begin
|
132
|
+
nodes = T.cast(
|
133
|
+
parse.select { |node| node.is_a?(Parser::Node::Unparsed) },
|
134
|
+
T::Array[Parser::Node::Unparsed],
|
135
|
+
)
|
136
|
+
nodes.flat_map(&:value)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
sig { params(defn: Definition, parse: T::Array[Parser::Node]).void }
|
141
|
+
def initialize(defn, parse)
|
142
|
+
@defn = defn
|
143
|
+
@parse = parse
|
144
|
+
check_required!
|
145
|
+
end
|
146
|
+
|
147
|
+
sig { void }
|
148
|
+
def check_required!
|
149
|
+
@defn.options.each do |opt|
|
150
|
+
next unless opt.required
|
151
|
+
|
152
|
+
node = @parse.detect do |node|
|
153
|
+
node.is_a?(Parser::Node::Option) && node.name == opt.name
|
154
|
+
end
|
155
|
+
if !node || T.cast(node, Parser::Node::Option).value.nil?
|
156
|
+
raise(MissingRequiredOption, opt.as_written_by_user)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
min_positions = @defn.positions.count(&:required?)
|
160
|
+
max_positions = if @defn.positions.last&.multiple?
|
161
|
+
Float::INFINITY
|
162
|
+
else
|
163
|
+
min_positions + @defn.positions.count(&:optional?)
|
164
|
+
end
|
165
|
+
raise(MissingRequiredPosition) if args.size < min_positions
|
166
|
+
raise(TooManyPositions) if args.size > max_positions
|
167
|
+
end
|
168
|
+
|
169
|
+
sig { params(flag: Definition::Flag).returns(T::Boolean) }
|
170
|
+
def lookup_flag(flag)
|
171
|
+
if flag.short
|
172
|
+
flags = T.cast(
|
173
|
+
parse.select { |node| node.is_a?(Parser::Node::ShortFlag) },
|
174
|
+
T::Array[Parser::Node::ShortFlag],
|
175
|
+
)
|
176
|
+
return true if flags.any? { |node| node.value == flag.short }
|
177
|
+
end
|
178
|
+
if flag.long
|
179
|
+
flags = T.cast(
|
180
|
+
parse.select { |node| node.is_a?(Parser::Node::LongFlag) },
|
181
|
+
T::Array[Parser::Node::LongFlag],
|
182
|
+
)
|
183
|
+
return true if flags.any? { |node| node.value == flag.long }
|
184
|
+
end
|
185
|
+
false
|
186
|
+
end
|
187
|
+
|
188
|
+
sig { params(opt: Definition::Option).returns(T.any(NilClass, String, T::Array[String])) }
|
189
|
+
def lookup_option(opt)
|
190
|
+
if opt.short
|
191
|
+
opts = T.cast(
|
192
|
+
parse.select { |node| node.is_a?(Parser::Node::ShortOption) },
|
193
|
+
T::Array[Parser::Node::ShortOption],
|
194
|
+
)
|
195
|
+
matches = opts.reverse.select { |node| node.name == opt.short }
|
196
|
+
if (first = matches.first)
|
197
|
+
return(opt.multi ? matches.map(&:value) : first.value)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
if opt.long
|
201
|
+
opts = T.cast(
|
202
|
+
parse.select { |node| node.is_a?(Parser::Node::LongOption) },
|
203
|
+
T::Array[Parser::Node::LongOption],
|
204
|
+
)
|
205
|
+
matches = opts.reverse.select { |node| node.name == opt.long }
|
206
|
+
if (first = matches.first)
|
207
|
+
return(opt.multi ? matches.map(&:value) : first.value)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
opt.multi ? [] : opt.default
|
211
|
+
end
|
212
|
+
|
213
|
+
sig { params(position: Definition::Position).returns(T.any(NilClass, String, T::Array[String])) }
|
214
|
+
def lookup_position(position)
|
215
|
+
if position.multiple?
|
216
|
+
args[position.index..]
|
217
|
+
else
|
218
|
+
args[position.index]
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
sig { returns(T::Array[String]) }
|
225
|
+
def args
|
226
|
+
@args ||= begin
|
227
|
+
nodes = T.cast(
|
228
|
+
parse.select { |node| node.is_a?(Parser::Node::Argument) },
|
229
|
+
T::Array[Parser::Node::Argument],
|
230
|
+
)
|
231
|
+
nodes.map(&:value)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module Args
|
7
|
+
class Parser
|
8
|
+
class Node
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { void }
|
12
|
+
def initialize
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
16
|
+
def ==(other)
|
17
|
+
self.class == other.class
|
18
|
+
end
|
19
|
+
|
20
|
+
class Option < Node
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
sig { returns(String) }
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
sig { returns(String) }
|
27
|
+
attr_reader :value
|
28
|
+
|
29
|
+
sig { params(name: String, value: String).void }
|
30
|
+
def initialize(name, value)
|
31
|
+
@name = name
|
32
|
+
@value = value
|
33
|
+
super()
|
34
|
+
end
|
35
|
+
private_class_method(:new) # don't instantiate this class directly
|
36
|
+
|
37
|
+
sig { returns(String) }
|
38
|
+
def inspect
|
39
|
+
"#<#{self.class.name} #{@name}=#{@value}>"
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
43
|
+
def ==(other)
|
44
|
+
!!(super(other) && @value == other.value && @name == other.name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class LongOption < Option
|
49
|
+
public_class_method(:new)
|
50
|
+
end
|
51
|
+
|
52
|
+
class ShortOption < Option
|
53
|
+
public_class_method(:new)
|
54
|
+
end
|
55
|
+
|
56
|
+
class Flag < Node
|
57
|
+
sig { returns(String) }
|
58
|
+
attr_reader :value
|
59
|
+
|
60
|
+
sig { params(value: String).void }
|
61
|
+
def initialize(value)
|
62
|
+
@value = value
|
63
|
+
super()
|
64
|
+
end
|
65
|
+
private_class_method(:new) # don't instantiate this class directly
|
66
|
+
|
67
|
+
sig { returns(String) }
|
68
|
+
def inspect
|
69
|
+
"#<#{self.class.name} #{@value}>"
|
70
|
+
end
|
71
|
+
|
72
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
73
|
+
def ==(other)
|
74
|
+
!!(super(other) && @value == other.value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class LongFlag < Flag
|
79
|
+
public_class_method(:new)
|
80
|
+
end
|
81
|
+
|
82
|
+
class ShortFlag < Flag
|
83
|
+
public_class_method(:new)
|
84
|
+
end
|
85
|
+
|
86
|
+
class Argument < Node
|
87
|
+
sig { returns(String) }
|
88
|
+
attr_reader :value
|
89
|
+
|
90
|
+
sig { params(value: String).void }
|
91
|
+
def initialize(value)
|
92
|
+
@value = value
|
93
|
+
super()
|
94
|
+
end
|
95
|
+
|
96
|
+
sig { returns(String) }
|
97
|
+
def inspect
|
98
|
+
"#<#{self.class.name} #{@value}>"
|
99
|
+
end
|
100
|
+
|
101
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
102
|
+
def ==(other)
|
103
|
+
!!(super(other) && @value == other.value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Unparsed < Node
|
108
|
+
sig { returns(T::Array[String]) }
|
109
|
+
attr_reader :value
|
110
|
+
|
111
|
+
sig { params(value: T::Array[String]).void }
|
112
|
+
def initialize(value)
|
113
|
+
@value = value
|
114
|
+
super()
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { returns(String) }
|
118
|
+
def inspect
|
119
|
+
"#<#{self.class.name} #{@value.join(" ")}>"
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
123
|
+
def ==(other)
|
124
|
+
!!(super(other) && @value == other.value)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -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
|