safer 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,17 @@
1
+ === 0.4.0 / 2011-10-29
2
+
3
+ * Safer::IVar functionality moved into Safer::IVarFactory class.
4
+ * Safer::IVar is now an instance of Safer::IVarFactory.
5
+ * Added DSL interface to Safer::IVarFactory.
6
+ * Safer::IVarFactory has pluggable symbol prefix generation.
7
+ * Added facility to generate symbols based on last component of class name.
8
+
9
+ === 0.3.2 / 2010-12-01
10
+
11
+ * Made compatible with ruby 1.9.2.
12
+ * Renamed README.txt to README.rdoc
13
+ * Refactored HashProtocol tests in accordance with common ruby practice.
14
+
1
15
  === 0.3.1 / 2010-11-21
2
16
 
3
17
  * ri Safer didn't include mention of HashProtocol. Fixed.
data/Manifest.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  .autotest
2
2
  History.txt
3
3
  Manifest.txt
4
- README.txt
4
+ README.rdoc
5
5
  Rakefile
6
6
  lib/safer.rb
7
7
  lib/safer/ivar.rb
File without changes
data/Rakefile CHANGED
@@ -2,7 +2,8 @@ require 'rubygems'
2
2
  require 'hoe'
3
3
 
4
4
  Hoe.spec 'safer' do
5
- developer('Aidan Cully', 'aidan@panix.com')
5
+ self.developer('Aidan Cully', 'aidan@panix.com')
6
6
 
7
7
  self.rubyforge_name = 'safer'
8
+ self.readme_file = 'README.rdoc'
8
9
  end
@@ -1,304 +1,302 @@
1
1
  require 'safer/ivar'
2
2
  require 'safer/protocol'
3
3
 
4
- class Safer
4
+ ##
5
+ # Check that the keys in a Hash object follow a set of constraints.
6
+ # ==Usage
7
+ # In this example, we use a Hash to simulate keyword parameters to our
8
+ # function.
9
+ # class YourClass
10
+ # SHP = Safer::HashProtocol
11
+ # # we support two versions of the keyword arguments to our function.
12
+ # # In both versions, the Hash is required to have a :type field.
13
+ # CommonKeywords = SHP::HasKey.create(:type)
14
+ # # In the old version, a person's full name is presented in the :name
15
+ # # keyword.
16
+ # V1Keywords = SHP::HasKey.create(:name)
17
+ # # In the new version, the full name is separated into :familyName
18
+ # # and :givenName.
19
+ # V2Keywords = SHP::HasKey.create(:familyName, :givenName)
20
+ # # Check if the keywords in the Hash match Version1 (that is,
21
+ # # :name and :type fields must be present in the Hash, and no other
22
+ # # fields should be present).
23
+ # V1Check = SHP::Only.new(SHP::All.new(*CommonKeywords + *V1Keywords))
24
+ # # Check if the keywords in the Hash match Version2 (that is,
25
+ # # :familyName, :givenName, and :type fields must be present in the Hash,
26
+ # # and no other fields should be present).
27
+ # V2Check = SHP::Only.new(SHP::All.new(*CommonKeywords + *V2Keywords))
28
+ # # Check if the Hash matches either version 1 or version 2.
29
+ # ValidCheck = SHP::Any.new(V1Check, V2Check)
30
+ # def my_fn(h)
31
+ # if ! (ValidCheck === h)
32
+ # raise ArgumentError, "h should conform to #{ValidCheck.description}"
33
+ # end
34
+ # case h
35
+ # when V1Check
36
+ # v1_process(h[:name], h[:type])
37
+ # when V2Check
38
+ # v2_process(h[:familyName], h[:givenName], h[:type])
39
+ # end
40
+ # end
41
+ # end
42
+ # ==Rationale
43
+ # It is a common design practice in Ruby code to use Hash objects to get an
44
+ # analogue to function keyword arguments in other languages. Among other
45
+ # uses, this practice simplifies the implementation of embedded DSLs within
46
+ # Ruby. When used in this way, the Hash keys are semantically meaningful,
47
+ # and are generally entered by hand. It's important to get these Hashes
48
+ # right. Safer::HashProtocol is intended to help, by simultaneously
49
+ # providing a means of documenting the key combinations required of a Hash,
50
+ # and by detecting when the Hash does not contain a valid set of keys.
51
+ class Safer::HashProtocol
5
52
  ##
