lib/banditmask/banditry.rb in banditmask-0.2.1 vs lib/banditmask/banditry.rb in banditmask-0.3.0
- old
+ new
@@ -1,55 +1,89 @@
class BanditMask
+ class MethodCollisionError < StandardError
+ end
+
module Banditry
##
- # Creates wrapper methods for reading and writing the bitmask stored in
- # +attribute+ using the class +with+. +with+ defaults to BanditMask, but
- # you can (and probably) should define your own class descending from
- # +BanditMask+ to fill this role. The name of the accessor methods will be
- # derived from +as+, e.g., if +as+ is +:foo+, the reader method will be
- # +:foo+ and the writer will be +:foo=+.
+ # Creates wrapper methods for reading, writing, and querying the bitmask
+ # stored in +attribute+ using the class +with+. +with+ defaults to
+ # BanditMask, but you can (and probably should) define your own class
+ # inheriting from +BanditMask+ to fill this role. The name of the accessor
+ # methods will be derived from +as+, e.g., if +as+ is +:foo+, the methods
+ # will be named +:foo+, +:foo=+, and +:foo?+.
#
- # The reader method will call BanditMask#bits to get an array of the
- # enabled bit names represented by +attribute+.
+ # The reader method will return a BanditMask representation of +attribute+.
#
- # The writer method will replace the current bitmask with an Integer
- # representation of a new BanditMask built up using BanditMask#<<.
+ # The writer method overwrites the current bitmask if with an Array of
+ # bits, or it updates the current bitmask if sent an individual bit, e.g.,
+ # using +#|+.
#
- # In addition to the accessor methods, a method named +has?+ will be added
- # which delegates to the BanditMask#include?.
+ # In addition to the accessor methods, a query method that delegates to
+ # BanditMask#include? will be added.
#
- # class ThingMask < BanditMask
- # # ...
- # # bit :foo, 0b1
- # # ...
+ # class FileMask < BanditMask
+ # bit :r, 0b001
+ # bit :w, 0b010
+ # bit :e, 0b100
# end
#
- # class Thing
- # attr_accessor :bitmask
+ # class FileObject
+ # attr_accessor :mode_mask
#
# extend BanditMask::Banditry
- # bandit_mask :bitmask, as: :bits, with: ThingMask
+ # bandit_mask :mode_mask, as: :mode, with: FileMask
# end
+ #
+ # file = FileObject.new
+ # file.mode_mask # => nil
+ # file.mode |= :r
+ # file.mode_mask # => 1
+ # file.mode |= :w
+ # file.mode_mask # => 3
+ # file.mode = [:r, :x]
+ # file.mode_mask # => 5
def bandit_mask(attribute, as:, with: BanditMask)
- cls = with
- wrapper = as
-
class_eval do
- ##
- # A reader method which instances a new BanditMask object and calls
- # BanditMask#bits.
- define_method wrapper do
- cls.new(send(attribute)).bits
- end
+ generate_reader attribute, as, with
+ generate_writer attribute, as, with
+ generate_query attribute, as, with
+ end
+ end
- define_method :"#{wrapper}=" do |bits|
- mask = bits.reduce(cls.new) { |mask, bit| mask << bit }
- send :"#{attribute}=", Integer(mask)
- end
+ private
- define_method :has? do |*bits|
- cls.new(send(attribute)).include? *bits
- end
+ def generate_reader(attr, virt, cls)
+ respond_to? virt and
+ raise MethodCollisionError, "method `#{virt}` already exists"
+
+ define_method virt do
+ instance_variable_get(:"@#{virt}") ||
+ instance_variable_set(:"@#{virt}", cls.new(send(attr)))
+ end
+ end
+
+ def generate_writer(attr, virt, cls)
+ respond_to? :"#{virt}=" and
+ raise MethodCollisionError, "method `#{virt}=` already exists"
+
+ define_method :"#{virt}=" do |bits|
+ mask = case bits
+ when BanditMask
+ bits
+ else
+ bits.inject(cls.new) { |bm, bit| bm << bit }
+ end
+ send :"#{attr}=", Integer(mask)
+ instance_variable_set :"@#{virt}", mask
+ end
+ end
+
+ def generate_query(attr, virt, cls)
+ respond_to? :"#{virt}?" and
+ raise MethodCollisionError, "method `#{virt}?` already exists"
+
+ define_method :"#{virt}?" do |*bits|
+ cls.new(send(attr)).include? *bits
end
end
end
end