averell23-systemu 1.2.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.
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
+