yury-twine 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/LICENSE +30 -0
  4. data/README.md +230 -0
  5. data/bin/twine +7 -0
  6. data/lib/twine.rb +36 -0
  7. data/lib/twine/cli.rb +200 -0
  8. data/lib/twine/encoding.rb +22 -0
  9. data/lib/twine/formatters.rb +20 -0
  10. data/lib/twine/formatters/abstract.rb +187 -0
  11. data/lib/twine/formatters/android.rb +254 -0
  12. data/lib/twine/formatters/apple.rb +328 -0
  13. data/lib/twine/output_processor.rb +57 -0
  14. data/lib/twine/placeholders.rb +54 -0
  15. data/lib/twine/plugin.rb +62 -0
  16. data/lib/twine/runner.rb +332 -0
  17. data/lib/twine/twine_file.rb +266 -0
  18. data/lib/twine/version.rb +3 -0
  19. data/test/command_test.rb +14 -0
  20. data/test/fixtures/consume_loc_drop.zip +0 -0
  21. data/test/fixtures/enc_utf16be.dummy +0 -0
  22. data/test/fixtures/enc_utf16be_bom.dummy +0 -0
  23. data/test/fixtures/enc_utf16le.dummy +0 -0
  24. data/test/fixtures/enc_utf16le_bom.dummy +0 -0
  25. data/test/fixtures/enc_utf8.dummy +2 -0
  26. data/test/fixtures/formatter_android.xml +15 -0
  27. data/test/fixtures/formatter_apple.strings +20 -0
  28. data/test/fixtures/formatter_django.po +30 -0
  29. data/test/fixtures/formatter_flash.properties +15 -0
  30. data/test/fixtures/formatter_gettext.po +26 -0
  31. data/test/fixtures/formatter_jquery.json +7 -0
  32. data/test/fixtures/formatter_tizen.xml +15 -0
  33. data/test/fixtures/gettext_multiline.po +10 -0
  34. data/test/fixtures/twine_accent_values.txt +13 -0
  35. data/test/test_abstract_formatter.rb +165 -0
  36. data/test/test_cli.rb +304 -0
  37. data/test/test_consume_loc_drop.rb +27 -0
  38. data/test/test_consume_localization_file.rb +119 -0
  39. data/test/test_formatters.rb +363 -0
  40. data/test/test_generate_all_localization_files.rb +102 -0
  41. data/test/test_generate_loc_drop.rb +80 -0
  42. data/test/test_generate_localization_file.rb +91 -0
  43. data/test/test_output_processor.rb +85 -0
  44. data/test/test_placeholders.rb +84 -0
  45. data/test/test_twine_definition.rb +111 -0
  46. data/test/test_twine_file.rb +58 -0
  47. data/test/test_validate_twine_file.rb +61 -0
  48. data/test/twine_file_dsl.rb +46 -0
  49. data/test/twine_test.rb +48 -0
  50. metadata +179 -0
