procreate 1.0.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.
- data/COPYING +20 -0
- data/ChangeLog +4 -0
- data/README +16 -0
- data/lib/procreate.rb +74 -0
- data/lib/procreate/posix/open4.rb +102 -0
- data/lib/procreate/win32/open4.rb +289 -0
- metadata +78 -0
data/COPYING
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007-2010 Charles Lowe
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/ChangeLog
ADDED
data/README
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
= Introduction
|
2
|
+
|
3
|
+
The procreate library provides a simple but powerful api for process creation,
|
4
|
+
with a thin, cross-platform wrapper around Open4.background.
|
5
|
+
|
6
|
+
Ara Howard's Open4 library does the heavy lifting on POSIX systems, and a
|
7
|
+
compatible Open4 implementation is included for windows use.
|
8
|
+
|
9
|
+
The primary documentation is for the Process.create method.
|
10
|
+
|
11
|
+
= TODO
|
12
|
+
|
13
|
+
* Some useful tests.
|
14
|
+
|
15
|
+
* Perhaps use separate platform gems so linux can depend on open4 gem, and
|
16
|
+
windows on windows api.
|
data/lib/procreate.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
if RUBY_PLATFORM =~ /mswin|mingw/
|
2
|
+
require 'procreate/win32/open4'
|
3
|
+
else
|
4
|
+
require 'procreate/posix/open4'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Process
|
8
|
+
# Return the pid of the process.
|
9
|
+
def pid
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return the process exit status. Will block until the process finishes
|
14
|
+
# executing.
|
15
|
+
def exitstatus
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Process.create is a thin, cross-platform wrapper around Open4.background.
|
21
|
+
#
|
22
|
+
# The given +cmdline+ is run in a background process and a thread object is
|
23
|
+
# returned as a handle. The thread is extended with the Process module, to
|
24
|
+
# facilitate testing for a process object and provide an extension point.
|
25
|
+
# Methods are provided to retrieve the #pid or #exitstatus.
|
26
|
+
#
|
27
|
+
# It takes arguments for :stdin, :stdout, and :stderr redirection, and also a
|
28
|
+
# :shell parameter which determines whether or not the command line should be
|
29
|
+
# run by a shell.
|
30
|
+
#
|
31
|
+
# The default if no streams are provided is that output is thrown away. You
|
32
|
+
# need to explicitly pass eg :stdout => STDOUT if you want to keep STDOUT from
|
33
|
+
# process.
|
34
|
+
#
|
35
|
+
# Currently the shell is avoided where possible to ensure the pid is that of
|
36
|
+
# the actual process, so it can be signaled (ie killed) reliably. Otherwise,
|
37
|
+
# the pid is that of the shell, and any signalling to that pid works as per
|
38
|
+
# the shell's documentation.
|
39
|
+
#
|
40
|
+
def Process.create cmdline, params={}
|
41
|
+
params = params.dup
|
42
|
+
shell = params.delete(:shell) # default is false, so nil is okay
|
43
|
+
if RUBY_PLATFORM !~ /mswin|mingw/
|
44
|
+
if Array === cmdline
|
45
|
+
raise ArgumentError, 'unable to use shell with array command' if shell
|
46
|
+
elsif shell
|
47
|
+
# nothing to do. exec will use /bin/sh anyway
|
48
|
+
else
|
49
|
+
# avoid use of shell, so that #pid on return value is actual process pid
|
50
|
+
# not shell pid
|
51
|
+
require 'shellwords'
|
52
|
+
cmdline = Shellwords.shellwords cmdline.to_s
|
53
|
+
cmdline = [cmdline, cmdline].transpose if cmdline.length == 1
|
54
|
+
end
|
55
|
+
# just due to different behaviour for Open4 unix
|
56
|
+
params.update :status => true
|
57
|
+
else
|
58
|
+
if Array === cmdline
|
59
|
+
# the windows version doesn't support array form of cmdline (though could
|
60
|
+
# perhaps fake using some shell quoting thing).
|
61
|
+
raise ArgumentError, 'array arguments not supported on windows'
|
62
|
+
elsif shell
|
63
|
+
# it won't use the shell by default with CreateProcess.
|
64
|
+
# so force it to do so here:
|
65
|
+
# FIXME, this should really be ENV['COMSPEC'] or whatever.
|
66
|
+
cmdline = "cmd /c #{cmdline}"
|
67
|
+
else
|
68
|
+
# nothing to do
|
69
|
+
end
|
70
|
+
end
|
71
|
+
Open4.background(cmdline, params).extend(Process)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'open4'
|
2
|
+
|
3
|
+
module PosixOpen4 = Open4
|
4
|
+
#
|
5
|
+
# This is a rewritten version of Open4.background. The reason for this is
|
6
|
+
# two-fold:
|
7
|
+
#
|
8
|
+
# * The existing function returns a thread that is extended to have a #pid
|
9
|
+
# method. This in turn waits on a queue, which will never have a pid pushed
|
10
|
+
# to it if there are any problems spawning. This version instead doesn't
|
11
|
+
# create the separate background thread until after the popen4 has
|
12
|
+
# succeeded, meaning any exceptions related to spawn are thrown immediately
|
13
|
+
# and there is no need for a pid queue.
|
14
|
+
# * The existing spawn function calls flatten! on its argv parameter. This is
|
15
|
+
# because it was trying to support either splatted or not-splatted array,
|
16
|
+
# but that falls over for the 1-arg array form of Kernel#exec (eg, to launch
|
17
|
+
# a command that has spaces in its name, without using the shell.)
|
18
|
+
#
|
19
|
+
def background argv, opts={}
|
20
|
+
cmd = [argv].flatten.join(' ')
|
21
|
+
|
22
|
+
getopt = getopts opts
|
23
|
+
|
24
|
+
ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
|
25
|
+
ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
|
26
|
+
exitstatus = getopt[ %w( exitstatus exit_status status ) ]
|
27
|
+
stdin = getopt[ %w( stdin in i 0 ) << 0 ]
|
28
|
+
stdout = getopt[ %w( stdout out o 1 ) << 1 ]
|
29
|
+
stderr = getopt[ %w( stderr err e 2 ) << 2 ]
|
30
|
+
timeout = getopt[ %w( timeout spawn_timeout ) ]
|
31
|
+
stdin_timeout = getopt[ %w( stdin_timeout ) ]
|
32
|
+
stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
|
33
|
+
stderr_timeout = getopt[ %w( stderr_timeout ) ]
|
34
|
+
status = getopt[ %w( status ) ]
|
35
|
+
cwd = getopt[ %w( cwd dir ) ]
|
36
|
+
|
37
|
+
exitstatus =
|
38
|
+
case exitstatus
|
39
|
+
when TrueClass, FalseClass
|
40
|
+
ignore_exit_failure = true if exitstatus
|
41
|
+
[0]
|
42
|
+
else
|
43
|
+
[*(exitstatus || 0)].map{|i| Integer i}
|
44
|
+
end
|
45
|
+
|
46
|
+
stdin ||= '' if stdin_timeout
|
47
|
+
stdout ||= '' if stdout_timeout
|
48
|
+
stderr ||= '' if stderr_timeout
|
49
|
+
|
50
|
+
started = false
|
51
|
+
begin
|
52
|
+
chdir(cwd) do
|
53
|
+
pid, i, o, e = popen4(*argv)
|
54
|
+
started = true
|
55
|
+
|
56
|
+
thread = Thread.new do
|
57
|
+
begin
|
58
|
+
# the semantics of this timeout have been changed slightly, in
|
59
|
+
# that it doesn't include the popen4 call itself anymore. i think
|
60
|
+
# the intention however was for it to apply primarily to the
|
61
|
+
# Process.waitpid
|
62
|
+
Timeout::timeout timeout do
|
63
|
+
te = ThreadEnsemble.new pid
|
64
|
+
te.add_thread(i, stdin) do |i, stdin|
|
65
|
+
relay stdin, i, stdin_timeout
|
66
|
+
i.close rescue nil
|
67
|
+
end
|
68
|
+
te.add_thread(o, stdout) do |o, stdout|
|
69
|
+
relay o, stdout, stdout_timeout
|
70
|
+
end
|
71
|
+
te.add_thread(e, stderr) do |o, stderr|
|
72
|
+
relay e, stderr, stderr_timeout
|
73
|
+
end
|
74
|
+
te.run
|
75
|
+
|
76
|
+
status = Process.waitpid2(pid).last
|
77
|
+
unless ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus)
|
78
|
+
raise SpawnError.new(cmd, status)
|
79
|
+
end
|
80
|
+
status
|
81
|
+
end
|
82
|
+
ensure
|
83
|
+
[i, o, e].each { |fd| fd.close unless fd.closed? }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
sc = class << thread; self; end
|
88
|
+
sc.module_eval do
|
89
|
+
define_method(:pid) { pid }
|
90
|
+
define_method(:spawn_status) { value }
|
91
|
+
define_method(:exitstatus) { spawn_status.exitstatus }
|
92
|
+
end
|
93
|
+
|
94
|
+
thread
|
95
|
+
end
|
96
|
+
rescue
|
97
|
+
raise unless not started and ignore_exec_failure
|
98
|
+
end
|
99
|
+
end
|
100
|
+
module_function :background
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'windows/api'
|
2
|
+
|
3
|
+
module Open4 # :nodoc:
|
4
|
+
end
|
5
|
+
|
6
|
+
#
|
7
|
+
# An implementation of Open4 for windows, based largely on a post by Simon
|
8
|
+
# Kröger to comp.lang.ruby - "Problem with popen on windows"
|
9
|
+
# (http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/a40bac71df1f4a4c).
|
10
|
+
#
|
11
|
+
# Provides an api that loosely matches Ara Howard's Open4 (at least for
|
12
|
+
# Open4.background so far).
|
13
|
+
#
|
14
|
+
module Win32Open4 = Open4
|
15
|
+
# Import necessary raw kernel32 functions
|
16
|
+
module Kernel32 # :nodoc:
|
17
|
+
API = Windows::API
|
18
|
+
API.auto_namespace = 'Win32Open4::Kernel32'
|
19
|
+
API.auto_constant = true
|
20
|
+
API.auto_method = true
|
21
|
+
API.auto_unicode = true
|
22
|
+
|
23
|
+
ERROR_SUCCESS = 0x00
|
24
|
+
FORMAT_MESSAGE_FROM_SYSTEM = 0x1000
|
25
|
+
FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x2000
|
26
|
+
|
27
|
+
WAIT_OBJECT_0 = 0
|
28
|
+
WAIT_TIMEOUT = 0x102
|
29
|
+
WAIT_ABANDONED = 128
|
30
|
+
WAIT_ABANDONED_0 = WAIT_ABANDONED
|
31
|
+
WAIT_FAILED = 0xFFFFFFFF
|
32
|
+
|
33
|
+
NORMAL_PRIORITY_CLASS = 0x00000020
|
34
|
+
STARTUP_INFO_SIZE = 68
|
35
|
+
PROCESS_INFO_SIZE = 16
|
36
|
+
SECURITY_ATTRIBUTES_SIZE = 12
|
37
|
+
|
38
|
+
HANDLE_FLAG_INHERIT = 1
|
39
|
+
HANDLE_FLAG_PROTECT_FROM_CLOSE = 2
|
40
|
+
|
41
|
+
STARTF_USESHOWWINDOW = 0x00000001
|
42
|
+
STARTF_USESTDHANDLES = 0x00000100
|
43
|
+
|
44
|
+
API.new('GetLastError', '', 'L', 'kernel32')
|
45
|
+
API.new('CloseHandle', 'L', 'I', 'kernel32')
|
46
|
+
API.new('WaitForSingleObject', 'LL', 'L')
|
47
|
+
API.new('GetExitCodeProcess', 'LP', 'B')
|
48
|
+
|
49
|
+
params = [
|
50
|
+
'L', # IN DWORD dwFlags,
|
51
|
+
'P', # IN LPCVOID lpSource,
|
52
|
+
'L', # IN DWORD dwMessageId,
|
53
|
+
'L', # IN DWORD dwLanguageId,
|
54
|
+
'P', # OUT LPSTR lpBuffer,
|
55
|
+
'L', # IN DWORD nSize,
|
56
|
+
'P', # IN va_list *Arguments
|
57
|
+
]
|
58
|
+
API.new('FormatMessage', params.join, 'L', 'kernel32')
|
59
|
+
|
60
|
+
params = [
|
61
|
+
'P', # pointer to read handle
|
62
|
+
'P', # pointer to write handle
|
63
|
+
'P', # pointer to security attributes
|
64
|
+
'L' # pipe size
|
65
|
+
]
|
66
|
+
API.new('CreatePipe', params.join, 'I', 'kernel32')
|
67
|
+
|
68
|
+
params = [
|
69
|
+
'L', # handle to an object
|
70
|
+
'L', # specifies flags to change
|
71
|
+
'L' # specifies new values for flags
|
72
|
+
]
|
73
|
+
API.new('SetHandleInformation', params.join, 'I', 'kernel32')
|
74
|
+
|
75
|
+
params = [
|
76
|
+
'L', # IN LPCSTR lpApplicationName
|
77
|
+
'P', # IN LPSTR lpCommandLine
|
78
|
+
'L', # IN LPSECURITY_ATTRIBUTES lpProcessAttributes
|
79
|
+
'L', # IN LPSECURITY_ATTRIBUTES lpThreadAttributes
|
80
|
+
'L', # IN BOOL bInheritHandles
|
81
|
+
'L', # IN DWORD dwCreationFlags
|
82
|
+
'L', # IN LPVOID lpEnvironment
|
83
|
+
'L', # IN LPCSTR lpCurrentDirectory
|
84
|
+
'P', # IN LPSTARTUPINFOA lpStartupInfo
|
85
|
+
'P' # OUT LPPROCESS_INFORMATION lpProcessInformation
|
86
|
+
]
|
87
|
+
API.new('CreateProcess', params.join, 'I', 'kernel32')
|
88
|
+
|
89
|
+
params = [
|
90
|
+
'L', # handle to file to write to
|
91
|
+
'P', # pointer to data to write to file
|
92
|
+
'L', # number of bytes to write
|
93
|
+
'P', # pointer to number of bytes written
|
94
|
+
'L' # pointer to structure for overlapped I/O
|
95
|
+
]
|
96
|
+
API.new('WriteFile', params.join, 'I', 'kernel32')
|
97
|
+
|
98
|
+
params = [
|
99
|
+
'L', # handle of file to read
|
100
|
+
'P', # pointer to buffer that receives data
|
101
|
+
'L', # number of bytes to read
|
102
|
+
'P', # pointer to number of bytes read
|
103
|
+
'L' # pointer to structure for data
|
104
|
+
]
|
105
|
+
API.new('ReadFile', params.join, 'I', 'kernel32')
|
106
|
+
|
107
|
+
params = [
|
108
|
+
'L', # handle to pipe to copy from
|
109
|
+
'L', # pointer to data buffer
|
110
|
+
'L', # size, in bytes, of data buffer
|
111
|
+
'L', # pointer to number of bytes read
|
112
|
+
'P', # pointer to total number of bytes available
|
113
|
+
'L' # pointer to unread bytes in this message
|
114
|
+
]
|
115
|
+
API.new('PeekNamedPipe', params.join, 'I', 'kernel32')
|
116
|
+
end
|
117
|
+
|
118
|
+
# Now add wrapper functions
|
119
|
+
module Kernel32 # :nodoc:
|
120
|
+
extend self
|
121
|
+
|
122
|
+
class Kernel32Error < StandardError # :nodoc:
|
123
|
+
end
|
124
|
+
|
125
|
+
module_function
|
126
|
+
|
127
|
+
def raise_last_error!
|
128
|
+
errorCode = GetLastError()
|
129
|
+
if errorCode != ERROR_SUCCESS
|
130
|
+
msg = ' ' * 255
|
131
|
+
msgLength = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, '', errorCode, 0, msg, 255, '')
|
132
|
+
msg.delete! 0.chr
|
133
|
+
msg.strip!
|
134
|
+
raise Kernel32Error, msg
|
135
|
+
else
|
136
|
+
raise Kernel32Error, 'GetLastError returned ERROR_SUCCESS'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# returns read and write handle
|
141
|
+
def create_pipe
|
142
|
+
read_handle, write_handle = Array.new(2) { [0].pack('I') }
|
143
|
+
sec_attrs = [SECURITY_ATTRIBUTES_SIZE, 0, 1].pack('III')
|
144
|
+
raise_last_error! if CreatePipe(read_handle, write_handle, sec_attrs, 0).zero?
|
145
|
+
[read_handle.unpack('I')[0], write_handle.unpack('I')[0]]
|
146
|
+
end
|
147
|
+
|
148
|
+
def set_handle_information(handle, flags, value)
|
149
|
+
raise_last_error! if SetHandleInformation(handle, flags, value).zero?
|
150
|
+
end
|
151
|
+
|
152
|
+
def close_handle(handle)
|
153
|
+
raise_last_error! if CloseHandle(handle).zero?
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_process(command, stdin, stdout, stderr)
|
157
|
+
startupInfo = [
|
158
|
+
STARTUP_INFO_SIZE,
|
159
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
160
|
+
STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW,
|
161
|
+
0, 0, 0,
|
162
|
+
stdin, stdout, stderr
|
163
|
+
].pack('IIIIIIIIIIIISSIIII')
|
164
|
+
processInfo = [0, 0, 0, 0].pack('IIII')
|
165
|
+
command = command + 0.chr
|
166
|
+
raise_last_error! if CreateProcessA(0, command, 0, 0, 1, 0, 0, 0, startupInfo, processInfo).zero?
|
167
|
+
# hProcess, hThread, dwProcessId, dwThreadId
|
168
|
+
processInfo.unpack('LLLL')
|
169
|
+
end
|
170
|
+
|
171
|
+
def write_file(hFile, buffer)
|
172
|
+
written = [0].pack('I')
|
173
|
+
raise_last_error! if WriteFile(hFile, buffer, buffer.size, written, 0).zero?
|
174
|
+
written.unpack('I')[0]
|
175
|
+
end
|
176
|
+
|
177
|
+
def read_file(hFile, size=1024)
|
178
|
+
number = [0].pack('I')
|
179
|
+
buffer = ' ' * size
|
180
|
+
# FIXME? we're masking errors here and just returning an empty string...
|
181
|
+
return '' if ReadFile(hFile, buffer, size, number, 0).zero?
|
182
|
+
buffer[0...number.unpack('I')[0]]
|
183
|
+
end
|
184
|
+
|
185
|
+
def peek_named_pipe(hFile)
|
186
|
+
available = [0].pack('I')
|
187
|
+
# FIXME? as above
|
188
|
+
return -1 if PeekNamedPipe(hFile, 0, 0, 0, available, 0).zero?
|
189
|
+
available.unpack('I')[0]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Responsible for launching a process, forwarding its stdout & stderr, and
|
194
|
+
# catching its error code.
|
195
|
+
# TODO: stdin is not currently handled in the ProcessHandler#relay method,
|
196
|
+
# though it should be fairly straightforward to add.
|
197
|
+
class ProcessHandler # :nodoc:
|
198
|
+
include Win32Open4::Kernel32
|
199
|
+
|
200
|
+
def initialize cmdline, params={}
|
201
|
+
stdin, @stdout, @stderr = params.values_at :stdin, :stdout, :stderr
|
202
|
+
if stdin
|
203
|
+
raise NotImplementError, 'no stdin forwarding yet'
|
204
|
+
end
|
205
|
+
|
206
|
+
# create 3 pipes
|
207
|
+
pw, pr, pe = Array.new(3) { create_pipe }
|
208
|
+
|
209
|
+
set_handle_information pw.last, HANDLE_FLAG_INHERIT, 0
|
210
|
+
set_handle_information pr.first, HANDLE_FLAG_INHERIT, 0
|
211
|
+
set_handle_information pe.first, HANDLE_FLAG_INHERIT, 0
|
212
|
+
|
213
|
+
@phandle, hThread, @processId, dwThreadId = create_process cmdline.to_s, pw.first, pr.last, pe.last
|
214
|
+
|
215
|
+
# we have to close the handles, so the pipes terminate with the process
|
216
|
+
close_handle pw.first
|
217
|
+
close_handle pr.last
|
218
|
+
close_handle pe.last
|
219
|
+
|
220
|
+
finalizer = self.class.finalizer [@phandle, pw.last, pr.first, pe.first]
|
221
|
+
ObjectSpace.define_finalizer self, finalizer
|
222
|
+
|
223
|
+
@pr, @pe = pr, pe
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.finalizer handles
|
227
|
+
proc do
|
228
|
+
handles.each { |h| close_handle h }
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def run
|
233
|
+
loop do
|
234
|
+
relay
|
235
|
+
|
236
|
+
case WaitForSingleObject @phandle, 10
|
237
|
+
when WAIT_TIMEOUT
|
238
|
+
# ok...
|
239
|
+
when WAIT_OBJECT_0
|
240
|
+
relay
|
241
|
+
exit_code = [0].pack 'L'
|
242
|
+
if GetExitCodeProcess @phandle, exit_code
|
243
|
+
exit_status = exit_code.unpack('L')[0]
|
244
|
+
end
|
245
|
+
return exit_status
|
246
|
+
when -1, WAIT_ABANDONED, WAIT_FAILED
|
247
|
+
raise_last_error!
|
248
|
+
end
|
249
|
+
|
250
|
+
sleep 0.01
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def relay
|
255
|
+
stdout = ''
|
256
|
+
avail = peek_named_pipe(@pr.first)
|
257
|
+
stdout = read_file(@pr.first, avail) if avail > 0
|
258
|
+
|
259
|
+
stderr = ''
|
260
|
+
avail = peek_named_pipe(@pe.first)
|
261
|
+
stderr = read_file(@pe.first, avail) if avail > 0
|
262
|
+
|
263
|
+
@stdout << stdout if !stdout.empty? and @stdout
|
264
|
+
@stderr << stderr if !stderr.empty? and @stderr
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
#
|
269
|
+
# Launches a process asynchronously, and returns a thread that can be used to
|
270
|
+
# retrieve exit status, or wait for completion. All calls used are
|
271
|
+
# non-blocking such that it works safely with ruby's green threads.
|
272
|
+
#
|
273
|
+
# Note that while broadly compatible there are a number of differences from
|
274
|
+
# original Open4.background - eg parameters like :status are ignored (in fact
|
275
|
+
# it works like :status => true in that it won't throw exceptions).
|
276
|
+
#
|
277
|
+
def self.background cmdline, params={}
|
278
|
+
ph = ProcessHandler.new cmdline, params
|
279
|
+
pid = ph.instance_variable_get :@processId
|
280
|
+
thread = Thread.new { ph.run }
|
281
|
+
sc = class << thread; self; end
|
282
|
+
sc.module_eval do
|
283
|
+
define_method(:pid) { pid }
|
284
|
+
define_method(:exitstatus) { value }
|
285
|
+
end
|
286
|
+
thread
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: procreate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Charles Lowe
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-04 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: A library for process creation with a simple but powerful api, built on Open4. Includes compatible Win32 Open4 implementation.
|
23
|
+
email: aquasync@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README
|
30
|
+
- ChangeLog
|
31
|
+
files:
|
32
|
+
- README
|
33
|
+
- COPYING
|
34
|
+
- ChangeLog
|
35
|
+
- lib/procreate/posix/open4.rb
|
36
|
+
- lib/procreate/win32/open4.rb
|
37
|
+
- lib/procreate.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/aquasync/procreate
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --main
|
45
|
+
- README
|
46
|
+
- --title
|
47
|
+
- procreate documentation
|
48
|
+
- --tab-width
|
49
|
+
- "2"
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.3.7
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Create processes in a flexible and cross-platform way.
|
77
|
+
test_files: []
|
78
|
+
|