6
- # Check that the keys in a Hash object follow a set of constraints.
7
- # ==Usage
8
- # In this example, we use a Hash to simulate keyword parameters to our
9
- # function.
10
- # class YourClass
11
- # SHP = Safer::HashProtocol
12
- # # we support two versions of the keyword arguments to our function.
13
- # # In both versions, the Hash is required to have a :type field.
14
- # CommonKeywords = SHP::HasKey.create(:type)
15
- # # In the old version, a person's full name is presented in the :name
16
- # # keyword.
17
- # V1Keywords = SHP::HasKey.create(:name)
18
- # # In the new version, the full name is separated into :familyName
19
- # # and :givenName.
20
- # V2Keywords = SHP::HasKey.create(:familyName, :givenName)
21
- # # Check if the keywords in the Hash match Version1 (that is,
22
- # # :name and :type fields must be present in the Hash, and no other
23
- # # fields should be present).
24
- # V1Check = SHP::Only.new(SHP::All.new(*CommonKeywords + *V1Keywords))
25
- # # Check if the keywords in the Hash match Version2 (that is,
26
- # # :familyName, :givenName, and :type fields must be present in the Hash,
27
- # # and no other fields should be present).
28
- # V2Check = SHP::Only.new(SHP::All.new(*CommonKeywords + *V2Keywords))
29
- # # Check if the Hash matches either version 1 or version 2.
30
- # ValidCheck = SHP::Any.new(V1Check, V2Check)
31
- # def my_fn(h)
32
- # if ! (ValidCheck === h)
33
- # raise ArgumentError, "h should conform to #{ValidCheck.description}"
34
- # end
35
- # case h
36
- # when V1Check
37
- # v1_process(h[:name], h[:type])
38
- # when V2Check
39
- # v2_process(h[:familyName], h[:givenName], h[:type])
40
- # end
41
- # end
42
- # end
43
- # ==Rationale
44
- # It is a common design practice in Ruby code to use Hash objects to get an
45
- # analogue to function keyword arguments in other languages. Among other
46
- # uses, this practice simplifies the implementation of embedded DSLs within
47
- # Ruby. When used in this way, the Hash keys are semantically meaningful,
48
- # and are generally entered by hand. It's important to get these Hashes
49
- # right. Safer::HashProtocol is intended to help, by simultaneously
50
- # providing a means of documenting the key combinations required of a Hash,
51
- # and by detecting when the Hash does not contain a valid set of keys.
52
- class HashProtocol
53
+ # Object signature required of HashProtocol objects. Used to derive
54
+ # Safer::HashProtocol::Protocol.
55
+ class ProtocolBase
53
56
  ##
54
- # Object signature required of HashProtocol objects. Used to derive
55
- # Safer::HashProtocol::Protocol.
56
- class ProtocolBase
57
- ##
58
- # Retrieve a human-readable description of this HashProtocol object.
59
- def description
60
- end
61
-
62
- ##
63
- # Check that a Hash (h) follows this object's protocol.
64
- def ===(h)
65
- end
57
+ # Retrieve a human-readable description of this HashProtocol object.
58
+ def description
59
+ end
66
60
 
67
- ##
68
- # Check that a Hash (h) follows this object's protocol, keeping
69
- # track of which parts of (h) do not. +remaining+ should only
70
- # be updated if +h+ matches this object.
71
- def match(h, remaining)
72
- end
73
- end # Safer::HashProtocol::ProtocolBase
74
61
  ##
75
- # Object signature required of HashProtocol objects. Derived from
76
- # ProtocolBase.
77
- Protocol = Safer::Protocol.create_from_class(ProtocolBase)
62
+ # Check that a Hash (h) follows this object's protocol.
63
+ def ===(h)
64
+ end
78
65
 
79
66
  ##
