subspawn 0.1.0 → 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/lib/subspawn/fd_parse.rb +35 -3
- data/lib/subspawn/fd_types.rb +1 -10
- data/lib/subspawn/replace-builtin.rb +36 -0
- data/lib/subspawn/replace-pty.rb +4 -0
- data/lib/subspawn/version.rb +1 -1
- data/lib/subspawn.rb +154 -14
- data/subspawn.gemspec +45 -0
- metadata +41 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d07057f5bd5fe708a6690914e4c71f391bd1caf008ce6393c3be6a2b70e4c941
|
4
|
+
data.tar.gz: '08af14e9800ce17abee24dd76094625da44b0151b8fbe141b85bcad4e3749653'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5ccf97896122ce4570ae0536b6fc3b8bdd10c458f1dd5c8cd35641f62516338f1896634940c52871ddaab6519e8aa4b84243f4308d0af1339daa4e05a62f5b6
|
7
|
+
data.tar.gz: d9befadc8ce0b8631012e425617195367e922c7db738a15034c3c183bb96ce8db7e2852a41f2644b6ae114148d7b17ceae6324cfa9d0b032711be54ac18de190
|
data/.rspec
CHANGED
data/lib/subspawn/fd_parse.rb
CHANGED
@@ -4,10 +4,11 @@ require_relative './pipes'
|
|
4
4
|
|
5
5
|
module SubSpawn::Internal
|
6
6
|
# argument value to int (or :tty)
|
7
|
-
def self.parse_fd(fd, allow_pty=false)
|
7
|
+
def self.parse_fd(fd, allow_pty=false, dests: nil)
|
8
8
|
# fd = if fd.respond_to? :to_path
|
9
9
|
# fd = if fd.respond_to? :to_file
|
10
10
|
# fd = if fd.respond_to? :to_file
|
11
|
+
fd = split_parse(fd, dests) if dests != nil and fd.respond_to? :underlying_write_io
|
11
12
|
case fd
|
12
13
|
when Integer then fd
|
13
14
|
when IO then fd.fileno
|
@@ -23,6 +24,14 @@ module SubSpawn::Internal
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
27
|
+
# TODO: does this support [:in, :out, :pty] => bidi as well as :child?
|
28
|
+
# Only on windows, we have "split IO" to fake a bidirectional pipe for PTYs
|
29
|
+
# This picks the right underlying IO
|
30
|
+
def self.split_parse(fd, d)
|
31
|
+
mode = guess_mode(d)
|
32
|
+
fd.send(:"underlying_#{mode}_io")
|
33
|
+
end
|
34
|
+
|
26
35
|
# mode conversion
|
27
36
|
def self.guess_mode(d)
|
28
37
|
read = d.include? 0 # stdin
|
@@ -38,11 +47,34 @@ module SubSpawn::Internal
|
|
38
47
|
end
|
39
48
|
end
|
40
49
|
|
50
|
+
def self.modestr_parse(str)
|
51
|
+
str = str.to_str
|
52
|
+
out = 0
|
53
|
+
if str.include? "b"
|
54
|
+
out = IO::BINARY
|
55
|
+
str = str.gsub("b", "")
|
56
|
+
end
|
57
|
+
if str.include? "x"
|
58
|
+
out = IO::EXCL
|
59
|
+
str = str.gsub("b", "")
|
60
|
+
end
|
61
|
+
out | case str
|
62
|
+
when "w" then IO::WRONLY | IO::CREAT | IO::TRUNC
|
63
|
+
when "r" then IO::RDONLY
|
64
|
+
when "r+", "w+" then IO::RDWR | IO::CREAT
|
65
|
+
when "a" then IO::WRONLY | IO::CREAT | IO::APPEND
|
66
|
+
when "a+" then IO::RDWR | IO::APPEND
|
67
|
+
else
|
68
|
+
raise ArgumentError "Unknown File mode"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
41
72
|
# make FdSource objects of each redirection
|
42
73
|
def self.parse_fd_opts(fds, &settty)
|
43
74
|
child_lookup = {}
|
44
75
|
fds.map do |dests, src|
|
45
76
|
d = dests.map{|x| parse_fd(x, true)} # TODO: configurable
|
77
|
+
src = src.to_io if src.respond_to? :to_io
|
46
78
|
src = case src
|
47
79
|
when Array
|
48
80
|
case src.first
|
@@ -54,7 +86,7 @@ module SubSpawn::Internal
|
|
54
86
|
raise ArgumentError, "Invalid :child FD source specification" unless src.length == 2
|
55
87
|
# {a => c, b => [child, a]} is the same as {[a, b] => c}
|
56
88
|
# so we can transform the former into the latter
|
57
|
-
newfd = parse_fd(src.last)
|
89
|
+
newfd = parse_fd(src.last, dests: d)
|
58
90
|
# TODO: this isn't an error, create a new one
|
59
91
|
raise ArgumentError, "Invalid :child FD source specification" unless child_lookup[newfd]
|
60
92
|
child_lookup[newfd].tap{|child|
|
@@ -81,7 +113,7 @@ module SubSpawn::Internal
|
|
81
113
|
settty.call(src.path)
|
82
114
|
d.delete(:tty)
|
83
115
|
end
|
84
|
-
FdSource::Basic.new d, parse_fd(src)
|
116
|
+
FdSource::Basic.new d, parse_fd(src, dests: d)
|
85
117
|
end
|
86
118
|
# save redirected fds so we can sneak a child reference in
|
87
119
|
src.tap{|x| d.each{|c|
|
data/lib/subspawn/fd_types.rb
CHANGED
@@ -93,16 +93,7 @@ module SubSpawn::Internal
|
|
93
93
|
@value = file
|
94
94
|
@mode = mode || ::File::RDONLY
|
95
95
|
if @mode.respond_to? :to_str
|
96
|
-
@mode =
|
97
|
-
when "w" then IO::WRONLY | IO::CREAT | IO::TRUNC
|
98
|
-
when "r" then IO::RDONLY
|
99
|
-
when "rb" then IO::RDONLY | IO::BINARY
|
100
|
-
when "wb" then IO::WRONLY | IO::BINARY | IO::CREAT | IO::TRUNC
|
101
|
-
when "r+", "w+" then IO::RDWR | IO::CREAT
|
102
|
-
# TODO: all!
|
103
|
-
else
|
104
|
-
raise ArgumentError "Unknown File mode"
|
105
|
-
end
|
96
|
+
@mode = SubSpawn::Internal.modestr_parse @mode.to_str
|
106
97
|
end
|
107
98
|
@perm = perm || 0o666
|
108
99
|
end
|
@@ -1,4 +1,7 @@
|
|
1
1
|
require 'subspawn'
|
2
|
+
require 'engine-hacks'
|
3
|
+
|
4
|
+
EngineHacks.use_child_status :subspawn_child_status
|
2
5
|
|
3
6
|
module Kernel
|
4
7
|
class << self
|
@@ -13,6 +16,11 @@ module Kernel
|
|
13
16
|
def spawn(*args)
|
14
17
|
SubSpawn.spawn_compat(*args)
|
15
18
|
end
|
19
|
+
alias :builtin_backtick :`
|
20
|
+
def `(str)
|
21
|
+
require 'open3'
|
22
|
+
Open3.capture2(str).first
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
module Process
|
@@ -26,5 +34,33 @@ module Process
|
|
26
34
|
def subspawn(args, opt={})
|
27
35
|
SubSpawn.spawn(args, opt)
|
28
36
|
end
|
37
|
+
|
38
|
+
# don't make a loop if waitpid isn't defined
|
39
|
+
if SubSpawn::Platform.method(:waitpid2)
|
40
|
+
def wait(*args)
|
41
|
+
SubSpawn.wait *args
|
42
|
+
end
|
43
|
+
def waitpid(*args)
|
44
|
+
SubSpawn.waitpid *args
|
45
|
+
end
|
46
|
+
def wait2(*args)
|
47
|
+
SubSpawn.wait2 *args
|
48
|
+
end
|
49
|
+
def waitpid2(*args)
|
50
|
+
SubSpawn.waitpid2 *args
|
51
|
+
end
|
52
|
+
def last_status
|
53
|
+
SubSpawn.last_status
|
54
|
+
end
|
55
|
+
def detach pid
|
56
|
+
SubSpawn.detach(pid)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class IO
|
63
|
+
def self.popen(*args, &block)
|
64
|
+
SubSpawn.popen_compat(*args, &block)
|
29
65
|
end
|
30
66
|
end
|
data/lib/subspawn/replace-pty.rb
CHANGED
data/lib/subspawn/version.rb
CHANGED
data/lib/subspawn.rb
CHANGED
@@ -5,17 +5,17 @@ if FFI::Platform.unix?
|
|
5
5
|
require 'subspawn/posix'
|
6
6
|
SubSpawn::Platform = SubSpawn::POSIX
|
7
7
|
elsif FFI::Platform.windows?
|
8
|
-
|
8
|
+
require 'subspawn/win32'
|
9
|
+
SubSpawn::Platform = SubSpawn::Win32
|
9
10
|
else
|
10
11
|
raise "Unknown FFI platform"
|
11
12
|
end
|
13
|
+
require 'subspawn/common'
|
12
14
|
|
13
15
|
module SubSpawn
|
14
|
-
#
|
15
|
-
def self.
|
16
|
-
#File.write('/tmp/spawn.trace', [command, *command2].inspect + "\n", mode: 'a+')
|
16
|
+
# Parse and convert the weird Ruby spawn API into something nicer
|
17
|
+
def self.__compat_parser(is_popen, command, command2)
|
17
18
|
|
18
|
-
# return just the pid
|
19
19
|
delta_env = nil
|
20
20
|
# check for env
|
21
21
|
if command.respond_to? :to_hash
|
@@ -31,6 +31,9 @@ module SubSpawn
|
|
31
31
|
if command.first.is_a? Array and command.first.length != 2
|
32
32
|
raise ArgumentError, "First argument must be an pair TODO: check this"
|
33
33
|
end
|
34
|
+
popen = if is_popen && command.length > 1
|
35
|
+
command.pop
|
36
|
+
end
|
34
37
|
raise ArgumentError, "Must provide a command to execute" if command.empty?
|
35
38
|
raise ArgumentError, "Must provide options as a hash" unless opt.is_a? Hash
|
36
39
|
if opt.key? :env and delta_env
|
@@ -53,7 +56,14 @@ module SubSpawn
|
|
53
56
|
rescue NoMethodError => e # by spec
|
54
57
|
raise TypeError.new(e)
|
55
58
|
end
|
56
|
-
|
59
|
+
return [popen, command, opt, copt]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parse and convert the weird Ruby spawn API into something nicer
|
63
|
+
def self.spawn_compat(command, *command2)
|
64
|
+
#File.write('/tmp/spawn.trace', [command, *command2].inspect + "\n", mode: 'a+')
|
65
|
+
|
66
|
+
__spawn_internal(*__compat_parser(false, command, command2)[1..-1]).first
|
57
67
|
end
|
58
68
|
# TODO: accept block mode?
|
59
69
|
def self.spawn(command, opt={})
|
@@ -107,7 +117,11 @@ module SubSpawn
|
|
107
117
|
#base.sid!# TODO: yes? no?
|
108
118
|
end
|
109
119
|
when :sid
|
110
|
-
base.sid!
|
120
|
+
if base.respond_to? :sid!
|
121
|
+
base.sid! if value
|
122
|
+
else
|
123
|
+
warn "SubSpawn Platform (#{base.class}) doesn't support 'sid'"
|
124
|
+
end
|
111
125
|
when :env
|
112
126
|
if env_opts[:deltas]
|
113
127
|
warn "Provided multiple ENV options"
|
@@ -127,19 +141,44 @@ module SubSpawn
|
|
127
141
|
raise TypeError, "pgroup must be boolean or integral" if value.is_a? Symbol
|
128
142
|
base.pgroup = value == true ? 0 : value if value
|
129
143
|
when :signal_mask # TODO: signal_default
|
130
|
-
base.signal_mask
|
144
|
+
if base.respond_to? :signal_mask
|
145
|
+
base.signal_mask(value)
|
146
|
+
else
|
147
|
+
warn "SubSpawn Platform (#{base.class}) doesn't support 'signal_mask'"
|
148
|
+
end
|
131
149
|
when /rlimit_(.*)/ # P.s
|
150
|
+
unless base.respond_to? :rlimit
|
151
|
+
warn "SubSpawn Platform (#{base.class}) doesn't support 'rlimit_*'"
|
152
|
+
else
|
153
|
+
name = $1
|
154
|
+
keys = [value].flatten
|
155
|
+
base.rlimit(name, *keys)
|
156
|
+
end
|
157
|
+
when /w32_(.*)/ # NEW
|
132
158
|
name = $1
|
133
|
-
|
134
|
-
base.
|
159
|
+
raise ArgumentError, "Unknown win32 argument: #{name}" unless %w{desktop title show_window window_pos window_size console_size window_fill start_flags}.include? name
|
160
|
+
unless base.respond_to? :name
|
161
|
+
warn "SubSpawn Platform (#{base.class}) doesn't support 'w32_#{$1}'"
|
162
|
+
else
|
163
|
+
base.send(name, *value)
|
164
|
+
end
|
135
165
|
when :rlimit # NEW?
|
136
166
|
raise ArgumentError, "rlimit as a hash must be a hash" unless value.respond_to? :to_h
|
137
|
-
|
138
|
-
|
167
|
+
|
168
|
+
unless base.respond_to? :rlimit
|
169
|
+
warn "SubSpawn Platform (#{base.class}) doesn't support 'rlimit_*'"
|
170
|
+
else
|
171
|
+
value.to_h.each do |key, values|
|
172
|
+
base.rlimit(key, *[values].flatten)
|
173
|
+
end
|
139
174
|
end
|
140
175
|
when :umask # P.s
|
141
176
|
raise ArgumentError, "umask must be numeric" unless value.is_a? Integer
|
142
|
-
base.umask
|
177
|
+
unless base.respond_to? :umask
|
178
|
+
warn "SubSpawn Platform (#{base.class}) doesn't support 'umask'"
|
179
|
+
else
|
180
|
+
base.umask = value
|
181
|
+
end
|
143
182
|
when :unsetenv_others # P.s
|
144
183
|
env_opts[:only] = !!value
|
145
184
|
env_opts[:set] ||= !!value
|
@@ -208,12 +247,113 @@ module SubSpawn
|
|
208
247
|
ensure
|
209
248
|
tty.close unless tty.closed?
|
210
249
|
# MRI waits this way to ensure the process is reaped
|
211
|
-
if Process.waitpid(pid, Process::WNOHANG)
|
250
|
+
if Process.waitpid(pid, Process::WNOHANG).nil?
|
251
|
+
Process.detach(pid)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.popen(command, mode="r", opt={}, &block)
|
257
|
+
#Many modes, and "-" is not supported at this time
|
258
|
+
__popen_internal(command, mode, opt, {}, &block)
|
259
|
+
end
|
260
|
+
def self.popen_compat(command, *command2, &block)
|
261
|
+
#Many modes, and "-" is not supported at this time
|
262
|
+
mode, command, opt, copt = __compat_parser(true, command, command2)
|
263
|
+
mode ||= "r"
|
264
|
+
__popen_internal(command, mode, opt, copt, &block)
|
265
|
+
end
|
266
|
+
#Many modes, and "-" is not supported at this time
|
267
|
+
def self.__popen_internal(command, mode, opt, copt, &block)
|
268
|
+
outputs = {}
|
269
|
+
# parse, but ignore irrelevant bits
|
270
|
+
parsed = Internal.modestr_parse(mode) & (~(IO::TRUNC | IO::CREAT | IO::APPEND | IO::EXCL))
|
271
|
+
looking = if parsed & IO::WRONLY != 0
|
272
|
+
outputs[:in] = :pipe
|
273
|
+
looking = [:in]
|
274
|
+
elsif parsed & IO::RDWR != 0
|
275
|
+
outputs[:out] = :pipe
|
276
|
+
outputs[:in] = :pipe
|
277
|
+
looking = [:out, :in] # read, write, from our POV
|
278
|
+
else # read only
|
279
|
+
outputs[:out] = :pipe
|
280
|
+
looking = [:out]
|
281
|
+
end
|
282
|
+
# do normal spawning. Note: we only chose the internal spawn for popen_compat
|
283
|
+
pid, rawio = __spawn_internal(command, outputs.merge(opt), copt)
|
284
|
+
|
285
|
+
# create a proxy to close the process
|
286
|
+
io_proxy = looking.length == 1 ? SubSpawn::Common::ClosableIO : SubSpawn::Common::BidiMergedIOClosable
|
287
|
+
io = io_proxy.new(*looking.map{|x|rawio[x]}) do
|
288
|
+
# MRI waits this way to ensure the process is reaped
|
289
|
+
Process.waitpid(pid) # TODO: I think there isn't a WNOHANG here
|
290
|
+
end
|
291
|
+
|
292
|
+
# return or call
|
293
|
+
return io unless block_given?
|
294
|
+
begin
|
295
|
+
return yield(io)
|
296
|
+
ensure
|
297
|
+
io.close unless io.closed?
|
298
|
+
# MRI waits this way to ensure the process is reaped
|
299
|
+
if Process.waitpid(pid, Process::WNOHANG).nil?
|
212
300
|
Process.detach(pid)
|
213
301
|
end
|
214
302
|
end
|
215
303
|
end
|
216
304
|
|
305
|
+
# Windows doesn't like mixing and matching who is spawning and who is waiting, so use
|
306
|
+
# subspawn.wait* if you used subspawn.spawn*, while using process.wait* if you used Process.spawn*
|
307
|
+
# though if you replace process, then it's a moot point
|
308
|
+
if SubSpawn::Platform.method_defined? :waitpid2
|
309
|
+
def self.wait(*args)
|
310
|
+
waitpid *args
|
311
|
+
end
|
312
|
+
def self.waitpid(*args)
|
313
|
+
waitpid2(*args)&.first
|
314
|
+
end
|
315
|
+
def self.wait2(*args)
|
316
|
+
waitpid2 *args
|
317
|
+
end
|
318
|
+
def self.waitpid2(*args)
|
319
|
+
SubSpawn::Platform.waitpid2 *args
|
320
|
+
end
|
321
|
+
def self.last_status
|
322
|
+
SubSpawn::Platform.last_status
|
323
|
+
end
|
324
|
+
else
|
325
|
+
def self.wait(*args)
|
326
|
+
Process.wait *args
|
327
|
+
end
|
328
|
+
def self.waitpid(*args)
|
329
|
+
Process.waitpid *args
|
330
|
+
end
|
331
|
+
def self.wait2(*args)
|
332
|
+
Process.wait2 *args
|
333
|
+
end
|
334
|
+
def self.waitpid2(*args)
|
335
|
+
Process.waitpid2 *args
|
336
|
+
end
|
337
|
+
def self.last_status
|
338
|
+
Process.last_status
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def self.detach(pid)
|
343
|
+
Thread.new do
|
344
|
+
pid, status = *SubSpawn.waitpid2(pid)
|
345
|
+
# TODO: ensure this loop isn't necessary
|
346
|
+
# while pid.nil?
|
347
|
+
# sleep 0.01
|
348
|
+
# pid, status = *SubSpawn.waitpid2(pid)
|
349
|
+
# end
|
350
|
+
status
|
351
|
+
end.tap do |thr|
|
352
|
+
thr[:pid] = pid
|
353
|
+
# TODO: does thread.pid need to exist?
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
217
357
|
COMPLETE_VERSION = {
|
218
358
|
subspawn: SubSpawn::VERSION,
|
219
359
|
platform: SubSpawn::Platform::COMPLETE_VERSION,
|
data/subspawn.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/subspawn/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "subspawn"
|
7
|
+
spec.version = SubSpawn::VERSION
|
8
|
+
spec.authors = ["Patrick Plenefisch"]
|
9
|
+
spec.email = ["simonpatp@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Advanced native subprocess spawning"
|
12
|
+
spec.description = "Advanced native subprocess spawning on MRI, JRuby, and TruffleRuby"
|
13
|
+
final_github = "https://github.com/byteit101/subspawn"
|
14
|
+
spec.homepage = final_github
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
#spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = final_github
|
21
|
+
spec.metadata["changelog_uri"] = final_github
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
# Uncomment to register a new dependency of your gem
|
35
|
+
spec.add_dependency "subspawn-common", "~> 0.2.0.pre1"
|
36
|
+
spec.add_dependency "subspawn-posix", "~> 0.2.0.pre1"
|
37
|
+
spec.add_dependency "subspawn-win32", "~> 0.2.0.pre1"
|
38
|
+
spec.add_dependency "ffi", "~> 1.0"
|
39
|
+
|
40
|
+
# For more information and examples about making a new gem, check out our
|
41
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
42
|
+
|
43
|
+
# You can use Ruby's license, or any of the JRuby tri-license option.
|
44
|
+
spec.licenses = ["Ruby", "EPL-2.0", "LGPL-2.1-or-later"]
|
45
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subspawn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Plenefisch
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: subspawn-common
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.0.pre1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.0.pre1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: subspawn-posix
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
33
|
+
version: 0.2.0.pre1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.2.0.pre1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: subspawn-win32
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.0.pre1
|
20
48
|
type: :runtime
|
21
49
|
prerelease: false
|
22
50
|
version_requirements: !ruby/object:Gem::Requirement
|
23
51
|
requirements:
|
24
52
|
- - "~>"
|
25
53
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
54
|
+
version: 0.2.0.pre1
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: ffi
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,13 +86,17 @@ files:
|
|
58
86
|
- lib/subspawn/replace-pty.rb
|
59
87
|
- lib/subspawn/replace.rb
|
60
88
|
- lib/subspawn/version.rb
|
89
|
+
- subspawn.gemspec
|
61
90
|
homepage: https://github.com/byteit101/subspawn
|
62
|
-
licenses:
|
91
|
+
licenses:
|
92
|
+
- Ruby
|
93
|
+
- EPL-2.0
|
94
|
+
- LGPL-2.1-or-later
|
63
95
|
metadata:
|
64
96
|
homepage_uri: https://github.com/byteit101/subspawn
|
65
97
|
source_code_uri: https://github.com/byteit101/subspawn
|
66
98
|
changelog_uri: https://github.com/byteit101/subspawn
|
67
|
-
post_install_message:
|
99
|
+
post_install_message:
|
68
100
|
rdoc_options: []
|
69
101
|
require_paths:
|
70
102
|
- lib
|
@@ -79,8 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
111
|
- !ruby/object:Gem::Version
|
80
112
|
version: '0'
|
81
113
|
requirements: []
|
82
|
-
rubygems_version: 3.
|
83
|
-
signing_key:
|
114
|
+
rubygems_version: 3.5.3
|
115
|
+
signing_key:
|
84
116
|
specification_version: 4
|
85
117
|
summary: Advanced native subprocess spawning
|
86
118
|
test_files: []
|