chattr 0.9.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/LICENSE +19 -0
- data/lib/chattr.rb +386 -0
- data/spec/runtest.rb +8 -0
- 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
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
|
+
|