must_be 1.0.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/.gitignore +2 -0
- data/README.md +348 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/doc/readme/examples.rb +262 -0
- data/doc/readme/run_examples.rb +47 -0
- data/lib/must_be/attr_typed.rb +78 -0
- data/lib/must_be/basic.rb +120 -0
- data/lib/must_be/containers.rb +291 -0
- data/lib/must_be/containers_registered_classes.rb +83 -0
- data/lib/must_be/core.rb +247 -0
- data/lib/must_be/nonstandard_control_flow.rb +159 -0
- data/lib/must_be/proxy.rb +62 -0
- data/lib/must_be.rb +9 -0
- data/must_be.gemspec +71 -0
- data/spec/must_be/attr_typed_spec.rb +225 -0
- data/spec/must_be/basic_spec.rb +578 -0
- data/spec/must_be/containers_spec.rb +952 -0
- data/spec/must_be/core_spec.rb +675 -0
- data/spec/must_be/nonstandard_control_flow_spec.rb +845 -0
- data/spec/must_be/proxy_spec.rb +194 -0
- data/spec/notify_matcher_spec.rb +59 -0
- data/spec/spec_helper.rb +180 -0
- data/spec/typical_usage_spec.rb +176 -0
- metadata +98 -0
| @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            class Module
         | 
| 2 | 
            +
              def attr_typed(symbol, *types, &test)
         | 
| 3 | 
            +
                raise TypeError, "#{symbol} is not a symbol" if symbol.is_a? Fixnum
         | 
| 4 | 
            +
                
         | 
| 5 | 
            +
                types.each do |type|
         | 
| 6 | 
            +
                  raise TypeError, "class or module required" unless type.is_a? Module
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
                
         | 
| 9 | 
            +
                attr_reader symbol
         | 
| 10 | 
            +
                name = symbol.to_sym.id2name
         | 
| 11 | 
            +
                check_method_name = "attr_typed__check_#{name}"
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                unless types.empty?
         | 
| 14 | 
            +
                  types_message = types.size == 1 ? types[0] :
         | 
| 15 | 
            +
                    types.size == 2 ? "#{types[0]} or #{types[1]}" :
         | 
| 16 | 
            +
                    "one of #{types.inspect}"
         | 
| 17 | 
            +
                        
         | 
| 18 | 
            +
                  type_check = lambda do |value|
         | 
| 19 | 
            +
                    if types.none?{|type| value.is_a? type }
         | 
| 20 | 
            +
                      must_notify("attribute `#{name}' must be a #{types_message},"\
         | 
| 21 | 
            +
                        " but value #{value.inspect} is a #{value.class}")
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                if test
         | 
| 27 | 
            +
                  test_check = lambda do |value|
         | 
| 28 | 
            +
                    unless test[value]
         | 
| 29 | 
            +
                      must_notify("attribute `#{name}' cannot be #{value.inspect}")
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                define_method(check_method_name, &(
         | 
| 35 | 
            +
                  if types.empty?
         | 
| 36 | 
            +
                    if test
         | 
| 37 | 
            +
                      test_check
         | 
| 38 | 
            +
                    else
         | 
| 39 | 
            +
                      lambda do |value|
         | 
| 40 | 
            +
                        if value.nil?
         | 
| 41 | 
            +
                          must_notify("attribute `#{name}' cannot be nil")
         | 
| 42 | 
            +
                        end
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  else
         | 
| 46 | 
            +
                    if test
         | 
| 47 | 
            +
                      lambda do |value|
         | 
| 48 | 
            +
                        type_check[value]
         | 
| 49 | 
            +
                        test_check[value]
         | 
| 50 | 
            +
                      end
         | 
| 51 | 
            +
                    else
         | 
| 52 | 
            +
                      type_check
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                ))
         | 
| 56 | 
            +
                
         | 
| 57 | 
            +
                module_eval %Q{
         | 
| 58 | 
            +
                  def #{name}=(value)
         | 
| 59 | 
            +
                    #{check_method_name}(value)
         | 
| 60 | 
            +
                    @#{name} = value
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                }
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
              
         | 
| 65 | 
            +
              MustBe.register_disabled_handler do |enabled|
         | 
