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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +13 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +45 -0
  8. data/LICENSE +22 -0
  9. data/README.md +103 -0
  10. data/Rakefile +2 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/doc/rules.md +316 -0
  14. data/doc/tags.md +488 -0
  15. data/example/example.en.yml +14 -0
  16. data/example/example.ja.yml +9 -0
  17. data/exe/i18n_flow +11 -0
  18. data/i18n_flow.gemspec +28 -0
  19. data/i18n_flow.yml +8 -0
  20. data/lib/i18n_flow/cli/color.rb +18 -0
  21. data/lib/i18n_flow/cli/command_base.rb +33 -0
  22. data/lib/i18n_flow/cli/copy_command.rb +69 -0
  23. data/lib/i18n_flow/cli/help_command.rb +29 -0
  24. data/lib/i18n_flow/cli/lint_command/ascii.erb +45 -0
  25. data/lib/i18n_flow/cli/lint_command/ascii_renderer.rb +58 -0
  26. data/lib/i18n_flow/cli/lint_command/markdown.erb +49 -0
  27. data/lib/i18n_flow/cli/lint_command/markdown_renderer.rb +55 -0
  28. data/lib/i18n_flow/cli/lint_command.rb +55 -0
  29. data/lib/i18n_flow/cli/read_config_command.rb +20 -0
  30. data/lib/i18n_flow/cli/search_command/default.erb +11 -0
  31. data/lib/i18n_flow/cli/search_command/default_renderer.rb +67 -0
  32. data/lib/i18n_flow/cli/search_command/oneline.erb +5 -0
  33. data/lib/i18n_flow/cli/search_command/oneline_renderer.rb +39 -0
  34. data/lib/i18n_flow/cli/search_command.rb +59 -0
  35. data/lib/i18n_flow/cli/split_command.rb +20 -0
  36. data/lib/i18n_flow/cli/version_command.rb +9 -0
  37. data/lib/i18n_flow/cli.rb +42 -0
  38. data/lib/i18n_flow/configuration.rb +205 -0
  39. data/lib/i18n_flow/parser.rb +34 -0
  40. data/lib/i18n_flow/repository.rb +39 -0
  41. data/lib/i18n_flow/search.rb +176 -0
  42. data/lib/i18n_flow/splitter/merger.rb +60 -0
  43. data/lib/i18n_flow/splitter/strategy.rb +66 -0
  44. data/lib/i18n_flow/splitter.rb +5 -0
  45. data/lib/i18n_flow/util.rb +57 -0
  46. data/lib/i18n_flow/validator/errors.rb +99 -0
  47. data/lib/i18n_flow/validator/file_scope.rb +58 -0
  48. data/lib/i18n_flow/validator/multiplexer.rb +58 -0
  49. data/lib/i18n_flow/validator/symmetry.rb +154 -0
  50. data/lib/i18n_flow/validator.rb +4 -0
  51. data/lib/i18n_flow/version.rb +7 -0
  52. data/lib/i18n_flow/yaml_ast_proxy/mapping.rb +72 -0
  53. data/lib/i18n_flow/yaml_ast_proxy/node.rb +128 -0
  54. data/lib/i18n_flow/yaml_ast_proxy/node_meta_data.rb +86 -0
  55. data/lib/i18n_flow/yaml_ast_proxy/sequence.rb +29 -0
  56. data/lib/i18n_flow/yaml_ast_proxy.rb +57 -0
  57. data/lib/i18n_flow.rb +15 -0
  58. data/spec/lib/i18n_flow/cli/command_base_spec.rb +46 -0
  59. data/spec/lib/i18n_flow/cli/help_command_spec.rb +13 -0
  60. data/spec/lib/i18n_flow/cli/version_command_spec.rb +13 -0
  61. data/spec/lib/i18n_flow/configuration_spec.rb +334 -0
  62. data/spec/lib/i18n_flow/repository_spec.rb +40 -0
  63. data/spec/lib/i18n_flow/splitter/merger_spec.rb +149 -0
  64. data/spec/lib/i18n_flow/util_spec.rb +194 -0
  65. data/spec/lib/i18n_flow/validator/file_scope_spec.rb +74 -0
  66. data/spec/lib/i18n_flow/validator/multiplexer_spec.rb +68 -0
  67. data/spec/lib/i18n_flow/validator/symmetry_spec.rb +511 -0
  68. data/spec/lib/i18n_flow/yaml_ast_proxy/node_spec.rb +151 -0
  69. data/spec/lib/i18n_flow_spec.rb +21 -0
  70. data/spec/spec_helper.rb +16 -0
  71. data/spec/support/repository_examples.rb +60 -0
  72. data/spec/support/util_macro.rb +14 -0
  73. metadata +214 -0
