wabur 0.1.0d1 → 0.1.0d2
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 +17 -3
- data/lib/wab/controller.rb +0 -0
- data/lib/wab/data.rb +47 -18
- data/lib/wab/impl/data.rb +371 -0
- data/lib/wab/impl/shell.rb +33 -0
- data/lib/wab/impl.rb +11 -0
- data/lib/wab/model.rb +0 -0
- data/lib/wab/shell.rb +15 -0
- data/lib/wab/uuid.rb +28 -0
- data/lib/wab/version.rb +1 -1
- data/lib/wab/view.rb +0 -0
- data/lib/wab.rb +5 -0
- data/pages/Plan.md +16 -6
- data/test/data_test.rb +254 -0
- data/test/impl_test.rb +14 -0
- metadata +69 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4ae2c00f65bf680b3432bd10ec6135aeed7e6bc
|
4
|
+
data.tar.gz: 01f5f822d40665b8f314d3744ccdcf7bf35c071c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48320600e036669b67ba89b0b241440891af68ca7f59c287e632a96e3df1512cc1b917c0bb8425d9d4e7040d4c145ccb5ed28d0d3f62b228aceecc40e58f3ad2
|
7
|
+
data.tar.gz: 7f83da64ecc5ed24f2bc2e225204f757873fda7f2843634bae74afb7be3dcf497098aa3d2754aa2be7f8ade8535b3096e25b3a7c9429723ead367cc1ed42bcd9
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# WABuR (Web Application Builder using Ruby)
|
2
2
|
|
3
|
+
[](http://travis-ci.org/ohler55/wabur?branch=develop)  
|
4
|
+
|
3
5
|
Ruby is a great language but for performance C is a better alternative. It is
|
4
6
|
possible to get the best of both as evident with [Oj](http://www.ohler.com/oj)
|
5
7
|
and [Ox](http://www.ohler.com/ox). C by itself allowed
|
@@ -43,16 +45,28 @@ C shell that handles HTTP and data storage.
|
|
43
45
|
|
44
46
|
[Continue reading ...](pages/Architecture.md)
|
45
47
|
|
46
|
-
## Participate
|
48
|
+
## Participate and Contribute
|
47
49
|
|
48
50
|
If you like the idea and want to help out or become a core developer on the
|
49
51
|
project send me an [email](mailto:peter@ohler.com). Get in on the ground floor
|
50
52
|
and lets make something awesome together.
|
51
53
|
|
54
|
+
### Guidelines
|
55
|
+
|
56
|
+
These are the simple guidelines for contrinuting.
|
57
|
+
|
58
|
+
1. Coordinate with me first before getting started to avoid duplication of
|
59
|
+
effort or implementing something in conflict with the plans.
|
60
|
+
|
61
|
+
2. Branch off the develop branch and submit a PR.
|
62
|
+
|
63
|
+
3. Write unit tests.
|
64
|
+
|
65
|
+
4. Write straight forward, clean, and simple code. No magic stuff, no monkey
|
66
|
+
patching Ruby core classes, and no inheriting from core classes.
|
67
|
+
|
52
68
|
## Planning
|
53
69
|
|
54
70
|
The plan is informal and high level until more details are defined.
|
55
71
|
|
56
72
|
[Details ...](pages/Plan.md)
|
57
|
-
|
58
|
-
|
data/lib/wab/controller.rb
CHANGED
File without changes
|
data/lib/wab/data.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
|
2
|
+
require 'uri'
|
3
|
+
|
2
4
|
module WAB
|
3
5
|
|
4
6
|
# The class representing the cananical data structure in WAB. Typically the
|
@@ -7,21 +9,12 @@ module WAB
|
|
7
9
|
# class (has the same methods and behavior).
|
8
10
|
class Data
|
9
11
|
|
10
|
-
# This method is included only for testing purposes of the Ruby base
|
11
|
-
# Shell. It should only be called by the Shell. Create a new Data instance
|
12
|
-
# with the initial value provided. The value must be a Hash or Array. The
|
13
|
-
# members of the Hash or Array must be nil, boolean, String, Integer,
|
14
|
-
# Float, BigDecimal, Array, Hash, Time, WAB::UUID, or WAB::IRI.
|
15
|
-
def initialize(value=nil)
|
16
|
-
# TBD
|
17
|
-
end
|
18
|
-
|
19
12
|
# Gets the Data element or value identified by the path where the path
|
20
13
|
# elements are separated by the '.' character. The path can also be a
|
21
14
|
# array of path node identifiers. For example, child.grandchild is the
|
22
15
|
# same as ['child', 'grandchild'].
|
23
16
|
def get(path)
|
24
|
-
|
17
|
+
raise NotImplementedError.new
|
25
18
|
end
|
26
19
|
|
27
20
|
# Sets the node value identified by the path where the path elements are
|
@@ -29,44 +22,80 @@ module WAB
|
|
29
22
|
# node identifiers. For example, child.grandchild is the same as ['child',
|
30
23
|
# 'grandchild']. The value must be one of the allowed data values
|
31
24
|
# described in the initialize method.
|
25
|
+
#
|
26
|
+
# For arrays, the behavior is similar to an Array#[] with the exception
|
27
|
+
# of a negative index less than the negative length in which case the
|
28
|
+
# value is prepended (Array#unshift).
|
29
|
+
#
|
30
|
+
# path:: path to location to be set
|
31
|
+
# value:: value to set
|
32
|
+
# repair:: flag indicating invalid value should be repaired if possible
|
32
33
|
def set(path, value)
|
33
|
-
|
34
|
+
raise NotImplementedError.new
|
34
35
|
end
|
35
36
|
|
36
37
|
# Each child of the Data instance is provided as an argument to a block
|
37
38
|
# when the each method is called.
|
38
39
|
def each()
|
39
|
-
|
40
|
+
raise NotImplementedError.new
|
40
41
|
end
|
41
42
|
|
42
43
|
# Each leaf of the Data instance is provided as an argument to a block
|
43
44
|
# when the each method is called. A leaf is a primitive that has no
|
44
45
|
# children and will be nil, a Boolean, String, Numberic, Time, WAB::UUID,
|
45
|
-
# or
|
46
|
+
# or URI.
|
46
47
|
def each_leaf()
|
47
|
-
|
48
|
+
raise NotImplementedError.new
|
48
49
|
end
|
49
50
|
|
50
51
|
# Make a deep copy of the Data instance.
|
51
52
|
def clone()
|
52
|
-
|
53
|
+
raise NotImplementedError.new
|
53
54
|
end
|
54
55
|
|
55
56
|
# Returns the instance converted to native Ruby values such as a Hash,
|
56
57
|
# Array, etc.
|
57
58
|
def native()
|
58
|
-
|
59
|
+
raise NotImplementedError.new
|
59
60
|
end
|
60
61
|
|
61
62
|
# Returns true if self and other are either the same or have the same
|
62
63
|
# contents. This is a deep comparison.
|
63
64
|
def eql?(other)
|
64
|
-
|
65
|
+
raise NotImplementedError.new
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the length of the root element.
|
69
|
+
def length()
|
70
|
+
raise NotImplementedError.new
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the number of leaves in the data tree.
|
74
|
+
def leaf_count()
|
75
|
+
raise NotImplementedError.new
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the number of nodes in the data tree.
|
79
|
+
def size()
|
80
|
+
raise NotImplementedError.new
|
65
81
|
end
|
66
82
|
|
67
83
|
# Encode the data as a JSON string.
|
68
84
|
def json(indent=0)
|
69
|
-
|
85
|
+
raise NotImplementedError.new
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# This method is included only to raise an error if an attempt is made to
|
91
|
+
# create an instance directly. The Shell implementation should provide a
|
92
|
+
# similar initializer which should create a new Data instance with the
|
93
|
+
# initial value provided. The value must be a Hash or Array. The members
|
94
|
+
# of the Hash or Array must be nil, boolean, String, Integer, Float,
|
95
|
+
# BigDecimal, Array, Hash, Time, URI::HTTP, or WAB::UUID. Keys to Hashes
|
96
|
+
# must be Symbols.
|
97
|
+
def initialize(value=nil, repair=false)
|
98
|
+
raise NotImplementedError.new
|
70
99
|
end
|
71
100
|
|
72
101
|
end # Data
|
@@ -0,0 +1,371 @@
|
|
1
|
+
|
2
|
+
require 'uri'
|
3
|
+
require 'oj'
|
4
|
+
|
5
|
+
module WAB
|
6
|
+
module Impl
|
7
|
+
|
8
|
+
# The class representing the cananical data structure in WAB. Typically
|
9
|
+
# the Data instances are factory created by the Shell and will most likely
|
10
|
+
# not be instance of this class but rather a class that is a duck-type of
|
11
|
+
# this class (has the same methods and behavior).
|
12
|
+
class Data < ::WAB::Data
|
13
|
+
|
14
|
+
# This method should not be called directly. New instances should be
|
15
|
+
# created by using a Shell#data method.
|
16
|
+
#
|
17
|
+
# Creates a new Data instance with the initial value provided. The value
|
18
|
+
# must be a Hash or Array. The members of the Hash or Array must be nil,
|
19
|
+
# boolean, String, Integer, Float, BigDecimal, Array, Hash, Time,
|
20
|
+
# WAB::UUID, or the Ruby URI::HTTP.
|
21
|
+
#
|
22
|
+
# value:: initial value
|
23
|
+
# repair:: flag indicating invalid value should be repaired if possible
|
24
|
+
def initialize(value, repair)
|
25
|
+
if repair
|
26
|
+
value = fix(value)
|
27
|
+
else
|
28
|
+
validate(value)
|
29
|
+
end
|
30
|
+
@root = value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Gets the Data element or value identified by the path where the path
|
34
|
+
# elements are separated by the '.' character. The path can also be a
|
35
|
+
# array of path node identifiers. For example, child.grandchild is the
|
36
|
+
# same as ['child', 'grandchild'].
|
37
|
+
def get(path)
|
38
|
+
path = path.to_s.split('.') unless path.is_a?(Array)
|
39
|
+
node = @root
|
40
|
+
path.each { |key|
|
41
|
+
if node.is_a?(Hash)
|
42
|
+
node = node[key.to_sym]
|
43
|
+
elsif node.is_a?(Array)
|
44
|
+
i = key.to_i
|
45
|
+
if 0 == i && '0' != key && 0 != key
|
46
|
+
node = nil
|
47
|
+
break
|
48
|
+
end
|
49
|
+
node = node[i]
|
50
|
+
else
|
51
|
+
node = nil
|
52
|
+
break
|
53
|
+
end
|
54
|
+
}
|
55
|
+
node
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sets the node value identified by the path where the path elements are
|
59
|
+
# separated by the '.' character. The path can also be a array of path
|
60
|
+
# node identifiers. For example, child.grandchild is the same as ['child',
|
61
|
+
# 'grandchild']. The value must be one of the allowed data values
|
62
|
+
# described in the initialize method.
|
63
|
+
#
|
64
|
+
# For arrays, the behavior is similar to an Array#[] with the exception
|
65
|
+
# of a negative index less than the negative length in which case the
|
66
|
+
# value is prepended (Array#unshift).
|
67
|
+
#
|
68
|
+
# path:: path to location to be set
|
69
|
+
# value:: value to set
|
70
|
+
# repair:: flag indicating invalid value should be repaired if possible
|
71
|
+
def set(path, value, repair=false)
|
72
|
+
raise StandardError.new("path can not be empty.") if path.empty?
|
73
|
+
if repair
|
74
|
+
value = fix_value(value)
|
75
|
+
else
|
76
|
+
validate_value(value)
|
77
|
+
end
|
78
|
+
node = @root
|
79
|
+
path = path.to_s.split('.') unless path.is_a?(Array)
|
80
|
+
path[0..-2].each { |key|
|
81
|
+
if node.is_a?(Hash)
|
82
|
+
key = key.to_sym
|
83
|
+
node[key] = {} unless node.has_key?(key)
|
84
|
+
node = node[key]
|
85
|
+
elsif node.is_a?(Array)
|
86
|
+
i = key.to_i
|
87
|
+
raise StandardError.new("path key must be an integer for an Array.") if (0 == i && '0' != key && 0 != key)
|
88
|
+
if i < node.length && -node.length < i
|
89
|
+
node = node[i]
|
90
|
+
else
|
91
|
+
# TBD if next key is a number then make an array instead
|
92
|
+
nn = {}
|
93
|
+
if i < -node.length
|
94
|
+
node.unshift(nn)
|
95
|
+
else
|
96
|
+
node[i] = nn
|
97
|
+
end
|
98
|
+
node = nn
|
99
|
+
end
|
100
|
+
else
|
101
|
+
raise StandardError.new("Can not set a member of an #{node.class}.")
|
102
|
+
end
|
103
|
+
}
|
104
|
+
key = path[-1]
|
105
|
+
if node.is_a?(Hash)
|
106
|
+
key = key.to_sym
|
107
|
+
node[key] = value
|
108
|
+
elsif node.is_a?(Array)
|
109
|
+
i = key.to_i
|
110
|
+
raise StandardError.new("path key must be an integer for an Array.") if (0 == i && '0' != key && 0 != key)
|
111
|
+
if i < -node.length
|
112
|
+
node.unshift(value)
|
113
|
+
else
|
114
|
+
node[i] = value
|
115
|
+
end
|
116
|
+
else
|
117
|
+
raise StandardError.new("Can not set a member of an #{node.class}.")
|
118
|
+
end
|
119
|
+
value
|
120
|
+
end
|
121
|
+
|
122
|
+
# Each child of the Data instance is provided as an argument to a block
|
123
|
+
# when the each method is called.
|
124
|
+
def each(&block)
|
125
|
+
each_node([], @root, block)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Each leaf of the Data instance is provided as an argument to a block
|
129
|
+
# when the each method is called. A leaf is a primitive that has no
|
130
|
+
# children and will be nil, a Boolean, String, Numberic, Time, WAB::UUID,
|
131
|
+
# or URI.
|
132
|
+
def each_leaf(&block)
|
133
|
+
each_leaf_node([], @root, block)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Make a deep copy of the Data instance.
|
137
|
+
def clone()
|
138
|
+
# avoid validation by using a empty Hash for the intial value.
|
139
|
+
c = self.class.new({}, false)
|
140
|
+
c.instance_variable_set(:@root, clone_value(@root))
|
141
|
+
c
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the instance converted to native Ruby values such as a Hash,
|
145
|
+
# Array, etc.
|
146
|
+
def native()
|
147
|
+
@root
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns true if self and other are either the same or have the same
|
151
|
+
# contents. This is a deep comparison.
|
152
|
+
def eql?(other)
|
153
|
+
# Any object that is of a class derived from the API class is a
|
154
|
+
# candidate for being ==.
|
155
|
+
return false unless other.is_a?(::WAB::Data)
|
156
|
+
values_eql?(@root, other.native)
|
157
|
+
end
|
158
|
+
alias == eql?
|
159
|
+
|
160
|
+
# Returns the length of the root element.
|
161
|
+
def length()
|
162
|
+
@root.length
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns the number of leaves in the data tree.
|
166
|
+
def leaf_count()
|
167
|
+
branch_count(@root)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns the number of nodes in the data tree.
|
171
|
+
def size()
|
172
|
+
branch_size(@root)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Encode the data as a JSON string.
|
176
|
+
def json(indent=0)
|
177
|
+
Oj.dump(@root, mode: :wab, indent: indent)
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# Raise an exception if the value is not a suitable data element. If the
|
183
|
+
# repair flag is true an attempt is made to fix the value if possible by
|
184
|
+
# replacing non-symbol keys with Symbols and converting unsupported
|
185
|
+
# objects with a to_h or a to_s.
|
186
|
+
#
|
187
|
+
# value:: value to validate
|
188
|
+
def validate(value)
|
189
|
+
if value.is_a?(Hash)
|
190
|
+
value.each_pair { |k, v|
|
191
|
+
raise StandardError.new("Hash keys must be Symbols.") unless k.is_a?(Symbol)
|
192
|
+
validate_value(v)
|
193
|
+
}
|
194
|
+
elsif value.is_a?(Array)
|
195
|
+
value.each { |v|
|
196
|
+
validate_value(v)
|
197
|
+
}
|
198
|
+
else
|
199
|
+
raise StandardError.new("Data values must be either a Hash or an Array")
|
200
|
+
end
|
201
|
+
value
|
202
|
+
end
|
203
|
+
|
204
|
+
def validate_value(value)
|
205
|
+
value_class = value.class
|
206
|
+
if value.nil? ||
|
207
|
+
TrueClass == value_class ||
|
208
|
+
FalseClass == value_class ||
|
209
|
+
Integer == value_class ||
|
210
|
+
Float == value_class ||
|
211
|
+
String == value_class ||
|
212
|
+
Time == value_class ||
|
213
|
+
BigDecimal == value_class ||
|
214
|
+
URI::HTTP == value_class ||
|
215
|
+
::WAB::UUID == value_class
|
216
|
+
# valid values
|
217
|
+
elsif Hash == value_class
|
218
|
+
value.each_pair { |k, v|
|
219
|
+
raise StandardError.new("Hash keys must be Symbols.") unless k.is_a?(Symbol)
|
220
|
+
validate_value(v)
|
221
|
+
}
|
222
|
+
elsif Array == value_class
|
223
|
+
value.each { |v|
|
224
|
+
validate_value(v)
|
225
|
+
}
|
226
|
+
else
|
227
|
+
raise StandardError.new("#{value_class.to_s} is not a valid Data value.")
|
228
|
+
end
|
229
|
+
value
|
230
|
+
end
|
231
|
+
|
232
|
+
# Fix values by returing either the value or the fixed alternative. In
|
233
|
+
# the cases of Hash and Array a copy is always made. (its just easier)
|
234
|
+
def fix(value)
|
235
|
+
if value.is_a?(Hash)
|
236
|
+
old = value
|
237
|
+
value = {}
|
238
|
+
old.each_pair { |k, v|
|
239
|
+
k = k.to_sym unless k.is_a?(Symbol)
|
240
|
+
value[k] = fix_value(v)
|
241
|
+
}
|
242
|
+
elsif value.is_a?(Array)
|
243
|
+
old = value
|
244
|
+
value = []
|
245
|
+
old.each { |v|
|
246
|
+
value << fix_value(v)
|
247
|
+
}
|
248
|
+
elsif value.respond_to?(:to_h) && 0 == value.method(:to_h).arity
|
249
|
+
value = value.to_h
|
250
|
+
raise StandardError.new("Data values must be either a Hash or an Array") unless value.is_a?(Hash)
|
251
|
+
value = fix(value)
|
252
|
+
else
|
253
|
+
raise StandardError.new("Data values must be either a Hash or an Array")
|
254
|
+
end
|
255
|
+
value
|
256
|
+
end
|
257
|
+
|
258
|
+
def fix_value(value)
|
259
|
+
value_class = value.class
|
260
|
+
if value.nil? ||
|
261
|
+
TrueClass == value_class ||
|
262
|
+
FalseClass == value_class ||
|
263
|
+
Integer == value_class ||
|
264
|
+
Float == value_class ||
|
265
|
+
String == value_class ||
|
266
|
+
Time == value_class ||
|
267
|
+
BigDecimal == value_class ||
|
268
|
+
URI::HTTP == value_class ||
|
269
|
+
::WAB::UUID == value_class
|
270
|
+
# valid values
|
271
|
+
elsif Hash == value_class
|
272
|
+
old = value
|
273
|
+
value = {}
|
274
|
+
old.each_pair { |k, v|
|
275
|
+
k = k.to_sym unless k.is_a?(Symbol)
|
276
|
+
value[k] = fix_value(v)
|
277
|
+
}
|
278
|
+
elsif Array == value_class
|
279
|
+
old = value
|
280
|
+
value = []
|
281
|
+
old.each { |v|
|
282
|
+
value << fix_value(v)
|
283
|
+
}
|
284
|
+
elsif value.respond_to?(:to_h) && 0 == value.method(:to_h).arity
|
285
|
+
value = value.to_h
|
286
|
+
raise StandardError.new("Data values must be either a Hash or an Array") unless value.is_a?(Hash)
|
287
|
+
value = fix(value)
|
288
|
+
elsif value.respond_to?(:to_s)
|
289
|
+
value = value.to_s
|
290
|
+
raise StandardError.new("Data values must be either a Hash or an Array") unless value.is_a?(String)
|
291
|
+
else
|
292
|
+
raise StandardError.new("#{value_class.to_s} is not a valid Data value.")
|
293
|
+
end
|
294
|
+
value
|
295
|
+
end
|
296
|
+
|
297
|
+
def each_node(path, value, block)
|
298
|
+
block.call(path, value)
|
299
|
+
if value.is_a?(Hash)
|
300
|
+
value.each_pair { |k, v| each_node(path + [k], v, block) }
|
301
|
+
elsif value.is_a?(Array)
|
302
|
+
value.each_index { |i| each_node(path + [i], value[i], block) }
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def each_leaf_node(path, value, block)
|
307
|
+
if value.is_a?(Hash)
|
308
|
+
value.each_pair { |k, v| each_leaf_node(path + [k], v, block) }
|
309
|
+
elsif value.is_a?(Array)
|
310
|
+
value.each_index { |i| each_leaf_node(path + [i], value[i], block) }
|
311
|
+
else
|
312
|
+
block.call(path, value)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def branch_count(value)
|
317
|
+
cnt = 0
|
318
|
+
if value.is_a?(Hash)
|
319
|
+
value.each_value { |v| cnt += branch_count(v) }
|
320
|
+
elsif value.is_a?(Array)
|
321
|
+
value.each { |v| cnt += branch_count(v) }
|
322
|
+
else
|
323
|
+
cnt = 1
|
324
|
+
end
|
325
|
+
cnt
|
326
|
+
end
|
327
|
+
|
328
|
+
def branch_size(value)
|
329
|
+
cnt = 1
|
330
|
+
if value.is_a?(Hash)
|
331
|
+
value.each_value { |v| cnt += branch_size(v) }
|
332
|
+
elsif value.is_a?(Array)
|
333
|
+
value.each { |v| cnt += branch_size(v) }
|
334
|
+
end
|
335
|
+
cnt
|
336
|
+
end
|
337
|
+
|
338
|
+
def values_eql?(v0, v1)
|
339
|
+
return false unless v0.class == v1.class
|
340
|
+
if v0.is_a?(Hash)
|
341
|
+
return false unless v0.length == v1.length
|
342
|
+
v0.each_key { |k|
|
343
|
+
return false unless values_eql?(v0[k], v1[k])
|
344
|
+
return false unless v1.has_key?(k)
|
345
|
+
}
|
346
|
+
elsif v0.is_a?(Array)
|
347
|
+
return false unless v0.length == v1.length
|
348
|
+
v0.each_index { |i|
|
349
|
+
return false unless values_eql?(v0[i], v1[i])
|
350
|
+
}
|
351
|
+
else
|
352
|
+
v0 == v1
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def clone_value(value)
|
357
|
+
if value.is_a?(Hash)
|
358
|
+
c = {}
|
359
|
+
value.each_pair { |k, v| c[k] = clone_value(v) }
|
360
|
+
elsif value.is_a?(Array)
|
361
|
+
c = []
|
362
|
+
value.each { |v| c << clone_value(v) }
|
363
|
+
else
|
364
|
+
c = value.clone
|
365
|
+
end
|
366
|
+
c
|
367
|
+
end
|
368
|
+
|
369
|
+
end # Data
|
370
|
+
end # Impl
|
371
|
+
end # WAB
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require 'wab'
|
3
|
+
|
4
|
+
module WAB
|
5
|
+
|
6
|
+
module Impl
|
7
|
+
|
8
|
+
# The shell for reference Ruby implementation.
|
9
|
+
class Shell < ::WAB::Shell
|
10
|
+
|
11
|
+
# Sets up the shell with a view, model, and type_key.
|
12
|
+
def initialize(view, model, type_key='kind')
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
# Create and return a new data instance with the provided initial value.
|
17
|
+
# The value must be a Hash or Array. The members of the Hash or Array
|
18
|
+
# must be nil, boolean, String, Integer, Float, BigDecimal, Array, Hash,
|
19
|
+
# Time, URI::HTTP, or WAB::UUID. Keys to Hashes must be Symbols.
|
20
|
+
#
|
21
|
+
# If the repair flag is true then an attempt will be made to fix the
|
22
|
+
# value by replacing String keys with Symbols and calling to_h or to_s
|
23
|
+
# on unsupported Objects.
|
24
|
+
#
|
25
|
+
# value:: initial value
|
26
|
+
# repair:: flag indicating invalid value should be repaired if possible
|
27
|
+
def data(value={}, repair=false)
|
28
|
+
Data.new(value, repair)
|
29
|
+
end
|
30
|
+
|
31
|
+
end # Shell
|
32
|
+
end # Impl
|
33
|
+
end # WAB
|
data/lib/wab/impl.rb
ADDED
data/lib/wab/model.rb
CHANGED
File without changes
|
data/lib/wab/shell.rb
CHANGED
@@ -42,5 +42,20 @@ module WAB
|
|
42
42
|
@controllers[type] = controller
|
43
43
|
end
|
44
44
|
|
45
|
+
# Create and return a new data instance with the provided initial value.
|
46
|
+
# The value must be a Hash or Array. The members of the Hash or Array must
|
47
|
+
# be nil, boolean, String, Integer, Float, BigDecimal, Array, Hash, Time,
|
48
|
+
# URI::HTTP, or WAB::UUID. Keys to Hashes must be Symbols.
|
49
|
+
#
|
50
|
+
# If the repair flag is true then an attempt will be made to fix the value
|
51
|
+
# by replacing String keys with Symbols and calling to_h or to_s on
|
52
|
+
# unsupported Objects.
|
53
|
+
#
|
54
|
+
# value:: initial value
|
55
|
+
# repair:: flag indicating invalid value should be repaired if possible
|
56
|
+
def data(value=nil, repair=false)
|
57
|
+
raise NotImplementedError.new
|
58
|
+
end
|
59
|
+
|
45
60
|
end # Shell
|
46
61
|
end # WAB
|
data/lib/wab/uuid.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
|
4
|
+
# The UUID class representing a 128 bit UUID although values are not
|
5
|
+
# validated for conformane to the ISO/IEC specifications.
|
6
|
+
class UUID
|
7
|
+
|
8
|
+
attr_reader :id
|
9
|
+
|
10
|
+
# Initializes a UUID from string representation of the UUID
|
11
|
+
# following the pattern "123e4567-e89b-12d3-a456-426655440000".
|
12
|
+
def initialize(id)
|
13
|
+
@id = id.downcase
|
14
|
+
# TBD change to WAB exception
|
15
|
+
raise Exception.new("Invalid UUID format.") if /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.match(@id).nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the string representation of the UUID.
|
19
|
+
def to_s
|
20
|
+
@id
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
other.is_a?(self.class) && @id == other.id
|
25
|
+
end
|
26
|
+
|
27
|
+
end # UUID
|
28
|
+
end # WAB
|
data/lib/wab/version.rb
CHANGED
data/lib/wab/view.rb
CHANGED
File without changes
|
data/lib/wab.rb
CHANGED
data/pages/Plan.md
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
# Plan
|
2
2
|
|
3
|
-
More details will be added here as progress continues. For now the
|
3
|
+
More details will be added here as progress continues. For now the high level plan is:
|
4
|
+
|
5
|
+
- _Define APIs - initial complete_
|
6
|
+
|
7
|
+
- Start the WAB::Dev module
|
8
|
+
- WAB::Dev::Data class
|
9
|
+
- WAB::Dev::UUID class
|
10
|
+
- WAB::Dev::Model class
|
11
|
+
- WAB::Dev::Shell class
|
12
|
+
- WAB::Dev::View class
|
13
|
+
- WAB::Dev::Controller class
|
14
|
+
|
15
|
+
- Extern Glue implemenation and test
|
16
|
+
- this is in the WAB module
|
4
17
|
|
5
|
-
- Define APIs
|
6
|
-
- View API
|
7
|
-
- Model/Store API
|
8
18
|
- Structure Ruby Framework
|
9
|
-
|
10
|
-
|
19
|
+
- Embedded Glue layer
|
20
|
+
- External Glue layer
|
11
21
|
|
data/test/data_test.rb
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
$: << File.dirname(__FILE__)
|
5
|
+
$: << File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'lib')
|
6
|
+
$: << File.join(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))), 'oj', 'ext')
|
7
|
+
$: << File.join(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))), 'oj', 'lib')
|
8
|
+
|
9
|
+
require 'minitest'
|
10
|
+
require 'minitest/autorun'
|
11
|
+
|
12
|
+
require 'wab'
|
13
|
+
require 'wab/impl'
|
14
|
+
|
15
|
+
$shell = ::WAB::Impl::Shell.new(nil, nil) if $shell.nil?
|
16
|
+
|
17
|
+
class DataTest < Minitest::Test
|
18
|
+
|
19
|
+
class ToHash
|
20
|
+
def initialize(x, y)
|
21
|
+
@h = {x: x, y: y }
|
22
|
+
end
|
23
|
+
def to_h
|
24
|
+
@h
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_json
|
29
|
+
d = $shell.data({
|
30
|
+
boo: true,
|
31
|
+
n: nil,
|
32
|
+
num: 7,
|
33
|
+
float: 7.654,
|
34
|
+
str: 'a string',
|
35
|
+
t: Time.gm(2017, 1, 5, 15, 4, 33.123456789),
|
36
|
+
big: BigDecimal('63.21'),
|
37
|
+
uri: URI('http://opo.technology/sample'),
|
38
|
+
uuid: ::WAB::UUID.new('b0ca922d-372e-41f4-8fea-47d880188ba3'),
|
39
|
+
a: [],
|
40
|
+
h: {},
|
41
|
+
})
|
42
|
+
assert_equal(%|{
|
43
|
+
"boo":true,
|
44
|
+
"n":null,
|
45
|
+
"num":7,
|
46
|
+
"float":7.654,
|
47
|
+
"str":"a string",
|
48
|
+
"t":"2017-01-05T15:04:33.123456789Z",
|
49
|
+
"big":0.6321e2,
|
50
|
+
"uri":"http://opo.technology/sample",
|
51
|
+
"uuid":"b0ca922d-372e-41f4-8fea-47d880188ba3",
|
52
|
+
"a":[],
|
53
|
+
"h":{}
|
54
|
+
}
|
55
|
+
|, d.json(2))
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_validate_keys
|
59
|
+
assert_raises() { d = $shell.data({ 'a' => 1}) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_repair_keys
|
63
|
+
d = $shell.data({ 'a' => 1}, true)
|
64
|
+
assert_equal({a:1}, d.native, "data not repaired")
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_validate_non_hash_array
|
68
|
+
assert_raises() { d = $shell.data(123) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_fix_non_hash_array
|
72
|
+
# can not fix this one
|
73
|
+
assert_raises() { d = $shell.data(123, true) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_validate_object
|
77
|
+
assert_raises() { d = $shell.data({a: 1..3}) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_repair_to_s_object
|
81
|
+
d = $shell.data({a: 1..3}, true)
|
82
|
+
assert_equal({a:'1..3'}, d.native, "data not repaired")
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_repair_to_h_object
|
86
|
+
d = $shell.data(ToHash.new(1, 2), true)
|
87
|
+
assert_equal({x:1,y:2}, d.native, "data not repaired")
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_hash_get
|
91
|
+
d = $shell.data({a: 1, b: 2})
|
92
|
+
assert_equal(2, d.get('b'), "failed to get 'b'")
|
93
|
+
assert_equal(2, d.get([:b]), "failed to get [:b]")
|
94
|
+
assert_equal(1, d.get(['a']), "failed to get ['a']")
|
95
|
+
assert_nil(d.get(['d']), "failed to get ['d']")
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_array_get
|
99
|
+
d = $shell.data(['a', 'b', 'c'])
|
100
|
+
assert_equal('b', d.get('1'), "failed to get '1'")
|
101
|
+
assert_equal('c', d.get(['2']), "failed to get ['2']")
|
102
|
+
assert_equal('b', d.get([1]), "failed to get [1]")
|
103
|
+
assert_equal('a', d.get([0]), "failed to get [0]")
|
104
|
+
assert_equal('c', d.get([-1]), "failed to get [-1]")
|
105
|
+
assert_nil(d.get([4]), "failed to get [4]")
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_get_mixed
|
109
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
110
|
+
assert_equal(3, d.get('b.2.c'), "failed to get 'b.2.c'")
|
111
|
+
assert_equal(3, d.get([:b, 2, 'c']), "failed to get [b, 2, c]")
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_hash_set
|
115
|
+
d = $shell.data({a: 1})
|
116
|
+
d.set('b', 2)
|
117
|
+
d.set([:c], 3)
|
118
|
+
d.set([:d], 1..4, true)
|
119
|
+
assert_raises() { d.set([:e], 1..5) }
|
120
|
+
assert_equal(%|{
|
121
|
+
"a":1,
|
122
|
+
"b":2,
|
123
|
+
"c":3,
|
124
|
+
"d":"1..4"
|
125
|
+
}
|
126
|
+
|, d.json(2))
|
127
|
+
# replace existing
|
128
|
+
d.set([:c], -3)
|
129
|
+
assert_equal(%|{
|
130
|
+
"a":1,
|
131
|
+
"b":2,
|
132
|
+
"c":-3,
|
133
|
+
"d":"1..4"
|
134
|
+
}
|
135
|
+
|, d.json(2))
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_array_set
|
139
|
+
d = $shell.data(['x'])
|
140
|
+
d.set([2], 'd')
|
141
|
+
assert_equal(%|["x",null,"d"]|, d.json(), "after d")
|
142
|
+
d.set([1], 'c')
|
143
|
+
assert_equal(%|["x","c","d"]|, d.json(), "after c")
|
144
|
+
d.set([0], 'y')
|
145
|
+
assert_equal(%|["y","c","d"]|, d.json(), "after y")
|
146
|
+
d.set([-3], 'b')
|
147
|
+
assert_equal(%|["b","c","d"]|, d.json(), "after b")
|
148
|
+
d.set([-4], 'a')
|
149
|
+
assert_equal(%|["a","b","c","d"]|, d.json(), "after a")
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_set_mixed
|
153
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
154
|
+
d.set('b.2', 'c')
|
155
|
+
assert_equal(%|{"a":1,"b":["a","b","c"]}|, d.json())
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_hash_length
|
159
|
+
d = $shell.data({a: 1, b: 2})
|
160
|
+
assert_equal(2, d.length())
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_array_length
|
164
|
+
d = $shell.data(['a', 'b', 'c'])
|
165
|
+
assert_equal(3, d.length())
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_mixed_length
|
169
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
170
|
+
assert_equal(2, d.length())
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_empty_leaf_count
|
174
|
+
d = $shell.data()
|
175
|
+
assert_equal(0, d.leaf_count())
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_hash_leaf_count
|
179
|
+
d = $shell.data({a: 1, b: 2, c:{}})
|
180
|
+
assert_equal(2, d.leaf_count())
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_array_leaf_count
|
184
|
+
d = $shell.data(['a', 'b', 'c', []])
|
185
|
+
assert_equal(3, d.leaf_count())
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_mixed_leaf_count
|
189
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
190
|
+
assert_equal(4, d.leaf_count())
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_empty_size
|
194
|
+
d = $shell.data()
|
195
|
+
assert_equal(1, d.size())
|
196
|
+
end
|
197
|
+
|
198
|
+
def test_hash_size
|
199
|
+
d = $shell.data({a: 1, b: 2, c:{}})
|
200
|
+
assert_equal(4, d.size())
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_array_size
|
204
|
+
d = $shell.data(['a', 'b', 'c', []])
|
205
|
+
assert_equal(5, d.size())
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_mixed_size
|
209
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
210
|
+
assert_equal(7, d.size())
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_each
|
214
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
215
|
+
paths = []
|
216
|
+
values = []
|
217
|
+
d.each { |p, v|
|
218
|
+
paths << p
|
219
|
+
values << v
|
220
|
+
}
|
221
|
+
assert_equal([[], [:a], [:b], [:b, 0], [:b, 1], [:b, 2], [:b, 2, :c]], paths, "paths mismatch")
|
222
|
+
assert_equal([{:a=>1, :b=>["a", "b", {:c=>3}]}, 1, ["a", "b", {:c=>3}], "a", "b", {:c=>3}, 3], values, "values mismatch")
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_each_leaf
|
226
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
227
|
+
paths = []
|
228
|
+
values = []
|
229
|
+
d.each_leaf { |p, v|
|
230
|
+
paths << p
|
231
|
+
values << v
|
232
|
+
}
|
233
|
+
assert_equal([[:a], [:b, 0], [:b, 1], [:b, 2, :c]], paths, "paths mismatch")
|
234
|
+
assert_equal([1, "a", "b", 3], values, "values mismatch")
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_eql
|
238
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
239
|
+
d2 = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
240
|
+
d3 = $shell.data({a: 1, b: ['a', 'b', { d: 3}]})
|
241
|
+
d4 = $shell.data({a: 1, b: ['a', 'b', { c: 4}]})
|
242
|
+
|
243
|
+
assert(d == d2, "same keys and values should be eql")
|
244
|
+
assert(!(d == d3), "same values different keys should not be eql")
|
245
|
+
assert(!(d == d4), "same keys different values should not be eql")
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_clone
|
249
|
+
d = $shell.data({a: 1, b: ['a', 'b', { c: 3}]})
|
250
|
+
c = d.clone
|
251
|
+
assert(d == c)
|
252
|
+
end
|
253
|
+
|
254
|
+
end # DataTest
|
data/test/impl_test.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
$: << File.dirname(__FILE__)
|
5
|
+
$: << File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'lib')
|
6
|
+
|
7
|
+
require 'minitest'
|
8
|
+
require 'minitest/autorun'
|
9
|
+
|
10
|
+
require 'wab/impl'
|
11
|
+
|
12
|
+
$shell = ::WAB::Impl::Shell.new(nil, nil)
|
13
|
+
|
14
|
+
require 'data_test'
|
metadata
CHANGED
@@ -1,15 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wabur
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.0d2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Ohler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
12
|
-
dependencies:
|
11
|
+
date: 2017-07-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: oj
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.3.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.3.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake-compiler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5'
|
13
69
|
description: 'Web Application Builder '
|
14
70
|
email: peter@ohler.com
|
15
71
|
executables: []
|
@@ -17,21 +73,27 @@ extensions: []
|
|
17
73
|
extra_rdoc_files:
|
18
74
|
- README.md
|
19
75
|
- pages/Architecture.md
|
20
|
-
- pages/Goals.md
|
21
76
|
- pages/Plan.md
|
77
|
+
- pages/Goals.md
|
22
78
|
files:
|
23
79
|
- LICENSE
|
24
80
|
- README.md
|
25
81
|
- lib/wab.rb
|
26
82
|
- lib/wab/controller.rb
|
27
83
|
- lib/wab/data.rb
|
84
|
+
- lib/wab/impl.rb
|
85
|
+
- lib/wab/impl/data.rb
|
86
|
+
- lib/wab/impl/shell.rb
|
28
87
|
- lib/wab/model.rb
|
29
88
|
- lib/wab/shell.rb
|
89
|
+
- lib/wab/uuid.rb
|
30
90
|
- lib/wab/version.rb
|
31
91
|
- lib/wab/view.rb
|
32
92
|
- pages/Architecture.md
|
33
93
|
- pages/Goals.md
|
34
94
|
- pages/Plan.md
|
95
|
+
- test/data_test.rb
|
96
|
+
- test/impl_test.rb
|
35
97
|
homepage: http://github.com/ohler55/wabur
|
36
98
|
licenses:
|
37
99
|
- MIT
|
@@ -61,4 +123,6 @@ rubygems_version: 2.6.11
|
|
61
123
|
signing_key:
|
62
124
|
specification_version: 4
|
63
125
|
summary: Web Application Builder
|
64
|
-
test_files:
|
126
|
+
test_files:
|
127
|
+
- test/impl_test.rb
|
128
|
+
- test/data_test.rb
|