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
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,14 +131,14 @@ module CLI
|
|
180
131
|
|
181
132
|
private
|
182
133
|
|
183
|
-
|
134
|
+
#: (String? label) -> Symbol?
|
184
135
|
def symbolize(label)
|
185
136
|
return if label.nil?
|
186
137
|
|
187
138
|
label.split('#').last&.to_sym
|
188
139
|
end
|
189
140
|
|
190
|
-
|
141
|
+
#: -> Symbol
|
191
142
|
def infer_name
|
192
143
|
to_skip = 1
|
193
144
|
Kernel.caller_locations.each do |loc|
|
@@ -197,7 +148,7 @@ module CLI
|
|
197
148
|
to_skip -= 1
|
198
149
|
next
|
199
150
|
end
|
200
|
-
return
|
151
|
+
return symbolize(loc.label) #: as !nil
|
201
152
|
end
|
202
153
|
raise(ArgumentError, 'could not infer name')
|
203
154
|
end
|
@@ -206,24 +157,18 @@ module CLI
|
|
206
157
|
|
207
158
|
DEFAULT_OPTIONS = [:helpflag]
|
208
159
|
|
209
|
-
|
160
|
+
#: -> bool
|
210
161
|
def helpflag
|
211
162
|
flag(name: :help, short: '-h', long: '--help', desc: 'Show this help message')
|
212
163
|
end
|
213
164
|
|
214
|
-
|
165
|
+
#: -> Array[String]
|
215
166
|
def unparsed
|
216
167
|
obj = assert_result!
|
217
168
|
obj.unparsed
|
218
169
|
end
|
219
170
|
|
220
|
-
|
221
|
-
params(
|
222
|
-
block: T.nilable(
|
223
|
-
T.proc.params(arg0: Symbol, arg1: T.nilable(String)).void,
|
224
|
-
),
|
225
|
-
).returns(T.untyped)
|
226
|
-
end
|
171
|
+
#: ?{ (Symbol arg0, String? arg1) -> void } -> untyped
|
227
172
|
def each_option(&block)
|
228
173
|
return enum_for(:each_option) unless block_given?
|
229
174
|
|
@@ -235,13 +180,7 @@ module CLI
|
|
235
180
|
end
|
236
181
|
end
|
237
182
|
|
238
|
-
|
239
|
-
params(
|
240
|
-
block: T.nilable(
|
241
|
-
T.proc.params(arg0: Symbol, arg1: T::Boolean).void,
|
242
|
-
),
|
243
|
-
).returns(T.untyped)
|
244
|
-
end
|
183
|
+
#: ?{ (Symbol arg0, bool arg1) -> void } -> untyped
|
245
184
|
def each_flag(&block)
|
246
185
|
return enum_for(:each_flag) unless block_given?
|
247
186
|
|
@@ -253,7 +192,7 @@ module CLI
|
|
253
192
|
end
|
254
193
|
end
|
255
194
|
|
256
|
-
|
195
|
+
#: (String name) -> (String | bool)?
|
257
196
|
def [](name)
|
258
197
|
obj = assert_result!
|
259
198
|
if obj.opt.respond_to?(name)
|
@@ -263,7 +202,7 @@ module CLI
|
|
263
202
|
end
|
264
203
|
end
|
265
204
|
|
266
|
-
|
205
|
+
#: (String name) -> String?
|
267
206
|
def lookup_option(name)
|
268
207
|
obj = assert_result!
|
269
208
|
obj.opt.send(name)
|
@@ -272,7 +211,7 @@ module CLI
|
|
272
211
|
nil
|
273
212
|
end
|
274
213
|
|
275
|
-
|
214
|
+
#: (String name) -> bool
|
276
215
|
def lookup_flag(name)
|
277
216
|
obj = assert_result!
|
278
217
|
obj.flag.send(name)
|
@@ -280,17 +219,18 @@ module CLI
|
|
280
219
|
false
|
281
220
|
end
|
282
221
|
|
283
|
-
|
222
|
+
#: -> Args::Evaluation
|
284
223
|
def assert_result!
|
285
224
|
raise(NotImplementedError, 'not implemented') if @obj.is_a?(Args::Definition)
|
286
225
|
|
287
226
|
@obj
|
288
227
|
end
|
289
228
|
|
290
|
-
|
229
|
+
#: (Args::Definition defn) -> void
|
291
230
|
def define!(defn)
|
292
231
|
@obj = defn
|
293
|
-
|
232
|
+
klass = self.class #: as Mixin::MixinClassMethods
|
233
|
+
klass.tracked_methods.each do |m|
|
294
234
|
send(m)
|
295
235
|
end
|
296
236
|
DEFAULT_OPTIONS.each do |m|
|
@@ -298,7 +238,7 @@ module CLI
|
|
298
238
|
end
|
299
239
|
end
|
300
240
|
|
301
|
-
|
241
|
+
#: (Args::Evaluation ev) -> void
|
302
242
|
def evaluate!(ev)
|
303
243
|
@obj = ev
|
304
244
|
ev.resolve_positions!
|
data/lib/cli/kit/parse_args.rb
CHANGED
@@ -6,18 +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
|
-
|
16
|
-
params(
|
17
|
-
args: T.any(Array, String),
|
18
|
-
opts_defn: T::Hash[Symbol, T::Array[T.untyped]],
|
19
|
-
).returns(T::Hash[Symbol, T.untyped])
|
20
|
-
end
|
14
|
+
#: ((Array | String) args, Hash[Symbol, Array[untyped]] opts_defn) -> Hash[Symbol, untyped]
|
21
15
|
def parse_args(args, opts_defn)
|
22
16
|
start_opts, parser_config = opts_defn.reduce([{}, []]) do |(ini, pcfg), (n, cfg)|
|
23
17
|
(vals, desc, short, klass) = cfg
|
@@ -38,7 +32,8 @@ module CLI
|
|
38
32
|
long = "--#{n.to_s.tr("_", "-")}" + (mark.nil? ? '' : " #{mark}")
|
39
33
|
opt_args = klass.nil? ? [short, long, desc] : [short, long, klass, desc]
|
40
34
|
|
41
|
-
|
35
|
+
unsafe_opt_p = opt_p #: as untyped
|
36
|
+
unsafe_opt_p.on(*opt_args) do |v|
|
42
37
|
acc_opts[n] = if acc_opts.key?(n)
|
43
38
|
Array(acc_opts[n]) + Array(v || def_val)
|
44
39
|
else
|
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?
|