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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f881d414a328e743c262513bde6977d3a28a3ec3
4
- data.tar.gz: f3d99084f2ad9ccb619ceb3572a4c0bc66863c5f
3
+ metadata.gz: f4ae2c00f65bf680b3432bd10ec6135aeed7e6bc
4
+ data.tar.gz: 01f5f822d40665b8f314d3744ccdcf7bf35c071c
5
5
  SHA512:
6
- metadata.gz: 0b4ca9f500c474908d6a4b740a86ec360f0386332e7d55bd724743124d041983c1854834cbac66a874c73b2a7f1b3c674224c830a4d0b3dee3f50204efe89b3f
7
- data.tar.gz: b71155371f81f098034483486ff036e36ccaee5c460072dc61a7fe3012df97de643c2eea5514c9561f50ace56c5b1685cbde4cd04e3a322d5dd490ce15f71f1a
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
+ [![Build Status](https://img.shields.io/travis/ohler55/wabur/develop.svg)](http://travis-ci.org/ohler55/wabur?branch=develop) ![Gem](https://img.shields.io/gem/v/wabur.svg) ![Gem](https://img.shields.io/gem/dt/wabur.svg)
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
-
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
- # TBD
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
- # TBD
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
- # TBD
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 WAB::IRI.
46
+ # or URI.
46
47
  def each_leaf()
47
- # TBD
48
+ raise NotImplementedError.new
48
49
  end
49
50
 
50
51
  # Make a deep copy of the Data instance.
51
52
  def clone()
52
- # TBD
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
- # TBD
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
- # TBD
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
- # TBD
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
@@ -0,0 +1,11 @@
1
+
2
+ require 'wab'
3
+
4
+ module WAB
5
+ # Web Application Builder implementation of the WAB APIs.
6
+ module Impl
7
+ end
8
+ end
9
+
10
+ require 'wab/impl/data'
11
+ require 'wab/impl/shell'
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
@@ -1,5 +1,5 @@
1
1
 
2
2
  module WAB
3
3
  # Current version of the module.
4
- VERSION = '0.1.0d1'
4
+ VERSION = '0.1.0d2'
5
5
  end
data/lib/wab/view.rb CHANGED
File without changes
data/lib/wab.rb CHANGED
@@ -3,3 +3,8 @@
3
3
  module WAB
4
4
  end
5
5
 
6
+ require 'wab/data'
7
+ require 'wab/model'
8
+ require 'wab/shell'
9
+ require 'wab/uuid'
10
+ require 'wab/version'
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 hight level plan is:
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
- - Embedded Glue layer
10
- - External Glue layer
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.0d1
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-06-03 00:00:00.000000000 Z
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