wabur 0.2.0d1 → 0.4.0d1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +40 -14
  3. data/bin/wabur +103 -0
  4. data/lib/wab/controller.rb +133 -90
  5. data/lib/wab/data.rb +14 -27
  6. data/lib/wab/errors.rb +14 -8
  7. data/lib/wab/impl/bool_expr.rb +25 -0
  8. data/lib/wab/impl/configuration.rb +166 -0
  9. data/lib/wab/impl/data.rb +155 -232
  10. data/lib/wab/impl/expr.rb +26 -0
  11. data/lib/wab/impl/expr_parser.rb +55 -0
  12. data/lib/wab/impl/exprs/and.rb +29 -0
  13. data/lib/wab/impl/exprs/between.rb +41 -0
  14. data/lib/wab/impl/exprs/eq.rb +28 -0
  15. data/lib/wab/impl/exprs/gt.rb +30 -0
  16. data/lib/wab/impl/exprs/gte.rb +30 -0
  17. data/lib/wab/impl/exprs/has.rb +26 -0
  18. data/lib/wab/impl/exprs/in.rb +28 -0
  19. data/lib/wab/impl/exprs/lt.rb +30 -0
  20. data/lib/wab/impl/exprs/lte.rb +30 -0
  21. data/lib/wab/impl/exprs/not.rb +27 -0
  22. data/lib/wab/impl/exprs/or.rb +29 -0
  23. data/lib/wab/impl/exprs/regex.rb +28 -0
  24. data/lib/wab/impl/handler.rb +95 -0
  25. data/lib/wab/impl/model.rb +197 -0
  26. data/lib/wab/impl/path_expr.rb +14 -0
  27. data/lib/wab/impl/shell.rb +92 -7
  28. data/lib/wab/impl/utils.rb +110 -0
  29. data/lib/wab/impl.rb +24 -0
  30. data/lib/wab/io/call.rb +4 -7
  31. data/lib/wab/io/engine.rb +128 -51
  32. data/lib/wab/io/shell.rb +61 -64
  33. data/lib/wab/io.rb +0 -2
  34. data/lib/wab/open_controller.rb +43 -0
  35. data/lib/wab/shell.rb +46 -61
  36. data/lib/wab/shell_logger.rb +13 -0
  37. data/lib/wab/utils.rb +36 -0
  38. data/lib/wab/uuid.rb +3 -6
  39. data/lib/wab/version.rb +2 -2
  40. data/lib/wab.rb +3 -0
  41. data/pages/Plan.md +20 -14
  42. data/test/bench_io_shell.rb +49 -0
  43. data/test/{impl_test.rb → helper.rb} +2 -4
  44. data/test/mirror_controller.rb +16 -0
  45. data/test/test_configuration.rb +38 -0
  46. data/test/test_data.rb +207 -0
  47. data/test/test_expr.rb +35 -0
  48. data/test/test_expr_and.rb +24 -0
  49. data/test/test_expr_between.rb +43 -0
  50. data/test/test_expr_eq.rb +24 -0
  51. data/test/test_expr_gt.rb +24 -0
  52. data/test/test_expr_gte.rb +24 -0
  53. data/test/test_expr_has.rb +19 -0
  54. data/test/test_expr_in.rb +24 -0
  55. data/test/test_expr_lt.rb +24 -0
  56. data/test/test_expr_lte.rb +24 -0
  57. data/test/test_expr_not.rb +22 -0
  58. data/test/test_expr_or.rb +24 -0
  59. data/test/test_expr_regex.rb +30 -0
  60. data/test/test_impl.rb +38 -0
  61. data/test/test_io_shell.rb +189 -0
  62. data/test/test_model.rb +31 -0
  63. data/test/test_runner.rb +177 -0
  64. data/test/tests.rb +3 -8
  65. metadata +91 -18
  66. data/lib/wab/model.rb +0 -136
  67. data/lib/wab/view.rb +0 -21
  68. data/test/data_test.rb +0 -253
  69. data/test/ioshell_test.rb +0 -461
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'logger'
5
+
6
+ module WAB
7
+ module Impl
8
+
9
+ # Handles the configuration for a Shell Implementation and the Ruby Runner
10
+ class Configuration
11
+
12
+ attr_accessor :map
13
+
14
+ def initialize(usage, options)
15
+ @map = {}
16
+ opts = OptionParser.new(usage)
17
+ config_file = nil
18
+ log_level = Logger::WARN
19
+
20
+ opts.on('-c', '--config PATH', String, 'Configuration file.') { |c| config_file = c }
21
+ opts.on('-r', '--require LIBRARY', String, 'Require.') { |r| require r }
22
+ opts.on('-v', '--verbose', 'Increase verbosity.') { log_level += 1 }
23
+ opts.on('-h', '--help', 'Show this display.') { puts opts.help; Process.exit!(0) }
24
+
25
+ # Process command-line arguments and append them, in order, to an empty hash @map
26
+ add_options(opts, options)
27
+
28
+ opts.parse(ARGV)
29
+
30
+ # Move the @map sideways and replace with defaults.
31
+ command_line_map = @map
32
+ @map = {}
33
+ build_default_map(options)
34
+
35
+ # If a config file was specified load it and merge into @map.
36
+ @map = merge_map(@map, parse_config_file(config_file)) unless config_file.nil?
37
+
38
+ # Merge in the command line map.
39
+ @map = merge_map(@map, command_line_map) unless command_line_map.empty?
40
+ end
41
+
42
+ # Walks the options map and calls +opts.on+ for each option so that all
43
+ # are provided when +--help+ is called.
44
+ def add_options(opts, options, path='')
45
+ options.each_pair { |k,v|
46
+ next unless v.is_a?(Hash)
47
+ key_path = path.empty? ? k.to_s : "#{path}.#{k}"
48
+ if v.has_key?(:val)
49
+ default = v[:val]
50
+ if default.is_a?(Array)
51
+ opts.on(v[:short], "--#{key_path} #{v[:arg]}", String, v[:doc]) { |val| arg_append(key_path, val, v[:parse]) }
52
+ else
53
+ opts.on(v[:short], "--#{key_path} #{v[:arg]}", v[:type], "#{v[:doc]} Default: #{default}") { |val| set(key_path, val) }
54
+ end
55
+ else
56
+ add_options(opts, v, key_path)
57
+ end
58
+ }
59
+ end
60
+
61
+ # Appends an arg to an array in the configuration.
62
+ def arg_append(path, val, parse)
63
+ parts = val.split('=')
64
+ if 1 < parts.length
65
+ val = {}
66
+ parse.each_index { |i| val[parse[i].to_sym] = parts[i] }
67
+ end
68
+ a = get(path)
69
+ if a.nil?
70
+ a = []
71
+ set(path, a)
72
+ end
73
+ a.push(val)
74
+ end
75
+
76
+ # Builds a map from the default options passed in.
77
+ def build_default_map(options, path='')
78
+ options.each_pair { |k,v|
79
+ next unless v.is_a?(Hash)
80
+ key_path = path.empty? ? k.to_s : "#{path}.#{k}"
81
+ if v.has_key?(:val)
82
+ set(key_path, v[:val])
83
+ else
84
+ build_default_map(v, key_path)
85
+ end
86
+ }
87
+ end
88
+
89
+ # Recursive merge of other into prime.
90
+ def merge_map(prime, other)
91
+ prime.merge(other) { |key,prime_value,other_value|
92
+ case prime_value
93
+ when Hash
94
+ merge_map(prime_value, other_value)
95
+ when Array
96
+ prime_value + other_value
97
+ else
98
+ other_value
99
+ end
100
+ }
101
+ end
102
+
103
+ # Returns a Hash of configuration data.
104
+ #
105
+ # TBD: Add validation to ensure only a Hash object is returned
106
+ def parse_config_file(file)
107
+ return {} unless File.exist?(file)
108
+
109
+ case File.extname(file)
110
+ when /\.conf$/i
111
+ parse_conf_file(file)
112
+ when /\.json$/i
113
+ Oj.load_file(file, mode: :strict, symbol_keys: true)
114
+ when /\.ya?ml$/i
115
+ begin
116
+ require 'safe_yaml/load'
117
+ SafeYAML.load_file(file) || {}
118
+ rescue LoadError
119
+ # Re-raise with a more descriptive message. This should generally
120
+ # abort the configuration loading.
121
+ raise LoadError.new(%{Could not load the requested resource. Please install the 'safe_yaml' gem via
122
+ Bundler or directly, and try loading again.
123
+ })
124
+ end
125
+ end
126
+ end
127
+
128
+ # Returns a Hash containing data obtained by parsing a UNIX style conf
129
+ # file.
130
+ #
131
+ # For example, +handler.sample.count = 63+ and +handler.sample.path = /v1+
132
+ # will be parsed into the following:
133
+ #
134
+ # { handler: { sample: { count: 63, path: "/v1" } } }
135
+ def parse_conf_file(file)
136
+ config = {}
137
+
138
+ File.open(File.expand_path(file)) do |f|
139
+ f.each_line do |line|
140
+ line.strip!
141
+ next if line.empty? || line.start_with?('#')
142
+ key, value = line.split('=').map(&:strip)
143
+ set_map(config, key, value)
144
+ end
145
+ end
146
+ config
147
+ end
148
+
149
+ def set_map(node, path, value)
150
+ return node if path.empty?
151
+ Utils.set_value(node, path, value)
152
+ end
153
+
154
+ def set(path, value)
155
+ set_map(@map, path, value)
156
+ end
157
+ alias []= set
158
+
159
+ def get(path)
160
+ Utils.get_node(@map, path)
161
+ end
162
+ alias [] get
163
+
164
+ end # Configuration
165
+ end # Impl
166
+ end # WAB
data/lib/wab/impl/data.rb CHANGED
@@ -1,4 +1,5 @@
1
1
 
