alib 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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