| 66 | 
            +
                if enabled
         | 
| 67 | 
            +
                  if method(:attr_typed__original)
         | 
| 68 | 
            +
                    alias attr_typed attr_typed__original
         | 
| 69 | 
            +
                    remove_method(:attr_typed__original)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                else
         | 
| 72 | 
            +
                  alias attr_typed__original attr_typed
         | 
| 73 | 
            +
                  define_method(:attr_typed) do |symbol, *types|
         | 
| 74 | 
            +
                    attr_accessor symbol
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
            end
         | 
| @@ -0,0 +1,120 @@ | |
| 1 | 
            +
            module MustBe
         | 
| 2 | 
            +
              def self.match_any_case?(v, cases)
         | 
| 3 | 
            +
                cases = [cases] unless cases.is_a? Array
         | 
| 4 | 
            +
                cases.any? {|c| c === v }
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
              def must_be(*cases)
         | 
| 8 | 
            +
                unless cases.empty? ? self : MustBe.match_any_case?(self, cases)
         | 
| 9 | 
            +
                  must_notify(self, __method__, cases, nil, ", but matches #{self.class}")
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
                self
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def must_not_be(*cases)
         | 
| 15 | 
            +
                if cases.empty? ? self : MustBe.match_any_case?(self, cases)
         | 
| 16 | 
            +
                  must_notify(self, __method__, cases, nil, ", but matches #{self.class}")
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                self
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              
         | 
| 21 | 
            +
            private
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def must_be_a__body(modules, test_method, method)
         | 
| 24 | 
            +
                if modules.size.zero?
         | 
| 25 | 
            +
                  raise ArgumentError, "wrong number of arguments (0 for 1)"
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
                ms = modules.last.is_a?(Module) ? modules : modules[0..-2]
         | 
| 28 | 
            +
                if ms.size.zero?
         | 
| 29 | 
            +
                  raise TypeError, "class or module required"
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
                ms.each do |mod|
         | 
| 32 | 
            +
                  raise TypeError, "class or module required" unless mod.is_a? Module
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
                    
         | 
| 35 | 
            +
                if ms.send(test_method) {|mod| is_a? mod }
         | 
| 36 | 
            +
                  must_notify(self, method, modules, nil, ", but is a #{self.class}")
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
                self
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            public
         | 
| 42 | 
            +
              
         | 
| 43 | 
            +
              def must_be_a(*modules)
         | 
| 44 | 
            +
                must_be_a__body(modules, :none?, __method__)
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
              
         | 
| 47 | 
            +
              def must_not_be_a(*modules)
         | 
| 48 | 
            +
                must_be_a__body(modules, :any?, __method__)
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
              
         | 
| 51 | 
            +
              def must_be_in(*collection)
         | 
| 52 | 
            +
                cs = collection.size == 1 ? collection[0] : collection
         | 
| 53 | 
            +
                unless cs.include? self
         | 
| 54 | 
            +
                  must_notify(self, __method__, collection)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
                self
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
              
         | 
| 59 | 
            +
              def must_not_be_in(*collection)
         | 
| 60 | 
            +
                cs = collection.size == 1 ? collection[0] : collection
         | 
| 61 | 
            +
                if cs.include? self
         | 
| 62 | 
            +
                  must_notify(self, __method__, collection)
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
                self
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
              
         | 
| 67 | 
            +
              def must_be_nil
         | 
| 68 | 
            +
                must_notify(self, __method__) unless nil?
         | 
| 69 | 
            +
                self
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
              
         | 
| 72 | 
            +
              def must_not_be_nil
         | 
| 73 | 
            +
                must_notify(self, __method__) if nil?
         | 
| 74 | 
            +
                self
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
              
         | 
| 77 | 
            +
              def must_be_true
         | 
| 78 | 
            +
                must_notify(self, __method__) unless self == true
         | 
| 79 | 
            +
                self
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
              
         | 
| 82 | 
            +
              def must_be_false
         | 
| 83 | 
            +
                must_notify(self, __method__) unless self == false
         | 
| 84 | 
            +
                self
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
              
         | 
| 87 | 
            +
              def must_be_boolean
         | 
| 88 | 
            +
                unless self == true or self == false
         | 