2
+ require 'date'
2
3
  require 'uri'
3
4
  require 'oj'
4
5
 
@@ -9,7 +10,28 @@ module WAB
9
10
  # the Data instances are factory created by the Shell and will most likely
10
11
  # not be instance of this class but rather a class that is a duck-type of
11
12
  # this class (has the same methods and behavior).
12
- class Data < ::WAB::Data
13
+ class Data < WAB::Data
14
+ attr_reader :root
15
+
16
+ def self.detect_string(s)
17
+ if WAB::Utils.uuid_format?(s)
18
+ WAB::UUID.new(s)
19
+ elsif WAB::Utils.wab_time_format?(s)
20
+ begin
21
+ DateTime.parse(s).to_time
22
+ rescue
23
+ s
24
+ end
25
+ elsif s.downcase.start_with?('http://')
26
+ begin
27
+ URI(s)
28
+ rescue
29
+ s
30
+ end
31
+ else
32
+ s
33
+ end
34
+ end
13
35
 
14
36
  # This method should not be called directly. New instances should be
15
37
  # created by using a Shell#data method.
@@ -30,36 +52,48 @@ module WAB
30
52
  @root = value
31
53
  end
32
54
 
55
+ # Returns the instance converted to native Ruby values such as a Hash,
56
+ # Array, etc.
57
+ alias native root
58
+
59
+ # Returns true if the Data element or value identified by the path
60
+ # exists where the path elements are separated by the '.' character. The
61
+ # path can also be a array of path node identifiers. For example,
62
+ # child.grandchild is the same as ['child', 'grandchild'].
63
+ def has?(path)
64
+ return (@root.is_a?(Hash) && @root.has_key?(path)) if path.is_a?(Symbol)
65
+
66
+ path = path.to_s.split('.') unless path.is_a?(Array)
67
+ node = @root
68
+ path.each { |key|
69
+ if node.is_a?(Hash)
70
+ key = key.to_sym
71
+ return false unless node.has_key?(key)
72
+ node = node[key]
73
+ elsif node.is_a?(Array)
74
+ i = key.to_i
75
+ return false if 0 == i && '0' != key && 0 != key
76
+ len = node.length
77
+ return false unless -len <= i && i < len
78
+ node = node[i]
79
+ else
80
+ return false
81
+ end
82
+ }
83
+ true
84
+ end
85
+
33
86
  # Gets the Data element or value identified by the path where the path
