doodle 0.1.8 → 0.1.9
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.
- data/History.txt +52 -0
- data/Manifest.txt +1 -0
- data/examples/mail-datatypes.rb +1 -0
- data/examples/mail.rb +1 -1
- data/examples/profile-options.rb +1 -1
- data/lib/doodle.rb +332 -103
- data/lib/doodle/app.rb +445 -0
- data/lib/doodle/datatypes.rb +124 -15
- data/lib/doodle/version.rb +1 -1
- data/lib/molic_orderedhash.rb +99 -162
- data/log/debug.log +1 -0
- data/spec/block_init_spec.rb +52 -0
- data/spec/bugs_spec.rb +114 -18
- data/spec/class_var_spec.rb +28 -14
- data/spec/conversion_spec.rb +36 -38
- data/spec/doodle_spec.rb +23 -23
- data/spec/has_spec.rb +19 -1
- data/spec/init_spec.rb +22 -16
- data/spec/member_init_spec.rb +122 -0
- data/spec/readonly_spec.rb +32 -0
- data/spec/singleton_spec.rb +7 -7
- data/spec/spec_helper.rb +1 -1
- data/spec/symbolize_keys_spec.rb +40 -0
- data/spec/to_hash_spec.rb +35 -0
- metadata +39 -22
data/lib/doodle/version.rb
CHANGED
data/lib/molic_orderedhash.rb
CHANGED
@@ -3,227 +3,164 @@
|
|
3
3
|
#
|
4
4
|
# DESCRIPTION
|
5
5
|
# Hash with preserved order and some array-like extensions
|
6
|
-
# Public domain.
|
6
|
+
# Public domain.
|
7
7
|
#
|
8
8
|
# THANKS
|
9
9
|
# Andrew Johnson for his suggestions and fixes of Hash[],
|
10
10
|
# merge, to_a, inspect and shift
|
11
|
-
class
|
12
|
-
|
11
|
+
class Doodle
|
12
|
+
class OrderedHash < ::Hash
|
13
13
|
attr_accessor :order
|
14
14
|
|
15
15
|
class << self
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
else
|
25
|
-
hsh[args.shift] = args.shift while args.size > 0
|
26
|
-
end
|
27
|
-
hsh
|
28
|
-
#--}}}
|
16
|
+
def [](*args)
|
17
|
+
hsh = OrderedHash.new
|
18
|
+
if Hash === args[0]
|
19
|
+
hsh.replace args[0]
|
20
|
+
elsif (args.size % 2) != 0
|
21
|
+
raise ArgumentError, "odd number of elements for Hash"
|
22
|
+
else
|
23
|
+
hsh[args.shift] = args.shift while args.size > 0
|
29
24
|
end
|
30
|
-
|
25
|
+
hsh
|
26
|
+
end
|
31
27
|
end
|
32
|
-
# def initialize
|
33
|
-
##--{{{
|
34
|
-
# @order = []
|
35
|
-
##--}}}
|
36
|
-
# end
|
37
28
|
def initialize(*a, &b)
|
38
|
-
#--{{{
|
39
29
|
super
|
40
30
|
@order = []
|
41
|
-
#--}}}
|
42
31
|
end
|
43
|
-
def store_only
|
44
|
-
|
45
|
-
store a,b
|
46
|
-
#--}}}
|
32
|
+
def store_only(a,b)
|
33
|
+
store a,b
|
47
34
|
end
|
48
|
-
alias orig_store store
|
49
|
-
def store
|
50
|
-
|
51
|
-
|
52
|
-
super a,b
|
53
|
-
#--}}}
|
35
|
+
alias orig_store store
|
36
|
+
def store(a,b)
|
37
|
+
@order.push a unless has_key? a
|
38
|
+
super a,b
|
54
39
|
end
|
55
40
|
alias []= store
|
56
|
-
def ==
|
57
|
-
|
58
|
-
|
59
|
-
super hsh2
|
60
|
-
#--}}}
|
41
|
+
def ==(hsh2)
|
42
|
+
return false if @order != hsh2.order
|
43
|
+
super hsh2
|
61
44
|
end
|
62
45
|
def clear
|
63
|
-
|
64
|
-
|
65
|
-
super
|
66
|
-
#--}}}
|
46
|
+
@order = []
|
47
|
+
super
|
67
48
|
end
|
68
|
-
def delete
|
69
|
-
|
70
|
-
|
71
|
-
super
|
72
|
-
#--}}}
|
49
|
+
def delete(key)
|
50
|
+
@order.delete key
|
51
|
+
super
|
73
52
|
end
|
74
53
|
def each_key
|
75
|
-
|
76
|
-
|
77
|
-
self
|
78
|
-
#--}}}
|
54
|
+
@order.each { |k| yield k }
|
55
|
+
self
|
79
56
|
end
|
80
57
|
def each_value
|
81
|
-
|
82
|
-
|
83
|
-
self
|
84
|
-
#--}}}
|
58
|
+
@order.each { |k| yield self[k] }
|
59
|
+
self
|
85
60
|
end
|
86
61
|
def each
|
87
|
-
|
88
|
-
|
89
|
-
self
|
90
|
-
#--}}}
|
62
|
+
@order.each { |k| yield k,self[k] }
|
63
|
+
self
|
91
64
|
end
|
92
|
-
alias each_pair each
|
65
|
+
alias each_pair each
|
93
66
|
def delete_if
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
self
|
99
|
-
#--}}}
|
67
|
+
@order.clone.each { |k|
|
68
|
+
delete k if yield
|
69
|
+
}
|
70
|
+
self
|
100
71
|
end
|
101
72
|
def values
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
ary
|
106
|
-
#--}}}
|
73
|
+
ary = []
|
74
|
+
@order.each { |k| ary.push self[k] }
|
75
|
+
ary
|
107
76
|
end
|
108
77
|
def keys
|
109
|
-
|
110
|
-
@order
|
111
|
-
#--}}}
|
78
|
+
@order
|
112
79
|
end
|
113
80
|
def invert
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
def
|
126
|
-
|
127
|
-
|
128
|
-
self == hsh2 ? nil : hsh2
|
129
|
-
#--}}}
|
130
|
-
end
|
131
|
-
def replace hsh2
|
132
|
-
#--{{{
|
133
|
-
@order = hsh2.keys
|
134
|
-
super hsh2
|
135
|
-
#--}}}
|
81
|
+
hsh2 = Hash.new
|
82
|
+
@order.each { |k| hsh2[self[k]] = k }
|
83
|
+
hsh2
|
84
|
+
end
|
85
|
+
def reject(&block)
|
86
|
+
self.dup.delete_if(&block)
|
87
|
+
end
|
88
|
+
def reject!(&block)
|
89
|
+
hsh2 = reject(&block)
|
90
|
+
self == hsh2 ? nil : hsh2
|
91
|
+
end
|
92
|
+
def replace(hsh2)
|
93
|
+
@order = hsh2.keys
|
94
|
+
super hsh2
|
136
95
|
end
|
137
96
|
def shift
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
orig_store(k,v)
|
159
|
-
true
|
160
|
-
else
|
161
|
-
false
|
162
|
-
end
|
163
|
-
#--}}}
|
97
|
+
key = @order.first
|
98
|
+
key ? [key,delete(key)] : super
|
99
|
+
end
|
100
|
+
def unshift(k,v)
|
101
|
+
unless self.include? k
|
102
|
+
@order.unshift k
|
103
|
+
orig_store(k,v)
|
104
|
+
true
|
105
|
+
else
|
106
|
+
false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
def push(k,v)
|
110
|
+
unless self.include? k
|
111
|
+
@order.push k
|
112
|
+
orig_store(k,v)
|
113
|
+
true
|
114
|
+
else
|
115
|
+
false
|
116
|
+
end
|
164
117
|
end
|
165
118
|
def pop
|
166
|
-
|
167
|
-
|
168
|
-
key ? [key,delete(key)] : nil
|
169
|
-
#--}}}
|
119
|
+
key = @order.last
|
120
|
+
key ? [key,delete(key)] : nil
|
170
121
|
end
|
171
122
|
def to_a
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
ary
|
176
|
-
#--}}}
|
123
|
+
ary = []
|
124
|
+
each { |k,v| ary << [k,v] }
|
125
|
+
ary
|
177
126
|
end
|
178
127
|
def to_s
|
179
|
-
|
180
|
-
self.to_a.to_s
|
181
|
-
#--}}}
|
128
|
+
self.to_a.to_s
|
182
129
|
end
|
183
130
|
def inspect
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
#--{{{
|
192
|
-
hsh2.each { |k,v| self[k] = v }
|
193
|
-
self
|
194
|
-
#--}}}
|
131
|
+
ary = []
|
132
|
+
each {|k,v| ary << k.inspect + "=>" + v.inspect}
|
133
|
+
'{' + ary.join(", ") + '}'
|
134
|
+
end
|
135
|
+
def update(hsh2)
|
136
|
+
hsh2.each { |k,v| self[k] = v }
|
137
|
+
self
|
195
138
|
end
|
196
139
|
alias :merge! update
|
197
140
|
def merge(hsh2)
|
198
|
-
|
199
|
-
self.dup.update(hsh2)
|
200
|
-
#--}}}
|
141
|
+
self.dup.update(hsh2)
|
201
142
|
end
|
202
143
|
def select
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
ary
|
207
|
-
#--}}}
|
144
|
+
ary = []
|
145
|
+
each { |k,v| ary << [k,v] if yield k,v }
|
146
|
+
ary
|
208
147
|
end
|
209
148
|
def class
|
210
|
-
#--{{{
|
211
149
|
Hash
|
212
|
-
#--}}}
|
213
150
|
end
|
214
151
|
|
215
152
|
attr_accessor "to_yaml_style"
|
216
|
-
def yaml_inline=
|
153
|
+
def yaml_inline=(bool)
|
217
154
|
if respond_to?("to_yaml_style")
|
218
155
|
self.to_yaml_style = :inline
|
219
156
|
else
|
220
157
|
unless defined? @__yaml_inline_meth
|
221
158
|
@__yaml_inline_meth =
|
222
159
|
lambda {|opts|
|
223
|
-
|
224
|
-
|
225
|
-
}
|
160
|
+
YAML::quick_emit(object_id, opts) {|emitter|
|
161
|
+
emitter << '{ ' << map{|kv| kv.join ': '}.join(', ') << ' }'
|
226
162
|
}
|
163
|
+
}
|
227
164
|
class << self
|
228
165
|
def to_yaml opts = {}
|
229
166
|
begin
|
@@ -239,5 +176,5 @@ class OrderedHash < ::Hash
|
|
239
176
|
@__yaml_inline = bool
|
240
177
|
end
|
241
178
|
def yaml_inline!() self.yaml_inline = true end
|
242
|
-
|
243
|
-
end
|
179
|
+
end # class OrderedHash
|
180
|
+
end
|
data/log/debug.log
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
describe 'Doodle', 'block initialization of scalar attributes' do
|
5
|
+
temporary_constant :Foo, :Bar, :Farm, :Barn, :Animal do
|
6
|
+
before :each do
|
7
|
+
class Animal < Doodle
|
8
|
+
has :species
|
9
|
+
end
|
10
|
+
class Barn < Doodle
|
11
|
+
has :animals, :collect => Animal
|
12
|
+
end
|
13
|
+
class Farm < Doodle
|
14
|
+
has Barn
|
15
|
+
end
|
16
|
+
class Foo < Doodle
|
17
|
+
has :ivar1, :kind => String
|
18
|
+
end
|
19
|
+
class Bar < Doodle
|
20
|
+
has :block, :kind => Proc
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should initialize an scalar attribute from a block' do
|
25
|
+
farm = Farm do
|
26
|
+
barn do
|
27
|
+
animal "pig"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
farm.barn.animals[0].species.should_be "pig"
|
31
|
+
end
|
32
|
+
it 'should fail trying to initialize an inappropriate attribute (not a Doodle or Proc) from a block' do
|
33
|
+
proc {
|
34
|
+
foo = Foo do
|
35
|
+
ivar1 do
|
36
|
+
"hello"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
}.should raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
it 'should initialize a Proc attribute from a block' do
|
42
|
+
bar = Bar do
|
43
|
+
block do
|
44
|
+
"hello"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
bar.block.class.should_be Proc
|
48
|
+
bar.block.call.should_be "hello"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/spec/bugs_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe 'Doodle', 'inheriting validations' do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
it 'should not duplicate validations when accessing them!' do
|
17
17
|
foo = Foo 2
|
18
18
|
foo.doodle.validations.size.should_be 1
|
@@ -21,7 +21,7 @@ describe 'Doodle', 'inheriting validations' do
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
describe 'Doodle', '
|
24
|
+
describe 'Doodle', 'loading good data from yaml' do
|
25
25
|
temporary_constant :Foo do
|
26
26
|
before :each do
|
27
27
|
class Foo < Doodle
|
@@ -30,12 +30,12 @@ describe 'Doodle', ' loading good data from yaml' do
|
|
30
30
|
Date.parse(s)
|
31
31
|
end
|
32
32
|
end
|
33
|
-
end
|
33
|
+
end
|
34
34
|
@str = %[
|
35
35
|
--- !ruby/object:Foo
|
36
36
|
date: "2000-7-01"
|
37
37
|
]
|
38
|
-
|
38
|
+
|
39
39
|
end
|
40
40
|
|
41
41
|
it 'should succeed without validation' do
|
@@ -45,7 +45,7 @@ describe 'Doodle', ' loading good data from yaml' do
|
|
45
45
|
it 'should validate ok' do
|
46
46
|
proc { foo = YAML::load(@str).validate! }.should_not raise_error
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
it 'should apply conversions' do
|
50
50
|
foo = YAML::load(@str).validate!
|
51
51
|
foo.date.should_be Date.new(2000, 7, 1)
|
@@ -54,7 +54,7 @@ describe 'Doodle', ' loading good data from yaml' do
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
describe 'Doodle', '
|
57
|
+
describe 'Doodle', 'loading bad data from yaml' do
|
58
58
|
temporary_constant :Foo do
|
59
59
|
before :each do
|
60
60
|
class Foo < Doodle
|
@@ -67,7 +67,7 @@ describe 'Doodle', ' loading bad data from yaml' do
|
|
67
67
|
@str = %[
|
68
68
|
--- !ruby/object:Foo
|
69
69
|
date: "2000"
|
70
|
-
]
|
70
|
+
]
|
71
71
|
end
|
72
72
|
|
73
73
|
it 'should succeed without validation' do
|
@@ -94,7 +94,7 @@ describe 'Doodle', 'loading bad data from yaml with default defined' do
|
|
94
94
|
@str = %[
|
95
95
|
--- !ruby/object:Foo
|
96
96
|
date: "2000"
|
97
|
-
]
|
97
|
+
]
|
98
98
|
end
|
99
99
|
|
100
100
|
it 'should succeed without validation' do
|
@@ -141,7 +141,7 @@ describe Doodle, 'initializing from hashes and yaml' do
|
|
141
141
|
|
142
142
|
yaml = %[
|
143
143
|
---
|
144
|
-
:address:
|
144
|
+
:address:
|
145
145
|
- Henry Wood House
|
146
146
|
- London
|
147
147
|
:name: Sean
|
@@ -151,14 +151,16 @@ describe Doodle, 'initializing from hashes and yaml' do
|
|
151
151
|
yaml = person.to_yaml
|
152
152
|
# be careful here - Ruby yaml is finicky (spaces after class names)
|
153
153
|
yaml = yaml.gsub(/\s*\n/m, "\n")
|
154
|
-
|
155
|
-
address:
|
156
|
-
- !ruby/object:AddressLine
|
157
|
-
|
158
|
-
- !ruby/object:AddressLine
|
159
|
-
|
160
|
-
name: Sean
|
161
|
-
]
|
154
|
+
# yaml.should_be %[--- !ruby/object:Person
|
155
|
+
# address:
|
156
|
+
# - !ruby/object:AddressLine
|
157
|
+
# text: Henry Wood House
|
158
|
+
# - !ruby/object:AddressLine
|
159
|
+
# text: London
|
160
|
+
# name: Sean
|
161
|
+
# ]
|
162
|
+
|
163
|
+
yaml.should_be "--- !ruby/object:Person\naddress:\n- !ruby/object:AddressLine\n text: Henry Wood House\n- !ruby/object:AddressLine\n text: London\nname: Sean\n"
|
162
164
|
person = YAML.load(yaml)
|
163
165
|
proc { person.validate! }.should_not raise_error
|
164
166
|
person.address.all?{ |x| x.kind_of? AddressLine }.should_be true
|
@@ -187,7 +189,7 @@ describe 'Doodle', 'hiding @__doodle__' do
|
|
187
189
|
include Doodle::Core
|
188
190
|
end
|
189
191
|
end
|
190
|
-
|
192
|
+
|
191
193
|
it 'should not reveal @__doodle__ in inspect string' do
|
192
194
|
foo = Foo 2
|
193
195
|
foo.inspect.should_not =~ /@__doodle__/
|
@@ -226,3 +228,97 @@ describe 'Doodle', 'hiding @__doodle__' do
|
|
226
228
|
end
|
227
229
|
end
|
228
230
|
end
|
231
|
+
|
232
|
+
describe 'Doodle', 'initalizing class level collectors' do
|
233
|
+
temporary_constant :Menu, :KeyedMenu, :Item, :SubMenu do
|
234
|
+
before :each do
|
235
|
+
class Item < Doodle
|
236
|
+
has :title
|
237
|
+
end
|
238
|
+
class Menu < Doodle
|
239
|
+
class << self
|
240
|
+
has :items, :collect => Item
|
241
|
+
end
|
242
|
+
end
|
243
|
+
class KeyedMenu < Doodle
|
244
|
+
class << self
|
245
|
+
has :items, :collect => Item, :key => :title
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'should collect first item specified in appendable collector' do
|
251
|
+
class SubMenu < Menu
|
252
|
+
item "Item 1"
|
253
|
+
end
|
254
|
+
SubMenu.items[0].title.should_be "Item 1"
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'should collect all items specified in appendable collector' do
|
258
|
+
class SubMenu < Menu
|
259
|
+
item "New Item 1"
|
260
|
+
item "New Item 2"
|
261
|
+
item "New Item 3"
|
262
|
+
end
|
263
|
+
SubMenu.items[0].title.should_be "New Item 1"
|
264
|
+
SubMenu.items[2].title.should_be "New Item 3"
|
265
|
+
SubMenu.items.size.should_be 3
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'should collect first item specified in keyed collector' do
|
269
|
+
class SubMenu < KeyedMenu
|
270
|
+
item "Item 1"
|
271
|
+
end
|
272
|
+
SubMenu.items["Item 1"].title.should_be "Item 1"
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'should collect all items specified in keyed collector' do
|
276
|
+
class SubMenu < KeyedMenu
|
277
|
+
item "New Item 1"
|
278
|
+
item "New Item 2"
|
279
|
+
item "New Item 3"
|
280
|
+
end
|
281
|
+
SubMenu.items["New Item 1"].title.should_be "New Item 1"
|
282
|
+
SubMenu.items["New Item 3"].title.should_be "New Item 3"
|
283
|
+
SubMenu.items.size.should_be 3
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'should collect all items specified in keyed collector in order' do
|
287
|
+
class SubMenu < KeyedMenu
|
288
|
+
item "New Item 1"
|
289
|
+
item "New Item 2"
|
290
|
+
item "New Item 3"
|
291
|
+
end
|
292
|
+
SubMenu.items.to_a[0][0].should_be "New Item 1"
|
293
|
+
SubMenu.items.to_a[2][0].should_be "New Item 3"
|
294
|
+
SubMenu.items.size.should_be 3
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
describe 'Doodle', 'validating required attributes after default attributes' do
|
300
|
+
temporary_constant :Foo do
|
301
|
+
before :each do
|
302
|
+
class Foo < Doodle
|
303
|
+
has :v1, :default => 1
|
304
|
+
has :v2
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'should validate required attribute after an attribute with default defined' do
|
309
|
+
proc { Foo.new }.should raise_error(Doodle::ValidationError)
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'should validate required attribute after an attribute with default defined specified #1' do
|
313
|
+
proc { Foo.new(1) }.should raise_error(Doodle::ValidationError)
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'should validate required attribute after an attribute with default defined specified #2' do
|
317
|
+
proc { Foo.new(:v1 => 1) }.should raise_error(Doodle::ValidationError)
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'should validate specified required attribute after an attribute with default defined not specified' do
|
321
|
+
proc { Foo.new(:v2 => 2) }.should_not raise_error
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|