yury-twine 0.9.1

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 (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