80
- # Base class of all classes exported from Safer::HashProtocol. Implements
81
- # ProtocolBase#description signature.
82
- class Base
83
- Safer::IVar.instance_variable(self, :description)
67
+ # Check that a Hash (h) follows this object's protocol, keeping
68
+ # track of which parts of (h) do not. +remaining+ should only
69
+ # be updated if +h+ matches this object.
70
+ def match(h, remaining)
71
+ end
72
+ end # Safer::HashProtocol::ProtocolBase
73
+ ##
74
+ # Object signature required of HashProtocol objects. Derived from
75
+ # ProtocolBase.
76
+ Protocol = Safer::Protocol.create_from_class(ProtocolBase)
84
77
 
85
- ##
86
- # :attr_reader: description
87
- # Human-readable description of this HashProtocol.
88
- Safer::IVar.export_reader(self, :description)
78
+ ##
79
+ # Base class of all classes exported from Safer::HashProtocol. Implements
80
+ # ProtocolBase#description signature.
81
+ class Base
82
+ Safer::IVar.instance_variable(self, :description)
89
83
 
90
- ##
91
- # Store +description+ into the #description reader attribute.
92
- def initialize(description)
93
- self.safer_hashprotocol_base__description = description
94
- end # Safer::HashProtocol::Base#initialize
95
- end # Safer::HashProtocol::Base
84
+ ##
85
+ # :attr_reader: description
86
+ # Human-readable description of this HashProtocol.
87
+ Safer::IVar.export_reader(self, :description)
96
88
 
97
89
  ##
98
- # Base class of Safer::HashProtocol implementations that provide some
99
- # derived value from a single "base" implementation.
100
- class Single < Safer::HashProtocol::Base
101
- Safer::IVar.instance_variable(self, :base)
90
+ # Store +description+ into the #description reader attribute.
91
+ def initialize(description)
92
+ self.safer_hashprotocol_base__description = description
93
+ end # Safer::HashProtocol::Base#initialize
94
+ end # Safer::HashProtocol::Base
102
95
 
103
- ##
104
- # :attr_reader: base
105
- # Base HashProtocol object from which this object's match operation
106
- # should be derived.
107
- Safer::IVar.export_reader(self, :base)
108
- ##
109
- # Stores the description and base HashProtocol object in this
110
- # HashProtocol object. The description is derived from the description
111
- # of the base object, prepended by +prefix+.
112
- def initialize(prefix, base)
113
- Protocol.instance_conforms?(base)
114
- super("#{prefix}#{base.description}")
115
- self.safer_hashprotocol_single__base = base
116
- end # Safer::HashProtocol::Single#initialize
117
- end # Safer::HashProtocol::Single
96
+ ##
97
+ # Base class of Safer::HashProtocol implementations that provide some
98
+ # derived value from a single "base" implementation.
99
+ class Single < Safer::HashProtocol::Base
100
+ Safer::IVar.instance_variable(self, :base)
118
101
 
119
102
  ##
120
- # Base class of Safer::HashProtocol implementations that provide some
121
- # derived value from a set of other HashProtocol implementations.
122
- class Compound < Safer::HashProtocol::Base
123
- Safer::IVar.instance_variable(self, :list)
103
+ # :attr_reader: base
104
+ # Base HashProtocol object from which this object's match operation
105
+ # should be derived.
106
+ Safer::IVar.export_reader(self, :base)
107
+ ##
108
+ # Stores the description and base HashProtocol object in this
109
+ # HashProtocol object. The description is derived from the description
110
+ # of the base object, prepended by +prefix+.
111
+ def initialize(prefix, base)
112
+ Protocol.instance_conforms?(base)
113
+ super("#{prefix}#{base.description}")
114
+ self.safer_hashprotocol_single__base = base
115
+ end # Safer::HashProtocol::Single#initialize
116
+ end # Safer::HashProtocol::Single
117
+
118
+ ##
119
+ # Base class of Safer::HashProtocol implementations that provide some
120
+ # derived value from a set of other HashProtocol implementations.
121
+ class Compound < Safer::HashProtocol::Base
122
+ Safer::IVar.instance_variable(self, :list)
124
123
 
125
- ##
126
- # :attr_reader: list
127
- # Set of HashProtocol objects from which this object's match operation
128
- # should be derived.
129
- Safer::IVar.export_reader(self, :list)
124
+ ##
125
+ # :attr_reader: list
126
+ # Set of HashProtocol objects from which this object's match operation
127
+ # should be derived.
128
+ Safer::IVar.export_reader(self, :list)
130
129
 
