open4 1.0.1 → 1.3.4

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/rakefile ADDED
@@ -0,0 +1,394 @@
1
+ This.rubyforge_project = 'codeforpeople'
2
+ This.author = "Ara T. Howard"
3
+ This.email = "ara.t.howard@gmail.com"
4
+ This.homepage = "https://github.com/ahoward/#{ This.lib }"
5
+
6
+ task :license do
7
+ open('LICENSE', 'w'){|fd| fd.puts "Ruby"}
8
+ end
9
+
10
+ task :default do
11
+ puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
12
+ end
13
+
14
+ task :test do
15
+ run_tests!
16
+ end
17
+
18
+ namespace :test do
19
+ task(:unit){ run_tests!(:unit) }
20
+ task(:functional){ run_tests!(:functional) }
21
+ task(:integration){ run_tests!(:integration) }
22
+ end
23
+
24
+ def run_tests!(which = nil)
25
+ which ||= '**'
26
+ test_dir = File.join(This.dir, "test")
27
+ test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
28
+ test_rbs = Dir.glob(test_glob).sort
29
+
30
+ div = ('=' * 119)
31
+ line = ('-' * 119)
32
+
33
+ test_rbs.each_with_index do |test_rb, index|
34
+ testno = index + 1
35
+ command = "#{ This.ruby } -rubygems -w -I ./lib -I ./test/lib #{ test_rb }"
36
+
37
+ puts
38
+ say(div, :color => :cyan, :bold => true)
39
+ say("@#{ testno } => ", :bold => true, :method => :print)
40
+ say(command, :color => :cyan, :bold => true)
41
+ say(line, :color => :cyan, :bold => true)
42
+
43
+ system(command)
44
+
45
+ say(line, :color => :cyan, :bold => true)
46
+
47
+ status = $?.exitstatus
48
+
49
+ if status.zero?
50
+ say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
51
+ say("SUCCESS", :color => :green, :bold => true)
52
+ else
53
+ say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
54
+ say("FAILURE", :color => :red, :bold => true)
55
+ end
56
+ say(line, :color => :cyan, :bold => true)
57
+
58
+ exit(status) unless status.zero?
59
+ end
60
+ end
61
+
62
+
63
+ task :gemspec do
64
+ ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
65
+ ignore_directories = ['pkg']
66
+ ignore_files = ['test/log']
67
+
68
+ shiteless =
69
+ lambda do |list|
70
+ list.delete_if do |entry|
71
+ next unless test(?e, entry)
72
+ extension = File.basename(entry).split(%r/[.]/).last
73
+ ignore_extensions.any?{|ext| ext === extension}
74
+ end
75
+ list.delete_if do |entry|
76
+ next unless test(?d, entry)
77
+ dirname = File.expand_path(entry)
78
+ ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
79
+ end
80
+ list.delete_if do |entry|
81
+ next unless test(?f, entry)
82
+ filename = File.expand_path(entry)
83
+ ignore_files.any?{|file| File.expand_path(file) == filename}
84
+ end
85
+ end
86
+
87
+ lib = This.lib
88
+ object = This.object
89
+ version = This.version
90
+ files = shiteless[Dir::glob("**/**")]
91
+ executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
92
+ #has_rdoc = true #File.exist?('doc')
93
+ test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb")
94
+ summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass"
95
+ description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass"
96
+ license = object.respond_to?(:license) ? object.license : "Ruby"
97
+
98
+ if This.extensions.nil?
99
+ This.extensions = []
100
+ extensions = This.extensions
101
+ %w( Makefile configure extconf.rb ).each do |ext|
102
+ extensions << ext if File.exists?(ext)
103
+ end
104
+ end
105
+ extensions = [extensions].flatten.compact
106
+
107
+ if This.dependencies.nil?
108
+ dependencies = []
109
+ else
110
+ case This.dependencies
111
+ when Hash
112
+ dependencies = This.dependencies.values
113
+ when Array
114
+ dependencies = This.dependencies
115
+ end
116
+ end
117
+
118
+ template =
119
+ if test(?e, 'gemspec.erb')
120
+ Template{ IO.read('gemspec.erb') }
121
+ else
122
+ Template {
123
+ <<-__
124
+ ## <%= lib %>.gemspec
125
+ #
126
+
127
+ Gem::Specification::new do |spec|
128
+ spec.name = <%= lib.inspect %>
129
+ spec.version = <%= version.inspect %>
130
+ spec.platform = Gem::Platform::RUBY
131
+ spec.summary = <%= lib.inspect %>
132
+ spec.description = <%= description.inspect %>
133
+ spec.license = <%= license.inspect %>
134
+
135
+ spec.files =\n<%= files.sort.pretty_inspect %>
136
+ spec.executables = <%= executables.inspect %>
137
+
138
+ spec.require_path = "lib"
139
+
140
+ spec.test_files = <%= test_files.inspect %>
141
+
142
+ <% dependencies.each do |lib_version| %>
143
+ spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
144
+ <% end %>
145
+
146
+ spec.extensions.push(*<%= extensions.inspect %>)
147
+
148
+ spec.rubyforge_project = <%= This.rubyforge_project.inspect %>
149
+ spec.author = <%= This.author.inspect %>
150
+ spec.email = <%= This.email.inspect %>
151
+ spec.homepage = <%= This.homepage.inspect %>
152
+ end
153
+ __
154
+ }
155
+ end
156
+
157
+ Fu.mkdir_p(This.pkgdir)
158
+ gemspec = "#{ lib }.gemspec"
159
+ open(gemspec, "w"){|fd| fd.puts(template)}
160
+ This.gemspec = gemspec
161
+ end
162
+
163
+ task :gem => [:clean, :gemspec] do
164
+ Fu.mkdir_p(This.pkgdir)
165
+ before = Dir['*.gem']
166
+ cmd = "gem build #{ This.gemspec }"
167
+ `#{ cmd }`
168
+ after = Dir['*.gem']
169
+ gem = ((after - before).first || after.first) or abort('no gem!')
170
+ Fu.mv(gem, This.pkgdir)
171
+ This.gem = File.join(This.pkgdir, File.basename(gem))
172
+ end
173
+
174
+ task :readme do
175
+ samples = ''
176
+ prompt = '~ > '
177
+ lib = This.lib
178
+ version = This.version
179
+
180
+ Dir['sample*/*'].sort.each do |sample|
181
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
182
+
183
+ cmd = "cat #{ sample }"
184
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
185
+ samples << Util.indent(`#{ cmd }`, 4) << "\n"
186
+
187
+ cmd = "ruby #{ sample }"
188
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
189
+
190
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
191
+ samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n"
192
+ end
193
+
194
+ template =
195
+ if test(?e, 'README.erb')
196
+ Template{ IO.read('README.erb') }
197
+ else
198
+ Template {
199
+ <<-__
200
+ NAME
201
+ #{ lib }
202
+
203
+ DESCRIPTION
204
+
205
+ INSTALL
206
+ gem install #{ lib }
207
+
208
+ SAMPLES
209
+ #{ samples }
210
+ __
211
+ }
212
+ end
213
+
214
+ open("README", "w"){|fd| fd.puts template}
215
+ end
216
+
217
+
218
+ task :clean do
219
+ Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)}
220
+ end
221
+
222
+
223
+ task :release => [:clean, :gemspec, :gem] do
224
+ gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
225
+ raise "which one? : #{ gems.inspect }" if gems.size > 1
226
+ raise "no gems?" if gems.size < 1
227
+
228
+ cmd = "gem push #{ This.gem }"
229
+ puts cmd
230
+ puts
231
+ system(cmd)
232
+ abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
233
+
234
+ cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
235
+ puts cmd
236
+ puts
237
+ system(cmd)
238
+ abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
239
+ end
240
+
241
+
242
+
243
+
244
+
245
+ BEGIN {
246
+ # support for this rakefile
247
+ #
248
+ $VERBOSE = nil
249
+
250
+ require 'ostruct'
251
+ require 'erb'
252
+ require 'fileutils'
253
+ require 'rbconfig'
254
+ require 'pp'
255
+
256
+ # fu shortcut
257
+ #
258
+ Fu = FileUtils
259
+
260
+ # cache a bunch of stuff about this rakefile/environment
261
+ #
262
+ This = OpenStruct.new
263
+
264
+ This.file = File.expand_path(__FILE__)
265
+ This.dir = File.dirname(This.file)
266
+ This.pkgdir = File.join(This.dir, 'pkg')
267
+
268
+ # grok lib
269
+ #
270
+ lib = ENV['LIB']
271
+ unless lib
272
+ lib = File.basename(Dir.pwd).sub(/[-].*$/, '')
273
+ end
274
+ This.lib = lib
275
+
276
+ # grok version
277
+ #
278
+ version = ENV['VERSION']
279
+ unless version
280
+ require "./lib/#{ This.lib }"
281
+ This.name = lib.capitalize
282
+ This.object = eval(This.name)
283
+ version = This.object.send(:version)
284
+ end
285
+ This.version = version
286
+
287
+ # see if dependencies are export by the module
288
+ #
289
+ if This.object.respond_to?(:dependencies)
290
+ This.dependencies = This.object.dependencies
291
+ end
292
+
293
+ # we need to know the name of the lib an it's version
294
+ #
295
+ abort('no lib') unless This.lib
296
+ abort('no version') unless This.version
297
+
298
+ # discover full path to this ruby executable
299
+ #
300
+ c = Config::CONFIG
301
+ bindir = c["bindir"] || c['BINDIR']
302
+ ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
303
+ ruby_ext = c['EXEEXT'] || ''
304
+ ruby = File.join(bindir, (ruby_install_name + ruby_ext))
305
+ This.ruby = ruby
306
+
307
+ # some utils
308
+ #
309
+ module Util
310
+ def indent(s, n = 2)
311
+ s = unindent(s)
312
+ ws = ' ' * n
313
+ s.gsub(%r/^/, ws)
314
+ end
315
+
316
+ def unindent(s)
317
+ indent = nil
318
+ s.each_line do |line|
319
+ next if line =~ %r/^\s*$/
320
+ indent = line[%r/^\s*/] and break
321
+ end
322
+ indent ? s.gsub(%r/^#{ indent }/, "") : s
323
+ end
324
+ extend self
325
+ end
326
+
327
+ # template support
328
+ #
329
+ class Template
330
+ def initialize(&block)
331
+ @block = block
332
+ @template = block.call.to_s
333
+ end
334
+ def expand(b=nil)
335
+ ERB.new(Util.unindent(@template)).result((b||@block).binding)
336
+ end
337
+ alias_method 'to_s', 'expand'
338
+ end
339
+ def Template(*args, &block) Template.new(*args, &block) end
340
+
341
+ # colored console output support
342
+ #
343
+ This.ansi = {
344
+ :clear => "\e[0m",
345
+ :reset => "\e[0m",
346
+ :erase_line => "\e[K",
347
+ :erase_char => "\e[P",
348
+ :bold => "\e[1m",
349
+ :dark => "\e[2m",
350
+ :underline => "\e[4m",
351
+ :underscore => "\e[4m",
352
+ :blink => "\e[5m",
353
+ :reverse => "\e[7m",
354
+ :concealed => "\e[8m",
355
+ :black => "\e[30m",
356
+ :red => "\e[31m",
357
+ :green => "\e[32m",
358
+ :yellow => "\e[33m",
359
+ :blue => "\e[34m",
360
+ :magenta => "\e[35m",
361
+ :cyan => "\e[36m",
362
+ :white => "\e[37m",
363
+ :on_black => "\e[40m",
364
+ :on_red => "\e[41m",
365
+ :on_green => "\e[42m",
366
+ :on_yellow => "\e[43m",
367
+ :on_blue => "\e[44m",
368
+ :on_magenta => "\e[45m",
369
+ :on_cyan => "\e[46m",
370
+ :on_white => "\e[47m"
371
+ }
372
+ def say(phrase, *args)
373
+ options = args.last.is_a?(Hash) ? args.pop : {}
374
+ options[:color] = args.shift.to_s.to_sym unless args.empty?
375
+ keys = options.keys
376
+ keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
377
+
378
+ color = options[:color]
379
+ bold = options.has_key?(:bold)
380
+
381
+ parts = [phrase]
382
+ parts.unshift(This.ansi[color]) if color
383
+ parts.unshift(This.ansi[:bold]) if bold
384
+ parts.push(This.ansi[:clear]) if parts.size > 1
385
+
386
+ method = options[:method] || :puts
387
+
388
+ Kernel.send(method, parts.join)
389
+ end
390
+
391
+ # always run out of the project dir
392
+ #
393
+ Dir.chdir(This.dir)
394
+ }
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'open4'
4
+ require 'pp'
5
+
6
+ # define a function we can call later. the function will take two
7
+ # arguments:
8
+ # command which we will run via open4 and be able to
9
+ # send stdin as well as collect the pid, stderr and stdout.
10
+ # input optional data string to send to command on stdin
11
+ #
12
+ # this returns a hash of the command's pid, stderr, stdout, and status.
13
+ def run_cmd(command, input = nil)
14
+
15
+ # we will use open4 in block form, which means that the variables
16
+ # used inside the block will not be available once open4 has
17
+ # finished. as long as variables are declared outside of the
18
+ # block, they can be set inside the block and are available after
19
+ # the block has finished.
20
+ err = out = procid = status = verbose = nil
21
+
22
+ # using a begin so we can use rescue later on.
23
+ begin
24
+
25
+ # run our command with open4 in block form.
26
+ stat = Open4::popen4(command) do |pid, stdin, stdout, stderr|
27
+
28
+ # the default behavior of ruby is to internally buffer I/O
29
+ # ports when they are opened. open4 may not detect that stderr
30
+ # and/or stdout has closed because ruby is helpfully buffering
31
+ # the pipe for us. if open4 hangs, try uncommenting the next
32
+ # two lines.
33
+ # stderr.sync = true
34
+ # stdout.sync = true
35
+
36
+ # set procid to pid so we can see it outside of the block.
37
+ procid = pid
38
+
39
+ # if you want to use stdin, talk to stdin here. i tried it
40
+ # with bc. generally i only need to capture output, not
41
+ # interact with commands i'm running.
42
+ stdin.puts input if input
43
+
44
+ # stdin is opened write only. you'll raise an exception if
45
+ # you try to read anything from it. here you can try to read
46
+ # the first character from stdin.
47
+ # stdin.gets(1)
48
+
49
+ # now close stdin.
50
+ stdin.close
51
+
52
+ # make stderr and stdout available outside the block as well.
53
+ # removing the read will return pointers to objects rather
54
+ # than the data that the objects contain.
55
+ out = stdout.read
56
+ err = stderr.read
57
+
58
+ # as stdin is write-only, stderr and stdout are read only.
59
+ # you'll raise an exception if you try to write to either.
60
+ # stderr.puts 'building appears to be on fire'
61
+
62
+ # end of open4 block. pid, stdin, stdout and stderr are no
63
+ # longer accessible.
64
+ end
65
+
66
+ # now outside of the open4 block, we can get the exit status
67
+ # of our command by calling stat.exitstatus.
68
+ status = stat.exitstatus
69
+
70
+ # our function returns status from a command. however, if you
71
+ # tell the function to run a command that does not exist, ruby
72
+ # will raise an exception. we will trap that exception here, make
73
+ # up a non-zero exit status, convert the ruby error to a string,
74
+ # and populate err with it.
75
+ rescue Errno::ENOENT => stderr
76
+ status = 1
77
+ err = stderr.to_s
78
+
79
+ # handle null commands gracefully
80
+ rescue TypeError => stderr
81
+ status = 2
82
+ err = 'Can\'t execute null command.'
83
+
84
+ # done calling and/or rescuing open4.
85
+ end
86
+
87
+ # uncomment to make function print output.
88
+ verbose = true
89
+
90
+ # print the values if verbose is not nil.
91
+ print "\n============================================================" if verbose
92
+ print "\ncommand: #{ command }" if verbose
93
+ print "\ninput : \n\n#{ input }\n" if (verbose and input)
94
+ print "\npid : #{ procid }" if verbose
95
+ print "\nstatus : #{ status }" if verbose
96
+ print "\nstdout : #{ out }\n" if verbose
97
+ print "\nstderr : #{ err }\n" if verbose
98
+ print "============================================================\n" if verbose
99
+
100
+ # now that (we think) we have handled everything, return a hash
101
+ # with the process id, standard error, standard output, and the
102
+ # exit status.
103
+ return {
104
+ :pid => procid, # integer
105
+ :stderr => err, # string
106
+ :stdout => out, # string
107
+ :status => status, # integer
108
+ }
109
+
110
+ # return terminates function. code here will not run!
111
+ print 'this will never show up.'
112
+
113
+ # end of run_cmd function.
114
+ end
115
+
116
+ # this will raise an exception which our function will trap,
117
+ # complaining that the command does not exist.
118
+ cmd = run_cmd('/bin/does/not/exist')
119
+
120
+ # something that will produce a fair amount of output. you do have
121
+ # an nmap source tree lying around, right?
122
+ cmd = run_cmd('cd nmap-5.51 ; ./configure')
123
+
124
+ # bc, to illustrate using stdin.
125
+ cmd = run_cmd('bc', "2^16\nquit")
126
+
127
+ # uncomment to see hash returned by run_cmd function.
128
+ # pp cmd
129
+
130
+ # test function with null command
131
+ cmd = run_cmd(nil)
data/samples/pfork4.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'open4'
2
+
3
+ echo = lambda do
4
+ $stdout.write $stdin.read
5
+ raise 'finish implementing me'
6
+ end
7
+
8
+ org_message = "hello, world!"
9
+ got_message = nil
10
+ exception = nil
11
+
12
+ begin
13
+ Open4.pfork4(echo) do |cid, stdin, stdout, stderr|
14
+ stdin.write org_message
15
+ stdin.close
16
+ got_message = stdout.read
17
+ end
18
+ rescue RuntimeError => e
19
+ exception = e.to_s
20
+ end
21
+
22
+ puts "org_message: #{org_message}"
23
+ puts "got_message: #{got_message}"
24
+ puts "exception : #{exception}"
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+
3
+ require 'minitest/autorun'
4
+ require 'open4'
5
+ require 'rbconfig'
6
+
7
+ module Open4
8
+ class TestCase < MiniTest::Unit::TestCase
9
+ include Open4
10
+
11
+ # Custom exception class for tests so we don't shadow possible
12
+ # programming errors.
13
+ class MyError < RuntimeError; end
14
+
15
+ def on_mri?
16
+ ::RbConfig::CONFIG['ruby_install_name'] == 'ruby'
17
+ end
18
+
19
+ def wait_status(cid)
20
+ Process.waitpid2(cid).last.exitstatus
21
+ end
22
+ end
23
+ end