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