team-umlaut/umlaut

View on GitHub
lib/cron_tab.rb

Summary

Maintainability
A
1 hr
Test Coverage
# Extracted from dpklib, Ruby library released under same license as Ruby.


class CronTab 
    attr_accessor :min, :hour, :mday, :mon, :wday, :command
  
    WDAY = %w(sun mon tue wed thu fri sat)
    FormatError = Class.new(StandardError)

    def initialize(str)
      super()

      self.min, self.hour, self.mday, self.mon, self.wday =
        CronTab.parse_timedate(str)

      self.command = str.scan( /(?:\S+\s+){5}(.*)/ ).shift
    end

    def ===(rhs)
      judge_date = proc {
        b = true
        b = b && (mday === rhs.mday)
        b = b && (mon === rhs.mon)
        b = b && (wday === rhs.wday)
      }
      judge_hour = proc {
        b = true
        b = b && (min === rhs.min)
        b = b && (hour === rhs.hour)
      }

      case rhs
      when Time
        judge_hour.call && judge_date.call
      when Dpklib::Hour
        judge_hour.call
      when Date
        judge_date.call
      else
        super
      end
    end
    alias include? ===;

    class NextSeeker 
      attr_accessor :scalar, :field, :lower_seeker
      
      def initialize(s, f, l)
        self.scalar = s
        self.field = f
        self.lower_seeker = l
      end
      
      def succ
        if lower_seeker.nil? || lower_seeker.succ then
          self.scalar = field.nextof(scalar)
          scalar
        else
          lower_seeker.recursive_zero
          self.scalar += 1
          succ
        end
      end

      def recursive_zero
        self.scalar = 0
        lower_seeker && lower_seeker.recursive_zero
      end
    end #/NextSeeker

    class YearField
      def nextof(nowyear)
        nowyear
      end
    end #/YearField

    def nexttime(nowtime = Time.now)
      nowmin = nowtime.min + 1

      seeker_min = NextSeeker.new(nowmin, min, nil)
      seeker_hour = NextSeeker.new(nowtime.hour, hour, seeker_min)
      seeker_mday = NextSeeker.new(nowtime.mday, mday, seeker_hour)
      seeker_mon = NextSeeker.new(nowtime.mon, mon, seeker_mday)
      seeker_year = NextSeeker.new(nowtime.year, YearField.new, seeker_mon)
      seeker_year.succ

      Time.local(seeker_year.scalar,
                 seeker_mon.scalar,
                 seeker_mday.scalar,
                 seeker_hour.scalar,
                 seeker_min.scalar, 0)
    end

    def waitsec(nowtime = Time.now)
      nexttime(nowtime).to_i - nowtime.to_i
    end

    def self.parse_timedate(str)
      minute, hour, day_of_month, month, day_of_week =
        str.scan(/^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/).shift

      day_of_week = day_of_week.downcase.gsub(/#{WDAY.join("|")}/){
        WDAY.index($&)
      }

      [
        parse_field(minute,       0, 59),
        parse_field(hour,         0, 23),
        parse_field(day_of_month, 1, 31),
        parse_field(month,        1, 12),
        parse_field(day_of_week,  0, 6),
      ]
    end

    class Field 
      attr_accessor :range, :every
      
      def initialize(r, e)
        self.range = r
        self.every = e
      end
      
      def ===(rhs)
        b = true
        b = b && ( (rhs - range.first) % every == 0 )
        b = b && ( range === rhs )
      end

      def nextof(now)
        if now < range.first then
          nextof(range.first)
        elsif range.last < now || (range.exclude_end? && range.last <= now) then
          nil
        else
          now + ( (now - range.first) % every )
        end
      end
    end

    class FieldSet 
      attr_accessor :fields
      
      def initialize(f)
        self.fields = f
      end
      
      def ===(rhs)
        b = false
        fields.each { |field|
          b ||= (field === rhs)
        }
        b
      end

      def nextof(now)
        ret = nil
        fields.each { |field|
          field_nextof = field.nextof(now)
          ret = field_nextof if ret.nil? || (field_nextof && field_nextof < ret)
        }
        ret
      end
    end

    def self.parse_field(str, first, last)
      list = str.split(",")
      list.map!{|r|
        r, every = r.split("/")
        every = every ? every.to_i : 1
        f,l = r.split("-")
        range = if f == "*"
                  first..last
                elsif l.nil?
                  f.to_i .. f.to_i
                elsif f.to_i < first
                  raise FormatError.new("out of range (#{f} for #{first})")
                elsif last < l.to_i
                  raise FormatError.new("out of range (#{l} for #{last})")
                else
                  f.to_i .. l.to_i
                end
        Field.new(range, every)
      }
      FieldSet.new(list)
    end

    #alias parse new
    #alias [] new
    
  end #/CronTab