purban/annotate_yaml

View on GitHub
lib/annotate_yaml/annotate_yaml.rb

Summary

Maintainability
A
1 hr
Test Coverage
module AnnotateYaml
  require 'yaml'

  DEFAULT_YML_LOOKUP = 'config/locales/*.yml'

  class YamlHashExplorer
    attr_reader :result

    def initialize(hash)
      @result = []
      yaml_navigation(hash, [])
    end

    private

    def yaml_navigation(yaml_hash, keys_array_origin)
      yaml_hash.each do |key, value|
        keys_array_updated = keys_array_origin.dup
        keys_array_updated.push(key)
        result.push({ value => keys_array_updated.join('.') }) if desired_terminatory_value?(value)

        yaml_navigation(value, keys_array_updated) if value.is_a?(Hash)
      end
    end

    def desired_terminatory_value?(value)
      !(value.nil? || value.is_a?(Hash) || value.is_a?(Array))
    end
  end

  class FileHandler

    attr_reader :file_name, :result

    def initialize(file_name)
      @file_name = file_name
      @result = ""
    end

    def call
      create_annotated_text
      rewrite_file
    end

    private

    def create_annotated_text
      adapted_file_content.each_line do |line|
        annotate_line(line)
      end
    end

    def annotate_line(line)
      begin
        if line_hash = YAML::load(line)
          _, line_value = line_hash.first

          if line_value.nil?
            result << line
          else
            key, value = current_hash.first
            if line_value == key
              result << annotation_for_line(line, value)
            else
              result << line
            end
          end
        else
          result << line
        end
      # YAML::load will generate an exception when references are used inside the yaml file. Exemple: errors: &errors
      rescue Psych::BadAlias
        result << line
      end
    end

    def annotation_for_line(line, value)
      (line.end_with?(" \# #{value}\n") ? line : line.gsub("\n", '') + " \# #{value}\n")
    end

    def rewrite_file
      File.open(file_name, "w+") do |f|
        f.write(result)
      end
    end

    def adapted_file_content
      File.open(file_name).read.gsub(/\r\n?/, "\n")
    end

    def hash_content
      # Remove the references inside the yaml file otherwise the hash will be duplicated at each reference, and we don't want that.
      # Exemple of reference: <<: *errors
      YAML.load(File.open(file_name).read.gsub(/<<: \*.*/, ''))
    end

    def array_of_hashes_of_values_with_keys
      @array_of_hashes_of_values_with_keys ||= YamlHashExplorer.new(hash_content).result
    end

    def current_hash
      array_of_hashes_of_values_with_keys.shift
    end
  end

  class << self
    def annotate_locales
      file_names.each do |file_name|
        FileHandler.new(file_name).call
      end
    end

    def file_names
      Dir.glob(DEFAULT_YML_LOOKUP)
    end
  end

end