newmen/versatile-diamond

View on GitHub
analyzer/lib/analyzer.rb

Summary

Maintainability
A
1 hr
Test Coverage
module VersatileDiamond
  using Patches::RichString

  # Class for analyzing configuration file writen on VD:DSL
  class Analyzer < Interpreter::Base
    class << self
      # Self method for reading and analyzing configuration file
      # If the results already where obtained then using it
      #
      # @param [String] config_path the path to configuration file
      # @option [String] :cache_dir the directory where cache files will be stored
      # @return [Organizers::AnalysisResult] the result of analysis
      def read_config(config_path, cache_dir: 'cache')
        Serializer.init!(cache_dir)
        result = Tools::Serializer.load(config_path)

        # Checks that analysis results already exists
        # If not then analyze and store dup
        if result
          Tools::Config.load!(config_path)
        else
          Tools::Config.init!

          content = File.open(config_path).readlines
          result = new(content, config_path).analyze
          Tools::Serializer.save(config_path, result)
          Tools::Config.save(config_path)
        end

        result
      end
    end

    # Initialize analyzer
    # @param [String] content the content whitch will be analyzed
    # @param [String] config_path the path to configuration file
    def initialize(content, config_path = nil)
      @content = content
      @line_number = 0
      @line = next_line
      @root = nil

      @config_path = config_path
    end

    # Prepare analysis result
    # @return [Organizers::AnalysisResult] the result of analysis
    def analyze
      read_all_lines
      grab_analysis
    rescue Errors::SyntaxError => e
      puts e.message(@config_path, @line_number)
      nil
    rescue Errors::Base
      puts "Versatile Diamond internal error at line #{@line_number}:"
      puts "\n\t#{@line.strip}" if @line
      puts
      puts "\nPlease report this error"
      raise
    end

  private

    # Launches analysis cycle
    def read_all_lines
      loop do
        interpret(@line, method(:change_root)) do
          begin
            pass_line_to(@root, @line)
          rescue Errors::SyntaxWarning => e
            puts e.message(@config_path, @line_number + 1)
            next_line || break
            retry
          end
        end

        next_line || break
      end
    end

    # Changes instance root variable which will be receive next shifted lines
    # @param [String] decreased_line the line for analyzing without forward
    #   extra spaces
    # @raise [Errors::SyntaxError] if instance of root cannot be instanced
    def change_root(decreased_line)
      root = head_and_tail(decreased_line).first
      @root = instance(root)
    end

    # Makes an anstance of interpreter
    # @param [String] name the underscored name of interpreter class
    # @raise [Errors::SyntaxError] if interpreter component cannot be instanced
    def instance(name)
      component = name.constantize
      if component
        component.new
      else
        syntax_error('common.undefined_component', component: name)
      end
    end

    # Iterate next line and cut comments
    # @return [String] the next line of content
    def next_line
      @line = @content[@line_number]
      @line_number += 1
      return unless @line
      @line.sub!(/#.+\Z/, '') # drop comments
      @line.rstrip!
      (!@line || @line != '') ? @line : next_line
    end

    # Grabs analysis of all collected data
    # @raise [Errors::SyntaxError] if found duplication of some reaction
    # @return [AnalysisResult] the result of analysis
    def grab_analysis
      Organizers::AnalysisResult.new
    rescue Organizers::AnalysisResult::ReactionDuplicate => e
      syntax_error('.reaction_duplicate', first: e.first, second: e.second)
    end
  end

end