require 'facets/multiton' # = Tuple # # Tuple is essentially an Array, but Comaparable and Immutable. # # A tuple can be made using #new or #[] just as one builds an array, # or using the #to_t method on a string or array. With a string tuple # remembers the first non-alphanumeric character as the tuple divider. # # == Usage # # t1 = Tuple[1,2,3] # t2 = Tuple[2,3,4] # # (t1 < t2) #=> true # (t1 > t2) #=> false # # t1 = '1.2.3'.to_t # t2 = '1-2-3'.to_t # # t1.to_s #=> "1.2.3" # t2.to_s #=> "1.2.3" # # (t1 == t2) #=> true # # Keep in mind that Tuple[1,2,3] is not the same as Tuple['1','2','3']. # #-- # TODO: The #hash method needs a touch-up. # # TODO: There are a few more methods yet to borrow from Array. # Consider how #+, #-, etc. ought to work. #++ class Tuple include ::Multiton include ::Enumerable include ::Comparable def self.multiton_id(arg=0, default=0, &block) if block_given? values = [] arg.times { |i| values << block[i] } elseif Integer === arg values = [ default ] * arg else values = arg.to_ary end values end def initialize(arg=0, default=0, &blk) if block_given? @values = [] arg.times { |i| @values << blk[i] } elseif Integer === arg @values = [ default ] * arg else @values = arg.to_ary end @default = default ##@divider = '.' end attr_accessor :default ##def divider( set=nil ) ## return @divider unless set ## @divider = set ## self ##end protected def values() @values end public SEGMENT_SEPARATOR = '.' def inspect() to_a.inspect end def to_t() self end def to_tuple() self end def to_a() Array(@values) end def to_ary() Array(@values) end def to_s(divider=nil) @values.join(divider || SEGMENT_SEPARATOR) end def size() @values.size end def length() @values.size end def empty?() return true if @values.empty? return true if @values == [ @default ] * @values.size false end def each( &block ) @values.each( &block ) end def each_index( &block ) @values.each_index( &block ) end def [](i) @values.fetch(i,@default) end def []=(i,v) @values[i] = v end def index() @values.index end def rindex() @values.rindex end # Unlike Array, Tuple#<< cannot act in place # becuase Tuple's are immutable. def <<( obj ) self.class.instance( to_a << obj ) end def pop() Tuple.instance( to_a.pop ) end def push( obj ) Tuple.instance( to_a.push(obj) ) end # Pulls a value off the beginning of a tuple. # This method is otherwsie known as #shift. def pull() Tuple.instance( to_a.shift ) end # Stands for "Put On Top". This method is the opposite of #pull and is # otherwise known as #unshift. def pot( obj ) Tuple.instance( to_a.unshift(obj) ) end alias_method :unshift, :pot alias_method :shift, :pull # Returns true if two tuple references are for the # very same tuple. def eql?( other ) return true if object_id == other.object_id ##return true if values.eql? other.values end # def <=>( other ) other = other.to_t [size, other.size].max.times do |i| c = self[i] <=> other[i] return c if c != 0 end 0 end # For pessimistic constraint (like '~>' in gems) def =~( other ) other = other.to_t upver = other.dup upver[0] += 1 self >= other and self < upver end def first() @values.first end def last() @values.last end # These are useful for using a Tuple as a version. def major() @values.first end def minor() @values.at(1) end def teeny() @values.at(2) end # Unique hash value. def hash # TODO: This needs to take into account the default # and maybe the divider too. to_a.hash end # class-level ----------------------------------------------- class << self # def []( *args ) instance( args ) end # Translates a string in the form on a set of numerical and/or # alphanumerical characters separated by non-word characters (eg \W+) # into a Tuple. The values of the tuple will be converted to integers # if they are purely numerical. # # Tuple.cast_from_string('1.2.3a') #=> [1,2,"3a"] # # It you would like to control the interpretation of each value # as it is added to the tuple you can supply a block. # # Tuple.cast_from_string('1.2.3a'){ |v| v.upcase } #=> ["1","2","3A"] # # This method is called by String#to_t. def cast_from_string( str, &yld ) args = str.to_s.split(/\W+/) div = /\W+/.match( str.to_s )[0] if block_given? args = args.collect{ |a| yld[a] } else args = args.collect { |i| /^[0-9]+$/ =~ i ? i.to_i : i } end self.instance(args) #.divider( div ) end # def cast_from_array( arr ) self.instance( arr ) end # Parses a constraint returning the operation as a lambda. def constraint_to_lambda( constraint, &yld ) op, val = *parse_constraint( constraint, &yld ) lambda { |t| t.send(op, val) } end # def parse_constraint( constraint, &yld ) constraint = constraint.strip re = %r{^(=~|~>|<=|>=|==|=|<|>)?\s*(\d+(:?[-.]\d+)*)$} if md = re.match( constraint ) if op = md[1] op = '=~' if op == '~>' op = '==' if op == '=' val = cast_from_string( md[2], &yld ) #instance( md[2] ) else op = '==' val = cast_from_string( constraint, &yld ) #instance( constraint ) end else raise ArgumentError, "invalid constraint" end return op, val end end end class ::String # Translates a string in the form on a set of numerical and/or # alphanumerical characters separated by non-word characters (eg \W+) # into a Tuple. The values of the tuple will be converted to integers # if they are purely numerical. # # '1.2.3a'.to_t #=> [1,2,"3a"] # # It you would like to control the interpretation of each value # as it is added to the tuple you can supply a block. # # '1.2.3a'.to_t { |v| v.upcase } #=> ["1","2","3A"] # # This method calls Tuple.cast_from_string. def to_t( &yld ) Tuple.cast_from_string( self, &yld ) end end class ::Array # Convert an array into a tuple. def to_t Tuple.cast_from_array( self ) end end # Copyright (c) 2005 Thomas Sawyer (Ruby License)