systemu 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/README ADDED
@@ -0,0 +1,149 @@
1
+ NAME
2
+
3
+ systemu.rb
4
+
5
+ SYNOPSIS
6
+
7
+ univeral capture of stdout and stderr and handling of child process pid for windows, *nix, etc.
8
+
9
+ URIS
10
+
11
+ http://rubyforge.org/projects/codeforpeople/
12
+ http://codeforpeople.com/lib/ruby/
13
+
14
+ INSTALL
15
+
16
+ gem install systemu
17
+
18
+ SAMPLES
19
+
20
+ <========< samples/a.rb >========>
21
+
22
+ ~ > cat samples/a.rb
23
+
24
+ #
25
+ # systemu can be used on any platform to return status, stdout, and stderr of
26
+ # any command. unlike other methods like open3/popen4 there is zero danger of
27
+ # full pipes or threading issues hanging your process or subprocess.
28
+ #
29
+ require 'systemu'
30
+
31
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
32
+
33
+ status, stdout, stderr = systemu date
34
+ p [ status, stdout, stderr ]
35
+
36
+ ~ > ruby samples/a.rb
37
+
38
+ [#<Process::Status: pid=9960,exited(0)>, "Fri Nov 03 17:22:23 MST 2006\n", "Fri Nov 03 17:22:23 MST 2006\n"]
39
+
40
+
41
+ <========< samples/b.rb >========>
42
+
43
+ ~ > cat samples/b.rb
44
+
45
+ #
46
+ # quite a few keys can be passed to the command to alter it's behaviour. if
47
+ # either stdout or stderr is supplied those objects should respond_to? '<<'
48
+ # and only status will be returned
49
+ #
50
+ require 'systemu'
51
+
52
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
53
+
54
+ stdout, stderr = '', ''
55
+ status = systemu date, 'stdout' => stdout, 'stderr' => stderr
56
+ p [ status, stdout, stderr ]
57
+
58
+ ~ > ruby samples/b.rb
59
+
60
+ [#<Process::Status: pid=9965,exited(0)>, "Fri Nov 03 17:22:23 MST 2006\n", "Fri Nov 03 17:22:23 MST 2006\n"]
61
+
62
+
63
+ <========< samples/c.rb >========>
64
+
65
+ ~ > cat samples/c.rb
66
+
67
+ #
68
+ # of course stdin can be supplied too. synonyms for 'stdin' include '0' and
69
+ # 0. the other stdio streams have similar shortcuts
70
+ #
71
+ require 'systemu'
72
+
73
+ cat = %q( ruby -e" ARGF.each{|line| puts line} " )
74
+
75
+ status = systemu cat, 0=>'the stdin for cat', 1=>stdout=''
76
+ puts stdout
77
+
78
+ ~ > ruby samples/c.rb
79
+
80
+ the stdin for cat
81
+
82
+
83
+ <========< samples/d.rb >========>
84
+
85
+ ~ > cat samples/d.rb
86
+
87
+ #
88
+ # the cwd can be supplied
89
+ #
90
+ require 'systemu'
91
+ require 'tmpdir'
92
+
93
+ pwd = %q( ruby -e" STDERR.puts Dir.pwd " )
94
+
95
+ status = systemu pwd, 2=>(stderr=''), :cwd=>Dir.tmpdir
96
+ puts stderr
97
+
98
+
99
+ ~ > ruby samples/d.rb
100
+
101
+ /tmp
102
+
103
+
104
+ <========< samples/e.rb >========>
105
+
106
+ ~ > cat samples/e.rb
107
+
108
+ #
109
+ # any environment vars specified are merged into the child's environment
110
+ #
111
+ require 'systemu'
112
+
113
+ env = %q( ruby -r yaml -e" puts ENV[ 'answer' ] " )
114
+
115
+ status = systemu env, 1=>stdout='', 'env'=>{ 'answer' => 0b101010 }
116
+ puts stdout
117
+
118
+ ~ > ruby samples/e.rb
119
+
120
+ 42
121
+
122
+
123
+ <========< samples/f.rb >========>
124
+
125
+ ~ > cat samples/f.rb
126
+
127
+ #
128
+ # if a block is specified then it is passed the child pid and run in a
129
+ # background thread. note that this thread will __not__ be blocked during the
130
+ # execution of the command so it may do useful work such as killing the child
131
+ # if execution time passes a certain threshold
132
+ #
133
+ require 'systemu'
134
+
135
+ looper = %q( ruby -e" loop{ STDERR.puts Time.now.to_i; sleep 1 } " )
136
+
137
+ status, stdout, stderr =
138
+ systemu looper do |cid|
139
+ sleep 3
140
+ Process.kill 9, cid
141
+ end
142
+
143
+ p [ status, stdout, stderr ]
144
+
145
+
146
+ ~ > ruby samples/f.rb
147
+
148
+ [#<Process::Status: pid=9985,signaled(SIGKILL=9)>, "", "1162599744\n1162599745\n1162599746\n1162599747\n"]
149
+
@@ -0,0 +1,20 @@
1
+ NAME
2
+
3
+ systemu.rb
4
+
5
+ SYNOPSIS
6
+
7
+ univeral capture of stdout and stderr and handling of child process pid for windows, *nix, etc.
8
+
9
+ URIS
10
+
11
+ http://rubyforge.org/projects/codeforpeople/
12
+ http://codeforpeople.com/lib/ruby/
13
+
14
+ INSTALL
15
+
16
+ gem install systemu
17
+
18
+ SAMPLES
19
+
20
+ @samples
@@ -0,0 +1,23 @@
1
+ lib, version = File::basename(File::dirname(File::expand_path(__FILE__))).split %r/-/, 2
2
+
3
+ require 'rubygems'
4
+
5
+ Gem::Specification::new do |spec|
6
+ spec.name = lib
7
+ spec.version = version
8
+ spec.platform = Gem::Platform::RUBY
9
+ spec.summary = lib
10
+
11
+ spec.files = Dir::glob "**/**"
12
+ spec.executables = Dir::glob("bin/*").map{|exe| File::basename exe}
13
+
14
+ spec.require_path = "lib"
15
+ spec.autorequire = lib
16
+
17
+ spec.has_rdoc = File::exist? "doc"
18
+ spec.test_suite_file = "test/#{ lib }.rb" if File::directory? "test"
19
+
20
+ spec.author = "Ara T. Howard"
21
+ spec.email = "ara.t.howard@noaa.gov"
22
+ spec.homepage = "http://codeforpeople.com/lib/ruby/#{ lib }/"
23
+ end
@@ -0,0 +1,32 @@
1
+ require 'pathname'
2
+
3
+ $VERBOSE=nil
4
+
5
+ def indent s, n = 2
6
+ ws = ' ' * n
7
+ s.gsub %r/^/, ws
8
+ end
9
+
10
+ template = IO::read 'README.tmpl'
11
+
12
+ samples = ''
13
+ prompt = '~ > '
14
+
15
+ Dir['sample*/*'].sort.each do |sample|
16
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
17
+
18
+ cmd = "cat #{ sample }"
19
+ samples << indent(prompt + cmd, 2) << "\n\n"
20
+ samples << indent(`#{ cmd }`, 4) << "\n"
21
+
22
+ cmd = "ruby #{ sample }"
23
+ samples << indent(prompt + cmd, 2) << "\n\n"
24
+
25
+ cmd = "ruby -W0 -Ilib #{ sample }"
26
+ samples << indent(`#{ cmd } 2>&1`, 4) << "\n"
27
+ end
28
+
29
+ #samples.gsub! %r/^/, ' '
30
+
31
+ readme = template.gsub %r/^\s*@samples\s*$/, samples
32
+ print readme
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rbconfig'
3
+ require 'find'
4
+ require 'ftools'
5
+ require 'tempfile'
6
+ include Config
7
+
8
+ LIBDIR = "lib"
9
+ LIBDIR_MODE = 0644
10
+
11
+ BINDIR = "bin"
12
+ BINDIR_MODE = 0755
13
+
14
+
15
+ $srcdir = CONFIG["srcdir"]
16
+ $version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
17
+ $libdir = File.join(CONFIG["libdir"], "ruby", $version)
18
+ $archdir = File.join($libdir, CONFIG["arch"])
19
+ $site_libdir = $:.find {|x| x =~ /site_ruby$/}
20
+ $bindir = CONFIG["bindir"] || CONFIG['BINDIR']
21
+ $ruby_install_name = CONFIG['ruby_install_name'] || CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
22
+ $ruby_ext = CONFIG['EXEEXT'] || ''
23
+ $ruby = File.join($bindir, ($ruby_install_name + $ruby_ext))
24
+
25
+ if !$site_libdir
26
+ $site_libdir = File.join($libdir, "site_ruby")
27
+ elsif $site_libdir !~ %r/#{Regexp.quote($version)}/
28
+ $site_libdir = File.join($site_libdir, $version)
29
+ end
30
+
31
+ def install_rb(srcdir=nil, destdir=nil, mode=nil, bin=nil)
32
+ #{{{
33
+ path = []
34
+ dir = []
35
+ Find.find(srcdir) do |f|
36
+ next unless FileTest.file?(f)
37
+ next if (f = f[srcdir.length+1..-1]) == nil
38
+ next if (/CVS$/ =~ File.dirname(f))
39
+ next if f =~ %r/\.lnk/
40
+ path.push f
41
+ dir |= [File.dirname(f)]
42
+ end
43
+ for f in dir
44
+ next if f == "."
45
+ next if f == "CVS"
46
+ File::makedirs(File.join(destdir, f))
47
+ end
48
+ for f in path
49
+ next if (/\~$/ =~ f)
50
+ next if (/^\./ =~ File.basename(f))
51
+ unless bin
52
+ File::install(File.join(srcdir, f), File.join(destdir, f), mode, true)
53
+ else
54
+ from = File.join(srcdir, f)
55
+ to = File.join(destdir, f)
56
+ shebangify(from) do |sf|
57
+ $deferr.print from, " -> ", File::catname(from, to), "\n"
58
+ $deferr.printf "chmod %04o %s\n", mode, to
59
+ File::install(sf, to, mode, false)
60
+ end
61
+ end
62
+ end
63
+ #}}}
64
+ end
65
+ def shebangify f
66
+ #{{{
67
+ open(f) do |fd|
68
+ buf = fd.read 42
69
+ if buf =~ %r/^\s*#\s*!.*ruby/o
70
+ ftmp = Tempfile::new("#{ $$ }_#{ File::basename(f) }")
71
+ begin
72
+ fd.rewind
73
+ ftmp.puts "#!#{ $ruby }"
74
+ while((buf = fd.read(8192)))
75
+ ftmp.write buf
76
+ end
77
+ ftmp.close
78
+ yield ftmp.path
79
+ ensure
80
+ ftmp.close!
81
+ end
82
+ else
83
+ yield f
84
+ end
85
+ end
86
+ #}}}
87
+ end
88
+ def ARGV.switch
89
+ #{{{
90
+ return nil if self.empty?
91
+ arg = self.shift
92
+ return nil if arg == '--'
93
+ if arg =~ /^-(.)(.*)/
94
+ return arg if $1 == '-'
95
+ raise 'unknown switch "-"' if $2.index('-')
96
+ self.unshift "-#{$2}" if $2.size > 0
97
+ "-#{$1}"
98
+ else
99
+ self.unshift arg
100
+ nil
101
+ end
102
+ #}}}
103
+ end
104
+ def ARGV.req_arg
105
+ #{{{
106
+ self.shift || raise('missing argument')
107
+ #}}}
108
+ end
109
+ def linkify d, linked = []
110
+ #--{{{
111
+ if test ?d, d
112
+ versioned = Dir[ File::join(d, "*-[0-9].[0-9].[0-9].rb") ]
113
+ versioned.each do |v|
114
+ src, dst = v, v.gsub(%r/\-[\d\.]+\.rb$/, '.rb')
115
+ lnk = nil
116
+ begin
117
+ if test ?l, dst
118
+ lnk = "#{ dst }.lnk"
119
+ puts "#{ dst } -> #{ lnk }"
120
+ File::rename dst, lnk
121
+ end
122
+ unless test ?e, dst
123
+ puts "#{ src } -> #{ dst }"
124
+ File::copy src, dst
125
+ linked << dst
126
+ end
127
+ ensure
128
+ if lnk
129
+ at_exit do
130
+ puts "#{ lnk } -> #{ dst }"
131
+ File::rename lnk, dst
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ linked
138
+ #--}}}
139
+ end
140
+
141
+
142
+ #
143
+ # main program
144
+ #
145
+
146
+ libdir = $site_libdir
147
+ bindir = $bindir
148
+ no_linkify = false
149
+ linked = nil
150
+ help = false
151
+
152
+ usage = <<-usage
153
+ #{ File::basename $0 }
154
+ -d, --destdir <destdir>
155
+ -l, --libdir <libdir>
156
+ -b, --bindir <bindir>
157
+ -r, --ruby <ruby>
158
+ -n, --no_linkify
159
+ -s, --sudo
160
+ -h, --help
161
+ usage
162
+
163
+ begin
164
+ while switch = ARGV.switch
165
+ case switch
166
+ when '-d', '--destdir'
167
+ libdir = ARGV.req_arg
168
+ when '-l', '--libdir'
169
+ libdir = ARGV.req_arg
170
+ when '-b', '--bindir'
171
+ bindir = ARGV.req_arg
172
+ when '-r', '--ruby'
173
+ $ruby = ARGV.req_arg
174
+ when '-n', '--no_linkify'
175
+ no_linkify = true
176
+ when '-s', '--sudo'
177
+ sudo = 'sudo'
178
+ when '-h', '--help'
179
+ help = true
180
+ else
181
+ raise "unknown switch #{switch.dump}"
182
+ end
183
+ end
184
+ rescue
185
+ STDERR.puts $!.to_s
186
+ STDERR.puts usage
187
+ exit 1
188
+ end
189
+
190
+ if help
191
+ STDOUT.puts usage
192
+ exit
193
+ end
194
+
195
+ unless no_linkify
196
+ linked = linkify('lib') + linkify('bin')
197
+ end
198
+
199
+ system "#{ $ruby } extconf.rb && make && #{ sudo } make install" if test(?s, 'extconf.rb')
200
+
201
+ install_rb(LIBDIR, libdir, LIBDIR_MODE)
202
+ install_rb(BINDIR, bindir, BINDIR_MODE, bin=true)
203
+
204
+ if linked
205
+ linked.each{|path| File::rm_f path}
206
+ end
@@ -0,0 +1,262 @@
1
+ require 'tmpdir'
2
+ require 'socket'
3
+ require 'fileutils'
4
+ require 'rbconfig'
5
+ require 'thread'
6
+
7
+ class Object
8
+ def systemu(*a, &b) SystemUniversal.new(*a, &b).systemu end
9
+ end
10
+
11
+ class SystemUniversal
12
+ #--{{{
13
+ VERSION = '1.0.0'
14
+ def version() VERSION end
15
+ #
16
+ # class methods
17
+ #
18
+
19
+ @host = Socket.gethostname
20
+ @ppid = Process.ppid
21
+ @pid = Process.pid
22
+
23
+ c = ::Config::CONFIG
24
+ ruby = File.join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
25
+ @ruby = if system('%s -e 42' % ruby)
26
+ ruby
27
+ else
28
+ system('%s -e 42' % 'ruby') ? 'ruby' : warn('no ruby in PATH/CONFIG')
29
+ end
30
+
31
+ class << self
32
+ %w( host ppid pid ruby ).each{|a| attr_accessor a}
33
+ end
34
+
35
+ #
36
+ # instance methods
37
+ #
38
+
39
+ def initialize argv, opts = {}, &block
40
+ #--{{{
41
+ getopt = getopts opts
42
+
43
+ @argv = argv
44
+ @block = block
45
+
46
+ @stdin = getopt[ ['stdin', 'in', '0', 0] ]
47
+ @stdout = getopt[ ['stdout', 'out', '1', 1] ]
48
+ @stderr = getopt[ ['stderr', 'err', '2', 2] ]
49
+ @env = getopt[ 'env' ]
50
+ @cwd = getopt[ 'cwd' ]
51
+
52
+ @host = getopt[ 'host', self.class.host ]
53
+ @ppid = getopt[ 'ppid', self.class.ppid ]
54
+ @pid = getopt[ 'pid', self.class.pid ]
55
+ @ruby = getopt[ 'ruby', self.class.ruby ]
56
+ #--}}}
57
+ end
58
+
59
+ def systemu
60
+ #--{{{
61
+ tmpdir do |tmp|
62
+ c = child_setup tmp
63
+ status = nil
64
+
65
+ if @block
66
+ q = Queue.new
67
+ t = Thread.new{ @block[ q.pop ] }
68
+ end
69
+
70
+ begin
71
+ quietly{
72
+ IO.popen "#{ @ruby } #{ c['program'] }", 'r+' do |pipe|
73
+ cid = pipe.gets.to_i
74
+ q.push cid if @block
75
+ pipe.read rescue nil
76
+ end
77
+ }
78
+ status = $?
79
+ ensure
80
+ t.kill rescue nil if t
81
+ end
82
+
83
+ if @stdout or @stderr
84
+ open(c['stdout']){|f| relay f => @stdout} if @stdout
85
+ open(c['stderr']){|f| relay f => @stderr} if @stderr
86
+ status
87
+ else
88
+ [status, IO.read(c['stdout']), IO.read(c['stderr'])]
89
+ end
90
+ end
91
+ #--}}}
92
+ end
93
+
94
+ def child_setup tmp
95
+ #--{{{
96
+ stdin = File.expand_path(File.join(tmp, 'stdin'))
97
+ stdout = File.expand_path(File.join(tmp, 'stdout'))
98
+ stderr = File.expand_path(File.join(tmp, 'stderr'))
99
+ program = File.expand_path(File.join(tmp, 'program'))
100
+ config = File.expand_path(File.join(tmp, 'config'))
101
+
102
+ if @stdin
103
+ open(stdin, 'w'){|f| relay @stdin => f}
104
+ else
105
+ FileUtils.touch stdin
106
+ end
107
+ FileUtils.touch stdout
108
+ FileUtils.touch stderr
109
+
110
+ c = {}
111
+ c['argv'] = @argv
112
+ c['env'] = @env
113
+ c['cwd'] = @cwd
114
+ c['stdin'] = stdin
115
+ c['stdout'] = stdout
116
+ c['stderr'] = stderr
117
+ c['program'] = program
118
+ open(config, 'w'){|f| Marshal.dump c, f}
119
+
120
+ open(program, 'w'){|f| f.write child_program(config)}
121
+
122
+ c
123
+ #--}}}
124
+ end
125
+
126
+ def quietly
127
+ #--{{{
128
+ v = $VERBOSE
129
+ $VERBOSE = nil
130
+ yield
131
+ ensure
132
+ $VERBOSE = v
133
+ #--}}}
134
+ end
135
+
136
+ def child_program config
137
+ #--{{{
138
+ <<-program
139
+ begin
140
+ config = Marshal.load(IO.read('#{ config }'))
141
+
142
+ argv = config['argv']
143
+ env = config['env']
144
+ cwd = config['cwd']
145
+ stdin = config['stdin']
146
+ stdout = config['stdout']
147
+ stderr = config['stderr']
148
+
149
+ Dir.chdir cwd if cwd
150
+ env.each{|k,v| ENV[k.to_s] = v.to_s} if env
151
+ rescue Exception => e
152
+ STDERR.warn e
153
+ exit 42
154
+ end
155
+
156
+ STDOUT.puts Process.pid
157
+ STDOUT.flush
158
+
159
+ STDIN.reopen stdin
160
+ STDOUT.reopen stdout
161
+ STDERR.reopen stderr
162
+
163
+ exec *argv
164
+ program
165
+ #--}}}
166
+ end
167
+
168
+ def relay srcdst
169
+ #--{{{
170
+ src, dst, ignored = srcdst.to_a.first
171
+ if src.respond_to? 'read'
172
+ while((buf = src.read(8192))); dst << buf; end
173
+ else
174
+ src.each{|buf| dst << buf}
175
+ end
176
+ #--}}}
177
+ end
178
+
179
+ def tmpdir d = Dir.tmpdir, &b
180
+ #--{{{
181
+ i = -1 and loop{
182
+ tmp = File.join d, "systemu_#{ @host }_#{ @ppid }_#{ @pid }_#{ rand }_#{ i += 1 }"
183
+
184
+ begin
185
+ Dir.mkdir tmp
186
+ rescue Errno::EEXIST
187
+ next
188
+ end
189
+
190
+ scrub = lambda do |t|
191
+ FileUtils.rm_rf t
192
+ scrub = lambda{ 42 }
193
+ end
194
+ at_exit{ scrub[tmp] }
195
+
196
+ break(
197
+ if b
198
+ begin; b[ tmp ]; ensure; scrub[ tmp ]; end
199
+ else
200
+ tmp
201
+ end
202
+ )
203
+ }
204
+ #--}}}
205
+ end
206
+
207
+ def getopts opts = {}
208
+ #--{{{
209
+ lambda do |*args|
210
+ keys, default, ignored = args
211
+ catch('opt') do
212
+ [keys].flatten.each do |key|
213
+ [key, key.to_s, key.to_s.intern].each do |key|
214
+ throw 'opt', opts[key] if opts.has_key?(key)
215
+ end
216
+ end
217
+ default
218
+ end
219
+ end
220
+ #--}}}
221
+ end
222
+ #--}}}
223
+ end
224
+
225
+
226
+ if $0 == __FILE__
227
+ #
228
+ # date
229
+ #
230
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
231
+
232
+ status, stdout, stderr = systemu date
233
+ p [status, stdout, stderr]
234
+
235
+ status = systemu date, 1=>(stdout = '')
236
+ p [status, stdout]
237
+
238
+ status = systemu date, 2=>(stderr = '')
239
+ p [status, stderr]
240
+ #
241
+ # sleep
242
+ #
243
+ sleep = %q( ruby -e" p(sleep(1)) " )
244
+ status, stdout, stderr = systemu sleep
245
+ p [status, stdout, stderr]
246
+
247
+ sleep = %q( ruby -e" p(sleep(42)) " )
248
+ status, stdout, stderr = systemu(sleep){|cid| Process.kill 9, cid}
249
+ p [status, stdout, stderr]
250
+ #
251
+ # env
252
+ #
253
+ env = %q( ruby -e" p ENV['A'] " )
254
+ status, stdout, stderr = systemu env, :env => {'A' => 42}
255
+ p [status, stdout, stderr]
256
+ #
257
+ # cwd
258
+ #
259
+ env = %q( ruby -e" p Dir.pwd " )
260
+ status, stdout, stderr = systemu env, :cwd => Dir.tmpdir
261
+ p [status, stdout, stderr]
262
+ end
@@ -0,0 +1,262 @@
1
+ require 'tmpdir'
2
+ require 'socket'
3
+ require 'fileutils'
4
+ require 'rbconfig'
5
+ require 'thread'
6
+
7
+ class Object
8
+ def systemu(*a, &b) SystemUniversal.new(*a, &b).systemu end
9
+ end
10
+
11
+ class SystemUniversal
12
+ #--{{{
13
+ VERSION = '1.0.0'
14
+ def version() VERSION end
15
+ #
16
+ # class methods
17
+ #
18
+
19
+ @host = Socket.gethostname
20
+ @ppid = Process.ppid
21
+ @pid = Process.pid
22
+
23
+ c = ::Config::CONFIG
24
+ ruby = File.join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
25
+ @ruby = if system('%s -e 42' % ruby)
26
+ ruby
27
+ else
28
+ system('%s -e 42' % 'ruby') ? 'ruby' : warn('no ruby in PATH/CONFIG')
29
+ end
30
+
31
+ class << self
32
+ %w( host ppid pid ruby ).each{|a| attr_accessor a}
33
+ end
34
+
35
+ #
36
+ # instance methods
37
+ #
38
+
39
+ def initialize argv, opts = {}, &block
40
+ #--{{{
41
+ getopt = getopts opts
42
+
43
+ @argv = argv
44
+ @block = block
45
+
46
+ @stdin = getopt[ ['stdin', 'in', '0', 0] ]
47
+ @stdout = getopt[ ['stdout', 'out', '1', 1] ]
48
+ @stderr = getopt[ ['stderr', 'err', '2', 2] ]
49
+ @env = getopt[ 'env' ]
50
+ @cwd = getopt[ 'cwd' ]
51
+
52
+ @host = getopt[ 'host', self.class.host ]
53
+ @ppid = getopt[ 'ppid', self.class.ppid ]
54
+ @pid = getopt[ 'pid', self.class.pid ]
55
+ @ruby = getopt[ 'ruby', self.class.ruby ]
56
+ #--}}}
57
+ end
58
+
59
+ def systemu
60
+ #--{{{
61
+ tmpdir do |tmp|
62
+ c = child_setup tmp
63
+ status = nil
64
+
65
+ if @block
66
+ q = Queue.new
67
+ t = Thread.new{ @block[ q.pop ] }
68
+ end
69
+
70
+ begin
71
+ quietly{
72
+ IO.popen "#{ @ruby } #{ c['program'] }", 'r+' do |pipe|
73
+ cid = pipe.gets.to_i
74
+ q.push cid if @block
75
+ pipe.read rescue nil
76
+ end
77
+ }
78
+ status = $?
79
+ ensure
80
+ t.kill rescue nil if t
81
+ end
82
+
83
+ if @stdout or @stderr
84
+ open(c['stdout']){|f| relay f => @stdout} if @stdout
85
+ open(c['stderr']){|f| relay f => @stderr} if @stderr
86
+ status
87
+ else
88
+ [status, IO.read(c['stdout']), IO.read(c['stderr'])]
89
+ end
90
+ end
91
+ #--}}}
92
+ end
93
+
94
+ def child_setup tmp
95
+ #--{{{
96
+ stdin = File.expand_path(File.join(tmp, 'stdin'))
97
+ stdout = File.expand_path(File.join(tmp, 'stdout'))
98
+ stderr = File.expand_path(File.join(tmp, 'stderr'))
99
+ program = File.expand_path(File.join(tmp, 'program'))
100
+ config = File.expand_path(File.join(tmp, 'config'))
101
+
102
+ if @stdin
103
+ open(stdin, 'w'){|f| relay @stdin => f}
104
+ else
105
+ FileUtils.touch stdin
106
+ end
107
+ FileUtils.touch stdout
108
+ FileUtils.touch stderr
109
+
110
+ c = {}
111
+ c['argv'] = @argv
112
+ c['env'] = @env
113
+ c['cwd'] = @cwd
114
+ c['stdin'] = stdin
115
+ c['stdout'] = stdout
116
+ c['stderr'] = stderr
117
+ c['program'] = program
118
+ open(config, 'w'){|f| Marshal.dump c, f}
119
+
120
+ open(program, 'w'){|f| f.write child_program(config)}
121
+
122
+ c
123
+ #--}}}
124
+ end
125
+
126
+ def quietly
127
+ #--{{{
128
+ v = $VERBOSE
129
+ $VERBOSE = nil
130
+ yield
131
+ ensure
132
+ $VERBOSE = v
133
+ #--}}}
134
+ end
135
+
136
+ def child_program config
137
+ #--{{{
138
+ <<-program
139
+ begin
140
+ config = Marshal.load(IO.read('#{ config }'))
141
+
142
+ argv = config['argv']
143
+ env = config['env']
144
+ cwd = config['cwd']
145
+ stdin = config['stdin']
146
+ stdout = config['stdout']
147
+ stderr = config['stderr']
148
+
149
+ Dir.chdir cwd if cwd
150
+ env.each{|k,v| ENV[k.to_s] = v.to_s} if env
151
+ rescue Exception => e
152
+ STDERR.warn e
153
+ exit 42
154
+ end
155
+
156
+ STDOUT.puts Process.pid
157
+ STDOUT.flush
158
+
159
+ STDIN.reopen stdin
160
+ STDOUT.reopen stdout
161
+ STDERR.reopen stderr
162
+
163
+ exec *argv
164
+ program
165
+ #--}}}
166
+ end
167
+
168
+ def relay srcdst
169
+ #--{{{
170
+ src, dst, ignored = srcdst.to_a.first
171
+ if src.respond_to? 'read'
172
+ while((buf = src.read(8192))); dst << buf; end
173
+ else
174
+ src.each{|buf| dst << buf}
175
+ end
176
+ #--}}}
177
+ end
178
+
179
+ def tmpdir d = Dir.tmpdir, &b
180
+ #--{{{
181
+ i = -1 and loop{
182
+ tmp = File.join d, "systemu_#{ @host }_#{ @ppid }_#{ @pid }_#{ rand }_#{ i += 1 }"
183
+
184
+ begin
185
+ Dir.mkdir tmp
186
+ rescue Errno::EEXIST
187
+ next
188
+ end
189
+
190
+ scrub = lambda do |t|
191
+ FileUtils.rm_rf t
192
+ scrub = lambda{ 42 }
193
+ end
194
+ at_exit{ scrub[tmp] }
195
+
196
+ break(
197
+ if b
198
+ begin; b[ tmp ]; ensure; scrub[ tmp ]; end
199
+ else
200
+ tmp
201
+ end
202
+ )
203
+ }
204
+ #--}}}
205
+ end
206
+
207
+ def getopts opts = {}
208
+ #--{{{
209
+ lambda do |*args|
210
+ keys, default, ignored = args
211
+ catch('opt') do
212
+ [keys].flatten.each do |key|
213
+ [key, key.to_s, key.to_s.intern].each do |key|
214
+ throw 'opt', opts[key] if opts.has_key?(key)
215
+ end
216
+ end
217
+ default
218
+ end
219
+ end
220
+ #--}}}
221
+ end
222
+ #--}}}
223
+ end
224
+
225
+
226
+ if $0 == __FILE__
227
+ #
228
+ # date
229
+ #
230
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
231
+
232
+ status, stdout, stderr = systemu date
233
+ p [status, stdout, stderr]
234
+
235
+ status = systemu date, 1=>(stdout = '')
236
+ p [status, stdout]
237
+
238
+ status = systemu date, 2=>(stderr = '')
239
+ p [status, stderr]
240
+ #
241
+ # sleep
242
+ #
243
+ sleep = %q( ruby -e" p(sleep(1)) " )
244
+ status, stdout, stderr = systemu sleep
245
+ p [status, stdout, stderr]
246
+
247
+ sleep = %q( ruby -e" p(sleep(42)) " )
248
+ status, stdout, stderr = systemu(sleep){|cid| Process.kill 9, cid}
249
+ p [status, stdout, stderr]
250
+ #
251
+ # env
252
+ #
253
+ env = %q( ruby -e" p ENV['A'] " )
254
+ status, stdout, stderr = systemu env, :env => {'A' => 42}
255
+ p [status, stdout, stderr]
256
+ #
257
+ # cwd
258
+ #
259
+ env = %q( ruby -e" p Dir.pwd " )
260
+ status, stdout, stderr = systemu env, :cwd => Dir.tmpdir
261
+ p [status, stdout, stderr]
262
+ end
@@ -0,0 +1,11 @@
1
+ #
2
+ # systemu can be used on any platform to return status, stdout, and stderr of
3
+ # any command. unlike other methods like open3/popen4 there is zero danger of
4
+ # full pipes or threading issues hanging your process or subprocess.
5
+ #
6
+ require 'systemu'
7
+
8
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
9
+
10
+ status, stdout, stderr = systemu date
11
+ p [ status, stdout, stderr ]
@@ -0,0 +1,12 @@
1
+ #
2
+ # quite a few keys can be passed to the command to alter it's behaviour. if
3
+ # either stdout or stderr is supplied those objects should respond_to? '<<'
4
+ # and only status will be returned
5
+ #
6
+ require 'systemu'
7
+
8
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
9
+
10
+ stdout, stderr = '', ''
11
+ status = systemu date, 'stdout' => stdout, 'stderr' => stderr
12
+ p [ status, stdout, stderr ]
@@ -0,0 +1,10 @@
1
+ #
2
+ # of course stdin can be supplied too. synonyms for 'stdin' include '0' and
3
+ # 0. the other stdio streams have similar shortcuts
4
+ #
5
+ require 'systemu'
6
+
7
+ cat = %q( ruby -e" ARGF.each{|line| puts line} " )
8
+
9
+ status = systemu cat, 0=>'the stdin for cat', 1=>stdout=''
10
+ puts stdout
@@ -0,0 +1,11 @@
1
+ #
2
+ # the cwd can be supplied
3
+ #
4
+ require 'systemu'
5
+ require 'tmpdir'
6
+
7
+ pwd = %q( ruby -e" STDERR.puts Dir.pwd " )
8
+
9
+ status = systemu pwd, 2=>(stderr=''), :cwd=>Dir.tmpdir
10
+ puts stderr
11
+
@@ -0,0 +1,9 @@
1
+ #
2
+ # any environment vars specified are merged into the child's environment
3
+ #
4
+ require 'systemu'
5
+
6
+ env = %q( ruby -r yaml -e" puts ENV[ 'answer' ] " )
7
+
8
+ status = systemu env, 1=>stdout='', 'env'=>{ 'answer' => 0b101010 }
9
+ puts stdout
@@ -0,0 +1,18 @@
1
+ #
2
+ # if a block is specified then it is passed the child pid and run in a
3
+ # background thread. note that this thread will __not__ be blocked during the
4
+ # execution of the command so it may do useful work such as killing the child
5
+ # if execution time passes a certain threshold
6
+ #
7
+ require 'systemu'
8
+
9
+ looper = %q( ruby -e" loop{ STDERR.puts Time.now.to_i; sleep 1 } " )
10
+
11
+ status, stdout, stderr =
12
+ systemu looper do |cid|
13
+ sleep 3
14
+ Process.kill 9, cid
15
+ end
16
+
17
+ p [ status, stdout, stderr ]
18
+
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: systemu
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2006-11-03 00:00:00.000000 -07:00
8
+ summary: systemu
9
+ require_paths:
10
+ - lib
11
+ email: ara.t.howard@noaa.gov
12
+ homepage: http://codeforpeople.com/lib/ruby/systemu/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: systemu
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Ara T. Howard
31
+ files:
32
+ - lib
33
+ - samples
34
+ - gen_readme.rb
35
+ - gemspec.rb
36
+ - install.rb
37
+ - README
38
+ - README.tmpl
39
+ - lib/systemu.rb
40
+ - lib/systemu-1.0.0.rb
41
+ - samples/a.rb
42
+ - samples/b.rb
43
+ - samples/c.rb
44
+ - samples/d.rb
45
+ - samples/e.rb
46
+ - samples/f.rb
47
+ test_files: []
48
+ rdoc_options: []
49
+ extra_rdoc_files: []
50
+ executables: []
51
+ extensions: []
52
+ requirements: []
53
+ dependencies: []