34
87
  # elements are separated by the '.' character. The path can also be a
35
88
  # array of path node identifiers. For example, child.grandchild is the
36
89
  # same as ['child', 'grandchild'].
37
90
  def get(path)
38
- if path.is_a?(Symbol)
39
- node = @root[path]
40
- else
41
- path = path.to_s.split('.') unless path.is_a?(Array)
42
- node = @root
43
- path.each { |key|
44
- if node.is_a?(Hash)
45
- node = node[key.to_sym]
46
- elsif node.is_a?(Array)
47
- i = key.to_i
48
- if 0 == i && '0' != key && 0 != key
49
- node = nil
50
- break
51
- end
52
- node = node[i]
53
- else
54
- node = nil
55
- break
56
- end
57
- }
58
- end
59
- return Data.new(node, false, false) if node.is_a?(Hash) || node.is_a?(Array)
60
- node
91
+ node = Utils.get_node(@root, path)
92
+
93
+ return node unless node.is_a?(Hash) || node.is_a?(Array)
94
+ Data.new(node, false, false)
61
95
  end
62
-
96
+
63
97
  # Sets the node value identified by the path where the path elements are
64
98
  # separated by the '.' character. The path can also be a array of path
65
99
  # node identifiers. For example, child.grandchild is the same as ['child',