131
- ##
132
- # Stores the description and set of base HashProtocol objects in this
133
- # HashProtocol object. The description is derived from the description
134
- # of the base HashProtocol objects, with +sep+ between each element.
135
- def initialize(sep, *list)
136
- desc = list.map do |el|
137
- Protocol.instance_conforms?(el)
138
- "(#{el.description})"
139
- end.join(sep)
140
- super(desc)
141
- self.safer_hashprotocol_compound__list = list
142
- end # Safer::HashProtocol::Compound#initialize
143
- end # Safer::HashProtocol::Compound
130
+ ##
131
+ # Stores the description and set of base HashProtocol objects in this
132
+ # HashProtocol object. The description is derived from the description
133
+ # of the base HashProtocol objects, with +sep+ between each element.
134
+ def initialize(sep, *list)
135
+ desc = list.map do |el|
136
+ Protocol.instance_conforms?(el)
137
+ "(#{el.description})"
138
+ end.join(sep)
139
+ super(desc)
140
+ self.safer_hashprotocol_compound__list = list
141
+ end # Safer::HashProtocol::Compound#initialize
142
+ end # Safer::HashProtocol::Compound
144
143
 
144
+ ##
145
+ # Terminal Safer::HashProtocol implementation, checks that a key exists in
146
+ # a Hash.
147
+ class HasKey < Safer::HashProtocol::Base
148
+ Safer::IVar.instance_variable(self, :key)
145
149
  ##
146
- # Terminal Safer::HashProtocol implementation, checks that a key exists in
147
- # a Hash.
148
- class HasKey < Safer::HashProtocol::Base
149
- Safer::IVar.instance_variable(self, :key)
150
- ##
151
- # The match operation will check that +key+ exists in the Hash.
152
- # +key.inspect+ is used for the description.
153
- def initialize(key)
154
- super(key.inspect)
155
- self.safer_hashprotocol_haskey__key = key
156
- end # Safer::HashProtocol::HasKey#initialize
150
+ # The match operation will check that +key+ exists in the Hash.
151
+ # +key.inspect+ is used for the description.
152
+ def initialize(key)
153
+ super(key.inspect)
154
+ self.safer_hashprotocol_haskey__key = key
155
+ end # Safer::HashProtocol::HasKey#initialize
157
156
 
158
- ##
159
- # Check that the +key+ from self.initialize is stored in Hash h.
160
- def ===(h)
161
- h.has_key?(self.safer_hashprotocol_haskey__key)
162
- end # Safer::HashProtocol::HasKey#===
163
- ##
164
- # Check that the +key+ from self.initialize is stored in Hash h. If it
165
- # is, remove that key from +remaining+.
166
- def match(h, remaining)
167
- if h.has_key?(self.safer_hashprotocol_haskey__key)
168
- remaining.delete(self.safer_hashprotocol_haskey__key)
169
- true
170
- else
171
- false
172
- end
173
- end # Safer::HashProtocol::HasKey#match
157
+ ##
158
+ # Check that the +key+ from self.initialize is stored in Hash h.
159
+ def ===(h)
160
+ h.has_key?(self.safer_hashprotocol_haskey__key)
161
+ end # Safer::HashProtocol::HasKey#===
162
+ ##
163
+ # Check that the +key+ from self.initialize is stored in Hash h. If it
164
+ # is, remove that key from +remaining+.
165
+ def match(h, remaining)
166
+ if h.has_key?(self.safer_hashprotocol_haskey__key)
167
+ remaining.delete(self.safer_hashprotocol_haskey__key)
168
+ true
169
+ else
170
+ false
171
+ end
172
+ end # Safer::HashProtocol::HasKey#match
174
173
 
175
- ##
176
- # Convenience function creates a HasKey object for each argument
177
- # passed in.
178
- def self.create(*args)
179
- args.map do |el| self.new(el) end
180
- end # Safer::HashProtocol::HasKey.create
181
- end # Safer::HashProtocol::HasKey
174
+ ##
175
+ # Convenience function creates a HasKey object for each argument
176
+ # passed in.
177
+ def self.create(*args)
178
+ args.map do |el| self.new(el) end
179
+ end # Safer::HashProtocol::HasKey.create
180
+ end # Safer::HashProtocol::HasKey
182
181
 
