alib 0.3.1 → 0.4.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.
@@ -0,0 +1,175 @@
1
+ require 'fcntl'
2
+
3
+ module Open4
4
+ #--{{{
5
+ def self.version
6
+ '0.4.0'
7
+ end
8
+
9
+ def popen4(*cmd)
10
+ #--{{{
11
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
12
+
13
+ verbose = $VERBOSE
14
+ begin
15
+ $VERBOSE = nil
16
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
17
+
18
+ cid = fork {
19
+ pw.last.close
20
+ STDIN.reopen pw.first
21
+ pw.first.close
22
+
23
+ pr.first.close
24
+ STDOUT.reopen pr.last
25
+ pr.last.close
26
+
27
+ pe.first.close
28
+ STDERR.reopen pe.last
29
+ pe.last.close
30
+
31
+ STDOUT.sync = STDERR.sync = true
32
+
33
+ begin
34
+ exec(*cmd)
35
+ raise "exec failed!"
36
+ rescue Exception => e
37
+ Marshal.dump(e, ps.last)
38
+ ps.last.flush
39
+ end
40
+ ps.last.close unless (ps.last.closed?)
41
+ exit!
42
+ }
43
+ ensure
44
+ $VERBOSE = verbose
45
+ end
46
+
47
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
48
+
49
+ begin
50
+ e = Marshal.load ps.first
51
+ raise(Exception === e ? e : "unknown failure!")
52
+ rescue EOFError # If we get an EOF error, then the exec was successful
53
+ 42
54
+ end
55
+
56
+ pw.last.sync = true
57
+
58
+ pi = [pw.last, pr.first, pe.first]
59
+
60
+ if defined? yield
61
+ begin
62
+ yield(cid, *pi)
63
+ Process.waitpid2(cid).last
64
+ ensure
65
+ pi.each{|fd| fd.close unless fd.closed?}
66
+ end
67
+ else
68
+ [cid, pw.last, pr.first, pe.first]
69
+ end
70
+ #--}}}
71
+ end
72
+ alias open4 popen4
73
+ module_function :popen4
74
+ module_function :open4
75
+
76
+ class Error < ::StandardError; end
77
+ class SpawnError < Error
78
+ #--{{{
79
+ attr 'cmd'
80
+ attr 'status'
81
+ def exitstatus
82
+ @status.exitstatus
83
+ end
84
+ def initialize cmd, status
85
+ @cmd, @status = cmd, status
86
+ super "cmd <#{ cmd }> failed with <#{ exitstatus }>"
87
+ end
88
+ #--}}}
89
+ end
90
+ def spawn cmd, opts = {}
91
+ #--{{{
92
+ getopt = lambda do |*args|
93
+ keys, default, ignored = args
94
+ catch('opt') do
95
+ [keys].flatten.each do |key|
96
+ [key, key.to_s, key.to_s.intern].each do |key|
97
+ throw 'opt', opts[key] if opts.has_key?(key)
98
+ end
99
+ end
100
+ default
101
+ end
102
+ end
103
+
104
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
105
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
106
+ exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
107
+ stdin = getopt[ ['stdin', 'in', '0', 0] ]
108
+ stdout = getopt[ ['stdout', 'out', '1', 1] ]
109
+ stderr = getopt[ ['stderr', 'err', '2', 2] ]
110
+ pid = getopt[ 'pid' ]
111
+
112
+ started = false
113
+
114
+ status =
115
+ begin
116
+ popen4(cmd) do |c, i, o, e|
117
+ started = true
118
+
119
+ if pid.respond_to? '<<'
120
+ pid << c
121
+ end
122
+
123
+ it = Thread.new(i,stdin) do |i,stdin|
124
+ if stdin
125
+ if stdin.respond_to? :each
126
+ stdin.each{|buf| i << buf}
127
+ elsif stdin.respond_to? :read
128
+ i << stdin.read
129
+ else
130
+ i << stdin.to_s
131
+ end
132
+ end
133
+ i.close
134
+ end
135
+
136
+ ot = Thread.new(o,stdout){|o,stdout| o.each{|buf| stdout << buf if stdout}}
137
+ et = Thread.new(e,stderr){|e,stderr| e.each{|buf| stderr << buf if stderr}}
138
+
139
+ it.join
140
+ ot.join if ot
141
+ et.join if et
142
+ end
143
+ rescue
144
+ raise unless(not started and ignore_exec_failure)
145
+ end
146
+
147
+ raise SpawnError.new(cmd, status) unless
148
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or (status.exitstatus == exitstatus))
149
+
150
+ status
151
+ #--}}}
152
+ end
153
+ module_function :spawn
154
+
155
+ def background cmd, opts = {}
156
+ #--{{{
157
+ require 'thread'
158
+ q = Queue.new
159
+ opts['pid'] = opts[:pid] = q
160
+ thread = Thread.new(cmd, opts){|cmd, opts| spawn cmd, opts}
161
+ pid = q.pop
162
+ sc = class << thread; self; end
163
+ sc.module_eval {
164
+ define_method(:pid){ pid }
165
+ define_method(:spawn_status){ @spawn_status ||= value }
166
+ define_method(:exitstatus){ spawn_status.exitstatus }
167
+ }
168
+ thread
169
+ #--}}}
170
+ end
171
+ alias bg background
172
+ module_function :background
173
+ module_function :bg
174
+ #--}}}
175
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # auto vivifying ordered hash that dumps as yaml nicely
3
+ #
4
+ class ALib::AutoOrderedHash < ALib::OrderedHash
5
+ #--{{{
6
+ def initialize(*args)
7
+ #--{{{
8
+ super(*args){|a,k| a[k] = __class__.new(*args)}
9
+ #--}}}
10
+ end
11
+ def class # for nice yaml
12
+ #--{{{
13
+ Hash
14
+ #--}}}
15
+ end
16
+ def __class__
17
+ #--{{{
18
+ ALib::AutoOrderedHash
19
+ #--}}}
20
+ end
21
+ #--}}}
22
+ end # class AutoOrderedHash
23
+ ALib::OrderedAutoHash = ALib::AutoOrderedHash
@@ -0,0 +1,243 @@
1
+ # AUTHOR
2
+ # jan molic /mig/at/1984/dot/cz/
3
+ #
4
+ # DESCRIPTION
5
+ # Hash with preserved order and some array-like extensions
6
+ # Public domain.
7
+ #
8
+ # THANKS
9
+ # Andrew Johnson for his suggestions and fixes of Hash[],
10
+ # merge, to_a, inspect and shift
11
+ class ALib::OrderedHash < ::Hash
12
+ #--{{{
13
+ attr_accessor :order
14
+
15
+ class << self
16
+ #--{{{
17
+ def [] *args
18
+ #--{{{
19
+ hsh = OrderedHash.new
20
+ if Hash === args[0]
21
+ hsh.replace args[0]
22
+ elsif (args.size % 2) != 0
23
+ raise ArgumentError, "odd number of elements for Hash"
24
+ else
25
+ hsh[args.shift] = args.shift while args.size > 0
26
+ end
27
+ hsh
28
+ #--}}}
29
+ end
30
+ #--}}}
31
+ end
32
+ # def initialize
33
+ ##--{{{
34
+ # @order = []
35
+ ##--}}}
36
+ # end
37
+ def initialize(*a, &b)
38
+ #--{{{
39
+ super
40
+ @order = []
41
+ #--}}}
42
+ end
43
+ def store_only a,b
44
+ #--{{{
45
+ store a,b
46
+ #--}}}
47
+ end
48
+ alias orig_store store
49
+ def store a,b
50
+ #--{{{
51
+ @order.push a unless has_key? a
52
+ super a,b
53
+ #--}}}
54
+ end
55
+ alias []= store
56
+ def == hsh2
57
+ #--{{{
58
+ return false if @order != hsh2.order
59
+ super hsh2
60
+ #--}}}
61
+ end
62
+ def clear
63
+ #--{{{
64
+ @order = []
65
+ super
66
+ #--}}}
67
+ end
68
+ def delete key
69
+ #--{{{
70
+ @order.delete key
71
+ super
72
+ #--}}}
73
+ end
74
+ def each_key
75
+ #--{{{
76
+ @order.each { |k| yield k }
77
+ self
78
+ #--}}}
79
+ end
80
+ def each_value
81
+ #--{{{
82
+ @order.each { |k| yield self[k] }
83
+ self
84
+ #--}}}
85
+ end
86
+ def each
87
+ #--{{{
88
+ @order.each { |k| yield k,self[k] }
89
+ self
90
+ #--}}}
91
+ end
92
+ alias each_pair each
93
+ def delete_if
94
+ #--{{{
95
+ @order.clone.each { |k|
96
+ delete k if yield
97
+ }
98
+ self
99
+ #--}}}
100
+ end
101
+ def values
102
+ #--{{{
103
+ ary = []
104
+ @order.each { |k| ary.push self[k] }
105
+ ary
106
+ #--}}}
107
+ end
108
+ def keys
109
+ #--{{{
110
+ @order
111
+ #--}}}
112
+ end
113
+ def invert
114
+ #--{{{
115
+ hsh2 = Hash.new
116
+ @order.each { |k| hsh2[self[k]] = k }
117
+ hsh2
118
+ #--}}}
119
+ end
120
+ def reject &block
121
+ #--{{{
122
+ self.dup.delete_if &block
123
+ #--}}}
124
+ end
125
+ def reject! &block
126
+ #--{{{
127
+ hsh2 = reject &block
128
+ self == hsh2 ? nil : hsh2
129
+ #--}}}
130
+ end
131
+ def replace hsh2
132
+ #--{{{
133
+ @order = hsh2.keys
134
+ super hsh2
135
+ #--}}}
136
+ end
137
+ def shift
138
+ #--{{{
139
+ key = @order.first
140
+ key ? [key,delete(key)] : super
141
+ #--}}}
142
+ end
143
+ def unshift k,v
144
+ #--{{{
145
+ unless self.include? k
146
+ @order.unshift k
147
+ orig_store(k,v)
148
+ true
149
+ else
150
+ false
151
+ end
152
+ #--}}}
153
+ end
154
+ def push k,v
155
+ #--{{{
156
+ unless self.include? k
157
+ @order.push k
158
+ orig_store(k,v)
159
+ true
160
+ else
161
+ false
162
+ end
163
+ #--}}}
164
+ end
165
+ def pop
166
+ #--{{{
167
+ key = @order.last
168
+ key ? [key,delete(key)] : nil
169
+ #--}}}
170
+ end
171
+ def to_a
172
+ #--{{{
173
+ ary = []
174
+ each { |k,v| ary << [k,v] }
175
+ ary
176
+ #--}}}
177
+ end
178
+ def to_s
179
+ #--{{{
180
+ self.to_a.to_s
181
+ #--}}}
182
+ end
183
+ def inspect
184
+ #--{{{
185
+ ary = []
186
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
187
+ '{' + ary.join(", ") + '}'
188
+ #--}}}
189
+ end
190
+ def update hsh2
191
+ #--{{{
192
+ hsh2.each { |k,v| self[k] = v }
193
+ self
194
+ #--}}}
195
+ end
196
+ alias :merge! update
197
+ def merge hsh2
198
+ #--{{{
199
+ self.dup update(hsh2)
200
+ #--}}}
201
+ end
202
+ def select
203
+ #--{{{
204
+ ary = []
205
+ each { |k,v| ary << [k,v] if yield k,v }
206
+ ary
207
+ #--}}}
208
+ end
209
+ def class
210
+ #--{{{
211
+ Hash
212
+ #--}}}
213
+ end
214
+
215
+ attr_accessor "to_yaml_style"
216
+ def yaml_inline= bool
217
+ if respond_to?("to_yaml_style")
218
+ self.to_yaml_style = :inline
219
+ else
220
+ unless defined? @__yaml_inline_meth
221
+ @__yaml_inline_meth =
222
+ lambda {|opts|
223
+ YAML::quick_emit(object_id, opts) {|emitter|
224
+ emitter << '{ ' << map{|kv| kv.join ': '}.join(', ') << ' }'
225
+ }
226
+ }
227
+ class << self
228
+ def to_yaml opts = {}
229
+ begin
230
+ @__yaml_inline ? @__yaml_inline_meth[ opts ] : super
231
+ rescue
232
+ @to_yaml_style = :inline
233
+ super
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+ @__yaml_inline = bool
240
+ end
241
+ def yaml_inline!() self.yaml_inline = true end
242
+ #--}}}
243
+ end # class OrderedHash
@@ -0,0 +1,1167 @@
1
+ #
2
+ # the utility module is a namespace for things which otherwise wouldn't have a
3
+ # home. the methods of Util can be used as module methods or included into a
4
+ # class and then used as instance OR class methods
5
+ #
6
+ module ALib::Util
7
+ #--{{{
8
+ class << self
9
+ #--{{{
10
+ def export(*syms)
11
+ #--{{{
12
+ syms.each do |sym|
13
+ sym = "#{ sym }".intern
14
+ module_function sym
15
+ public sym
16
+ end
17
+ #--}}}
18
+ end
19
+ def append_features c
20
+ #--{{{
21
+ super
22
+ c.extend self
23
+ #--}}}
24
+ end
25
+ #--}}}
26
+ end
27
+ #
28
+ # requires a certain version of ruby or higher
29
+ # require_version '1.8.0'
30
+ #
31
+ def require_version version
32
+ #--{{{
33
+ major, minor, teeny = "#{ version }".split(%r/\./o).map{|n| Integer(n)}
34
+ required = "#{ major }.#{ minor }.#{ teeny }"
35
+ _major, _minor, _teeny =
36
+ %w( MAJOR MINOR TEENY ).map{|k| Integer(::Config::CONFIG[k])}
37
+ actual = "#{ _major }.#{ _minor }.#{ _teeny }"
38
+ unless _major > major or _major == major and _minor >= minor
39
+ STDERR.puts("=" * 79)
40
+ STDERR.puts "this program requires a ruby version >= <#{ required }>"
41
+ STDERR.puts
42
+ STDERR.puts "you are currenlty running ruby version <#{ actual }>"
43
+ STDERR.puts
44
+ STDERR.puts "possible problems which could cause this are:"
45
+ STDERR.puts " - improper PATH environment variable setting"
46
+ STDERR.puts " - ruby > <#{ required }> has not been installed"
47
+ STDERR.puts("=" * 79)
48
+ exit 1
49
+ end
50
+ #--}}}
51
+ end
52
+ #
53
+ # the basename of $0
54
+ #
55
+ def prognam
56
+ #--{{{
57
+ File::basename $0
58
+ #--}}}
59
+ end
60
+ export 'prognam'
61
+ #
62
+ # marshal'd 'deep' copy of obj
63
+ #
64
+ def mcp obj
65
+ #--{{{
66
+ Marshal.load(Marshal.dump(obj))
67
+ #--}}}
68
+ end
69
+ export 'mcp'
70
+ #
71
+ # self.class
72
+ #
73
+ def klass
74
+ #--{{{
75
+ Module === self ? self : self.class
76
+ #--}}}
77
+ end
78
+ export 'klass'
79
+ #
80
+ # evaluates block in singleton scope or simply returns singleton_class
81
+ #
82
+ def singleton_class &b
83
+ #--{{{
84
+ sc =
85
+ class << self; self; end
86
+ b ? sc.module_eval(&b) : sc
87
+ #--}}}
88
+ end
89
+ #
90
+ # File::expand_path + link resolution
91
+ #
92
+ def realpath path
93
+ #--{{{
94
+ path = File::expand_path "#{ path }"
95
+ begin
96
+ Pathname.new(path).realpath.to_s
97
+ rescue Errno::ENOENT, Errno::ENOTDIR
98
+ path
99
+ end
100
+ #--}}}
101
+ end
102
+ export 'realpath'
103
+ #
104
+ # collect n hashed into one hash - later keys overried earlier keys
105
+ #
106
+ def hashify(*hashes)
107
+ #--{{{
108
+ hashes.inject(accum={}){|accum,hash| accum.update hash}
109
+ #--}}}
110
+ end
111
+ export 'hashify'
112
+ #
113
+ # look up key in hash as key, then string of key, then intern of key -
114
+ # returning the value or, if key is not found, nil or default
115
+ #
116
+ def getopt opt, hash, default = nil
117
+ #--{{{
118
+ keys = opt.respond_to?('each') ? opt : [opt]
119
+
120
+ keys.each do |key|
121
+ return hash[key] if hash.has_key? key
122
+ key = "#{ key }"
123
+ return hash[key] if hash.has_key? key
124
+ key = key.intern
125
+ return hash[key] if hash.has_key? key
126
+ end
127
+
128
+ return default
129
+ #--}}}
130
+ end
131
+ alias get_opt getopt
132
+ export 'getopt'
133
+ export 'get_opt'
134
+ #
135
+ # determine if a key, key.to_s, or key.to_s.intern is in hash
136
+ # see getopt
137
+ #
138
+ def hasopt opt, hash, default = false
139
+ #--{{{
140
+ keys = opt.respond_to?('each') ? opt : [opt]
141
+
142
+ keys.each do |key|
143
+ return key if hash.has_key? key
144
+ key = "#{ key }"
145
+ return key if hash.has_key? key
146
+ key = key.intern
147
+ return key if hash.has_key? key
148
+ end
149
+
150
+ return default
151
+ #--}}}
152
+ end
153
+ alias has_opt hasopt
154
+ alias hasopt? hasopt
155
+ alias has_opt? hasopt
156
+ export 'hasopt'
157
+ export 'hasopt?'
158
+ export 'has_opt'
159
+ export 'has_opt?'
160
+ #
161
+ # delete key in hash as key, then string of key, then intern of key -
162
+ # returning the value or, if key is not found, nil or default
163
+ #
164
+ def delopt opt, hash, default = nil
165
+ #--{{{
166
+ keys = opt.respond_to?('each') ? opt : [opt]
167
+
168
+ keys.each do |key|
169
+ return hash.delete(key) if hash.has_key? key
170
+ key = "#{ key }"
171
+ return hash.delete(key) if hash.has_key? key
172
+ key = key.intern
173
+ return hash.delete(key) if hash.has_key? key
174
+ end
175
+
176
+ return default
177
+ #--}}}
178
+ end
179
+ alias delete_opt delopt
180
+ alias extract_opt delopt
181
+ alias extractopt delopt
182
+ alias xopt delopt
183
+ export 'delete_opt'
184
+ export 'extract_opt'
185
+ export 'extractopt'
186
+ export 'xopt'
187
+ export 'delopt'
188
+ #
189
+ # returns true if pid is running, false otherwise
190
+ #
191
+ def alive pid
192
+ #--{{{
193
+ pid = Integer("#{ pid }")
194
+ begin
195
+ Process::kill 0, pid
196
+ true
197
+ rescue Errno::ESRCH
198
+ false
199
+ end
200
+ #--}}}
201
+ end
202
+ alias alive? alive
203
+ export 'alive', 'alive?'
204
+ #
205
+ # brutally shut down a process. opts can contain the keys 'signals' which
206
+ # should be a list of signals used to send to the process and the key
207
+ # 'suspend' which is the amount of time to wait after firing each signal
208
+ # before seeing if the process is dead. the defaults are %w(TERM QUIT KILL)
209
+ # and 4 respectively
210
+ #
211
+ def maim(pid, opts = {})
212
+ #--{{{
213
+ sigs = getopt 'signals', opts, %w(SIGTERM SIGQUIT SIGKILL)
214
+ suspend = getopt 'suspend', opts, 4
215
+ pid = Integer("#{ pid }")
216
+ existed = false
217
+ sigs.each do |sig|
218
+ begin
219
+ Process::kill(sig, pid)
220
+ existed = true
221
+ rescue Errno::ESRCH
222
+ unless existed
223
+ return nil
224
+ else
225
+ return true
226
+ end
227
+ end
228
+ return true unless alive?(pid)
229
+ sleep suspend
230
+ return true unless alive?(pid)
231
+ end
232
+ return(not alive?(pid))
233
+ #--}}}
234
+ end
235
+ export 'maim'
236
+ #
237
+ # YYYY-MM-DD hh:mm:ss.uuuuuu representation of time. if the option
238
+ # 'nospace' is true spaces are replaced with underscores/or the value of the
239
+ # nospace option - useful for constructing filenames
240
+ #
241
+ def timestamp arg = Time::now
242
+ #--{{{
243
+ if Time === arg
244
+ arg.iso8601 2
245
+ else
246
+ opts =
247
+ case arg
248
+ when Hash
249
+ opts = arg
250
+ when Time
251
+ {'time' => arg}
252
+ else
253
+ raise ArgumentError, "#{ arg.inspect } (#{ arg.class })"
254
+ end
255
+ time = getopt 'time', opts, Time::now
256
+ local = getopt 'local', opts, false
257
+ nospace = getopt('nospace', opts, getopt('no_space', opts, false))
258
+ dateonly = getopt('dateonly', opts, getopt('date_only', opts, false))
259
+ time = time.utc unless local
260
+ usec = "#{ time.usec }"
261
+ usec << ('0' * (6 - usec.size)) if usec.size < 6
262
+ stamp =
263
+ unless dateonly
264
+ time.strftime('%Y-%m-%d %H:%M:%S.') << usec
265
+ else
266
+ time.strftime('%Y-%m-%d')
267
+ end
268
+ if nospace
269
+ spc = TrueClass === nospace ? 'T' : "#{ nospace }"
270
+ stamp.gsub! %r/\s+/, spc
271
+ end
272
+ stamp
273
+ end
274
+ #--}}}
275
+ end
276
+ export 'timestamp'
277
+ #
278
+ # inverse of timestamp. the option 'local' determines whether the timestamp
279
+ # is interpreted as a local or utc time.
280
+ #
281
+ # TODO - review hack to fix Time::parse bug with ms and tz
282
+ # TODO - review hack to fix Time::parse bug with ms and tz
283
+ # TODO - review hack to fix Time::parse bug with ms and tz
284
+ def stamptime string, opts = {}
285
+ #--{{{
286
+ tz = string[ %r/-\d\d:\d\d\s*$/ ]
287
+ time = nil
288
+ if opts.empty? or tz
289
+ u = string[%r/\.\d+/]
290
+ string[%r/\.\d+/] = '' if u
291
+ time = Time::parse string
292
+ time += u.to_f if u
293
+ else
294
+ local = getopt 'local', opts, false
295
+ string = "#{ string }"
296
+ pat = %r/^\s*(\d\d\d\d)-(\d\d)-(\d\d)[\s_tT]+(\d\d):(\d\d):(\d\d)(?:.(\d+))?\s*$/o
297
+ match = pat.match string
298
+ raise ArgumentError, "<#{ string.inspect }>" unless match
299
+ yyyy,mm,dd,h,m,s,u = match.to_a[1..-1].map{|m| (m || 0).to_i}
300
+ if local
301
+ time = Time::local yyyy,mm,dd,h,m,s,u
302
+ else
303
+ time = Time::gm yyyy,mm,dd,h,m,s,u
304
+ end
305
+ end
306
+ return time
307
+ #--}}}
308
+ end
309
+
310
+ def version_cmp a, b
311
+ #--{{{
312
+ to_v = lambda{|v| v.to_s.scan(%r/\d+/).map{|c| Integer c}}
313
+ to_v[a] <=> to_v[b]
314
+ #--}}}
315
+ end
316
+ export 'version_cmp'
317
+
318
+ # TODO - 1.8.4 parses usec but 1.8.2 does not. handle differently.
319
+ def stamptime string, opts = {}
320
+ #--{{{
321
+ string = string.to_s
322
+ local = getopt('local', opts, getopt('localtime', opts, false))
323
+ utc = getopt('utc', opts, false)
324
+
325
+ gte_184 = (defined?(RUBY_VERSION) and [0,1].include?(version_cmp(RUBY_VERSION, "1.8.4")))
326
+
327
+ time = nil
328
+
329
+ tz_pat = %r/([\+\-]\d\d:\d\d|Z)\s*$/
330
+
331
+ if utc and string !~ tz_pat
332
+ string = "#{ string }Z"
333
+ end
334
+
335
+ if gte_184
336
+ time = Time::parse string.to_s
337
+ else
338
+ # strip time zone
339
+ z = string[ tz_pat ]
340
+ string[ tz_pat ] = "" if z
341
+
342
+ # yank out usec
343
+ u = string[ %r/\.\d+\s*$/ ]
344
+ string[ %r/\.\d+\s*$/ ] = "" if u
345
+
346
+ # parse with time zone restored, adding usec
347
+ time = Time::parse "#{ string }#{ z }"
348
+ time += u.to_f if u
349
+ end
350
+
351
+ local ? time.localtime : time#.utc
352
+ #--}}}
353
+ end
354
+ export 'stamptime'
355
+ #
356
+ # escapes any occurances of char in s with esc modifying s inplace
357
+ #
358
+ def escape! s, char, esc
359
+ #--{{{
360
+ # re = %r/([#{ 0x5c.chr << esc }]*)#{ char }/
361
+ re = %r/([#{ Regexp::quote esc }]*)#{ Regexp::quote char }/
362
+ s.gsub!(re) do
363
+ (($1.size % 2 == 0) ? ($1 << esc) : $1) + char
364
+ end
365
+ #--}}}
366
+ end
367
+ export 'escape!'
368
+ #
369
+ # new copy of s with any occurances of char escaped with esc
370
+ #
371
+ def escape s, char, esc
372
+ #--{{{
373
+ escape! "#{ s }", char, esc
374
+ #--}}}
375
+ end
376
+ export 'escape'
377
+ #
378
+ # a quiet fork
379
+ #
380
+ def fork(*args, &block)
381
+ #--{{{
382
+ begin
383
+ verbose = $VERBOSE
384
+ $VERBOSE = nil
385
+ Process::fork(*args, &block)
386
+ ensure
387
+ $VERBOSE = verbose
388
+ end
389
+ #--}}}
390
+ end
391
+ export 'fork'
392
+ #
393
+ # an quiet exec
394
+ #
395
+ def exec(*args, &block)
396
+ #--{{{
397
+ begin
398
+ verbose = $VERBOSE
399
+ $VERBOSE = nil
400
+ Kernel::exec(*args, &block)
401
+ ensure
402
+ $VERBOSE = verbose
403
+ end
404
+ #--}}}
405
+ end
406
+ export 'exec'
407
+ #
408
+ # a quiet system
409
+ #
410
+ def system(*args, &block)
411
+ #--{{{
412
+ begin
413
+ verbose = $VERBOSE
414
+ $VERBOSE = nil
415
+ Kernel::system(*args, &block)
416
+ ensure
417
+ $VERBOSE = verbose
418
+ end
419
+ #--}}}
420
+ end
421
+ export 'system'
422
+ #
423
+ # lookup, and cache, the hostname
424
+ #
425
+ def hostname
426
+ #--{{{
427
+ @__hostname__ ||= Socket::gethostname
428
+ #--}}}
429
+ end
430
+ export 'hostname'
431
+ #
432
+ # lookup, and cache, the host (first bit of hostname quad)
433
+ #
434
+ def host
435
+ #--{{{
436
+ @__host__ ||= hostname.gsub(%r/\..*$/o,'')
437
+ #--}}}
438
+ end
439
+ export 'host'
440
+ #
441
+ # format exception as Logger class does - no backtrace
442
+ #
443
+ def emsg e
444
+ #--{{{
445
+ "#{ e.message } - (#{ e.class })"
446
+ #--}}}
447
+ end
448
+ export 'emsg'
449
+ #
450
+ # format exception backtrace as string
451
+ #
452
+ def btrace e
453
+ #--{{{
454
+ (e.backtrace or []).join("\n")
455
+ #--}}}
456
+ end
457
+ export 'btrace'
458
+ #
459
+ # format exception as Logger class does - with backtrace
460
+ #
461
+ def errmsg e
462
+ #--{{{
463
+ emsg(e) << "\n" << btrace(e)
464
+ #--}}}
465
+ end
466
+ export 'errmsg'
467
+ #
468
+ # determine equality of two exceptions
469
+ #
470
+ def erreq a, b
471
+ #--{{{
472
+ a.class == b.class and
473
+ a.message == b.message and
474
+ a.backtrace == b.backtrace
475
+ #--}}}
476
+ end
477
+ export 'erreq'
478
+ #
479
+ # generate a temporary filename for the directory dir using seed as a
480
+ # basename
481
+ #
482
+ def tmpnam(*argv)
483
+ #--{{{
484
+ args, opts = argv_split argv
485
+ dirname = argv.shift || getopt(%w(dir base prefix), opts, '.')
486
+ seed = getopt 'seed', opts, prognam
487
+ reap = getopt 'reap', opts, true
488
+
489
+ dirname = File.expand_path dirname
490
+
491
+ seed = seed.gsub(%r/[^0-9a-zA-Z]/,'_').gsub(%r/\s+/, '')
492
+ host = hostname.gsub(%r/\./, '_')
493
+
494
+ if reap
495
+ begin
496
+ baseglob = "%s__*__*__*__%s" % [ host, seed ]
497
+ glob = File.join(dirname, baseglob)
498
+ host_re = %r/^#{ host }$/
499
+ candidates = Dir[glob]
500
+ candidates.each do |candidate|
501
+ basename = File.basename candidate
502
+ parts = basename.split %r/__/, 5
503
+ if parts[0] =~ host_re
504
+ pid = Integer parts[1]
505
+ unless alive? pid
506
+ FileUtils.rm_rf candidate
507
+ end
508
+ end
509
+ end
510
+ rescue => e
511
+ warn(errmsg(e)) rescue nil
512
+ end
513
+ end
514
+
515
+ basename =
516
+ "%s__%s__%s__%s__%s" % [
517
+ host,
518
+ Process::pid,
519
+ timestamp('nospace' => true),
520
+ rand,
521
+ seed,
522
+ ]
523
+
524
+ File.join(dirname, basename)
525
+ #--}}}
526
+ end
527
+ export 'tmpnam'
528
+ #
529
+ # generate a temporary directory
530
+ #
531
+ def tmpdir(*argv)
532
+ #--{{{
533
+ args, opts = argv_split argv
534
+ retries = getopt 'retries', opts, 42
535
+ turd = getopt 'turd', opts
536
+ dir = args.first || getopt(%w(dir base prefix), opts)
537
+ dir ||= ENV['ALIB_TMP'] || ENV['ALIB_TEMP']
538
+ dir ||= '.'
539
+ opts['dir'] = dir
540
+ d = nil
541
+ retries.times do
542
+ td = tmpnam(opts)
543
+ break if ((d = FileUtils::mkdir_p td rescue nil))
544
+ end
545
+ raise "surpassed max retries <#{ retries }>" unless d
546
+ d = File::expand_path d
547
+
548
+ delete = lambda do
549
+ Dir::glob(File::join(d, "**")).each{|e| FileUtils::rm_rf e}
550
+ FileUtils::rm_rf d
551
+ end
552
+
553
+ if block_given?
554
+ cwd = Dir::pwd
555
+ begin
556
+ Dir::chdir d
557
+ yield [cwd,d]
558
+ ensure
559
+ Dir::chdir cwd if cwd
560
+ delete.call unless turd
561
+ end
562
+ else
563
+ at_exit &delete unless turd
564
+ return d
565
+ end
566
+
567
+ #--}}}
568
+ end
569
+ export 'tmpdir'
570
+ #
571
+ # make best effort to invalidate any inode caching done by nfs clients
572
+ #
573
+ def uncache file
574
+ #--{{{
575
+ refresh = nil
576
+ begin
577
+ is_a_file = File === file
578
+ path = (is_a_file ? file.path : file.to_s)
579
+ stat = (is_a_file ? file.stat : File::stat(file.to_s))
580
+ refresh = tmpnam(File::dirname(path))
581
+ File::link path, refresh rescue File::symlink path, refresh
582
+ File::chmod stat.mode, path
583
+ File::utime stat.atime, stat.mtime, path
584
+ open(File::dirname(path)){|d| d.fsync rescue nil}
585
+ ensure
586
+ begin
587
+ File::unlink refresh if refresh
588
+ rescue Errno::ENOENT
589
+ end
590
+ end
591
+ #--}}}
592
+ end
593
+ export 'uncache'
594
+ #
595
+ # wrap a string using options width (default 80) and indent using the indent
596
+ # option (default 0)
597
+ #
598
+ def columnize buf, opts = {}
599
+ #--{{{
600
+ width = getopt 'width', opts, 80
601
+ indent = getopt 'indent', opts
602
+ indent = Fixnum === indent ? (' ' * indent) : "#{ indent }"
603
+ column = []
604
+ words = buf.split %r/\s+/o
605
+ row = "#{ indent }"
606
+ while((word = words.shift))
607
+ if((row.size + word.size) < (width - 1))
608
+ row << word
609
+ else
610
+ column << row
611
+ row = "#{ indent }"
612
+ row << word
613
+ end
614
+ row << ' ' unless row.size == (width - 1)
615
+ end
616
+ column << row unless row.strip.empty?
617
+ column.join "\n"
618
+ #--}}}
619
+ end
620
+ export 'columnize'
621
+ #
622
+ # search for a default value for 'var' using
623
+ # * DEFAULT_VAR
624
+ # * self.class.var
625
+ # returning the option default, or nil
626
+ #
627
+ def defval var, opts = {}
628
+ #--{{{
629
+ default = getopt 'default', opts, nil
630
+ c = getopt 'class', opts, klass
631
+
632
+ d0, d1 = "default_#{ var }".intern, "#{ var }".intern
633
+ c0, c1 = "DEFAULT_#{ var }".upcase.intern, "#{ var }".upcase.intern
634
+
635
+ [d0, d1].each{|x| return(c.send(x)) if c.respond_to?(x)}
636
+ [c0, c1].each{|x| return(c.const_get(x)) if c.const_defined?(x)}
637
+
638
+ return default
639
+ #--}}}
640
+ end
641
+ export 'defval'
642
+ #
643
+ # initiates a catch block to do 'something'. if the method try_again! is
644
+ # called the block is done over - if the method give_up! is called the block
645
+ # is aborted. calls to attempt may be nested.
646
+ #
647
+ def attempt label = 'attempt'
648
+ #--{{{
649
+ ret = nil
650
+ n_attempts = 0
651
+ loop{ break unless catch("#{ label }"){ ret = yield(n_attempts += 1) } == 'try_again' }
652
+ ret
653
+ #--}}}
654
+ end
655
+ export 'attempt'
656
+ #
657
+ # see attempt
658
+ #
659
+ def try_again! label = 'attempt'
660
+ #--{{{
661
+ throw "#{ label }", 'try_again'
662
+ #--}}}
663
+ end
664
+ alias try_again try_again!
665
+ alias again! try_again!
666
+ export 'try_again!', 'again!', 'try_again'
667
+ #
668
+ # see attempt
669
+ #
670
+ def give_up! label = 'attempt'
671
+ #--{{{
672
+ throw "#{ label }", 'give_up'
673
+ #--}}}
674
+ end
675
+ alias giveup! give_up!
676
+ alias give_up give_up!
677
+ export 'give_up!', 'giveup!', 'give_up'
678
+ #
679
+ # a better const_get
680
+ #
681
+ def constant_get(hierachy)
682
+ #--{{{
683
+ ancestors = hierachy.split(%r/::/)
684
+ parent = Object
685
+ while((child = ancestors.shift))
686
+ klass = parent.const_get child
687
+ parent = klass
688
+ end
689
+ klass
690
+ #--}}}
691
+ end
692
+ export 'constant_get'
693
+ #
694
+ # creates a class by class name
695
+ #
696
+ def klass_stamp(hierachy, *a, &b)
697
+ #--{{{
698
+ constant_get(hierachy)::new(*a, &b)
699
+ #--}}}
700
+ end
701
+ export 'klass_stamp'
702
+ #
703
+ # shortcut to Find2
704
+ #
705
+ def find(*a, &b)
706
+ #--{{{
707
+ a << '.' if a.empty?
708
+ ALib::Find2::find(*a, &b)
709
+ #--}}}
710
+ end
711
+ export 'find'
712
+ #
713
+ # shortcut to Find2
714
+ #
715
+ def find2(*a, &b)
716
+ #--{{{
717
+ a << '.' if a.empty?
718
+ ALib::Find2::find2(*a, &b)
719
+ #--}}}
720
+ end
721
+ export 'find2'
722
+ #
723
+ # shortcut to Find2.prune
724
+ #
725
+ def prune
726
+ #--{{{
727
+ ALib::Find2.prune
728
+ #--}}}
729
+ end
730
+ export 'prune'
731
+ #
732
+ # pull out options from arglist
733
+ #
734
+ def optfilter(*list)
735
+ #--{{{
736
+ args, opts = [ list ].flatten.partition{|item| not Hash === item}
737
+ [args, hashify(*opts)]
738
+ #--}}}
739
+ end
740
+ export 'optfilter'
741
+ #
742
+ # pop of options from an arglist
743
+ #
744
+ def argv_split(argv)
745
+ #--{{{
746
+ args = argv
747
+ opts = Hash === args.last ? args.pop : {}
748
+ [args, opts]
749
+ #--}}}
750
+ end
751
+ export 'argv_split'
752
+ #
753
+ # split a path into dirname, basename, extension
754
+ #
755
+ def splitpath path
756
+ #--{{{
757
+ path = "#{ path }"
758
+ dirname, basename = File::split path
759
+ [ dirname, (%r/^([^\.]*)(.*)$/).match(basename)[1,2] ].flatten
760
+ #--}}}
761
+ end
762
+ export 'splitpath'
763
+ #
764
+ # handle a pathname after unzipping (iff needed)
765
+ #
766
+ def unzipped path, z_pat = %r/\.(?:z|gz)$/io
767
+ #--{{{
768
+ zipped = zipped?(path, z_pat)
769
+ unless zipped
770
+ yield path
771
+ else
772
+ unzipped = unzip path
773
+ begin
774
+ yield unzipped
775
+ ensure
776
+ zip unzipped
777
+ end
778
+ end
779
+ #--}}}
780
+ end
781
+ export 'unzipped'
782
+ #
783
+ # zip a file - return zipped name
784
+ #
785
+ def zip path, z_ext = nil
786
+ #--{{{
787
+ z_ext ||= '.gz'
788
+ z_ext.gsub! %r/^\s*\.+/, '.'
789
+ spawn "gzip --suffix #{ z_ext } --force #{ path }"
790
+ zipped = "#{ path }#{ z_ext }"
791
+ raise "could not create <#{ zipped }>" unless test ?e, zipped
792
+ if block_given?
793
+ yield zipped
794
+ else
795
+ zipped
796
+ end
797
+ #--}}}
798
+ end
799
+ alias gzip zip
800
+ export 'gzip'
801
+ export 'zip'
802
+ #
803
+ # return the zipped name of a path
804
+ #
805
+ def zipped_name path, z_ext = nil
806
+ #--{{{
807
+ z_ext ||= '.gz'
808
+ z_ext.gsub! %r/^\s*\.+/, '.'
809
+ "#{ path }#{ z_ext }"
810
+ #--}}}
811
+ end
812
+ export 'zipped_name'
813
+ #
814
+ # unzip a file - return unzipped name
815
+ #
816
+ def unzip path, z_pat = nil
817
+ #--{{{
818
+ z_pat ||= %r/\.(?:z|gz)$/io
819
+ spawn "gzip --force --decompress #{ path }" if zipped?(path, z_pat)
820
+ #spawn "gzip --force --decompress #{ path }"
821
+ uzn = unzipped_name path, z_pat
822
+ if block_given?
823
+ yield uzn
824
+ else
825
+ uzn
826
+ end
827
+ #--}}}
828
+ end
829
+ alias gunzip unzip
830
+ export 'gunzip'
831
+ export 'unzip'
832
+ #
833
+ # return the unzipped name of a path
834
+ #
835
+ def unzipped_name path, z_pat = nil
836
+ #--{{{
837
+ z_pat ||= %r/\.(?:z|gz)$/io
838
+ path.gsub z_pat, ''
839
+ #--}}}
840
+ end
841
+ export 'unzipped_name'
842
+ #
843
+ # guess if a file is zipped based on pathname
844
+ #
845
+ def zipped? path, z_pat = %r/\.(?:z|gz)$/io
846
+ #--{{{
847
+ path =~ z_pat
848
+ #--}}}
849
+ end
850
+ alias gzipped? zipped?
851
+ alias zipped zipped?
852
+ export 'zipped'
853
+ export 'gzipped?'
854
+ export 'zipped?'
855
+ #
856
+ # convert a string to an integer of any base
857
+ #
858
+ def strtod(s, opts = {})
859
+ #--{{{
860
+ base = getopt 'base', opts, 10
861
+ case base
862
+ when 2
863
+ s = "0b#{ s }" unless s =~ %r/^0b\d/
864
+ when 8
865
+ s = "0#{ s }" unless s =~ %r/^0\d/
866
+ when 16
867
+ s = "0x#{ s }" unless s =~ %r/^0x\d/
868
+ end
869
+ Integer s
870
+ #--}}}
871
+ end
872
+ export 'strtod'
873
+ #
874
+ # convert a string to an integer
875
+ #
876
+ def atoi(s, opts = {})
877
+ #--{{{
878
+ strtod("#{ s }".gsub(%r/^0+/,''), 'base' => 10)
879
+ #--}}}
880
+ end
881
+ export 'atoi'
882
+ #
883
+ # bin a integer into a int range using modulo logic
884
+ #
885
+ def rangemod n, ir
886
+ #--{{{
887
+ a, b = Integer(ir.first), Integer(ir.last)
888
+ a, b = b, a if a >= b
889
+ exclusive = ir.exclude_end?
890
+ size = b - a
891
+ size += 1 unless exclusive
892
+ offset = a - 0
893
+ x = (n + offset).modulo size
894
+ x += offset
895
+ #--}}}
896
+ end
897
+ #
898
+ # bin a integer into a int range using modulo logic
899
+ #
900
+ def rangemod n, ir
901
+ #--{{{
902
+ a, b = [ir.first.to_i, ir.last.to_i].sort
903
+ b += 1 if ir.exclude_end?
904
+ size = b - a
905
+ if n < a
906
+ v = n - a
907
+ d = v.abs
908
+ r = d % size
909
+ n = b - r
910
+ elsif n > b
911
+ v = n - b
912
+ d = v.abs
913
+ r = d % size
914
+ n = a + r
915
+ end
916
+ raise "failed to rangemod #{ n } => #{ ir }" unless
917
+ ir.include? n
918
+ n
919
+ #--}}}
920
+ end
921
+ export 'rangemod'
922
+ #
923
+ # explode a
924
+ #
925
+ def hms s
926
+ #--{{{
927
+ h, s = s.divmod 3600
928
+ m, s = s.divmod 60
929
+ [h.to_i, m.to_i, s]
930
+ #--}}}
931
+ end
932
+ export 'hms'
933
+ #
934
+ # expand variables in a string destructively (to the string)
935
+ #
936
+ def expand! string, vars = {}
937
+ #--{{{
938
+ loop do
939
+ changed = false
940
+ vars.each do |var, value|
941
+ var.gsub! %r/[^a-zA-Z0-9_]/, ''
942
+ [
943
+ %r/\$#{ var }\b/,
944
+ %r/\@#{ var }\b/,
945
+ %r/\${\s*#{ var }\s*}/,
946
+ %r/\@{\s*#{ var }\s*}/
947
+ ].each do |pat|
948
+ changed = string.gsub! pat, "#{ value }"
949
+ end
950
+ end
951
+ break unless changed
952
+ end
953
+ string
954
+ #--}}}
955
+ end
956
+ export 'expand!'
957
+ def expand string, opts = {}
958
+ #--{{{
959
+ expand! string.dup, opts
960
+ #--}}}
961
+ end
962
+ export 'expand'
963
+ #
964
+ # determine path of current ruby interpreter (argv[0] in c)
965
+ #
966
+ def which_ruby
967
+ #--{{{
968
+ c = ::Config::CONFIG
969
+ File::join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
970
+ #--}}}
971
+ end
972
+ export 'which_ruby'
973
+ #
974
+ # declare multi-dimensional arrays as in md[2,3,4]
975
+ #
976
+ def md
977
+ #--{{{
978
+ @__md__ ||=
979
+ lambda{|*ds| Array::new(ds.shift||0).map{md[*ds] unless ds.empty?}}
980
+ #--}}}
981
+ end
982
+ export 'md'
983
+ #
984
+ # find natural left margin of a here-doc and un-indent it
985
+ #
986
+ def unindent! s
987
+ #--{{{
988
+ indent = nil
989
+ s.each do |line|
990
+ next if line =~ %r/^\s*$/
991
+ indent = line[%r/^\s*/] and break
992
+ end
993
+ s.gsub! %r/^#{ indent }/, "" if indent
994
+ indent ? s : nil
995
+ #--}}}
996
+ end
997
+ export 'unindent!'
998
+
999
+ def unindent s
1000
+ #--{{{
1001
+ s = "#{ s }"
1002
+ unindent! s
1003
+ s
1004
+ #--}}}
1005
+ end
1006
+ export 'unindent'
1007
+
1008
+ def parse_timespec spec
1009
+ #--{{{
1010
+ ret = nil
1011
+ pat = %r/(\d+(?:\.\d+)?)\s*([sSmMhHdD][^\d]*)?/
1012
+ begin
1013
+ "#{ spec }".scan(pat) do |m|
1014
+ n = Float m[0]
1015
+ unit = m[1]
1016
+ if unit
1017
+ factor =
1018
+ case unit
1019
+ when %r/^m/i
1020
+ 60
1021
+ when %r/^h/i
1022
+ 60 * 60
1023
+ when %r/^d/i
1024
+ 60 * 60 * 24
1025
+ else
1026
+ 1
1027
+ end
1028
+ n *= factor
1029
+ end
1030
+ ret ||= 0.0
1031
+ ret += n
1032
+ end
1033
+ rescue
1034
+ raise "bad time spec <#{ spec }>"
1035
+ end
1036
+ ret
1037
+ #--}}}
1038
+ end
1039
+ export 'parse_timespec'
1040
+
1041
+ def snake_case string
1042
+ #--{{{
1043
+ return string unless string =~ %r/[A-Z]/
1044
+ string.reverse.scan(%r/[A-Z]+|[^A-Z]*[A-Z]+?/).reverse.map{|word| word.reverse.downcase}.join '_'
1045
+ #--}}}
1046
+ end
1047
+ export 'snake_case'
1048
+
1049
+ def camel_case string
1050
+ #--{{{
1051
+ return string if string =~ %r/[A-Z]/ and string !~ %r/_/
1052
+ words = string.strip.split %r/\s*_+\s*/
1053
+ words.map!{|w| w.downcase.sub(%r/^./){|c| c.upcase}}
1054
+ words.join
1055
+ #--}}}
1056
+ end
1057
+ export 'camel_case'
1058
+
1059
+ def atomic_copy src, dst
1060
+ #--{{{
1061
+ f, fu = File, FileUtils
1062
+ src_dirname, src_basename = f.split src
1063
+
1064
+ dst = f.join dst, src_basename if test ?d, dst
1065
+
1066
+ dst_dirname, dst_basename = f.split dst
1067
+
1068
+ tmp = f.join dst_dirname, ".#{ src_basename }.#{ hostname }.tmp"
1069
+
1070
+ begin
1071
+ fu.cp_r src, tmp, :preserve => true
1072
+ begin
1073
+ fu.mv tmp, dst
1074
+ rescue
1075
+ uncache tmp rescue nil
1076
+ uncache dst rescue nil
1077
+ sleep 42
1078
+ fu.mv tmp, dst
1079
+ end
1080
+ ensure
1081
+ fu.rm_f tmp
1082
+ end
1083
+
1084
+ dst
1085
+ #--}}}
1086
+ end
1087
+ export 'atomic_copy'
1088
+
1089
+ def child_object
1090
+ #--{{{
1091
+ r, w = IO.pipe
1092
+ IO.popen('-') do |pipe|
1093
+ if pipe
1094
+ w.close
1095
+ buf = pipe.read
1096
+ Process.wait
1097
+ unless $? == 0
1098
+ loaded = Marshal.load r.read
1099
+ case loaded
1100
+ when Exception
1101
+ raise loaded
1102
+ else
1103
+ klass, message, backtrace = loaded
1104
+ e = klass.new message
1105
+ e.set_backtrace backtrace
1106
+ raise e
1107
+ end
1108
+ end
1109
+ Marshal.load(buf)
1110
+ else
1111
+ r.close
1112
+ begin
1113
+ print(Marshal.dump(yield))
1114
+ rescue Exception => e
1115
+ dumped =
1116
+ begin
1117
+ Marshal.dump e # we can't do this in ruby 1.8.1
1118
+ rescue
1119
+ Marshal.dump [e.class, e.message, e.backtrace]
1120
+ end
1121
+ w.write dumped
1122
+ exit! 42
1123
+ end
1124
+ end
1125
+ end
1126
+ ensure
1127
+ r.close rescue nil
1128
+ #--}}}
1129
+ end
1130
+ export 'child_object'
1131
+
1132
+ def curl uri, opts = {}
1133
+ #--{{{
1134
+ retries = begin;(getopt 'retries', opts, 42).to_i;rescue NoMethodError;0;end
1135
+ wait = Float(getopt('wait', opts, 2))
1136
+ begin
1137
+ uri = URI === uri ? uri : URI::parse(uri.to_s)
1138
+ req = Net::HTTP::Get.new(uri.path || 'index.html')
1139
+ res = Net::HTTP::start(uri.host, uri.port){|http| http.request(req)}
1140
+ res.body
1141
+ rescue Timeout::Error, Errno::ECONNREFUSED => e
1142
+ if retries > 0
1143
+ retries -= 1
1144
+ #Kernel::warn(errmsg(e))
1145
+ sleep wait
1146
+ retry
1147
+ else
1148
+ raise
1149
+ end
1150
+ end
1151
+ #--}}}
1152
+ end
1153
+ export 'curl'
1154
+
1155
+ #
1156
+ # import open4 methods
1157
+ #
1158
+ %w(spawn open4 bg background).each do |m|
1159
+ module_eval <<-code
1160
+ def #{ m }(*a, &b)
1161
+ ::Open4::#{ m }(*a, &b)
1162
+ end
1163
+ export '#{ m }'
1164
+ code
1165
+ end
1166
+ #--}}}
1167
+ end # module Util