@@ -74,8 +108,8 @@ module WAB
74
108
  # value:: value to set
75
109
  # repair:: flag indicating invalid value should be repaired if possible
76
110
  def set(path, value, repair=false)
77
- raise StandardError.new("path can not be empty.") if path.empty?
78
- if value.is_a?(::WAB::Data)
111
+ raise WAB::Error, 'path can not be empty.' if path.empty?
112
+ if value.is_a?(WAB::Data)
79
113
  value = value.native
80
114
  elsif repair
81
115
  value = fix_value(value)
@@ -83,47 +117,7 @@ module WAB
83
117
  validate_value(value)
84
118
  end
85
119
  node = @root
86
- path = path.to_s.split('.') unless path.is_a?(Array)
87
- path[0..-2].each { |key|
88
- if node.is_a?(Hash)
89
- key = key.to_sym
90
- node[key] = {} unless node.has_key?(key)
91
- node = node[key]
92
- elsif node.is_a?(Array)
93
- i = key.to_i
94
- raise StandardError.new("path key must be an integer for an Array.") if (0 == i && '0' != key && 0 != key)
95
- if i < node.length && -node.length < i
96
- node = node[i]
97
- else
98
- # TBD if next key is a number then make an array instead
99
- nn = {}
100
- if i < -node.length
101
- node.unshift(nn)
102
- else
103
- node[i] = nn
104
- end
105
- node = nn
106
- end
107
- else
108
- raise StandardError.new("Can not set a member of an #{node.class}.")
109
- end
110
- }
111
- key = path[-1]
112
- if node.is_a?(Hash)
113
- key = key.to_sym
114
- node[key] = value
115
- elsif node.is_a?(Array)
116
- i = key.to_i
117
- raise StandardError.new("path key must be an integer for an Array.") if (0 == i && '0' != key && 0 != key)
118
- if i < -node.length
119
- node.unshift(value)
120
- else
121
- node[i] = value
122
- end
123
- else
124
- raise StandardError.new("Can not set a member of an #{node.class}.")
125
- end
126
- value
120
+ Utils.set_value(node, path, value)
127
121
  end
128
122
 
129
123
  # Each child of the Data instance is provided as an argument to a block
@@ -141,44 +135,13 @@ module WAB
141
135
  end
142
136
 
143
137
  # Make a deep copy of the Data instance.
144
- def clone()
138
+ def deep_dup()
145
139
  # avoid validation by using a empty Hash for the intial value.
