<commit_before>
module Familia
  
  class RedisObject
    @registration = {}
    @classes = []
    
    # To be called inside every class that inherits RedisObject
    # +meth+ becomes the base for the class and instances methods
    # that are created for the given +klass+ (e.g. Obj.list)
    def RedisObject.register klass, meth
      registration[meth] = klass
    end
    
    def RedisObject.registration
      @registration
    end
    
    def RedisObject.classes
      @classes
    end
    
    @db, @ttl = nil, nil
    class << self
      attr_accessor :parent
      attr_writer :ttl, :classes, :db, :uri
      def ttl v=nil
        @ttl = v unless v.nil?
        @ttl || (parent ? parent.ttl : nil)
      end
      def db v=nil
        @db = v unless v.nil?
        @db || (parent ? parent.db : nil)
      end
      def uri v=nil
        @uri = v unless v.nil?
        @uri || (parent ? parent.uri : Familia.uri)
      end
      def inherited(obj)
        obj.db = self.db
        obj.ttl = self.ttl
        obj.uri = self.uri
        obj.parent = self
        RedisObject.classes << obj
        super(obj)
      end
    end
    
    attr_reader :name, :parent
    attr_accessor :redis
    
    # +name+: If parent is set, this will be used as the suffix 
    # for rediskey. Otherwise this becomes the value of the key.
    # If this is an Array, the elements will be joined.
    # 
    # Options:
    #
    # :parent: The Familia object that this redis object belongs
    # to. This can be a class that includes Familia or an instance.
    # 
    # :ttl => the time to live in seconds. When not nil, this will
    # set the redis expire for this key whenever #save is called. 
    # You can also call it explicitly via #update_expiration.
    #
    # :default => the default value (String-only)
    #
    # :class => A class that responds to Familia.load_method and 
    # Familia.dump_method. These will be used when loading and
    # saving data from/to redis to unmarshal/marshal the class. 
    #
    # :db => the redis database to use (ignored if :redis is used).
    #
    # :redis => an instance of Redis.
    #
    # Uses the redis connection of the parent or the value of 
    # opts[:redis] or Familia.redis (in that order).
    def initialize name, opts={}
      @name, @opts = name, opts
      @name = @name.join(Familia.delim) if Array === @name
      @opts[:ttl] ||= self.class.ttl
      @opts[:db] ||= self.class.db
      @parent = @opts.delete(:parent)
      @redis = parent.redis if parent?
      @redis ||= @opts.delete(:redis) || Familia.redis(@opts[:db])
      init if respond_to? :init
    end
    
    # returns a redis key based on the parent 
    # object so it will include the proper index.
    def rediskey
      parent? ? parent.rediskey(name) : Familia.rediskey(name)
    end
    
    def parent?
      Class === parent || parent.kind_of?(Familia)
    end
    
    def update_expiration(ttl=nil)
      ttl ||= @opts[:ttl]
      return unless ttl && ttl.to_i > 0
      #Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{rediskey} to #{self.ttl}"
      expire ttl.to_i
    end
    
    def move db
      redis.move rediskey, db
    end
        
    # TODO: rename, renamenx
    
    def type 
      redis.type rediskey
    end
    
    def delete 
      redis.del rediskey
    end
    alias_method :clear, :delete
    alias_method :del, :delete
    
    #def destroy! 
    #  clear
    #  # TODO: delete redis objects for this instance
    #end
    
    def exists?
      !size.zero?
    end
    
    def ttl
      redis.ttl rediskey
    end
    
    def expire sec
      redis.expire rediskey, sec.to_i
    end
    
    def expireat unixtime
      redis.expireat rediskey, unixtime
    end
    
    def to_redis v
      return v unless @opts[:class]
      if v.respond_to? Familia.dump_method
        v.send Familia.dump_method
      else
        Familia.ld "No such method: #{v.class}.#{Familia.dump_method}"
        v
      end
    end
    
    def from_redis v
      return v unless @opts[:class]
      case @opts[:class]
      when String
        v.to_s
      when Fixnum, Float
        @opts[:class].induced_from v
      else
        if @opts[:class].method_defined? Familia.load_method
          @opts[:class].send Familia.load_method, v
        else
          Familia.ld "No such method: #{@opts[:class]}##{Familia.load_method}"
          v
        end
      end
    end 
    
  end
  
  
  class List < RedisObject
    
    def size
      redis.llen rediskey
    end
    alias_method :length, :size
    
    def << v
      redis.rpush rediskey, to_redis(v)
      redis.ltrim rediskey, -@opts[:maxlength], -1 if @opts[:maxlength]
      self
    end
    alias_method :push, :<<

    def unshift v
      redis.lpush rediskey, to_redis(v)
      # TODO: test maxlength
      redis.ltrim rediskey, 0, @opts[:maxlength] - 1 if @opts[:maxlength]
      self
    end
    
    def pop
      from_redis redis.rpop(rediskey)
    end
    
    def shift
      from_redis redis.lpop(key)
    end
    
    def [] idx, count=nil
      if idx.is_a? Range
        range idx.first, idx.last
      elsif count
        case count <=> 0
        when 1  then range(idx, idx + count - 1)
        when 0  then []
        when -1 then nil
        end
      else
        at idx
      end
    end
    alias_method :slice, :[]
    
    def delete v, count=0
      redis.lrem rediskey, count, to_redis(v)
    end<commit_msg>Fixed List#shift<commit_after>
