i18n_flow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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