cli-kit 4.0.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/cla.yml +22 -0
- data/.github/workflows/ruby.yml +34 -2
- data/.gitignore +2 -0
- data/.rubocop.sorbet.yml +47 -0
- data/.rubocop.yml +16 -1
- data/Gemfile +10 -1
- data/Gemfile.lock +94 -18
- data/README.md +46 -3
- data/Rakefile +1 -0
- data/bin/onchange +30 -0
- data/bin/tapioca +29 -0
- data/bin/testunit +1 -0
- data/cli-kit.gemspec +2 -2
- data/dev.yml +35 -3
- data/examples/minimal/example.rb +3 -1
- data/examples/single-file/example.rb +25 -35
- data/gen/lib/gen/commands/help.rb +8 -10
- data/gen/lib/gen/commands/new.rb +23 -9
- data/gen/lib/gen/commands.rb +21 -9
- data/gen/lib/gen/entry_point.rb +12 -3
- data/gen/lib/gen/generator.rb +28 -7
- data/gen/lib/gen/help.rb +63 -0
- data/gen/lib/gen.rb +18 -23
- data/gen/template/bin/update-deps +2 -2
- data/gen/template/lib/__app__/commands.rb +1 -4
- data/gen/template/lib/__app__.rb +8 -17
- data/gen/template/test/example_test.rb +1 -1
- data/lib/cli/kit/args/definition.rb +344 -0
- data/lib/cli/kit/args/evaluation.rb +245 -0
- data/lib/cli/kit/args/parser/node.rb +132 -0
- data/lib/cli/kit/args/parser.rb +129 -0
- data/lib/cli/kit/args/tokenizer.rb +133 -0
- data/lib/cli/kit/args.rb +16 -0
- data/lib/cli/kit/base_command.rb +17 -32
- data/lib/cli/kit/command_help.rb +271 -0
- data/lib/cli/kit/command_registry.rb +69 -17
- data/lib/cli/kit/config.rb +25 -22
- data/lib/cli/kit/core_ext.rb +30 -0
- data/lib/cli/kit/error_handler.rb +131 -70
- data/lib/cli/kit/executor.rb +19 -3
- data/lib/cli/kit/ini.rb +31 -38
- data/lib/cli/kit/levenshtein.rb +12 -4
- data/lib/cli/kit/logger.rb +16 -2
- data/lib/cli/kit/opts.rb +301 -0
- data/lib/cli/kit/resolver.rb +8 -0
- data/lib/cli/kit/sorbet_runtime_stub.rb +156 -0
- data/lib/cli/kit/support/test_helper.rb +23 -14
- data/lib/cli/kit/support.rb +2 -0
- data/lib/cli/kit/system.rb +188 -54
- data/lib/cli/kit/util.rb +48 -103
- data/lib/cli/kit/version.rb +3 -1
- data/lib/cli/kit.rb +103 -7
- metadata +22 -10
- data/.github/probots.yml +0 -2
- data/lib/cli/kit/autocall.rb +0 -21
- data/lib/cli/kit/ruby_backports/enumerable.rb +0 -6
data/lib/cli/kit/ini.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require 'cli/kit'
|
4
|
+
|
1
5
|
module CLI
|
2
6
|
module Kit
|
3
7
|
# INI is a language similar to JSON or YAML, but simplied
|
@@ -13,87 +17,76 @@ module CLI
|
|
13
17
|
# See the ini_test.rb file for more examples
|
14
18
|
#
|
15
19
|
class Ini
|
20
|
+
extend T::Sig
|
21
|
+
|
22
|
+
sig { returns(T::Hash[String, T::Hash[String, String]]) }
|
16
23
|
attr_accessor :ini
|
17
24
|
|
18
|
-
|
25
|
+
sig do
|
26
|
+
params(path: T.nilable(String), config: T.nilable(String), default_section: String).void
|
27
|
+
end
|
28
|
+
def initialize(path = nil, config: nil, default_section: '[global]')
|
19
29
|
@config = if path && File.exist?(path)
|
20
30
|
File.readlines(path)
|
21
31
|
elsif config
|
22
32
|
config.lines
|
23
33
|
end
|
24
34
|
@ini = {}
|
25
|
-
@current_key =
|
26
|
-
@default_section = default_section
|
27
|
-
@convert_types = convert_types
|
35
|
+
@current_key = default_section
|
28
36
|
end
|
29
37
|
|
38
|
+
sig { returns(T::Hash[String, T::Hash[String, String]]) }
|
30
39
|
def parse
|
31
40
|
return @ini if @config.nil?
|
32
41
|
|
33
42
|
@config.each do |l|
|
34
43
|
l.strip!
|
35
44
|
|
36
|
-
# If section, then set current key, this will nest the setting
|
37
45
|
if section_designator?(l)
|
38
46
|
@current_key = l
|
39
|
-
|
40
|
-
# A new line will reset the current key
|
41
|
-
elsif l.strip.empty?
|
42
|
-
@current_key = nil
|
43
|
-
|
44
|
-
# Otherwise set the values
|
45
47
|
else
|
46
48
|
k, v = l.split('=', 2).map(&:strip)
|
47
|
-
set_val(k, v)
|
49
|
+
set_val(k, v) if k && v
|
48
50
|
end
|
49
51
|
end
|
52
|
+
|
50
53
|
@ini
|
51
54
|
end
|
52
55
|
|
56
|
+
sig { returns(String) }
|
53
57
|
def git_format
|
54
|
-
to_ini(
|
58
|
+
to_ini(git_format: true)
|
55
59
|
end
|
56
60
|
|
61
|
+
sig { returns(String) }
|
57
62
|
def to_s
|
58
|
-
to_ini
|
63
|
+
to_ini
|
59
64
|
end
|
60
65
|
|
61
66
|
private
|
62
67
|
|
63
|
-
|
68
|
+
sig { params(git_format: T::Boolean).returns(String) }
|
69
|
+
def to_ini(git_format: false)
|
64
70
|
optional_tab = git_format ? "\t" : ''
|
65
71
|
str = []
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
str << to_ini(v, git_format: git_format)
|
71
|
-
else
|
72
|
+
@ini.each do |section_designator, section|
|
73
|
+
str << '' unless str.empty? || git_format
|
74
|
+
str << section_designator
|
75
|
+
section.each do |k, v|
|
72
76
|
str << "#{optional_tab}#{k} = #{v}"
|
73
77
|
end
|
74
78
|
end
|
75
|
-
str
|
79
|
+
str.join("\n")
|
76
80
|
end
|
77
81
|
|
82
|
+
sig { params(key: String, val: String).void }
|
78
83
|
def set_val(key, val)
|
79
|
-
|
80
|
-
|
81
|
-
current_key =
|
82
|
-
if current_key
|
83
|
-
@ini[current_key] ||= {}
|
84
|
-
@ini[current_key][key] = typed_val(val)
|
85
|
-
else
|
86
|
-
@ini[key] = typed_val(val)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def typed_val(val)
|
91
|
-
return val.to_s unless @convert_types
|
92
|
-
return val.to_i if val =~ /^-?[0-9]+$/
|
93
|
-
return val.to_f if val =~ /^-?[0-9]+\.[0-9]*$/
|
94
|
-
val.to_s
|
84
|
+
current_key = @current_key
|
85
|
+
@ini[current_key] ||= {}
|
86
|
+
@ini[current_key][key] = val
|
95
87
|
end
|
96
88
|
|
89
|
+
sig { params(k: String).returns(T::Boolean) }
|
97
90
|
def section_designator?(k)
|
98
91
|
k.start_with?('[') && k.end_with?(']')
|
99
92
|
end
|
data/lib/cli/kit/levenshtein.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
# Copyright (c) 2014-2016 Yuki Nishijima
|
2
4
|
|
3
5
|
# MIT License
|
@@ -21,13 +23,18 @@
|
|
21
23
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
24
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
25
|
|
26
|
+
require 'cli/kit'
|
27
|
+
|
24
28
|
module CLI
|
25
29
|
module Kit
|
26
30
|
module Levenshtein
|
31
|
+
extend T::Sig
|
32
|
+
|
27
33
|
# This code is based directly on the Text gem implementation
|
28
34
|
# Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
|
29
35
|
#
|
30
36
|
# Returns a value representing the "cost" of transforming str1 into str2
|
37
|
+
sig { params(str1: String, str2: String).returns(Integer) }
|
31
38
|
def distance(str1, str2)
|
32
39
|
n = str1.length
|
33
40
|
m = str2.length
|
@@ -35,7 +42,7 @@ module CLI
|
|
35
42
|
return n if m.zero?
|
36
43
|
|
37
44
|
d = (0..m).to_a
|
38
|
-
x =
|
45
|
+
x = 0
|
39
46
|
|
40
47
|
# to avoid duplicating an enumerable object, create it outside of the loop
|
41
48
|
str2_codepoints = str2.codepoints
|
@@ -45,9 +52,9 @@ module CLI
|
|
45
52
|
while j < m
|
46
53
|
cost = char1 == str2_codepoints[j] ? 0 : 1
|
47
54
|
x = min3(
|
48
|
-
d[j + 1] + 1, # insertion
|
49
|
-
i + 1,
|
50
|
-
d[j] + cost # substitution
|
55
|
+
T.must(d[j + 1]) + 1, # insertion
|
56
|
+
i + 1, # deletion
|
57
|
+
T.must(d[j]) + cost, # substitution
|
51
58
|
)
|
52
59
|
d[j] = i
|
53
60
|
i = x
|
@@ -67,6 +74,7 @@ module CLI
|
|
67
74
|
# faster than `[a, b, c].min` and puts less GC pressure.
|
68
75
|
# See https://github.com/yuki24/did_you_mean/pull/1 for a performance
|
69
76
|
# benchmark.
|
77
|
+
sig { params(a: Integer, b: Integer, c: Integer).returns(Integer) }
|
70
78
|
def min3(a, b, c)
|
71
79
|
if a < b && a < c
|
72
80
|
a
|
data/lib/cli/kit/logger.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require 'cli/kit'
|
1
4
|
require 'logger'
|
2
5
|
require 'fileutils'
|
3
6
|
|
4
7
|
module CLI
|
5
8
|
module Kit
|
6
9
|
class Logger
|
10
|
+
extend T::Sig
|
11
|
+
|
7
12
|
MAX_LOG_SIZE = 5 * 1024 * 1000 # 5MB
|
8
13
|
MAX_NUM_LOGS = 10
|
9
14
|
|
10
15
|
# Constructor for CLI::Kit::Logger
|
11
16
|
#
|
12
17
|
# @param debug_log_file [String] path to the file where debug logs should be stored
|
18
|
+
sig { params(debug_log_file: String, env_debug_name: String).void }
|
13
19
|
def initialize(debug_log_file:, env_debug_name: 'DEBUG')
|
14
20
|
FileUtils.mkpath(File.dirname(debug_log_file))
|
15
21
|
@debug_logger = ::Logger.new(debug_log_file, MAX_NUM_LOGS, MAX_LOG_SIZE)
|
@@ -21,6 +27,7 @@ module CLI
|
|
21
27
|
#
|
22
28
|
# @param msg [String] the message to log
|
23
29
|
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
30
|
+
sig { params(msg: String, debug: T::Boolean).void }
|
24
31
|
def info(msg, debug: true)
|
25
32
|
$stdout.puts CLI::UI.fmt(msg)
|
26
33
|
@debug_logger.info(format_debug(msg)) if debug
|
@@ -31,6 +38,7 @@ module CLI
|
|
31
38
|
#
|
32
39
|
# @param msg [String] the message to log
|
33
40
|
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
41
|
+
sig { params(msg: String, debug: T::Boolean).void }
|
34
42
|
def warn(msg, debug: true)
|
35
43
|
$stdout.puts CLI::UI.fmt("{{yellow:#{msg}}}")
|
36
44
|
@debug_logger.warn(format_debug(msg)) if debug
|
@@ -41,6 +49,7 @@ module CLI
|
|
41
49
|
#
|
42
50
|
# @param msg [String] the message to log
|
43
51
|
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
52
|
+
sig { params(msg: String, debug: T::Boolean).void }
|
44
53
|
def error(msg, debug: true)
|
45
54
|
$stderr.puts CLI::UI.fmt("{{red:#{msg}}}")
|
46
55
|
@debug_logger.error(format_debug(msg)) if debug
|
@@ -51,6 +60,7 @@ module CLI
|
|
51
60
|
#
|
52
61
|
# @param msg [String] the message to log
|
53
62
|
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
63
|
+
sig { params(msg: String, debug: T::Boolean).void }
|
54
64
|
def fatal(msg, debug: true)
|
55
65
|
$stderr.puts CLI::UI.fmt("{{red:{{bold:Fatal:}} #{msg}}}")
|
56
66
|
@debug_logger.fatal(format_debug(msg)) if debug
|
@@ -60,6 +70,7 @@ module CLI
|
|
60
70
|
# Logs to the debug file, taking into account CLI::UI::StdoutRouter.current_id
|
61
71
|
#
|
62
72
|
# @param msg [String] the message to log
|
73
|
+
sig { params(msg: String).void }
|
63
74
|
def debug(msg)
|
64
75
|
$stdout.puts CLI::UI.fmt(msg) if debug?
|
65
76
|
@debug_logger.debug(format_debug(msg))
|
@@ -67,15 +78,18 @@ module CLI
|
|
67
78
|
|
68
79
|
private
|
69
80
|
|
81
|
+
sig { params(msg: String).returns(String) }
|
70
82
|
def format_debug(msg)
|
71
83
|
msg = CLI::UI.fmt(msg)
|
72
84
|
return msg unless CLI::UI::StdoutRouter.current_id
|
73
|
-
|
85
|
+
|
86
|
+
"[#{CLI::UI::StdoutRouter.current_id&.fetch(:id, nil)}] #{msg}"
|
74
87
|
end
|
75
88
|
|
89
|
+
sig { returns(T::Boolean) }
|
76
90
|
def debug?
|
77
91
|
val = ENV[@env_debug_name]
|
78
|
-
val && val != '0' && val != ''
|
92
|
+
!!val && val != '0' && val != ''
|
79
93
|
end
|
80
94
|
end
|
81
95
|
end
|
data/lib/cli/kit/opts.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require 'cli/kit'
|
4
|
+
|
5
|
+
module CLI
|
6
|
+
module Kit
|
7
|
+
class Opts
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
module Mixin
|
11
|
+
extend T::Sig
|
12
|
+
include Kernel
|
13
|
+
|
14
|
+
module MixinClassMethods
|
15
|
+
extend T::Sig
|
16
|
+
|
17
|
+
sig { params(included_module: Module).void }
|
18
|
+
def include(included_module)
|
19
|
+
super
|
20
|
+
return unless included_module.is_a?(MixinClassMethods)
|
21
|
+
|
22
|
+
included_module.tracked_methods.each { |m| track_method(m) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# No signature - Sorbet uses method_added internally, so can't verify it
|
26
|
+
def method_added(method_name) # rubocop:disable Sorbet/EnforceSignatures
|
27
|
+
super
|
28
|
+
track_method(method_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(method_name: Symbol).void }
|
32
|
+
def track_method(method_name)
|
33
|
+
@tracked_methods ||= []
|
34
|
+
@tracked_methods << method_name unless @tracked_methods.include?(method_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { returns(T::Array[Symbol]) }
|
38
|
+
def tracked_methods
|
39
|
+
@tracked_methods || []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
extend T::Sig
|
45
|
+
|
46
|
+
sig { params(klass: Module).void }
|
47
|
+
def included(klass)
|
48
|
+
klass.extend(MixinClassMethods)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
sig do
|
53
|
+
params(
|
54
|
+
name: Symbol,
|
55
|
+
short: T.nilable(String),
|
56
|
+
long: T.nilable(String),
|
57
|
+
desc: T.nilable(String),
|
58
|
+
default: T.any(NilClass, String, T.proc.returns(String)),
|
59
|
+
).returns(T.nilable(String))
|
60
|
+
end
|
61
|
+
def option(name: infer_name, short: nil, long: nil, desc: nil, default: nil)
|
62
|
+
unless default.nil?
|
63
|
+
raise(ArgumentError, 'declare options with non-nil defaults using `option!` instead of `option`')
|
64
|
+
end
|
65
|
+
|
66
|
+
case @obj
|
67
|
+
when Args::Definition
|
68
|
+
@obj.add_option(
|
69
|
+
name, short: short, long: long, desc: desc, default: default
|
70
|
+
)
|
71
|
+
'(result unavailable)'
|
72
|
+
when Args::Evaluation
|
73
|
+
@obj.opt.send(name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
sig do
|
78
|
+
params(
|
79
|
+
name: Symbol,
|
80
|
+
short: T.nilable(String),
|
81
|
+
long: T.nilable(String),
|
82
|
+
desc: T.nilable(String),
|
83
|
+
default: T.any(NilClass, String, T.proc.returns(String)),
|
84
|
+
).returns(String)
|
85
|
+
end
|
86
|
+
def option!(name: infer_name, short: nil, long: nil, desc: nil, default: nil)
|
87
|
+
case @obj
|
88
|
+
when Args::Definition
|
89
|
+
@obj.add_option(
|
90
|
+
name, short: short, long: long, desc: desc, default: default
|
91
|
+
)
|
92
|
+
'(result unavailable)'
|
93
|
+
when Args::Evaluation
|
94
|
+
@obj.opt.send(name)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
sig do
|
99
|
+
params(
|
100
|
+
name: Symbol,
|
101
|
+
short: T.nilable(String),
|
102
|
+
long: T.nilable(String),
|
103
|
+
desc: T.nilable(String),
|
104
|
+
default: T.any(T::Array[String], T.proc.returns(T::Array[String])),
|
105
|
+
).returns(T::Array[String])
|
106
|
+
end
|
107
|
+
def multi_option(name: infer_name, short: nil, long: nil, desc: nil, default: [])
|
108
|
+
case @obj
|
109
|
+
when Args::Definition
|
110
|
+
@obj.add_option(
|
111
|
+
name, short: short, long: long, desc: desc, default: default, multi: true
|
112
|
+
)
|
113
|
+
['(result unavailable)']
|
114
|
+
when Args::Evaluation
|
115
|
+
@obj.opt.send(name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
sig do
|
120
|
+
params(
|
121
|
+
name: Symbol,
|
122
|
+
short: T.nilable(String),
|
123
|
+
long: T.nilable(String),
|
124
|
+
desc: T.nilable(String),
|
125
|
+
).returns(T::Boolean)
|
126
|
+
end
|
127
|
+
def flag(name: infer_name, short: nil, long: nil, desc: nil)
|
128
|
+
case @obj
|
129
|
+
when Args::Definition
|
130
|
+
@obj.add_flag(name, short: short, long: long, desc: desc)
|
131
|
+
false
|
132
|
+
when Args::Evaluation
|
133
|
+
@obj.flag.send(name)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
sig { params(name: Symbol, desc: T.nilable(String)).returns(String) }
|
138
|
+
def position!(name: infer_name, desc: nil)
|
139
|
+
case @obj
|
140
|
+
when Args::Definition
|
141
|
+
@obj.add_position(name, desc: desc, required: true, multi: false)
|
142
|
+
'(result unavailable)'
|
143
|
+
when Args::Evaluation
|
144
|
+
@obj.position.send(name)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
sig do
|
149
|
+
params(
|
150
|
+
name: Symbol,
|
151
|
+
desc: T.nilable(String),
|
152
|
+
default: T.any(NilClass, String, T.proc.returns(String)),
|
153
|
+
skip: T.any(
|
154
|
+
NilClass,
|
155
|
+
T.proc.returns(T::Boolean),
|
156
|
+
T.proc.params(arg0: String).returns(T::Boolean),
|
157
|
+
),
|
158
|
+
).returns(T.nilable(String))
|
159
|
+
end
|
160
|
+
def position(name: infer_name, desc: nil, default: nil, skip: nil)
|
161
|
+
case @obj
|
162
|
+
when Args::Definition
|
163
|
+
@obj.add_position(name, desc: desc, required: false, multi: false, default: default, skip: skip)
|
164
|
+
'(result unavailable)'
|
165
|
+
when Args::Evaluation
|
166
|
+
@obj.position.send(name)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
sig { params(name: Symbol, desc: T.nilable(String)).returns(T::Array[String]) }
|
171
|
+
def rest(name: infer_name, desc: nil)
|
172
|
+
case @obj
|
173
|
+
when Args::Definition
|
174
|
+
@obj.add_position(name, desc: desc, required: false, multi: true)
|
175
|
+
['(result unavailable)']
|
176
|
+
when Args::Evaluation
|
177
|
+
@obj.position.send(name)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
sig { returns(Symbol) }
|
184
|
+
def infer_name
|
185
|
+
to_skip = 1
|
186
|
+
Kernel.caller_locations&.each do |loc|
|
187
|
+
next if loc.path =~ /sorbet-runtime/
|
188
|
+
|
189
|
+
if to_skip > 0
|
190
|
+
to_skip -= 1
|
191
|
+
next
|
192
|
+
end
|
193
|
+
return(T.must(loc.label&.to_sym))
|
194
|
+
end
|
195
|
+
raise(ArgumentError, 'could not infer name')
|
196
|
+
end
|
197
|
+
end
|
198
|
+
include(Mixin)
|
199
|
+
|
200
|
+
DEFAULT_OPTIONS = [:helpflag]
|
201
|
+
|
202
|
+
sig { returns(T::Boolean) }
|
203
|
+
def helpflag
|
204
|
+
flag(name: :help, short: '-h', long: '--help', desc: 'Show this help message')
|
205
|
+
end
|
206
|
+
|
207
|
+
sig { returns(T::Array[String]) }
|
208
|
+
def unparsed
|
209
|
+
obj = assert_result!
|
210
|
+
obj.unparsed
|
211
|
+
end
|
212
|
+
|
213
|
+
sig do
|
214
|
+
params(
|
215
|
+
block: T.nilable(
|
216
|
+
T.proc.params(arg0: Symbol, arg1: T.nilable(String)).void,
|
217
|
+
),
|
218
|
+
).returns(T.untyped)
|
219
|
+
end
|
220
|
+
def each_option(&block)
|
221
|
+
return(enum_for(:each_option)) unless block_given?
|
222
|
+
|
223
|
+
obj = assert_result!
|
224
|
+
obj.defn.options.each do |opt|
|
225
|
+
name = opt.name
|
226
|
+
value = obj.opt.send(name)
|
227
|
+
yield(name, value)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
sig do
|
232
|
+
params(
|
233
|
+
block: T.nilable(
|
234
|
+
T.proc.params(arg0: Symbol, arg1: T::Boolean).void,
|
235
|
+
),
|
236
|
+
).returns(T.untyped)
|
237
|
+
end
|
238
|
+
def each_flag(&block)
|
239
|
+
return(enum_for(:each_flag)) unless block_given?
|
240
|
+
|
241
|
+
obj = assert_result!
|
242
|
+
obj.defn.flags.each do |flag|
|
243
|
+
name = flag.name
|
244
|
+
value = obj.flag.send(name)
|
245
|
+
yield(name, value)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
sig { params(name: String).returns(T.nilable(T.any(String, T::Boolean))) }
|
250
|
+
def [](name)
|
251
|
+
obj = assert_result!
|
252
|
+
if obj.opt.respond_to?(name)
|
253
|
+
obj.opt.send(name)
|
254
|
+
elsif obj.flag.respond_to?(name)
|
255
|
+
obj.flag.send(name)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
sig { params(name: String).returns(T.nilable(String)) }
|
260
|
+
def lookup_option(name)
|
261
|
+
obj = assert_result!
|
262
|
+
obj.opt.send(name)
|
263
|
+
rescue NoMethodError
|
264
|
+
# TODO: should we raise a KeyError?
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
|
268
|
+
sig { params(name: String).returns(T::Boolean) }
|
269
|
+
def lookup_flag(name)
|
270
|
+
obj = assert_result!
|
271
|
+
obj.flag.send(name)
|
272
|
+
rescue NoMethodError
|
273
|
+
false
|
274
|
+
end
|
275
|
+
|
276
|
+
sig { returns(Args::Evaluation) }
|
277
|
+
def assert_result!
|
278
|
+
raise(NotImplementedError, 'not implemented') if @obj.is_a?(Args::Definition)
|
279
|
+
|
280
|
+
@obj
|
281
|
+
end
|
282
|
+
|
283
|
+
sig { params(defn: Args::Definition).void }
|
284
|
+
def define!(defn)
|
285
|
+
@obj = defn
|
286
|
+
T.cast(self.class, Mixin::MixinClassMethods).tracked_methods.each do |m|
|
287
|
+
send(m)
|
288
|
+
end
|
289
|
+
DEFAULT_OPTIONS.each do |m|
|
290
|
+
send(m)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
sig { params(ev: Args::Evaluation).void }
|
295
|
+
def evaluate!(ev)
|
296
|
+
@obj = ev
|
297
|
+
ev.resolve_positions!
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
data/lib/cli/kit/resolver.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'cli/kit'
|
2
4
|
|
3
5
|
module CLI
|
4
6
|
module Kit
|
5
7
|
class Resolver
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(tool_name: String, command_registry: CLI::Kit::CommandRegistry).void }
|
6
11
|
def initialize(tool_name:, command_registry:)
|
7
12
|
@tool_name = tool_name
|
8
13
|
@command_registry = command_registry
|
9
14
|
end
|
10
15
|
|
16
|
+
sig { params(args: T::Array[String]).returns([T.class_of(CLI::Kit::BaseCommand), String, T::Array[String]]) }
|
11
17
|
def call(args)
|
12
18
|
args = args.dup
|
13
19
|
command_name = args.shift
|
@@ -24,6 +30,7 @@ module CLI
|
|
24
30
|
|
25
31
|
private
|
26
32
|
|
33
|
+
sig { params(name: T.nilable(String)).void }
|
27
34
|
def command_not_found(name)
|
28
35
|
CLI::UI::Frame.open('Command not found', color: :red, timing: false) do
|
29
36
|
$stderr.puts(CLI::UI.fmt("{{command:#{@tool_name} #{name}}} was not found"))
|
@@ -52,6 +59,7 @@ module CLI
|
|
52
59
|
end
|
53
60
|
end
|
54
61
|
|
62
|
+
sig { returns(T::Array[String]) }
|
55
63
|
def commands_and_aliases
|
56
64
|
@command_registry.command_names + @command_registry.aliases.keys
|
57
65
|
end
|