182
+ ##
183
+ # Check that at least one of a set of Safer::HashProtocol objects matches
184
+ # a Hash.
185
+ class Any < Safer::HashProtocol::Compound
183
186
  ##
184
- # Check that at least one of a set of Safer::HashProtocol objects matches
185
- # a Hash.
186
- class Any < Safer::HashProtocol::Compound
187
- ##
188
- # The description for this object will be the descriptions for each
189
- # argument, separated by " OR ".
190
- def initialize(*list)
191
- super(" OR ", *list)
192
- end # Safer::HashProtocol::Any#initialize
187
+ # The description for this object will be the descriptions for each
188
+ # argument, separated by " OR ".
189
+ def initialize(*list)
190
+ super(" OR ", *list)
191
+ end # Safer::HashProtocol::Any#initialize
193
192
 
194
- ##
195
- # Check if any elements from the +list+ argument to initialize match
196
- # Hash h.
197
- def ===(h)
198
- self.list.any? do |el| el === h end
199
- end # Safer::HashProtocol::Any#===
193
+ ##
194
+ # Check if any elements from the +list+ argument to initialize match
195
+ # Hash h.
196
+ def ===(h)
197
+ self.list.any? do |el| el === h end
198
+ end # Safer::HashProtocol::Any#===
200
199
 
201
- ##
202
- # Check if any elements from the +list+ argument to initialize match
203
- # Hash h, keeping track of which elements from h have not been matched.
204
- def match(h, remaining)
205
- self.list.inject(false) do |memo, el|
206
- if el.match(h, remaining)
207
- true
208
- else
209
- memo
210
- end
200
+ ##
201
+ # Check if any elements from the +list+ argument to initialize match
202
+ # Hash h, keeping track of which elements from h have not been matched.
203
+ def match(h, remaining)
204
+ self.list.inject(false) do |memo, el|
205
+ if el.match(h, remaining)
206
+ true
207
+ else
208
+ memo
211
209
  end
212
- end # Safer::HashProtocol::Any#match
213
- end # Safer::HashProtocol::Any
210
+ end
211
+ end # Safer::HashProtocol::Any#match
212
+ end # Safer::HashProtocol::Any
214
213
 
214
+ ##
215
+ # Check that all of a set of Safer::HashProtocol objects match a Hash.
216
+ class All < Safer::HashProtocol::Compound
215
217
  ##
216
- # Check that all of a set of Safer::HashProtocol objects match a Hash.
217
- class All < Safer::HashProtocol::Compound
218
- ##
219
- # The description for this object will be the descriptions for each
220
- # argument, separated by " AND ".
221
- def initialize(*list)
222
- super(" AND ", *list)
223
- end # Safer::HashProtocol::All#initialize
218
+ # The description for this object will be the descriptions for each
219
+ # argument, separated by " AND ".
220
+ def initialize(*list)
221
+ super(" AND ", *list)
222
+ end # Safer::HashProtocol::All#initialize
224
223
 
225
- ##
226
- # Check if all elements from the +list+ argument to initialize match
227
- # Hash h.
228
- def ===(h)
229
- self.list.all? do |el| el === h end
230
- end # Safer::HashProtocol::All#===
224
+ ##
225
+ # Check if all elements from the +list+ argument to initialize match
226
+ # Hash h.
227
+ def ===(h)
228
+ self.list.all? do |el| el === h end
229
+ end # Safer::HashProtocol::All#===
231
230
 
232
- ##
233
- # Check if all elements from the +list+ argument to initialize match
234
- # Hash h. If they do, +remaining+ will be updated to remove all matching
235
- # elements from +list+. If not all elements match, +remaining+ will be
236
- # unmodified.
237
- def match(h, remaining)
238
- nremaining = remaining.dup
239
- rval = self.list.all? do |el| el.match(h, nremaining) end
240
- if rval
241
- remaining.update(nremaining)
242
- end
243
- rval
244
- end # Safer::HashProtocol::All#match
245
- end # Safer::HashProtocol::All
231
+ ##
232
+ # Check if all elements from the +list+ argument to initialize match
233
+ # Hash h. If they do, +remaining+ will be updated to remove all matching
234
+ # elements from +list+. If not all elements match, +remaining+ will be
235
+ # unmodified.
236
+ def match(h, remaining)
237
+ nremaining = remaining.dup
238
+ rval = self.list.all? do |el| el.match(h, nremaining) end
239
+ if rval
240
+ remaining.update(nremaining)
241
+ end
242
+ rval
243
+ end # Safer::HashProtocol::All#match
244
+ end # Safer::HashProtocol::All
246
245
 
