twine 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -0
  3. data/lib/twine.rb +21 -0
  4. data/lib/twine/cli.rb +68 -91
  5. data/lib/twine/formatters/abstract.rb +133 -82
  6. data/lib/twine/formatters/android.rb +49 -67
  7. data/lib/twine/formatters/apple.rb +33 -47
  8. data/lib/twine/formatters/django.rb +38 -48
  9. data/lib/twine/formatters/flash.rb +25 -40
  10. data/lib/twine/formatters/gettext.rb +37 -44
  11. data/lib/twine/formatters/jquery.rb +31 -33
  12. data/lib/twine/formatters/tizen.rb +38 -55
  13. data/lib/twine/output_processor.rb +57 -0
  14. data/lib/twine/placeholders.rb +54 -0
  15. data/lib/twine/runner.rb +78 -115
  16. data/lib/twine/stringsfile.rb +83 -60
  17. data/lib/twine/version.rb +1 -1
  18. data/test/command_test_case.rb +12 -0
  19. data/test/fixtures/consume_loc_drop.zip +0 -0
  20. data/test/fixtures/formatter_android.xml +15 -0
  21. data/test/fixtures/formatter_apple.strings +20 -0
  22. data/test/fixtures/formatter_django.po +28 -0
  23. data/test/fixtures/formatter_flash.properties +15 -0
  24. data/test/fixtures/formatter_gettext.po +26 -0
  25. data/test/fixtures/formatter_jquery.json +7 -0
  26. data/test/fixtures/formatter_tizen.xml +15 -0
  27. data/test/fixtures/gettext_multiline.po +10 -0
  28. data/test/fixtures/twine_accent_values.txt +13 -0
  29. data/test/test_abstract_formatter.rb +152 -0
  30. data/test/test_cli.rb +288 -0
  31. data/test/test_consume_loc_drop.rb +27 -0
  32. data/test/test_consume_string_file.rb +53 -0
  33. data/test/test_formatters.rb +236 -0
  34. data/test/test_generate_all_string_files.rb +44 -0
  35. data/test/test_generate_loc_drop.rb +44 -0
  36. data/test/test_generate_string_file.rb +51 -0
  37. data/test/test_output_processor.rb +85 -0
  38. data/test/test_placeholders.rb +86 -0
  39. data/test/test_strings_file.rb +58 -0
  40. data/test/test_strings_row.rb +47 -0
  41. data/test/test_validate_strings_file.rb +55 -0
  42. data/test/twine_file_dsl.rb +46 -0
  43. data/test/twine_test_case.rb +44 -0
  44. metadata +80 -37
  45. data/test/fixtures/en-1.json +0 -5
  46. data/test/fixtures/en-1.po +0 -16
  47. data/test/fixtures/en-1.strings +0 -10
  48. data/test/fixtures/en-2.po +0 -23
  49. data/test/fixtures/en-3.xml +0 -8
  50. data/test/fixtures/fr-1.xml +0 -10
  51. data/test/fixtures/strings-1.txt +0 -17
  52. data/test/fixtures/strings-2.txt +0 -5
  53. data/test/fixtures/strings-3.txt +0 -5
  54. data/test/fixtures/test-json-line-breaks/consumed.txt +0 -5
  55. data/test/fixtures/test-json-line-breaks/generated.json +0 -3
  56. data/test/fixtures/test-json-line-breaks/line-breaks.json +0 -3
  57. data/test/fixtures/test-json-line-breaks/line-breaks.txt +0 -4
  58. data/test/fixtures/test-output-1.txt +0 -12
  59. data/test/fixtures/test-output-10.txt +0 -9
  60. data/test/fixtures/test-output-11.txt +0 -9
  61. data/test/fixtures/test-output-12.txt +0 -12
  62. data/test/fixtures/test-output-2.txt +0 -12
  63. data/test/fixtures/test-output-3.txt +0 -18
  64. data/test/fixtures/test-output-4.txt +0 -21
  65. data/test/fixtures/test-output-5.txt +0 -4
  66. data/test/fixtures/test-output-6.txt +0 -10
  67. data/test/fixtures/test-output-7.txt +0 -16
  68. data/test/fixtures/test-output-8.txt +0 -9
  69. data/test/fixtures/test-output-9.txt +0 -21
  70. data/test/twine_test.rb +0 -134
