scout-essentials 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.vimproject +78 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +18 -0
  7. data/Rakefile +47 -0
  8. data/VERSION +1 -0
  9. data/lib/scout/cmd.rb +348 -0
  10. data/lib/scout/concurrent_stream.rb +284 -0
  11. data/lib/scout/config.rb +168 -0
  12. data/lib/scout/exceptions.rb +77 -0
  13. data/lib/scout/indiferent_hash/case_insensitive.rb +30 -0
  14. data/lib/scout/indiferent_hash/options.rb +115 -0
  15. data/lib/scout/indiferent_hash.rb +96 -0
  16. data/lib/scout/log/color.rb +224 -0
  17. data/lib/scout/log/color_class.rb +269 -0
  18. data/lib/scout/log/fingerprint.rb +69 -0
  19. data/lib/scout/log/progress/report.rb +244 -0
  20. data/lib/scout/log/progress/util.rb +173 -0
  21. data/lib/scout/log/progress.rb +106 -0
  22. data/lib/scout/log/trap.rb +107 -0
  23. data/lib/scout/log.rb +441 -0
  24. data/lib/scout/meta_extension.rb +100 -0
  25. data/lib/scout/misc/digest.rb +63 -0
  26. data/lib/scout/misc/filesystem.rb +25 -0
  27. data/lib/scout/misc/format.rb +255 -0
  28. data/lib/scout/misc/helper.rb +31 -0
  29. data/lib/scout/misc/insist.rb +56 -0
  30. data/lib/scout/misc/monitor.rb +66 -0
  31. data/lib/scout/misc/system.rb +73 -0
  32. data/lib/scout/misc.rb +10 -0
  33. data/lib/scout/named_array.rb +138 -0
  34. data/lib/scout/open/lock/lockfile.rb +587 -0
  35. data/lib/scout/open/lock.rb +68 -0
  36. data/lib/scout/open/remote.rb +135 -0
  37. data/lib/scout/open/stream.rb +491 -0
  38. data/lib/scout/open/util.rb +244 -0
  39. data/lib/scout/open.rb +170 -0
  40. data/lib/scout/path/find.rb +204 -0
  41. data/lib/scout/path/tmpfile.rb +8 -0
  42. data/lib/scout/path/util.rb +127 -0
  43. data/lib/scout/path.rb +51 -0
  44. data/lib/scout/persist/open.rb +17 -0
  45. data/lib/scout/persist/path.rb +15 -0
  46. data/lib/scout/persist/serialize.rb +157 -0
  47. data/lib/scout/persist.rb +104 -0
  48. data/lib/scout/resource/open.rb +8 -0
  49. data/lib/scout/resource/path.rb +80 -0
  50. data/lib/scout/resource/produce/rake.rb +69 -0
  51. data/lib/scout/resource/produce.rb +151 -0
  52. data/lib/scout/resource/scout.rb +3 -0
  53. data/lib/scout/resource/software.rb +178 -0
  54. data/lib/scout/resource/util.rb +59 -0
  55. data/lib/scout/resource.rb +40 -0
  56. data/lib/scout/simple_opt/accessor.rb +54 -0
  57. data/lib/scout/simple_opt/doc.rb +126 -0
  58. data/lib/scout/simple_opt/get.rb +57 -0
  59. data/lib/scout/simple_opt/parse.rb +67 -0
  60. data/lib/scout/simple_opt/setup.rb +26 -0
  61. data/lib/scout/simple_opt.rb +5 -0
  62. data/lib/scout/tmpfile.rb +129 -0
  63. data/lib/scout-essentials.rb +10 -0
  64. data/scout-essentials.gemspec +143 -0
  65. data/share/color/color_names +507 -0
  66. data/share/color/diverging_colors.hex +12 -0
  67. data/share/software/install_helpers +523 -0
  68. data/test/scout/indiferent_hash/test_case_insensitive.rb +16 -0
  69. data/test/scout/indiferent_hash/test_options.rb +46 -0
  70. data/test/scout/log/test_color.rb +0 -0
  71. data/test/scout/log/test_progress.rb +108 -0
  72. data/test/scout/misc/test_digest.rb +30 -0
  73. data/test/scout/misc/test_filesystem.rb +30 -0
  74. data/test/scout/misc/test_insist.rb +13 -0
  75. data/test/scout/misc/test_system.rb +21 -0
  76. data/test/scout/open/test_lock.rb +52 -0
  77. data/test/scout/open/test_remote.rb +25 -0
  78. data/test/scout/open/test_stream.rb +676 -0
  79. data/test/scout/open/test_util.rb +73 -0
  80. data/test/scout/path/test_find.rb +110 -0
  81. data/test/scout/path/test_util.rb +22 -0
  82. data/test/scout/persist/test_open.rb +37 -0
  83. data/test/scout/persist/test_path.rb +37 -0
  84. data/test/scout/persist/test_serialize.rb +114 -0
  85. data/test/scout/resource/test_path.rb +58 -0
  86. data/test/scout/resource/test_produce.rb +94 -0
  87. data/test/scout/resource/test_software.rb +24 -0
  88. data/test/scout/resource/test_util.rb +38 -0
  89. data/test/scout/simple_opt/test_doc.rb +16 -0
  90. data/test/scout/simple_opt/test_get.rb +11 -0
  91. data/test/scout/simple_opt/test_parse.rb +10 -0
  92. data/test/scout/simple_opt/test_setup.rb +77 -0
  93. data/test/scout/test_cmd.rb +85 -0
  94. data/test/scout/test_concurrent_stream.rb +29 -0
  95. data/test/scout/test_config.rb +66 -0
  96. data/test/scout/test_indiferent_hash.rb +26 -0
  97. data/test/scout/test_log.rb +32 -0
  98. data/test/scout/test_meta_extension.rb +80 -0
  99. data/test/scout/test_misc.rb +6 -0
  100. data/test/scout/test_named_array.rb +43 -0
  101. data/test/scout/test_open.rb +146 -0
  102. data/test/scout/test_path.rb +54 -0
  103. data/test/scout/test_persist.rb +186 -0
  104. data/test/scout/test_resource.rb +26 -0
  105. data/test/scout/test_tmpfile.rb +53 -0
  106. data/test/test_helper.rb +50 -0
  107. metadata +247 -0
