i18n_flow 0.1.0
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 +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
|