doodle 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|