twine 1.0.6 → 1.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.
- 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.
|