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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f6d46a283d86aa50c84b7b229f2e3159edd72816
4
- data.tar.gz: c412942ce007dbd236cc32b4b9ceadf99734cbe4
2
+ SHA256:
3
+ metadata.gz: d8667f50ad7147b8b7edac0d82ce542c2ecc973604c8334e878513b6acaa9a14
4
+ data.tar.gz: 1ceb2b2b18dea8a3585bd91a1b427b30ebd9c888817dcf7140b3d211cd9e157c
5
5
  SHA512:
6
- metadata.gz: b44a73df4eb789f78cbec2a37cbe496f7cda95ea62b7e56c2eb189d5f26b5c0a5ef497b7c185a78961ba263df64e386a7efc2b58a58dab540eb0ea9a81b68922
7
- data.tar.gz: c9814e961ac0c8eb36c61122c89b3d9bd321133dd8b0441e2cfdd352a65e71d74cf8cae31d0da9903988e80c524fef332dd5fc56c2d4dc622e3f581364770636
6
+ metadata.gz: a961953ec7b74510c10e17114b84c227bac43117a869ba4254d7410a16ea9413e4133a2c9f099c3d67922fd6e1632d841f366e79d28687e61f73861721dea43a
7
+ data.tar.gz: 954b69b0d6e0fadec38cf145e0e1772eb1fde0259337b45ffac172d41b6f6c031b33373c768cfbb48ad3e9cfb1565dc9e91db8e825023421fdc30098439a4d86
@@ -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 = /msgid *"(.*)"$/
19
- value_regex = /msgstr *"(.*)"$/m
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
- "##\n # Django Strings File\n # Generated by Twine #{Twine::VERSION}\n # Language: #{lang}\nmsgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=UTF-8\\n\""
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
- "#--------- #{section.name} ---------#\n"
62
+ "# --------- #{section.name} --------- #\n"
61
63
  end
62
64
 
63
65
  def format_definition(definition, lang)
@@ -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
@@ -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."
@@ -1,3 +1,3 @@
1
1
  module Twine
2
- VERSION = '1.0.6'
2
+ VERSION = '1.1'
3
3
  end
@@ -1,12 +1,11 @@
1
- ##
2
- # Django Strings File
3
- # Generated by Twine <%= Twine::VERSION %>
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
- #--------- Section 1 ---------#
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
- #--------- Section 2 ---------#
20
+ # --------- Section 2 --------- #
22
21
 
23
22
  # base translation: "value3-english"
24
23
  msgid "key3"
@@ -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
@@ -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>' => '&lt;b>bold&lt;/b>',
143
+ '<i>italic</i>' => '&lt;i>italic&lt;/i>',
144
+ '<u>underline</u>' => '&lt;u>underline&lt;/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
@@ -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.0.6
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: 2019-05-28 00:00:00.000000000 Z
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: '1.1'
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: '1.1'
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: '10.4'
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: '10.4'
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/mobiata/twine
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.0'
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
- rubyforge_project:
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.