wabur 0.2.0d1 → 0.4.0d1

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 (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