@@ -0,0 +1,44 @@
1
+ require 'command_test_case'
2
+
3
+ class TestGenerateAllStringFiles < CommandTestCase
4
+ class TestCreateFolders < CommandTestCase
5
+ def new_runner(create_folders)
6
+ options = {}
7
+ options[:output_path] = @output_dir
8
+ options[:format] = 'apple'
9
+ options[:create_folders] = create_folders
10
+
11
+ @twine_file = build_twine_file 'en', 'es' do
12
+ add_section 'Section' do
13
+ add_row key: 'value'
14
+ end
15
+ end
16
+
17
+ Twine::Runner.new(options, @twine_file)
18
+ end
19
+
20
+ def test_fails_if_output_folder_does_not_exist
21
+ assert_raises Twine::Error do
22
+ new_runner(false).generate_all_string_files
23
+ end
24
+ end
25
+
26
+ def test_creates_output_folder
27
+ FileUtils.remove_entry_secure @output_dir
28
+ new_runner(true).generate_all_string_files
29
+ assert File.exists? @output_dir
30
+ end
31
+
32
+ def test_does_not_create_language_folders_by_default
33
+ Dir.mkdir File.join @output_dir, 'en.lproj'
34
+ new_runner(false).generate_all_string_files
35
+ refute File.exists?(File.join(@output_dir, 'es.lproj')), "language folder should not be created"
36
+ end
37
+
38
+ def test_creates_language_folders
39
+ new_runner(true).generate_all_string_files
40
+ assert File.exists?(File.join(@output_dir, 'en.lproj')), "language folder 'en.lproj' should be created"
41
+ assert File.exists?(File.join(@output_dir, 'es.lproj')), "language folder 'es.lproj' should be created"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ require 'command_test_case'
2
+
3
+ class TestGenerateLocDrop < CommandTestCase
4
+ def setup
5
+ super
6
+
7
+ options = {}
8
+ options[:output_path] = @output_path
9
+ options[:format] = 'apple'
10
+
11
+ @twine_file = build_twine_file 'en', 'fr' do
12
+ add_section 'Section' do
13
+ add_row key: 'value'
14
+ end
15
+ end
16
+
17
+ @runner = Twine::Runner.new(options, @twine_file)
18
+ end
19
+
20
+ def test_generates_zip_file
21
+ @runner.generate_loc_drop
22
+
23
+ assert File.exists?(@output_path), "language folder should not be created"
24
+ end
25
+
26
+ def test_zip_file_structure
27
+ @runner.generate_loc_drop
28
+
29
+ names = []
30
+ Zip::File.open(@output_path) do |zipfile|
31
+ zipfile.each do |entry|
32
+ names << entry.name
33
+ end
34
+ end
35
+ assert_equal ['Locales/', 'Locales/en.strings', 'Locales/fr.strings'], names
36
+ end
37
+
38
+ def test_uses_formatter
39
+ formatter = prepare_mock_formatter Twine::Formatters::Apple
40
+ formatter.expects(:write_file).twice.with() { |path, lang| FileUtils.touch path }
41
+
42
+ @runner.generate_loc_drop
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ require 'command_test_case'
2
+
3
+ class TestGenerateStringFile < CommandTestCase
4
+ def new_runner(language, file)
5
+ options = {}
6
+ options[:output_path] = File.join(@output_dir, file) if file
7
+ options[:languages] = language if language
8
+
9
+ @strings = Twine::StringsFile.new
10
+ @strings.language_codes.concat KNOWN_LANGUAGES
11
+
12
+ Twine::Runner.new(options, @strings)
13
+ end
14
+
15
+ def prepare_mock_write_file_formatter(formatter_class)
16
+ formatter = prepare_mock_formatter(formatter_class)
17
+ formatter.expects(:write_file)
18
+ end
19
+
20
+ def test_deducts_android_format_from_output_path
21
+ prepare_mock_write_file_formatter Twine::Formatters::Android
22
+
23
+ new_runner('fr', 'fr.xml').generate_string_file
24
+ end
25
+
26
+ def test_deducts_apple_format_from_output_path
27
+ prepare_mock_write_file_formatter Twine::Formatters::Apple
28
+
29
+ new_runner('fr', 'fr.strings').generate_string_file
30
+ end
31
+
32
+ def test_deducts_jquery_format_from_output_path
33
+ prepare_mock_write_file_formatter Twine::Formatters::JQuery
34
+
35
+ new_runner('fr', 'fr.json').generate_string_file
36
+ end
37
+
38
+ def test_deducts_gettext_format_from_output_path
39
+ prepare_mock_write_file_formatter Twine::Formatters::Gettext
40
+
41
+ new_runner('fr', 'fr.po').generate_string_file
42
+ end
43
+
44
+ def test_deducts_language_from_output_path
45
+ random_language = KNOWN_LANGUAGES.sample
46
+ formatter = prepare_mock_formatter Twine::Formatters::Android
47
+ formatter.expects(:write_file).with(anything, random_language)
48
+
49
+ new_runner(nil, "#{random_language}.xml").generate_string_file
50
+ end
51
+ end
@@ -0,0 +1,85 @@
1
+ require 'twine_test_case'
2
+
3
+ class TestOutputProcessor < TwineTestCase
4
+ def setup
5
+ super
6
+
7
+ @strings = build_twine_file 'en', 'fr' do
8
+ add_section 'Section' do
9
+ add_row key1: 'value1', tags: ['tag1']
10
+ add_row key2: 'value2', tags: ['tag1', 'tag2']
11
+ add_row key3: 'value3', tags: ['tag2']
12
+ add_row key4: { en: 'value4-en', fr: 'value4-fr' }
13
+ end
14
+ end
15
+ end
16
+
17
+ def test_includes_all_keys_by_default
18
+ processor = Twine::Processors::OutputProcessor.new(@strings, {})
19
+ result = processor.process('en')
20
+
21
+ assert_equal %w(key1 key2 key3 key4), result.strings_map.keys.sort
22
+ end
23
+
24
+ def test_filter_by_tag
25
+ processor = Twine::Processors::OutputProcessor.new(@strings, { tags: ['tag1'] })
26
+ result = processor.process('en')
27
+
28
+ assert_equal %w(key1 key2), result.strings_map.keys.sort
29
+ end
30
+
31
+ def test_filter_by_multiple_tags
32
+ processor = Twine::Processors::OutputProcessor.new(@strings, { tags: ['tag1', 'tag2'] })
33
+ result = processor.process('en')
34
+
35
+ assert_equal %w(key1 key2 key3), result.strings_map.keys.sort
36
+ end
37
+
38
+ def test_filter_untagged
39
+ processor = Twine::Processors::OutputProcessor.new(@strings, { tags: ['tag1'], untagged: true })
40
+ result = processor.process('en')
41
+
42
+ assert_equal %w(key1 key2 key4), result.strings_map.keys.sort
43
+ end
44
+
45
+ def test_include_translated
46
+ processor = Twine::Processors::OutputProcessor.new(@strings, { include: 'translated' })
47
+ result = processor.process('fr')
48
+
49
+ assert_equal %w(key4), result.strings_map.keys.sort
50
+ end
51
+
52
+ def test_include_untranslated
53
+ processor = Twine::Processors::OutputProcessor.new(@strings, { include: 'untranslated' })
54
+ result = processor.process('fr')
55
+
56
+ assert_equal %w(key1 key2 key3), result.strings_map.keys.sort
57
+ end
58
+
59
+ class TranslationFallback < TwineTestCase
60
+ def setup
61
+ super
62
+
63
+ @strings = build_twine_file 'en', 'fr', 'de' do
64
+ add_section 'Section' do
65
+ add_row key1: { en: 'value1-en', fr: 'value1-fr' }
66
+ end
67
+ end
68
+ end
69
+
70
+ def test_fallback_to_default_language
71
+ processor = Twine::Processors::OutputProcessor.new(@strings, {})
72
+ result = processor.process('de')
73
+
74
+ assert_equal 'value1-en', result.strings_map['key1'].translations['de']
75
+ end
76
+
77
+ def test_fallback_to_developer_language
78
+ processor = Twine::Processors::OutputProcessor.new(@strings, {developer_language: 'fr'})
79
+ result = processor.process('de')
80
+
81
+ assert_equal 'value1-fr', result.strings_map['key1'].translations['de']
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,86 @@
1
+ require 'twine_test_case'
2
+
3
+ class PlaceholderTestCase < TwineTestCase
4
+ def assert_starts_with(prefix, value)
5
+ msg = message(nil) { "Expected #{mu_pp(value)} to start with #{mu_pp(prefix)}" }
6
+ assert value.start_with?(prefix), msg
7
+ end
8
+
9
+ def placeholder(type = nil)
10
+ # %[parameter][flags][width][.precision][length]type (see https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification)
11
+ lucky = lambda { rand > 0.5 }
12
+ placeholder = '%'
13
+ placeholder += (rand * 20).to_i.to_s + '$' if lucky.call
14
+ placeholder += '-+ 0#'.chars.to_a.sample if lucky.call
15
+ placeholder += (0.upto(20).map(&:to_s) << "*").sample if lucky.call
16
+ placeholder += '.' + (0.upto(20).map(&:to_s) << "*").sample if lucky.call
17
+ placeholder += %w(h hh l ll L z j t).sample if lucky.call
18
+ placeholder += type || 'diufFeEgGxXocpaA'.chars.to_a.sample # this does not contain s or @ because strings are a special case
19
+ end
20
+ end
21
+
22
+ class PlaceholderTest < TwineTestCase
23
+ class ToAndroid < PlaceholderTestCase
24
+ def to_android(value)
25
+ Twine::Placeholders.convert_placeholders_from_twine_to_android(value)
26
+ end
27
+
28
+ def test_replaces_string_placeholder
29
+ placeholder = placeholder('@')
30
+ expected = placeholder
31
+ expected[-1] = 's'
32
+ assert_equal "some #{expected} value", to_android("some #{placeholder} value")
33
+ end
34
+
35
+ def test_does_not_change_regular_at_signs
36
+ input = "some @ more @@ signs @"
37
+ assert_equal input, to_android(input)
38
+ end
39
+
40
+ def test_does_not_modify_single_percent_signs
41
+ assert_equal "some % value", to_android("some % value")
42
+ end
43
+
44
+ def test_escapes_single_percent_signs_if_placeholder_present
45
+ assert_starts_with "some %% v", to_android("some % value #{placeholder}")
46
+ end
47
+
48
+ def test_does_not_modify_double_percent_signs
49
+ assert_equal "some %% value", to_android("some %% value")
50
+ end
51
+
52
+ def test_does_not_modify_double_percent_signs_if_placeholder_present
53
+ assert_starts_with "some %% v", to_android("some %% value #{placeholder}")
54
+ end
55
+
56
+ def test_does_not_modify_single_placeholder
57
+ input = "some #{placeholder} text"
58
+ assert_equal input, to_android(input)
59
+ end
60
+
61
+ def test_numbers_multiple_placeholders
62
+ assert_equal "first %1$d second %2$f", to_android("first %d second %f")
63
+ end
64
+
65
+ def test_does_not_modify_numbered_placeholders
66
+ input = "second %2$f first %1$d"
67
+ assert_equal input, to_android(input)
68
+ end
69
+
70
+ def test_raises_an_error_when_mixing_numbered_and_non_numbered_placeholders
71
+ assert_raises Twine::Error do
72
+ to_android("some %d second %2$f")
73
+ end
74
+ end
75
+ end
76
+
77
+ class FromAndroid < PlaceholderTestCase
78
+ def from_android(value)
79
+ Twine::Placeholders.convert_placeholders_from_android_to_twine(value)
80
+ end
81
+
82
+ def test_replaces_string_placeholder
83
+ assert_equal "some %@ value", from_android("some %s value")
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,58 @@
1
+ require 'twine_test_case'
2
+
3
+ class TestStringsFile < TwineTestCase
4
+ class Reading < TwineTestCase
5
+ def setup
6
+ super
7
+
8
+ @strings = Twine::StringsFile.new
9
+ @strings.read fixture('twine_accent_values.txt')
10
+ end
11
+
12
+ def test_reading_keeps_leading_accent
13
+ assert_equal '`value', @strings.strings_map['value_with_leading_accent'].translations['en']
14
+ end
15
+
16
+ def test_reading_keeps_trailing_accent
17
+ assert_equal 'value`', @strings.strings_map['value_with_trailing_accent'].translations['en']
18
+ end
19
+
20
+ def test_reading_keeps_leading_space
21
+ assert_equal ' value', @strings.strings_map['value_with_leading_space'].translations['en']
22
+ end
23
+
24
+ def test_reading_keeps_trailing_space
25
+ assert_equal 'value ', @strings.strings_map['value_with_trailing_space'].translations['en']
26
+ end
27
+
28
+ def test_reading_keeps_wrapping_spaces
29
+ assert_equal ' value ', @strings.strings_map['value_wrapped_by_spaces'].translations['en']
30
+ end
31
+
32
+ def test_reading_keeps_wrapping_accents
33
+ assert_equal '`value`', @strings.strings_map['value_wrapped_by_accents'].translations['en']
34
+ end
35
+ end
36
+
37
+ class Writing < TwineTestCase
38
+
39
+ def test_accent_wrapping
40
+ @strings = build_twine_file 'en' do
41
+ add_section 'Section' do
42
+ add_row value_with_leading_accent: '`value'
43
+ add_row value_with_trailing_accent: 'value`'
44
+ add_row value_with_leading_space: ' value'
45
+ add_row value_with_trailing_space: 'value '
46
+ add_row value_wrapped_by_spaces: ' value '
47
+ add_row value_wrapped_by_accents: '`value`'
48
+ end
49
+ end
50
+
51
+ @strings.write @output_path
52
+
53
+ assert_equal content('twine_accent_values.txt'), output_content
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,47 @@
1
+ require 'twine_test_case'
2
+
3
+ class TestStringsRow < TwineTestCase
4
+ def setup
5
+ super
6
+
7
+ @reference = Twine::StringsRow.new 'reference-key'
8
+ @reference.comment = 'reference comment'
9
+ @reference.tags = ['ref1']
10
+ @reference.translations['en'] = 'ref-value'
11
+
12
+ @row = Twine::StringsRow.new 'key'
13
+ @row.reference_key = @reference.key
14
+ @row.reference = @reference
15
+ end
16
+
17
+ def test_reference_comment_used
18
+ assert_equal 'reference comment', @row.comment
19
+ end
20
+
21
+ def test_reference_comment_override
22
+ @row.comment = 'row comment'
23
+
24
+ assert_equal 'row comment', @row.comment
25
+ end
26
+
27
+ def test_reference_tags_used
28
+ assert @row.matches_tags?(['ref1'], false)
29
+ end
30
+
31
+ def test_reference_tags_override
32
+ @row.tags = ['tag1']
33
+
34
+ refute @row.matches_tags?(['ref1'], false)
35
+ assert @row.matches_tags?(['tag1'], false)
36
+ end
37
+
38
+ def test_reference_translation_used
39
+ assert_equal 'ref-value', @row.translated_string_for_lang('en')
40
+ end
41
+
42
+ def test_reference_translation_override
43
+ @row.translations['en'] = 'value'
44
+
45
+ assert_equal 'value', @row.translated_string_for_lang('en')
46
+ end
47
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'command_test_case'
4
+
5
+ class TestValidateStringsFile < CommandTestCase
6
+ def setup
7
+ super
8
+ @options = { strings_file: 'input.txt' }
9
+
10
+ @twine_file = build_twine_file 'en' do
11
+ add_section 'Section 1' do
12
+ add_row key1: 'value1', tags: ['tag1']
13
+ add_row key2: 'value2', tags: ['tag1']
14
+ end
15
+
16
+ add_section 'Section 2' do
17
+ add_row key3: 'value3', tags: ['tag1', 'tag2']
18
+ add_row key4: 'value4', tags: ['tag2']
19
+ end
20
+ end
21
+ end
22
+
23
+ def random_row
24
+ @twine_file.strings_map[@twine_file.strings_map.keys.sample]
25
+ end
26
+
27
+ def test_recognizes_valid_file
28
+ Twine::Runner.new(@options, @twine_file).validate_strings_file
29
+ assert_equal "input.txt is valid.\n", Twine::stdout.string
30
+ end
31
+
32
+ def test_reports_duplicate_keys
33
+ @twine_file.sections[0].rows << random_row
34
+
35
+ assert_raises Twine::Error do
36
+ Twine::Runner.new(@options, @twine_file).validate_strings_file
37
+ end
38
+ end
39
+
40
+ def test_reports_missing_tags
41
+ random_row.tags.clear
42
+
43
+ assert_raises Twine::Error do
44
+ Twine::Runner.new(@options, @twine_file).validate_strings_file
45
+ end
46
+ end
47
+
48
+ def test_reports_invalid_characters_in_keys
49
+ random_row.key[0] = "!?;:,^`´'\"\\|/(){}[]~-+*=#$%".chars.to_a.sample
50
+
51
+ assert_raises Twine::Error do
52
+ Twine::Runner.new(@options, @twine_file).validate_strings_file
53
+ end
54
+ end
55
+ end