i18n_flow 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +45 -0
- data/LICENSE +22 -0
- data/README.md +103 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/rules.md +316 -0
- data/doc/tags.md +488 -0
- data/example/example.en.yml +14 -0
- data/example/example.ja.yml +9 -0
- data/exe/i18n_flow +11 -0
- data/i18n_flow.gemspec +28 -0
- data/i18n_flow.yml +8 -0
- data/lib/i18n_flow/cli/color.rb +18 -0
- data/lib/i18n_flow/cli/command_base.rb +33 -0
- data/lib/i18n_flow/cli/copy_command.rb +69 -0
- data/lib/i18n_flow/cli/help_command.rb +29 -0
- data/lib/i18n_flow/cli/lint_command/ascii.erb +45 -0
- data/lib/i18n_flow/cli/lint_command/ascii_renderer.rb +58 -0
- data/lib/i18n_flow/cli/lint_command/markdown.erb +49 -0
- data/lib/i18n_flow/cli/lint_command/markdown_renderer.rb +55 -0
- data/lib/i18n_flow/cli/lint_command.rb +55 -0
- data/lib/i18n_flow/cli/read_config_command.rb +20 -0
- data/lib/i18n_flow/cli/search_command/default.erb +11 -0
- data/lib/i18n_flow/cli/search_command/default_renderer.rb +67 -0
- data/lib/i18n_flow/cli/search_command/oneline.erb +5 -0
- data/lib/i18n_flow/cli/search_command/oneline_renderer.rb +39 -0
- data/lib/i18n_flow/cli/search_command.rb +59 -0
- data/lib/i18n_flow/cli/split_command.rb +20 -0
- data/lib/i18n_flow/cli/version_command.rb +9 -0
- data/lib/i18n_flow/cli.rb +42 -0
- data/lib/i18n_flow/configuration.rb +205 -0
- data/lib/i18n_flow/parser.rb +34 -0
- data/lib/i18n_flow/repository.rb +39 -0
- data/lib/i18n_flow/search.rb +176 -0
- data/lib/i18n_flow/splitter/merger.rb +60 -0
- data/lib/i18n_flow/splitter/strategy.rb +66 -0
- data/lib/i18n_flow/splitter.rb +5 -0
- data/lib/i18n_flow/util.rb +57 -0
- data/lib/i18n_flow/validator/errors.rb +99 -0
- data/lib/i18n_flow/validator/file_scope.rb +58 -0
- data/lib/i18n_flow/validator/multiplexer.rb +58 -0
- data/lib/i18n_flow/validator/symmetry.rb +154 -0
- data/lib/i18n_flow/validator.rb +4 -0
- data/lib/i18n_flow/version.rb +7 -0
- data/lib/i18n_flow/yaml_ast_proxy/mapping.rb +72 -0
- data/lib/i18n_flow/yaml_ast_proxy/node.rb +128 -0
- data/lib/i18n_flow/yaml_ast_proxy/node_meta_data.rb +86 -0
- data/lib/i18n_flow/yaml_ast_proxy/sequence.rb +29 -0
- data/lib/i18n_flow/yaml_ast_proxy.rb +57 -0
- data/lib/i18n_flow.rb +15 -0
- data/spec/lib/i18n_flow/cli/command_base_spec.rb +46 -0
- data/spec/lib/i18n_flow/cli/help_command_spec.rb +13 -0
- data/spec/lib/i18n_flow/cli/version_command_spec.rb +13 -0
- data/spec/lib/i18n_flow/configuration_spec.rb +334 -0
- data/spec/lib/i18n_flow/repository_spec.rb +40 -0
- data/spec/lib/i18n_flow/splitter/merger_spec.rb +149 -0
- data/spec/lib/i18n_flow/util_spec.rb +194 -0
- data/spec/lib/i18n_flow/validator/file_scope_spec.rb +74 -0
- data/spec/lib/i18n_flow/validator/multiplexer_spec.rb +68 -0
- data/spec/lib/i18n_flow/validator/symmetry_spec.rb +511 -0
- data/spec/lib/i18n_flow/yaml_ast_proxy/node_spec.rb +151 -0
- data/spec/lib/i18n_flow_spec.rb +21 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/repository_examples.rb +60 -0
- data/spec/support/util_macro.rb +14 -0
- metadata +214 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
module I18nFlow::Validator
|
2
|
+
class Error
|
3
|
+
attr_reader :key
|
4
|
+
attr_reader :file
|
5
|
+
attr_reader :line
|
6
|
+
|
7
|
+
def initialize(key)
|
8
|
+
@key = key
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
return false unless other.is_a?(self.class)
|
13
|
+
data == other.data
|
14
|
+
end
|
15
|
+
|
16
|
+
def data
|
17
|
+
[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def single?
|
21
|
+
!!@single
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_location(node)
|
25
|
+
@file = node.file_path
|
26
|
+
@line = node.start_line
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class InvalidTypeError < Error
|
32
|
+
def initialize(key, single: false)
|
33
|
+
super(key)
|
34
|
+
@single = single
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class MissingKeyError < Error
|
39
|
+
def initialize(key, single: false)
|
40
|
+
super(key)
|
41
|
+
@single = single
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class ExtraKeyError < Error
|
46
|
+
def initialize(key, single: false)
|
47
|
+
super(key)
|
48
|
+
@single = single
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class InvalidTodoError < Error
|
53
|
+
end
|
54
|
+
|
55
|
+
class TodoContentError < Error
|
56
|
+
attr_reader :expect
|
57
|
+
attr_reader :actual
|
58
|
+
|
59
|
+
def initialize(key, expect:, actual:)
|
60
|
+
super(key)
|
61
|
+
@expect = expect
|
62
|
+
@actual = actual
|
63
|
+
end
|
64
|
+
|
65
|
+
def data
|
66
|
+
super + [expect, actual]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class InvalidLocaleError < Error
|
71
|
+
attr_reader :expect
|
72
|
+
attr_reader :actual
|
73
|
+
|
74
|
+
def initialize(key, expect:, actual:)
|
75
|
+
super(key)
|
76
|
+
@expect = expect
|
77
|
+
@actual = actual
|
78
|
+
end
|
79
|
+
|
80
|
+
def data
|
81
|
+
super + [expect, actual]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class AsymmetricArgsError < Error
|
86
|
+
attr_reader :expect
|
87
|
+
attr_reader :actual
|
88
|
+
|
89
|
+
def initialize(key, expect:, actual:)
|
90
|
+
super(key)
|
91
|
+
@expect = expect
|
92
|
+
@actual = actual
|
93
|
+
end
|
94
|
+
|
95
|
+
def data
|
96
|
+
super + [expect, actual]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
require_relative '../util'
|
3
|
+
|
4
|
+
module I18nFlow::Validator
|
5
|
+
class FileScope
|
6
|
+
attr_reader :ast
|
7
|
+
attr_reader :filepath
|
8
|
+
|
9
|
+
def initialize(ast, filepath:)
|
10
|
+
@ast = ast
|
11
|
+
@filepath = filepath
|
12
|
+
end
|
13
|
+
|
14
|
+
def filepath_scopes
|
15
|
+
@filepath_scopes ||= I18nFlow::Util.filepath_to_scope(filepath)
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate!
|
19
|
+
@errors = nil
|
20
|
+
validate_scope(ast, scopes: filepath_scopes)
|
21
|
+
end
|
22
|
+
|
23
|
+
def errors
|
24
|
+
@errors ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def validate_scope(tree, scopes:)
|
30
|
+
scopes.each_with_index do |scope, i|
|
31
|
+
node = tree[scope]
|
32
|
+
|
33
|
+
if node.nil?
|
34
|
+
full_key = scopes[0..i].join('.')
|
35
|
+
errors << MissingKeyError.new(full_key, single: true).set_location(tree)
|
36
|
+
break
|
37
|
+
end
|
38
|
+
|
39
|
+
if tree.mapping? && tree.size > 1
|
40
|
+
parent_scopes = scopes[0...i]
|
41
|
+
(tree.keys - [scope]).each do |key|
|
42
|
+
full_key = [*parent_scopes, key].join('.')
|
43
|
+
errors << ExtraKeyError.new(full_key, single: true).set_location(node)
|
44
|
+
end
|
45
|
+
break
|
46
|
+
end
|
47
|
+
|
48
|
+
if node.scalar?
|
49
|
+
full_key = scopes[0..i].join('.')
|
50
|
+
errors << InvalidTypeError.new(full_key, single: true).set_location(node)
|
51
|
+
break
|
52
|
+
end
|
53
|
+
|
54
|
+
tree = node
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'file_scope'
|
2
|
+
require_relative 'symmetry'
|
3
|
+
require_relative '../parser'
|
4
|
+
|
5
|
+
module I18nFlow::Validator
|
6
|
+
class Multiplexer
|
7
|
+
attr_reader :repository
|
8
|
+
attr_reader :valid_locales
|
9
|
+
attr_reader :locale_pairs
|
10
|
+
attr_reader :linters
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
repository:,
|
14
|
+
valid_locales:,
|
15
|
+
locale_pairs:,
|
16
|
+
linters: %i[file_scope symmetry]
|
17
|
+
)
|
18
|
+
@repository = repository
|
19
|
+
@valid_locales = valid_locales
|
20
|
+
@locale_pairs = locale_pairs
|
21
|
+
@linters = linters
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate!
|
25
|
+
@errors = nil
|
26
|
+
|
27
|
+
if linters.include?(:file_scope)
|
28
|
+
repository.asts_by_path.each do |path, tree|
|
29
|
+
validator = FileScope.new(tree, filepath: path)
|
30
|
+
validator.validate!
|
31
|
+
validator.errors.each do |err|
|
32
|
+
errors[err.file][err.key] = err
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if linters.include?(:symmetry)
|
38
|
+
repository.asts_by_scope.each do |scope, locale_trees|
|
39
|
+
locale_pairs.each do |(master, slave)|
|
40
|
+
master_tree = locale_trees[master]
|
41
|
+
slave_tree = locale_trees[slave]
|
42
|
+
next unless master_tree && slave_tree
|
43
|
+
|
44
|
+
validator = Symmetry.new(master_tree[master], slave_tree[slave])
|
45
|
+
validator.validate!
|
46
|
+
validator.errors.each do |err|
|
47
|
+
errors[err.file][err.key] = err
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def errors
|
55
|
+
@errors ||= Hash.new { |h, k| h[k] = {} }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
require_relative '../util'
|
3
|
+
|
4
|
+
module I18nFlow::Validator
|
5
|
+
class Symmetry
|
6
|
+
attr_reader :ast_1
|
7
|
+
attr_reader :ast_2
|
8
|
+
|
9
|
+
def initialize(ast_1, ast_2)
|
10
|
+
@ast_1 = ast_1
|
11
|
+
@ast_2 = ast_2
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate!
|
15
|
+
@errors = nil
|
16
|
+
validate_content(ast_1, ast_2)
|
17
|
+
end
|
18
|
+
|
19
|
+
def errors
|
20
|
+
@errors ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def validate_content(t1, t2)
|
26
|
+
keys = t1.keys | t2.keys
|
27
|
+
|
28
|
+
keys.each do |k|
|
29
|
+
validate_node(t1, t2, k)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_node(t1, t2, key)
|
34
|
+
n1 = t1[key]
|
35
|
+
n2 = t2[key]
|
36
|
+
|
37
|
+
check_only_tag(n1, n2)&.tap do |err|
|
38
|
+
errors << err if err
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
check_asymmetric_key(n1, n2, t2)&.tap do |err|
|
43
|
+
errors << err if err
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
check_type(n1, n2)&.tap do |err|
|
48
|
+
errors << err
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
check_todo_tag(n1, n2)&.tap do |err|
|
53
|
+
errors << err
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
if n1.scalar? || n1.alias?
|
58
|
+
check_args(n1, n2)&.tap do |err|
|
59
|
+
errors << err
|
60
|
+
end
|
61
|
+
else
|
62
|
+
validate_content(n1, n2)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def check_only_tag(n1, n2)
|
67
|
+
return unless n1&.marked_as_only? || n2&.marked_as_only?
|
68
|
+
|
69
|
+
if n1 && !n1.valid_locale?
|
70
|
+
return InvalidLocaleError.new(n1.full_key,
|
71
|
+
expect: n1.valid_locales,
|
72
|
+
actual: n1.locale,
|
73
|
+
).set_location(n1)
|
74
|
+
end
|
75
|
+
|
76
|
+
if n2 && !n2.valid_locale?
|
77
|
+
return InvalidLocaleError.new(n2.full_key,
|
78
|
+
expect: n2.valid_locales,
|
79
|
+
actual: n2.locale,
|
80
|
+
).set_location(n2)
|
81
|
+
end
|
82
|
+
|
83
|
+
if n1 && !n2 && n1.marked_as_only?
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
|
87
|
+
if !n1 && n2 && n2.marked_as_only?
|
88
|
+
return false
|
89
|
+
end
|
90
|
+
|
91
|
+
if n1 && n2 && n1.valid_locales.any? && !n1.valid_locales.include?(n2.locale)
|
92
|
+
return InvalidLocaleError.new(n2.full_key,
|
93
|
+
expect: n1.valid_locales,
|
94
|
+
actual: n2.locale,
|
95
|
+
).set_location(n2)
|
96
|
+
end
|
97
|
+
|
98
|
+
if n1 && n2 && n2.valid_locales.any? && !n2.valid_locales.include?(n1.locale)
|
99
|
+
return InvalidLocaleError.new(n1.full_key,
|
100
|
+
expect: n2.valid_locales,
|
101
|
+
actual: n1.locale,
|
102
|
+
).set_location(n1)
|
103
|
+
end
|
104
|
+
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
def check_type(n1, n2)
|
109
|
+
return unless n1 && n2
|
110
|
+
return if n1.scalar? == n2.scalar?
|
111
|
+
|
112
|
+
InvalidTypeError.new(n2.full_key).set_location(n2)
|
113
|
+
end
|
114
|
+
|
115
|
+
def check_asymmetric_key(n1, n2, t2)
|
116
|
+
return false if n1&.ignored_violation == :key || n2&.ignored_violation == :key
|
117
|
+
return if n1 && n2
|
118
|
+
|
119
|
+
if n1
|
120
|
+
full_key = [t2.locale, *n1.scopes.drop(1)].join('.')
|
121
|
+
MissingKeyError.new(full_key).set_location(t2)
|
122
|
+
else
|
123
|
+
ExtraKeyError.new(n2.full_key).set_location(n2)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def check_todo_tag(n1, n2)
|
128
|
+
return unless n2.marked_as_todo?
|
129
|
+
|
130
|
+
if !n2.scalar?
|
131
|
+
InvalidTodoError.new(n2.full_key).set_location(n2)
|
132
|
+
elsif n2.value != n1.value
|
133
|
+
TodoContentError.new(n2.full_key,
|
134
|
+
expect: n1.value,
|
135
|
+
actual: n2.value,
|
136
|
+
).set_location(n2)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def check_args(n1, n2)
|
141
|
+
return if n1.ignored_violation == :args || n2.ignored_violation == :args
|
142
|
+
|
143
|
+
args_1 = I18nFlow::Util.extract_args(n1.value).uniq
|
144
|
+
args_2 = I18nFlow::Util.extract_args(n2.value).uniq
|
145
|
+
|
146
|
+
return if args_1 == args_2
|
147
|
+
|
148
|
+
AsymmetricArgsError.new(n2.full_key,
|
149
|
+
expect: args_1,
|
150
|
+
actual: args_2,
|
151
|
+
).set_location(n2)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'psych'
|
2
|
+
require_relative 'node'
|
3
|
+
|
4
|
+
module I18nFlow::YamlAstProxy
|
5
|
+
class Mapping < Node
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :indexed_object, :==, :keys, :size
|
9
|
+
|
10
|
+
def each
|
11
|
+
indexed_object.each do |k, _|
|
12
|
+
yield k, cache[k]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def values
|
17
|
+
indexed_object.map { |k, _| cache[k] }
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(key, value)
|
21
|
+
super.tap do
|
22
|
+
cache.delete(key)
|
23
|
+
synchronize!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
alias []= set
|
27
|
+
|
28
|
+
def batch
|
29
|
+
@locked = true
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
@locked = false
|
33
|
+
synchronize!
|
34
|
+
end
|
35
|
+
|
36
|
+
def merge!(other)
|
37
|
+
return unless other&.is_a?(Mapping)
|
38
|
+
|
39
|
+
batch do
|
40
|
+
other.batch do
|
41
|
+
other.each do |k, rhs|
|
42
|
+
if (lhs = self[k])
|
43
|
+
lhs.merge!(rhs)
|
44
|
+
else
|
45
|
+
self[k] = rhs.node
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def cache
|
55
|
+
@cache ||= Hash.new { |h, k| h[k] = wrap(indexed_object[k], key: k) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def indexed_object
|
59
|
+
@indexed_object ||= node.children
|
60
|
+
.each_slice(2)
|
61
|
+
.map { |k, v| [k.value, v] }
|
62
|
+
.to_h
|
63
|
+
end
|
64
|
+
|
65
|
+
def synchronize!
|
66
|
+
return if @locked
|
67
|
+
|
68
|
+
children = indexed_object.flat_map { |k, v| [Psych::Nodes::Scalar.new(k), v] }
|
69
|
+
node.children.replace(children)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require_relative 'node_meta_data'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module I18nFlow::YamlAstProxy
|
5
|
+
class Node
|
6
|
+
extend Forwardable
|
7
|
+
include NodeMetaData
|
8
|
+
|
9
|
+
TAG_IGNORE = /^!ignore:(args|key)$/
|
10
|
+
TAG_TODO = /^!todo(?::([,a-zA-Z_-]+))?$/
|
11
|
+
TAG_ONLY = /^!only(?::([,a-zA-Z_-]+))?$/
|
12
|
+
|
13
|
+
attr_reader :node
|
14
|
+
attr_reader :parent
|
15
|
+
attr_reader :scopes
|
16
|
+
attr_reader :file_path
|
17
|
+
attr_reader :ignored_violation
|
18
|
+
|
19
|
+
def_delegators :indexed_object, :each
|
20
|
+
|
21
|
+
def initialize(
|
22
|
+
node,
|
23
|
+
parent: nil,
|
24
|
+
scopes: [],
|
25
|
+
file_path: nil
|
26
|
+
)
|
27
|
+
@node = node
|
28
|
+
@parent = parent
|
29
|
+
@scopes = scopes
|
30
|
+
@file_path = file_path
|
31
|
+
|
32
|
+
parse_tag!(node.tag)
|
33
|
+
end
|
34
|
+
|
35
|
+
def get(key)
|
36
|
+
wrap(indexed_object[key], key: key)
|
37
|
+
end
|
38
|
+
alias [] get
|
39
|
+
|
40
|
+
def set(key, value)
|
41
|
+
indexed_object[key] = value
|
42
|
+
end
|
43
|
+
alias []= set
|
44
|
+
|
45
|
+
def value
|
46
|
+
node.value if node.respond_to?(:value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def merge!(other)
|
50
|
+
return unless other&.is_a?(Node)
|
51
|
+
|
52
|
+
if scalar? && other.scalar?
|
53
|
+
node.value = other.value
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
if !scalar? && !other.scalar?
|
58
|
+
batch do
|
59
|
+
other.batch do
|
60
|
+
other.each do |k, rhs|
|
61
|
+
if (lhs = self[k])
|
62
|
+
lhs.merge!(rhs)
|
63
|
+
else
|
64
|
+
self[k] = rhs.node
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def batch
|
73
|
+
yield
|
74
|
+
end
|
75
|
+
|
76
|
+
def ==(other)
|
77
|
+
return false unless other.is_a?(self.class)
|
78
|
+
identity_data == other.identity_data
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_yaml
|
82
|
+
parent.to_yaml(nil, line_width: -1)
|
83
|
+
end
|
84
|
+
|
85
|
+
def keys
|
86
|
+
return [] if scalar? || alias?
|
87
|
+
indexed_object.keys
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def identity_data
|
93
|
+
(scalar? || alias?) ? value : indexed_object
|
94
|
+
end
|
95
|
+
|
96
|
+
def indexed_object
|
97
|
+
@indexed_object ||= I18nFlow::YamlAstProxy.create(node,
|
98
|
+
parent: parent,
|
99
|
+
scopes: scopes,
|
100
|
+
file_path: file_path,
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def wrap(value, key:)
|
105
|
+
I18nFlow::YamlAstProxy.create(value,
|
106
|
+
parent: node,
|
107
|
+
scopes: [*scopes, key],
|
108
|
+
file_path: file_path,
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse_tag!(tag)
|
113
|
+
return unless tag
|
114
|
+
|
115
|
+
case tag
|
116
|
+
when TAG_TODO
|
117
|
+
@tag = :todo
|
118
|
+
@todo_locales = $1.to_s.split(',').freeze
|
119
|
+
when TAG_ONLY
|
120
|
+
@tag = :only
|
121
|
+
@valid_locales = $1.to_s.split(',').freeze
|
122
|
+
when TAG_IGNORE
|
123
|
+
@tag = :ignore
|
124
|
+
@ignored_violation = $1.freeze.to_sym
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module I18nFlow::YamlAstProxy
|
2
|
+
module NodeMetaData
|
3
|
+
def num_lines
|
4
|
+
return 1 unless end_line
|
5
|
+
end_line - start_line + 1
|
6
|
+
end
|
7
|
+
|
8
|
+
def key
|
9
|
+
scopes.last
|
10
|
+
end
|
11
|
+
|
12
|
+
def locale
|
13
|
+
scopes.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def full_key
|
17
|
+
scopes.join('.')
|
18
|
+
end
|
19
|
+
|
20
|
+
def start_line
|
21
|
+
node.start_line + line_correction
|
22
|
+
end
|
23
|
+
|
24
|
+
def end_line
|
25
|
+
node.end_line + line_correction
|
26
|
+
end
|
27
|
+
|
28
|
+
def start_column
|
29
|
+
node.start_column
|
30
|
+
end
|
31
|
+
|
32
|
+
def end_column
|
33
|
+
node.end_column
|
34
|
+
end
|
35
|
+
|
36
|
+
def anchor
|
37
|
+
node.anchor
|
38
|
+
end
|
39
|
+
|
40
|
+
def sequence?
|
41
|
+
is_a?(Sequence)
|
42
|
+
end
|
43
|
+
|
44
|
+
def mapping?
|
45
|
+
is_a?(Mapping)
|
46
|
+
end
|
47
|
+
|
48
|
+
def scalar?
|
49
|
+
node.is_a?(Psych::Nodes::Scalar)
|
50
|
+
end
|
51
|
+
|
52
|
+
def alias?
|
53
|
+
node.is_a?(Psych::Nodes::Alias)
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_anchor?
|
57
|
+
!!anchor
|
58
|
+
end
|
59
|
+
|
60
|
+
def marked_as_todo?
|
61
|
+
@tag == :todo
|
62
|
+
end
|
63
|
+
|
64
|
+
def marked_as_only?
|
65
|
+
@tag == :only
|
66
|
+
end
|
67
|
+
|
68
|
+
def todo_locales
|
69
|
+
@todo_locales ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
def valid_locales
|
73
|
+
@valid_locales ||= []
|
74
|
+
end
|
75
|
+
|
76
|
+
def valid_locale?
|
77
|
+
valid_locales.empty? || valid_locales.include?(locale)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def line_correction
|
83
|
+
node.is_a?(Psych::Nodes::Scalar) ? 1 : 0
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'node'
|
2
|
+
|
3
|
+
module I18nFlow::YamlAstProxy
|
4
|
+
class Sequence < Node
|
5
|
+
def_delegators :indexed_object, :==, :<<, :size
|
6
|
+
|
7
|
+
def each
|
8
|
+
indexed_object.each.with_index do |o, i|
|
9
|
+
yield i, wrap(o, key: i)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge!(other)
|
14
|
+
return unless other&.is_a?(Sequence)
|
15
|
+
|
16
|
+
indexed_object.concat(other.send(:indexed_object))
|
17
|
+
end
|
18
|
+
|
19
|
+
def keys
|
20
|
+
(0...size).to_a
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def indexed_object
|
26
|
+
node.children
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|