averell23-systemu 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README +160 -0
  2. data/README.tmpl +30 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/systemu.rb +323 -0
  5. metadata +59 -0
data/README ADDED
@@ -0,0 +1,160 @@
1
+ NAME
2
+
3
+ systemu
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
+ http://codeforpeople.rubyforge.org/svn/
14
+
15
+ INSTALL
16
+
17
+ gem install systemu
18
+
19
+ HISTORY
20
+
21
+ 1.2.0
22
+
23
+ - fixed handling of background thread management - needed
24
+ Thread.current.abort_on_exception = true
25
+
26
+ - fixed reporting of child pid, it was reported as the parent's pid before
27
+
28
+ SAMPLES
29
+
30
+ <========< samples/a.rb >========>
31
+
32
+ ~ > cat samples/a.rb
33
+
34
+ #
35
+ # systemu can be used on any platform to return status, stdout, and stderr of
36
+ # any command. unlike other methods like open3/popen4 there is zero danger of
37
+ # full pipes or threading issues hanging your process or subprocess.
38
+ #
39
+ require 'systemu'
40
+
41
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
42
+
43
+ status, stdout, stderr = systemu date
44
+ p [ status, stdout, stderr ]
45
+
46
+ ~ > ruby samples/a.rb
47
+
48
+ [#<Process::Status: pid=987,exited(0)>, "Thu Dec 06 16:01:59 -0700 2007\n", "Thu Dec 06 16:01:59 -0700 2007\n"]
49
+
50
+
51
+ <========< samples/b.rb >========>
52
+
53
+ ~ > cat samples/b.rb
54
+
55
+ #
56
+ # quite a few keys can be passed to the command to alter it's behaviour. if
57
+ # either stdout or stderr is supplied those objects should respond_to? '<<'
58
+ # and only status will be returned
59
+ #
60
+ require 'systemu'
61
+
62
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
63
+
64
+ stdout, stderr = '', ''
65
+ status = systemu date, 'stdout' => stdout, 'stderr' => stderr
66
+ p [ status, stdout, stderr ]
67
+
68
+ ~ > ruby samples/b.rb
69
+
70
+ [#<Process::Status: pid=992,exited(0)>, "Thu Dec 06 16:01:59 -0700 2007\n", "Thu Dec 06 16:01:59 -0700 2007\n"]
71
+
72
+
73
+ <========< samples/c.rb >========>
74
+
75
+ ~ > cat samples/c.rb
76
+
77
+ #
78
+ # of course stdin can be supplied too. synonyms for 'stdin' include '0' and
79
+ # 0. the other stdio streams have similar shortcuts
80
+ #
81
+ require 'systemu'
82
+
83
+ cat = %q( ruby -e" ARGF.each{|line| puts line} " )
84
+
85
+ status = systemu cat, 0=>'the stdin for cat', 1=>stdout=''
86
+ puts stdout
87
+
88
+ ~ > ruby samples/c.rb
89
+
90
+ the stdin for cat
91
+
92
+
93
+ <========< samples/d.rb >========>
94
+
95
+ ~ > cat samples/d.rb
96
+
97
+ #
98
+ # the cwd can be supplied
99
+ #
100
+ require 'systemu'
101
+ require 'tmpdir'
102
+
103
+ pwd = %q( ruby -e" STDERR.puts Dir.pwd " )
104
+
105
+ status = systemu pwd, 2=>(stderr=''), :cwd=>Dir.tmpdir
106
+ puts stderr
107
+
108
+
109
+ ~ > ruby samples/d.rb
110
+
111
+ /private/tmp
112
+
113
+
114
+ <========< samples/e.rb >========>
115
+
116
+ ~ > cat samples/e.rb
117
+
118
+ #
119
+ # any environment vars specified are merged into the child's environment
120
+ #
121
+ require 'systemu'
122
+
123
+ env = %q( ruby -r yaml -e" puts ENV[ 'answer' ] " )
124
+
125
+ status = systemu env, 1=>stdout='', 'env'=>{ 'answer' => 0b101010 }
126
+ puts stdout
127
+
128
+ ~ > ruby samples/e.rb
129
+
130
+ 42
131
+
132
+
133
+ <========< samples/f.rb >========>
134
+
135
+ ~ > cat samples/f.rb
136
+
137
+ #
138
+ # if a block is specified then it is passed the child pid and run in a
139
+ # background thread. note that this thread will __not__ be blocked during the
140
+ # execution of the command so it may do useful work such as killing the child
141
+ # if execution time passes a certain threshold
142
+ #
143
+ require 'systemu'
144
+
145
+ looper = %q( ruby -e" loop{ STDERR.puts Time.now.to_i; sleep 1 } " )
146
+
147
+ status, stdout, stderr =
148
+ systemu looper do |cid|
149
+ sleep 3
150
+ Process.kill 9, cid
151
+ end
152
+
153
+ p status
154
+ p stderr
155
+
156
+ ~ > ruby samples/f.rb
157
+
158
+ #<Process::Status: pid=1012,signaled(SIGKILL=9)>
159
+ "1196982119\n1196982120\n1196982121\n"
160
+
@@ -0,0 +1,30 @@
1
+ NAME
2
+
3
+ systemu
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
+ http://codeforpeople.rubyforge.org/svn/
14
+
15
+ INSTALL
16
+
17
+ gem install systemu
18
+
19
+ HISTORY
20
+
21
+ 1.2.0
22
+
23
+ - fixed handling of background thread management - needed
24
+ Thread.current.abort_on_exception = true
25
+
26
+ - fixed reporting of child pid, it was reported as the parent's pid before
27
+
28
+ SAMPLES
29
+
30
+ @samples
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 2
4
+ :patch: 0
@@ -0,0 +1,323 @@
1
+ # vim: ts=2:sw=2:sts=2:et:fdm=marker
2
+ require 'tmpdir'
3
+ require 'socket'
4
+ require 'fileutils'
5
+ require 'rbconfig'
6
+ require 'thread'
7
+ require 'yaml'
8
+
9
+ class Object
10
+ def systemu(*a, &b) SystemUniversal.new(*a, &b).systemu end
11
+ end
12
+
13
+ class SystemUniversal
14
+ #
15
+ # constants
16
+ #
17
+ SystemUniversal::VERSION = '1.2.0' unless defined? SystemUniversal::VERSION
18
+ def version() SystemUniversal::VERSION end
19
+ #
20
+ # class methods
21
+ #
22
+
23
+ @host = Socket.gethostname
24
+ @ppid = Process.ppid
25
+ @pid = Process.pid
26
+ @turd = ENV['SYSTEMU_TURD']
27
+
28
+ c = ::Config::CONFIG
29
+ ruby = File.join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
30
+ @ruby = if system('%s -e 42' % ruby)
31
+ ruby
32
+ else
33
+ system('%s -e 42' % 'ruby') ? 'ruby' : warn('no ruby in PATH/CONFIG')
34
+ end
35
+
36
+ class << self
37
+ %w( host ppid pid ruby turd ).each{|a| attr_accessor a}
38
+ end
39
+
40
+ #
41
+ # instance methods
42
+ #
43
+
44
+ def initialize argv, opts = {}, &block
45
+ getopt = getopts opts
46
+
47
+ @argv = argv
48
+ @block = block
49
+
50
+ @stdin = getopt[ ['stdin', 'in', '0', 0] ]
51
+ @stdout = getopt[ ['stdout', 'out', '1', 1] ]
52
+ @stderr = getopt[ ['stderr', 'err', '2', 2] ]
53
+ @env = getopt[ 'env' ]
54
+ @cwd = getopt[ 'cwd' ]
55
+
56
+ @host = getopt[ 'host', self.class.host ]
57
+ @ppid = getopt[ 'ppid', self.class.ppid ]
58
+ @pid = getopt[ 'pid', self.class.pid ]
59
+ @ruby = getopt[ 'ruby', self.class.ruby ]
60
+ end
61
+
62
+ def systemu
63
+ tmpdir do |tmp|
64
+ c = child_setup tmp
65
+ status = nil
66
+
67
+ begin
68
+ thread = nil
69
+
70
+ quietly{
71
+ IO.popen "#{ @ruby } #{ c['program'] }", 'r+' do |pipe|
72
+ line = pipe.gets
73
+ case line
74
+ when %r/^pid: \d+$/
75
+ cid = Integer line[%r/\d+/]
76
+ else
77
+ begin
78
+ buf = pipe.read
79
+ buf = "#{ line }#{ buf }"
80
+ e = Marshal.load buf
81
+ raise unless Exception === e
82
+ raise e
83
+ rescue
84
+ raise "wtf?\n#{ buf }\n"
85
+ end
86
+ end
87
+ thread = new_thread cid, @block if @block
88
+ pipe.read rescue nil
89
+ end
90
+ }
91
+ status = $?
92
+ ensure
93
+ if thread
94
+ begin
95
+ class << status
96
+ attr 'thread'
97
+ end
98
+ status.instance_eval{ @thread = thread }
99
+ rescue
100
+ 42
101
+ end
102
+ end
103
+ end
104
+
105
+ if @stdout or @stderr
106
+ open(c['stdout']){|f| relay f => @stdout} if @stdout
107
+ open(c['stderr']){|f| relay f => @stderr} if @stderr
108
+ status
109
+ else
110
+ [status, IO.read(c['stdout']), IO.read(c['stderr'])]
111
+ end
112
+ end
113
+ end
114
+
115
+ def new_thread cid, block
116
+ q = Queue.new
117
+ Thread.new(cid) do |cid|
118
+ current = Thread.current
119
+ current.abort_on_exception = true
120
+ q.push current
121
+ block.call cid
122
+ end
123
+ q.pop
124
+ end
125
+
126
+ def child_setup tmp
127
+ stdin = File.expand_path(File.join(tmp, 'stdin'))
128
+ stdout = File.expand_path(File.join(tmp, 'stdout'))
129
+ stderr = File.expand_path(File.join(tmp, 'stderr'))
130
+ program = File.expand_path(File.join(tmp, 'program'))
131
+ config = File.expand_path(File.join(tmp, 'config'))
132
+
133
+ if @stdin
134
+ open(stdin, 'w'){|f| relay @stdin => f}
135
+ else
136
+ FileUtils.touch stdin
137
+ end
138
+ FileUtils.touch stdout
139
+ FileUtils.touch stderr
140
+
141
+ c = {}
142
+ c['argv'] = @argv
143
+ c['env'] = @env
144
+ c['cwd'] = @cwd
145
+ c['stdin'] = stdin
146
+ c['stdout'] = stdout
147
+ c['stderr'] = stderr
148
+ c['program'] = program
149
+ open(config, 'w'){|f| YAML.dump c, f}
150
+
151
+ open(program, 'w'){|f| f.write child_program(config)}
152
+
153
+ c
154
+ end
155
+
156
+ def quietly
157
+ v = $VERBOSE
158
+ $VERBOSE = nil
159
+ yield
160
+ ensure
161
+ $VERBOSE = v
162
+ end
163
+
164
+ def child_program config
165
+ <<-program
166
+ require 'rbconfig'
167
+ is_jruby = (::Config::CONFIG['RUBY_INSTALL_NAME'] =~ /jruby/) != nil
168
+
169
+ PIPE = STDOUT.dup
170
+ begin
171
+ if(is_jruby)
172
+ require 'java'
173
+
174
+ include_class 'java.io.PrintStream'
175
+ include_class 'java.io.ByteArrayOutputStream'
176
+ include_class 'java.io.FileOutputStream'
177
+ include_class 'java.io.FileInputStream'
178
+ include_class 'java.io.BufferedOutputStream'
179
+ include_class 'java.io.BufferedInputStream'
180
+ include_class 'java.lang.System'
181
+ end
182
+
183
+ require 'yaml'
184
+
185
+ config = YAML.load(IO.read('#{ config }'))
186
+
187
+ argv = config['argv']
188
+ env = config['env']
189
+ cwd = config['cwd']
190
+ stdin = config['stdin']
191
+ stdout = config['stdout']
192
+ stderr = config['stderr']
193
+
194
+ Dir.chdir cwd if cwd
195
+ env.each{|k,v| ENV[k.to_s] = v.to_s} if env
196
+
197
+ if(is_jruby)
198
+ file_in = BufferedInputStream.new(FileInputStream.new(stdin))
199
+ System.setIn(file_in)
200
+ file_out = PrintStream.new(BufferedOutputStream.new(FileOutputStream.new(stdout)))
201
+ System.setOut(file_out)
202
+ file_err = PrintStream.new(BufferedOutputStream.new(FileOutputStream.new(stderr)))
203
+ System.setErr(file_err)
204
+ else
205
+ STDIN.reopen stdin
206
+ STDOUT.reopen stdout
207
+ STDERR.reopen stderr
208
+ end
209
+
210
+ PIPE.puts "pid: \#{ Process.pid }"
211
+ PIPE.flush ### the process is ready yo!
212
+ PIPE.close unless(is_jruby)
213
+
214
+ exec *argv
215
+ rescue Exception => e
216
+ PIPE.write Marshal.dump(e) rescue nil
217
+ exit 42
218
+ end
219
+ program
220
+ end
221
+
222
+ def relay srcdst
223
+ src, dst, ignored = srcdst.to_a.first
224
+ if src.respond_to? 'read'
225
+ while((buf = src.read(8192))); dst << buf; end
226
+ else
227
+ src.each{|buf| dst << buf}
228
+ end
229
+ end
230
+
231
+ def tmpdir d = Dir.tmpdir, max = 42, &b
232
+ i = -1 and loop{
233
+ i += 1
234
+
235
+ tmp = File.join d, "systemu_#{ @host }_#{ @ppid }_#{ @pid }_#{ rand }_#{ i += 1 }"
236
+
237
+ begin
238
+ Dir.mkdir tmp
239
+ rescue Errno::EEXIST
240
+ raise if i >= max
241
+ next
242
+ end
243
+
244
+ break(
245
+ if b
246
+ begin
247
+ b.call tmp
248
+ ensure
249
+ FileUtils.rm_rf tmp unless SystemU.turd
250
+ end
251
+ else
252
+ tmp
253
+ end
254
+ )
255
+ }
256
+ end
257
+
258
+ def getopts opts = {}
259
+ lambda do |*args|
260
+ keys, default, ignored = args
261
+ catch('opt') do
262
+ [keys].flatten.each do |key|
263
+ [key, key.to_s, key.to_s.intern].each do |key|
264
+ throw 'opt', opts[key] if opts.has_key?(key)
265
+ end
266
+ end
267
+ default
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ SystemU = SystemUniversal unless defined? SystemU
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+ if $0 == __FILE__
288
+ #
289
+ # date
290
+ #
291
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
292
+
293
+ status, stdout, stderr = systemu date
294
+ p [status, stdout, stderr]
295
+
296
+ status = systemu date, 1=>(stdout = '')
297
+ p [status, stdout]
298
+
299
+ status = systemu date, 2=>(stderr = '')
300
+ p [status, stderr]
301
+ #
302
+ # sleep
303
+ #
304
+ sleep = %q( ruby -e" p(sleep(1)) " )
305
+ status, stdout, stderr = systemu sleep
306
+ p [status, stdout, stderr]
307
+
308
+ sleep = %q( ruby -e" p(sleep(42)) " )
309
+ status, stdout, stderr = systemu(sleep){|cid| Process.kill 9, cid}
310
+ p [status, stdout, stderr]
311
+ #
312
+ # env
313
+ #
314
+ env = %q( ruby -e" p ENV['A'] " )
315
+ status, stdout, stderr = systemu env, :env => {'A' => 42}
316
+ p [status, stdout, stderr]
317
+ #
318
+ # cwd
319
+ #
320
+ env = %q( ruby -e" p Dir.pwd " )
321
+ status, stdout, stderr = systemu env, :cwd => Dir.tmpdir
322
+ p [status, stdout, stderr]
323
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: averell23-systemu
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Ara T. Howard (ara.t.howard@noaa.gov)
8
+ - Daniel Hahn (JRuby modifications)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-09-11 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Special version for JRuby compatibility
18
+ email: hahn@netseven.it
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README
25
+ - README.tmpl
26
+ files:
27
+ - README.tmpl
28
+ - VERSION.yml
29
+ - lib/systemu.rb
30
+ - README
31
+ has_rdoc: true
32
+ homepage: http://codeforpeople.com/lib/ruby/systemu
33
+ post_install_message:
34
+ rdoc_options:
35
+ - --inline-source
36
+ - --charset=UTF-8
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: System Universal
58
+ test_files: []
59
+