childprocess 0.1.5 → 0.1.6

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.
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