constraint 0.1 → 0.2

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.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ # queens.rb
3
+ # @Author: Thomas Link (samul@web.de)
4
+ # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
5
+ # @Created: 11-Jun-2005.
6
+ # @Last Change: 12-Jun-2005.
7
+ # @Revision: 0.290
8
+
9
+ require 'constraint'
10
+
11
+ QUEEN_MIN = 1
12
+ QUEEN_MAX = 5
13
+ MAX_BOARD_N = (QUEEN_MAX ** QUEEN_MAX) - 1
14
+
15
+ class QueensBoard < Constraint::CArray
16
+ attr_reader :curr_board_n
17
+
18
+ def new_constrained
19
+ @boards = []
20
+ @masks = []
21
+ @curr_board_n = 0
22
+ super
23
+ end
24
+
25
+ def generate_constrained_value(value)
26
+ if (@curr_board_n += 1) <= MAX_BOARD_N
27
+ return board(@curr_board_n)
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def board(pos=nil)
34
+ if pos
35
+ unless (b = @boards[pos])
36
+ b = @boards[pos] = []
37
+ (QUEEN_MAX - 1).downto(0) do |x|
38
+ y, pos = pos.divmod(QUEEN_MAX ** x)
39
+ b << y
40
+ end
41
+ end
42
+ return b
43
+ else
44
+ return @constrained_value
45
+ end
46
+ end
47
+
48
+ def good?(x, y)
49
+ @constrained_value.each_with_index do |ox, oy|
50
+ if y == oy
51
+ next
52
+ elsif x == ox or ((y - oy).abs == (x - ox).abs)
53
+ return false
54
+ end
55
+ end
56
+ return true
57
+ end
58
+
59
+ or_constraint('Good') do |qb, queen|
60
+ rv = true
61
+ qb.constrained_value.each_with_index do |x, y|
62
+ if !qb.good?(x, y)
63
+ rv = false
64
+ break
65
+ end
66
+ end
67
+ rv
68
+ end
69
+ end
70
+
71
+
72
+ if __FILE__ == $0
73
+ b = QueensBoard.new
74
+
75
+ puts "First solution:"
76
+ p b.next_constrained_value!.board.collect {|e| e}
77
+
78
+ puts "All Solutions:"
79
+ for board in b.collect_constrained_values_until {|e| b.curr_board_n >= MAX_BOARD_N}
80
+ p board.board.collect {|e| e}
81
+ end
82
+ end
83
+
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+ # queens.rb
3
+ # @Author: Thomas Link (samul@web.de)
4
+ # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
5
+ # @Created: 11-Jun-2005.
6
+ # @Last Change: 12-Jun-2005.
7
+ # @Revision: 0.388
8
+
9
+ require 'constraint'
10
+
11
+ QUEEN_MIN = 1
12
+ QUEEN_MAX = 5
13
+ MAX_BOARD_N = (QUEEN_MAX ** QUEEN_MAX) - 1
14
+
15
+ class QueensBoard < Constraint::CArray
16
+ attr_reader :curr_board_n
17
+
18
+ def new_constrained
19
+ @boards = []
20
+ @masks = []
21
+ @curr_board_n = 0
22
+ super
23
+ end
24
+
25
+ def generate_constrained_value(value)
26
+ if (@curr_board_n += 1) <= MAX_BOARD_N
27
+ return board(@curr_board_n)
28
+ else
29
+ super(@curr_board_n)
30
+ end
31
+ end
32
+
33
+ def board(pos=nil)
34
+ if pos
35
+ unless (@constrained_value = @boards[pos])
36
+ @constrained_value = []
37
+ (QUEEN_MAX - 1).downto(0) do |x|
38
+ y, pos = pos.divmod(QUEEN_MAX ** x)
39
+ self << [x, y]
40
+ end
41
+ @boards[pos] = @constrained_value
42
+ end
43
+ end
44
+ return @constrained_value
45
+ end
46
+
47
+ or_constraint('Good') do |qb, queen|
48
+ x, y = queen
49
+ rv = true
50
+ qb.constrained_value.each do |other_queen|
51
+ if queen.equal?(other_queen)
52
+ next
53
+ end
54
+ ox, oy = other_queen
55
+ if y == oy or x == ox or ((y - oy).abs == (x - ox).abs)
56
+ rv = false
57
+ break
58
+ end
59
+ end
60
+ rv
61
+ end
62
+ end
63
+
64
+
65
+ if __FILE__ == $0
66
+ b = QueensBoard.new
67
+
68
+ puts "First solution:"
69
+ p b.next_constrained_value!.board.collect {|x, y| y}
70
+
71
+ puts "All Solutions:"
72
+ for board in b.collect_constrained_values_until {|e| b.curr_board_n >= MAX_BOARD_N - 1}
73
+ b = board.board
74
+ # b.sort do |q1, q2|
75
+ # x1, y1 = q1
76
+ # x2, y2 = q2
77
+ # x1 <=> x2
78
+ # end
79
+ p b.collect {|x, y| y}
80
+ end
81
+ end
82
+
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+ # queens.rb
3
+ # @Author: Thomas Link (samul@web.de)
4
+ # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
5
+ # @Created: 11-Jun-2005.
6
+ # @Last Change: 12-Jun-2005.
7
+ # @Revision: 0.223
8
+
9
+ require 'constraint'
10
+
11
+ QUEEN_MIN = 1
12
+ QUEEN_MAX = 5
13
+
14
+ class QueensBoard < Constraint::Shell
15
+ def new_constrained
16
+ @boards = []
17
+ @masks = []
18
+ @max_board = (QUEEN_MAX ** QUEEN_MAX) - 1
19
+ nil
20
+ end
21
+
22
+ def process_constrained_value
23
+ if @constrained_value
24
+ super
25
+ end
26
+ end
27
+
28
+ def generate_constrained_value(value)
29
+ if !value
30
+ return 0
31
+ elsif value < @max_board
32
+ return value + 1
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def board(pos=@constrained_value)
39
+ unless (b = @boards[pos])
40
+ b = @boards[pos] = []
41
+ QUEEN_MAX.downto(1) do |x|
42
+ xx, pos = pos.divmod(QUEEN_MAX ** (x - 1))
43
+ b << xx
44
+ end
45
+ end
46
+ return b
47
+ end
48
+
49
+ def strike_any?(x, y, board)
50
+ board.each_with_index do |oy, ox|
51
+ if x == ox
52
+ next
53
+ elsif y == oy or ((y - oy).abs == (x - ox).abs)
54
+ return true
55
+ end
56
+ end
57
+ return false
58
+ end
59
+
60
+ or_constraint('Good') do |qb, board|
61
+ b = qb.board(board)
62
+ rv = true
63
+ b.each_with_index do |y, i|
64
+ if qb.strike_any?(i, y, b)
65
+ rv = false
66
+ break
67
+ end
68
+ end
69
+ rv
70
+ end
71
+ end
72
+
73
+
74
+ if __FILE__ == $0
75
+ b = QueensBoard.new
76
+
77
+ puts "First solution:"
78
+ p b.next_constrained_value!.board.collect {|e| e}
79
+
80
+ puts "All Solutions:"
81
+ for board in b.collect_constrained_values_until {|e| e >= (QUEEN_MAX ** QUEEN_MAX) - 2}
82
+ p board.board.collect {|e| e}
83
+ end
84
+ end
85
+
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ for i in queens*; do
3
+ echo ------$i
4
+ time ruby $i
5
+ done
@@ -2,110 +2,114 @@
2
2
  # @Author: Thomas Link (samul AT web.de)
