twine 1.0.6 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/twine/cli.rb +11 -0
- data/lib/twine/formatters/android.rb +1 -1
- data/lib/twine/formatters/django.rb +7 -5
- data/lib/twine/placeholders.rb +6 -0
- data/lib/twine/runner.rb +7 -0
- data/lib/twine/version.rb +1 -1
- data/test/fixtures/formatter_django.po +5 -6
- data/test/test_cli.rb +10 -0
- data/test/test_formatters.rb +28 -0
- data/test/test_placeholders.rb +17 -0
- data/test/test_validate_twine_file.rb +8 -0
- metadata +9 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d8667f50ad7147b8b7edac0d82ce542c2ecc973604c8334e878513b6acaa9a14
|
4
|
+
data.tar.gz: 1ceb2b2b18dea8a3585bd91a1b427b30ebd9c888817dcf7140b3d211cd9e157c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a961953ec7b74510c10e17114b84c227bac43117a869ba4254d7410a16ea9413e4133a2c9f099c3d67922fd6e1632d841f366e79d28687e61f73861721dea43a
|
7
|
+
data.tar.gz: 954b69b0d6e0fadec38cf145e0e1772eb1fde0259337b45ffac172d41b6f6c031b33373c768cfbb48ad3e9cfb1565dc9e91db8e825023421fdc30098439a4d86
|
data/lib/twine/cli.rb
CHANGED
@@ -45,6 +45,14 @@ module Twine
|
|
45
45
|
files are UTF-16 without BOM, you need to specify if it's UTF-16LE or UTF16-BE.
|
46
46
|
DESC
|
47
47
|
},
|
48
|
+
escape_all_tags: {
|
49
|
+
switch: ['--[no-]escape-all-tags'],
|
50
|
+
description: <<-DESC,
|
51
|
+
Always escape all HTML tags. By default the Android formatter will ONLY escape styling tags, if a
|
52
|
+
string also contains placeholders. This flag enforces that styling tags are escaped regardless of
|
53
|
+
placeholders.
|
54
|
+
DESC
|
55
|
+
},
|
48
56
|
file_name: {
|
49
57
|
switch: ['-n', '--file-name FILE_NAME'],
|
50
58
|
description: 'This flag may be used to overwrite the default file name of the format.'
|
@@ -111,6 +119,7 @@ module Twine
|
|
111
119
|
optional_options: [
|
112
120
|
:developer_language,
|
113
121
|
:encoding,
|
122
|
+
:escape_all_tags,
|
114
123
|
:format,
|
115
124
|
:include,
|
116
125
|
:languages,
|
@@ -133,6 +142,7 @@ module Twine
|
|
133
142
|
:create_folders,
|
134
143
|
:developer_language,
|
135
144
|
:encoding,
|
145
|
+
:escape_all_tags,
|
136
146
|
:file_name,
|
137
147
|
:format,
|
138
148
|
:include,
|
@@ -152,6 +162,7 @@ module Twine
|
|
152
162
|
optional_options: [
|
153
163
|
:developer_language,
|
154
164
|
:encoding,
|
165
|
+
:escape_all_tags,
|
155
166
|
:include,
|
156
167
|
:quiet,
|
157
168
|
:tags,
|
@@ -125,7 +125,7 @@ module Twine
|
|
125
125
|
# if not, escape opening angle brackes unless it's a supported styling tag
|
126
126
|
# https://github.com/scelis/twine/issues/212
|
127
127
|
# https://stackoverflow.com/questions/3235131/#18199543
|
128
|
-
if number_of_twine_placeholders(value) > 0
|
128
|
+
if number_of_twine_placeholders(value) > 0 or @options[:escape_all_tags]
|
129
129
|
# matches all `<` but <![CDATA
|
130
130
|
angle_bracket = /<(?!(\/?(\!\[CDATA)))/
|
131
131
|
else
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Twine
|
2
2
|
module Formatters
|
3
|
+
# For a description of the .po file format, see https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
3
4
|
class Django < Abstract
|
4
5
|
def format_name
|
5
6
|
'django'
|
@@ -14,9 +15,9 @@ module Twine
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def read(io, lang)
|
17
|
-
comment_regex =
|
18
|
-
key_regex =
|
19
|
-
value_regex =
|
18
|
+
comment_regex = /^\s*#\. *"?(.*)"?$/
|
19
|
+
key_regex = /^msgid *"(.*)"$/
|
20
|
+
value_regex = /^msgstr *"(.*)"$/m
|
20
21
|
|
21
22
|
while line = io.gets
|
22
23
|
comment_match = comment_regex.match(line)
|
@@ -53,11 +54,12 @@ module Twine
|
|
53
54
|
end
|
54
55
|
|
55
56
|
def format_header(lang)
|
56
|
-
|
57
|
+
# see https://www.gnu.org/software/trans-coord/manual/gnun/html_node/PO-Header.html for details
|
58
|
+
"# Django Strings File\n# Generated by Twine #{Twine::VERSION}\n# Language: #{lang}\nmsgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=UTF-8\\n\""
|
57
59
|
end
|
58
60
|
|
59
61
|
def format_section_header(section)
|
60
|
-
"
|
62
|
+
"# --------- #{section.name} --------- #\n"
|
61
63
|
end
|
62
64
|
|
63
65
|
def format_definition(definition, lang)
|
data/lib/twine/placeholders.rb
CHANGED
@@ -72,5 +72,11 @@ module Twine
|
|
72
72
|
def convert_placeholders_from_flash_to_twine(input)
|
73
73
|
input.gsub /\{\d+\}/, '%@'
|
74
74
|
end
|
75
|
+
|
76
|
+
# Python supports placeholders in the form of `%(amount)03d`
|
77
|
+
# see https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting
|
78
|
+
def contains_python_specific_placeholder(input)
|
79
|
+
/%\([a-zA-Z0-9_-]+\)#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH}#{PLACEHOLDER_TYPES}/.match(input) != nil
|
80
|
+
end
|
75
81
|
end
|
76
82
|
end
|
data/lib/twine/runner.rb
CHANGED
@@ -241,6 +241,7 @@ module Twine
|
|
241
241
|
duplicate_keys = Set.new
|
242
242
|
keys_without_tags = Set.new
|
243
243
|
invalid_keys = Set.new
|
244
|
+
keys_with_python_only_placeholders = Set.new
|
244
245
|
valid_key_regex = /^[A-Za-z0-9_]+$/
|
245
246
|
|
246
247
|
@twine_file.sections.each do |section|
|
@@ -253,6 +254,8 @@ module Twine
|
|
253
254
|
keys_without_tags.add(definition.key) if definition.tags == nil or definition.tags.length == 0
|
254
255
|
|
255
256
|
invalid_keys << definition.key unless definition.key =~ valid_key_regex
|
257
|
+
|
258
|
+
keys_with_python_only_placeholders << definition.key if definition.translations.values.any? { |v| Placeholders.contains_python_specific_placeholder(v) }
|
256
259
|
end
|
257
260
|
end
|
258
261
|
|
@@ -275,6 +278,10 @@ module Twine
|
|
275
278
|
errors << "Found key(s) with invalid characters:\n#{join_keys.call(invalid_keys)}"
|
276
279
|
end
|
277
280
|
|
281
|
+
unless keys_with_python_only_placeholders.empty?
|
282
|
+
errors << "Found key(s) with placeholders that are only supported by Python:\n#{join_keys.call(keys_with_python_only_placeholders)}"
|
283
|
+
end
|
284
|
+
|
278
285
|
raise Twine::Error.new errors.join("\n\n") unless errors.empty?
|
279
286
|
|
280
287
|
Twine::stdout.puts "#{@options[:twine_file]} is valid."
|
data/lib/twine/version.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
# Language: en
|
1
|
+
# Django Strings File
|
2
|
+
# Generated by Twine <%= Twine::VERSION %>
|
3
|
+
# Language: en
|
5
4
|
msgid ""
|
6
5
|
msgstr ""
|
7
6
|
"Content-Type: text/plain; charset=UTF-8\n"
|
8
7
|
|
9
|
-
|
8
|
+
# --------- Section 1 --------- #
|
10
9
|
|
11
10
|
#. comment key1
|
12
11
|
# base translation: "value1-english"
|
@@ -18,7 +17,7 @@ msgid "key2"
|
|
18
17
|
msgstr "value2-english"
|
19
18
|
|
20
19
|
|
21
|
-
|
20
|
+
# --------- Section 2 --------- #
|
22
21
|
|
23
22
|
# base translation: "value3-english"
|
24
23
|
msgid "key3"
|
data/test/test_cli.rb
CHANGED
@@ -48,6 +48,13 @@ class CLITest < TwineTest
|
|
48
48
|
assert_equal 'UTF16', @options[:encoding]
|
49
49
|
end
|
50
50
|
|
51
|
+
def assert_option_escape_all_tags
|
52
|
+
parse_with "--escape-all-tags"
|
53
|
+
assert @options[:escape_all_tags]
|
54
|
+
parse_with "--no-escape-all-tags"
|
55
|
+
refute @options[:escape_all_tags]
|
56
|
+
end
|
57
|
+
|
51
58
|
def assert_option_format
|
52
59
|
random_format = Twine::Formatters.formatters.sample.format_name.downcase
|
53
60
|
parse_with "--format #{random_format}"
|
@@ -177,6 +184,7 @@ class TestGenerateLocalizationFileCLI < CLITest
|
|
177
184
|
assert_help
|
178
185
|
assert_option_developer_language
|
179
186
|
assert_option_encoding
|
187
|
+
assert_option_escape_all_tags
|
180
188
|
assert_option_format
|
181
189
|
assert_option_include
|
182
190
|
assert_option_single_language
|
@@ -217,6 +225,7 @@ class TestGenerateAllLocalizationFilesCLI < CLITest
|
|
217
225
|
assert_help
|
218
226
|
assert_option_developer_language
|
219
227
|
assert_option_encoding
|
228
|
+
assert_option_escape_all_tags
|
220
229
|
assert_option_format
|
221
230
|
assert_option_include
|
222
231
|
assert_option_quiet
|
@@ -268,6 +277,7 @@ class TestGenerateLocalizationArchiveCLI < CLITest
|
|
268
277
|
assert_help
|
269
278
|
assert_option_developer_language
|
270
279
|
assert_option_encoding
|
280
|
+
assert_option_escape_all_tags
|
271
281
|
assert_option_include
|
272
282
|
assert_option_quiet
|
273
283
|
assert_option_tags
|
data/test/test_formatters.rb
CHANGED
@@ -138,6 +138,11 @@ class TestAndroidFormatter < FormatterTest
|
|
138
138
|
'<xliff:g id="42">untouched</xliff:g>' => '<xliff:g id="42">untouched</xliff:g>',
|
139
139
|
'<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>'
|
140
140
|
}
|
141
|
+
@escape_all_test_values = {
|
142
|
+
'<b>bold</b>' => '<b>bold</b>',
|
143
|
+
'<i>italic</i>' => '<i>italic</i>',
|
144
|
+
'<u>underline</u>' => '<u>underline</u>'
|
145
|
+
}
|
141
146
|
end
|
142
147
|
|
143
148
|
def test_read_format
|
@@ -217,6 +222,11 @@ class TestAndroidFormatter < FormatterTest
|
|
217
222
|
@formatter.set_translation_for_key 'key1', 'en', input
|
218
223
|
assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
|
219
224
|
end
|
225
|
+
|
226
|
+
@escape_all_test_values.each do |expected, input|
|
227
|
+
@formatter.set_translation_for_key 'key1', 'en', input
|
228
|
+
assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
|
229
|
+
end
|
220
230
|
end
|
221
231
|
|
222
232
|
def test_format_file
|
@@ -245,6 +255,11 @@ class TestAndroidFormatter < FormatterTest
|
|
245
255
|
@escape_test_values.each do |input, expected|
|
246
256
|
assert_equal expected, @formatter.format_value(input)
|
247
257
|
end
|
258
|
+
|
259
|
+
@formatter.options.merge!({ escape_all_tags: true })
|
260
|
+
@escape_all_test_values.each do |input, expected|
|
261
|
+
assert_equal expected, @formatter.format_value(input)
|
262
|
+
end
|
248
263
|
end
|
249
264
|
|
250
265
|
def test_format_value_escapes_non_resource_identifier_at_signs
|
@@ -539,6 +554,19 @@ class TestDjangoFormatter < FormatterTest
|
|
539
554
|
language = %w(en-GB de fr).sample
|
540
555
|
assert_equal language, @formatter.determine_language_given_path("/output/#{language}/#{@formatter.default_file_name}")
|
541
556
|
end
|
557
|
+
|
558
|
+
def test_ignores_commented_out_strings
|
559
|
+
content = <<-EOCONTENT
|
560
|
+
#~ msgid "foo"
|
561
|
+
#~ msgstr "This should be ignored"
|
562
|
+
EOCONTENT
|
563
|
+
|
564
|
+
io = StringIO.new(content)
|
565
|
+
|
566
|
+
@formatter.read io, 'en'
|
567
|
+
|
568
|
+
assert_nil @empty_twine_file.definitions_by_key["foo"]
|
569
|
+
end
|
542
570
|
end
|
543
571
|
|
544
572
|
class TestFlashFormatter < FormatterTest
|
data/test/test_placeholders.rb
CHANGED
@@ -122,4 +122,21 @@ class PlaceholderTest < TwineTest
|
|
122
122
|
assert_equal "some %@ more %@ text %@", from_flash("some {0} more {1} text {2}")
|
123
123
|
end
|
124
124
|
end
|
125
|
+
|
126
|
+
class PythonPlaceholder < PlaceholderTest
|
127
|
+
def test_negative_for_regular_placeholders
|
128
|
+
assert_equal false, Twine::Placeholders.contains_python_specific_placeholder(placeholder)
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_positive_for_named_placeholders
|
132
|
+
inputs = [
|
133
|
+
"%(language)s has",
|
134
|
+
"For %(number)03d quotes",
|
135
|
+
"bought on %(app_name)s"
|
136
|
+
]
|
137
|
+
inputs.each do |input|
|
138
|
+
assert_equal true, Twine::Placeholders.contains_python_specific_placeholder(input)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
125
142
|
end
|
@@ -58,4 +58,12 @@ class TestValidateTwineFile < CommandTest
|
|
58
58
|
Twine::Runner.new(@options.merge(pedantic: true), @twine_file).validate_twine_file
|
59
59
|
end
|
60
60
|
end
|
61
|
+
|
62
|
+
def test_reports_python_specific_placeholders
|
63
|
+
random_definition.translations["en"] = "%(python_only)s"
|
64
|
+
|
65
|
+
assert_raises Twine::Error do
|
66
|
+
Twine::Runner.new(@options, @twine_file).validate_twine_file
|
67
|
+
end
|
68
|
+
end
|
61
69
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: '1.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Celis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: safe_yaml
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '13.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '13.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,7 +158,7 @@ files:
|
|
158
158
|
- test/test_validate_twine_file.rb
|
159
159
|
- test/twine_file_dsl.rb
|
160
160
|
- test/twine_test.rb
|
161
|
-
homepage: https://github.com/
|
161
|
+
homepage: https://github.com/scelis/twine
|
162
162
|
licenses:
|
163
163
|
- BSD-3-Clause
|
164
164
|
metadata: {}
|
@@ -170,15 +170,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
170
170
|
requirements:
|
171
171
|
- - ">="
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: '2.
|
173
|
+
version: '2.4'
|
174
174
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
175
|
requirements:
|
176
176
|
- - ">="
|
177
177
|
- !ruby/object:Gem::Version
|
178
178
|
version: '0'
|
179
179
|
requirements: []
|
180
|
-
|
181
|
-
rubygems_version: 2.5.2.3
|
180
|
+
rubygems_version: 3.1.2
|
182
181
|
signing_key:
|
183
182
|
specification_version: 4
|
184
183
|
summary: Manage strings and their translations for your iOS, Android and other projects.
|