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 +149 -0
- data/README.tmpl +20 -0
- data/gemspec.rb +23 -0
- data/gen_readme.rb +32 -0
- data/install.rb +206 -0
- data/lib/systemu-1.0.0.rb +262 -0
- data/lib/systemu.rb +262 -0
- data/samples/a.rb +11 -0
- data/samples/b.rb +12 -0
- data/samples/c.rb +10 -0
- data/samples/d.rb +11 -0
- data/samples/e.rb +9 -0
- data/samples/f.rb +18 -0
- metadata +53 -0
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
|
+
|
data/README.tmpl
ADDED
@@ -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
|
data/gemspec.rb
ADDED
@@ -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
|
data/gen_readme.rb
ADDED
@@ -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
|
data/install.rb
ADDED
@@ -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
|
data/lib/systemu.rb
ADDED
@@ -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
|
data/samples/a.rb
ADDED
@@ -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 ]
|
data/samples/b.rb
ADDED
@@ -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 ]
|
data/samples/c.rb
ADDED
@@ -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
|
data/samples/d.rb
ADDED
data/samples/e.rb
ADDED
data/samples/f.rb
ADDED
@@ -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: []
|