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.
- data/a.rb +1 -0
- data/alib-0.4.0.gem +0 -0
- data/b.rb +1 -0
- data/build +0 -0
- data/gemspec.rb +23 -0
- data/install +143 -0
- data/install.rb +143 -0
- data/lib/alib-0.4.0.rb +104 -0
- data/lib/alib-0.4.0/autohash.rb +11 -0
- data/lib/alib-0.4.0/bsearch.rb +255 -0
- data/lib/alib-0.4.0/configfile.rb +113 -0
- data/lib/alib-0.4.0/find2.rb +294 -0
- data/lib/alib-0.4.0/listfile.rb +42 -0
- data/lib/alib-0.4.0/logging.rb +152 -0
- data/lib/alib-0.4.0/main.rb +594 -0
- data/lib/alib-0.4.0/open4.rb +175 -0
- data/lib/alib-0.4.0/orderedautohash.rb +23 -0
- data/lib/alib-0.4.0/orderedhash.rb +243 -0
- data/lib/alib-0.4.0/util.rb +1167 -0
- data/lib/alib.rb +63 -3831
- metadata +23 -3
- data/lib/alib-0.3.1.rb +0 -3872
@@ -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
|