@@ -0,0 +1,194 @@
1
+ require 'i18n_flow/util'
2
+
3
+ describe I18nFlow::Util do
4
+ describe '.extract_args' do
5
+ it 'should extract translation arguments' do
6
+ {
7
+ '' => %w[],
8
+ 'foo' => %w[],
9
+ '%{arg_1}' => %w[arg_1],
10
+ 'foo %{arg_1}' => %w[arg_1],
11
+ 'foo %{arg_1} bar %{arg_2}' => %w[arg_1 arg_2],
12
+ 'foo %{arg_2} bar %{arg_1}' => %w[arg_1 arg_2],
13
+ '%{arg_1}%{arg_2}' => %w[arg_1 arg_2],
14
+ '%{arg_1}%%{arg_2}' => %w[arg_1],
15
+ '%%{arg_1} bar %%%{arg_2}' => %w[arg_2],
16
+ }.each do |input, output|
17
+ expect(I18nFlow::Util.extract_args(input)).to eq(output)
18
+ end
19
+ end
20
+ end
21
+
22
+ describe '.filepath_to_scope' do
23
+ it 'should parse file path into locale and scopes' do
24
+ {
25
+ 'en.yml' => %w[en],
26
+ '.en.yml' => %w[en],
27
+ 'foo.en.yml' => %w[en foo],
28
+ 'foo/bar/en.yml' => %w[en foo bar],
29
+ 'foo/bar/baz.en.yml' => %w[en foo bar baz],
30
+ 'foo/bar/baz.bax.en.yml' => %w[en foo bar baz bax],
31
+ }.each do |input, output|
32
+ expect(I18nFlow::Util.filepath_to_scope(input)).to eq(output)
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '.scope_to_filepath' do
38
+ it 'should build file path from scopes' do
39
+ {
40
+ [nil, 'en'] => 'en.yml',
41
+ %w[en] => 'en.yml',
42
+ %w[en foo] => 'foo.en.yml',
43
+ %w[en foo bar] => 'foo/bar.en.yml',
44
+ %w[en foo bar baz] => 'foo/bar/baz.en.yml',
45
+ %w[en foo bar baz bax] => 'foo/bar/baz/bax.en.yml',
46
+ }.each do |input, output|
47
+ expect(I18nFlow::Util.scope_to_filepath(input)).to eq(output)
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '.find_file_upward' do
53
+ let(:file) { FakeFS::FakeFile.new }
54
+ let(:file_name) { 'file' }
55
+
56
+ it 'should return a file path if a file is exist in the current directory' do
57
+ pwd = '/a/b/c/d'
58
+ file_path = "/a/b/c/d/#{file_name}"
59
+
60
+ FakeFS::FileSystem.add(pwd)
61
+ FakeFS::FileSystem.add(file_path, file)
62
+
63
+ Dir.chdir(pwd) do
64
+ expect(I18nFlow::Util.find_file_upward(file_name)).to eq(file_path)
65
+ end
66
+ end
67
+
68
+ it 'should return a file path if a file is exist at the parent directory' do
69
+ pwd = '/a/b/c/d'
70
+ file_path = "/a/b/c/#{file_name}"
71
+
72
+ FakeFS::FileSystem.add(pwd)
73
+ FakeFS::FileSystem.add(file_path, file)
74
+
75
+ Dir.chdir(pwd) do
76
+ expect(I18nFlow::Util.find_file_upward(file_name)).to eq(file_path)
77
+ end
78
+ end
79
+
80
+ it 'should return a file path if a file is exist at somewhere of parent directories' do
81
+ pwd = '/a/b/c/d'
82
+ file_path = "/a/#{file_name}"
83
+
84
+ FakeFS::FileSystem.add(pwd)
85
+ FakeFS::FileSystem.add(file_path, file)
86
+
87
+ Dir.chdir(pwd) do
88
+ expect(I18nFlow::Util.find_file_upward(file_name)).to eq(file_path)
89
+ end
90
+ end
91
+
92
+ it 'should return nil if a file is not exist in any parent directories' do
93
+ pwd = '/a/b/c/d'
94
+ file_path = "/abc/#{file_name}"
95
+
96
+ FakeFS::FileSystem.add(pwd)
97
+ FakeFS::FileSystem.add(file_path, file)
98
+
99
+ Dir.chdir(pwd) do
100
+ expect(I18nFlow::Util.find_file_upward(file_name)).to be_nil
101
+ end
102
+ end
103
+
104
+ context 'Multiple file names' do
105
+ let(:file_2) { FakeFS::FakeFile.new }
106
+ let(:file_name_2) { 'file' }
107
+
108
+ it 'should return a file path if one of files is exist at somewhere of parent directories' do
109
+ pwd = '/a/b/c/d'
110
+ file_path = "/a/#{file_name}"
111
+
112
+ FakeFS::FileSystem.add(pwd)
113
+ FakeFS::FileSystem.add(file_path, file)
114
+
115
+ Dir.chdir(pwd) do
116
+ expect(I18nFlow::Util.find_file_upward(file_name, file_name_2)).to eq(file_path)
117
+ end
118
+ end
119
+
120
+ it 'should return the first match if one of files is exist at somewhere of parent directories' do
121
+ pwd = '/a/b/c/d'
122
+ file_path = "/a/#{file_name}"
123
+ file_path_2 = "/a/#{file_name_2}"
124
+
125
+ FakeFS::FileSystem.add(pwd)
126
+ FakeFS::FileSystem.add(file_path, file)
127
+ FakeFS::FileSystem.add(file_path_2, file)
128
+
129
+ Dir.chdir(pwd) do
130
+ expect(I18nFlow::Util.find_file_upward(file_name, file_name_2)).to eq(file_path)
131
+ expect(I18nFlow::Util.find_file_upward(file_name_2, file_name)).to eq(file_path_2)
132
+ end
133
+ end
134
+
135
+ it 'should return nil if non of files is not exist in any parent directories' do
136
+ pwd = '/a/b/c/d'
137
+ file_path = "/abc/#{file_name}"
138
+ file_path_2 = "/cba/#{file_name_2}"
139
+
140
+ FakeFS::FileSystem.add(pwd)
141
+ FakeFS::FileSystem.add(file_path, file)
142
+ FakeFS::FileSystem.add(file_path_2, file)
143
+
144
+ Dir.chdir(pwd) do
145
+ expect(I18nFlow::Util.find_file_upward(file_name, file_name_2)).to be_nil
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '.parse_options' do
152
+ it 'should parse short options' do
153
+ args = ['-a', '-b']
154
+ options = I18nFlow::Util.parse_options(args)
155
+
156
+ expect(args).to be_empty
157
+ expect(options).to be_a(Hash)
158
+ expect(options['a']).to be(true)
159
+ expect(options['b']).to be(true)
160
+ end
161
+
162
+ it 'should parse long options' do
163
+ args = ['--long-a', '--long-b']
164
+ options = I18nFlow::Util.parse_options(args)
165
+
166
+ expect(args).to be_empty
167
+ expect(options).to be_a(Hash)
168
+ expect(options['long-a']).to be(true)
169
+ expect(options['long-b']).to be(true)
170
+ end
171
+
172
+ it 'should parse long options with value' do
173
+ args = ['--long-a=value-of-a', '--long-b=value-of-b']
174
+ options = I18nFlow::Util.parse_options(args)
175
+
176
+ expect(args).to be_empty
177
+ expect(options).to be_a(Hash)
178
+ expect(options['long-a']).to eq('value-of-a')
179
+ expect(options['long-b']).to eq('value-of-b')
180
+ end
181
+
182
+ it 'should not parse options after once non-option args appears' do
183
+ args = ['-a', '--long-a', 'non-option', '--long-b=value-of-b']
184
+ options = I18nFlow::Util.parse_options(args)
185
+
186
+ expect(args).to eq(['non-option', '--long-b=value-of-b'])
187
+ expect(options).to be_a(Hash)
188
+ expect(options['a']).to be(true)
189
+ expect(options['long-a']).to be(true)
190
+ expect(options['long-b']).to be_nil
191
+ end
192
+ end
193
+
194
+ end
@@ -0,0 +1,74 @@
1
+ require 'i18n_flow/validator/file_scope'
2
+ require 'i18n_flow/validator/errors'
3
+
4
+ describe I18nFlow::Validator::FileScope do
5
+ let(:filepath) { 'foo/bar.en.yml' }
6
+ let(:validator) { I18nFlow::Validator::FileScope.new(nil, filepath: filepath) }
7
+
8
+ describe '#validate' do
9
+ it 'should pass if the filepath and the scope are perfectly matched' do
10
+ ast = parse_yaml(<<-YAML)
11
+ en:
12
+ foo:
13
+ bar:
14
+ key_1: text_1
15
+ YAML
16
+
17
+ allow(validator).to receive(:ast).and_return(ast)
18
+ validator.validate!
19
+
20
+ expect(validator.errors).to eq([])
21
+ end
22
+
23
+ it 'should fail if the scope is missing' do
24
+ ast = parse_yaml(<<-YAML)
25
+ en:
26
+ foo:
27
+ key_1: text_1
28
+ YAML
29
+
30
+ allow(validator).to receive(:ast).and_return(ast)
31
+ validator.validate!
32
+
33
+ expect(validator.errors).to eq([
34
+ I18nFlow::Validator::MissingKeyError.new('en.foo.bar'),
35
+ ])
36
+ end
37
+
38
+ it 'should fail if its structure is invalid' do
39
+ ast = parse_yaml(<<-YAML)
40
+ en:
41
+ foo:
42
+ bar: text_1
43
+ YAML
44
+
45
+ allow(validator).to receive(:ast).and_return(ast)
46
+ validator.validate!
47
+
48
+ expect(validator.errors).to eq([
49
+ I18nFlow::Validator::InvalidTypeError.new('en.foo.bar'),
50
+ ])
51
+ end
52
+
53
+ it 'should fail if it contains extra keys' do
54
+ ast = parse_yaml(<<-YAML)
55
+ en:
56
+ foo:
57
+ bar:
58
+ key_1: text_1
59
+ baz:
60
+ key_2: text_2
61
+ bax:
62
+ key_2: text_2
63
+ YAML
64
+
65
+ allow(validator).to receive(:ast).and_return(ast)
66
+ validator.validate!
67
+
68
+ expect(validator.errors).to eq([
69
+ I18nFlow::Validator::ExtraKeyError.new('en.foo.baz'),
70
+ I18nFlow::Validator::ExtraKeyError.new('en.foo.bax'),
71
+ ])
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,68 @@
1
+ describe I18nFlow::Validator::Multiplexer do
2
+ include_examples :create_repository
3
+
4
+ let(:validator) do
5
+ I18nFlow::Validator::Multiplexer.new(
6
+ repository: repository,
7
+ valid_locales: %w[en ja fr],
8
+ locale_pairs: [
9
+ %w[en ja],
10
+ %w[ja fr],
11
+ ],
12
+ )
13
+ end
14
+
15
+ describe '#validate' do
16
+ it 'should pass' do
17
+ validator.validate!
18
+ expect(validator.errors).to eq({})
19
+ end
20
+
21
+ context 'violate single validator rules' do
22
+ let(:models_user_ja_yml) do
23
+ <<-YAML
24
+ ja:
25
+ modulo:
26
+ user:
27
+ key_1: text_1
28
+ YAML
29
+ end
30
+
31
+ it 'should fail' do
32
+ validator.validate!
33
+ expect(validator.errors).to eq({
34
+ 'models/user.ja.yml' => {
35
+ 'ja.models' => I18nFlow::Validator::MissingKeyError.new('ja.models'),
36
+ 'ja.modulo' => I18nFlow::Validator::ExtraKeyError.new('ja.modulo'),
37
+ },
38
+ })
39
+ end
40
+ end
41
+
42
+ context 'violate symmetry validator rules' do
43
+ let(:views_profiles_show_ja_yml) do
44
+ <<-YAML
45
+ ja:
46
+ views:
47
+ profiles:
48
+ show:
49
+ key_2: text_2
50
+ YAML
51
+ end
52
+
53
+ it 'should fail' do
54
+ validator.validate!
55
+ expect(validator.errors).to eq({
56
+ 'views/profiles/show.ja.yml' => {
57
+ 'ja.views.profiles.show.key_1' => I18nFlow::Validator::MissingKeyError.new('ja.views.profiles.show.key_1'),
58
+ 'ja.views.profiles.show.key_2' => I18nFlow::Validator::ExtraKeyError.new('ja.views.profiles.show.key_2'),
59
+ },
60
+ 'views/profiles/show.fr.yml' => {
61
+ "fr.views.profiles.show.key_1"=> I18nFlow::Validator::ExtraKeyError.new('fr.views.profiles.show.key_1'),
62
+ 'fr.views.profiles.show.key_2'=> I18nFlow::Validator::MissingKeyError.new('fr.views.profiles.show.key_2'),
63
+ },
64
+ })
65
+ end
66
+ end
67
+ end
68
+ end