r7kamura/chrono

View on GitHub
lib/chrono/fields/base.rb

Summary

Maintainability
A
25 mins
Test Coverage
module Chrono
  module Fields
    class Base
      class InvalidField < StandardError
        attr_reader :source

        def initialize(message, source)
          super("#{message}: #{source}")
          @source = source
        end
      end

      attr_reader :source

      def initialize(source)
        @source = source
      end

      def to_a
        if has_multiple_elements?
          fields.map(&:to_a).flatten.uniq.sort
        else
          validate!
          wildcard? ? [] : lower.step(upper, step).to_a.sort
        end
      end

      private

      def interpolated
        source.gsub("*", "#{range.first}-#{range.last}")
      end

      def wildcards
        []
      end

      def wildcard?
        wildcards.include?(source)
      end

      def validate!
        unless match_data
          raise InvalidField.new('Unparsable field', source)
        end

        return true if wildcard?

        if lower < range.begin || range.end < upper
          raise InvalidField.new('The field is out-of-range', source)
        end

        if upper < lower
          raise InvalidField.new('The range is evaluated to empty', source)
        end

        true
      end

      def lower
        @lower ||= match_data[1].to_i
      end

      def upper
        if match_data[2]
          match_data[2].to_i
        else
          lower
        end
      end

      def step
        if match_data[3]
          match_data[3].to_i
        else
          1
        end
      end

      def pattern
        ptn = %r<\A(\d+)(?:-(\d+))?(?:/(\d+))?\z>
        if wildcards.any?
          ptn = Regexp.union(ptn, *wildcards.map { |w| /\A#{w}\z/ })
        end
        ptn
      end

      def match_data
        @match_data ||= interpolated.match(pattern)
      end

      def elements
        @elements ||= source.split(",")
      end

      def has_multiple_elements?
        elements.size >= 2
      end

      def fields
        elements.map {|element| self.class.new(element) }
      end
    end
  end
end