| 89 | 
            +
                  must_notify(self, __method__)
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
                self
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
              
         | 
| 94 | 
            +
              def must_be_close(expected, delta = 0.1)
         | 
| 95 | 
            +
                difference = (self - expected).abs
         | 
| 96 | 
            +
                unless difference < delta
         | 
| 97 | 
            +
                  must_notify(self, __method__, [expected, delta], nil,
         | 
| 98 | 
            +
                    ", difference is #{difference}")
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
                self
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
              
         | 
| 103 | 
            +
              def must_not_be_close(expected, delta = 0.1)
         | 
| 104 | 
            +
                if (self - expected).abs < delta
         | 
| 105 | 
            +
                  must_notify(self, __method__, [expected, delta])
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
                self
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
            end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            ### Proc Case Equality Patch ###
         | 
| 112 | 
            +
            #
         | 
| 113 | 
            +
            # Semantics of case equality `===' for Proc changed between Ruby 1.8 (useless)
         | 
| 114 | 
            +
            # and Ruby 1.9 (awesome).  So let's fix 'er up.
         | 
| 115 | 
            +
            #
         | 
| 116 | 
            +
            if RUBY_VERSION < "1.9"
         | 
| 117 | 
            +
              class Proc
         | 
| 118 | 
            +
                alias === call
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
            end
         | 
| @@ -0,0 +1,291 @@ | |
| 1 | 
            +
            require 'forwardable'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module MustBe
         | 
| 4 | 
            +
              class ContainerNote < Note
         | 
| 5 | 
            +
                extend Forwardable
         | 
| 6 | 
            +
                
         | 
| 7 | 
            +
                attr_accessor :original_note, :container
         | 
| 8 | 
            +
                
         | 
| 9 | 
            +
                def_delegators :@original_note,
         | 
| 10 | 
            +
                  :receiver, :receiver=,
         | 
| 11 | 
            +
                  :assertion, :assertion=,
         | 
| 12 | 
            +
                  :args, :args=,
         | 
| 13 | 
            +
                  :block, :block=,
         | 
| 14 | 
            +
                  :additional_message, :additional_message=,
         | 
| 15 | 
            +
                  :prefix, :prefix=
         | 
| 16 | 
            +
                
         | 
| 17 | 
            +
                def initialize(original_note, container = nil)
         | 
| 18 | 
            +
                  @original_note = original_note
         | 
| 19 | 
            +
                  @container = container
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
                def to_s
         | 
| 23 | 
            +
                  if assertion
         | 
| 24 | 
            +
                    @original_note.to_s+" in container #{MustBe.short_inspect(container)}"
         | 
| 25 | 
            +
                  else
         | 
| 26 | 
            +
                    @original_note.to_s
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                
         | 
| 30 | 
            +
                alias regular_backtrace backtrace
         | 
| 31 | 
            +
                
         | 
| 32 | 
            +
                def backtrace
         | 
| 33 | 
            +
                  return unless regular_backtrace
         | 
| 34 | 
            +
                  
         | 
| 35 | 
            +
                  if container.respond_to?(:must_only_ever_contain_backtrace) and
         | 
| 36 | 
            +
                      container.must_only_ever_contain_backtrace
         | 
| 37 | 
            +
                    regular_backtrace+["=== caused by container ==="]+
         | 
| 38 | 
            +
                      container.must_only_ever_contain_backtrace
         | 
| 39 | 
            +
                  else
         | 
| 40 | 
            +
                    regular_backtrace
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
              
         | 
| 45 | 
            +
              class PairNote < ContainerNote
         | 
| 46 | 
            +
                attr_accessor :key, :value, :cases, :negate
         | 
| 47 | 
            +
                
         | 
| 48 | 
            +
                def initialize(key, value, cases, container, negate)
         | 
| 49 | 
            +
                  super(Note.new(""), container)
         | 
| 50 | 
            +
                  @key = key
         | 
| 51 | 
            +
                  @value = value
         | 
| 52 | 
            +
                  @cases = cases
         | 
| 53 | 
            +
                  @negate = negate
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                def to_s
         | 
| 57 | 
            +
                  match = negate ? "matches" : "does not match"
         | 
