taichi-ishitani/rggen

View on GitHub
lib/rggen/input_base/item.rb

Summary

Maintainability
A
35 mins
Test Coverage
module RgGen
  module InputBase
    class Item < Base::Item
      include RegxpPatterns

      class InputMatcher
        def initialize(pattern, options)
          @options  = options
          @pattern  =
            if @options.fetch(:match_wholly, true)
              /\A#{pattern}\z/
            else
              pattern
            end
        end

        attr_reader :match_data

        def match_automatically?
          @options.fetch(:match_automatically, true)
        end

        def match(rhs)
          rhs = rhs.to_s if @options[:convert_to_string]
          rhs = delete_blanks(rhs) if @options.fetch(:ignore_blank, true)
          @match_data =
            case rhs
            when @pattern
              Regexp.last_match
            end
        end

        private

        BLANK_REGEXP  = [
          /\A[ \t]+/,
          /(?<=\w)[ \t]+(?=[[:punct:]])/,
          /(?<=[[:punct:]])[ \t]+(?=\w)/,
          /[ \t]+\z/
        ].inject(&:|).freeze

        def delete_blanks(rhs)
          return rhs unless rhs.respond_to?(:gsub)
          rhs.gsub(BLANK_REGEXP, '')
        end
      end

      define_helpers do
        attr_reader :builders
        attr_reader :validators
        attr_reader :input_matcher

        def field(field_name, options = {}, &body)
          return if fields.include?(field_name)

          define_method(field_name) do |*args, &block|
            field_method(field_name, options, body, args, block)
          end

          fields  << field_name
        end

        def fields
          @fields ||= []
        end

        def build(&body)
          @builders ||= []
          @builders << body
        end

        def validate(&body)
          @validators ||= []
          @validators << body
        end

        def input_pattern(pattern, options = {})
          @input_matcher  = InputMatcher.new(pattern, options)
        end

        def active_item?
          !passive_item?
        end

        def passive_item?
          @builders.nil? || @builders.empty?
        end
      end

      def self.inherited(subclass)
        super(subclass)
        [:@fields, :@builders, :@validators].each do |v|
          subclass.inherit_class_instance_variable(v, self, &:dup)
        end
        subclass.inherit_class_instance_variable(:@input_matcher, self)
      end

      class_delegator :fields
      class_delegator :builders
      class_delegator :validators
      class_delegator :input_matcher

      def build(*sources)
        return unless builders
        pattern_match(sources.last) if match_automatically?
        builders.each do |builder|
          instance_exec(*sources, &builder)
        end
      end

      def validate
        return if @validated
        return unless validators
        validators.each do |validator|
          instance_exec(&validator)
        end
        @validated  = true
      end

      private

      def pattern_match(rhs)
        input_matcher && input_matcher.match(rhs)
      end

      def match_data
        input_matcher && input_matcher.match_data
      end

      def pattern_matched?
        match_data.not_nil?
      end

      def captures
        match_data && match_data.captures
      end

      def match_automatically?
        input_matcher && input_matcher.match_automatically?
      end

      def field_method(field_name, options, body, args, block)
        validate if options[:need_validation]
        if body
          instance_exec(*args, &body)
        elsif options[:forward_to_helper]
          self.class.__send__(field_name, *args, &block)
        elsif options.key?(:forward_to)
          __send__(options[:forward_to], *args, &block)
        else
          default_field_method(field_name, options[:default])
        end
      end

      def default_field_method(field_name, default_value)
        variable_name =
          if field_name =~ /\A([a-zA-Z0-9]\w*)\?\z/
            Regexp.last_match[1].variablize
          else
            field_name.variablize
          end

        if instance_variable_defined?(variable_name)
          instance_variable_get(variable_name)
        else
          default_value
        end
      end
    end
  end
end