balinterdi/i15r

View on GitHub
lib/i15r/pattern_matcher.rb

Summary

Maintainability
C
1 day
Test Coverage
# -*- coding: utf-8 -*-
class I15R
  class PatternMatcher
    HAML_SYMBOLS = ["%", "#", "{", "}", "(", ")", ".", "_", "-"]
    PATTERNS = {
      :erb => [
        /<%=\s*link_to\s+(?!.*&.*;)(?<title>['"].+?['"])/,
        /<%=.*label(_tag)?[^,]+?(?<label-title>(['"].+?['"]|:[[:alnum:]_]+))[^,]+%>.*$/,
        /<%=.*label(_tag)?.*?,\s*(?<label-title>(['"].+?['"]|:[[:alnum:]_]+))/,
        /<%=.*submit(_tag)?\s+(?<submit-text>(['"].+?['"]|:[[:alnum:]_]+))/,
        />(?<tag-content>(?!&.*;)[[:space:][:alnum:][:punct:]]+?)<\//,
        /\s+title=['"](?<link-title>.+?)['"]/,
        /\s+alt=['"](?<img-alt>.+?)['"]/,
        /^\s*(?<pre-tag-text>[[:alnum:]]+[[:alnum:][:space:][:punct:]]*?)</,
        /^(?!^var .*)(?!.*(%>|{|})$)(?!.*(:|=).*(;|,)$)(?!.*return .*;$)(?!.*=)(?!^(if |unless ).*(\=|\&|\|))(?!^(if |unless )\S+$)(?!^end$)(?!.*do.*\|$)\s*(?<no-markup-content>[[:alnum:]]+[[:alnum:][:space:][:punct:]]*)/
      ],
      :haml => [
        /=.*link_to\s+(?<title>['"].+?['"]),/,
        /=.*label(_tag)?[^,]+?(?<label-title>(['"].+?['"]|:[[:alnum:]_]+))[^,]*$/,
        /=.*label(_tag)?.*?,\s*(?<label-title>(['"].+?['"]|:[[:alnum:]_]+))/,
        /=.*submit(_tag)?\s+(?<submit-text>(['"].+?['"]|:[[:alnum:]_]+))/,
        %r{^\s*(?<content>[[:space:][:alnum:]'/(),]+)$},
        %r{^\s*[[#{HAML_SYMBOLS.join('')}][:alnum:]]+?\{.+?\}\s+(?<content>.+)$},
        %r{^\s*[[#{HAML_SYMBOLS.join('')}][:alnum:]]+?\(.+?\)\s+(?<content>.+)$},
        %r{^\s*[[#{(HAML_SYMBOLS - ['{', '}', '(', ')']).join('')}][:alnum:]]+?\s+(?<content>.+)$}
      ]
    }

    def initialize(prefix, file_type, locale_creator = I15R::LocaleCreator.new, options={})
      if locale_creator.is_a? Hash
        options = locale_creator
        locale_creator = I15R::LocaleCreator.new
      end
      @prefix = prefix
      @file_type = file_type
      transformer_class = self.class.const_get("#{file_type.to_s.capitalize}Transformer")
      @transformer = transformer_class.new(options[:add_default], options[:override_i18n_method] || 'I18n.t', locale_creator)
    end

    def translation_key(text)
      #TODO: downcase does not work properly for accented chars, like 'Ú', see function in ActiveSupport that deals with this
      #TODO: [:punct:] would be nice but it includes _ which we don't want to remove
      key = text.strip.downcase.gsub(/[\s\/]+/, '_').gsub(/[!?.,:"';()#\/\\]/, '')
      "#{@prefix}.#{key}"
    end

    def run(text)
      lines = text.split("\n")
      new_lines = lines.map do |line|
        new_line = line
        if line !~ /[[:alpha:]]/
          new_line
        else
          PATTERNS[@file_type].detect do |pattern|
            if m = pattern.match(line)
              m.names.each do |group_name|
                if /\w/.match(m[group_name])
                  new_line = @transformer.transform(m, m[group_name], line, translation_key(m[group_name]))
                end
              end
            end
          end
        end
        if block_given? and line != new_line
          yield line, new_line
        end
        new_line
      end
      new_lines.join("\n")
    end

    class Transformer
      def initialize(add_default, i18n_method, locale_creator)
        @add_default = add_default
        @i18n_method = i18n_method
        @locale_creator = locale_creator
      end

      private
        def i18n_string(key, original)
          @locale_creator.add(key, original)

          if @add_default
            if original.to_s[0] == ':'
              original = original.to_s[1..-1]
            end
            unless original[0] == "'" or original[0] == '"'
              original = %("#{original}")
            end
            %(#{@i18n_method}("#{key}", :default => #{original}))
          else
            %(#{@i18n_method}("#{key}"))
          end
        end
    end

    class ErbTransformer < Transformer

      def transform(match_data, match, line, translation_key)
        return line if line.match /\bt\(/
        if match_data.to_s.index("<%")
          line.gsub(match, i18n_string(translation_key, match))
        else
          line.gsub(match, "<%= #{i18n_string(translation_key, match)} %>")
        end
      end

    end

    class HamlTransformer < Transformer

      def transform(match_data, match, line, translation_key)
        return line if line.match /\bt\(/
        leading_whitespace = line[/^(\s+)/, 1]
        no_leading_whitespace = if leading_whitespace
          line[leading_whitespace.size..-1]
        else
          line
        end
        if ['/', '-'].include?(no_leading_whitespace[0])
          return line
        end

        # Space can only occur in haml markup in an attribute list
        # enclosed in { } or ( ). If the first segment has { or (
        # we are still in the markup and need to go on to find the beginning
        # of the string to be replaced
        i = 0
        haml_segment = true
        attribute_list_start = nil
        segments = no_leading_whitespace.split(/\s+/)
        while haml_segment
          s = segments[i]
          if attribute_list_start
            attribute_list_end = [')', '}'].detect { |sym| s.index(sym) }
            if attribute_list_end
              haml_segment = false
            end
          else
            attribute_list_start = ['(', '{'].detect { |sym| s.index(sym) }
            unless attribute_list_start
              haml_segment = false
            end
          end
          i += 1
        end

        until_first_whitespace = segments[0...i].join(' ')
        if HAML_SYMBOLS.any? { |sym| until_first_whitespace.index(sym) }
          haml_markup = until_first_whitespace
          content = segments[i..-1].join(' ')
          if haml_markup[-1] == '='
            haml_markup += ' '
          else
            haml_markup += '= '
          end
        else
          haml_markup = ''
          content = no_leading_whitespace
          content.insert(0, '= ') unless content[0] == '='
        end

        new_line = (leading_whitespace or '') + haml_markup + content
        new_line.gsub(match.gsub(/\s+$/, ''), i18n_string(translation_key, match))
      end
    end

  end
end