module Familia
  
  class RedisObject
    @registration = {}
    @classes = []
    
    # To be called inside every class that inherits RedisObject
    # +meth+ becomes the base for the class and instances methods
    # that are created for the given +klass+ (e.g. Obj.list)
    def RedisObject.register klass, meth
      registration[meth] = klass
    end
    
    def RedisObject.registration
      @registration
    end
    
    def RedisObject.classes
      @classes
    end
    
    @db, @ttl = nil, nil
    class << self
      attr_accessor :parent
      attr_writer :ttl, :classes, :db, :uri
      def ttl v=nil
        @ttl = v unless v.nil?
        @ttl || (parent ? parent.ttl : nil)
      end
      def db v=nil
        @db = v unless v.nil?
        @db || (parent ? parent.db : nil)
      end
      def uri v=nil
        @uri = v unless v.nil?
        @uri || (parent ? parent.uri : Familia.uri)
      end
      def inherited(obj)
        obj.db = self.db
        obj.ttl = self.ttl
        obj.uri = self.uri
        obj.parent = self
        RedisObject.classes << obj
        super(obj)
      end
    end
    
    attr_reader :name, :parent
    attr_accessor :redis
    
    # +name+: If parent is set, this will be used as the suffix 
    # for rediskey. Otherwise this becomes the value of the key.
    # If this is an Array, the elements will be joined.
    # 
    # Options:
    #
    # :parent: The Familia object that this redis object belongs
    # to. This can be a class that includes Familia or an instance.
    # 
    # :ttl => the time to live in seconds. When not nil, this will
    # set the redis expire for this key whenever #save is called. 
    # You can also call it explicitly via #update_expiration.
    #
    # :default => the default value (String-only)
    #
    # :class => A class that responds to Familia.load_method and 
    # Familia.dump_method. These will be used when loading and
    # saving data from/to redis to unmarshal/marshal the class. 
    #
    # :db => the redis database to use (ignored if :redis is used).
    #
    # :redis => an instance of Redis.
    #
    # Uses the redis connection of the parent or the value of 
    # opts[:redis] or Familia.redis (in that order).
    def initialize name, opts={}
      @name, @opts = name, opts
      @name = @name.join(Familia.delim) if Array === @name
      @opts[:ttl] ||= self.class.ttl
      @opts[:db] ||= self.class.db
      @parent = @opts.delete(:parent)
      @redis = parent.redis if parent?
      @redis ||= @opts.delete(:redis) || Familia.redis(@opts[:db])
      init if respond_to? :init
    end
    
    # returns a redis key based on the parent 
    # object so it will include the proper index.
    def rediskey
      parent? ? parent.rediskey(name) : Familia.rediskey(name)
    end
    
    def parent?
      Class === parent || parent.kind_of?(Familia)
    end
    
    def update_expiration(ttl=nil)
      ttl ||= @opts[:ttl]
      return unless ttl && ttl.to_i > 0
      #Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{rediskey} to #{self.ttl}"
      expire ttl.to_i
    end
    
    def move db
      redis.move rediskey, db
    end
        
    # TODO: rename, renamenx
    
    def type 
      redis.type rediskey
    end
    
    def delete 
      redis.del rediskey
    end
    alias_method :clear, :delete
    alias_method :del, :delete
    
    #def destroy! 
    #  clear
    #  # TODO: delete redis objects for this instance
    #end
    
    def exists?
      !size.zero?
    end
    
    def ttl
      redis.ttl rediskey
    end
    
    def expire sec
      redis.expire rediskey, sec.to_i
    end
    
    def expireat unixtime
      redis.expireat rediskey, unixtime
    end
    
    def to_redis v
      return v unless @opts[:class]
      if v.respond_to? Familia.dump_method
        v.send Familia.dump_method
      else
        Familia.ld "No such method: #{v.class}.#{Familia.dump_method}"
        v
      end
    end
    
    def from_redis v
      return v unless @opts[:class]
      case @opts[:class]
      when String
        v.to_s
      when Fixnum, Float
        @opts[:class].induced_from v
      else
        if @opts[:class].method_defined? Familia.load_method
          @opts[:class].send Familia.load_method, v
        else
          Familia.ld "No such method: #{@opts[:class]}##{Familia.load_method}"
          v
        end
      end
    end 
    
  end
  
  
  class List < RedisObject
    
    def size
      redis.llen rediskey
    end
    alias_method :length, :size
    
    def << v
      redis.rpush rediskey, to_redis(v)
      redis.ltrim rediskey, -@opts[:maxlength], -1 if @opts[:maxlength]
      self
    end
    alias_method :push, :<<

    def unshift v
      redis.lpush rediskey, to_redis(v)
      # TODO: test maxlength
      redis.ltrim rediskey, 0, @opts[:maxlength] - 1 if @opts[:maxlength]
      self
    end
    
    def pop
      from_redis redis.rpop(rediskey)
    end
    
    def shift
      from_redis redis.lpop(rediskey)
    end
    
    def [] idx, count=nil
      if idx.is_a? Range
        range idx.first, idx.last
      elsif count
        case count <=> 0
        when 1  then range(idx, idx + count - 1)
        when 0  then []
        when -1 then nil
        end
      else
        at idx
      end
    end
    alias_method :slice, :[]
    
    def delete v, count=0
      redis.lrem rediskey, count, to_redis(v)
    end
{'fraction_non_alphanumeric': 0.09100073313782991, 'fraction_numerical': 0.0018328445747800588, 'mean_word_length': 1.883985200845666, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1652186', 'n_tokens_mistral': 3666, 'n_tokens_neox': 3635, 'n_words': 1116}