cli-kit 5.0.1 → 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/.github/workflows/ruby.yml +2 -2
- data/.rubocop.sorbet.yml +1 -0
- data/.rubocop.yml +1 -2
- data/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +47 -46
- data/Rakefile +0 -1
- data/bin/testunit +0 -1
- data/cli-kit.gemspec +1 -1
- data/dev.yml +3 -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 +41 -60
- 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 +25 -28
- 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 +19 -35
- 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 +34 -87
- data/lib/cli/kit/parse_args.rb +6 -6
- 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 +5 -9
- data/lib/cli/kit/sorbet_runtime_stub.rb +0 -154
data/lib/cli/kit/opts.rb
CHANGED
@@ -5,16 +5,11 @@ require 'cli/kit'
|
|
5
5
|
module CLI
|
6
6
|
module Kit
|
7
7
|
class Opts
|
8
|
-
extend T::Sig
|
9
|
-
|
10
8
|
module Mixin
|
11
|
-
extend T::Sig
|
12
9
|
include Kernel
|
13
10
|
|
14
11
|
module MixinClassMethods
|
15
|
-
|
16
|
-
|
17
|
-
sig { params(included_module: Module).void }
|
12
|
+
#: (Module included_module) -> void
|
18
13
|
def include(included_module)
|
19
14
|
super
|
20
15
|
return unless included_module.is_a?(MixinClassMethods)
|
@@ -28,36 +23,26 @@ module CLI
|
|
28
23
|
track_method(method_name)
|
29
24
|
end
|
30
25
|
|
31
|
-
|
26
|
+
#: (Symbol method_name) -> void
|
32
27
|
def track_method(method_name)
|
33
28
|
@tracked_methods ||= []
|
34
29
|
@tracked_methods << method_name unless @tracked_methods.include?(method_name)
|
35
30
|
end
|
36
31
|
|
37
|
-
|
32
|
+
#: -> Array[Symbol]
|
38
33
|
def tracked_methods
|
39
34
|
@tracked_methods || []
|
40
35
|
end
|
41
36
|
end
|
42
37
|
|
43
38
|
class << self
|
44
|
-
|
45
|
-
|
46
|
-
sig { params(klass: Module).void }
|
39
|
+
#: (Module klass) -> void
|
47
40
|
def included(klass)
|
48
41
|
klass.extend(MixinClassMethods)
|
49
42
|
end
|
50
43
|
end
|
51
44
|
|
52
|
-
|
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
|
45
|
+
#: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?, ?default: (String | ^-> String)?) -> String?
|
61
46
|
def option(name: infer_name, short: nil, long: nil, desc: nil, default: nil)
|
62
47
|
unless default.nil?
|
63
48
|
raise(ArgumentError, 'declare options with non-nil defaults using `option!` instead of `option`')
|
@@ -74,15 +59,7 @@ module CLI
|
|
74
59
|
end
|
75
60
|
end
|
76
61
|
|
77
|
-
|
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
|
62
|
+
#: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?, ?default: (String | ^-> String)?) -> String
|
86
63
|
def option!(name: infer_name, short: nil, long: nil, desc: nil, default: nil)
|
87
64
|
case @obj
|
88
65
|
when Args::Definition
|
@@ -95,15 +72,7 @@ module CLI
|
|
95
72
|
end
|
96
73
|
end
|
97
74
|
|
98
|
-
|
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
|
75
|
+
#: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?, ?default: (Array[String] | ^-> Array[String])) -> Array[String]
|
107
76
|
def multi_option(name: infer_name, short: nil, long: nil, desc: nil, default: [])
|
108
77
|
case @obj
|
109
78
|
when Args::Definition
|
@@ -116,14 +85,7 @@ module CLI
|
|
116
85
|
end
|
117
86
|
end
|
118
87
|
|
119
|
-
|
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
|
88
|
+
#: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?) -> bool
|
127
89
|
def flag(name: infer_name, short: nil, long: nil, desc: nil)
|
128
90
|
case @obj
|
129
91
|
when Args::Definition
|
@@ -134,7 +96,7 @@ module CLI
|
|
134
96
|
end
|
135
97
|
end
|
136
98
|
|
137
|
-
|
99
|
+
#: (?name: Symbol, ?desc: String?) -> String
|
138
100
|
def position!(name: infer_name, desc: nil)
|
139
101
|
case @obj
|
140
102
|
when Args::Definition
|
@@ -145,18 +107,7 @@ module CLI
|
|
145
107
|
end
|
146
108
|
end
|
147
109
|
|
148
|
-
|
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
|
110
|
+
#: (?name: Symbol, ?desc: String?, ?default: (String | ^-> String)?, ?skip: (^-> bool | ^(String arg0) -> bool)?) -> String?
|
160
111
|
def position(name: infer_name, desc: nil, default: nil, skip: nil)
|
161
112
|
case @obj
|
162
113
|
when Args::Definition
|
@@ -167,7 +118,7 @@ module CLI
|
|
167
118
|
end
|
168
119
|
end
|
169
120
|
|
170
|
-
|
121
|
+
#: (?name: Symbol, ?desc: String?) -> Array[String]
|
171
122
|
def rest(name: infer_name, desc: nil)
|
172
123
|
case @obj
|
173
124
|
when Args::Definition
|
@@ -180,7 +131,14 @@ module CLI
|
|
180
131
|
|
181
132
|
private
|
182
133
|
|
183
|
-
|
134
|
+
#: (String? label) -> Symbol?
|
135
|
+
def symbolize(label)
|
136
|
+
return if label.nil?
|
137
|
+
|
138
|
+
label.split('#').last&.to_sym
|
139
|
+
end
|
140
|
+
|
141
|
+
#: -> Symbol
|
184
142
|
def infer_name
|
185
143
|
to_skip = 1
|
186
144
|
Kernel.caller_locations.each do |loc|
|
@@ -190,7 +148,7 @@ module CLI
|
|
190
148
|
to_skip -= 1
|
191
149
|
next
|
192
150
|
end
|
193
|
-
return(
|
151
|
+
return symbolize(loc.label) #: as !nil
|
194
152
|
end
|
195
153
|
raise(ArgumentError, 'could not infer name')
|
196
154
|
end
|
@@ -199,26 +157,20 @@ module CLI
|
|
199
157
|
|
200
158
|
DEFAULT_OPTIONS = [:helpflag]
|
201
159
|
|
202
|
-
|
160
|
+
#: -> bool
|
203
161
|
def helpflag
|
204
162
|
flag(name: :help, short: '-h', long: '--help', desc: 'Show this help message')
|
205
163
|
end
|
206
164
|
|
207
|
-
|
165
|
+
#: -> Array[String]
|
208
166
|
def unparsed
|
209
167
|
obj = assert_result!
|
210
168
|
obj.unparsed
|
211
169
|
end
|
212
170
|
|
213
|
-
|
214
|
-
params(
|
215
|
-
block: T.nilable(
|
216
|
-
T.proc.params(arg0: Symbol, arg1: T.nilable(String)).void,
|
217
|
-
),
|
218
|
-
).returns(T.untyped)
|
219
|
-
end
|
171
|
+
#: ?{ (Symbol arg0, String? arg1) -> void } -> untyped
|
220
172
|
def each_option(&block)
|
221
|
-
return
|
173
|
+
return enum_for(:each_option) unless block_given?
|
222
174
|
|
223
175
|
obj = assert_result!
|
224
176
|
obj.defn.options.each do |opt|
|
@@ -228,15 +180,9 @@ module CLI
|
|
228
180
|
end
|
229
181
|
end
|
230
182
|
|
231
|
-
|
232
|
-
params(
|
233
|
-
block: T.nilable(
|
234
|
-
T.proc.params(arg0: Symbol, arg1: T::Boolean).void,
|
235
|
-
),
|
236
|
-
).returns(T.untyped)
|
237
|
-
end
|
183
|
+
#: ?{ (Symbol arg0, bool arg1) -> void } -> untyped
|
238
184
|
def each_flag(&block)
|
239
|
-
return
|
185
|
+
return enum_for(:each_flag) unless block_given?
|
240
186
|
|
241
187
|
obj = assert_result!
|
242
188
|
obj.defn.flags.each do |flag|
|
@@ -246,7 +192,7 @@ module CLI
|
|
246
192
|
end
|
247
193
|
end
|
248
194
|
|
249
|
-
|
195
|
+
#: (String name) -> (String | bool)?
|
250
196
|
def [](name)
|
251
197
|
obj = assert_result!
|
252
198
|
if obj.opt.respond_to?(name)
|
@@ -256,7 +202,7 @@ module CLI
|
|
256
202
|
end
|
257
203
|
end
|
258
204
|
|
259
|
-
|
205
|
+
#: (String name) -> String?
|
260
206
|
def lookup_option(name)
|
261
207
|
obj = assert_result!
|
262
208
|
obj.opt.send(name)
|
@@ -265,7 +211,7 @@ module CLI
|
|
265
211
|
nil
|
266
212
|
end
|
267
213
|
|
268
|
-
|
214
|
+
#: (String name) -> bool
|
269
215
|
def lookup_flag(name)
|
270
216
|
obj = assert_result!
|
271
217
|
obj.flag.send(name)
|
@@ -273,17 +219,18 @@ module CLI
|
|
273
219
|
false
|
274
220
|
end
|
275
221
|
|
276
|
-
|
222
|
+
#: -> Args::Evaluation
|
277
223
|
def assert_result!
|
278
224
|
raise(NotImplementedError, 'not implemented') if @obj.is_a?(Args::Definition)
|
279
225
|
|
280
226
|
@obj
|
281
227
|
end
|
282
228
|
|
283
|
-
|
229
|
+
#: (Args::Definition defn) -> void
|
284
230
|
def define!(defn)
|
285
231
|
@obj = defn
|
286
|
-
|
232
|
+
klass = self.class #: as Mixin::MixinClassMethods
|
233
|
+
klass.tracked_methods.each do |m|
|
287
234
|
send(m)
|
288
235
|
end
|
289
236
|
DEFAULT_OPTIONS.each do |m|
|
@@ -291,7 +238,7 @@ module CLI
|
|
291
238
|
end
|
292
239
|
end
|
293
240
|
|
294
|
-
|
241
|
+
#: (Args::Evaluation ev) -> void
|
295
242
|
def evaluate!(ev)
|
296
243
|
@obj = ev
|
297
244
|
ev.resolve_positions!
|
data/lib/cli/kit/parse_args.rb
CHANGED
@@ -6,13 +6,12 @@ module CLI
|
|
6
6
|
# because sorbet type-checking takes the pedantic route that module doesn't include Kernel, therefore
|
7
7
|
# this is necessary (even tho it's ~probably fine~)
|
8
8
|
include Kernel
|
9
|
-
extend T::Sig
|
10
9
|
|
11
|
-
#
|
10
|
+
# untyped is used in two places. The interpretation of dynamic values from the provided `opts`
|
12
11
|
# and the resulting args[:opts] is pretty broad. There seems to be minimal value in expressing a
|
13
|
-
# tighter subset of
|
12
|
+
# tighter subset of untyped.
|
14
13
|
|
15
|
-
|
14
|
+
#: ((Array | String) args, Hash[Symbol, Array[untyped]] opts_defn) -> Hash[Symbol, untyped]
|
16
15
|
def parse_args(args, opts_defn)
|
17
16
|
start_opts, parser_config = opts_defn.reduce([{}, []]) do |(ini, pcfg), (n, cfg)|
|
18
17
|
(vals, desc, short, klass) = cfg
|
@@ -33,7 +32,8 @@ module CLI
|
|
33
32
|
long = "--#{n.to_s.tr("_", "-")}" + (mark.nil? ? '' : " #{mark}")
|
34
33
|
opt_args = klass.nil? ? [short, long, desc] : [short, long, klass, desc]
|
35
34
|
|
36
|
-
|
35
|
+
unsafe_opt_p = opt_p #: as untyped
|
36
|
+
unsafe_opt_p.on(*opt_args) do |v|
|
37
37
|
acc_opts[n] = if acc_opts.key?(n)
|
38
38
|
Array(acc_opts[n]) + Array(v || def_val)
|
39
39
|
else
|
@@ -43,7 +43,7 @@ module CLI
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
arg_v = args.strip.split(/\s+/).map(&:strip)
|
46
|
+
arg_v = (args.is_a?(Array) ? args : args.strip.split(/\s+/)).map(&:strip)
|
47
47
|
sub = prsr.parse(arg_v)
|
48
48
|
|
49
49
|
{ opts: start_opts.merge(acc_opts) }.tap do |a|
|
data/lib/cli/kit/resolver.rb
CHANGED
@@ -5,15 +5,13 @@ require 'cli/kit'
|
|
5
5
|
module CLI
|
6
6
|
module Kit
|
7
7
|
class Resolver
|
8
|
-
|
9
|
-
|
10
|
-
sig { params(tool_name: String, command_registry: CLI::Kit::CommandRegistry).void }
|
8
|
+
#: (tool_name: String, command_registry: CLI::Kit::CommandRegistry) -> void
|
11
9
|
def initialize(tool_name:, command_registry:)
|
12
10
|
@tool_name = tool_name
|
13
11
|
@command_registry = command_registry
|
14
12
|
end
|
15
13
|
|
16
|
-
|
14
|
+
#: (Array[String] args) -> [singleton(CLI::Kit::BaseCommand), String, Array[String]]
|
17
15
|
def call(args)
|
18
16
|
args = args.dup
|
19
17
|
command_name = args.shift
|
@@ -30,7 +28,7 @@ module CLI
|
|
30
28
|
|
31
29
|
private
|
32
30
|
|
33
|
-
|
31
|
+
#: (String? name) -> void
|
34
32
|
def command_not_found(name)
|
35
33
|
CLI::UI::Frame.open('Command not found', color: :red, timing: false) do
|
36
34
|
$stderr.puts(CLI::UI.fmt("{{command:#{@tool_name} #{name}}} was not found"))
|
@@ -59,7 +57,7 @@ module CLI
|
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
62
|
-
|
60
|
+
#: -> Array[String]
|
63
61
|
def commands_and_aliases
|
64
62
|
@command_registry.command_names + @command_registry.aliases.keys
|
65
63
|
end
|
@@ -14,7 +14,8 @@ module CLI
|
|
14
14
|
CLI::Kit::System.reset!
|
15
15
|
# this is in minitest, but sorbet doesn't know that. probably we
|
16
16
|
# could structure this better.
|
17
|
-
|
17
|
+
uself = self #: as untyped
|
18
|
+
uself.assert(false, errors) if should_raise && !errors.nil?
|
18
19
|
errors
|
19
20
|
end
|
20
21
|
|
@@ -67,7 +68,8 @@ module CLI
|
|
67
68
|
|
68
69
|
# Otherwise handle the command
|
69
70
|
if expected_command[:allow]
|
70
|
-
|
71
|
+
uself = self #: as untyped
|
72
|
+
uself.original_system(*a, sudo: sudo, env: env, **kwargs)
|
71
73
|
else
|
72
74
|
FakeSuccess.new(expected_command[:success])
|
73
75
|
end
|
@@ -83,7 +85,8 @@ module CLI
|
|
83
85
|
|
84
86
|
# Otherwise handle the command
|
85
87
|
if expected_command[:allow]
|
86
|
-
|
88
|
+
uself = self #: as untyped
|
89
|
+
uself.original_capture2(*a, sudo: sudo, env: env, **kwargs)
|
87
90
|
else
|
88
91
|
[
|
89
92
|
expected_command[:stdout],
|
@@ -102,7 +105,8 @@ module CLI
|
|
102
105
|
|
103
106
|
# Otherwise handle the command
|
104
107
|
if expected_command[:allow]
|
105
|
-
|
108
|
+
uself = self #: as untyped
|
109
|
+
uself.original_capture2e(*a, sudo: sudo, env: env, **kwargs)
|
106
110
|
else
|
107
111
|
[
|
108
112
|
expected_command[:stdout],
|
@@ -121,7 +125,8 @@ module CLI
|
|
121
125
|
|
122
126
|
# Otherwise handle the command
|
123
127
|
if expected_command[:allow]
|
124
|
-
|
128
|
+
uself = self #: as untyped
|
129
|
+
uself.original_capture3(*a, sudo: sudo, env: env, **kwargs)
|
125
130
|
else
|
126
131
|
[
|
127
132
|
expected_command[:stdout],
|
data/lib/cli/kit/system.rb
CHANGED
@@ -9,8 +9,6 @@ module CLI
|
|
9
9
|
module System
|
10
10
|
SUDO_PROMPT = CLI::UI.fmt('{{info:(sudo)}} Password: ')
|
11
11
|
class << self
|
12
|
-
extend T::Sig
|
13
|
-
|
14
12
|
# Ask for sudo access with a message explaning the need for it
|
15
13
|
# Will make subsequent commands capable of running with sudo for a period of time
|
16
14
|
#
|
@@ -20,7 +18,7 @@ module CLI
|
|
20
18
|
# #### Usage
|
21
19
|
# `ctx.sudo_reason("We need to do a thing")`
|
22
20
|
#
|
23
|
-
|
21
|
+
#: (String msg) -> void
|
24
22
|
def sudo_reason(msg)
|
25
23
|
# See if sudo has a cached password
|
26
24
|
%x(env SUDO_ASKPASS=/usr/bin/false sudo -A true > /dev/null 2>&1)
|
@@ -48,16 +46,7 @@ module CLI
|
|
48
46
|
# #### Usage
|
49
47
|
# `out, stat = CLI::Kit::System.capture2('ls', 'a_folder')`
|
50
48
|
#
|
51
|
-
|
52
|
-
params(
|
53
|
-
cmd: String,
|
54
|
-
args: String,
|
55
|
-
sudo: T.any(T::Boolean, String),
|
56
|
-
env: T::Hash[String, T.nilable(String)],
|
57
|
-
kwargs: T.untyped,
|
58
|
-
)
|
59
|
-
.returns([String, Process::Status])
|
60
|
-
end
|
49
|
+
#: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) -> [String, Process::Status]
|
61
50
|
def capture2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs)
|
62
51
|
delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2)
|
63
52
|
end
|
@@ -79,16 +68,7 @@ module CLI
|
|
79
68
|
# #### Usage
|
80
69
|
# `out_and_err, stat = CLI::Kit::System.capture2e('ls', 'a_folder')`
|
81
70
|
#
|
82
|
-
|
83
|
-
params(
|
84
|
-
cmd: String,
|
85
|
-
args: String,
|
86
|
-
sudo: T.any(T::Boolean, String),
|
87
|
-
env: T::Hash[String, T.nilable(String)],
|
88
|
-
kwargs: T.untyped,
|
89
|
-
)
|
90
|
-
.returns([String, Process::Status])
|
91
|
-
end
|
71
|
+
#: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) -> [String, Process::Status]
|
92
72
|
def capture2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs)
|
93
73
|
delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2e)
|
94
74
|
end
|
@@ -111,70 +91,22 @@ module CLI
|
|
111
91
|
# #### Usage
|
112
92
|
# `out, err, stat = CLI::Kit::System.capture3('ls', 'a_folder')`
|
113
93
|
#
|
114
|
-
|
115
|
-
params(
|
116
|
-
cmd: String,
|
117
|
-
args: String,
|
118
|
-
sudo: T.any(T::Boolean, String),
|
119
|
-
env: T::Hash[String, T.nilable(String)],
|
120
|
-
kwargs: T.untyped,
|
121
|
-
)
|
122
|
-
.returns([String, String, Process::Status])
|
123
|
-
end
|
94
|
+
#: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) -> [String, String, Process::Status]
|
124
95
|
def capture3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs)
|
125
96
|
delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture3)
|
126
97
|
end
|
127
98
|
|
128
|
-
|
129
|
-
params(
|
130
|
-
cmd: String,
|
131
|
-
args: String,
|
132
|
-
sudo: T.any(T::Boolean, String),
|
133
|
-
env: T::Hash[String, T.nilable(String)],
|
134
|
-
kwargs: T.untyped,
|
135
|
-
block: T.nilable(
|
136
|
-
T.proc.params(stdin: IO, stdout: IO, wait_thr: Process::Waiter)
|
137
|
-
.returns([IO, IO, Process::Waiter]),
|
138
|
-
),
|
139
|
-
)
|
140
|
-
.returns([IO, IO, Process::Waiter])
|
141
|
-
end
|
99
|
+
#: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) ?{ (IO stdin, IO stdout, Process::Waiter wait_thr) -> [IO, IO, Process::Waiter] } -> [IO, IO, Process::Waiter]
|
142
100
|
def popen2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block)
|
143
101
|
delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen2, &block)
|
144
102
|
end
|
145
103
|
|
146
|
-
|
147
|
-
params(
|
148
|
-
cmd: String,
|
149
|
-
args: String,
|
150
|
-
sudo: T.any(T::Boolean, String),
|
151
|
-
env: T::Hash[String, T.nilable(String)],
|
152
|
-
kwargs: T.untyped,
|
153
|
-
block: T.nilable(
|
154
|
-
T.proc.params(stdin: IO, stdout: IO, wait_thr: Process::Waiter)
|
155
|
-
.returns([IO, IO, Process::Waiter]),
|
156
|
-
),
|
157
|
-
)
|
158
|
-
.returns([IO, IO, Process::Waiter])
|
159
|
-
end
|
104
|
+
#: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) ?{ (IO stdin, IO stdout, Process::Waiter wait_thr) -> [IO, IO, Process::Waiter] } -> [IO, IO, Process::Waiter]
|
160
105
|
def popen2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block)
|
161
106
|
delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen2e, &block)
|
162
107
|
end
|
163
108
|
|
164
|
-
|
165
|
-
params(
|
166
|
-
cmd: String,
|
167
|
-
args: String,
|
168
|
-
sudo: T.any(T::Boolean, String),
|
169
|
-
env: T::Hash[String, T.nilable(String)],
|
170
|
-
kwargs: T.untyped,
|
171
|
-
block: T.nilable(
|
172
|
-
T.proc.params(stdin: IO, stdout: IO, stderr: IO, wait_thr: Process::Waiter)
|
173
|
-
.returns([IO, IO, IO, Process::Waiter]),
|
174
|
-
),
|
175
|
-
)
|
176
|
-
.returns([IO, IO, IO, Process::Waiter])
|
177
|
-
end
|
109
|
+
#: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) ?{ (IO stdin, IO stdout, IO stderr, Process::Waiter wait_thr) -> [IO, IO, IO, Process::Waiter] } -> [IO, IO, IO, Process::Waiter]
|
178
110
|
def popen3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block)
|
179
111
|
delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen3, &block)
|
180
112
|
end
|
@@ -194,18 +126,7 @@ module CLI
|
|
194
126
|
# #### Usage
|
195
127
|
# `stat = CLI::Kit::System.system('ls', 'a_folder')`
|
196
128
|
#
|
197
|
-
|
198
|
-
params(
|
199
|
-
cmd: String,
|
200
|
-
args: String,
|
201
|
-
sudo: T.any(T::Boolean, String),
|
202
|
-
env: T::Hash[String, T.nilable(String)],
|
203
|
-
stdin: T.nilable(T.any(IO, String, Integer, Symbol)),
|
204
|
-
kwargs: T.untyped,
|
205
|
-
block: T.nilable(T.proc.params(out: String, err: String).void),
|
206
|
-
)
|
207
|
-
.returns(Process::Status)
|
208
|
-
end
|
129
|
+
#: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], ?stdin: (IO | String | Integer | Symbol)?, **untyped kwargs) ?{ (String out, String err) -> void } -> Process::Status
|
209
130
|
def system(cmd, *args, sudo: false, env: ENV.to_h, stdin: nil, **kwargs, &block)
|
210
131
|
cmd, args = apply_sudo(cmd, args, sudo)
|
211
132
|
|
@@ -219,7 +140,8 @@ module CLI
|
|
219
140
|
STDIN
|
220
141
|
end
|
221
142
|
cmd, args = resolve_path(cmd, args, env)
|
222
|
-
|
143
|
+
process = Process #: as untyped
|
144
|
+
pid = process.spawn(env, cmd, *args, 0 => in_stream, :out => out_w, :err => err_w, **kwargs)
|
223
145
|
out_w.close
|
224
146
|
err_w.close
|
225
147
|
|
@@ -260,15 +182,16 @@ module CLI
|
|
260
182
|
# Split off trailing partial UTF-8 Characters. UTF-8 Multibyte characters start with a 11xxxxxx byte that tells
|
261
183
|
# how many following bytes are part of this character, followed by some number of 10xxxxxx bytes. This simple
|
262
184
|
# algorithm will split off a whole trailing multi-byte character.
|
263
|
-
|
185
|
+
#: (String data) -> [String, String]
|
264
186
|
def split_partial_characters(data)
|
265
|
-
last_byte =
|
187
|
+
last_byte = data.getbyte(-1) #: as !nil
|
266
188
|
return [data, ''] if (last_byte & 0b1000_0000).zero?
|
267
189
|
|
268
190
|
# UTF-8 is up to 4 characters per rune, so we could never want to trim more than that, and we want to avoid
|
269
191
|
# allocating an array for the whole of data with bytes
|
270
192
|
min_bound = -[4, data.bytesize].min
|
271
|
-
|
193
|
+
fb = data.byteslice(min_bound..-1) #: as !nil
|
194
|
+
final_bytes = fb.bytes
|
272
195
|
partial_character_sub_index = final_bytes.rindex { |byte| byte & 0b1100_0000 == 0b1100_0000 }
|
273
196
|
|
274
197
|
# Bail out for non UTF-8
|
@@ -293,10 +216,13 @@ module CLI
|
|
293
216
|
|
294
217
|
partial_character_index = min_bound + partial_character_sub_index
|
295
218
|
|
296
|
-
[
|
219
|
+
[
|
220
|
+
data.byteslice(0...partial_character_index), #: as !nil
|
221
|
+
data.byteslice(partial_character_index..-1), #: as !nil
|
222
|
+
]
|
297
223
|
end
|
298
224
|
|
299
|
-
|
225
|
+
#: -> Symbol
|
300
226
|
def os
|
301
227
|
return :mac if /darwin/.match(RUBY_PLATFORM)
|
302
228
|
return :linux if /linux/.match(RUBY_PLATFORM)
|
@@ -305,7 +231,7 @@ module CLI
|
|
305
231
|
raise "Could not determine OS from platform #{RUBY_PLATFORM}"
|
306
232
|
end
|
307
233
|
|
308
|
-
|
234
|
+
#: (String cmd, Hash[String, String?] env) -> String?
|
309
235
|
def which(cmd, env)
|
310
236
|
exts = os == :windows ? (env['PATHEXT'] || 'exe').split(';') : ['']
|
311
237
|
(env['PATH'] || '').split(File::PATH_SEPARATOR).each do |path|
|
@@ -320,10 +246,7 @@ module CLI
|
|
320
246
|
|
321
247
|
private
|
322
248
|
|
323
|
-
|
324
|
-
params(cmd: String, args: T::Array[String], sudo: T.any(T::Boolean, String))
|
325
|
-
.returns([String, T::Array[String]])
|
326
|
-
end
|
249
|
+
#: (String cmd, Array[String] args, (String | bool) sudo) -> [String, Array[String]]
|
327
250
|
def apply_sudo(cmd, args, sudo)
|
328
251
|
return [cmd, args] if !sudo || Process.uid.zero?
|
329
252
|
|
@@ -331,21 +254,12 @@ module CLI
|
|
331
254
|
['sudo', args.unshift('-E', '-S', '-p', SUDO_PROMPT, '--', cmd)]
|
332
255
|
end
|
333
256
|
|
334
|
-
|
335
|
-
params(
|
336
|
-
cmd: String,
|
337
|
-
args: T::Array[String],
|
338
|
-
kwargs: T::Hash[Symbol, T.untyped],
|
339
|
-
sudo: T.any(T::Boolean, String),
|
340
|
-
env: T::Hash[String, T.nilable(String)],
|
341
|
-
method: Symbol,
|
342
|
-
block: T.untyped,
|
343
|
-
).returns(T.untyped)
|
344
|
-
end
|
257
|
+
#: (String cmd, Array[String] args, Hash[Symbol, untyped] kwargs, ?sudo: (String | bool), ?env: Hash[String, String?], ?method: Symbol) ?{ (?) -> untyped } -> untyped
|
345
258
|
def delegate_open3(cmd, args, kwargs, sudo: raise, env: raise, method: raise, &block)
|
346
259
|
cmd, args = apply_sudo(cmd, args, sudo)
|
347
260
|
cmd, args = resolve_path(cmd, args, env)
|
348
|
-
|
261
|
+
open3 = Open3 #: as untyped
|
262
|
+
open3.send(method, env, cmd, *args, **kwargs, &block)
|
349
263
|
rescue Errno::EINTR
|
350
264
|
raise(Errno::EINTR, "command interrupted: #{cmd} #{args.join(" ")}")
|
351
265
|
end
|
@@ -359,10 +273,7 @@ module CLI
|
|
359
273
|
# project.
|
360
274
|
#
|
361
275
|
# See https://github.com/Shopify/dev/pull/625 for more details.
|
362
|
-
|
363
|
-
params(cmd: String, args: T::Array[String], env: T::Hash[String, T.nilable(String)])
|
364
|
-
.returns([String, T::Array[String]])
|
365
|
-
end
|
276
|
+
#: (String cmd, Array[String] args, Hash[String, String?] env) -> [String, Array[String]]
|
366
277
|
def resolve_path(cmd, args, env)
|
367
278
|
# If only one argument was provided, make sure it's interpreted by a shell.
|
368
279
|
if args.empty?
|