wabur 0.2.0d1 → 0.4.0d1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -14
- data/bin/wabur +103 -0
- data/lib/wab/controller.rb +133 -90
- data/lib/wab/data.rb +14 -27
- data/lib/wab/errors.rb +14 -8
- data/lib/wab/impl/bool_expr.rb +25 -0
- data/lib/wab/impl/configuration.rb +166 -0
- data/lib/wab/impl/data.rb +155 -232
- data/lib/wab/impl/expr.rb +26 -0
- data/lib/wab/impl/expr_parser.rb +55 -0
- data/lib/wab/impl/exprs/and.rb +29 -0
- data/lib/wab/impl/exprs/between.rb +41 -0
- data/lib/wab/impl/exprs/eq.rb +28 -0
- data/lib/wab/impl/exprs/gt.rb +30 -0
- data/lib/wab/impl/exprs/gte.rb +30 -0
- data/lib/wab/impl/exprs/has.rb +26 -0
- data/lib/wab/impl/exprs/in.rb +28 -0
- data/lib/wab/impl/exprs/lt.rb +30 -0
- data/lib/wab/impl/exprs/lte.rb +30 -0
- data/lib/wab/impl/exprs/not.rb +27 -0
- data/lib/wab/impl/exprs/or.rb +29 -0
- data/lib/wab/impl/exprs/regex.rb +28 -0
- data/lib/wab/impl/handler.rb +95 -0
- data/lib/wab/impl/model.rb +197 -0
- data/lib/wab/impl/path_expr.rb +14 -0
- data/lib/wab/impl/shell.rb +92 -7
- data/lib/wab/impl/utils.rb +110 -0
- data/lib/wab/impl.rb +24 -0
- data/lib/wab/io/call.rb +4 -7
- data/lib/wab/io/engine.rb +128 -51
- data/lib/wab/io/shell.rb +61 -64
- data/lib/wab/io.rb +0 -2
- data/lib/wab/open_controller.rb +43 -0
- data/lib/wab/shell.rb +46 -61
- data/lib/wab/shell_logger.rb +13 -0
- data/lib/wab/utils.rb +36 -0
- data/lib/wab/uuid.rb +3 -6
- data/lib/wab/version.rb +2 -2
- data/lib/wab.rb +3 -0
- data/pages/Plan.md +20 -14
- data/test/bench_io_shell.rb +49 -0
- data/test/{impl_test.rb → helper.rb} +2 -4
- data/test/mirror_controller.rb +16 -0
- data/test/test_configuration.rb +38 -0
- data/test/test_data.rb +207 -0
- data/test/test_expr.rb +35 -0
- data/test/test_expr_and.rb +24 -0
- data/test/test_expr_between.rb +43 -0
- data/test/test_expr_eq.rb +24 -0
- data/test/test_expr_gt.rb +24 -0
- data/test/test_expr_gte.rb +24 -0
- data/test/test_expr_has.rb +19 -0
- data/test/test_expr_in.rb +24 -0
- data/test/test_expr_lt.rb +24 -0
- data/test/test_expr_lte.rb +24 -0
- data/test/test_expr_not.rb +22 -0
- data/test/test_expr_or.rb +24 -0
- data/test/test_expr_regex.rb +30 -0
- data/test/test_impl.rb +38 -0
- data/test/test_io_shell.rb +189 -0
- data/test/test_model.rb +31 -0
- data/test/test_runner.rb +177 -0
- data/test/tests.rb +3 -8
- metadata +91 -18
- data/lib/wab/model.rb +0 -136
- data/lib/wab/view.rb +0 -21
- data/test/data_test.rb +0 -253
- 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 <
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
78
|
-
if value.is_a?(
|
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
|
-
|
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
|
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,
|
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
|
-
|
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
|
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
|
173
|
+
raise WAB::TypeError
|
215
174
|
end
|
216
175
|
value
|
217
176
|
end
|
218
177
|
|
219
178
|
def validate_value(value)
|
220
|
-
|
221
|
-
if value.
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
185
|
+
raise WAB::TypeError, "#{value_class} is not a valid Data value."
|
245
186
|
end
|
246
|
-
value
|
247
187
|
end
|
248
188
|
|
249
|
-
|
250
|
-
|
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
|
-
|
253
|
-
|
254
|
-
value =
|
255
|
-
|
256
|
-
|
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
|
220
|
+
raise WAB::TypeError unless value.is_a?(Hash)
|
268
221
|
value = fix(value)
|
269
222
|
else
|
270
|
-
raise
|
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
|
278
|
-
|
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
|
-
|
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
|
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(
|
242
|
+
raise StandardError.new('Data values must be either a Hash or an Array') unless value.is_a?(String)
|
310
243
|
else
|
311
|
-
raise
|
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
|
336
|
-
cnt = 0
|
278
|
+
def deep_dup_value(value)
|
337
279
|
if value.is_a?(Hash)
|
338
|
-
|
280
|
+
c = {}
|
281
|
+
value.each_pair { |k, v| c[k] = deep_dup_value(v) }
|
339
282
|
elsif value.is_a?(Array)
|
340
|
-
value.
|
283
|
+
c = value.map { |v| deep_dup_value(v) }
|
341
284
|
else
|
342
|
-
|
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
|
-
|
298
|
+
c
|
345
299
|
end
|
346
300
|
|
347
|
-
def
|
348
|
-
|
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
|
358
|
-
|
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
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
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
|