3
3
  # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
4
4
  # @Created: 23-Mai-2005.
5
- # @Last Change: 09-Jun-2005.
6
- # @Revision: 607
5
+ # @Last Change: 14-Jun-2005.
6
+ # @Revision: 986
7
7
 
8
-
9
- class ConstraintViolation < Exception
10
- end
8
+ require 'forwardable'
11
9
 
12
10
 
13
- # If CONSTRAINT_DISABLE is set to true *before* loading this library,
11
+ # If CONSTRAINT_DISABLE is set to true _before_ loading this library,
14
12
  # constraint checking is turned off.
15
13
  CONSTRAINT_DISABLE = false unless defined?(CONSTRAINT_DISABLE)
16
14
 
17
15
 
18
16
  module Constraint
19
- VERSION = '0.1'
17
+ VERSION = '0.2'
18
+
19
+
20
+ class Violation < Exception
21
+ end
20
22
 
21
- # The base constraint class. Always evaluates to true.
22
- class NilConstraint
23
+ class NoMoreValues < Violation
24
+ end
25
+
26
+ # The base constraint class.
27
+ class SingleConstraint
23
28
  # shell:: The Shell class containing this constraint
24
29
  # name:: This constraint's name
25
30
  # description:: Optional constraint description
26
31
  # block:: The constraint that must evaluate to true in order for this constraint to succeed