| 58 | 
            +
                  "#{prefix}pair {#{MustBe.short_inspect(key)}=>"\
         | 
| 59 | 
            +
                    "#{MustBe.short_inspect(value)}} #{match}"\
         | 
| 60 | 
            +
                    " #{MustBe.short_inspect(cases)} in"\
         | 
| 61 | 
            +
                    " container #{MustBe.short_inspect(container)}"
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              def self.check_pair_against_hash_cases(key, value, cases, negate = false)
         | 
| 66 | 
            +
                if negate
         | 
| 67 | 
            +
                  if cases.empty?
         | 
| 68 | 
            +
                    !key and !value
         | 
| 69 | 
            +
                  else
         | 
| 70 | 
            +
                    cases.all? do |c|
         | 
| 71 | 
            +
                      c.all? do |k, v|
         | 
| 72 | 
            +
                        not (match_any_case?(key, k) and match_any_case?(value, v))
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                else
         | 
| 77 | 
            +
                  if cases.empty?
         | 
| 78 | 
            +
                    key and value
         | 
| 79 | 
            +
                  else
         | 
| 80 | 
            +
                    cases.any? do |c|
         | 
| 81 | 
            +
                      c.any? do |k, v|
         | 
| 82 | 
            +
                        match_any_case?(key, k) and match_any_case?(value, v)
         | 
| 83 | 
            +
                      end
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
              
         | 
| 89 | 
            +
              def self.must_check_member_against_cases(container, member, cases,
         | 
| 90 | 
            +
                  negate = false)
         | 
| 91 | 
            +
                member.must_check(lambda do
         | 
| 92 | 
            +
                  if negate
         | 
| 93 | 
            +
                    member.must_not_be(*cases)
         | 
| 94 | 
            +
                  else
         | 
| 95 | 
            +
                    member.must_be(*cases)
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                end) do |note|
         | 
| 98 | 
            +
                  note = ContainerNote.new(note, container)
         | 
| 99 | 
            +
                  block_given? ? yield(note) : note
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              def self.must_check_pair_against_hash_cases(container, key, value, cases,
         | 
| 104 | 
            +
                  negate = false)
         | 
| 105 | 
            +
                unless MustBe.check_pair_against_hash_cases(key, value, cases, negate)
         | 
| 106 | 
            +
                  note = PairNote.new(key, value, cases, container, negate)
         | 
| 107 | 
            +
                  must_notify(block_given? ? yield(note) : note)
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
              end
         | 
| 110 | 
            +
              
         | 
| 111 | 
            +
              def self.must_only_contain(container, cases, negate = false)
         | 
| 112 | 
            +
                prefix = negate ? "must_not_contain: " : "must_only_contain: "
         | 
| 113 | 
            +
                
         | 
| 114 | 
            +
                advice = MustOnlyEverContain.registered_class(container)    
         | 
| 115 | 
            +
                if advice and advice.respond_to? :must_only_contain_check
         | 
| 116 | 
            +
                  advice.must_only_contain_check(container, cases, negate)
         | 
| 117 | 
            +
                elsif container.respond_to? :each_pair
         | 
| 118 | 
            +
                  container.each_pair do |key, value|
         | 
| 119 | 
            +
                    MustBe.must_check_pair_against_hash_cases(container, key, value,
         | 
| 120 | 
            +
                        cases, negate) do |note|
         | 
| 121 | 
            +
                      note.prefix = prefix
         | 
| 122 | 
            +
                      note
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
                else
         | 
| 126 | 
            +
                  container.each do |member|
         | 
| 127 | 
            +
                    MustBe.must_check_member_against_cases(container, member, cases,
         | 
| 128 | 
            +
                        negate) do |note|
         | 
| 129 | 
            +
                      note.prefix = prefix
         | 
| 130 | 
            +
                      note
         | 
| 131 | 
            +
                    end
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
                container
         | 
| 135 | 
            +
              end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
              def must_only_contain(*cases)
         | 
| 138 | 
            +
                MustBe.must_only_contain(self, cases)
         | 
| 139 | 
            +
              end
         | 
| 140 | 
            +
              
         | 
| 141 | 
            +
              def must_not_contain(*cases)
         | 
| 142 | 
            +
                MustBe.must_only_contain(self, cases, true)
         | 