146
140
  c = self.class.new({}, false)
147
- c.instance_variable_set(:@root, clone_value(@root))
141
+ c.instance_variable_set(:@root, deep_dup_value(@root))
148
142
  c
149
143
  end
150
144
 
151
- # Returns the instance converted to native Ruby values such as a Hash,
152
- # Array, etc.
153
- def native()
154
- @root
155
- end
156
-
157
- # Returns true if self and other are either the same or have the same
158
- # contents. This is a deep comparison.
159
- def eql?(other)
160
- # Any object that is of a class derived from the API class is a
161
- # candidate for being ==.
162
- return false unless other.is_a?(::WAB::Data)
163
- values_eql?(@root, other.native)
164
- end
165
- alias == eql?
166
-
167
- # Returns the length of the root element.
168
- def length()
169
- @root.length
170
- end
171
-
172
- # Returns the number of leaves in the data tree.
173
- def leaf_count()
174
- branch_count(@root)
175
- end
176
-
177
- # Returns the number of nodes in the data tree.
178
- def size()
179
- branch_size(@root)
180
- end
181
-
182
145
  # Encode the data as a JSON string.
183
146
  def json(indent=0)
184
147
  Oj.dump(@root, mode: :wab, indent: indent)
@@ -189,7 +152,8 @@ module WAB
189
152
  # UUID:: "b0ca922d-372e-41f4-8fea-47d880188ba3"
190
153
  # URI:: "http://opo.technology/sample", HTTP only
191
154
  def detect()
192
- # TBD
155
+ return detect_hash(@root) if @root.is_a?(Hash)
156
+ detect_array(@root) if @root.is_a?(Array)
193
157
  end
194
158
 
195
159
  private
@@ -202,117 +166,96 @@ module WAB
202
166
  # value:: value to validate
203
167
  def validate(value)
204
168
  if value.is_a?(Hash)
205
- value.each_pair { |k, v|
206
- raise StandardError.new("Hash keys must be Symbols.") unless k.is_a?(Symbol)
207
- validate_value(v)
208
- }
169
+ validate_hash(value)
209
170
  elsif value.is_a?(Array)
210
- value.each { |v|
211
- validate_value(v)
212
- }
171
+ value.each { |v| validate_value(v) }
213
172
  else
214
- raise StandardError.new("Data values must be either a Hash or an Array")
173
+ raise WAB::TypeError
215
174
  end
216
175
  value
217
176
  end
218
177
 
219
178
  def validate_value(value)
220
- value_class = value.class
221
- if value.nil? ||
222
- TrueClass == value_class ||
223
- FalseClass == value_class ||
224
- Integer == value_class ||
225
- Float == value_class ||
226
- String == value_class ||
227
- Time == value_class ||
228
- BigDecimal == value_class ||
229
- URI::HTTP == value_class ||
230
- ::WAB::UUID == value_class
231
- # valid values
232
- elsif Hash == value_class
233
- value.each_pair { |k, v|
234
- raise StandardError.new("Hash keys must be Symbols.") unless k.is_a?(Symbol)
235
- validate_value(v)
236
- }
237
- elsif Array == value_class
238
- value.each { |v|
239
- validate_value(v)
240
- }
241
- elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
242
- # valid value
179
+ return value if valid_class(value)
180
+ if value.is_a?(Hash)
181
+ validate_hash(value)
182
+ elsif value.is_a?(Array)
183
+ value.each { |v| validate_value(v) }
243
184
  else
244
- raise StandardError.new("#{value_class.to_s} is not a valid Data value.")
185
+ raise WAB::TypeError, "#{value_class} is not a valid Data value."
245
186
  end
246
- value
247
187
  end
248
188
 
