webhippie/po_to_json

View on GitHub
lib/po_to_json.rb

Summary

Maintainability
B
5 hrs
Test Coverage
#
# Copyright (c) 2012-2015 Dropmysite.com <https://dropmyemail.com>
# Copyright (c) 2015 Webhippie <http://www.webhippie.de>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

gem "json", version: ">= 1.6.0"
require "json"

class PoToJson
  autoload :Version, File.expand_path("po_to_json/version", __dir__)

  attr_accessor :files, :glue, :options

  def initialize(files, glue = "|")
    @files = files
    @glue = glue
  end

  def generate_for_jed(language, overwrite = {})
    @options = parse_options(overwrite.merge(language: language))
    @parsed ||= inject_meta(parse_document)

    generated = build_json_for(build_jed_for(@parsed))

    [
      @options[:variable_locale_scope] ? "var" : "",
      "#{@options[:variable]} = #{@options[:variable]} || {};",
      "#{@options[:variable]}['#{@options[:language]}'] = #{generated};"
    ].join(" ")
  end

  def generate_for_json(language, overwrite = {})
    @options = parse_options(overwrite.merge(language: language))
    @parsed ||= inject_meta(parse_document)

    generated = build_json_for(build_json_for(@parsed))

    raise "Not implemented yet, current value is #{generated}!"
  end

  def parse_document
    reset_buffer
    reset_result

    File.foreach(files) do |line|
      matches_values_for(line.chomp)
    end

    flush_buffer
    parse_header

    values
  end

  def flush_buffer
    return unless buffer[:msgid]

    build_trans
    assign_trans

    reset_buffer
  end

  def parse_header
    return if reject_header

    values[""][0].split("\\n").each do |line|
      next if line.empty?

      build_header_for(line)
    end

    values[""] = headers
  end

  def reject_header
    if values[""].nil? || values[""][0].nil?
      values[""] = {}
      true
    else
      false
    end
  end

  protected

  def trans
    @trans ||= []
  end

  def errors
    @errors ||= []
  end

  def values
    @values ||= {}
  end

  def buffer
    @buffer ||= {}
  end

  def headers
    @headers ||= {}
  end

  def lastkey
    @lastkey ||= ""
  end

  def reset_result
    @values = {}
    @errors = []
  end

  def reset_buffer
    @buffer = {}
    @trans = []
    @lastkey = ""
  end

  def detect_ctxt
    msgctxt = buffer[:msgctxt]
    msgid = buffer[:msgid]

    if msgctxt && !msgctxt.empty?
      [msgctxt, glue, msgid].join("")
    else
      msgid
    end
  end

  def detect_plural
    plural = buffer[:msgid_plural]
    plural if plural && !plural.empty?
  end

  def build_trans
    buffer.each do |key, string|
      trans[$1.to_i] = string if key.to_s =~ /^msgstr_(\d+)/
    end

    # trans.unshift(detect_plural) if detect_plural
  end

  def assign_trans
    values[detect_ctxt] = trans unless trans.empty?
  end

  def push_buffer(value, key = nil)
    value = $1 if value =~ /^"(.*)"/
    value.gsub!(/\\"/, "\"")

    if key.nil?
      buffer[lastkey] = [
        buffer[lastkey],
        value
      ].join("")
    else
      buffer[key] = value
      @lastkey = key
    end
  end

  def parse_options(options)
    defaults = {
      pretty: false,
      domain: "app",
      variable: "locales",
      variable_locale_scope: true
    }

    defaults.merge(options)
  end

  def inject_meta(hash)
    hash[""]["lang"] ||= options[:language]
    hash[""]["domain"] ||= options[:domain]
    hash[""]["plural_forms"] ||= hash[""]["Plural-Forms"]

    hash
  end

  def build_header_for(line)
    if line =~ /(.*?):(.*)/
      key = $1
      value = $2

      if headers.key? key
        errors.push "Duplicate header: #{line}"
      elsif key =~ /#-#-#-#-#/
        errors.push "Marked header: #{line}"
      else
        headers[key] = value.strip
      end
    else
      errors.push "Malformed header: #{line}"
    end
  end

  def build_json_for(hash)
    if options[:pretty]
      JSON.pretty_generate(hash)
    else
      JSON.generate(hash)
    end
  end

  def build_jed_for(hash)
    {
      domain: options[:domain],
      locale_data: {
        options[:domain] => hash
      }
    }
  end

  def matches_values_for(line)
    return if generic_rejects? line
    return if generic_detects? line

    return if iterate_detects_for(line)

    errors.push "Strange line #{line}"
  end

  def iterate_detects_for(line)
    specific_detects.each do |detect|
      match = line.match(detect[:regex])

      if match
        if detect[:index]
          push_buffer(match[detect[:index]], detect[:key].call(match))
        else
          push_buffer(line)
        end

        return true
      end
    end

    false
  end

  def generic_rejects?(line)
    if line.match(/^$/) || line.match(/^(#[^~]|\#$)/)
      flush_buffer && true
    else
      false
    end
  end

  def generic_detects?(line)
    match = line.match(/^(?:#~ )?msgctxt\s+(.*)/)

    if match
      push_buffer(
        match[1],
        :msgctxt
      )

      return true
    end

    false
  end

  def specific_detects
    [{
      regex: /^(?:#~ )?msgctxt\s+(.*)/,
      index: 1,
      key: proc { :msgctxt }
    }, {
      regex: /^(?:#~ )?msgid\s+(.*)/,
      index: 1,
      key: proc { :msgid }
    }, {
      regex: /^(?:#~ )?msgid_plural\s+(.*)/,
      index: 1,
      key: proc { :msgid_plural }
    }, {
      regex: /^(?:#~ )?msgstr\s+(.*)/,
      index: 1,
      key: proc { :msgstr_0 }
    }, {
      regex: /^(?:#~ )?msgstr\[0\]\s+(.*)/,
      index: 1,
      key: proc { :msgstr_0 }
    }, {
      regex: /^(?:#~ )?msgstr\[(\d+)\]\s+(.*)/,
      index: 2,
      key: proc { |m| "msgstr_#{m[1]}".to_sym }
    }, {
      regex: /^(?:#~ )?"/,
      index: nil
    }]
  end
end