childprocess 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
File without changes
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.1.6
data/childprocess.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{childprocess}
8
- s.version = "0.1.5"
8
+ s.version = "0.1.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jari Bakken"]
12
- s.date = %q{2010-12-10}
12
+ s.date = %q{2010-12-22}
13
13
  s.description = %q{This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.}
14
14
  s.email = %q{jari.bakken@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
  ]
19
19
  s.files = [
20
20
  ".document",
21
+ ".rspec",
21
22
  "LICENSE",
22
23
  "README.rdoc",
23
24
  "Rakefile",
@@ -47,7 +48,6 @@ Gem::Specification.new do |s|
47
48
  "spec/abstract_io_spec.rb",
48
49
  "spec/childprocess_spec.rb",
49
50
  "spec/jruby_spec.rb",
50
- "spec/spec.opts",
51
51
  "spec/spec_helper.rb",
52
52
  "spec/unix_spec.rb",
53
53
  "spec/windows_spec.rb"
@@ -1,6 +1,6 @@
1
1
  module ChildProcess
2
2
  class AbstractIO
3
- attr_reader :stderr, :stdout
3
+ attr_reader :stderr, :stdout, :stdin
4
4
 
5
5
  def inherit!
6
6
  @stdout = STDOUT
@@ -17,6 +17,15 @@ module ChildProcess
17
17
  @stdout = io
18
18
  end
19
19
 
20
+ #
21
+ # @api private
22
+ #
23
+
24
+ def _stdin=(io)
25
+ check_type io
26
+ @stdin = io
27
+ end
28
+
20
29
  private
21
30
 
22
31
  def check_type(io)
@@ -7,6 +7,10 @@ module ChildProcess
7
7
  #
8
8
  attr_accessor :detach
9
9
 
10
+ #
11
+ # Set this to true if you want to write to the process' stdin (process.io.stdin)
12
+ #
13
+ attr_accessor :duplex
10
14
 
11
15
  #
12
16
  # Create a new process with the given args.
@@ -20,6 +24,7 @@ module ChildProcess
20
24
  @started = false
21
25
  @exit_code = nil
22
26
  @detach = false
27
+ @duplex = false
23
28
  end
24
29
 
25
30
  #
@@ -117,6 +122,10 @@ module ChildProcess
117
122
  @detach
118
123
  end
119
124
 
125
+ def duplex?
126
+ @duplex
127
+ end
128
+
120
129
  def log(*args)
121
130
  $stderr.puts "#{self.inspect} : #{args.inspect}" if $DEBUG
122
131
  end
@@ -126,4 +135,4 @@ module ChildProcess
126
135
  end
127
136
 
128
137
  end # AbstractProcess
129
- end # ChildProcess
138
+ end # ChildProcess
@@ -30,13 +30,14 @@ module ChildProcess
30
30
 
31
31
  private
32
32
 
33
- def launch_process
33
+ def launch_process(&blk)
34
34
  pb = java.lang.ProcessBuilder.new(@args)
35
35
 
36
36
  env = pb.environment
37
37
  ENV.each { |k,v| env.put(k, v) }
38
38
 
39
39
  @process = pb.start
40
+
40
41
  setup_io
41
42
  end
42
43
 
@@ -48,6 +49,12 @@ module ChildProcess
48
49
  @process.getErrorStream.close
49
50
  @process.getInputStream.close
50
51
  end
52
+
53
+ if duplex?
54
+ io._stdin = @process.getOutputStream.to_io
55
+ else
56
+ @process.getOutputStream.close
57
+ end
51
58
  end
52
59
 
53
60
  def redirect(input, output)
@@ -72,13 +72,27 @@ module ChildProcess
72
72
  stderr = @io.stderr
73
73
  end
74
74
 
75
+ if duplex?
76
+ reader, writer = ::IO.pipe
77
+ end
78
+
75
79
  @pid = fork {
76
80
  STDOUT.reopen(stdout || "/dev/null")
77
81
  STDERR.reopen(stderr || "/dev/null")
78
82
 
83
+ if duplex?
84
+ STDIN.reopen(reader)
85
+ writer.close
86
+ end
87
+
79
88
  exec(*@args)
80
89
  }
81
90
 
91
+ if duplex?
92
+ io._stdin = writer
93
+ reader.close
94
+ end
95
+
82
96
  ::Process.detach(@pid) if detach?
83
97
  end
84
98
 
@@ -33,7 +33,7 @@ module ChildProcess
33
33
  raise ArgumentError, "expected #{file.inspect} to respond to :fileno"
34
34
  end
35
35
 
36
- handle = Lib.get_os_file_handle(file.fileno)
36
+ handle = Lib.handle_for(file.fileno)
37
37
 
38
38
  ok = Lib.set_handle_information(handle, HANDLE_FLAG_INHERIT, 0)
39
39
  ok or raise Error, Lib.last_error_message
@@ -23,6 +23,8 @@ module ChildProcess::Windows
23
23
  INVALID_HANDLE_VALUE = 0xFFFFFFFF
24
24
  HANDLE_FLAG_INHERIT = 0x00000001
25
25
 
26
+ DUPLICATE_SAME_ACCESS = 0x00000002
27
+
26
28
  module Lib
27
29
  enum :wait_status, [ :wait_object_0, 0,
28
30
  :wait_timeout, 0x102,
@@ -18,8 +18,22 @@ module ChildProcess
18
18
  si[:dwFlags] |= STARTF_USESTDHANDLES
19
19
  inherit = true
20
20
 
21
- si[:hStdOutput] = get_os_file_handle(opts[:stdout].fileno) if opts[:stdout]
22
- si[:hStdError] = get_os_file_handle(opts[:stderr].fileno) if opts[:stderr]
21
+ si[:hStdOutput] = handle_for(opts[:stdout].fileno) if opts[:stdout]
22
+ si[:hStdError] = handle_for(opts[:stderr].fileno) if opts[:stderr]
23
+ end
24
+
25
+ if opts[:duplex]
26
+ read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
27
+ write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
28
+ sa = SecurityAttributes.new(:inherit => true)
29
+
30
+ ok = create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
31
+ ok or raise Error, last_error_message
32
+
33
+ read_pipe = read_pipe_ptr.read_pointer
34
+ write_pipe = write_pipe_ptr.read_pointer
35
+
36
+ si[:hStdInput] = read_pipe
23
37
  end
24
38
 
25
39
  ok = create_process(nil, cmd_ptr, nil, nil, inherit, flags, nil, nil, si, pi)
@@ -28,6 +42,12 @@ module ChildProcess
28
42
  close_handle pi[:hProcess]
29
43
  close_handle pi[:hThread]
30
44
 
45
+ if opts[:duplex]
46
+ opts[:stdin] = io_for(duplicate_handle(write_pipe), File::WRONLY)
47
+ close_handle read_pipe
48
+ close_handle write_pipe
49
+ end
50
+
31
51
  pi[:dwProcessId]
32
52
  end
33
53
 
@@ -43,12 +63,12 @@ module ChildProcess
43
63
  buf.read_string(size).strip
44
64
  end
45
65
 
46
- def self.get_os_file_handle(fd_or_io)
66
+ def self.handle_for(fd_or_io)
47
67
  case fd_or_io
48
68
  when IO
49
- handle = _get_osfhandle(fd.fileno)
69
+ handle = get_osfhandle(fd.fileno)
50
70
  when Fixnum
51
- handle = _get_osfhandle(fd_or_io)
71
+ handle = get_osfhandle(fd_or_io)
52
72
  else
53
73
  if fd_or_io.respond_to?(:to_io)
54
74
  io = fd_or_io.to_io
@@ -57,19 +77,42 @@ module ChildProcess
57
77
  raise TypeError, "expected #to_io to return an instance of IO"
58
78
  end
59
79
 
60
- handle = _get_osfhandle(io.fileno)
80
+ handle = get_osfhandle(io.fileno)
61
81
  else
62
82
  raise TypeError, "invalid type: #{fd_or_io.inspect}"
63
83
  end
64
84
  end
65
85
 
66
86
  if handle == INVALID_HANDLE_VALUE
67
- raise Error, Lib.last_error_message
87
+ raise Error, last_error_message
68
88
  end
69
89
 
70
90
  handle
71
91
  end
72
92
 
93
+ def self.io_for(handle, flags = File::RDONLY)
94
+ fd = open_osfhandle(handle, flags)
95
+ if fd == -1
96
+ raise Error, last_error_message
97
+ end
98
+
99
+ ::IO.for_fd fd, flags
100
+ end
101
+
102
+ def self.duplicate_handle(handle)
103
+ dup = FFI::MemoryPointer.new(:pointer)
104
+ proc = current_process
105
+
106
+ ok = _duplicate_handle(
107
+ proc, handle, proc, dup, 0, false, DUPLICATE_SAME_ACCESS)
108
+
109
+ ok or raise Error, last_error_message
110
+
111
+ dup.read_pointer
112
+ ensure
113
+ close_handle proc
114
+ end
115
+
73
116
  #
74
117
  # BOOL WINAPI CreateProcess(
75
118
  # __in_opt LPCTSTR lpApplicationName,
@@ -181,7 +224,16 @@ module ChildProcess
181
224
  # );
182
225
  #
183
226
 
184
- attach_function :_get_osfhandle, :_get_osfhandle, [:int], :long
227
+ attach_function :get_osfhandle, :_get_osfhandle, [:int], :long
228
+
229
+ #
230
+ # int _open_osfhandle (
231
+ # intptr_t osfhandle,
232
+ # int flags
233
+ # );
234
+ #
235
+
236
+ attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int
185
237
 
186
238
  # BOOL WINAPI SetHandleInformation(
187
239
  # __in HANDLE hObject,
@@ -191,6 +243,42 @@ module ChildProcess
191
243
 
192
244
  attach_function :set_handle_information, :SetHandleInformation, [:long, :ulong, :ulong], :bool
193
245
 
246
+ # BOOL WINAPI CreatePipe(
247
+ # __out PHANDLE hReadPipe,
248
+ # __out PHANDLE hWritePipe,
249
+ # __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
250
+ # __in DWORD nSize
251
+ # );
252
+
253
+ attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool
254
+
255
+ #
256
+ # HANDLE WINAPI GetCurrentProcess(void);
257
+ #
258
+
259
+ attach_function :current_process, :GetCurrentProcess, [], :pointer
260
+
261
+ #
262
+ # BOOL WINAPI DuplicateHandle(
263
+ # __in HANDLE hSourceProcessHandle,
264
+ # __in HANDLE hSourceHandle,
265
+ # __in HANDLE hTargetProcessHandle,
266
+ # __out LPHANDLE lpTargetHandle,
267
+ # __in DWORD dwDesiredAccess,
268
+ # __in BOOL bInheritHandle,
269
+ # __in DWORD dwOptions
270
+ # );
271
+ #
272
+
273
+ attach_function :_duplicate_handle, :DuplicateHandle, [
274
+ :pointer,
275
+ :pointer,
276
+ :pointer,
277
+ :pointer,
278
+ :ulong,
279
+ :bool,
280
+ :ulong
281
+ ], :bool
194
282
  end # Lib
195
283
  end # Windows
196
284
  end # ChildProcess
@@ -40,6 +40,7 @@ module ChildProcess
40
40
  opts = {
41
41
  :inherit => false,
42
42
  :detach => detach?,
43
+ :duplex => duplex?
43
44
  }
44
45
 
45
46
  if @io
@@ -47,12 +48,16 @@ module ChildProcess
47
48
  opts[:stderr] = @io.stderr
48
49
  end
49
50
 
50
- # TODO: escape/quote arguments properly
51
- command = @args.join ' '
51
+ # TODO: escape/quote arguments properly
52
+ command = @args.join ' '
52
53
 
53
54
  @pid = Lib.create_proc(command, opts)
54
55
  @handle = Handle.open(@pid)
55
56
 
57
+ if duplex?
58
+ io._stdin = opts[:stdin]
59
+ end
60
+
56
61
  self
57
62
  end
58
63
 
@@ -57,4 +57,25 @@ module ChildProcess::Windows
57
57
  :dwThreadId, :ulong
58
58
  end
59
59
 
60
+ #
61
+ # typedef struct _SECURITY_ATTRIBUTES {
62
+ # DWORD nLength;
63
+ # LPVOID lpSecurityDescriptor;
64
+ # BOOL bInheritHandle;
65
+ # } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
66
+ #
67
+
68
+ class SecurityAttributes < FFI::Struct
69
+ layout :nLength, :ulong,
70
+ :lpSecurityDescriptor, :pointer, # void ptr
71
+ :bInheritHandle, :int
72
+
73
+ def initialize(opts = {})
74
+ super()
75
+
76
+ self[:nLength] = self.class.size
77
+ self[:lpSecurityDescriptor] = nil
78
+ self[:bInheritHandle] = opts[:inherit] ? 1 : 0
79
+ end
80
+ end
60
81
  end
@@ -66,12 +66,13 @@ describe ChildProcess do
66
66
  pending "how do we spec this?"
67
67
  end
68
68
 
69
- it "can redirect stdout and stderr" do
69
+ it "can redirect stdout, stderr" do
70
70
  process = ruby(<<-CODE)
71
71
  [STDOUT, STDERR].each_with_index do |io, idx|
72
72
  io.sync = true
73
73
  io.puts idx
74
74
  end
75
+
75
76
  sleep 0.2
76
77
  CODE
77
78
 
@@ -83,6 +84,7 @@ describe ChildProcess do
83
84
  process.io.stderr = err
84
85
 
85
86
  process.start
87
+ process.io.stdin.should be_nil
86
88
  process.poll_for_exit(EXIT_TIMEOUT)
87
89
 
88
90
  out.rewind
@@ -96,6 +98,31 @@ describe ChildProcess do
96
98
  end
97
99
  end
98
100
 
101
+ it "can write to stdin if duplex = true" do
102
+ process = ruby(<<-CODE)
103
+ puts(STDIN.gets.chomp)
104
+ CODE
105
+
106
+ out = Tempfile.new("duplex")
107
+
108
+ begin
109
+ process.io.stdout = out
110
+ process.io.stderr = out
111
+ process.duplex = true
112
+
113
+ process.start
114
+ process.io.stdin.puts "hello world"
115
+ process.io.stdin.close # JRuby seems to need this
116
+
117
+ process.poll_for_exit(EXIT_TIMEOUT)
118
+
119
+ out.rewind
120
+ out.read.should == "hello world\n"
121
+ ensure
122
+ out.close
123
+ end
124
+ end
125
+
99
126
  it "can set close-on-exec when IO is inherited" do
100
127
  server = TCPServer.new("localhost", 4433)
101
128
  ChildProcess.close_on_exec server
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 5
9
- version: 0.1.5
8
+ - 6
9
+ version: 0.1.6
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jari Bakken
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-10 00:00:00 +01:00
17
+ date: 2010-12-22 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -71,6 +71,7 @@ extra_rdoc_files:
71
71
  - README.rdoc
72
72
  files:
73
73
  - .document
74
+ - .rspec
74
75
  - LICENSE
75
76
  - README.rdoc
76
77
  - Rakefile
@@ -100,7 +101,6 @@ files:
100
101
  - spec/abstract_io_spec.rb
101
102
  - spec/childprocess_spec.rb
102
103
  - spec/jruby_spec.rb
103
- - spec/spec.opts
104
104
  - spec/spec_helper.rb
105
105
  - spec/unix_spec.rb
106
106
  - spec/windows_spec.rb