scout-essentials 1.0.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.
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,284 @@
1
+ require_relative 'indiferent_hash'
2
+
3
+ module AbortedStream
4
+ attr_accessor :exception
5
+ def self.setup(obj, exception = nil)
6
+ obj.extend AbortedStream
7
+ obj.exception = exception
8
+ end
9
+ end
10
+
11
+ module ConcurrentStream
12
+ attr_accessor :threads, :pids, :callback, :abort_callback, :filename, :joined, :aborted, :autojoin, :lock, :no_fail, :pair, :thread, :stream_exception, :log, :std_err, :next
13
+
14
+ def self.setup(stream, options = {}, &block)
15
+ threads, pids, callback, abort_callback, filename, autojoin, lock, no_fail, pair, next_stream = IndiferentHash.process_options options, :threads, :pids, :callback, :abort_callback, :filename, :autojoin, :lock, :no_fail, :pair, :next
16
+ stream.extend ConcurrentStream unless ConcurrentStream === stream
17
+
18
+ stream.threads ||= []
19
+ stream.pids ||= []
20
+ stream.threads.concat(Array === threads ? threads : [threads]) unless threads.nil?
21
+ stream.pids.concat(Array === pids ? pids : [pids]) unless pids.nil? or pids.empty?
22
+ stream.autojoin = autojoin unless autojoin.nil?
23
+ stream.no_fail = no_fail unless no_fail.nil?
24
+ stream.std_err = ""
25
+
26
+ stream.next = next_stream unless next_stream.nil?
27
+ stream.pair = pair unless pair.nil?
28
+
29
+ callback = block if block_given?
30
+ if callback
31
+ if stream.callback
32
+ old_callback = stream.callback
33
+ stream.callback = Proc.new do
34
+ old_callback.call
35
+ callback.call
36
+ end
37
+ else
38
+ stream.callback = callback
39
+ end
40
+ end
41
+
42
+ if abort_callback
43
+ if stream.abort_callback
44
+ old_abort_callback = stream.abort_callback
45
+ stream.abort_callback = Proc.new do
46
+ old_abort_callback.call
47
+ abort_callback.call
48
+ end
49
+ else
50
+ stream.abort_callback = abort_callback
51
+ end
52
+ end
53
+
54
+ stream.filename = filename.nil? ? stream.inspect.split(":").last[0..-2] : filename
55
+
56
+ stream.lock = lock unless lock.nil?
57
+
58
+ stream.aborted = false
59
+
60
+ stream
61
+ end
62
+
63
+ def annotate(stream)
64
+ ConcurrentStream.setup(stream, :threads => threads, :pids => pids, :callback => callback, :abort_callback => abort_callback, :filename => filename, :autojoin => autojoin, :lock => lock)
65
+ stream
66
+ end
67
+
68
+ def clear
69
+ @threads = @pids = @callback = @abort_callback = @joined = nil
70
+ end
71
+
72
+ def joined?
73
+ @joined
74
+ end
75
+
76
+ def aborted?
77
+ @aborted
78
+ end
79
+
80
+ def join_threads
81
+ if @threads
82
+ @threads.each do |t|
83
+ next if t == Thread.current
84
+ begin
85
+ t.join
86
+ if Process::Status === t.value
87
+ if ! (t.value.success? || no_fail)
88
+
89
+ if log
90
+ msg = "Error joining #{self.filename || self.inspect}. Last log line: #{log}"
91
+ else
92
+ msg = "Error joining #{self.filename || self.inspect}"
93
+ end
94
+
95
+ raise ConcurrentStreamProcessFailed.new t.pid, msg, self
96
+ end
97
+ end
98
+ rescue Exception
99
+ if no_fail
100
+ Log.low "Not failing on exception joining thread in ConcurrenStream - #{filename} - #{$!.message}"
101
+ else
102
+ Log.low "Exception joining thread in ConcurrenStream #{Log.fingerprint self} - #{Log.fingerprint t} - #{$!.message}"
103
+ stream_raise_exception $!
104
+ end
105
+ end
106
+ end
107
+ end
108
+ @threads = []
109
+ end
110
+
111
+ def join_pids
112
+ if @pids and @pids.any?
113
+ @pids.each do |pid|
114
+ begin
115
+ Process.waitpid(pid, Process::WUNTRACED)
116
+ stream_raise_exception ConcurrentStreamProcessFailed.new(pid, "Error in waitpid", self) unless $?.success? or no_fail
117
+ rescue Errno::ECHILD
118
+ end
119
+ end
120
+ @pids = []
121
+ end
122
+ end
123
+
124
+ def join_callback
125
+ if @callback and not joined?
126
+ begin
127
+ @callback.call
128
+ ensure
129
+ @callback = nil
130
+ end
131
+ end
132
+ end
133
+
134
+ def join
135
+ begin
136
+ join_threads
137
+ join_pids
138
+ raise stream_exception if stream_exception
139
+ join_callback
140
+ close unless closed?
141
+ ensure
142
+ @joined = true
143
+ begin
144
+ lock.unlock if lock && lock.locked?
145
+ rescue
146
+ Log.exception $!
147
+ end
148
+ raise stream_exception if stream_exception
149
+ end
150
+ end
151
+
152
+ def abort_threads(exception = nil)
153
+ return unless @threads and @threads.any?
154
+ name = Log.fingerprint(Thread.current)
155
+ name += " - file:#{filename}" if filename
156
+ Log.low "Aborting threads (#{name}) - #{@threads.collect{|t| Log.fingerprint(t) } * ", "}"
157
+
158
+ threads = @threads.dup
159
+ @threads.clear
160
+ threads.each do |t|
161
+ next if t == Thread.current
162
+ next if t["aborted"]
163
+ t["aborted"] = true
164
+ exception = exception.nil? ? Aborted.new : exception
165
+ Log.debug "Aborting thread #{Log.fingerprint(t)} with exception: #{exception}"
166
+ t.raise(exception)
167
+ t.join
168
+ end
169
+ end
170
+
171
+ def abort_pids
172
+ @pids.each do |pid|
173
+ begin
174
+ Log.low "Killing PID #{pid} in ConcurrentStream #{filename}"
175
+ Process.kill :INT, pid
176
+ rescue Errno::ESRCH
177
+ end
178
+ end if @pids
179
+ @pids = []
180
+ end
181
+
182
+ def abort(exception = nil)
183
+ self.stream_exception ||= exception
184
+ if @aborted
185
+ Log.medium "Already aborted stream #{Log.fingerprint self} [#{@aborted}]"
186
+ return
187
+ else
188
+ Log.medium "Aborting stream #{Log.fingerprint self} [#{@aborted}]"
189
+ end
190
+ AbortedStream.setup(self, exception)
191
+ @aborted = true
192
+ begin
193
+ @abort_callback.call exception if @abort_callback
194
+
195
+ abort_threads(exception)
196
+ abort_pids
197
+
198
+ @callback = nil
199
+ @abort_callback = nil
200
+
201
+ if @pair && @pair.respond_to?(:abort) && ! @pair.aborted?
202
+ Log.medium "Aborting pair stream #{Log.fingerprint self}: #{Log.fingerprint @pair }"
203
+ @pair.abort exception
204
+ end
205
+ ensure
206
+ close unless closed?
207
+
208
+ if lock and lock.locked?
209
+ lock.unlock
210
+ end
211
+ end
212
+ end
213
+
214
+ def close(*args)
215
+ if autojoin
216
+ begin
217
+ super(*args)
218
+ rescue
219
+ self.abort
220
+ self.join
221
+ stream_raise_exception $!
222
+ ensure
223
+ self.join if ! @stream_exception && (self.closed? || self.eof?)
224
+ end
225
+ else
226
+ super(*args)
227
+ end
228
+ end
229
+
230
+ def read(*args)
231
+ begin
232
+ super(*args)
233
+ rescue Exception
234
+ @stream_exception ||= $!
235
+ raise @stream_exception
236
+ ensure
237
+ if ! @stream_exception && autojoin && ! closed?
238
+ begin
239
+ done = eof?
240
+ rescue Exception
241
+ self.abort($!)
242
+ raise $!
243
+ end
244
+ close if done
245
+ end
246
+ end
247
+ end
248
+
249
+ def add_callback(&block)
250
+ old_callback = callback
251
+ @callback = Proc.new do
252
+ old_callback.call if old_callback
253
+ block.call
254
+ end
255
+ end
256
+
257
+ def stream_raise_exception(exception)
258
+ self.stream_exception = exception
259
+ threads.each do |thread|
260
+ thread.raise exception
261
+ end
262
+ self.abort
263
+ end
264
+
265
+ def self.process_stream(stream, close: true, join: true, message: "process_stream", **kwargs, &block)
266
+ ConcurrentStream.setup(stream, **kwargs)
267
+ begin
268
+ begin
269
+ yield
270
+ ensure
271
+ stream.close if close && stream.respond_to?(:close) && ! (stream.respond_to?(:closed?) && stream.closed?)
272
+ stream.join if join && stream.respond_to?(:join) && ! stream.joined?
273
+ end
274
+ rescue Aborted
275
+ Log.low "Aborted #{message}: #{$!.message}"
276
+ stream.abort($!) if stream.respond_to?(:abort) && ! stream.aborted?
277
+ raise $!
278
+ rescue Exception
279
+ Log.low "Exception #{message}: #{$!.message}"
280
+ stream.abort($!) if stream.respond_to?(:abort) && ! stream.aborted?
281
+ raise $!
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,168 @@
1
+ require_relative 'path'
2
+ require_relative 'resource'
3
+ require_relative 'resource/scout'
4
+
5
+ module Scout::Config
6
+
7
+ CACHE ||= IndiferentHash.setup({})
8
+
9
+ GOT_KEYS=[]
10
+
11
+ def self.add_entry(key, value, tokens)
12
+ tokens = [tokens] unless Array === tokens
13
+ tokens << "key:#{key}" unless tokens.include?("key:#{key}")
14
+ CACHE[key.to_s] ||= []
15
+ CACHE[key.to_s] << [tokens, value]
16
+ end
17
+
18
+ def self.load_file(file)
19
+ Log.debug "Loading config file: #{ file }"
20
+ TSV.traverse file, :type => :array do |line|
21
+ next if line =~ /^#/
22
+ key, value, *tokens = line.strip.split(/\s/)
23
+
24
+ self.add_entry(key, value, tokens) if key
25
+ end
26
+ end
27
+
28
+ def self.load_config
29
+ Scout.etc.config.find_all.reverse.each do |file|
30
+ self.load_file(file)
31
+ end
32
+ end
33
+
34
+ def self.set(values, *tokens)
35
+ if not Hash === values
36
+ values = {values => tokens.shift}
37
+ end
38
+
39
+ values.each do |key,value|
40
+ add_entry key, value, tokens
41
+ end
42
+ end
43
+
44
+ def self.token_priority(token)
45
+ token, _sep, priority = token.to_s.partition("::")
46
+
47
+ if priority.nil? || priority.empty?
48
+ type, _sep, rest = token.partition(":")
49
+ priority = case type
50
+ when "workflow"
51
+ 4
52
+ when "task"
53
+ 3
54
+ when "file"
55
+ 2
56
+ when "line"
57
+ 1
58
+ when "key"
59
+ 20
60
+ else
61
+ 10
62
+ end
63
+ else
64
+ priority = priority.to_i
65
+ end
66
+
67
+ [token, priority]
68
+ end
69
+
70
+ def self.match(entries, give_token)
71
+ priorities = {}
72
+ entries.each do |tokens, value|
73
+ best_prio = nil
74
+ tokens = [tokens] unless Array === tokens
75
+ tokens.each do |tok|
76
+ tok, prio = token_priority tok
77
+ next unless tok == give_token
78
+
79
+ best_prio = prio if best_prio.nil? or best_prio > prio
80
+ next if prio > best_prio
81
+
82
+ priorities[prio] ||= []
83
+ priorities[prio].unshift value
84
+ end
85
+ end if entries
86
+ priorities
87
+ end
88
+
89
+ # For equal priorities the matching prioritizes tokens ealier in the list
90
+ def self.get(key, *tokens)
91
+ options = tokens.pop if Hash === tokens.last
92
+ default = options.nil? ? nil : options[:default]
93
+
94
+ tokens = ["key:" + key] if tokens.empty?
95
+
96
+ tokens = tokens.flatten
97
+ file, _sep, line = caller.reject{|l|
98
+ l =~ /rbbt\/(?:resource\.rb|workflow\.rb)/ or
99
+ l =~ /rbbt\/resource\/path\.rb/ or
100
+ l =~ /rbbt\/util\/misc\.rb/ or
101
+ l =~ /accessor\.rb/ or
102
+ l =~ /progress-monitor\.rb/
103
+ }.first.partition(":")
104
+
105
+ File.expand_path(file)
106
+
107
+ tokens << ("file:" << file)
108
+ tokens << ("line:" << file << ":" << line.sub(/:in \`.*/,''))
109
+
110
+ entries = CACHE[key.to_s]
111
+ priorities = {}
112
+ tokens.each do |token|
113
+ token_prio = match entries, token.to_s
114
+ token_prio.each do |prio, values|
115
+ priorities[prio] ||= []
116
+ priorities[prio].concat(values)
117
+ end
118
+ end
119
+
120
+ value = priorities.empty? ? default : priorities.collect{|p| p }.sort_by{|p,v| p}.first.last.first
121
+ value = false if value == 'false'
122
+
123
+ Log.debug "Value #{value.inspect} for config key '#{ key }': #{tokens * ", "}"
124
+ GOT_KEYS << [key, value, tokens]
125
+
126
+ if String === value && m = value.match(/^env:(.*)/)
127
+ variable = m.captures.first
128
+ ENV[variable]
129
+ elsif value == 'nil'
130
+ nil
131
+ else
132
+ value
133
+ end
134
+ end
135
+
136
+ def self.with_config
137
+ saved_config = {}
138
+ CACHE.each do |k,v|
139
+ saved_config[k] = v.dup
140
+ end
141
+ saved_got_keys = GOT_KEYS.dup
142
+ begin
143
+ yield
144
+ ensure
145
+ CACHE.replace(saved_config)
146
+ GOT_KEYS.replace(saved_got_keys)
147
+ end
148
+ end
149
+
150
+ def self.process_config(config)
151
+ if Path.is_filename?(config) && File.exist?(config)
152
+ Scout::Config.load_file(config)
153
+ elsif Scout.etc.config_profile[config].exists?
154
+ Scout::Config.load_file(Scout.etc.config_profile[config].find)
155
+ else
156
+ key, value, *tokens = config.split(/\s/)
157
+ tokens = tokens.collect do |tok|
158
+ tok, _sep, prio = tok.partition("::")
159
+ prio = "0" if prio.nil? or prio.empty?
160
+ [tok, prio] * "::"
161
+ end
162
+ Scout::Config.set({key => value}, *tokens)
163
+ end
164
+ end
165
+
166
+
167
+ self.load_config
168
+ end
@@ -0,0 +1,77 @@
1
+ class ScoutDeprecated < StandardError; end
2
+ class ScoutException < StandardError; end
3
+
4
+ class FieldNotFoundError < StandardError;end
5
+
6
+ class TryAgain < StandardError; end
7
+ class StopInsist < Exception
8
+ attr_accessor :exception
9
+ def initialize(exception)
10
+ @exception = exception
11
+ end
12
+ end
13
+
14
+ class Aborted < StandardError; end
15
+
16
+ class ParameterException < ScoutException; end
17
+ class MissingParameterException < ParameterException
18
+ def initialize(parameter)
19
+ super("Missing parameter '#{parameter}'")
20
+ end
21
+ end
22
+ class ProcessFailed < StandardError;
23
+ attr_accessor :pid, :msg
24
+ def initialize(pid = Process.pid, msg = nil)
25
+ @pid = pid
26
+ @msg = msg
27
+ if @pid
28
+ if @msg
29
+ message = "Process #{@pid} failed - #{@msg}"
30
+ else
31
+ message = "Process #{@pid} failed"
32
+ end
33
+ else
34
+ message = "Failed to run #{@msg}"
35
+ end
36
+ super(message)
37
+ end
38
+ end
39
+
40
+ class ConcurrentStreamProcessFailed < ProcessFailed
41
+ attr_accessor :concurrent_stream
42
+ def initialize(pid = Process.pid, msg = nil, concurrent_stream = nil)
43
+ super(pid, msg)
44
+ @concurrent_stream = concurrent_stream
45
+ end
46
+ end
47
+
48
+ class OpenURLError < StandardError; end
49
+
50
+ class DontClose < Exception
51
+ attr_accessor :payload
52
+ def initialize(payload = nil)
53
+ @payload = payload
54
+ end
55
+ end
56
+
57
+
58
+ class KeepLocked < Exception
59
+ attr_accessor :payload
60
+ def initialize(payload)
61
+ @payload = payload
62
+ end
63
+ end
64
+
65
+ class KeepBar < Exception
66
+ attr_accessor :payload
67
+ def initialize(payload)
68
+ @payload = payload
69
+ end
70
+ end
71
+
72
+ class LockInterrupted < TryAgain; end
73
+
74
+ class ClosedStream < StandardError; end
75
+
76
+ class ResourceNotFound < ScoutException; end
77
+
@@ -0,0 +1,30 @@
1
+ module CaseInsensitiveHash
2
+
3
+ def self.setup(hash)
4
+ hash.extend CaseInsensitiveHash
5
+ end
6
+
7
+ def downcase_keys
8
+ @downcase_keys ||= begin
9
+ down = {}
10
+ keys.collect{|key|
11
+ down[key.to_s.downcase] = key
12
+ }
13
+ down
14
+ end
15
+ end
16
+
17
+ def [](key, *rest)
18
+ value = super(key, *rest)
19
+ return value unless value.nil?
20
+ key_downcase = key.to_s.downcase
21
+ super(downcase_keys[key_downcase])
22
+ end
23
+
24
+ def values_at(*keys)
25
+ keys.collect do |key|
26
+ self[key]
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,115 @@
1
+ module IndiferentHash
2
+ def self.add_defaults(options, defaults = {})
3
+ options = string2hash options if String === options
4
+ IndiferentHash.setup(options)
5
+
6
+ defaults = string2hash defaults if String === defaults
7
+
8
+ defaults.each do |key, value|
9
+ next if options.include?(key)
10
+
11
+ options[key] = value
12
+ end
13
+
14
+ options
15
+ end
16
+
17
+ def self.process_options(hash, *keys)
18
+ IndiferentHash.setup(hash)
19
+
20
+ defaults = keys.pop if Hash === keys.last
21
+ hash = IndiferentHash.add_defaults hash, defaults if defaults
22
+
23
+ if keys.length == 1
24
+ hash.include?(keys.first.to_sym) ? hash.delete(keys.first.to_sym) : hash.delete(keys.first.to_s)
25
+ else
26
+ keys.collect do |key| hash.include?(key.to_sym) ? hash.delete(key.to_sym) : hash.delete(key.to_s) end
27
+ end
28
+ end
29
+
30
+ def self.pull_keys(hash, prefix)
31
+ new = {}
32
+ prefix = prefix.to_s
33
+ hash.keys.each do |key|
34
+ if key.to_s =~ /#{ prefix }_(.*)/
35
+ case
36
+ when String === key
37
+ new[$1] = hash.delete key
38
+ when Symbol === key
39
+ new[$1.to_sym] = hash.delete key
40
+ end
41
+ else
42
+ if key.to_s == prefix.to_s
43
+ new[key] = hash.delete key
44
+ end
45
+ end
46
+ end
47
+
48
+ IndiferentHash.setup(new)
49
+ end
50
+
51
+ def self.zip2hash(list1, list2)
52
+ hash = {}
53
+ list1.each_with_index do |e,i|
54
+ hash[e] = list2[i]
55
+ end
56
+ IndiferentHash.setup(hash)
57
+ end
58
+
59
+ def self.positional2hash(keys, *values)
60
+ if Hash === values.last
61
+ extra = values.pop
62
+ inputs = IndiferentHash.zip2hash(keys, values)
63
+ inputs.delete_if{|k,v| v.nil? or (String === v and v.empty?)}
64
+ inputs = IndiferentHash.add_defaults inputs, extra
65
+ inputs.delete_if{|k,v| not keys.include?(k) and not (Symbol === k ? keys.include?(k.to_s) : keys.include?(k.to_sym))}
66
+ inputs
67
+ else
68
+ IndiferentHash.zip2hash(keys, values)
69
+ end
70
+ end
71
+
72
+ def self.array2hash(array, default = nil)
73
+ hash = {}
74
+ array.each do |key, value|
75
+ value = default.dup if value.nil? and not default.nil?
76
+ hash[key] = value
77
+ end
78
+ IndiferentHash.setup(hash)
79
+ end
80
+
81
+ def self.process_to_hash(list)
82
+ result = yield list
83
+ zip2hash(list, result)
84
+ end
85
+
86
+ def self.hash2string(hash)
87
+ hash.sort_by{|k,v| k.to_s}.collect{|k,v|
88
+ next unless %w(Symbol String Float Fixnum Integer Numeric TrueClass FalseClass Module Class Object).include? v.class.to_s
89
+ [ Symbol === k ? ":" << k.to_s : k.to_s.chomp,
90
+ Symbol === v ? ":" << v.to_s : v.to_s.chomp] * "="
91
+ }.compact * "#"
92
+ end
93
+
94
+ def self.string2hash(string)
95
+ options = {}
96
+
97
+ string.split('#').each do |str|
98
+ key, _, value = str.partition "="
99
+
100
+ key = key[1..-1].to_sym if key[0] == ":"
101
+
102
+ options[key] = true and next if value.empty?
103
+ options[key] = value[1..-1].to_sym and next if value[0] == ":"
104
+ options[key] = Regexp.new(/#{value[1..-2]}/) and next if value[0] == "/" and value[-1] == "/"
105
+ options[key] = value[1..-2] and next if value =~ /^['"].*['"]$/
106
+ options[key] = value.to_i and next if value =~ /^\d+$/
107
+ options[key] = value.to_f and next if value =~ /^\d*\.\d+$/
108
+ options[key] = true and next if value == "true"
109
+ options[key] = false and next if value == "false"
110
+ options[key] = value
111
+ end
112
+
113
+ IndiferentHash.setup(options)
114
+ end
115
+ end