249
- # Fix values by returing either the value or the fixed alternative. In
250
- # the cases of Hash and Array a copy is always made. (its just easier)
189
+ def validate_hash(hsh)
190
+ hsh.each_pair { |k, v|
191
+ raise WAB::KeyError unless k.is_a?(Symbol)
192
+ validate_value(v)
193
+ }
194
+ end
195
+
196
+ def valid_class(value)
197
+ value_class = value.class
198
+ value.nil? ||
199
+ TrueClass == value_class ||
200
+ FalseClass == value_class ||
201
+ Integer == value_class ||
202
+ Float == value_class ||
203
+ String == value_class ||
204
+ Time == value_class ||
205
+ BigDecimal == value_class ||
206
+ URI::HTTP == value_class ||
207
+ WAB::UUID == value_class ||
208
+ WAB::Utils.pre_24_fixnum?(value)
209
+ end
210
+
211
+ # Fix values by returing either the value or the fixed alternative.
251
212
  def fix(value)
252
- if value.is_a?(Hash)
253
- old = value
254
- value = {}
255
- old.each_pair { |k, v|
256
- k = k.to_sym unless k.is_a?(Symbol)
257
- value[k] = fix_value(v)
258
- }
259
- elsif value.is_a?(Array)
260
- old = value
261
- value = []
262
- old.each { |v|
263
- value << fix_value(v)
264
- }
213
+ value_class = value.class
214
+ if Hash == value_class
215
+ value = fix_hash(value)
216
+ elsif Array == value_class
217
+ value = value.map { |v| fix_value(v) }
265
218
  elsif value.respond_to?(:to_h) && 0 == value.method(:to_h).arity
266
219
  value = value.to_h
267
- raise StandardError.new("Data values must be either a Hash or an Array") unless value.is_a?(Hash)
220
+ raise WAB::TypeError unless value.is_a?(Hash)
268
221
  value = fix(value)
269
222
  else
270
- raise StandardError.new("Data values must be either a Hash or an Array")
223
+ raise WAB::TypeError
271
224
  end
272
225
  value
273
226
  end
274
227
 
275
228
  def fix_value(value)
229
+ return value if valid_class(value)
230
+
276
231
  value_class = value.class
277
- if value.nil? ||
278
- TrueClass == value_class ||
279
- FalseClass == value_class ||
280
- Integer == value_class ||
281
- Float == value_class ||
282
- String == value_class ||
283
- Time == value_class ||
284
- BigDecimal == value_class ||
285
- URI::HTTP == value_class ||
286
- ::WAB::UUID == value_class
287
- # valid values
288
- elsif Hash == value_class
289
- old = value
290
- value = {}
291
- old.each_pair { |k, v|
292
- k = k.to_sym unless k.is_a?(Symbol)
293
- value[k] = fix_value(v)
294
- }
232
+ if Hash == value_class
233
+ value = fix_hash(value)
295
234
  elsif Array == value_class
296
- old = value
297
- value = []
298
- old.each { |v|
299
- value << fix_value(v)
300
- }
301
- elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
302
- # valid value
235
+ value = value.map { |v| fix_value(v) }
303
236
  elsif value.respond_to?(:to_h) && 0 == value.method(:to_h).arity
304
237
  value = value.to_h
305
- raise StandardError.new("Data values must be either a Hash or an Array") unless value.is_a?(Hash)
238
+ raise WAB::TypeError unless value.is_a?(Hash)
306
239
  value = fix(value)
307
240
  elsif value.respond_to?(:to_s)
308
241
  value = value.to_s
309
- raise StandardError.new("Data values must be either a Hash or an Array") unless value.is_a?(String)
242
+ raise StandardError.new('Data values must be either a Hash or an Array') unless value.is_a?(String)
310
243
  else
311
- raise StandardError.new("#{value_class.to_s} is not a valid Data value.")
244
+ raise WAB::TypeError, "#{value_class} is not a valid Data value."
312
245
  end
313
246
  value
314
247
  end
315
248
 
249
+ def fix_hash(hsh)
250
+ old = hsh
251
+ hsh = {}
252
+ old.each_pair { |k, v|
253
+ k = k.to_sym unless k.is_a?(Symbol)
254
+ hsh[k] = fix_value(v)
255
+ }
256
+ hsh
257
+ end
258
+
316
259
  def each_node(path, value, block)
