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