doodle 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/History.txt +24 -0
  2. data/Manifest.txt +26 -1
  3. data/README.txt +9 -8
  4. data/lib/doodle.rb +43 -1496
  5. data/lib/doodle/app.rb +6 -0
  6. data/lib/doodle/attribute.rb +165 -0
  7. data/lib/doodle/base.rb +180 -0
  8. data/lib/doodle/collector-1.9.rb +72 -0
  9. data/lib/doodle/collector.rb +191 -0
  10. data/lib/doodle/comparable.rb +8 -0
  11. data/lib/doodle/conversion.rb +80 -0
  12. data/lib/doodle/core.rb +42 -0
  13. data/lib/doodle/datatype-holder.rb +39 -0
  14. data/lib/doodle/debug.rb +20 -0
  15. data/lib/doodle/deferred.rb +13 -0
  16. data/lib/doodle/equality.rb +21 -0
  17. data/lib/doodle/exceptions.rb +29 -0
  18. data/lib/doodle/factory.rb +91 -0
  19. data/lib/doodle/getter-setter.rb +154 -0
  20. data/lib/doodle/info.rb +298 -0
  21. data/lib/doodle/inherit.rb +40 -0
  22. data/lib/doodle/json.rb +38 -0
  23. data/lib/doodle/marshal.rb +16 -0
  24. data/lib/doodle/normalized_array.rb +512 -0
  25. data/lib/doodle/normalized_hash.rb +356 -0
  26. data/lib/doodle/ordered-hash.rb +8 -0
  27. data/lib/doodle/singleton.rb +23 -0
  28. data/lib/doodle/smoke-and-mirrors.rb +23 -0
  29. data/lib/doodle/to_hash.rb +17 -0
  30. data/lib/doodle/utils.rb +173 -11
  31. data/lib/doodle/validation.rb +122 -0
  32. data/lib/doodle/version.rb +1 -1
  33. data/lib/molic_orderedhash.rb +24 -10
  34. data/spec/assigned_spec.rb +45 -0
  35. data/spec/attributes_spec.rb +7 -7
  36. data/spec/collector_spec.rb +100 -13
  37. data/spec/doodle_context_spec.rb +5 -5
  38. data/spec/from_spec.rb +43 -3
  39. data/spec/json_spec.rb +232 -0
  40. data/spec/member_init_spec.rb +11 -11
  41. data/spec/modules_spec.rb +4 -4
  42. data/spec/multi_collector_spec.rb +91 -0
  43. data/spec/must_spec.rb +32 -0
  44. data/spec/spec_helper.rb +14 -4
  45. data/spec/specialized_attribute_class_spec.rb +2 -2
  46. data/spec/typed_collector_spec.rb +57 -0
  47. data/spec/xml_spec.rb +8 -8
  48. metadata +33 -3
