Class: ZLocalize::SourceProcessor

Inherits:
Object
  • Object
show all
Defined in:
lib/zlocalize/source_processor.rb

Constant Summary collapse

BARE_TRANSLATE_METHODS =

Methods recognized as translation calls when called without an explicit receiver (bare calls), mapped to whether they yield plural entries.

{ '_' => false, 'n_' => true }.freeze
QUALIFIED_TRANSLATE_METHODS =

Methods recognized as translation calls when called on the ZLocalize constant, mapped to whether they yield plural entries.

{ 'translate' => false, 'pluralize' => true }.freeze

Instance Method Summary collapse

Constructor Details

#initialize(filename, root, is_erb = false) ⇒ SourceProcessor

Returns a new instance of SourceProcessor.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/zlocalize/source_processor.rb', line 23

def initialize(filename, root, is_erb = false)
  @in_hash = 0
  @translate_calls = []
  @root = File.join(File.expand_path(root).downcase,'/') # add a trailing /
  @filename = File.expand_path(filename).downcase
  @relative_filename = @filename.gsub(@root,'')
  @line_offset = 0
  content = File.open(filename, "r") { |f| f.read }
  if is_erb
    content = ActionView::Template::Handlers::ERB::Erubi.new(content, escape: true, trim: true).src
    # Erubi compiles ERB to bare Ruby with top-level `yield`, which Prism
    # rejects (yield is only valid inside a method). Wrap in a method and
    # track the offset so reported line numbers still match the template.
    content = "def __zlocalize_erb__\n#{content}\nend"
    @line_offset = 1
  end
  begin
    process(content)
  rescue ArgumentError => ae
    raise ArgumentError.new("In #{filename} #{ae.message}")
  end
end

Instance Method Details

#format_parse_errors(parse_result) ⇒ Object



106
107
108
109
110
# File 'lib/zlocalize/source_processor.rb', line 106

def format_parse_errors(parse_result)
  parse_result.errors.map do |error|
    "line #{error.location.start_line}: #{error.message}"
  end.join("\n")
end

#get_string_array_node_value(node) ⇒ Object



95
96
97
98
99
100
# File 'lib/zlocalize/source_processor.rb', line 95

def get_string_array_node_value(node)
  unless node.is_a?(Prism::ArrayNode)
    raise ArgumentError.new("On line #{node.location.start_line} at column #{node.location.start_column+1} : Array expected but got: #{node.inspect}")
  end
  node.elements.map { |element| get_string_node_value(element) }
end

#get_string_node_value(node) ⇒ Object



88
89
90
91
92
93
# File 'lib/zlocalize/source_processor.rb', line 88

def get_string_node_value(node)
  unless string_node?(node)
    raise ArgumentError.new("On line #{node.location.start_line} at column #{node.location.start_column+1} : String Expected but got: #{node.inspect}")
  end
  return node.unescaped
end

#make_translation_entry(h) ⇒ Object



126
127
128
129
130
# File 'lib/zlocalize/source_processor.rb', line 126

def make_translation_entry(h)
  TranslationEntry.new('plural'     => h[:name] == 'n_' || h[:name] == 'pluralize',
                       'source'     => h[:parameter],
                       'references' => [ "#{@relative_filename}:#{h[:line_no]}" ])
end

#process(content) ⇒ Object

Raises:

  • (ArgumentError)


46
47
48
49
50
# File 'lib/zlocalize/source_processor.rb', line 46

def process(content)
  parse_result = Prism.parse(content)
  raise ArgumentError.new("Syntax error: #{format_parse_errors(parse_result)}") unless parse_result.success?
  walk(parse_result.value)
end

#record_call(node, plural) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/zlocalize/source_processor.rb', line 72

def record_call(node, plural)
  argument_nodes = node.arguments.arguments
  source = plural ? get_string_array_node_value(argument_nodes[0]) : get_string_node_value(argument_nodes[0])
  @translate_calls << { name: node.name.to_s,
                        line_no: node.message_loc.start_line - @line_offset,
                        char_no: node.message_loc.start_column + 1,
                        parameter: source }
  # keep harvesting inside any nested arguments (e.g. options hashes containing
  # further translation calls)
  argument_nodes[1..].each { |child| walk(child) } if argument_nodes.size > 1
end

#string_node?(node) ⇒ Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/zlocalize/source_processor.rb', line 102

def string_node?(node)
  node.is_a?(Prism::StringNode)
end

#translation_entriesObject

return a Hash of all translation entries we collected



113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/zlocalize/source_processor.rb', line 113

def translation_entries
  entries = {}
  @translate_calls.each do |c|
    e = make_translation_entry(c)
    if entries[e.source]
      entries[e.source].references += e.references
    else
      entries[e.source] = e
    end
  end
  entries
end

#walk(node) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/zlocalize/source_processor.rb', line 52

def walk(node)
  return unless node.is_a?(Prism::Node)

  if node.is_a?(Prism::CallNode)
    if node.receiver.nil?
      plural = BARE_TRANSLATE_METHODS[node.name.to_s]
      if !plural.nil? && node.arguments && node.arguments.arguments.size >= 1
        record_call(node, plural) and return
      end
    elsif zlocalize_const?(node.receiver)
      plural = QUALIFIED_TRANSLATE_METHODS[node.name.to_s]
      if !plural.nil? && node.arguments && node.arguments.arguments.size >= 1
        record_call(node, plural) and return
      end
    end
  end

  node.compact_child_nodes.each { |child| walk(child) }
end

#zlocalize_const?(node) ⇒ Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/zlocalize/source_processor.rb', line 84

def zlocalize_const?(node)
  node.is_a?(Prism::ConstantReadNode) && node.name.to_s == 'ZLocalize'
end