317
260
  block.call(path, value)
318
261
  if value.is_a?(Hash)
@@ -332,69 +275,49 @@ module WAB
332
275
  end
333
276
  end
334
277
 
335
- def branch_count(value)
336
- cnt = 0
278
+ def deep_dup_value(value)
337
279
  if value.is_a?(Hash)
338
- value.each_value { |v| cnt += branch_count(v) }
280
+ c = {}
281
+ value.each_pair { |k, v| c[k] = deep_dup_value(v) }
339
282
  elsif value.is_a?(Array)
340
- value.each { |v| cnt += branch_count(v) }
283
+ c = value.map { |v| deep_dup_value(v) }
341
284
  else
342
- cnt = 1
285
+ value_class = value.class
286
+ c = if value.nil? ||
287
+ TrueClass == value_class ||
288
+ FalseClass == value_class ||
289
+ Integer == value_class ||
290
+ Float == value_class ||
291
+ String == value_class ||
292
+ WAB::Utils.pre_24_fixnum?(value)
293
+ value
294
+ else
295
+ value.dup
296
+ end
343
297
  end
344
- cnt
298
+ c
345
299
  end
346
300
 
347
- def branch_size(value)
348
- cnt = 1
349
- if value.is_a?(Hash)
350
- value.each_value { |v| cnt += branch_size(v) }
351
- elsif value.is_a?(Array)
352
- value.each { |v| cnt += branch_size(v) }
353
- end
354
- cnt
301
+ def detect_hash(h)
302
+ h.each_key { |k| detect_elememt(h, k) }
355
303
  end
356
304
 
357
- def values_eql?(v0, v1)
358
- return false unless v0.class == v1.class
359
- if v0.is_a?(Hash)
360
- return false unless v0.length == v1.length
361
- v0.each_key { |k|
362
- return false unless values_eql?(v0[k], v1[k])
363
- return false unless v1.has_key?(k)
364
- }
365
- elsif v0.is_a?(Array)
366
- return false unless v0.length == v1.length
367
- v0.each_index { |i|
368
- return false unless values_eql?(v0[i], v1[i])
369
- }
370
- else
371
- v0 == v1
372
- end
305
+ def detect_array(a)
306
+ a.each_index { |i| detect_elememt(a, i) }
373
307
  end
374
308
 
375
- def clone_value(value)
376
- if value.is_a?(Hash)
377
- c = {}
378
- value.each_pair { |k, v| c[k] = clone_value(v) }
379
- elsif value.is_a?(Array)
380
- c = []
381
- value.each { |v| c << clone_value(v) }
382
- else
383
- value_class = value.class
384
- if value.nil? ||
385
- TrueClass == value_class ||
386
- FalseClass == value_class ||
387
- Integer == value_class ||
388
- Float == value_class ||
389
- String == value_class
390
- c = value
391
- elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
392
- c = value
393
- else
394
- c = value.clone
395
- end
309
+ def detect_elememt(collection, key)
310
+ item = collection[key]
311
+
312
+ case item
313
+ when Hash
314
+ detect_hash(item)
315
+ when Array
316
+ detect_array(item)
317
+ when String
318
+ element = WAB::Impl::Data.detect_string(item)
319
+ collection[key] = element unless element == item
396
320
  end
397
- c
398
321
  end
399
322
 
400
323
  end # Data
@@ -0,0 +1,26 @@
1
+
2
+ module WAB
3
+ module Impl
4
+
5
+ # The base class for expression that are used in the TQL where and filter clauses.
6
+ class Expr
7
+
8
+ def initialize()
9
+ end
10
+
11
+ # Evaluate the expression using the supplied WAB::Data object. Each
12
+ # expression subclass evaluates differently.
13
+ #
14
+ # data:: data object to evaluate against.
15
+ def eval(_data)
16
+ false
17
+ end
18
+
19
+ # Return a native Ruby representation of the expression.
20
+ def native()
21
+ []
22
+ end
23
+
24
+ end # Expr
25
+ end # Impl
26
+ end # WAB