| 143 | 
            +
              end
         | 
| 144 | 
            +
              
         | 
| 145 | 
            +
              module MustOnlyEverContain
         | 
| 146 | 
            +
                REGISTERED_CLASSES = {}
         | 
| 147 | 
            +
                
         | 
| 148 | 
            +
                module Base
         | 
| 149 | 
            +
                  attr_accessor :must_only_ever_contain_cases, 
         | 
| 150 | 
            +
                    :must_only_ever_contain_backtrace, :must_only_ever_contain_negate
         | 
| 151 | 
            +
                  
         | 
| 152 | 
            +
                  module ClassMethods
         | 
| 153 | 
            +
                    def must_check_contents_after(*methods)
         | 
| 154 | 
            +
                      methods.each do |method|
         | 
| 155 | 
            +
                        define_method(method) do |*args, &block|
         | 
| 156 | 
            +
                          begin
         | 
| 157 | 
            +
                            super(*args, &block)
         | 
| 158 | 
            +
                          ensure
         | 
| 159 | 
            +
                            must_check_contents
         | 
| 160 | 
            +
                          end
         | 
| 161 | 
            +
                        end
         | 
| 162 | 
            +
                      end
         | 
| 163 | 
            +
                    end
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
                  
         | 
| 166 | 
            +
                  def self.included(base)
         | 
| 167 | 
            +
                    base.extend(ClassMethods)
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
                  
         | 
| 170 | 
            +
                  def must_only_ever_contain_prefix
         | 
| 171 | 
            +
                    must_only_ever_contain_negate ? "must_never_ever_contain: " : 
         | 
| 172 | 
            +
                      "must_only_ever_contain: "
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  def must_only_ever_contain_cases=(cases)
         | 
| 176 | 
            +
                    cases = [cases] unless cases.is_a? Array
         | 
| 177 | 
            +
                    @must_only_ever_contain_cases = cases
         | 
| 178 | 
            +
                    
         | 
| 179 | 
            +
                    must_check(lambda { must_check_contents }) do |note|
         | 
| 180 | 
            +
                      note.prefix = must_only_ever_contain_prefix
         | 
| 181 | 
            +
                      note
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
                  
         | 
| 185 | 
            +
                protected
         | 
| 186 | 
            +
                
         | 
| 187 | 
            +
                  def must_check_member(member)
         | 
| 188 | 
            +
                    MustBe.must_check_member_against_cases(self, member, 
         | 
| 189 | 
            +
                      must_only_ever_contain_cases, must_only_ever_contain_negate)
         | 
| 190 | 
            +
                  end
         | 
| 191 | 
            +
                  
         | 
| 192 | 
            +
                  def must_check_pair(key, value)
         | 
| 193 | 
            +
                    MustBe.must_check_pair_against_hash_cases(self, key, value, 
         | 
| 194 | 
            +
                      must_only_ever_contain_cases, must_only_ever_contain_negate)
         | 
| 195 | 
            +
                  end
         | 
| 196 | 
            +
                  
         | 
| 197 | 
            +
                  def must_check_contents(members = self)
         | 
| 198 | 
            +
                    MustBe.must_only_contain(members, must_only_ever_contain_cases,
         | 
| 199 | 
            +
                      must_only_ever_contain_negate)
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
                end
         | 
| 202 | 
            +
                
         | 
| 203 | 
            +
              public
         | 
| 204 | 
            +
                
         | 
| 205 | 
            +
                ##
         | 
| 206 | 
            +
                # Creates a module from `body' which includes MustOnlyEverContain::Base.
         | 
| 207 | 
            +
                # The module will be mixed into an objects of type `klass' when
         | 
| 208 | 
            +
                # `must_only_ever_contain' is called.  The module should override methods 
         | 
| 209 | 
            +
                # of`klass' which modify the contents of the object.
         | 
| 210 | 
            +
                #
         | 
| 211 | 
            +
                # If the module has a class method
         | 
| 212 | 
            +
                # `must_only_contain_check(object, cases, negate = false)',
         | 
| 213 | 
            +
                # then this method is used by `MustBe.must_only_contain'
         | 
| 214 | 
            +
                # to check the contents of `object' against `cases'.
         | 