27
32
  def initialize(shell, name, description=nil, &block)
28
- @shell = shell
29
- @other = shell.constraints
33
+ # @other = shell.constraints
30
34
  @name = name
31
- @description = description
32
35
  @this = block
33
36
  end
34
37
 
35
- # Return true if +args+ complies with @this constraint.
36
- def evaluate(object)
37
- begin
38
- rv = @this.call(object)
39
- rescue Exception => e
40
- @shell.log_constraint_exception(e)
41
- end
42
- begin
43
- if @other
44
- return evaluate_control(rv, object)
45
- elsif rv
46
- return object
47
- else
48
- return evaluate(handle_violation(object))
49
- end
50
- rescue ConstraintViolation => e
51
- raise e
38
+ def evaluate(invoker, object)
39
+ if @this.call(invoker, object)
40
+ object
41
+ else
42
+ invoker.handle_constraint_violation(@name, object)
52
43
  end
53
44
  end
54
-
55
- # Delegate to Shell#handle_constraint_violation.
56
- def handle_violation(object)
57
- return @shell.handle_constraint_violation(@name, object)
58
- end
59
-
60
- private
61
- # Combine constraints. In the base class, always evaluate to
62
- # true.
63
- def evaluate_control(value, object)
64
- raise "Subclass responsibility"
65
- # value ? object : handle_violation(object)
66
- end
67
45
  end
68
-
69
- class OrConstraint < NilConstraint
70
- private
71
- # Return true if +value+ or the @other constraint are true.
72
- def evaluate_control(value, object)
73
- value ? object : @other.evaluate(object)
46
+
47
+ # All constraints must succeed.
48
+ class AndConstraint < Array
49
+ def evaluate(invoker, object)
50
+ for a in self
51
+ object = a.evaluate(invoker, object)
52
+ end
53
+ object
74
54
  end
75
55
  end
76
-
77
- class AndConstraint < NilConstraint
78
- private
79
- # Return true if +value+ and the @other constraint are true.
80
- def evaluate_control(value, object)
81
- return value ? @other.evaluate(object) : handle_violation(object)
56
+
57
+ # At least one constraint must succeed.
58
+ class OrConstraint < Array
59
+ def evaluate(invoker, object)
60
+ c = nil
61
+ for o in self
62
+ begin
63
+ return o.evaluate(invoker, object)
64
+ rescue Constraint::Violation => e
65
+ c = e
66
+ end
67
+ end
68
+ raise c
82
69
  end
83
70
  end
84
-
71
+
85
72
  module Helper
86
73
  # The current class's/object's head constraint.
87
74
  attr_reader :constraints
88
- # A hash of constraint descriptions.
89
- attr_reader :descriptions
75
+ # A hash of constraint constaint_descriptions.
76
+ attr_reader :constaint_descriptions
77
+ # An array of constraint handlers
78
+ attr_reader :constaint_handlers
79
+ # An array of additional attributes that should be copied when
80
+ # replicating a shell
81
+ attr_reader :constraint_attributes
90
82
 
