i18n-tasks 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +125 -38
- data/config/locales/en.yml +14 -5
- data/config/locales/ru.yml +14 -5
- data/i18n-tasks.gemspec +4 -2
- data/lib/i18n/tasks/command/commands/data.rb +14 -0
- data/lib/i18n/tasks/command/commands/missing.rb +3 -1
- data/lib/i18n/tasks/command/option_parsers/enum.rb +4 -3
- data/lib/i18n/tasks/command/options/common.rb +1 -1
- data/lib/i18n/tasks/command/options/locales.rb +12 -3
- data/lib/i18n/tasks/configuration.rb +8 -2
- data/lib/i18n/tasks/data/file_system_base.rb +5 -0
- data/lib/i18n/tasks/data/router/isolating_router.rb +146 -0
- data/lib/i18n/tasks/data/tree/siblings.rb +2 -2
- data/lib/i18n/tasks/interpolations.rb +1 -1
- data/lib/i18n/tasks/key_pattern_matching.rb +4 -4
- data/lib/i18n/tasks/reports/terminal.rb +6 -0
- data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +26 -0
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +1 -1
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +55 -25
- data/lib/i18n/tasks/scanners/local_ruby_parser.rb +2 -2
- data/lib/i18n/tasks/scanners/pattern_scanner.rb +1 -1
- data/lib/i18n/tasks/scanners/prism_scanner.rb +83 -0
- data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +41 -0
- data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +334 -0
- data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +273 -0
- data/lib/i18n/tasks/scanners/relative_keys.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +5 -4
- data/lib/i18n/tasks/scanners/ruby_key_literals.rb +1 -1
- data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +27 -0
- data/lib/i18n/tasks/split_key.rb +30 -47
- data/lib/i18n/tasks/translation.rb +4 -1
- data/lib/i18n/tasks/translators/base_translator.rb +11 -1
- data/lib/i18n/tasks/translators/deepl_translator.rb +32 -1
- data/lib/i18n/tasks/translators/google_translator.rb +35 -12
- data/lib/i18n/tasks/translators/openai_translator.rb +55 -23
- data/lib/i18n/tasks/translators/watsonx_translator.rb +155 -0
- data/lib/i18n/tasks/translators/yandex_translator.rb +5 -1
- data/lib/i18n/tasks/used_keys.rb +1 -0
- data/lib/i18n/tasks/version.rb +1 -1
- data/lib/i18n/tasks.rb +1 -0
- data/templates/config/i18n-tasks.yml +26 -3
- metadata +33 -26
- data/lib/i18n/tasks/scanners/erb_ast_processor.rb +0 -74
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/key_pattern_matching'
|
4
|
+
require 'i18n/tasks/data/tree/node'
|
5
|
+
|
6
|
+
module I18n::Tasks
|
7
|
+
module Data::Router
|
8
|
+
# Route based on source file path
|
9
|
+
class IsolatingRouter
|
10
|
+
include ::I18n::Tasks::KeyPatternMatching
|
11
|
+
|
12
|
+
attr_reader :config_read_patterns, :base_locale
|
13
|
+
|
14
|
+
def initialize(_adapter, data_config)
|
15
|
+
@base_locale = data_config[:base_locale]
|
16
|
+
@config_read_patterns = Array.wrap(data_config[:read])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Route keys to destinations
|
20
|
+
# @param forest [I18n::Tasks::Data::Tree::Siblings] forest roots are locales.
|
21
|
+
# @yieldparam [String] dest_path
|
22
|
+
# @yieldparam [I18n::Tasks::Data::Tree::Siblings] tree_slice
|
23
|
+
# @return [Hash] mapping of destination => [ [key, value], ... ]
|
24
|
+
def route(locale, forest, &block)
|
25
|
+
return to_enum(:route, locale, forest) unless block
|
26
|
+
|
27
|
+
locale = locale.to_s
|
28
|
+
out = {}
|
29
|
+
|
30
|
+
forest.keys do |key_namespaced_with_source_path, _node|
|
31
|
+
source_path, key = key_namespaced_with_source_path.match(/\A<([^>]*)>\.(.*)/).captures
|
32
|
+
target_path = alternate_path_for(source_path, locale)
|
33
|
+
next unless source_path && key && target_path
|
34
|
+
|
35
|
+
(out[target_path] ||= Set.new) << "#{locale}.#{key}"
|
36
|
+
end
|
37
|
+
|
38
|
+
out.each do |target_path, keys|
|
39
|
+
file_namespace_subtree = I18n::Tasks::Data::Tree::Siblings.new(
|
40
|
+
nodes: forest.get("#{locale}.<#{alternate_path_for(target_path, base_locale)}>")
|
41
|
+
)
|
42
|
+
file_namespace_subtree.set_root_key!(locale)
|
43
|
+
|
44
|
+
block.yield(
|
45
|
+
target_path,
|
46
|
+
file_namespace_subtree.select_keys { |key, _| keys.include?(key) }
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def alternate_path_for(source_path, locale)
|
52
|
+
source_path = source_path.dup
|
53
|
+
|
54
|
+
config_read_patterns.each do |pattern|
|
55
|
+
regexp = Glob.new(format(pattern, locale: '(*)')).to_regexp
|
56
|
+
next unless source_path.match?(regexp)
|
57
|
+
|
58
|
+
source_path.match(regexp) do |match_data|
|
59
|
+
(1..match_data.size - 1).reverse_each do |capture_index|
|
60
|
+
capture_begin, capture_end = match_data.offset(capture_index)
|
61
|
+
source_path.slice!(Range.new(capture_begin, capture_end, true))
|
62
|
+
source_path.insert(capture_begin, locale.to_s)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
return source_path
|
67
|
+
end
|
68
|
+
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# based on https://github.com/alexch/rerun/blob/36f2d237985b670752abbe4a7f6814893cdde96f/lib/rerun/glob.rb
|
73
|
+
class Glob
|
74
|
+
NO_LEADING_DOT = '(?=[^\.])'
|
75
|
+
START_OF_FILENAME = '(?:\A|\/)'
|
76
|
+
END_OF_STRING = '\z'
|
77
|
+
|
78
|
+
def initialize(pattern)
|
79
|
+
@pattern = pattern
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_regexp_string # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
83
|
+
chars = smoosh(@pattern.chars)
|
84
|
+
|
85
|
+
curlies = 0
|
86
|
+
escaping = false
|
87
|
+
|
88
|
+
string = chars.map do |char|
|
89
|
+
if escaping
|
90
|
+
escaping = false
|
91
|
+
next char
|
92
|
+
end
|
93
|
+
|
94
|
+
case char
|
95
|
+
when '**' then '(?:[^/]+/)*'
|
96
|
+
when '*' then '.*'
|
97
|
+
when '?' then '.'
|
98
|
+
when '.' then '\.'
|
99
|
+
when '{'
|
100
|
+
curlies += 1
|
101
|
+
'('
|
102
|
+
when '}'
|
103
|
+
if curlies.positive?
|
104
|
+
curlies -= 1
|
105
|
+
')'
|
106
|
+
else
|
107
|
+
char
|
108
|
+
end
|
109
|
+
when ','
|
110
|
+
if curlies.positive?
|
111
|
+
'|'
|
112
|
+
else
|
113
|
+
char
|
114
|
+
end
|
115
|
+
when '\\'
|
116
|
+
escaping = true
|
117
|
+
'\\'
|
118
|
+
else char
|
119
|
+
end
|
120
|
+
end.join
|
121
|
+
|
122
|
+
START_OF_FILENAME + string + END_OF_STRING
|
123
|
+
end
|
124
|
+
|
125
|
+
def to_regexp
|
126
|
+
Regexp.new(to_regexp_string)
|
127
|
+
end
|
128
|
+
|
129
|
+
def smoosh(chars)
|
130
|
+
out = []
|
131
|
+
until chars.empty?
|
132
|
+
char = chars.shift
|
133
|
+
if char == '*' && chars.first == '*'
|
134
|
+
chars.shift
|
135
|
+
chars.shift if chars.first == '/'
|
136
|
+
out.push('**')
|
137
|
+
else
|
138
|
+
out.push(char)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
out
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -36,7 +36,7 @@ module I18n::Tasks::Data::Tree
|
|
36
36
|
# @param to_pattern [Regexp]
|
37
37
|
# @param root [Boolean]
|
38
38
|
# @return {old key => new key}
|
39
|
-
def mv_key!(from_pattern, to_pattern, root: false) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
39
|
+
def mv_key!(from_pattern, to_pattern, root: false, retain: false) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
40
40
|
moved_forest = Siblings.new
|
41
41
|
moved_nodes = []
|
42
42
|
old_key_to_new_key = {}
|
@@ -69,7 +69,7 @@ module I18n::Tasks::Data::Tree
|
|
69
69
|
node.value = new_target.to_sym
|
70
70
|
end
|
71
71
|
end
|
72
|
-
remove_nodes_and_emptied_ancestors!
|
72
|
+
remove_nodes_and_emptied_ancestors!(moved_nodes) unless retain
|
73
73
|
merge! moved_forest
|
74
74
|
old_key_to_new_key
|
75
75
|
end
|
@@ -5,7 +5,7 @@ module I18n::Tasks
|
|
5
5
|
class << self
|
6
6
|
attr_accessor :variable_regex
|
7
7
|
end
|
8
|
-
@variable_regex =
|
8
|
+
@variable_regex = /(?<!%)%{[^}]+}/.freeze
|
9
9
|
|
10
10
|
def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disable Metrics/AbcSize
|
11
11
|
locales ||= self.locales
|
@@ -31,10 +31,10 @@ module I18n::Tasks::KeyPatternMatching
|
|
31
31
|
|
32
32
|
def key_pattern_re_body(key_pattern)
|
33
33
|
key_pattern
|
34
|
-
.gsub(
|
35
|
-
.gsub(
|
36
|
-
.gsub(
|
37
|
-
.gsub(
|
34
|
+
.gsub('.', '\.')
|
35
|
+
.gsub('*:', '[^.]+?')
|
36
|
+
.gsub('*', '.*')
|
37
|
+
.gsub(':', '(?<=^|\.)[^.]+?(?=\.|$)')
|
38
38
|
.gsub(/\{(.*?)}/) { "(#{Regexp.last_match(1).strip.gsub(/\s*,\s*/, '|')})" }
|
39
39
|
end
|
40
40
|
end
|
@@ -93,6 +93,12 @@ module I18n
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
def cp_results(results)
|
97
|
+
results.each do |(from, to)|
|
98
|
+
print_info "#{Rainbow(from).cyan} #{Rainbow('+').yellow.bright} #{Rainbow(to).green}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
96
102
|
def check_normalized_results(non_normalized)
|
97
103
|
if non_normalized.empty?
|
98
104
|
print_success 'All data is normalized'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks/scanners/ast_matchers/base_matcher'
|
4
|
+
require 'i18n/tasks/scanners/results/occurrence'
|
5
|
+
|
6
|
+
module I18n::Tasks::Scanners::AstMatchers
|
7
|
+
class DefaultI18nSubjectMatcher < BaseMatcher
|
8
|
+
def convert_to_key_occurrences(send_node, method_name, location: send_node.loc)
|
9
|
+
children = Array(send_node&.children)
|
10
|
+
return unless children[1] == :default_i18n_subject
|
11
|
+
|
12
|
+
key = @scanner.absolute_key(
|
13
|
+
'.subject',
|
14
|
+
location.expression.source_buffer.name,
|
15
|
+
calling_method: method_name
|
16
|
+
)
|
17
|
+
[
|
18
|
+
key,
|
19
|
+
I18n::Tasks::Scanners::Results::Occurrence.from_range(
|
20
|
+
raw_key: key,
|
21
|
+
range: location.expression
|
22
|
+
)
|
23
|
+
]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -16,7 +16,7 @@ module I18n::Tasks::Scanners::AstMatchers
|
|
16
16
|
receiver = children[0]
|
17
17
|
method_name = children[1]
|
18
18
|
|
19
|
-
return unless method_name == :human_attribute_name && receiver
|
19
|
+
return unless method_name == :human_attribute_name && receiver&.type == :const
|
20
20
|
|
21
21
|
value = children[2]
|
22
22
|
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'i18n/tasks/scanners/ruby_ast_scanner'
|
4
|
-
require 'i18n/tasks/scanners/
|
5
|
-
require 'better_html/errors'
|
6
|
-
require 'better_html/parser'
|
4
|
+
require 'i18n/tasks/scanners/local_ruby_parser'
|
7
5
|
|
8
6
|
module I18n::Tasks::Scanners
|
9
7
|
# Scan for I18n.translate calls in ERB-file better-html and ASTs
|
10
8
|
class ErbAstScanner < RubyAstScanner
|
9
|
+
DEFAULT_REGEXP = /<%(={1,2}|-|\#|%)?(.*?)([-=])?%>/m.freeze
|
10
|
+
|
11
11
|
def initialize(**args)
|
12
12
|
super(**args)
|
13
|
-
@
|
13
|
+
@ruby_parser = LocalRubyParser.new(ignore_blocks: true)
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
@@ -20,29 +20,59 @@ module I18n::Tasks::Scanners
|
|
20
20
|
# @param path Path to file to parse
|
21
21
|
# @return [{Parser::AST::Node}, [Parser::Source::Comment]]
|
22
22
|
def path_to_ast_and_comments(path)
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
comments = []
|
24
|
+
buffer = make_buffer(path)
|
25
|
+
|
26
|
+
children = []
|
27
|
+
buffer
|
28
|
+
.source
|
29
|
+
.scan(DEFAULT_REGEXP) do |indicator, code, tailch, _rspace|
|
30
|
+
match = Regexp.last_match
|
31
|
+
character = indicator ? indicator[0] : nil
|
32
|
+
|
33
|
+
start = match.begin(0) + 2 + (character&.size || 0)
|
34
|
+
stop = match.end(0) - 2 - (tailch&.size || 0)
|
35
|
+
|
36
|
+
case character
|
37
|
+
when '=', nil, '-'
|
38
|
+
parsed, parsed_comments = handle_code(buffer, code, start, stop)
|
39
|
+
comments.concat(parsed_comments)
|
40
|
+
children << parsed unless parsed.nil?
|
41
|
+
when '#', '#-'
|
42
|
+
comments << handle_comment(buffer, start, stop)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
[root_node(children, buffer), comments]
|
26
47
|
end
|
27
48
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
49
|
+
def handle_code(buffer, code, start, stop)
|
50
|
+
range = ::Parser::Source::Range.new(buffer, start, stop)
|
51
|
+
location =
|
52
|
+
Parser::Source::Map::Definition.new(
|
53
|
+
range.begin,
|
54
|
+
range.begin,
|
55
|
+
range.begin,
|
56
|
+
range.end
|
57
|
+
)
|
58
|
+
@ruby_parser.parse(code, location: location)
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_comment(buffer, start, stop)
|
62
|
+
range = ::Parser::Source::Range.new(buffer, start, stop)
|
63
|
+
::Parser::Source::Comment.new(range)
|
64
|
+
end
|
65
|
+
|
66
|
+
def root_node(children, buffer)
|
67
|
+
range = ::Parser::Source::Range.new(buffer, 0, buffer.source.size)
|
68
|
+
location =
|
69
|
+
Parser::Source::Map::Definition.new(
|
70
|
+
range.begin,
|
71
|
+
range.begin,
|
72
|
+
range.begin,
|
73
|
+
range.end
|
74
|
+
)
|
75
|
+
::Parser::AST::Node.new(:erb, children, location: location)
|
46
76
|
end
|
47
77
|
end
|
48
78
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'i18n/tasks/scanners/ruby_parser_factory'
|
4
4
|
|
5
5
|
module I18n::Tasks::Scanners
|
6
6
|
class LocalRubyParser
|
@@ -9,7 +9,7 @@ module I18n::Tasks::Scanners
|
|
9
9
|
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/.freeze
|
10
10
|
|
11
11
|
def initialize(ignore_blocks: false)
|
12
|
-
@parser =
|
12
|
+
@parser = RubyParserFactory.create_parser
|
13
13
|
@ignore_blocks = ignore_blocks
|
14
14
|
end
|
15
15
|
|
@@ -12,7 +12,7 @@ module I18n::Tasks::Scanners
|
|
12
12
|
include OccurrenceFromPosition
|
13
13
|
include RubyKeyLiterals
|
14
14
|
|
15
|
-
TRANSLATE_CALL_RE = /(?<=^|[^\
|
15
|
+
TRANSLATE_CALL_RE = /(?<=^|[^\p{L}_'\-.]|[^\p{L}'-]I18n\.|I18n\.)t(?:!|ranslate!?)?/.freeze
|
16
16
|
IGNORE_LINES = {
|
17
17
|
'coffee' => /^\s*#(?!\si18n-tasks-use)/,
|
18
18
|
'erb' => /^\s*<%\s*#(?!\si18n-tasks-use)/,
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'file_scanner'
|
4
|
+
require_relative 'ruby_ast_scanner'
|
5
|
+
|
6
|
+
module I18n::Tasks::Scanners
|
7
|
+
class PrismScanner < FileScanner
|
8
|
+
MAGIC_COMMENT_SKIP_PRISM = 'i18n-tasks-skip-prism'
|
9
|
+
|
10
|
+
def initialize(**args)
|
11
|
+
unless VISITOR
|
12
|
+
warn(
|
13
|
+
'Please make sure `prism` is available to use this feature. Fallback to Ruby AST Scanner.'
|
14
|
+
)
|
15
|
+
end
|
16
|
+
super
|
17
|
+
|
18
|
+
@fallback = RubyAstScanner.new(**args)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
# Extract all occurrences of translate calls from the file at the given path.
|
24
|
+
#
|
25
|
+
# @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file
|
26
|
+
def scan_file(path)
|
27
|
+
return @fallback.send(:scan_file, path) if VISITOR.nil?
|
28
|
+
|
29
|
+
process_results(path, PARSER.parse_file(path))
|
30
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
31
|
+
raise(
|
32
|
+
::I18n::Tasks::CommandError.new(
|
33
|
+
e,
|
34
|
+
"Error scanning #{path}: #{e.message}"
|
35
|
+
)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Need to have method that can be overridden to be able to test it
|
40
|
+
def process_results(path, parse_results)
|
41
|
+
parsed = parse_results.value
|
42
|
+
comments = parse_results.comments
|
43
|
+
|
44
|
+
return @fallback.send(:scan_file, path) if skip_prism_comment?(comments)
|
45
|
+
|
46
|
+
rails = if config[:prism_visitor].blank?
|
47
|
+
true
|
48
|
+
else
|
49
|
+
config[:prism_visitor] != 'ruby'
|
50
|
+
end
|
51
|
+
|
52
|
+
visitor = VISITOR.new(comments: comments, rails: rails)
|
53
|
+
parsed.accept(visitor)
|
54
|
+
|
55
|
+
occurrences = []
|
56
|
+
visitor.process.each do |translation_call|
|
57
|
+
result = translation_call.occurrences(path)
|
58
|
+
occurrences << result if result
|
59
|
+
end
|
60
|
+
|
61
|
+
occurrences
|
62
|
+
end
|
63
|
+
|
64
|
+
def skip_prism_comment?(comments)
|
65
|
+
comments.any? do |comment|
|
66
|
+
content =
|
67
|
+
comment.respond_to?(:slice) ? comment.slice : comment.location.slice
|
68
|
+
content.include?(MAGIC_COMMENT_SKIP_PRISM)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# This block handles adding a fallback if the `prism` gem is not available.
|
73
|
+
begin
|
74
|
+
require 'prism'
|
75
|
+
require_relative 'prism_scanners/visitor'
|
76
|
+
PARSER = Prism
|
77
|
+
VISITOR = I18n::Tasks::Scanners::PrismScanners::Visitor
|
78
|
+
rescue LoadError
|
79
|
+
PARSER = nil
|
80
|
+
VISITOR = nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prism/visitor'
|
4
|
+
|
5
|
+
# This class is used to parse the arguments to e.g. a Prism::CallNode and return the values we need
|
6
|
+
# for turning them into translations and occurrences.
|
7
|
+
# Used in the PrismScanners::Visitor class.
|
8
|
+
module I18n::Tasks::Scanners::PrismScanners
|
9
|
+
class ArgumentsVisitor < Prism::Visitor
|
10
|
+
def visit_keyword_hash_node(node)
|
11
|
+
node.child_nodes.each_with_object({}) do |child, hash|
|
12
|
+
hash[visit(child.key)] = visit(child.value)
|
13
|
+
hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_symbol_node(node)
|
18
|
+
node.value
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit_string_node(node)
|
22
|
+
node.content
|
23
|
+
end
|
24
|
+
|
25
|
+
def visit_array_node(node)
|
26
|
+
node.child_nodes.map { |child| visit(child) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_arguments_node(node)
|
30
|
+
node.child_nodes.map { |child| visit(child) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_integer_node(node)
|
34
|
+
node.value
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_lambda_node(node)
|
38
|
+
node
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|