rbbt-util 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/bin/tchash.rb +15 -0
- data/bin/tsv.rb +14 -0
- data/lib/rbbt/util/cachehelper.rb +100 -0
- data/lib/rbbt/util/cmd.rb +140 -0
- data/lib/rbbt/util/data_module.rb +81 -0
- data/lib/rbbt/util/excel2tsv.rb +32 -0
- data/lib/rbbt/util/filecache.rb +58 -0
- data/lib/rbbt/util/log.rb +50 -0
- data/lib/rbbt/util/misc.rb +158 -0
- data/lib/rbbt/util/open.rb +200 -0
- data/lib/rbbt/util/pkg_config.rb +78 -0
- data/lib/rbbt/util/pkg_data.rb +110 -0
- data/lib/rbbt/util/pkg_software.rb +130 -0
- data/lib/rbbt/util/simpleDSL.rb +92 -0
- data/lib/rbbt/util/simpleopt.rb +56 -0
- data/lib/rbbt/util/tc_hash.rb +124 -0
- data/lib/rbbt/util/tmpfile.rb +42 -0
- data/lib/rbbt/util/tsv.rb +804 -0
- data/lib/rbbt-util.rb +13 -0
- data/lib/rbbt.rb +15 -0
- data/share/install/software/lib/install_helpers +257 -0
- data/test/rbbt/util/test_cmd.rb +30 -0
- data/test/rbbt/util/test_data_module.rb +45 -0
- data/test/rbbt/util/test_excel2tsv.rb +10 -0
- data/test/rbbt/util/test_filecache.rb +36 -0
- data/test/rbbt/util/test_misc.rb +22 -0
- data/test/rbbt/util/test_open.rb +89 -0
- data/test/rbbt/util/test_simpleDSL.rb +55 -0
- data/test/rbbt/util/test_simpleopt.rb +10 -0
- data/test/rbbt/util/test_tc_hash.rb +18 -0
- data/test/rbbt/util/test_tmpfile.rb +20 -0
- data/test/rbbt/util/test_tsv.rb +652 -0
- data/test/test_helper.rb +9 -0
- data/test/test_pkg.rb +38 -0
- data/test/test_rbbt.rb +90 -0
- metadata +185 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010-2011 Miguel Vázquez García
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/tchash.rb
ADDED
data/bin/tsv.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rbbt/util/simpleopt'
|
4
|
+
|
5
|
+
options = SOPT.parse "-h--help:-to--tsv-options*:-p--persistence"
|
6
|
+
|
7
|
+
command = ARGV.shift
|
8
|
+
file = ARGV.shift
|
9
|
+
|
10
|
+
case command
|
11
|
+
when 'cat'
|
12
|
+
puts TSV.new(file, options["tsv-options"].merge(options["persistence"]))
|
13
|
+
when '
|
14
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module CacheHelper
|
4
|
+
CACHE_DIR = '/tmp/cachehelper'
|
5
|
+
FileUtils.mkdir_p(CACHE_DIR) unless File.exist?(CACHE_DIR)
|
6
|
+
|
7
|
+
LOG_TIME = false
|
8
|
+
class CacheLocked < Exception; end
|
9
|
+
|
10
|
+
def self.time(id)
|
11
|
+
t = Time.now
|
12
|
+
data = block.call
|
13
|
+
STDERR.puts "#{ id } time: #{Time.now - t}"
|
14
|
+
data
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.cachedir=(dir)
|
18
|
+
@@cachedir=dir
|
19
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.cachedir
|
23
|
+
@@cachedir ||= CACHE_DIR
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def self.reset
|
28
|
+
FileUtils.rm Dir.glob(cachedir + '*')
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.reset_locks
|
32
|
+
FileUtils.rm Dir.glob(cachedir + '*.lock')
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def self.build_filename(name, key)
|
37
|
+
File.join(cachedir, name + ": " + Digest::MD5.hexdigest(key.to_s))
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.do(filename, block)
|
41
|
+
FileUtils.touch(filename + '.lock')
|
42
|
+
|
43
|
+
if LOG_TIME
|
44
|
+
data = time do
|
45
|
+
block.call
|
46
|
+
end
|
47
|
+
else
|
48
|
+
data = block.call
|
49
|
+
end
|
50
|
+
|
51
|
+
File.open(filename, 'w'){|f| f.write data}
|
52
|
+
FileUtils.rm(filename + '.lock')
|
53
|
+
return data
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.clean(name)
|
57
|
+
FileUtils.rm Dir.glob(File.join(cachedir, "#{ name }*"))
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.cache_ready?(name, key)
|
61
|
+
filename = CacheHelper.build_filename(name, key)
|
62
|
+
File.exist?(filename)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.cache(name, key = [], wait = nil, &block)
|
66
|
+
filename = CacheHelper.build_filename(name, key)
|
67
|
+
begin
|
68
|
+
case
|
69
|
+
when File.exist?(filename)
|
70
|
+
return File.open(filename){|f| f.read}
|
71
|
+
when File.exist?(filename + '.lock')
|
72
|
+
raise CacheLocked
|
73
|
+
else
|
74
|
+
if wait.nil?
|
75
|
+
CacheHelper.do(filename, block)
|
76
|
+
else
|
77
|
+
Thread.new{CacheHelper.do(filename, block)}
|
78
|
+
return wait
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
rescue CacheLocked
|
83
|
+
if wait.nil?
|
84
|
+
sleep 30
|
85
|
+
retry
|
86
|
+
else
|
87
|
+
return wait
|
88
|
+
end
|
89
|
+
rescue Exception
|
90
|
+
FileUtils.rm(filename + '.lock') if File.exist?(filename + '.lock')
|
91
|
+
raise $!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.marshal_cache(name, key = [])
|
96
|
+
Marshal::load( cache(name, key) do
|
97
|
+
Marshal::dump(yield)
|
98
|
+
end)
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'rbbt/util/misc'
|
2
|
+
require 'rbbt/util/log'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module CMD
|
6
|
+
class CMDError < StandardError;end
|
7
|
+
|
8
|
+
module SmartIO
|
9
|
+
def self.tie(io, pid = nil, post = nil)
|
10
|
+
io.instance_eval{
|
11
|
+
@pid = pid
|
12
|
+
@post = post
|
13
|
+
alias original_close close
|
14
|
+
def close
|
15
|
+
begin
|
16
|
+
Process.waitpid(@pid, Process::WNOHANG) if @pid
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
|
20
|
+
@post.call if @post
|
21
|
+
original_close
|
22
|
+
end
|
23
|
+
|
24
|
+
alias original_read read
|
25
|
+
def read
|
26
|
+
data = Misc.fixutf8(original_read)
|
27
|
+
self.close unless self.closed?
|
28
|
+
data
|
29
|
+
end
|
30
|
+
}
|
31
|
+
io
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.process_cmd_options(options = {})
|
36
|
+
string = ""
|
37
|
+
options.each do |option, value|
|
38
|
+
case
|
39
|
+
when value.nil? || FalseClass === value
|
40
|
+
next
|
41
|
+
when TrueClass === value
|
42
|
+
string << "#{option} "
|
43
|
+
else
|
44
|
+
if option.chars.to_a.last == "="
|
45
|
+
string << "#{option}#{value} "
|
46
|
+
else
|
47
|
+
string << "#{option} #{value} "
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
string.strip
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.cmd(cmd, options = {}, &block)
|
56
|
+
options = Misc.add_defaults options, :stderr => Log::DEBUG
|
57
|
+
in_content = options.delete(:in)
|
58
|
+
stderr = options.delete(:stderr)
|
59
|
+
pipe = options.delete(:pipe)
|
60
|
+
post = options.delete(:post)
|
61
|
+
|
62
|
+
if stderr == true
|
63
|
+
stderr = Log::HIGH
|
64
|
+
end
|
65
|
+
|
66
|
+
# Process cmd_options
|
67
|
+
cmd_options = process_cmd_options options
|
68
|
+
if cmd =~ /'\{opt\}'/
|
69
|
+
cmd.sub!('\'{opt}\'', cmd_options)
|
70
|
+
else
|
71
|
+
cmd << " " << cmd_options
|
72
|
+
end
|
73
|
+
|
74
|
+
sout, serr = IO.pipe, IO.pipe
|
75
|
+
|
76
|
+
case
|
77
|
+
when (IO === in_content and not StringIO === in_content)
|
78
|
+
sin = [in_content, nil]
|
79
|
+
else StringIO === in_content
|
80
|
+
sin = IO.pipe
|
81
|
+
end
|
82
|
+
|
83
|
+
pid = fork {
|
84
|
+
begin
|
85
|
+
|
86
|
+
sin.last.close if sin.last
|
87
|
+
STDIN.reopen sin.first
|
88
|
+
sin.first.close
|
89
|
+
|
90
|
+
|
91
|
+
serr.first.close
|
92
|
+
STDERR.reopen serr.last
|
93
|
+
serr.last.close
|
94
|
+
|
95
|
+
sout.first.close
|
96
|
+
STDOUT.reopen sout.last
|
97
|
+
sout.last.close
|
98
|
+
|
99
|
+
STDOUT.sync = STDERR.sync = true
|
100
|
+
exec(cmd)
|
101
|
+
rescue Exception
|
102
|
+
raise CMDError, $!.message
|
103
|
+
end
|
104
|
+
|
105
|
+
}
|
106
|
+
sin.first.close
|
107
|
+
sout.last.close
|
108
|
+
serr.last.close
|
109
|
+
|
110
|
+
case
|
111
|
+
when String === in_content
|
112
|
+
sin.last.write in_content
|
113
|
+
sin.last.close
|
114
|
+
when StringIO === in_content
|
115
|
+
Thread.new do
|
116
|
+
while l = in_content.gets
|
117
|
+
sin.last.write l
|
118
|
+
end
|
119
|
+
sin.last.close
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Thread.new do
|
124
|
+
while l = serr.first.gets
|
125
|
+
Log.log l, stderr if Integer === stderr
|
126
|
+
end
|
127
|
+
serr.first.close
|
128
|
+
end
|
129
|
+
|
130
|
+
if pipe
|
131
|
+
SmartIO.tie sout.first, pid, post
|
132
|
+
sout.first
|
133
|
+
else
|
134
|
+
out = StringIO.new sout.first.read
|
135
|
+
SmartIO.tie out
|
136
|
+
Process.waitpid pid
|
137
|
+
out
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module DataModule
|
2
|
+
|
3
|
+
def self.extended(base)
|
4
|
+
if defined? base::PKG and base::PKG
|
5
|
+
base.pkg_module = base::PKG
|
6
|
+
else
|
7
|
+
base.pkg_module = Rbbt
|
8
|
+
end
|
9
|
+
|
10
|
+
base.sharedir = PKGData.get_caller_sharedir
|
11
|
+
end
|
12
|
+
|
13
|
+
def pkg_module
|
14
|
+
@pkg_module
|
15
|
+
end
|
16
|
+
|
17
|
+
def pkg_module=(pkg_module)
|
18
|
+
@pkg_module = pkg_module
|
19
|
+
end
|
20
|
+
|
21
|
+
def sharedir
|
22
|
+
@sharedir
|
23
|
+
end
|
24
|
+
|
25
|
+
def sharedir=(sharedir)
|
26
|
+
@sharedir = sharedir
|
27
|
+
end
|
28
|
+
|
29
|
+
alias old_method_missing method_missing
|
30
|
+
def method_missing(name, *args, &block)
|
31
|
+
if args.any?
|
32
|
+
filename = File.join(self.to_s, args.first, name.to_s)
|
33
|
+
else
|
34
|
+
filename = File.join(self.to_s, name.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
pkg_module.add_datafiles filename => ['', self.to_s, sharedir]
|
39
|
+
rescue
|
40
|
+
Log.debug $!.message
|
41
|
+
old_method_missing name, *args, &block
|
42
|
+
end
|
43
|
+
|
44
|
+
pkg_module.find_datafile filename
|
45
|
+
end
|
46
|
+
|
47
|
+
module WithKey
|
48
|
+
def klass=(klass)
|
49
|
+
@klass = klass
|
50
|
+
end
|
51
|
+
|
52
|
+
def klass
|
53
|
+
@klass
|
54
|
+
end
|
55
|
+
|
56
|
+
def key=(key)
|
57
|
+
@key = key
|
58
|
+
end
|
59
|
+
|
60
|
+
def key
|
61
|
+
@key
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(name, *args)
|
65
|
+
if key
|
66
|
+
klass.send(name, key, *args)
|
67
|
+
else
|
68
|
+
klass.send(name, *args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_key(key)
|
74
|
+
klass = self
|
75
|
+
o = Object.new
|
76
|
+
o.extend WithKey
|
77
|
+
o.klass = self
|
78
|
+
o.key = key
|
79
|
+
o
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rbbt/util/tsv'
|
2
|
+
require 'spreadsheet'
|
3
|
+
|
4
|
+
class TSV
|
5
|
+
def self.excel2tsv(file, options = {})
|
6
|
+
sheet = options.delete :sheet
|
7
|
+
header = options.delete :header
|
8
|
+
header = true unless header == false
|
9
|
+
sheet ||= 0
|
10
|
+
TmpFile.with_file do |filename|
|
11
|
+
workbook = Spreadsheet.open File.open(file)
|
12
|
+
sheet = workbook.worksheet sheet
|
13
|
+
|
14
|
+
rows = []
|
15
|
+
|
16
|
+
sheet.each do |row|
|
17
|
+
rows << row.values_at(0..(row.size - 1))
|
18
|
+
end
|
19
|
+
|
20
|
+
File.open(filename, 'w') do |f|
|
21
|
+
if header
|
22
|
+
header = rows.shift
|
23
|
+
f.puts "#" + header * "\t"
|
24
|
+
end
|
25
|
+
|
26
|
+
rows.each do |row| f.puts row * "\t" end
|
27
|
+
end
|
28
|
+
|
29
|
+
TSV.new(filename, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rbbt/util/misc'
|
3
|
+
|
4
|
+
# Provides caching functionality for files downloaded from the internet
|
5
|
+
module FileCache
|
6
|
+
CACHEDIR = "/tmp/rbbt_cache"
|
7
|
+
FileUtils.mkdir CACHEDIR unless File.exist? CACHEDIR
|
8
|
+
|
9
|
+
def self.cachedir=(cachedir)
|
10
|
+
CACHEDIR.replace cachedir
|
11
|
+
FileUtils.mkdir_p CACHEDIR unless File.exist? CACHEDIR
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cachedir
|
15
|
+
CACHEDIR
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.path(filename)
|
19
|
+
filename = File.basename filename
|
20
|
+
|
21
|
+
filename.match(/(.+)\.(.+)/)
|
22
|
+
|
23
|
+
base = filename.sub(/\..+/,'')
|
24
|
+
dirs = base.scan(/./).values_at(0,1,2,3,4).compact.reverse
|
25
|
+
|
26
|
+
File.join(File.join(CACHEDIR, *dirs), filename)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add(filename, content)
|
30
|
+
path = path(filename)
|
31
|
+
|
32
|
+
FileUtils.makedirs(File.dirname(path), :mode => 0777)
|
33
|
+
|
34
|
+
Misc.sensiblewrite(path, content)
|
35
|
+
|
36
|
+
FileUtils.chmod 0666, path
|
37
|
+
|
38
|
+
path
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.found(filename)
|
42
|
+
File.exists? FileCache.path(filename)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.get(filename)
|
46
|
+
path = path(filename)
|
47
|
+
|
48
|
+
return nil if ! File.exists? path
|
49
|
+
|
50
|
+
File.open(path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.del(filename)
|
54
|
+
path = path(filename)
|
55
|
+
|
56
|
+
FileUtils.rm path if File.exist? path
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Log
|
2
|
+
|
3
|
+
DEBUG = 0
|
4
|
+
LOW = 1
|
5
|
+
MEDIUM = 2
|
6
|
+
HIGH = 3
|
7
|
+
|
8
|
+
def severity=(severity)
|
9
|
+
@@severity = severity
|
10
|
+
end
|
11
|
+
|
12
|
+
def severity
|
13
|
+
@@severity
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.log(message, severity = MEDIUM)
|
17
|
+
STDERR.puts "#{Time.now}[#{severity.to_s}]: " + message if severity >= @@severity
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.debug(message)
|
21
|
+
log(message, DEBUG)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.low(message)
|
25
|
+
log(message, LOW)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.medium(message)
|
29
|
+
log(message, MEDIUM)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.high(message)
|
33
|
+
log(message, HIGH)
|
34
|
+
end
|
35
|
+
|
36
|
+
case ENV['RBBT_LOG']
|
37
|
+
when 'DEBUG'
|
38
|
+
@@severity = DEBUG
|
39
|
+
when 'LOW'
|
40
|
+
@@severity = LOW
|
41
|
+
when 'MEDIUM'
|
42
|
+
@@severity = MEDIUM
|
43
|
+
when 'HIGH'
|
44
|
+
@@severity = HIGH
|
45
|
+
when nil
|
46
|
+
@@severity = HIGH
|
47
|
+
else
|
48
|
+
@@severity = ENV['RBBT_LOG'].to_i
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'iconv'
|
2
|
+
module Misc
|
3
|
+
class FieldNotFoundError < StandardError;end
|
4
|
+
|
5
|
+
def self.env_add(var, value, sep = ":", prepend = true)
|
6
|
+
ENV[var] ||= ""
|
7
|
+
return if ENV[var] =~ /(#{sep}|^)#{Regexp.quote value}(#{sep}|$)/
|
8
|
+
if prepend
|
9
|
+
ENV[var] = value + sep + ENV[var]
|
10
|
+
else
|
11
|
+
ENV[var] += sep + ENV[var]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.count(list)
|
16
|
+
counts = Hash.new 0
|
17
|
+
list.each do |item|
|
18
|
+
counts[item] += 1
|
19
|
+
end
|
20
|
+
|
21
|
+
counts
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.profile
|
25
|
+
require 'ruby-prof'
|
26
|
+
RubyProf.start
|
27
|
+
begin
|
28
|
+
res = yield
|
29
|
+
rescue Exception
|
30
|
+
puts "Profiling aborted"
|
31
|
+
raise $!
|
32
|
+
ensure
|
33
|
+
result = RubyProf.stop
|
34
|
+
printer = RubyProf::FlatPrinter.new(result)
|
35
|
+
printer.print(STDOUT, 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.fixutf8(string)
|
42
|
+
if string.respond_to?(:valid_encoding?) and ! string.valid_encoding?
|
43
|
+
@@ic ||= Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
44
|
+
@@ic.iconv(string)
|
45
|
+
else
|
46
|
+
string
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.add_defaults(options, defaults = {})
|
51
|
+
case
|
52
|
+
when Hash === options
|
53
|
+
new_options = options.dup
|
54
|
+
when String === options
|
55
|
+
new_options = string2hash options
|
56
|
+
else
|
57
|
+
raise "Format of '#{options.inspect}' not understood"
|
58
|
+
end
|
59
|
+
defaults.each do |key, value|
|
60
|
+
new_options[key] = value if new_options[key].nil?
|
61
|
+
end
|
62
|
+
new_options
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.string2hash(string)
|
66
|
+
|
67
|
+
options = {}
|
68
|
+
string.split(/#/).each do |str|
|
69
|
+
if str.match(/(.*)=(.*)/)
|
70
|
+
option, value = $1, $2
|
71
|
+
else
|
72
|
+
option, value = str, true
|
73
|
+
end
|
74
|
+
|
75
|
+
option = option.sub(":",'').to_sym if option.chars.first == ':'
|
76
|
+
|
77
|
+
|
78
|
+
if value == true
|
79
|
+
options[option] = option.to_s.chars.first != '!'
|
80
|
+
else
|
81
|
+
options[option] = begin eval(value) rescue value end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
options
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.sensiblewrite(path, content)
|
89
|
+
begin
|
90
|
+
case
|
91
|
+
when String === content
|
92
|
+
File.open(path, 'w') do |f| f.write content end
|
93
|
+
when (IO === content or StringIO === content)
|
94
|
+
File.open(path, 'w') do |f| while l = content.gets; f.write l; end end
|
95
|
+
else
|
96
|
+
File.open(path, 'w') do |f| end
|
97
|
+
end
|
98
|
+
rescue Interrupt
|
99
|
+
raise "Interrupted (Ctrl-c)"
|
100
|
+
rescue Exception
|
101
|
+
FileUtils.rm_f path
|
102
|
+
raise $!
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.field_position(fields, field, quiet = false)
|
107
|
+
return field if Integer === field or Range === field
|
108
|
+
raise FieldNotFoundError, "Field information missing" if fields.nil? && ! quiet
|
109
|
+
fields.each_with_index{|f,i| return i if f == field}
|
110
|
+
field_re = Regexp.new /#{field}/i
|
111
|
+
fields.each_with_index{|f,i| return i if f =~ field_re}
|
112
|
+
raise FieldNotFoundError, "Field '#{ field }' was not found" unless quiet
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class NamedArray < Array
|
117
|
+
attr_accessor :fields
|
118
|
+
|
119
|
+
def self.name(array, fields)
|
120
|
+
a = self.new(array)
|
121
|
+
a.fields = fields
|
122
|
+
a
|
123
|
+
end
|
124
|
+
|
125
|
+
def positions(fields)
|
126
|
+
fields.collect{|field|
|
127
|
+
Misc.field_position(@fields, field)
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
alias original_get_brackets []
|
132
|
+
def [](key)
|
133
|
+
original_get_brackets(Misc.field_position(fields, key))
|
134
|
+
end
|
135
|
+
|
136
|
+
alias original_set_brackets []=
|
137
|
+
def []=(key,value)
|
138
|
+
original_set_brackets(Misc.field_position(fields, key), value)
|
139
|
+
end
|
140
|
+
|
141
|
+
alias original_values_at values_at
|
142
|
+
def values_at(*keys)
|
143
|
+
keys = keys.collect{|k| Misc.field_position(fields, k) }
|
144
|
+
original_values_at(*keys)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def profile
|
150
|
+
require 'ruby-prof'
|
151
|
+
RubyProf.start
|
152
|
+
yield
|
153
|
+
result = RubyProf.stop
|
154
|
+
|
155
|
+
# Print a flat profile to text
|
156
|
+
printer = RubyProf::FlatPrinter.new(result)
|
157
|
+
printer.print(STDOUT, 0)
|
158
|
+
end
|