91
- # Define an OrConstraint. If the new constraint succeedes, all
92
- # previous constraints become obsolete.
83
+ # Define an OrConstraint. Either this or the previous constraint have to succeed.
93
84
  # name:: This constraint's name
94
85
  # description:: Optional constraint description
95
86
  # block:: The constraint that must evaluate to true in order for this constraint to succeed
96
87
  def or_constraint(name, desc=nil, &block)
97
88
  describe_constraint(name, desc)
98
- @constraints = OrConstraint.new(self, name, desc, &block)
89
+ @constraints = @constraints.dup
90
+ constraint = SingleConstraint.new(self, name, desc, &block)
91
+ if @constraints.empty?
92
+ @constraints << constraint
93
+ else
94
+ last = @constraints.last
95
+ case last
96
+ when OrConstraint
97
+ last << constraint
98
+ else
99
+ @constraints[-1] = OrConstraint.new([last, constraint])
100
+ end
101
+ end
99
102
  end
100
103
 
101
- # Define an AndConstraint. This and all previous constraints
104
+ # Define an SingleConstraint. This and all previous constraints
102
105
  # have to succeed.
103
106
  # name:: This constraint's name
104
107
  # description:: Optional constraint description
105
108
  # block:: The constraint that must evaluate to true in order for this constraint to succeed
106
109
  def and_constraint(name, desc=nil, &block)
107
110
  describe_constraint(name, desc)
108
- @constraints = AndConstraint.new(self, name, desc, &block)
111
+ @constraints = @constraints.dup
112
+ @constraints << SingleConstraint.new(self, name, desc, &block)
109
113
  end
110
114
 
111
115
  # If $VERBOSE is set, print the exception to $stderr.
@@ -115,50 +119,94 @@ module Constraint
115
119
  end
116
120
  end
117
121
 
118
- # Handle constraint violations. Can be overwritten by subclasses
119
- # in order to rescue the constraint. The return value will
120
- # replace the original object.
121
- # name:: The constraint's name that didn't succeed
122
- # object:: The object that caused the violation
123
- def handle_constraint_violation(name, object)
124
- c = @descriptions.collect {|name, desc| desc}.join(', ')
125
- raise ConstraintViolation,
126
- "#{self.class}: #{object.class} didn't meet constraints (#{c}): #{object.inspect}",
127
- caller[5..-1]
122
+ # Define a new constraint handler. The block takes a
123
+ # continuation as its first argument. If the amendment of the
124
+ # object isn't successful, the handler must throw an
125
+ # Constraint::Violation exception.
126
+ #
127
+ # Example:
128
+ #
129
+ # enum = EvenInteger.new(2)
130
+ # enum.and_constraint('LT10') {|enum, n| n < 10}
131
+ # enum.on_constraint_violation('LT10') do |o, n|
132
+ # n > 10 ? 10 : 0
133
+ # end
134
+ #
135
+ # *names:: The constraints names
136
+ # block:: The handler (argument: object)
137
+ def on_constraint_violation(*names, &block)
138
+ @constaint_handlers = @constaint_handlers.dup
139
+ names.each do |name|
140
+ @constaint_handlers[name] ||= []
141
+ @constaint_handlers[name] << block
142
+ end
128
143
  end
129
144
 
145
+ def constraint_attr(name, val)
146
+ (@constraint_attributes = @constraint_attributes.dup) << name
147
+ instance_eval %{
148
+ class << self
149
+ attr_accessor :#{name}
150
+ end
151
+ attr_accessor :#{name}
152
+ }
153
+ instance_variable_set("@#{name}", val)
154
+ end
155
+
156
+ # Copy needed class/object variables.
157
+ def replicate_constraints(predecessor)
158
+ @constaint_descriptions = predecessor.constaint_descriptions
159
+ @constaint_handlers = predecessor.constaint_handlers
160
+ @constraints = predecessor.constraints
161
+ for name in (@constraint_attributes = predecessor.constraint_attributes)
162
+ nname = "@#{name}"
163
+ instance_variable_set(nname, predecessor.instance_variable_get(nname))
164
+ end
165
+ end
166
+
130
167
  private