@@ -0,0 +1,298 @@
1
+ class Doodle
2
+
3
+ # place to stash bookkeeping info
4
+ class DoodleInfo
5
+ attr_accessor :this
6
+ attr_accessor :local_attributes
7
+ attr_accessor :local_validations
8
+ attr_accessor :local_conversions
9
+ attr_accessor :validation_on
10
+ attr_accessor :arg_order
11
+ attr_accessor :errors
12
+ attr_accessor :parent
13
+
14
+ # takes one param - the object being doodled
15
+ def initialize(object)
16
+ @this = object
17
+ @local_attributes = Doodle::OrderedHash.new
18
+ @local_validations = []
19
+ @validation_on = true
20
+ @local_conversions = {}
21
+ @arg_order = []
22
+ @errors = []
23
+ #@parent = nil
24
+ @parent = Doodle.parent
25
+ end
26
+
27
+ # hide from inspect
28
+ m = instance_method(:inspect)
29
+ define_method :__inspect__ do
30
+ m.bind(self).call
31
+ end
32
+
33
+ # hide from inspect
34
+ def inspect
35
+ ''
36
+ end
37
+
38
+ # handle errors either by collecting in :errors or raising an exception
39
+ def handle_error(name, *args)
40
+ # don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
41
+ if !errors.include?([name, *args])
42
+ errors << [name, *args]
43
+ end
44
+ if Doodle.raise_exception_on_error
45
+ raise(*args)
46
+ end
47
+ end
48
+
49
+ # provide an alternative inheritance chain that works for singleton
50
+ # classes as well as modules, classes and instances
51
+ def parents
52
+ anc = if @this.respond_to?(:ancestors)
53
+ if @this.ancestors.include?(@this)
54
+ @this.ancestors[1..-1]
55
+ else
56
+ # singletons have no doodle_parents (they're orphans)
57
+ []
58
+ end
59
+ else
60
+ @this.class.ancestors
61
+ end
62
+ anc.select{|x| x.kind_of?(Class)}
63
+ end
64
+
65
+ # send message to all doodle_parents and collect results
66
+ def collect_inherited(message)
67
+ result = []
68
+ parents.each do |klass|
69
+ if klass.respond_to?(:doodle) && klass.doodle.respond_to?(message)
70
+ result.unshift(*klass.doodle.__send__(message))
71
+ else
72
+ break
73
+ end
74
+ end
75
+ result
76
+ end
77
+ private :collect_inherited
78
+
79
+ # collect results of calling method which returns a hash
80
+ # - if tf == true, returns all inherited attributes
81
+ # - if tf == false, returns only those attributes defined in the current object/class
82
+ def handle_inherited_hash(tf, method)
83
+ if tf
84
+ collect_inherited(method).inject(Doodle::OrderedHash.new){ |hash, item|
85
+ hash.merge(Doodle::OrderedHash[*item])
86
+ }.merge(@this.doodle.__send__(method))
87
+ else
88
+ @this.doodle.__send__(method)
89
+ end
90
+ end
91
+ private :handle_inherited_hash
92
+
93
+ # returns array of Attributes
94
+ # - if tf == true, returns all inherited attributes
95
+ # - if tf == false, returns only those attributes defined in the current object/class
96
+ def attributes(tf = true)
97
+ results = handle_inherited_hash(tf, :local_attributes)
98
+ # if an instance, include the singleton_class attributes
99
+ #p [:attributes, @this, @this.singleton_class, @this.singleton_class.methods(false), results]
100
+ if !@this.kind_of?(Class) && @this.singleton_class.doodle.respond_to?(:attributes)
101
+ results = results.merge(@this.singleton_class.doodle.attributes)
102
+ end
103
+ results
104
+ end
105
+
106
+ # return class level attributes
107
+ def class_attributes
108
+ attrs = Doodle::OrderedHash.new
109
+ if @this.kind_of?(Class)
110
+ attrs = collect_inherited(:class_attributes).inject(Doodle::OrderedHash.new){ |hash, item|
111
+ hash.merge(Doodle::OrderedHash[*item])
112
+ }.merge(@this.singleton_class.doodle.respond_to?(:attributes) ? @this.singleton_class.doodle.attributes : { })
113
+ attrs
114
+ else
115
+ @this.class.doodle.class_attributes
116
+ end
117
+ end
118
+
119
+ # access list of validations
120
+ #
121
+ # note: validations are handled differently to attributes and
122
+ # conversions because ~all~ validations apply (so are stored as an
123
+ # array), whereas attributes and conversions are keyed by name and
124
+ # kind respectively, so only the most recent applies
125
+ #
126
+ def validations(tf = true)
127
+ if tf
128
+ local_validations + collect_inherited(:local_validations)
129
+ else
130
+ local_validations
131
+ end
132
+ end
133
+
134
+ # find an attribute
135
+ def lookup_attribute(name)
136
+ # (look at singleton attributes first)
137
+ # fixme[this smells like a hack to me]
138
+ if @this.class == Class
139
+ class_attributes[name]
140
+ else
141
+ attributes[name]
142
+ end
143
+ end
144
+
145
+ # returns hash of conversions
146
+ # - if tf == true, returns all inherited conversions
147
+ # - if tf == false, returns only those conversions defined in the current object/class
148
+ def conversions(tf = true)
149
+ handle_inherited_hash(tf, :local_conversions)
150
+ end
151
+
152
+ # return hash of key => value pairs of initial values (where defined)
153
+ # - if tf == true, returns all inherited initial values
154
+ # - if tf == false, returns only those initial values defined in current object/class
155
+ def initial_values(tf = true)
156
+ attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
157
+ #p [:initial_values, a.name]
158
+ hash[n] = case a.init
159
+ when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum, Symbol
160
+ # uncloneable values
161
+ #p [:initial_values, :special, a.name, a.init]
162
+ a.init
163
+ when DeferredBlock
164
+ #p [:initial_values, self, DeferredBlock, a.name]
165
+ begin
166
+ @this.instance_eval(&a.init.block)
167
+ rescue Object => e
168
+ #p [:exception_in_deferred_block, e]
169
+ raise
170
+ end
171
+ else
172
+ if a.init.kind_of?(Class)
173
+ #p [:initial_values, :class]
174
+ a.init.new
175
+ else
176
+ #p [:initial_values, :clone, a.name]
177
+ begin
178
+ a.init.clone
179
+ rescue Exception => e
180
+ warn "tried to clone #{a.init.class} in :init option (#{e})"
181
+ #p [:initial_values, :exception, a.name, e]
182
+ a.init
183
+ end
184
+ end
185
+ end
186
+ hash
187
+ }
188
+ end
189
+
190
+ # turn off validation, execute block, then set validation to same
191
+ # state as it was before +defer_validation+ was called - can be nested
192
+ def defer_validation(&block)
193
+ #p [:defer_validation, self.validation_on, @this]
194
+ old_validation = self.validation_on
195
+ self.validation_on = false
196
+ v = nil
197
+ begin
198
+ v = @this.instance_eval(&block)
199
+ ensure
200
+ self.validation_on = old_validation
201
+ end
202
+ @this.validate!(false)
203
+ v
204
+ end
205
+
206
+ # helper function to initialize from hash - this is safe to use
207
+ # after initialization (validate! is called if this method is
208
+ # called after initialization)
209
+ def update(*args, &block)
210
+ # p [:doodle_initialize_from_hash, :args, *args]
211
+ defer_validation do
212
+ # hash initializer
213
+ # separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
214
+ key_values, args = args.partition{ |x| x.kind_of?(Hash)}
215
+ #DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args] }
216
+ #!p [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args]
217
+
218
+ # set up initial values with ~clones~ of specified values (so not shared between instances)
219
+ #init_values = initial_values
220
+ #!p [:init_values, init_values]
221
+
222
+ # match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
223
+ #arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
224
+ arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
225
+ #!p [self.class, :doodle_initialize_from_hash, :arg_keywords, arg_keywords]
226
+
227
+ # merge all hash args into one
228
+ key_values = key_values.inject(arg_keywords) { |hash, item|
229
+ #!p [self.class, :doodle_initialize_from_hash, :merge, hash, item]
230
+ hash.merge(item)
231
+ }
232
+ #!p [self.class, :doodle_initialize_from_hash, :key_values2, key_values]
233
+
234
+ # convert keys to symbols (note not recursively - only first level == doodle keywords)
235
+ Doodle::Utils.symbolize_keys!(key_values)
236
+ #DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values2, key_values, :args2, args] }
237
+ #!p [self.class, :doodle_initialize_from_hash, :key_values3, key_values]
238
+
239
+ # create attributes
240
+ key_values.keys.each do |key|
241
+ #DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]] }
242
+ #p [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]]
243
+ #p [:update, :setting, key, key_values[key], __doodle__.validation_on]
244
+ if respond_to?(key)
245
+ __send__(key, key_values[key])
246
+ else
247
+ # raise error if not defined
248
+ __doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' => #{key_values[key].inspect} for #{self} #{doodle.attributes.map{ |k,v| k.inspect}.join(', ')}", Doodle::Utils.doodle_caller
249
+ end
250
+ end
251
+ # do init_values after user supplied values so init blocks can
252
+ # depend on user supplied values
253
+ # - don't reset values which are supplied in args
254
+ #p [:getting_init_values, instance_variables]
255
+ __doodle__.initial_values.each do |key, value|
256
+ if !key_values.key?(key) && respond_to?(key)
257
+ #p [:initial_values, key, value]
258
+ __send__(key, value)
259
+ end
260
+ end
261
+ if block_given?
262
+ #p [:update, block, __doodle__.validation_on]
263
+ #p [:this, self]
264
+ instance_eval(&block)
265
+ end
266
+ end
267
+ @this
268
+ end
269
+
270
+ # returns array of values (including defaults)
271
+ # - if tf == true, returns all inherited values (default)
272
+ # - if tf == false, returns only those values defined in current object
273
+ def values(tf = true)
274
+ attributes(tf).map{ |k, a| @this.send(k)}
275
+ end
276
+
277
+ # returns array of attribute names
278
+ # - if tf == true, returns all inherited attribute names (default)
279
+ # - if tf == false, returns only those attribute names defined in current object
280
+ def keys(tf = true)
281
+ attributes(tf).keys
282
+ end
283
+
284
+ # returns array of [key, value] pairs including default values
285
+ # - if tf == true, returns all inherited [key, value] pairs (default)
286
+ # - if tf == false, returns only those [key, value] pairs defined in current object
287
+ def key_values(tf = true)
288
+ keys(tf).zip(values(tf))
289
+ end
290
+
291
+ # returns array of [key, value] pairs excluding default values
292
+ # - if tf == true, returns all inherited [key, value] pairs (default)
293
+ # - if tf == false, returns only those [key, value] pairs defined in current object
294
+ def key_values_without_defaults(tf = true)
295
+ keys(tf).reject{|k| @this.default?(k) }.map{ |k, a| [k, @this.send(k)]}
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,40 @@
1
+ class Doodle
2
+ # = inherit
3
+ # the intent of inherit is to provide a way to create directives
4
+ # that affect all members of a class 'family' without having to
5
+ # modify Module, Class or Object - in some ways, it's similar to Ara
6
+ # Howard's mixable[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/197296]
7
+ # though not as tidy :S
8
+ #
9
+ # this works down to third level <tt>class << self</tt> - in practice, this is
10
+ # perfectly good - it would be great to have a completely general
11
+ # solution but I doubt whether the payoff is worth the effort
12
+
13
+ module Inherited
14
+ def self.included(other)
15
+ other.extend(Inherited)
16
+ other.send(:include, Factory)
17
+ end
18
+
19
+ # fake module inheritance chain
20
+ def inherit(other, &block)
21
+ # include in instance method chain
22
+ include other
23
+ include Inherited
24
+
25
+ sc = class << self; self; end
26
+ sc.module_eval {
27
+ # class method chain
28
+ include other
29
+ # singleton method chain
30
+ extend other
31
+ # ensure that subclasses also inherit this module
32
+ define_method :inherited do |klass|
33
+ #p [:inherit, :inherited, klass]
34
+ klass.__send__(:inherit, other) # n.b. closure
35
+ end
36
+ }
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,38 @@
1
+ require 'json'
2
+ require 'pp'
3
+
4
+ class Doodle
5
+ module JSON
6
+ module InstanceMethods
7
+ def to_json(*a)
8
+ # don't include default values
9
+ values = doodle.key_values_without_defaults
10
+ value_hash = Hash[*Doodle::Utils.flatten_first_level(values)]
11
+ {
12
+ 'json_class' => self.class.name,
13
+ 'data' => value_hash,
14
+ }.to_json(*a)
15
+ end
16
+ end
17
+ module ClassMethods
18
+ def json_create(o)
19
+ #pp [:json_create, o]
20
+ const = Doodle::Utils.const_resolve(o['json_class'])
21
+ const.new(o['data'])
22
+ end
23
+ def from_json(src)
24
+ v = ::JSON::parse(src)
25
+ if v.kind_of?(Hash)
26
+ new(v)
27
+ else
28
+ v
29
+ end
30
+ end
31
+ end
32
+ def self.included(other)
33
+ other.module_eval { include InstanceMethods }
34
+ other.extend(ClassMethods)
35
+ end
36
+ end
37
+ include Doodle::JSON
38
+ end
@@ -0,0 +1,16 @@
1
+ class Doodle
2
+ module ModMarshal
3
+ # helper for Marshal.dump
4
+ def marshal_dump
5
+ # note: perhaps should also dump singleton attribute definitions?
6
+ instance_variables.map{|x| [x, instance_variable_get(x)] }
7
+ end
8
+ # helper for Marshal.load
9
+ def marshal_load(data)
10
+ data.each do |name, value|
11
+ instance_variable_set(name, value)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,512 @@
1
+ require 'enumerator'
2
+
3
+ module ArraySentence
4
+ def join_with(sep = ', ', last = ' and ')
5
+ if self.size > 1
6
+ self[0..-2].join(sep) + last + self[-1].to_s
7
+ else
8
+ self[0].to_s
9
+ end
10
+ end
11
+ end
12
+
13
+ module NormalizedArrayMethods
14
+ module InstanceMethods
15
+ include ArraySentence
16
+
17
+ # def &(other)
18
+ # super
19
+ # end
20
+
21
+ # def *(other)
22
+ # super
23
+ # end
24
+
25
+ # def +(other)
26
+ # super
27
+ # end
28
+
29
+ # def -(other)
30
+ # super
31
+ # end
32
+
33
+ def initialize(*args, &block)
34
+ #p [:init, 1, args, block]
35
+ if block_given?
36
+ #p [:init, 2]
37
+ original_block = block
38
+ block = proc { |index|
39
+ #p [:init, 3, index]
40
+ normalize_value(original_block[normalize_index(index)])
41
+ }
42
+ #p [:init, 4, block]
43
+ end
44
+ replace(super(*args, &block))
45
+ end
46
+
47
+ # def initialize(*args, &block)
48
+ # replace(super)
49
+ # end
50
+
51
+ def normalize_index(index)
52
+ index
53
+ end
54
+
55
+ def normalize_value(value)
56
+ p [self.class, :normalize_value, value]
57
+ value
58
+ end
59
+
60
+ # convenience method to check indices
61
+ def normalize_indices(*indices)
62
+ indices.map{ |index| normalize_index(index) }
63
+ end
64
+
65
+ # convenience method to check values
66
+ def normalize_values(*values)
67
+ if values.empty?
68
+ values = self
69
+ end
70
+ values.map{ |value| normalize_value(value) }
71
+ end
72
+
73
+ def <<(value)
74
+ super(normalize_value(value))
75
+ end
76
+
77
+ # def <=>(other)
78
+ # super(normalize_values(*other))
79
+ # end
80
+
81
+ # def ==(other)
82
+ # super(normalize_values(*other))
83
+ # end
84
+
85
+ def [](index)
86
+ super(normalize_index(index))
87
+ end
88
+
89
+ def []=(index, value)
90
+ super(normalize_index(index), normalize_value(value))
91
+ end
92
+
93
+ # def assoc(key)
94
+ # super
95
+ # end
96
+
97
+ def at(index)
98
+ super(normalize_index(index))
99
+ end
100
+
101
+ # def clear()
102
+ # super
103
+ # end
104
+
105
+ # def collect(&block)
106
+ # #FIXME
107
+ # end
108
+
109
+ def collect!(&block)
110
+ super() {|x| normalize_value(block.call(x))}
111
+ end
112
+
113
+ # def compact()
114
+ # super
115
+ # end
116
+
117
+ # def compact!()
118
+ # super
119
+ # end
120
+
121
+ def concat(other)
122
+ super(normalize_values(*other))
123
+ end
124
+
125
+ def delete(value)
126
+ super(normalize_value(value))
127
+ end
128
+
129
+ def delete_at(index)
130
+ super(normalize_index(index))
131
+ end
132
+
133
+ # def delete_if(&block)
134
+ # super
135
+ # end
136
+
137
+ # def each(&block)
138
+ # super
139
+ # end
140
+
141
+ # def each_index(&block)
142
+ # super
143
+ # end
144
+
145
+ # def empty?()
146
+ # super
147
+ # end
148
+
149
+ # def eql?(other)
150
+ # super(normalize_values(*other))
151
+ # end
152
+
153
+ def fetch(index)
154
+ super(normalize_index(index))
155
+ end
156
+
157
+ # array.fill(obj) -> array
158
+ # array.fill(obj, start [, length]) -> array
159
+ # array.fill(obj, range ) -> array
160
+ # array.fill {|index| block } -> array
161
+ # array.fill(start [, length] ) {|index| block } -> array
162
+ # array.fill(range) {|index| block } -> array
163
+ def fill(*args, &block)
164
+ # TODO: check value
165
+ # check start, length
166
+ replace(super)
167
+ end
168
+
169
+ def first(count = 1)
170
+ # TODO: check count
171
+ super
172
+ end
173
+
174
+ # def flatten()
175
+ # super
176
+ # end
177
+
178
+ # def flatten!()
179
+ # super
180
+ # end
181
+
182
+ # def frozen?()
183
+ # super
184
+ # end
185
+
186
+ # def hash()
187
+ # super
188
+ # end
189
+
190
+ def include?(value)
191
+ super(normalize_value(value))
192
+ end
193
+
194
+ def index(value)
195
+ super(normalize_value(value))
196
+ end
197
+
198
+ def insert(index, *values)
199
+ super(normalize_index(index), normalize_values(*values))
200
+ end
201
+
202
+ # def inspect()
203
+ # super
204
+ # end
205
+
206
+ # def join(sep = $,)
207
+ # super
208
+ # end
209
+
210
+ def last(count = 1)
211
+ # TODO: check count
212
+ super
213
+ end
214
+
215
+ # def length()
216
+ # super
217
+ # end
218
+
219
+ # def map(&block)
220
+ # super
221
+ # end
222
+
223
+ def map!(&block)
224
+ collect!(&block)
225
+ end
226
+
227
+ # def nitems()
228
+ # super
229
+ # end
230
+
231
+ # def pack()
232
+ # super
233
+ # end
234
+
235
+ # def pop()
236
+ # super
237
+ # end
238
+
239
+ def push(*values)
240
+ super(normalize_values(*values))
241
+ end
242
+
243
+ # def rassoc(key)
244
+ # super
245
+ # end
246
+
247
+ # def reject(&block)
248
+ # super
249
+ # end
250
+
251
+ def reject!(&block)
252
+ super() {|x| normalize_value(block.call(x))}
253
+ end
254
+
255
+ def replace(other)
256
+ #p [:replace, other]
257
+ super(other.to_enum(:each_with_index).map{ |v, i|
258
+ #p [:replace_norm, i, v]
259
+ normalize_index(i);
260
+ normalize_value(v)
261
+ })
262
+ end
263
+
264
+ # def reverse()
265
+ # super
266
+ # end
267
+
268
+ # def reverse!()
269
+ # super
270
+ # end
271
+
272
+ # def reverse_each(&block)
273
+ # super
274
+ # end
275
+
276
+ def rindex(value)
277
+ super(normalize_value(value))
278
+ end
279
+
280
+ # def select(&block)
281
+ # super
282
+ # end
283
+
284
+ # def shift()
285
+ # super
286
+ # end
287
+
288
+ # alias :size :length
289
+
290
+ # array[index] -> obj or nil
291
+ # array[start, length] -> an_array or nil
292
+ # array[range] -> an_array or nil
293
+ # array.slice(index) -> obj or nil
294
+ # array.slice(start, length) -> an_array or nil
295
+ # array.slice(range) -> an_array or nil
296
+ def slice(*args)
297
+ # TODO: check indices
298
+ super
299
+ end
300
+
301
+ def slice!(*args)
302
+ # TODO: check indices
303
+ super
304
+ end
305
+
306
+ # def sort(&block)
307
+ # super
308
+ # end
309
+
310
+ # def sort!(&block)
311
+ # super
312
+ # end
313
+
314
+ # def to_a()
315
+ # super
316
+ # end
317
+
318
+ # def to_ary()
319
+ # super
320
+ # end
321
+
322
+ # def to_s()
323
+ # super
324
+ # end
325
+
326
+ # def transpose()
327
+ # super
328
+ # end
329
+
330
+ # def uniq()
331
+ # super
332
+ # end
333
+
334
+ # def uniq!()
335
+ # super
336
+ # end
337
+
338
+ def unshift(*values)
339
+ super(normalize_values(*values))
340
+ end
341
+
342
+ def values_at(*indices)
343
+ super(normalize_indices(*indices))
344
+ end
345
+ # deprecated - use values_at
346
+ alias :indexes :values_at
347
+ alias :indices :values_at
348
+
349
+ # should this be normalized?
350
+ def zip(other)
351
+ super(normalize_values(*other))
352
+ end
353
+
354
+ # def |(other)
355
+ # super(other.map{ |x| normalize_value(x)})
356
+ # end
357
+
358
+ end
359
+
360
+ module ClassMethods
361
+ def [](*args)
362
+ super.normalize_values
363
+ end
364
+ end
365
+ end
366
+
367
+ class NormalizedArray < Array
368
+ include NormalizedArrayMethods::InstanceMethods
369
+ extend NormalizedArrayMethods::ClassMethods
370
+ end
371
+
372
+ if __FILE__ == $0
373
+ require 'rubygems'
374
+ require 'assertion'
375
+
376
+ class StringArray < NormalizedArray
377
+ def normalize_value(v)
378
+ p [self.class, :normalize_value, v]
379
+ v.to_s
380
+ end
381
+ end
382
+
383
+ def BoundedArray(upper_bound)
384
+ typed_class = Class.new(NormalizedArray) do
385
+ define_method :normalize_index do |index|
386
+ raise IndexError, "index #{index} out of range" if !(0..upper_bound).include?(index)
387
+ index
388
+ end
389
+ end
390
+ typed_class
391
+ end
392
+
393
+ def TypedArray(*klasses)
394
+ typed_class = Class.new(NormalizedArray) do
395
+ define_method :normalize_value do |v|
396
+ if !klasses.any?{ |klass| v.kind_of?(klass) }
397
+ raise TypeError, "#{self.class}: #{v.class}(#{v.inspect}) is not a kind of #{klasses.map{ |c| c.to_s }.join(', ')}", [caller[-1]]
398
+ end
399
+ v
400
+ end
401
+ end
402
+ typed_class
403
+ end
404
+ TypedStringArray = TypedArray(String)
405
+
406
+ # na = Array.new(4) { |i|
407
+ # p [:in_block, i]
408
+ # 42
409
+ # }
410
+ # p na[1]
411
+ # p na[1] == 42
412
+
413
+ sa = StringArray.new(3) { |i|
414
+ p [:in_block, i]
415
+ 42
416
+ }
417
+ assert { sa[1] == "42" }
418
+ assert_error { sa[1] == 42 }
419
+ assert_error { sa.values == ["42"] * 3 }
420
+ assert_error { sa.values == [42] }
421
+
422
+ sa = nil
423
+ assert_ok { sa = StringArray.new([1,2,3]) }
424
+ assert { sa == ["1", "2", "3"] }
425
+ assert { (sa | ["4", "5", "6"]) == ["1", "2", "3", "4", "5", "6"] }
426
+ assert { (sa | [4, 5, 6]) == ["1", "2", "3", 4, 5, 6] }
427
+ assert { (sa | [4, 5, 6]).class == Array }
428
+
429
+ # equality
430
+ assert { sa == ["1", "2", "3"] }
431
+
432
+ ## -FIXME: not sure about this coercion-
433
+ assert_error { sa.eql?( [1, 2, 3] )}
434
+
435
+ ## -FIXME: equality should not be non-commutative-
436
+ assert_error { sa == [1, 2, 3] }
437
+ assert_error { [1, 2, 3] == sa }
438
+
439
+ BoundedArray4 = BoundedArray(4)
440
+ ca = BoundedArray4.new([1,2,3])
441
+ assert { ca == [1, 2, 3] }
442
+ assert { ca[4] = 42 }
443
+ expect_error(IndexError) { ca[5] = 42 }
444
+ expect_error('out of range') { ca[5] = 42 }
445
+ expect_error { ca[5] = 42 }
446
+ #expect_ok { false }
447
+ #expect_error { true }
448
+ #assert_ok { true }
449
+
450
+ assert_ok { ca = BoundedArray4.new([1,2,3,4,5]) }
451
+ assert_error { ca = BoundedArray4.new([1,2,3,4,5,6]) }
452
+
453
+ assert_error { sa = TypedStringArray.new([1,2,3]) }
454
+ assert_ok { sa = TypedStringArray.new(["1","2","3"]) }
455
+
456
+ # ca = BoundedArray.new([1,2,3,4,5])
457
+ # pp ca
458
+
459
+ TypedIntegerArray = TypedArray(Integer)
460
+ ia = TypedIntegerArray.new([1,2,3])
461
+ expect_error { ia[1] = "hello" }
462
+
463
+ StringOrIntegerArray = TypedArray(Integer, String)
464
+ ma = nil
465
+ expect_ok { ma = StringOrIntegerArray.new([1,"2",3]) }
466
+ expect_ok { ma[0] = "hello" }
467
+ expect_error { ma[1] = Date.new }
468
+
469
+ assert {
470
+ ma = [].extend(ArraySentence)
471
+ ma.join_with(', ', ' and ') == ""
472
+ }
473
+
474
+ assert {
475
+ ma = [nil].extend(ArraySentence)
476
+ ma.join_with(', ', ' and ') == ""
477
+ }
478
+
479
+ assert {
480
+ ma = [nil, nil].extend(ArraySentence)
481
+ ma.join_with(', ', ' and ') == " and "
482
+ }
483
+
484
+ assert {
485
+ ma = [nil, nil, nil].extend(ArraySentence)
486
+ ma.join_with(', ', ' and ') == ", and "
487
+ }
488
+
489
+ assert {
490
+ ma = [1].extend(ArraySentence)
491
+ ma.join_with(', ', ' and ') == "1"
492
+ }
493
+
494
+ assert {
495
+ ma = [1, 2].extend(ArraySentence)
496
+ ma.join_with(', ', ' and ') == "1 and 2"
497
+ }
498
+
499
+ assert {
500
+ ma = [1, 2, 3].extend(ArraySentence)
501
+ ma.join_with(', ', ' or ') == "1, 2 or 3"
502
+ }
503
+
504
+ assert {
505
+ ma = [1, 2, 3].extend(ArraySentence)
506
+ ma.join_with(', ', ' and ') == "1, 2 and 3"
507
+ }
508
+
509
+ expect_error("Fixnum(1) is not a kind of String") { TypedStringArray[1,2,3] }
510
+ expect_error(TypeError) { TypedStringArray[1,2,3] }
511
+
512
+ end