| 215 | 
            +
                # `must_only_contain_check' should call `MustBe#must_notify' for any
         | 
| 216 | 
            +
                # contents which do not match `cases'.  (Or if `negate' is true, then
         | 
| 217 | 
            +
                # `MustBe#must_notify' should be called for any contents that do match
         | 
| 218 | 
            +
                # `cases'.)
         | 
| 219 | 
            +
                #
         | 
| 220 | 
            +
                def self.register(klass, &body)
         | 
| 221 | 
            +
                  unless klass.is_a? Class
         | 
| 222 | 
            +
                    raise ArgumentError, "invalid value for Class: #{klass.inspect}"
         | 
| 223 | 
            +
                  end
         | 
| 224 | 
            +
                  if REGISTERED_CLASSES[klass]
         | 
| 225 | 
            +
                    raise ArgumentError, "handler for #{klass} previously provided"
         | 
| 226 | 
            +
                  end
         | 
| 227 | 
            +
                  
         | 
| 228 | 
            +
                  REGISTERED_CLASSES[klass] = mod = Module.new
         | 
| 229 | 
            +
                  mod.send(:include, Base)
         | 
| 230 | 
            +
                  mod.class_eval &body
         | 
| 231 | 
            +
                  
         | 
| 232 | 
            +
                  mutator_advice = Module.new
         | 
| 233 | 
            +
                  mod.instance_methods(false).each do |method_name|
         | 
| 234 | 
            +
                    mutator_advice.send(:define_method, method_name) do |*args, &block|
         | 
| 235 | 
            +
                      must_check(lambda { super(*args, &block) }) do |note|
         | 
| 236 | 
            +
                        note.prefix = nil
         | 
| 237 | 
            +
                        call_s = Note.new(self.class, method_name, args, block).message
         | 
| 238 | 
            +
                        call_s.sub!(".", "#")
         | 
| 239 | 
            +
                        note.prefix = "#{must_only_ever_contain_prefix}#{call_s}: "
         | 
| 240 | 
            +
                        note
         | 
| 241 | 
            +
                      end
         | 
| 242 | 
            +
                    end
         | 
| 243 | 
            +
                  end
         | 
| 244 | 
            +
                  mod.const_set(:MutatorAdvice, mutator_advice)
         | 
| 245 | 
            +
                  mod.instance_eval do
         | 
| 246 | 
            +
                    def extended(base)
         | 
| 247 | 
            +
                      base.extend(const_get(:MutatorAdvice))
         | 
| 248 | 
            +
                    end
         | 
| 249 | 
            +
                  end
         | 
| 250 | 
            +
                  
         | 
| 251 | 
            +
                  mod
         | 
| 252 | 
            +
                end
         | 
| 253 | 
            +
                
         | 
| 254 | 
            +
                def self.registered_class(object)
         | 
| 255 | 
            +
                  REGISTERED_CLASSES[object.class]
         | 
| 256 | 
            +
                end
         | 
| 257 | 
            +
                
         | 
| 258 | 
            +
                def self.unregister(klass)
         | 
| 259 | 
            +
                  REGISTERED_CLASSES.delete(klass)
         | 
| 260 | 
            +
                end
         | 
| 261 | 
            +
              end
         | 
| 262 | 
            +
              
         | 
| 263 | 
            +
              def self.must_only_ever_contain(container, cases, negate = false)
         | 
| 264 | 
            +
                unless container.singleton_methods.empty?
         | 
| 265 | 
            +
                  method_name = "must_#{negate ? "never" : "only"}_ever_contain"
         | 
| 266 | 
            +
                  raise ArgumentError, "#{method_name} adds singleton methods but"\
         | 
| 267 | 
            +
                    " receiver #{MustBe.short_inspect(container)} already"\
         | 
| 268 | 
            +
                    " has singleton methods #{container.singleton_methods.inspect}"
         | 
| 269 | 
            +
                end
         | 
| 270 | 
            +
                
         | 
| 271 | 
            +
                advice = MustOnlyEverContain.registered_class(container)
         | 
| 272 | 
            +
                if advice
         | 
| 273 | 
            +
                  container.extend advice
         | 
| 274 | 
            +
                  container.must_only_ever_contain_backtrace = caller
         | 