@@ -0,0 +1,255 @@
1
+ module Misc
2
+ COLOR_LIST = %w(#BC80BD #CCEBC5 #FFED6F #8DD3C7 #FFFFB3 #BEBADA #FB8072 #80B1D3 #FDB462 #B3DE69 #FCCDE5 #D9D9D9)
3
+
4
+ def self.colors_for(list)
5
+ unused = COLOR_LIST.dup
6
+
7
+ used = {}
8
+ colors = list.collect do |elem|
9
+ if used.include? elem
10
+ used[elem]
11
+ else
12
+ color = unused.shift
13
+ used[elem]=color
14
+ color
15
+ end
16
+ end
17
+
18
+ [colors, used]
19
+ end
20
+
21
+ def self.format_seconds(time, extended = false)
22
+ seconds = time.to_i
23
+ str = [seconds/3600, seconds/60 % 60, seconds % 60].map{|t| "%02i" % t }.join(':')
24
+ str << ".%02i" % ((time - seconds) * 100) if extended
25
+ str
26
+ end
27
+
28
+ CHAR_SENCONDS = ENV["SCOUT_NOCOLOR"] == "true" ? "sec" : "″"
29
+ def self.format_seconds_short(time)
30
+ if time < 0.0001
31
+ "%.5g" % time + CHAR_SENCONDS
32
+ elsif time < 60
33
+ "%.2g" % time + CHAR_SENCONDS
34
+ else
35
+ format_seconds(time)
36
+ end
37
+ end
38
+
39
+
40
+
41
+ MAX_TTY_LINE_WIDTH = 100
42
+ def self.format_paragraph(text, size = nil, indent = nil, offset = nil)
43
+ size ||= Log.tty_size || MAX_TTY_LINE_WIDTH
44
+ size = MAX_TTY_LINE_WIDTH if size > MAX_TTY_LINE_WIDTH
45
+ indent ||= 0
46
+ offset ||= 0
47
+
48
+ i = 0
49
+ size = size + offset + indent
50
+ re = /((?:\n\s*\n\s*)|(?:\n\s*(?=\*)))/
51
+ text.split(re).collect do |paragraph|
52
+ i += 1
53
+ str = if i % 2 == 1
54
+ words = paragraph.gsub(/\s+/, "\s").split(" ")
55
+ lines = []
56
+ line = " "*offset
57
+ word = words.shift
58
+ while word
59
+ word = word[0..size-indent-offset-4] + '...' if word.length >= size - indent - offset
60
+ while word and Log.uncolor(line).length + Log.uncolor(word).length <= size - indent
61
+ line << word << " "
62
+ word = words.shift
63
+ end
64
+ offset = 0
65
+ lines << ((" " * indent) << line[0..-2])
66
+ line = ""
67
+ end
68
+ (lines * "\n")
69
+ else
70
+ paragraph
71
+ end
72
+ offset = 0
73
+ str
74
+ end*""
75
+ end
76
+
77
+ def self.format_definition_list_item(dt, dd, indent = nil, size = nil, color = :yellow)
78
+ if size.nil?
79
+ base_size = MAX_TTY_LINE_WIDTH
80
+ base_indent = indent || (base_size / 3)
81
+ size = base_size - base_indent
82
+ end
83
+
84
+ indent ||= base_indent || size / 3
85
+
86
+ dd = "" if dd.nil?
87
+ dt = Log.color color, dt if color
88
+ dt = dt.to_s unless dd.empty?
89
+ len = Log.uncolor(dt).length
90
+
91
+ if indent < 0
92
+ text = format_paragraph(dd, size, indent.abs-1, 0)
93
+ text = dt << "\n" << text
94
+ else
95
+ offset = len - indent
96
+ offset = 0 if offset < 0
97
+ text = format_paragraph(dd, size, indent.abs+1, offset)
98
+ text[0..len-1] = dt
99
+ end
100
+ text
101
+ end
102
+
103
+ def self.format_definition_list(defs, indent = nil, size = nil, color = :yellow, sep = "\n\n")
104
+ indent ||= 30
105
+ size ||= (Log.tty_size || MAX_TTY_LINE_WIDTH) - indent
106
+ entries = []
107
+ defs.each do |dt,dd|
108
+ text = format_definition_list_item(dt,dd,indent, size,color)
109
+ entries << text
110
+ end
111
+ entries * sep
112
+ end
113
+
114
+ def self.camel_case(string)
115
+ return string if string !~ /_/ && string =~ /[A-Z]+.*/
116
+ string.split(/_|(\d+)/).map{|e|
117
+ (e =~ /^[A-Z]{2,}$/ ? e : e.capitalize)
118
+ }.join
119
+ end
120
+
121
+ def self.camel_case_lower(string)
122
+ string.split('_').inject([]){ |buffer,e|
123
+ buffer.push(buffer.empty? ? e.downcase : (e =~ /^[A-Z]{2,}$/ ? e : e.capitalize))
124
+ }.join
125
+ end
126
+
127
+ def self.snake_case(string)
128
+ return nil if string.nil?
129
+ string = string.to_s if Symbol === string
130
+ string.
131
+ gsub(/([A-Z]{2,})([A-Z][a-z])/,'\1_\2').
132
+ gsub(/([a-z])([A-Z])/,'\1_\2').
133
+ gsub(/\s/,'_').gsub(/[^\w]/, '').
134
+ split("_").collect{|p| p.match(/[A-Z]{2,}/) ? p : p.downcase } * "_"
135
+ end
136
+
137
+ # source: https://gist.github.com/ekdevdes/2450285
138
+ # author: Ethan Kramer (https://github.com/ekdevdes)
139
+ def self.humanize(value, options = {})
140
+ if options.empty?
141
+ options[:format] = :sentence
142
+ end
143
+
144
+ values = value.to_s.split('_')
145
+ values.each_index do |index|
146
+ # lower case each item in array
147
+ # Miguel Vazquez edit: Except for acronyms
148
+ values[index].downcase! unless values[index].match(/[a-zA-Z][A-Z]/)
149
+ end
150
+ if options[:format] == :allcaps
151
+ values.each do |value|
152
+ value.capitalize!
153
+ end
154
+
155
+ if options.empty?
156
+ options[:seperator] = " "
157
+ end
158
+
159
+ return values.join " "
160
+ end
161
+
162
+ if options[:format] == :class
163
+ values.each do |value|
164
+ value.capitalize!
165
+ end
166
+
167
+ return values.join ""
168
+ end
169
+
170
+ if options[:format] == :sentence
171
+ values[0].capitalize! unless values[0].match(/[a-zA-Z][A-Z]/)
172
+
173
+ return values.join " "
174
+ end
175
+
176
+ if options[:format] == :nocaps
177
+ return values.join " "
178
+ end
179
+ end
180
+
181
+ def self.fixascii(string)
182
+ if string.respond_to?(:encode)
183
+ self.fixutf8(string).encode("ASCII-8BIT")
184
+ else
185
+ string
186
+ end
187
+ end
188
+
189
+ def self.to_utf8(string)
190
+ string.encode("UTF-16BE", :invalid => :replace, :undef => :replace, :replace => "?").encode('UTF-8')
191
+ end
192
+
193
+ def self.fixutf8(string)
194
+ return nil if string.nil?
195
+ return string if string.respond_to?(:encoding) && string.encoding.to_s == "UTF-8" && (string.respond_to?(:valid_encoding?) && string.valid_encoding?) ||
196
+ (string.respond_to?(:valid_encoding) && string.valid_encoding)
197
+
198
+ if string.respond_to?(:encode)
199
+ string.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
200
+ else
201
+ require 'iconv'
202
+ @@ic ||= Iconv.new('UTF-8//IGNORE', 'UTF-8')
203
+ @@ic.iconv(string)
204
+ end
205
+ end
206
+
207
+ def self.humanize_list(list)
208
+ return "" if list.empty?
209
+ if list.length == 1
210
+ list.first
211
+ else
212
+ list[0..-2].collect{|e| e.to_s} * ", " << " and " << list[-1].to_s
213
+ end
214
+ end
215
+
216
+ def self.parse_sql_values(txt)
217
+ io = StringIO.new txt.strip
218
+
219
+ values = []
220
+ fields = []
221
+ current = nil
222
+ quoted = false
223
+ while c = io.getc
224
+ if quoted
225
+ if c == "'"
226
+ quoted = false
227
+ else
228
+ current << c
229
+ end
230
+ else
231
+ case c
232
+ when "("
233
+ current = ""
234
+ when ")"
235
+ fields << current
236
+ values << fields
237
+ fields = []
238
+ current = nil
239
+ when ','
240
+ if not current.nil?
241
+ fields << current
242
+ current = ""
243
+ end
244
+ when "'"
245
+ quoted = true
246
+ when ";"
247
+ break
248
+ else
249
+ current << c
250
+ end
251
+ end
252
+ end
253
+ values
254
+ end
255
+ end
@@ -0,0 +1,31 @@
1
+ module Misc
2
+ def self.intersect_sorted_arrays(a1, a2)
3
+ e1, e2 = a1.shift, a2.shift
4
+ intersect = []
5
+ while true
6
+ break if e1.nil? or e2.nil?
7
+ case e1 <=> e2
8
+ when 0
9
+ intersect << e1
10
+ e1, e2 = a1.shift, a2.shift
11
+ when -1
12
+ e1 = a1.shift while not e1.nil? and e1 < e2
13
+ when 1
14
+ e2 = a2.shift
15
+ e2 = a2.shift while not e2.nil? and e2 < e1
16
+ end
17
+ end
18
+ intersect
19
+ end
20
+
21
+ def self.counts(array)
22
+ counts = {}
23
+ array.each do |e|
24
+ counts[e] ||= 0
25
+ counts[e] += 1
26
+ end
27
+
28
+ counts
29
+ end
30
+
31
+ end
@@ -0,0 +1,56 @@
1
+ module Misc
2
+ def self.insist(times = 4, sleep = nil, msg = nil)
3
+ sleep_array = nil
4
+
5
+ try = 0
6
+ begin
7
+ begin
8
+ yield
9
+ rescue Exception
10
+ if Array === times
11
+ sleep_array = times
12
+ times = sleep_array.length
13
+ sleep = sleep_array.shift
14
+ end
15
+
16
+ if sleep.nil?
17
+ sleep_array = ([0] + [0.001, 0.01, 0.1, 0.5] * (times / 3)).sort[0..times-1]
18
+ sleep = sleep_array.shift
19
+ end
20
+ raise $!
21
+ end
22
+ rescue TryAgain
23
+ sleep sleep
24
+ retry
25
+ rescue StopInsist
26
+ raise $!.exception
27
+ rescue Aborted, Interrupt
28
+ if msg
29
+ Log.warn("Not Insisting after Aborted: #{$!.message} -- #{msg}")
30
+ else
31
+ Log.warn("Not Insisting after Aborted: #{$!.message}")
32
+ end
33
+ raise $!
34
+ rescue Exception
35
+ Log.exception $! if ENV["SCOUT_LOG_INSIST"] == 'true'
36
+ if msg
37
+ Log.warn("Insisting after exception: #{$!.class} #{$!.message} -- #{msg}")
38
+ elsif FalseClass === msg
39
+ nil
40
+ else
41
+ Log.warn("Insisting after exception: #{$!.class} #{$!.message}")
42
+ end
43
+
44
+ if sleep and try > 0
45
+ sleep sleep
46
+ sleep = sleep_array.shift || sleep if sleep_array
47
+ else
48
+ Thread.pass
49
+ end
50
+
51
+ try += 1
52
+ retry if try < times
53
+ raise $!
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ module Misc
2
+ def self.pid_alive?(pid)
3
+ !! Process.kill(0, pid) rescue false
4
+ end
5
+
6
+ def self.benchmark(repeats = 1, message = nil)
7
+ require 'benchmark'
8
+ res = nil
9
+ begin
10
+ measure = Benchmark.measure do
11
+ repeats.times do
12
+ res = yield
13
+ end
14
+ end
15
+ if message
16
+ puts "#{message }: #{ repeats } repeats"
17
+ else
18
+ puts "Benchmark for #{ repeats } repeats (#{caller.first})"
19
+ end
20
+ puts measure
21
+ rescue Exception
22
+ puts "Benchmark aborted"
23
+ raise $!
24
+ end
25
+ res
26
+ end
27
+
28
+ def self.profile(options = {})
29
+ require 'ruby-prof'
30
+ profiler = RubyProf::Profile.new
31
+ profiler.start
32
+ begin
33
+ res = yield
34
+ rescue Exception
35
+ puts "Profiling aborted"
36
+ raise $!
37
+ ensure
38
+ result = profiler.stop
39
+ printer = RubyProf::FlatPrinter.new(result)
40
+ printer.print(STDOUT, options)
41
+ end
42
+
43
+ res
44
+ end
45
+
46
+ def self.exec_time(&block)
47
+ start = Time.now
48
+ eend = nil
49
+ begin
50
+ yield
51
+ ensure
52
+ eend = Time.now
53
+ end
54
+ eend - start
55
+ end
56
+
57
+ def self.wait_for_interrupt
58
+ while true
59
+ begin
60
+ sleep 1
61
+ rescue Interrupt
62
+ break
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,73 @@
1
+ module Misc
2
+
3
+ def self.hostname
4
+ @@hostname ||= begin
5
+ `hostname`.strip
6
+ end
7
+ end
8
+
9
+ def self.children(ppid = nil)
10
+ require 'sys/proctable'
11
+
12
+ ppid ||= Process.pid
13
+ Sys::ProcTable.ps.select{ |pe| pe.ppid == ppid }
14
+ end
15
+
16
+ def self.env_add(var, value, sep = ":", prepend = true)
17
+ if ENV[var].nil?
18
+ ENV[var] = value
19
+ elsif ENV[var] =~ /(#{sep}|^)#{Regexp.quote value}(#{sep}|$)/
20
+ return
21
+ else
22
+ if prepend
23
+ ENV[var] = value + sep + ENV[var]
24
+ else
25
+ ENV[var] += sep + value
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.with_env(var, value, &block)
31
+ old_value = ENV[var]
32
+ begin
33
+ ENV[var] = value
34
+ yield
35
+ ensure
36
+ ENV[var] = old_value
37
+ end
38
+ end
39
+
40
+ def self.update_git(gem_name = 'scout-gear')
41
+ gem_name = 'scout-gear' if gem_name.nil?
42
+ dir = File.join(__dir__, '../../../../', gem_name)
43
+ return unless Open.exist?(dir)
44
+ Misc.in_dir dir do
45
+ begin
46
+ begin
47
+ CMD.cmd_log('git pull')
48
+ rescue
49
+ raise "Could not update #{gem_name}"
50
+ end
51
+
52
+ begin
53
+ CMD.cmd_log('git submodule update')
54
+ rescue
55
+ raise "Could not update #{gem_name} submodules"
56
+ end
57
+
58
+
59
+ begin
60
+ CMD.cmd_log('rake install')
61
+ rescue
62
+ raise "Could not install updated #{gem_name}"
63
+ end
64
+ rescue
65
+ Log.warn $!.message
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.processors
71
+ Etc.nprocessors
72
+ end
73
+ end
data/lib/scout/misc.rb ADDED
@@ -0,0 +1,10 @@
1
+ require_relative 'misc/format'
2
+ require_relative 'misc/insist'
3
+ require_relative 'misc/digest'
4
+ require_relative 'misc/filesystem'
5
+ require_relative 'misc/monitor'
6
+ require_relative 'misc/system'
7
+ require_relative 'misc/helper'
8
+
9
+ module Misc
10
+ end
@@ -0,0 +1,138 @@
1
+ require_relative 'meta_extension'
2
+ module NamedArray
3
+ extend MetaExtension
4
+ extension_attr :fields, :key
5
+
6
+ def self.field_match(field, name)
7
+ if (String === field) && (String === name)
8
+ field == name ||
9
+ field.start_with?(name) || field.include?("(" + name + ")") ||
10
+ name.start_with?(field) || name.include?("(" + field + ")")
11
+ else
12
+ field == name
13
+ end
14
+ end
15
+
16
+ def self.identify_name(names, selected, strict: false)
17
+ res = (Array === selected ? selected : [selected]).collect do |field|
18
+ case field
19
+ when nil
20
+ 0
21
+ when Range
22
+ field
23
+ when Integer
24
+ field
25
+ when Symbol
26
+ field == :key ? field : identify_name(names, field.to_s)
27
+ when (names.nil? and String)
28
+ if field =~ /^\d+$/
29
+ identify_field(key_field, fields, field.to_i)
30
+ else
31
+ raise "No name information available and specified name not numeric: #{ field }"
32
+ end
33
+ when Symbol
34
+ names.index{|f| f.to_s == field.to_s }
35
+ when String
36
+ pos = names.index{|f| f.to_s == field }
37
+ next pos if pos
38
+ if field =~ /^\d+$/
39
+ next identify_names(names, field.to_i)
40
+ end
41
+ next pos if strict
42
+ pos = names.index{|name| field_match(field, name) }
43
+ next pos if pos
44
+ nil
45
+ else
46
+ raise "Field '#{ Log.fingerprint field }' was not understood. Options: (#{ Log.fingerprint names })"
47
+ end
48
+ end
49
+
50
+ Array === selected ? res : res.first
51
+ end
52
+
53
+ def identify_name(selected)
54
+ NamedArray.identify_name(fields, selected)
55
+ end
56
+
57
+ def positions(fields)
58
+ if Array == fields
59
+ fields.collect{|field|
60
+ NamedArray.identify_name(@fields, field)
61
+ }
62
+ else
63
+ NamedArray.identify_name(@fields, fields)
64
+ end
65
+ end
66
+
67
+ def [](key)
68
+ pos = NamedArray.identify_name(@fields, key)
69
+ return nil if pos.nil?
70
+ super(pos)
71
+ end
72
+
73
+ def concat(other)
74
+ super(other)
75
+ self.fields.concat(other.fields) if NamedArray === other
76
+ self
77
+ end
78
+
79
+ def to_hash
80
+ hash = {}
81
+ self.fields.zip(self) do |field,value|
82
+ hash[field] = value
83
+ end
84
+ IndiferentHash.setup hash
85
+ end
86
+
87
+ def values_at(*positions)
88
+ super(*identify_name(positions))
89
+ end
90
+
91
+ def self._zip_fields(array, max = nil)
92
+ return [] if array.nil? or array.empty? or (first = array.first).nil?
93
+
94
+ max = array.collect{|l| l.length }.max if max.nil?
95
+
96
+ rest = array[1..-1].collect{|v|
97
+ v.length == 1 & max > 1 ? v * max : v
98
+ }
99
+
100
+ first = first * max if first.length == 1 and max > 1
101
+
102
+ first.zip(*rest)
103
+ end
104
+
105
+ def self.zip_fields(array)
106
+ if array.length < 10000
107
+ _zip_fields(array)
108
+ else
109
+ zipped_slices = []
110
+ max = array.collect{|l| l.length}.max
111
+ array.each_slice(10000) do |slice|
112
+ zipped_slices << _zip_fields(slice, max)
113
+ end
114
+ new = zipped_slices.first
115
+ zipped_slices[1..-1].each do |rest|
116
+ rest.each_with_index do |list,i|
117
+ new[i].concat list
118
+ end
119
+ end
120
+ new
121
+ end
122
+ end
123
+
124
+ def self.add_zipped(source, new)
125
+ source.zip(new).each do |s,n|
126
+ s.concat(n)
127
+ end
128
+ source
129
+ end
130
+
131
+ def method_missing(name, *args)
132
+ if identify_name(name)
133
+ return self[name]
134
+ else
135
+ return super(name, *args)
136
+ end
137
+ end
138
+ end