246
+ ##
247
+ # Check that a base Safer::HashProtocol object does NOT match a Hash.
248
+ # That is, invert the match of a base HashProtocol object.
249
+ class Not < Safer::HashProtocol::Single
247
250
  ##
248
- # Check that a base Safer::HashProtocol object does NOT match a Hash.
249
- # That is, invert the match of a base HashProtocol object.
250
- class Not < Safer::HashProtocol::Single
251
- ##
252
- # The description for this object will be "NOT #{base.description}".
253
- def initialize(base)
254
- super("NOT ", base)
255
- end # Safer::HashProtocol::Not#initialize
251
+ # The description for this object will be "NOT #{base.description}".
252
+ def initialize(base)
253
+ super("NOT ", base)
254
+ end # Safer::HashProtocol::Not#initialize
256
255
 
257
- ##
258
- # Check that the base object does NOT match the hash.
259
- def ===(h)
260
- ! (self.base === h)
261
- end # Safer::HashProtocol::Not#===
256
+ ##
257
+ # Check that the base object does NOT match the hash.
258
+ def ===(h)
259
+ ! (self.base === h)
260
+ end # Safer::HashProtocol::Not#===
262
261
 
263
- ##
264
- # Check that the base object does NOT match the hash. Does not update
265
- # +remaining+ under any circumstance.
266
- def match(h, remaining)
267
- self === h
268
- end # Safer::HashProtocol::Not#match
269
- end # Safer::HashProtocol::Not
262
+ ##
263
+ # Check that the base object does NOT match the hash. Does not update
264
+ # +remaining+ under any circumstance.
265
+ def match(h, remaining)
266
+ self === h
267
+ end # Safer::HashProtocol::Not#match
268
+ end # Safer::HashProtocol::Not
270
269
 
270
+ ##
271
+ # Check that the Hash only contains elements that match the base
272
+ # Safer::HashProtocol object.
273
+ class Only < Safer::HashProtocol::Single
271
274
  ##
272
- # Check that the Hash only contains elements that match the base
273
- # Safer::HashProtocol object.
274
- class Only < Safer::HashProtocol::Single
275
- ##
276
- # The description for this object will be "ONLY #{base.description}".
277
- def initialize(base)
278
- super("ONLY ", base)
279
- end
280
- ##
281
- # Check that all keys in a hash are matched by the base HashProtocol
282
- # object.
283
- def ===(h)
284
- remaining = h.dup
285
- if self.base.match(h, remaining)
286
- remaining.empty?
287
- else
288
- false
289
- end
275
+ # The description for this object will be "ONLY #{base.description}".
276
+ def initialize(base)
277
+ super("ONLY ", base)
278
+ end
279
+ ##
280
+ # Check that all keys in a hash are matched by the base HashProtocol
281
+ # object.
282
+ def ===(h)
283
+ remaining = h.dup
284
+ if self.base.match(h, remaining)
285
+ remaining.empty?
286
+ else
287
+ false
290
288
  end
291
- ##
292
- # Check that all keys in a hash are matched by the base HashProtocol
293
- # object. If they are, then empty +remaining+.
294
- def match(h, remaining)
295
- if self === h
296
- remaining.clear
297
- true
298
- else
299
- false
300
- end
289
+ end
290
+ ##
291
+ # Check that all keys in a hash are matched by the base HashProtocol
292
+ # object. If they are, then empty +remaining+.
293
+ def match(h, remaining)
294
+ if self === h
295
+ remaining.clear
296
+ true
297
+ else
298
+ false
301
299
  end
302
- end # Safer::HashProtocol::Only
303
- end # Safer::HashProtocol
304
- end # Safer
300
+ end
301
+ end # Safer::HashProtocol::Only
302
+ end # Safer::HashProtocol