| 275 | 
            +
                  container.must_only_ever_contain_negate = negate
         | 
| 276 | 
            +
                  container.must_only_ever_contain_cases = cases
         | 
| 277 | 
            +
                else
         | 
| 278 | 
            +
                  raise TypeError,
         | 
| 279 | 
            +
                    "No MustOnlyEverContain.registered_class for #{container.class}"
         | 
| 280 | 
            +
                end
         | 
| 281 | 
            +
                container
         | 
| 282 | 
            +
              end
         | 
| 283 | 
            +
              
         | 
| 284 | 
            +
              def must_only_ever_contain(*cases)
         | 
| 285 | 
            +
                MustBe.must_only_ever_contain(self, cases)
         | 
| 286 | 
            +
              end
         | 
| 287 | 
            +
              
         | 
| 288 | 
            +
              def must_never_ever_contain(*cases)
         | 
| 289 | 
            +
                MustBe.must_only_ever_contain(self, cases, true)
         | 
| 290 | 
            +
              end
         | 
| 291 | 
            +
            end
         | 
| @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            module MustBe::MustOnlyEverContain
         | 
| 2 | 
            +
              
         | 
| 3 | 
            +
              ### Array ###
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              register Array do
         | 
| 6 | 
            +
                must_check_contents_after :collect!, :map!, :flatten!
         | 
| 7 | 
            +
                
         | 
| 8 | 
            +
                def <<(obj)
         | 
| 9 | 
            +
                  must_check_member(obj)
         | 
| 10 | 
            +
                  super
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                def []=(*args)
         | 
| 14 | 
            +
                  if args.size == 3 or args[0].is_a? Range
         | 
| 15 | 
            +
                    value = args.last
         | 
| 16 | 
            +
                    if value.nil?
         | 
| 17 | 
            +
                      # No check needed.
         | 
| 18 | 
            +
                    elsif value.is_a? Array
         | 
| 19 | 
            +
                      value.map {|v| must_check_member(v) }
         | 
| 20 | 
            +
                    else
         | 
| 21 | 
            +
                      must_check_member(value)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  else
         | 
| 24 | 
            +
                    must_check_member(args[1])
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                  super
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
                
         | 
| 29 | 
            +
                def concat(other_array)
         | 
| 30 | 
            +
                  must_check_contents(other_array)
         | 
| 31 | 
            +
                  super
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                def fill(*args)
         | 
| 35 | 
            +
                  if block_given?
         | 
| 36 | 
            +
                    begin
         | 
| 37 | 
            +
                      super
         | 
| 38 | 
            +
                    ensure
         | 
| 39 | 
            +
                      must_check_contents
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  else
         | 
| 42 | 
            +
                    must_check_member(args[0])
         | 
| 43 | 
            +
                    super
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                
         | 
| 47 | 
            +
                def insert(index, *objs)
         | 
| 48 | 
            +
                  must_check_contents(objs)
         | 
| 49 | 
            +
                  super
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
                
         | 
| 52 | 
            +
                def push(*objs)
         | 
| 53 | 
            +
                  must_check_contents(objs)
         | 
| 54 | 
            +
                  super
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
                
         | 
| 57 | 
            +
                def replace(other_array)
         | 
| 58 | 
            +
                  must_check_contents(other_array)
         | 
| 59 | 
            +
                  super
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
                
         | 
| 62 | 
            +
                def unshift(*objs)
         | 
| 63 | 
            +
                  must_check_contents(objs)
         | 
| 64 | 
            +
                  super
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
              
         | 
| 68 | 
            +
              ### Hash ###
         | 
| 69 | 
            +
              
         | 
| 70 | 
            +
              register Hash do
         | 
| 71 | 
            +
                must_check_contents_after :replace, :merge!, :update
         | 
| 72 | 
            +
                
         | 
| 73 | 
            +
                def []=(key, value)
         | 
| 74 | 
            +
                  must_check_pair(key, value)
         | 
| 75 | 
            +
                  super
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
                      
         | 
| 78 | 
            +
                def store(key, value)
         | 
| 79 | 
            +
                  must_check_pair(key, value)
         | 
| 80 | 
            +
                  super
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         |