cli-kit 5.1.0 → 5.2.0
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/.rubocop.sorbet.yml +1 -0
- data/.rubocop.yml +1 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +22 -24
- data/Rakefile +0 -1
- data/bin/testunit +0 -1
- data/examples/minimal/example.rb +1 -1
- data/examples/single-file/example.rb +4 -3
- data/gen/lib/gen/commands/help.rb +1 -3
- data/gen/lib/gen/commands/new.rb +2 -6
- data/gen/lib/gen/commands.rb +1 -7
- data/gen/lib/gen/entry_point.rb +1 -5
- data/gen/lib/gen/generator.rb +15 -19
- data/gen/lib/gen/help.rb +3 -7
- data/lib/cli/kit/args/definition.rb +37 -95
- data/lib/cli/kit/args/evaluation.rb +40 -59
- data/lib/cli/kit/args/parser/node.rb +19 -23
- data/lib/cli/kit/args/parser.rb +12 -16
- data/lib/cli/kit/args/tokenizer.rb +15 -18
- data/lib/cli/kit/base_command.rb +4 -8
- data/lib/cli/kit/command_help.rb +24 -27
- data/lib/cli/kit/command_registry.rb +40 -36
- data/lib/cli/kit/config.rb +14 -15
- data/lib/cli/kit/core_ext.rb +4 -6
- data/lib/cli/kit/error_handler.rb +17 -33
- data/lib/cli/kit/executor.rb +7 -16
- data/lib/cli/kit/ini.rb +8 -12
- data/lib/cli/kit/levenshtein.rb +7 -5
- data/lib/cli/kit/logger.rb +8 -10
- data/lib/cli/kit/opts.rb +26 -86
- data/lib/cli/kit/parse_args.rb +5 -10
- data/lib/cli/kit/resolver.rb +4 -6
- data/lib/cli/kit/support/test_helper.rb +10 -5
- data/lib/cli/kit/system.rb +25 -114
- data/lib/cli/kit/util.rb +15 -31
- data/lib/cli/kit/version.rb +1 -1
- data/lib/cli/kit.rb +17 -35
- metadata +1 -2
- data/lib/cli/kit/sorbet_runtime_stub.rb +0 -154
@@ -7,27 +7,23 @@ module CLI
|
|
7
7
|
module Args
|
8
8
|
class Parser
|
9
9
|
class Node
|
10
|
-
|
11
|
-
|
12
|
-
sig { void }
|
10
|
+
#: -> void
|
13
11
|
def initialize
|
14
12
|
end
|
15
13
|
|
16
|
-
|
14
|
+
#: (untyped other) -> bool
|
17
15
|
def ==(other)
|
18
16
|
self.class == other.class
|
19
17
|
end
|
20
18
|
|
21
19
|
class Option < Node
|
22
|
-
|
23
|
-
|
24
|
-
sig { returns(String) }
|
20
|
+
#: String
|
25
21
|
attr_reader :name
|
26
22
|
|
27
|
-
|
23
|
+
#: String
|
28
24
|
attr_reader :value
|
29
25
|
|
30
|
-
|
26
|
+
#: (String name, String value) -> void
|
31
27
|
def initialize(name, value)
|
32
28
|
@name = name
|
33
29
|
@value = value
|
@@ -35,12 +31,12 @@ module CLI
|
|
35
31
|
end
|
36
32
|
private_class_method(:new) # don't instantiate this class directly
|
37
33
|
|
38
|
-
|
34
|
+
#: -> String
|
39
35
|
def inspect
|
40
36
|
"#<#{self.class.name} #{@name}=#{@value}>"
|
41
37
|
end
|
42
38
|
|
43
|
-
|
39
|
+
#: (untyped other) -> bool
|
44
40
|
def ==(other)
|
45
41
|
!!(super(other) && @value == other.value && @name == other.name)
|
46
42
|
end
|
@@ -55,22 +51,22 @@ module CLI
|
|
55
51
|
end
|
56
52
|
|
57
53
|
class Flag < Node
|
58
|
-
|
54
|
+
#: String
|
59
55
|
attr_reader :value
|
60
56
|
|
61
|
-
|
57
|
+
#: (String value) -> void
|
62
58
|
def initialize(value)
|
63
59
|
@value = value
|
64
60
|
super()
|
65
61
|
end
|
66
62
|
private_class_method(:new) # don't instantiate this class directly
|
67
63
|
|
68
|
-
|
64
|
+
#: -> String
|
69
65
|
def inspect
|
70
66
|
"#<#{self.class.name} #{@value}>"
|
71
67
|
end
|
72
68
|
|
73
|
-
|
69
|
+
#: (untyped other) -> bool
|
74
70
|
def ==(other)
|
75
71
|
!!(super(other) && @value == other.value)
|
76
72
|
end
|
@@ -85,42 +81,42 @@ module CLI
|
|
85
81
|
end
|
86
82
|
|
87
83
|
class Argument < Node
|
88
|
-
|
84
|
+
#: String
|
89
85
|
attr_reader :value
|
90
86
|
|
91
|
-
|
87
|
+
#: (String value) -> void
|
92
88
|
def initialize(value)
|
93
89
|
@value = value
|
94
90
|
super()
|
95
91
|
end
|
96
92
|
|
97
|
-
|
93
|
+
#: -> String
|
98
94
|
def inspect
|
99
95
|
"#<#{self.class.name} #{@value}>"
|
100
96
|
end
|
101
97
|
|
102
|
-
|
98
|
+
#: (untyped other) -> bool
|
103
99
|
def ==(other)
|
104
100
|
!!(super(other) && @value == other.value)
|
105
101
|
end
|
106
102
|
end
|
107
103
|
|
108
104
|
class Unparsed < Node
|
109
|
-
|
105
|
+
#: Array[String]
|
110
106
|
attr_reader :value
|
111
107
|
|
112
|
-
|
108
|
+
#: (Array[String] value) -> void
|
113
109
|
def initialize(value)
|
114
110
|
@value = value
|
115
111
|
super()
|
116
112
|
end
|
117
113
|
|
118
|
-
|
114
|
+
#: -> String
|
119
115
|
def inspect
|
120
116
|
"#<#{self.class.name} #{@value.join(" ")}>"
|
121
117
|
end
|
122
118
|
|
123
|
-
|
119
|
+
#: (untyped other) -> bool
|
124
120
|
def ==(other)
|
125
121
|
!!(super(other) && @value == other.value)
|
126
122
|
end
|
data/lib/cli/kit/args/parser.rb
CHANGED
@@ -6,32 +6,28 @@ module CLI
|
|
6
6
|
module Kit
|
7
7
|
module Args
|
8
8
|
class Parser
|
9
|
-
extend T::Sig
|
10
|
-
|
11
9
|
autoload :Node, 'cli/kit/args/parser/node'
|
12
10
|
|
13
11
|
Error = Class.new(Args::Error)
|
14
12
|
|
15
13
|
class InvalidOptionError < Error
|
16
|
-
|
17
|
-
sig { params(option: String).void }
|
14
|
+
#: (String option) -> void
|
18
15
|
def initialize(option)
|
19
16
|
super("invalid option -- '#{option}'")
|
20
17
|
end
|
21
18
|
end
|
22
19
|
|
23
20
|
class OptionRequiresAnArgumentError < Error
|
24
|
-
|
25
|
-
sig { params(option: String).void }
|
21
|
+
#: (String option) -> void
|
26
22
|
def initialize(option)
|
27
23
|
super("option requires an argument -- '#{option}'")
|
28
24
|
end
|
29
25
|
end
|
30
26
|
|
31
|
-
|
27
|
+
#: (Array[Tokenizer::Token] tokens) -> Array[Node]
|
32
28
|
def parse(tokens)
|
33
|
-
nodes =
|
34
|
-
args =
|
29
|
+
nodes = [] #: Array[Node]
|
30
|
+
args = tokens #: Array[Tokenizer::Token?]
|
35
31
|
args << nil # to make each_cons pass (args.last, nil) on the final round.
|
36
32
|
state = :init
|
37
33
|
# TODO: test that "--height -- 3" is parsed correctly.
|
@@ -40,7 +36,10 @@ module CLI
|
|
40
36
|
when :skip
|
41
37
|
state = :init
|
42
38
|
when :init
|
43
|
-
state, val = parse_token(
|
39
|
+
state, val = parse_token(
|
40
|
+
arg, #: as !nil
|
41
|
+
next_arg,
|
42
|
+
)
|
44
43
|
nodes << val
|
45
44
|
when :unparsed
|
46
45
|
unless arg.is_a?(Tokenizer::Token::UnparsedArgument)
|
@@ -60,17 +59,14 @@ module CLI
|
|
60
59
|
nodes
|
61
60
|
end
|
62
61
|
|
63
|
-
|
62
|
+
#: (Definition definition) -> void
|
64
63
|
def initialize(definition)
|
65
64
|
@defn = definition
|
66
65
|
end
|
67
66
|
|
68
67
|
private
|
69
68
|
|
70
|
-
|
71
|
-
params(token: Tokenizer::Token, next_token: T.nilable(Tokenizer::Token))
|
72
|
-
.returns([Symbol, Parser::Node])
|
73
|
-
end
|
69
|
+
#: (Tokenizer::Token token, Tokenizer::Token? next_token) -> [Symbol, Parser::Node]
|
74
70
|
def parse_token(token, next_token)
|
75
71
|
case token
|
76
72
|
when Tokenizer::Token::LongOptionName
|
@@ -104,7 +100,7 @@ module CLI
|
|
104
100
|
end
|
105
101
|
end
|
106
102
|
|
107
|
-
|
103
|
+
#: (Tokenizer::Token::OptionName arg, Tokenizer::Token? next_arg) -> Node
|
108
104
|
def parse_option(arg, next_arg)
|
109
105
|
case next_arg
|
110
106
|
when nil, Tokenizer::Token::LongOptionName,
|
@@ -6,43 +6,37 @@ module CLI
|
|
6
6
|
module Kit
|
7
7
|
module Args
|
8
8
|
module Tokenizer
|
9
|
-
extend T::Sig
|
10
|
-
|
11
9
|
Error = Class.new(Args::Error)
|
12
10
|
|
13
11
|
class InvalidShortOption < Error
|
14
|
-
|
15
|
-
sig { params(short_option: String).void }
|
12
|
+
#: (String short_option) -> void
|
16
13
|
def initialize(short_option)
|
17
14
|
super("invalid short option: '-#{short_option}'")
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
21
18
|
class InvalidCharInShortOption < Error
|
22
|
-
|
23
|
-
sig { params(short_option: String, char: String).void }
|
19
|
+
#: (String short_option, String char) -> void
|
24
20
|
def initialize(short_option, char)
|
25
21
|
super("invalid character '#{char}' in short option: '-#{short_option}'")
|
26
22
|
end
|
27
23
|
end
|
28
24
|
|
29
25
|
class Token
|
30
|
-
|
31
|
-
|
32
|
-
sig { returns(String) }
|
26
|
+
#: String
|
33
27
|
attr_reader :value
|
34
28
|
|
35
|
-
|
29
|
+
#: (String value) -> void
|
36
30
|
def initialize(value)
|
37
31
|
@value = value
|
38
32
|
end
|
39
33
|
|
40
|
-
|
34
|
+
#: -> String
|
41
35
|
def inspect
|
42
36
|
"#<#{self.class.name} #{@value}>"
|
43
37
|
end
|
44
38
|
|
45
|
-
|
39
|
+
#: (untyped other) -> bool
|
46
40
|
def ==(other)
|
47
41
|
self.class == other.class && @value == other.value
|
48
42
|
end
|
@@ -58,9 +52,7 @@ module CLI
|
|
58
52
|
end
|
59
53
|
|
60
54
|
class << self
|
61
|
-
|
62
|
-
|
63
|
-
sig { params(raw_args: T::Array[String]).returns(T::Array[Token]) }
|
55
|
+
#: (Array[String] raw_args) -> Array[Token]
|
64
56
|
def tokenize(raw_args)
|
65
57
|
args = []
|
66
58
|
|
@@ -76,12 +68,17 @@ module CLI
|
|
76
68
|
mode = :unparsed
|
77
69
|
when /\A--./
|
78
70
|
name, value = arg.split('=', 2)
|
79
|
-
|
71
|
+
name = name #: as !nil
|
72
|
+
args << Token::LongOptionName.new(
|
73
|
+
name[2..-1], #: as !nil
|
74
|
+
)
|
80
75
|
if value
|
81
76
|
args << Token::OptionValue.new(value)
|
82
77
|
end
|
83
78
|
when /\A-./
|
84
|
-
args.concat(tokenize_short_option(
|
79
|
+
args.concat(tokenize_short_option(
|
80
|
+
arg[1..-1], #: as !nil
|
81
|
+
))
|
85
82
|
else
|
86
83
|
args << if args.last.is_a?(Token::OptionName)
|
87
84
|
Token::OptionValueOrPositionalArgument.new(arg)
|
@@ -95,7 +92,7 @@ module CLI
|
|
95
92
|
args
|
96
93
|
end
|
97
94
|
|
98
|
-
|
95
|
+
#: (String arg) -> Array[Token]
|
99
96
|
def tokenize_short_option(arg)
|
100
97
|
args = []
|
101
98
|
mode = :init
|
data/lib/cli/kit/base_command.rb
CHANGED
@@ -4,28 +4,24 @@ require 'cli/kit'
|
|
4
4
|
|
5
5
|
module CLI
|
6
6
|
module Kit
|
7
|
+
# @abstract
|
7
8
|
class BaseCommand
|
8
|
-
extend T::Sig
|
9
|
-
extend T::Helpers
|
10
9
|
include CLI::Kit::CommandHelp
|
11
10
|
extend CLI::Kit::CommandHelp::ClassMethods
|
12
|
-
abstract!
|
13
11
|
|
14
12
|
class << self
|
15
|
-
|
16
|
-
|
17
|
-
sig { returns(T::Boolean) }
|
13
|
+
#: -> bool
|
18
14
|
def defined?
|
19
15
|
true
|
20
16
|
end
|
21
17
|
|
22
|
-
|
18
|
+
#: (Array[String] args, String command_name) -> void
|
23
19
|
def call(args, command_name)
|
24
20
|
new.call(args, command_name)
|
25
21
|
end
|
26
22
|
end
|
27
23
|
|
28
|
-
|
24
|
+
#: -> bool
|
29
25
|
def has_subcommands?
|
30
26
|
false
|
31
27
|
end
|
data/lib/cli/kit/command_help.rb
CHANGED
@@ -5,10 +5,9 @@ require 'cli/kit'
|
|
5
5
|
module CLI
|
6
6
|
module Kit
|
7
7
|
module CommandHelp
|
8
|
-
extend T::Sig
|
9
8
|
include Kernel # for sorbet
|
10
9
|
|
11
|
-
|
10
|
+
#: (Array[String] args, String name) -> void
|
12
11
|
def call(args, name)
|
13
12
|
begin
|
14
13
|
opts = self.class.opts_class
|
@@ -36,26 +35,24 @@ module CLI
|
|
36
35
|
end
|
37
36
|
|
38
37
|
# use to implement error handling
|
39
|
-
|
38
|
+
#: (untyped op, String name) -> void
|
40
39
|
def invoke_wrapper(op, name)
|
41
40
|
invoke(op, name)
|
42
41
|
end
|
43
42
|
|
44
|
-
|
43
|
+
#: (untyped op, String name) -> void
|
45
44
|
def invoke(op, name)
|
46
45
|
raise(NotImplementedError, '#invoke must be implemented, or #call overridden')
|
47
46
|
end
|
48
47
|
|
49
48
|
class << self
|
50
|
-
|
51
|
-
|
52
|
-
sig { params(tool_name: String).void }
|
49
|
+
#: String
|
53
50
|
attr_writer :tool_name
|
54
51
|
|
55
|
-
|
52
|
+
#: Integer
|
56
53
|
attr_writer :max_desc_length
|
57
54
|
|
58
|
-
|
55
|
+
#: -> String
|
59
56
|
def _tool_name
|
60
57
|
unless @tool_name
|
61
58
|
raise 'You must set CLI::Kit::CommandHelp.tool_name='
|
@@ -64,14 +61,13 @@ module CLI
|
|
64
61
|
@tool_name
|
65
62
|
end
|
66
63
|
|
67
|
-
|
64
|
+
#: -> Integer
|
68
65
|
def _max_desc_length
|
69
66
|
@max_desc_length || 80
|
70
67
|
end
|
71
68
|
end
|
72
69
|
|
73
70
|
module ClassMethods
|
74
|
-
extend T::Sig
|
75
71
|
include Kernel # for sorbet
|
76
72
|
|
77
73
|
DEFAULT_HELP_SECTIONS = [
|
@@ -82,7 +78,7 @@ module CLI
|
|
82
78
|
:options,
|
83
79
|
]
|
84
80
|
|
85
|
-
|
81
|
+
#: -> String
|
86
82
|
def build_help
|
87
83
|
h = (@help_sections || DEFAULT_HELP_SECTIONS).map do |section|
|
88
84
|
case section
|
@@ -103,7 +99,7 @@ module CLI
|
|
103
99
|
CLI::UI.fmt(h)
|
104
100
|
end
|
105
101
|
|
106
|
-
|
102
|
+
#: -> String
|
107
103
|
def _command_name
|
108
104
|
return @command_name if @command_name
|
109
105
|
|
@@ -111,12 +107,12 @@ module CLI
|
|
111
107
|
last_camel.gsub(/([a-z])([A-Z])/, '\1-\2').downcase
|
112
108
|
end
|
113
109
|
|
114
|
-
|
110
|
+
#: -> String
|
115
111
|
def _desc
|
116
112
|
@desc
|
117
113
|
end
|
118
114
|
|
119
|
-
|
115
|
+
#: -> String
|
120
116
|
def build_desc
|
121
117
|
out = +"{{command:#{CommandHelp._tool_name} #{_command_name}}}"
|
122
118
|
if @desc
|
@@ -125,14 +121,15 @@ module CLI
|
|
125
121
|
"{{bold:#{out}}}"
|
126
122
|
end
|
127
123
|
|
128
|
-
|
124
|
+
#: -> untyped
|
129
125
|
def opts_class
|
130
|
-
|
126
|
+
uself = self #: as untyped
|
127
|
+
uself.const_get(:Opts) # rubocop:disable Sorbet/ConstantsFromStrings
|
131
128
|
rescue NameError
|
132
129
|
Class.new(CLI::Kit::Opts)
|
133
130
|
end
|
134
131
|
|
135
|
-
|
132
|
+
#: -> String?
|
136
133
|
def build_options
|
137
134
|
opts = opts_class
|
138
135
|
return unless opts
|
@@ -151,7 +148,7 @@ module CLI
|
|
151
148
|
|
152
149
|
return if @defn.options.empty? && @defn.flags.empty?
|
153
150
|
|
154
|
-
merged =
|
151
|
+
merged = @defn.options #: Array[(Args::Definition::Option | Args::Definition::Flag)]
|
155
152
|
merged += @defn.flags
|
156
153
|
merged.sort_by!(&:name)
|
157
154
|
"{{bold:Options:}}\n" + merged.map do |o|
|
@@ -179,12 +176,12 @@ module CLI
|
|
179
176
|
end.join("\n")
|
180
177
|
end
|
181
178
|
|
182
|
-
|
179
|
+
#: (Array[Symbol] sections) -> void
|
183
180
|
def help_sections(sections)
|
184
181
|
@help_sections = sections
|
185
182
|
end
|
186
183
|
|
187
|
-
|
184
|
+
#: (String command_name) -> void
|
188
185
|
def command_name(command_name)
|
189
186
|
if @command_name
|
190
187
|
raise(ArgumentError, "Command name already set to #{@command_name}")
|
@@ -193,7 +190,7 @@ module CLI
|
|
193
190
|
@command_name = command_name
|
194
191
|
end
|
195
192
|
|
196
|
-
|
193
|
+
#: (String desc) -> void
|
197
194
|
def desc(desc)
|
198
195
|
# A limit of 80 characters has been chosen to fit on standard terminal configurations. `long_desc` is
|
199
196
|
# available when descriptions don't fit nicely in that space. If you're using CLI::Kit for an application
|
@@ -213,7 +210,7 @@ module CLI
|
|
213
210
|
@desc = desc
|
214
211
|
end
|
215
212
|
|
216
|
-
|
213
|
+
#: (String long_desc) -> void
|
217
214
|
def long_desc(long_desc)
|
218
215
|
if @long_desc
|
219
216
|
raise(ArgumentError, 'long description already set')
|
@@ -222,7 +219,7 @@ module CLI
|
|
222
219
|
@long_desc = long_desc
|
223
220
|
end
|
224
221
|
|
225
|
-
|
222
|
+
#: -> String
|
226
223
|
def build_usage
|
227
224
|
'{{bold:Usage:}}' + case (@usage || []).size
|
228
225
|
when 0
|
@@ -236,7 +233,7 @@ module CLI
|
|
236
233
|
end
|
237
234
|
end
|
238
235
|
|
239
|
-
|
236
|
+
#: -> String?
|
240
237
|
def build_examples
|
241
238
|
return unless @examples
|
242
239
|
|
@@ -254,13 +251,13 @@ module CLI
|
|
254
251
|
end.join("\n\n")
|
255
252
|
end
|
256
253
|
|
257
|
-
|
254
|
+
#: (String usage) -> void
|
258
255
|
def usage(usage)
|
259
256
|
@usage ||= []
|
260
257
|
@usage << usage
|
261
258
|
end
|
262
259
|
|
263
|
-
|
260
|
+
#: (String command, String? explanation) -> void
|
264
261
|
def example(command, explanation)
|
265
262
|
@examples ||= []
|
266
263
|
@examples << [command, explanation]
|
@@ -5,58 +5,60 @@ require 'cli/kit'
|
|
5
5
|
module CLI
|
6
6
|
module Kit
|
7
7
|
class CommandRegistry
|
8
|
-
|
8
|
+
#: type command_or_proc = singleton(CLI::Kit::BaseCommand) | ^() -> singleton(CLI::Kit::BaseCommand)
|
9
9
|
|
10
|
-
|
11
|
-
T.any(T.class_of(CLI::Kit::BaseCommand), T.proc.returns(T.class_of(CLI::Kit::BaseCommand)))
|
12
|
-
end
|
13
|
-
|
14
|
-
sig { returns(T::Hash[String, CommandOrProc]) }
|
10
|
+
#: Hash[String, command_or_proc]
|
15
11
|
attr_reader :commands
|
16
12
|
|
17
|
-
|
13
|
+
#: Hash[String, String]
|
18
14
|
attr_reader :aliases
|
19
15
|
|
16
|
+
# @interface
|
20
17
|
module ContextualResolver
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def command_names; end
|
18
|
+
# @abstract
|
19
|
+
#: -> Array[String]
|
20
|
+
def command_names
|
21
|
+
raise(NotImplementedError)
|
22
|
+
end
|
27
23
|
|
28
|
-
|
29
|
-
|
24
|
+
# @abstract
|
25
|
+
#: -> Hash[String, String]
|
26
|
+
def aliases
|
27
|
+
raise(NotImplementedError)
|
28
|
+
end
|
30
29
|
|
31
|
-
|
32
|
-
|
30
|
+
# @abstract
|
31
|
+
#: (String) -> singleton(CLI::Kit::BaseCommand)
|
32
|
+
def command_class(_name)
|
33
|
+
raise(NotImplementedError)
|
34
|
+
end
|
33
35
|
end
|
34
36
|
|
35
37
|
module NullContextualResolver
|
36
|
-
extend T::Sig
|
37
38
|
extend ContextualResolver
|
38
39
|
|
39
40
|
class << self
|
40
|
-
|
41
|
-
|
42
|
-
sig { override.returns(T::Array[String]) }
|
41
|
+
# @override
|
42
|
+
#: -> Array[String]
|
43
43
|
def command_names
|
44
44
|
[]
|
45
45
|
end
|
46
46
|
|
47
|
-
|
47
|
+
# @override
|
48
|
+
#: -> Hash[String, String]
|
48
49
|
def aliases
|
49
50
|
{}
|
50
51
|
end
|
51
52
|
|
52
|
-
|
53
|
+
# @override
|
54
|
+
#: (String _name) -> singleton(CLI::Kit::BaseCommand)
|
53
55
|
def command_class(_name)
|
54
56
|
raise(CLI::Kit::Abort, 'Cannot be called on the NullContextualResolver since command_names is empty')
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
59
|
-
|
61
|
+
#: (default: String, ?contextual_resolver: ContextualResolver) -> void
|
60
62
|
def initialize(default:, contextual_resolver: NullContextualResolver)
|
61
63
|
@commands = {}
|
62
64
|
@aliases = {}
|
@@ -64,47 +66,49 @@ module CLI
|
|
64
66
|
@contextual_resolver = contextual_resolver
|
65
67
|
end
|
66
68
|
|
67
|
-
|
69
|
+
#: -> Hash[String, singleton(CLI::Kit::BaseCommand)]
|
68
70
|
def resolved_commands
|
69
71
|
@commands.each_with_object({}) do |(k, v), a|
|
70
72
|
a[k] = resolve_class(v)
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
74
|
-
|
76
|
+
#: (command_or_proc const, String name) -> void
|
75
77
|
def add(const, name)
|
76
78
|
commands[name] = const
|
77
79
|
end
|
78
80
|
|
79
|
-
|
81
|
+
#: (String? name) -> [singleton(CLI::Kit::BaseCommand)?, String]
|
80
82
|
def lookup_command(name)
|
81
83
|
name = @default if name.to_s.empty?
|
82
|
-
resolve_command(
|
84
|
+
resolve_command(
|
85
|
+
name, #: as !nil
|
86
|
+
)
|
83
87
|
end
|
84
88
|
|
85
|
-
|
89
|
+
#: (String from, String to) -> void
|
86
90
|
def add_alias(from, to)
|
87
91
|
aliases[from] = to unless aliases[from]
|
88
92
|
end
|
89
93
|
|
90
|
-
|
94
|
+
#: -> Array[String]
|
91
95
|
def command_names
|
92
96
|
@contextual_resolver.command_names + commands.keys
|
93
97
|
end
|
94
98
|
|
95
|
-
|
99
|
+
#: (String name) -> bool
|
96
100
|
def exist?(name)
|
97
101
|
!resolve_command(name).first.nil?
|
98
102
|
end
|
99
103
|
|
100
104
|
private
|
101
105
|
|
102
|
-
|
106
|
+
#: (String name) -> String
|
103
107
|
def resolve_alias(name)
|
104
108
|
aliases[name] || @contextual_resolver.aliases.fetch(name, name)
|
105
109
|
end
|
106
110
|
|
107
|
-
|
111
|
+
#: (String name) -> [singleton(CLI::Kit::BaseCommand)?, String]
|
108
112
|
def resolve_command(name)
|
109
113
|
name = resolve_alias(name)
|
110
114
|
resolve_global_command(name) ||
|
@@ -112,7 +116,7 @@ module CLI
|
|
112
116
|
[nil, name]
|
113
117
|
end
|
114
118
|
|
115
|
-
|
119
|
+
#: (String name) -> [singleton(CLI::Kit::BaseCommand), String]?
|
116
120
|
def resolve_global_command(name)
|
117
121
|
klass = resolve_class(commands.fetch(name, nil))
|
118
122
|
return unless klass
|
@@ -122,7 +126,7 @@ module CLI
|
|
122
126
|
nil
|
123
127
|
end
|
124
128
|
|
125
|
-
|
129
|
+
#: (String name) -> [singleton(CLI::Kit::BaseCommand), String]?
|
126
130
|
def resolve_contextual_command(name)
|
127
131
|
found = @contextual_resolver.command_names.include?(name)
|
128
132
|
return unless found
|
@@ -130,7 +134,7 @@ module CLI
|
|
130
134
|
[@contextual_resolver.command_class(name), name]
|
131
135
|
end
|
132
136
|
|
133
|
-
|
137
|
+
#: (command_or_proc? class_or_proc) -> singleton(CLI::Kit::BaseCommand)?
|
134
138
|
def resolve_class(class_or_proc)
|
135
139
|
case class_or_proc
|
136
140
|
when nil
|