procreate 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|