@@ -0,0 +1,27 @@
1
+ require 'command_test'
2
+
3
+ class TestConsumeLocDrop < CommandTest
4
+ def setup
5
+ super
6
+
7
+ options = {}
8
+ options[:input_path] = fixture_path 'consume_loc_drop.zip'
9
+ options[:output_path] = @output_path
10
+ options[:format] = 'apple'
11
+
12
+ @twine_file = build_twine_file 'en', 'es' do
13
+ add_section 'Section' do
14
+ add_definition key1: 'value1'
15
+ end
16
+ end
17
+
18
+ @runner = Twine::Runner.new(options, @twine_file)
19
+ end
20
+
21
+ def test_consumes_zip_file
22
+ @runner.consume_loc_drop
23
+
24
+ assert @twine_file.definitions_by_key['key1'].translations['en'], 'value1-english'
25
+ assert @twine_file.definitions_by_key['key1'].translations['es'], 'value1-spanish'
26
+ end
27
+ end
@@ -0,0 +1,119 @@
1
+ require 'command_test'
2
+
3
+ class TestConsumeLocalizationFile < CommandTest
4
+ def new_runner(language, file)
5
+ options = {}
6
+ options[:output_path] = File.join(@output_dir, file) if file
7
+ options[:input_path] = File.join(@output_dir, file) if file
8
+ FileUtils.touch options[:input_path]
9
+ options[:languages] = language if language
10
+
11
+ @twine_file = Twine::TwineFile.new
12
+ @twine_file.language_codes.concat KNOWN_LANGUAGES
13
+
14
+ Twine::Runner.new(options, @twine_file)
15
+ end
16
+
17
+ def prepare_mock_read_formatter(formatter_class)
18
+ formatter = prepare_mock_formatter(formatter_class)
19
+ formatter.expects(:read)
20
+ end
21
+
22
+ def test_deducts_android_format_from_output_path
23
+ prepare_mock_read_formatter Twine::Formatters::Android
24
+
25
+ new_runner('fr', 'fr.xml').consume_localization_file
26
+ end
27
+
28
+ def test_deducts_apple_format_from_output_path
29
+ prepare_mock_read_formatter Twine::Formatters::Apple
30
+
31
+ new_runner('fr', 'fr.strings').consume_localization_file
32
+ end
33
+
34
+ def test_deducts_jquery_format_from_output_path
35
+ prepare_mock_read_formatter Twine::Formatters::JQuery
36
+
37
+ new_runner('fr', 'fr.json').consume_localization_file
38
+ end
39
+
40
+ def test_deducts_gettext_format_from_output_path
41
+ prepare_mock_read_formatter Twine::Formatters::Gettext
42
+
43
+ new_runner('fr', 'fr.po').consume_localization_file
44
+ end
45
+
46
+ def test_deducts_language_from_input_path
47
+ random_language = KNOWN_LANGUAGES.sample
48
+ formatter = prepare_mock_formatter Twine::Formatters::Android
49
+ formatter.expects(:read).with(anything, random_language)
50
+
51
+ new_runner(nil, "#{random_language}.xml").consume_localization_file
52
+ end
53
+
54
+ class TestEncodings < CommandTest
55
+ class DummyFormatter < Twine::Formatters::Abstract
56
+ attr_reader :content
57
+
58
+ def extension
59
+ '.dummy'
60
+ end
61
+
62
+ def format_name
63
+ 'dummy'
64
+ end
65
+
66
+ def read(io, lang)
67
+ @content = io.read
68
+ end
69
+ end
70
+
71
+ def new_runner(input_path, encoding = nil)
72
+ options = {}
73
+ options[:output_path] = @output_path
74
+ options[:input_path] = input_path
75
+ options[:encoding] = encoding if encoding
76
+ options[:languages] = 'en'
77
+
78
+ @twine_file = Twine::TwineFile.new
79
+ @twine_file.language_codes.concat KNOWN_LANGUAGES
80
+
81
+ Twine::Runner.new(options, @twine_file)
82
+ end
83
+
84
+ def setup
85
+ super
86
+ @expected_content = "Üß`\nda\n"
87
+ end
88
+
89
+ def test_reads_utf8
90
+ formatter = prepare_mock_formatter DummyFormatter
91
+ new_runner(fixture_path('enc_utf8.dummy')).consume_localization_file
92
+ assert_equal @expected_content, formatter.content
93
+ end
94
+
95
+ def test_reads_utf16le_bom
96
+ formatter = prepare_mock_formatter DummyFormatter
97
+ new_runner(fixture_path('enc_utf16le_bom.dummy')).consume_localization_file
98
+ assert_equal @expected_content, formatter.content
99
+ end
100
+
101
+ def test_reads_utf16be_bom
102
+ formatter = prepare_mock_formatter DummyFormatter
103
+ new_runner(fixture_path('enc_utf16be_bom.dummy')).consume_localization_file
104
+ assert_equal @expected_content, formatter.content
105
+ end
106
+
107
+ def test_reads_utf16le
108
+ formatter = prepare_mock_formatter DummyFormatter
109
+ new_runner(fixture_path('enc_utf16le.dummy'), 'UTF-16LE').consume_localization_file
110
+ assert_equal @expected_content, formatter.content
111
+ end
112
+
113
+ def test_reads_utf16be
114
+ formatter = prepare_mock_formatter DummyFormatter
115
+ new_runner(fixture_path('enc_utf16be.dummy'), 'UTF-16BE').consume_localization_file
116
+ assert_equal @expected_content, formatter.content
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,363 @@
1
+ require 'twine_test'
2
+
3
+ class FormatterTest < TwineTest
4
+ def setup(formatter_class)
5
+ super()
6
+
7
+ @twine_file = build_twine_file 'en' do
8
+ add_section 'Section 1' do
9
+ add_definition key1: 'value1-english', comment: 'comment key1'
10
+ add_definition key2: 'value2-english'
11
+ end
12
+
13
+ add_section 'Section 2' do
14
+ add_definition key3: 'value3-english'
15
+ add_definition key4: 'value4-english', comment: 'comment key4'
16
+ end
17
+ end
18
+
19
+ @empty_twine_file = Twine::TwineFile.new
20
+ @formatter = formatter_class.new
21
+ @formatter.twine_file = @empty_twine_file
22
+ @formatter.options = { consume_all: true, consume_comments: true }
23
+ end
24
+
25
+ def assert_translations_read_correctly
26
+ 1.upto(4) do |i|
27
+ assert_equal "value#{i}-english", @empty_twine_file.definitions_by_key["key#{i}"].translations['en']
28
+ end
29
+ end
30
+
31
+ def assert_file_contents_read_correctly
32
+ assert_translations_read_correctly
33
+
34
+ assert_equal "comment key1", @empty_twine_file.definitions_by_key["key1"].comment
35
+ assert_equal "comment key4", @empty_twine_file.definitions_by_key["key4"].comment
36
+ end
37
+ end
38
+
39
+ class TestAndroidFormatter < FormatterTest
40
+ def setup
41
+ super Twine::Formatters::Android
42
+
43
+ @escape_test_values = {
44
+ 'this & that' => 'this &amp; that',
45
+ 'this < that' => 'this &lt; that',
46
+ "it's complicated" => "it\\'s complicated",
47
+ 'a "good" way' => 'a \"good\" way',
48
+ '<b>bold</b>' => '&lt;b>bold&lt;/b>',
49
+ '<a href="target">link</a>' => '&lt;a href=\"target\">link&lt;/a>',
50
+
51
+ '<xliff:g></xliff:g>' => '<xliff:g></xliff:g>',
52
+ '<xliff:g>untouched</xliff:g>' => '<xliff:g>untouched</xliff:g>',
53
+ '<xliff:g id="42">untouched</xliff:g>' => '<xliff:g id="42">untouched</xliff:g>',
54
+ '<xliff:g id="1">first</xliff:g> inbetween <xliff:g id="2">second</xliff:g>' => '<xliff:g id="1">first</xliff:g> inbetween <xliff:g id="2">second</xliff:g>'
55
+ }
56
+ end
57
+
58
+ def test_read_format
59
+ @formatter.read content_io('formatter_android.xml'), 'en'
60
+
61
+ assert_file_contents_read_correctly
62
+ end
63
+
64
+ def test_read_multiline_translation
65
+ content = <<-EOCONTENT
66
+ <?xml version="1.0" encoding="utf-8"?>
67
+ <resources>
68
+ <string name="foo">This is
69
+ a string</string>
70
+ </resources>
71
+ EOCONTENT
72
+
73
+ io = StringIO.new(content)
74
+
75
+ @formatter.read io, 'en'
76
+
77
+ assert_equal 'This is\n a string', @empty_twine_file.definitions_by_key["foo"].translations['en']
78
+ end
79
+
80
+ def test_set_translation_converts_leading_spaces
81
+ @formatter.set_translation_for_key 'key1', 'en', "\u0020value"
82
+ assert_equal ' value', @empty_twine_file.definitions_by_key['key1'].translations['en']
83
+ end
84
+
85
+ def test_set_translation_coverts_trailing_spaces
86
+ @formatter.set_translation_for_key 'key1', 'en', "value\u0020\u0020"
87
+ assert_equal 'value ', @empty_twine_file.definitions_by_key['key1'].translations['en']
88
+ end
89
+
90
+ def test_set_translation_converts_string_placeholders
91
+ @formatter.set_translation_for_key 'key1', 'en', "value %s"
92
+ assert_equal 'value %@', @empty_twine_file.definitions_by_key['key1'].translations['en']
93
+ end
94
+
95
+ def test_set_translation_unescapes_at_signs
96
+ @formatter.set_translation_for_key 'key1', 'en', '\@value'
97
+ assert_equal '@value', @empty_twine_file.definitions_by_key['key1'].translations['en']
98
+ end
99
+
100
+ def test_set_translation_unescaping
101
+ @escape_test_values.each do |expected, input|
102
+ @formatter.set_translation_for_key 'key1', 'en', input
103
+ assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
104
+ end
105
+ end
106
+
107
+ def test_format_file
108
+ formatter = Twine::Formatters::Android.new
109
+ formatter.twine_file = @twine_file
110
+ assert_equal content('formatter_android.xml'), formatter.format_file('en')
111
+ end
112
+
113
+ def test_format_key_with_space
114
+ assert_equal 'key ', @formatter.format_key('key ')
115
+ end
116
+
117
+ def test_format_value_with_leading_space
118
+ assert_equal "\\u0020value", @formatter.format_value(' value')
119
+ end
120
+
121
+ def test_format_value_with_trailing_space
122
+ assert_equal "value\\u0020", @formatter.format_value('value ')
123
+ end
124
+
125
+ def test_format_value_escaping
126
+ @escape_test_values.each do |input, expected|
127
+ assert_equal expected, @formatter.format_value(input)
128
+ end
129
+ end
130
+
131
+ def test_format_value_escapes_non_resource_identifier_at_signs
132
+ assert_equal '\@whatever \@\@', @formatter.format_value('@whatever @@')
133
+ end
134
+
135
+ def test_format_value_does_not_modify_resource_identifiers
136
+ identifier = '@android:string/cancel'
137
+ assert_equal identifier, @formatter.format_value(identifier)
138
+ end
139
+
140
+ def test_deducts_language_from_resource_folder
141
+ language = %w(en de fr).sample
142
+ assert_equal language, @formatter.determine_language_given_path("res/values-#{language}")
143
+ end
144
+
145
+ def test_deducts_language_and_region_from_resource_folder
146
+ assert_equal 'de-AT', @formatter.determine_language_given_path("res/values-de-rAT")
147
+ end
148
+
149
+ def test_maps_laguage_deducted_from_resource_folder
150
+ assert_equal 'zh-Hans', @formatter.determine_language_given_path("res/values-zh-rCN")
151
+ end
152
+
153
+ def test_does_not_deduct_language_from_device_capability_resource_folder
154
+ assert_nil @formatter.determine_language_given_path('res/values-w820dp')
155
+ end
156
+
157
+ def test_output_path_is_prefixed
158
+ assert_equal 'values-en', @formatter.output_path_for_language('en')
159
+ end
160
+
161
+ def test_output_path_language_mappings
162
+ mappings = {
163
+ 'zh-Hans' => 'zh-rCN',
164
+ 'zh-Hant' => 'zh-rHK',
165
+ 'en-UK' => 'en-rGB',
166
+ 'id' => 'in',
167
+ 'no' => 'nb'
168
+ }
169
+ mappings.each do |lang, output_path|
170
+ assert_equal "values-#{output_path}", @formatter.output_path_for_language(lang)
171
+ end
172
+ end
173
+ end
174
+
175
+ class TestAppleFormatter < FormatterTest
176
+ def setup
177
+ super Twine::Formatters::Apple
178
+ end
179
+
180
+ def test_read_format
181
+ @formatter.read content_io('formatter_apple.strings'), 'en'
182
+
183
+ assert_file_contents_read_correctly
184
+ end
185
+
186
+ def test_reads_quoted_keys
187
+ @formatter.read StringIO.new('"key" = "value"'), 'en'
188
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
189
+ end
190
+
191
+ def test_reads_unquoted_keys
192
+ @formatter.read StringIO.new('key = "value"'), 'en'
193
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
194
+ end
195
+
196
+ def test_ignores_leading_whitespace_before_quoted_keys
197
+ @formatter.read StringIO.new("\t \"key\" = \"value\""), 'en'
198
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
199
+ end
200
+
201
+ def test_ignores_leading_whitespace_before_unquoted_keys
202
+ @formatter.read StringIO.new("\t key = \"value\""), 'en'
203
+ assert_equal 'value', @empty_twine_file.definitions_by_key['key'].translations['en']
204
+ end
205
+
206
+ def test_allows_quotes_in_quoted_keys
207
+ @formatter.read StringIO.new('"ke\"y" = "value"'), 'en'
208
+ assert_equal 'value', @empty_twine_file.definitions_by_key['ke"y'].translations['en']
209
+ end
210
+
211
+ def test_does_not_allow_quotes_in_quoted_keys
212
+ @formatter.read StringIO.new('ke"y = "value"'), 'en'
213
+ assert_nil @empty_twine_file.definitions_by_key['key']
214
+ end
215
+
216
+ def test_allows_equal_signs_in_quoted_keys
217
+ @formatter.read StringIO.new('"k=ey" = "value"'), 'en'
218
+ assert_equal 'value', @empty_twine_file.definitions_by_key['k=ey'].translations['en']
219
+ end
220
+
221
+ def test_does_not_allow_equal_signs_in_unquoted_keys
222
+ @formatter.read StringIO.new('k=ey = "value"'), 'en'
223
+ assert_nil @empty_twine_file.definitions_by_key['key']
224
+ end
225
+
226
+ def test_format_file
227
+ formatter = Twine::Formatters::Apple.new
228
+ formatter.twine_file = @twine_file
229
+ assert_equal content('formatter_apple.strings'), formatter.format_file('en')
230
+ end
231
+
232
+ def test_format_key_with_space
233
+ assert_equal 'key ', @formatter.format_key('key ')
234
+ end
235
+
236
+ def test_format_value_with_leading_space
237
+ assert_equal ' value', @formatter.format_value(' value')
238
+ end
239
+
240
+ def test_format_value_with_trailing_space
241
+ assert_equal 'value ', @formatter.format_value('value ')
242
+ end
243
+ end
244
+
245
+ class TestJQueryFormatter < FormatterTest
246
+
247
+ def setup
248
+ super Twine::Formatters::JQuery
249
+ end
250
+
251
+ def test_read_format
252
+ @formatter.read content_io('formatter_jquery.json'), 'en'
253
+
254
+ assert_translations_read_correctly
255
+ end
256
+
257
+ def test_format_file
258
+ formatter = Twine::Formatters::JQuery.new
259
+ formatter.twine_file = @twine_file
260
+ assert_equal content('formatter_jquery.json'), formatter.format_file('en')
261
+ end
262
+
263
+ def test_empty_sections_are_removed
264
+ @twine_file = build_twine_file 'en' do
265
+ add_section 'Section 1' do
266
+ end
267
+
268
+ add_section 'Section 2' do
269
+ add_definition key: 'value'
270
+ end
271
+ end
272
+ formatter = Twine::Formatters::JQuery.new
273
+ formatter.twine_file = @twine_file
274
+ refute_includes formatter.format_file('en'), ','
275
+ end
276
+
277
+ def test_format_value_with_newline
278
+ assert_equal "value\nwith\nline\nbreaks", @formatter.format_value("value\nwith\nline\nbreaks")
279
+ end
280
+ end
281
+
282
+ class TestGettextFormatter < FormatterTest
283
+
284
+ def setup
285
+ super Twine::Formatters::Gettext
286
+ end
287
+
288
+ def test_read_format
289
+ @formatter.read content_io('formatter_gettext.po'), 'en'
290
+
291
+ assert_file_contents_read_correctly
292
+ end
293
+
294
+ def test_read_with_multiple_line_value
295
+ @formatter.read content_io('gettext_multiline.po'), 'en'
296
+
297
+ assert_equal 'multiline\nstring', @empty_twine_file.definitions_by_key['key1'].translations['en']
298
+ end
299
+
300
+ def test_format_file
301
+ formatter = Twine::Formatters::Gettext.new
302
+ formatter.twine_file = @twine_file
303
+ assert_equal content('formatter_gettext.po'), formatter.format_file('en')
304
+ end
305
+
306
+ end
307
+
308
+ class TestTizenFormatter < FormatterTest
309
+
310
+ def setup
311
+ super Twine::Formatters::Tizen
312
+ end
313
+
314
+ def test_read_format
315
+ skip 'the current implementation of Tizen formatter does not support reading'
316
+ @formatter.read content_io('formatter_tizen.xml'), 'en'
317
+
318
+ assert_file_contents_read_correctly
319
+ end
320
+
321
+ def test_format_file
322
+ formatter = Twine::Formatters::Tizen.new
323
+ formatter.twine_file = @twine_file
324
+ assert_equal content('formatter_tizen.xml'), formatter.format_file('en')
325
+ end
326
+
327
+ end
328
+
329
+ class TestDjangoFormatter < FormatterTest
330
+ def setup
331
+ super Twine::Formatters::Django
332
+ end
333
+
334
+ def test_read_format
335
+ @formatter.read content_io('formatter_django.po'), 'en'
336
+
337
+ assert_file_contents_read_correctly
338
+ end
339
+
340
+ def test_format_file
341
+ formatter = Twine::Formatters::Django.new
342
+ formatter.twine_file = @twine_file
343
+ assert_equal content('formatter_django.po'), formatter.format_file('en')
344
+ end
345
+ end
346
+
347
+ class TestFlashFormatter < FormatterTest
348
+ def setup
349
+ super Twine::Formatters::Flash
350
+ end
351
+
352
+ def test_read_format
353
+ @formatter.read content_io('formatter_flash.properties'), 'en'
354
+
355
+ assert_file_contents_read_correctly
356
+ end
357
+
358
+ def test_format_file
359
+ formatter = Twine::Formatters::Flash.new
360
+ formatter.twine_file = @twine_file
361
+ assert_equal content('formatter_flash.properties'), formatter.format_file('en')
362
+ end
363
+ end