131
168
  # Copy constraint-related class-local variables from +klass+ to self.
132
- def inherit_constraint(klass)
133
- @descriptions = klass.descriptions.dup
134
- @constraints = klass.constraints
135
- end
169
+ # def inherit_constraint(klass)
170
+ # @constaint_descriptions = klass.constaint_descriptions
171
+ # @constaint_handlers = klass.constaint_handlers
172
+ # @constraints = klass.constraints
173
+ # end
136
174
 
137
175
  # Register a constraint description. If no description is
138
176
  # provided, the +name+ is used.
139
177
  # name:: This constraint's name
140
178
  # desc:: Optional constraint description
141
179
  def describe_constraint(name, desc=nil)
142
- @descriptions[name] = (desc || name)
180
+ @constaint_descriptions[name] = (desc || name)
143
181
  end
144
182
  end
145
183
 
146
184
  # If CONSTRAINT_DISABLE is set to true *before* loading this library,
147
185
  # constraint checking is turned off.
148
186
  class Shell
187
+ extend Forwardable
188
+
149
189
  # The actual value that is covered by the Shell.
150
190
  attr_reader :constrained_value
151
191
 
152
- @descriptions = {}
153
- @constraints = nil
192
+ @constaint_descriptions = {}
193
+ @constaint_handlers = {}
194
+ # @constraints = nil
195
+ @constraints = AndConstraint.new
196
+ @constraint_attributes = []
154
197
 
155
198
  class << self
156
199
  include Helper
