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