subspawn 0.1.1 → 0.2.0.pre1
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/.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 +37 -8
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,6 +86,7 @@ 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
91
|
licenses:
|
63
92
|
- Ruby
|
@@ -67,7 +96,7 @@ metadata:
|
|
67
96
|
homepage_uri: https://github.com/byteit101/subspawn
|
68
97
|
source_code_uri: https://github.com/byteit101/subspawn
|
69
98
|
changelog_uri: https://github.com/byteit101/subspawn
|
70
|
-
post_install_message:
|
99
|
+
post_install_message:
|
71
100
|
rdoc_options: []
|
72
101
|
require_paths:
|
73
102
|
- lib
|
@@ -82,8 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
111
|
- !ruby/object:Gem::Version
|
83
112
|
version: '0'
|
84
113
|
requirements: []
|
85
|
-
rubygems_version: 3.
|
86
|
-
signing_key:
|
114
|
+
rubygems_version: 3.5.3
|
115
|
+
signing_key:
|
87
116
|
specification_version: 4
|
88
117
|
summary: Advanced native subprocess spawning
|
89
118
|
test_files: []
|