157
-
200
+
201
+ def delegate_to_constrained_value(meth, alias_name)
202
+ class_eval %{alias #{alias_name} #{meth}}
203
+ def_delegator(:@constrained_value, meth)
204
+ end
205
+
158
206
  def inherited(subclass)
159
207
  parent = self
160
208
  subclass.class_eval do
161
- inherit_constraint(parent)
209
+ replicate_constraints(parent)
162
210
  end
163
211
  end
164
212
 
@@ -174,12 +222,12 @@ module Constraint
174
222
  # Wrap +methods+ so that some arguments are filtered through
175
223
  # #check_constraints. This is useful in two situations:
176
224
  #
177
- # * if filtering the return value would be inefficient --
225
+ # * if filtering the return value would be inefficient --
178
226
  # e.g. with methods performing trivial Array operations
179
227
  # where the return value will comply with the constraints
180
228
  # if its argument do.
181
229
  #
182
- # * if the return value of the method call is not an
230
+ # * if the return value of the method call is not an
183
231
  # instance of the actual value's class (only then will the
184
232
  # value's integrity be checked)
185
233
  #
@@ -187,7 +235,6 @@ module Constraint
187
235
  #
188
236
  # methods:: Method names as symbols
189
237
  # block:: Select the arguments that should be filtered
190
-
191
238
  def with_arguments_constrained(*methods, &block)
192
239
  unless CONSTRAINT_DISABLE
193
240
  methods.each do |method|
@@ -215,24 +262,29 @@ module Constraint
215
262
 
216
263
  include Helper
217
264
 
218
- def initialize(core=nil, predecessor=nil)
219
- if predecessor
220
- @descriptions = predecessor.descriptions
221
- @constraints = predecessor.constraints
222
- else
223
- @descriptions = self.class.descriptions.dup
224
- @constraints = self.class.constraints
225
- @constraints = @constraints.dup if @constraints
226
- end
227
-
228
- if core
229
- @constrained_value = core
230
- else
231
- @constrained_value = new_constrained
232
- end
265
+ delegate_to_constrained_value(:inspect, :inspect_constraint_shell)
266
+ delegate_to_constrained_value(:=~, :match_constraint_shell)
267
+ delegate_to_constrained_value(:instance_eval, :constraint_shell_eval)
233
268
 
269
+ # delegate_to_constrained_value(:tainted?, :tainted_constraint_shell?)
270
+ # delegate_to_constrained_value(:taint, :taint_constraint_shell)
271
+ # delegate_to_constrained_value(:untaint, :untaint_constraint_shell)
272
+
273
+ delegate_to_constrained_value(:===, :case_equal_constraint_shell)
274
+ # def ===(other)
275
+ # if other.kind_of?(Shell)
276
+ # super
277
+ # else
278
+ # @constrained_value === other
279
+ # end
280
+ # end
281
+
282
+ def initialize(core=nil, predecessor=nil, &block)
283
+ predecessor ||= self.class
284
+ replicate_constraints(predecessor)
285
+ @constrained_value = core || new_constrained
234
286
  unless CONSTRAINT_DISABLE
235
- process_constrained_value {|e| check_constraints(e)}
287
+ check_constrained_value
236
288
  end
237
289
  end
238
290
 
@@ -244,12 +296,86 @@ module Constraint
244
296
  replicate_constraint_shell(super)
245
297
  end
246
298
 
247
- # Construct a new instance.
299
+ # Construct a new instance. By default, this method calls
300
+ # generate_constrained_value(nil).
248
301
  def new_constrained
249
- # nil
250
- raise 'Subclass responsibility'
302
+ generate_constrained_value(nil)
303
+ # raise 'Subclass responsibility'
304
+ end
305
+
306
+ # Generate the next value for #next_constrained_value.
307
+ def generate_constrained_value(value)
308
+ raise Constraint::NoMoreValues, "No more values: #{value}"
309
+ end
310
+
311
+ # "Increase" the current value to the next valid one (see
312
+ # #generate_constrained_value). Modify the object in-place.
313
+ def next_constrained_value!(value=nil)
314
+ begin
315
+ cv = @constrained_value
316
+ if value
317
+ @constrained_value = value
318
+ end
319
+ begin
320
+ while (@constrained_value = generate_constrained_value(@constrained_value))
321
+ check_constrained_value
322
+ return self
323
+ end
324
+ rescue Constraint::NoMoreValues => e
325
+ raise e
326
+ rescue Constraint::Violation => e
327
+ retry
328
+ end
329
+ raise_constraint_violation(nil, value, "No more values: #{value}")
330
+ rescue Exception => e
331
+ @constrained_value = cv
332
+ raise e
333
+ end
334
+ end
335
+
336
+ # Return an object with an increased value (see
337
+ # #generate_constrained_value).
338
+ def next_constrained_value(value=nil)
339
+ self.clone.next_constrained_value!(value)
340
+ end
341
+
342
+ # Return an array of constrained values starting from the
343
+ # current value until a value for which block is true. Requires
344
+ # #generate_constrained_value to be defined.
345
+ def collect_constrained_values_while(&block)
346
+ collect_constrained_values_control do |val, acc|
347
+ if (rv = block.call(val))
348
+ acc << self.dup
349
+ next_constrained_value!
350
+ end
351
+ rv
352
+ end
353
+ end
354
+
355
+ # Return an array of constrained values starting from the
356
+ # current value until a value for which block is true. Requires
357
+ # #generate_constrained_value to be defined.
358
+ def collect_constrained_values_until(&block)
359
+ collect_constrained_values_control do |val, acc|
360
+ if (rv = !block.call(val))
361
+ acc << self.dup
362
+ next_constrained_value!
363
+ end
364
+ rv
365
+ end
251
366
  end
252
367
 
368
+ # Collect all values. Shell#generate_constrained_value must
369
+ # provide a means to decide when no more values should be
370
+ # generated.
371
+ def collect_constrained_values
372
+ collect_constrained_values_control do |val, acc|
373
+ acc << self.dup
374
+ next_constrained_value!
375
+ true
376
+ end
377
+ end
378
+
253
379
  # Filters @constrained_value through the supplied block (usually a
254
380
  # call to #check_constraints) and collects the output in
255
381
  # @constrained_value).
@@ -258,6 +384,10 @@ module Constraint
258
384
  @constrained_value = yield @constrained_value
259
385
  end
260
386
 
387
+ def check_constrained_value
388
+ process_constrained_value {|e| check_constraints(e)}
389
+ end
390
+
261
391
  if CONSTRAINT_DISABLE
262
392
  def check_constraints(object)
263
393
  object
@@ -266,7 +396,7 @@ module Constraint
266
396
  # Check if +object+ complies with @constraints.
267
397
  def check_constraints(object)
268
398
  if @constraints
269
- return @constraints.evaluate(object)
399
+ return @constraints.evaluate(self, object)
270
400
  else
271
401
  return object
272
402
  end
@@ -288,7 +418,7 @@ module Constraint
288
418
  # Shell.with_arguments_constrained and friends.
289
419
  #
290
420
  # new_value:: The result from the method call
291
- # [block]:: Optional, called @constrained_value is +new_value+
421
+ # block:: Optional, called @constrained_value is +new_value+
292
422
  def with_constraints(new_value)
293
423
  if new_value.equal?(@constrained_value)
294
424
  yield if block_given?
@@ -300,6 +430,25 @@ module Constraint
300
430
  end
301
431
  end
302
432
 
433
+ # Handle constraint violations. Can be overwritten by subclasses
434
+ # in order to rescue the constraint. The return value will
435
+ # replace the original object.
436
+ # name:: The constraint's name that didn't succeed
437
+ # object:: The object that caused the violation
438
+ def handle_constraint_violation(name, object)
439
+ handlers = @constaint_handlers[name]
440
+ if handlers
441
+ for h in handlers
442
+ begin
443
+ return h.call(self, object)
444
+ rescue Constraint::Violation
445
+ next
446
+ end
447
+ end
448
+ end
449
+ raise_constraint_violation(name, object)
450
+ end
451
+
303
452
  if CONSTRAINT_DISABLE
304
453
  def method_missing(method, *args, &block)
305
454
  with_constraints(@constrained_value.send(method, *args, &block))
@@ -313,7 +462,7 @@ module Constraint
313
462
  def method_missing(method, *args, &block)
314
463
  # $stderr.puts "Constraint::Shell: Delegate method: #{method}" if $DEBUG
315
464
  with_constraints(@constrained_value.send(method, *args, &block)) do
316
- process_constrained_value {|e| check_constraints(e)}
465
+ check_constrained_value
317
466
  end
318
467
  end
319
468
  end
@@ -324,12 +473,47 @@ module Constraint
324
473
  end
325
474
 
326
475
  private
327
- # Some kind of semi-deep copy.
328
- def replicate_constraint_shell(new_instance)
329
- cv = @constrained_value.clone
330
- new_instance.instance_eval do
476
+ # Eval block while it's yielding true. Maintain
477
+ # @constrained_value in case of an exception.
478
+ def collect_constrained_values_control(&block)
479
+ begin
480
+ acc = []
481
+ cv = @constrained_value
482
+ begin
483
+ while block.call(@constrained_value, acc)
484
+ end
485
+ rescue Constraint::Violation
486
+ end
487
+ return acc
488
+ ensure
331
489
  @constrained_value = cv
332
490
  end
491
+ end
492
+
493
+ # Raise a Constraint::Violation.
494
+ def raise_constraint_violation(name, object, msg=nil)
495
+ i = 0
496
+ caller.each_with_index do |e,l|
497
+ if e =~ /^(\w+:)?([^:].*?[\\\/])+?constraint.rb:\d+:/
498
+ i = l
499
+ end
500
+ end
501
+ msg ||= "#{object.class} didn't meet constraint '#{name}': #{object.inspect}"
502
+ raise Constraint::Violation, msg, caller[i+1..-1]
503
+ end
504
+
505
+ # Some kind of semi-deep copy. If @constrained_value can't be
506
+ # cloned (e.g., a Fixnum), the new_instance will be returned as
507
+ # such.
508
+ # new_instance:: A duplicate of Shell
509
+ def replicate_constraint_shell(new_instance)
510
+ begin
511
+ cv = @constrained_value.clone
512
+ new_instance.constraint_shell_eval do
513
+ @constrained_value = cv
514
+ end
515
+ rescue TypeError
516
+ end
333
517
  new_instance
334
518
  end
335
519
  end