chattr 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/LICENSE +19 -0
  2. data/lib/chattr.rb +386 -0
  3. data/spec/runtest.rb +8 -0
  4. metadata +47 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2007 Clifford Heath.
2
+
3
+ This software is provided 'as-is', without any express or implied warranty.
4
+ In no event will the authors be held liable for any damages arising from the
5
+ use of this software.
6
+
7
+ Permission is granted to anyone to use this software for any purpose,
8
+ including commercial applications, and to alter it and redistribute it
9
+ freely, subject to the following restrictions:
10
+
11
+ 1. The origin of this software must not be misrepresented; you must not
12
+ claim that you wrote the original software. If you use this software
13
+ in a product, an acknowledgment in the product documentation would be
14
+ appreciated but is not required.
15
+
16
+ 2. Altered source versions must be plainly marked as such, and must not be
17
+ misrepresented as being the original software.
18
+
19
+ 3. This notice may not be removed or altered from any source distribution.
data/lib/chattr.rb ADDED
@@ -0,0 +1,386 @@
1
+ =begin rdoc
2
+ # Checked arrays and checked attributes (and array attributes) support
3
+ # * class-checking (values must be of specified class),
4
+ # * allow or deny nil values
5
+ # * a block for checking validity of values
6
+ #
7
+ # Author: Clifford Heath.
8
+ =end
9
+
10
+ =begin rdoc
11
+ call-seq:
12
+ Array(class).new -> array
13
+ class MyClass < Array(class); end
14
+ class MyClass < Array class do |elem| elem.isvalid end
15
+ class MyClass < Array {|elem| elem.respond_to?(:meth) }
16
+
17
+ Checked array classes are subclasses of #Array
18
+ that are created using this <tt>Array()</tt> method.
19
+ They provide two kinds of checking on values entered into the array:
20
+ * class checking (using kind_of?)
21
+ * calling a block to determine validity
22
+
23
+ When any value is entered into the Array, by any method,
24
+ that doesn't satisfy your checks, you'll get a nice exception.
25
+ Beware of catching this exception, as in some cases
26
+ (+flatten+ for instance) the operation has been completed
27
+ before the new array value is checked and the exception thrown.
28
+
29
+ Here's a simple example:
30
+ int_array = Array(Integer).new
31
+ int_array << 4
32
+ int_array.concat [ 6, 8, 10 ]
33
+ int_array << "Oops" # This will throw an exception
34
+
35
+ Surprisingly, a call to <tt>Array()</tt> works even as a superclass in a new class definition:
36
+ class MyArray < Array(Integer)
37
+ def my_meth()
38
+ ...
39
+ end
40
+ end
41
+
42
+ and you can even use both together. An exception is thrown if the block returns false/nil:
43
+ class MyArray < Array(Integer) {|i|
44
+ (0..5).include?(i) # Value must be an integer from 0 to 5
45
+ }
46
+ end
47
+ MyArray.new << 6 # This will throw an exception
48
+
49
+ The parameter to Array is optional, which removes the class check.
50
+ You can use this to implement type checking that's all your own:
51
+ class MyArray < Array {|i|
52
+ i.respond_to?(:+@) && i.respond_to(:to_i)
53
+ }
54
+ def my_meth()
55
+ end
56
+ end
57
+
58
+ Note that these latter examples create _two_ new classes,
59
+ one from the call to the Array() method, and one that you declared.
60
+ So you don't need to worry about overriding the methods that perform
61
+ the checking; +super+ works as normal.
62
+
63
+ There is no way to specify an initial size or a default value.
64
+
65
+ The methods that are overridden in order to implement the checking are
66
+ listed below. All documented types of parameter lists and blocks are supported.
67
+ - new
68
+ - Array.[] (constructor)
69
+ - []=
70
+ - <<
71
+ - concat
72
+ - fill
73
+ - flatten!
74
+ - replace
75
+ - insert
76
+ - collect!
77
+ - map!
78
+ - push
79
+
80
+ =end
81
+ def Array(type = nil, &block)
82
+ Class.new(Array).class_eval <<-END
83
+ if (Class === type)
84
+ @@_valid_type = lambda{|o| o.kind_of?(type) && (!block || block.call(o)) }
85
+ else
86
+ @@_valid_type ||= (block || lambda{|o| true})
87
+ end
88
+
89
+ def self.new(*a, &b)
90
+ r = super() # Is this correct?
91
+ if (a.size == 1 && a[0].class != Fixnum)
92
+ r.concat(a[0])
93
+ elsif (b)
94
+ throw "Wrong number of parameters for Array.new" if a.size != 1
95
+ (0...a[0]).each{|i|
96
+ v = b.call(i)
97
+ throw "Illegal array member from block initializer: \#{v.inspect}" unless @@_valid_type.call(v)
98
+ r[i] = v
99
+ }
100
+ else
101
+ v = a[1] || nil
102
+ if (a[1])
103
+ throw "Illegal array member initializer: \#{v.inspect}" unless @@_valid_type.call(v)
104
+ end
105
+ if (a.size > 0)
106
+ (0...a[0]).each_index{|i|
107
+ r[i] = v
108
+ }
109
+ end
110
+ end
111
+ r
112
+ end
113
+
114
+ def self.[](*a)
115
+ r = self.new
116
+ r.concat(a)
117
+ end
118
+
119
+ def []=(*args)
120
+ element = args.last
121
+ throw "Illegal array member assignment: \#{element.inspect}" unless @@_valid_type.call(element)
122
+ super(*args)
123
+ end
124
+
125
+ def <<(element)
126
+ throw "Illegal array member append: \#{element.inspect}" unless @@_valid_type.call(element)
127
+ super(element)
128
+ end
129
+
130
+ def concat(other)
131
+ other.each{|e|
132
+ throw "Illegal array member in concat: \#{e.inspect}" unless @@_valid_type.call(e)
133
+ }
134
+ super(other)
135
+ end
136
+
137
+ def fill(*a, &b)
138
+ unless b
139
+ v = a.shift
140
+ throw "Illegal array value fill: \#{v.inspect}" unless @@_valid_type.call(v)
141
+ b = lambda{|i| v}
142
+ end
143
+
144
+ case a.size
145
+ when 0 # Fill all members:
146
+ self.each_index{|i|
147
+ e = b.call(i)
148
+ self[i] = e
149
+ }
150
+ when 1 # Fill start..end or using Range:
151
+ r = a[0]
152
+ r = (a[0]..self.size-1) unless r.kind_of?(Range)
153
+ r.each{|i|
154
+ e = b.call(i)
155
+ throw "Illegal array block fill: \#{e.inspect}" unless @@_valid_type.call(e)
156
+ self[i] = e
157
+ }
158
+ when 2
159
+ start = a[0]
160
+ a[0] = Range.new(start, start+a.pop)
161
+ end
162
+ self
163
+ end
164
+
165
+ def check_valid(operation)
166
+ each{|e|
167
+ throw "Illegal array element: \#{e.inspect} after \#{operation}" unless @@_valid_type.call(e)
168
+ }
169
+ end
170
+
171
+ def flatten!()
172
+ saved = clone
173
+ a = super
174
+ begin
175
+ check_valid "flatten!"
176
+ rescue
177
+ clear
178
+ concat saved
179
+ throw
180
+ end
181
+ a
182
+ end
183
+
184
+ def replace(a)
185
+ saved = clone
186
+ begin
187
+ clear
188
+ concat(a)
189
+ rescue
190
+ clear # Restore the value
191
+ concat saved
192
+ throw
193
+ end
194
+ self
195
+ end
196
+
197
+ def insert(*a)
198
+ start = a.shift
199
+ a.each{|e|
200
+ throw "Illegal array element insert: \#{e.inspect}" unless @@_valid_type.call(e)
201
+ }
202
+ super(start, *a)
203
+ end
204
+
205
+ def collect!
206
+ each_with_index{|e, i|
207
+ v = yield(e)
208
+ throw "Illegal array element in collect!: \#{v.inspect}" unless @@_valid_type.call(v)
209
+ self[i] = v
210
+ }
211
+ self
212
+ end
213
+
214
+ def map!(&b)
215
+ collect!(&b)
216
+ end
217
+
218
+ def push(*a)
219
+ concat a
220
+ self
221
+ end
222
+
223
+ self
224
+ END
225
+ end
226
+
227
+ =begin rdoc
228
+ The checked attribute methods are added to Module.
229
+ You can use them as a safer replacement for +attr_accessor+.
230
+ =end
231
+
232
+ class Module
233
+
234
+ =begin rdoc
235
+ call-seq:
236
+ typed_attr :attr
237
+ typed_attr nil, :attr
238
+ typed_attr Class, :attr
239
+ typed_attr Class, nil, 'default', :attr
240
+ typed_attr Class, nil, :attr do |val| val.isvalid end
241
+ typed_attr(Integer, 0, :attr) {|val| (0..5).include?(val) }
242
+ ... and combinations
243
+
244
+ typed_attr is like attr_accessor,
245
+ but with optional value checking using either _class_.kind_of? or by calling your block,
246
+ or both. Assignment of any value that fails the checks causes an assertion to be thrown.
247
+
248
+ The parameter list is processed in order, and may contain:
249
+ - a class. The following attributes will require values that are <tt>kind_of?</tt> this class
250
+ - +nil+. Adding +nil+ to the argument list allows +nil+ as a value for the following
251
+ attributes, which otherwise would disallow +nil+,
252
+ - a Symbol, which is used as the name for an attribute,
253
+ - any other value, which is used as a default value for following attributes.
254
+
255
+ In addition, typed_attr may be given a block.
256
+ Any value to be assigned will be passed to this block,
257
+ which must not return false/nil or an exception will be thrown.
258
+ You'll need to parenthesize the parameter list for a {|| } block,
259
+ or just use a do...end block.
260
+
261
+ Here's an example:
262
+ class MyClass
263
+ typed_attr nil, :attr1 # This attribute is unchecked
264
+ typed_attr :attr2 # This attribute may be assigned any non-nil value
265
+ typed_attr String, "hi", :attr3 # This attribute must be a string and defaults to "hi"
266
+ typed_attr Integer, 0, :attr4 do |i|
267
+ (0..5).include?(i) # This attribute must be an Integer in 0..5
268
+ end
269
+ typed_attr String, :foo, Integer, :bar # Two attributes of different types. Don't do this please!
270
+ typed_attr(String, :attr5) {|s|
271
+ s.size >= 4 # Values must have at least 4 characters
272
+ }
273
+ end
274
+
275
+ Note that if you don't allow nil, you should use a default value,
276
+ or a new object will break your rules. This won't cause an exception,
277
+ as typed_attr assumes you'll initialise the value in the object's
278
+ constructor.
279
+
280
+ =end
281
+ def typed_attr(*names, &block)
282
+ klass = Object
283
+ nil_ok = false
284
+ def_val = nil
285
+
286
+ names.each{|name|
287
+ case name
288
+ when Class
289
+ klass = name
290
+ when NilClass
291
+ nil_ok = true
292
+ when Symbol
293
+ define_method(name) {
294
+ v = instance_variable_get("@#{name}")
295
+ # This is awkward if nil is a valid value:
296
+ if (v == nil && (!nil_ok || !instance_variables.include?("@#{name}")))
297
+ v = instance_variable_set("@#{name}", def_val)
298
+ end
299
+ v
300
+ }
301
+ define_method("#{name}=") {|val|
302
+ if val == nil
303
+ unless nil_ok
304
+ throw "Can't assign nil to #{name} which is restricted to class #{klass}"
305
+ end
306
+ else
307
+ if !val.kind_of?(klass)
308
+ throw "Can't assign #{val.inspect} of class #{val.class} to attribute #{name} which is restricted to class #{klass}"
309
+ elsif (block && !block.call(val))
310
+ raise "Invalid value assigned to #{name}: #{val.inspect}"
311
+ end
312
+ end
313
+ instance_variable_set("@#{name}", val)
314
+ }
315
+ else
316
+ def_val = name # Save this as a default value
317
+ end
318
+ }
319
+ end
320
+
321
+ =begin rdoc
322
+ call-seq:
323
+ array_attr :attr
324
+ array_attr Class, :attr, Class2, :attr2
325
+ array_attr Class, :attr do |val| val.isvalid end
326
+ array_attr(:attr) {|val| val.isvalid }
327
+
328
+ array_attr is like attr_accessor, but the attributes it creates are checked Arrays, see checked Array.
329
+
330
+ The parameter list is processed in order, and may contain:
331
+ - a class. The following array attributes will require values that are <tt>kind_of?</tt> this class
332
+ - a Symbol, which is used as the name for an array attribute.
333
+
334
+ In addition, array_attr may be given a block.
335
+ Any value to be used as a member of the array will be passed to this block,
336
+ which must not return false/nil or an exception will be thrown.
337
+ You'll need to parenthesize the parameter list for a {|| } block,
338
+ or just use a do...end block.
339
+
340
+ There's no need to initialize the attribute with an empty array, that comes for free.
341
+
342
+ nil checking and default values are not provided as with +typed_attr+.
343
+
344
+ Here's an example:
345
+ class MyClass
346
+ array_attr String, :attr1 do|s|
347
+ s.size >= 5 # All array members must have >=5 characters
348
+ end
349
+ array_attr(:attr2) {|e| # Array members must be Integers or Strings
350
+ e.kind_of?(String) || e.kind_of?(Integer)
351
+ }
352
+ end
353
+
354
+ c = MyClass.new
355
+ c.attr1 << "ouch" # Error, string is too short
356
+ c.attr2 << { :hi => "there" } # Error, value must be Integer or String
357
+ =end
358
+ def array_attr(*names, &block)
359
+ klass = Object
360
+ names.each{|name|
361
+ case name
362
+ when Class
363
+ klass = name
364
+ when Symbol
365
+ define_method(name) {
366
+ instance_variable_get("@#{name}") ||
367
+ instance_variable_set("@#{name}", Array(klass, &block).new)
368
+ }
369
+ define_method("#{name}=") {|val|
370
+ a = instance_variable_get("@#{name}") ||
371
+ instance_variable_set("@#{name}", Array(klass, &block).new)
372
+ saved = a.clone
373
+ a.clear
374
+ begin
375
+ a.concat(val) # If conversion is legal, this will do it
376
+ rescue
377
+ instance_variable_set("@#{name}", saved)
378
+ throw
379
+ end
380
+ }
381
+ else
382
+ throw "Parameter to array_attr must be Class or Symbol"
383
+ end
384
+ }
385
+ end
386
+ end
data/spec/runtest.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ gem 'rspec', ">= 0.8.0"
3
+ require 'spec'
4
+
5
+ Spec::Runner::CommandLine::run(
6
+ %w{test/chattr_spec.rb -f s},
7
+ $stderr, $stdout
8
+ )
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: chattr
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.9.0
7
+ date: 2007-03-03 00:00:00 +11:00
8
+ summary: Methods for defining type-checked arrays and attributes
9
+ require_paths:
10
+ - lib
11
+ email: clifford dot heath at gmail dot com
12
+ homepage: http://rubyforge.org/projects/chattr
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: chattr
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Clifford Heath
31
+ files:
32
+ - lib/chattr.rb
33
+ - LICENSE
34
+ test_files:
35
+ - spec/runtest.rb
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ requirements: []
45
+
46
+ dependencies: []
47
+