safer 0.3.1 → 0.4.0

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 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