dotstrings 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +23 -4
  4. data/lib/dotstrings/file.rb +51 -4
  5. data/lib/dotstrings/item.rb +11 -0
  6. data/lib/dotstrings/parser.rb +35 -10
  7. data/lib/dotstrings/version.rb +1 -1
  8. data/lib/dotstrings.rb +6 -4
  9. metadata +4 -54
  10. data/.editorconfig +0 -13
  11. data/.github/workflows/ci.yml +0 -33
  12. data/.gitignore +0 -5
  13. data/.rubocop.yml +0 -41
  14. data/Gemfile +0 -5
  15. data/Gemfile.lock +0 -57
  16. data/Rakefile +0 -10
  17. data/dotstrings.gemspec +0 -29
  18. data/test/fixtures/escaped_backslashes.strings +0 -2
  19. data/test/fixtures/escaped_carriage_returns.strings +0 -2
  20. data/test/fixtures/escaped_new_lines.strings +0 -2
  21. data/test/fixtures/escaped_nil.strings +0 -2
  22. data/test/fixtures/escaped_quotes.strings +0 -2
  23. data/test/fixtures/escaped_single_quotes.strings +0 -2
  24. data/test/fixtures/escaped_tabs.strings +0 -2
  25. data/test/fixtures/escaped_unicode.strings +0 -2
  26. data/test/fixtures/escaped_unicode~bad_surrogate_order.strings +0 -1
  27. data/test/fixtures/escaped_unicode~duplicated_high_surrogate.strings +0 -1
  28. data/test/fixtures/escaped_unicode~incomplete_surrogate_pair.strings +0 -1
  29. data/test/fixtures/escaped_unicode~non_surrogate_after_high_surrogate.strings +0 -1
  30. data/test/fixtures/utf16be_bom.strings +0 -0
  31. data/test/fixtures/utf16le_bom.strings +0 -0
  32. data/test/fixtures/utf8_bom.strings +0 -2
  33. data/test/fixtures/valid.strings +0 -8
  34. data/test/test_dotstrings.rb +0 -126
  35. data/test/test_file.rb +0 -116
  36. data/test/test_helper.rb +0 -10
  37. data/test/test_item.rb +0 -15
  38. data/test/test_parser.rb +0 -50
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7f215ea68d4b6ce65de6f15811800b976eb722b89a4618bce4503338508df51
4
- data.tar.gz: 51a72c22c7233b00df29f97d876abb3e59517de4bf0b78797ab8897d122250fe
3
+ metadata.gz: 334f5c70b24599274f9e48180edc217e272305dc0880ceceb12588314ee29173
4
+ data.tar.gz: de469892a932333e2995b0b9ee8919fed43861186805f343f6f0ba43e70d2297
5
5
  SHA512:
6
- metadata.gz: 0d9247c103eb79e3e908a55af5661de97cae316cc36660c7c872a869f077a9875060eab7bab7caa0a9aa8c2ef82d6ce7c5b9acede49fa1227790b4aad53b205d
7
- data.tar.gz: af144c8d6b3f9f552fc71db2df31db95f1189575fa8eb9e2c5f97ab0a15234024bcd97692218be4bbd04f7352f6a3d63966b4ff4948df84379158733583920ad
6
+ metadata.gz: 17b42c2392f3bb05695e64c0d8f61134f9b78edb598ccefbd90ace096705cf871b4861fac378646ace56ebc545e88987dd46c5342bebfa8d3575e957a45423d7
7
+ data.tar.gz: eeabf73dfe816634936ebf46eba3e09e729842c2af5497674e7ee3d474c46b7e58f7838904cb60c6c075e4c42a64b48a8e9b99d1624e2a39202e601492ef615a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.5.0] - 2022-09-24
4
+ * Added `strict` parameter to the parser to allow for more lenient parsing. This is useful in cases where we don't want the parser to raise an error when encountering multiple comments or escaped characters that don't need to be escaped.
5
+
6
+ ## [v0.4.0] - 2022-09-18
7
+ ### Added
8
+ * Added `DotStrings::File#each`, `DotStrings::File#length`, `DotStrings::File#count`, and `DotStrings::File#empty?` methods.
9
+ * Allow comparing `DotStrings::File` objects.
10
+
3
11
  ## [v0.3.0] - 2022-08-07
4
12
  ### Changed
5
13
  * Improved unicode code point parsing and validation.
@@ -19,6 +27,9 @@
19
27
  ### Added
20
28
  * Initial release.
21
29
 
30
+ [v0.5.0]: https://github.com/raymondjavaxx/dotstrings/releases/tag/v0.5.0
31
+ [v0.4.0]: https://github.com/raymondjavaxx/dotstrings/releases/tag/v0.4.0
32
+ [v0.3.0]: https://github.com/raymondjavaxx/dotstrings/releases/tag/v0.3.0
22
33
  [v0.2.0]: https://github.com/raymondjavaxx/dotstrings/releases/tag/v0.2.0
23
34
  [v0.1.1]: https://github.com/raymondjavaxx/dotstrings/releases/tag/v0.1.1
24
35
  [v0.1.0]: https://github.com/raymondjavaxx/dotstrings/releases/tag/v0.1.0
data/README.md CHANGED
@@ -36,15 +36,32 @@ file.items.each do |item|
36
36
  end
37
37
  ```
38
38
 
39
- ## Examples
39
+ ## Strict Mode
40
+
41
+ By default, the parser runs in *strict mode*. This means that it will raise a `DotStrings::ParsingError` if it encounters comments that are not tied to a key-value pair. For example, the following file will raise an error because the first comment is not followed by a key-value pair:
40
42
 
41
- ### Listing keys
43
+ ```
44
+ /* Spanish localizations */
45
+
46
+ /* Title for a button for accepting something */
47
+ "Accept" = "Aceptar";
48
+ ```
49
+
50
+ In *strict mode*, the parser will also raise an error if it encounters escaped characters that don't need to be escaped. For example, the following file will raise an error because the `?` character doesn't need to be escaped:
51
+
52
+ ```
53
+ /* Confirmation message */
54
+ "Are you sure\?" = "¿Estás seguro\?";
55
+ ```
56
+
57
+ If you want to disable *strict mode*, you can pass `strict: false` to the `DotStrings.parse_file()` method. This will match the behavior of Apple's own parser, which is more lenient.
42
58
 
43
59
  ```ruby
44
- puts file.keys
45
- # => ["key 1", "key 2", ...]
60
+ file = DotStrings.parse_file('es-ES/Localizable.strings', strict: false)
46
61
  ```
47
62
 
63
+ ## Examples
64
+
48
65
  ### Accessing items by key
49
66
 
50
67
  ```ruby
@@ -73,3 +90,5 @@ file << DotStrings::Item(
73
90
  ```ruby
74
91
  File.write('en-US/Localizable.strings', file.to_s)
75
92
  ```
93
+
94
+ For more examples, consult the [documentation](https://www.rubydoc.info/gems/dotstrings/DotStrings) or the [test suite](test).
@@ -45,12 +45,13 @@ module DotStrings
45
45
  # file = DotStrings::File.parse(io)
46
46
  #
47
47
  # @param io [IO] The IO object to parse.
48
+ # @param strict [Boolean] Whether to parse in strict mode.
48
49
  # @return [DotStrings::File] The parsed file.
49
50
  # @raise [DotStrings::ParsingError] if the file could not be parsed.
50
- def self.parse(io)
51
+ def self.parse(io, strict: true)
51
52
  items = []
52
53
 
53
- parser = Parser.new
54
+ parser = Parser.new(strict: strict)
54
55
  parser.on_item { |item| items << item }
55
56
  parser << normalize_encoding(io.read)
56
57
 
@@ -64,11 +65,12 @@ module DotStrings
64
65
  # file = DotStrings::File.parse_file('path/to/en.lproj/Localizable.strings')
65
66
  #
66
67
  # @param path [String] The path to the file to parse.
68
+ # @param strict [Boolean] Whether to parse in strict mode.
67
69
  # @return [DotStrings::File] The parsed file.
68
70
  # @raise [DotStrings::ParsingError] if the file could not be parsed.
69
- def self.parse_file(path)
71
+ def self.parse_file(path, strict: true)
70
72
  ::File.open(path, 'r') do |file|
71
- parse(file)
73
+ parse(file, strict: strict)
72
74
  end
73
75
  end
74
76
 
@@ -131,6 +133,51 @@ module DotStrings
131
133
  self
132
134
  end
133
135
 
136
+ ##
137
+ # Calls the given block once for each item in the file.
138
+ #
139
+ # @param block [Proc] The block to call.
140
+ # @example
141
+ # file.each do |item|
142
+ # puts "#{item.key} > #{item.value}"
143
+ # end
144
+ def each(&block)
145
+ @items.each(&block)
146
+ self
147
+ end
148
+
149
+ ##
150
+ # Returns the number of items in the file.
151
+ def length
152
+ @items.length
153
+ end
154
+
155
+ ##
156
+ # Returns the number of items in the file.
157
+ #
158
+ # If a block is given, it will count the number of items for which the block returns true.
159
+ #
160
+ # @example
161
+ # file.count # => 10
162
+ # file.count { |item| item.key.start_with?('button.') } # => 3
163
+ def count(&block)
164
+ @items.count(&block)
165
+ end
166
+
167
+ ##
168
+ # Returns `true` if the file doen't contain any items.
169
+ def empty?
170
+ @items.empty?
171
+ end
172
+
173
+ def ==(other)
174
+ eql?(other)
175
+ end
176
+
177
+ def eql?(other)
178
+ other.is_a?(self.class) && @items.eql?(other.items)
179
+ end
180
+
134
181
  ##
135
182
  # Serializes the file to a string.
136
183
  #
@@ -12,6 +12,17 @@ module DotStrings
12
12
  @value = value
13
13
  end
14
14
 
15
+ def ==(other)
16
+ eql?(other)
17
+ end
18
+
19
+ def eql?(other)
20
+ other.is_a?(Item) &&
21
+ comment == other.comment &&
22
+ key == other.key &&
23
+ value == other.value
24
+ end
25
+
15
26
  ##
16
27
  # Serializes the item to string.
17
28
  #
@@ -7,6 +7,9 @@ module DotStrings
7
7
 
8
8
  ##
9
9
  # Parser for .strings files.
10
+ #
11
+ # You can use this class directly, but it is recommended to use
12
+ # {File.parse} and {File.parse_file} wrappers instead.
10
13
  class Parser
11
14
  # Special tokens
12
15
  TOK_SLASH = '/'
@@ -39,7 +42,13 @@ module DotStrings
39
42
  STATE_UNICODE_SURROGATE = 11
40
43
  STATE_UNICODE_SURROGATE_U = 12
41
44
 
42
- def initialize
45
+ ##
46
+ # Returns a new Parser instance.
47
+ #
48
+ # @param strict [Boolean] Whether to parse in strict mode.
49
+ def initialize(strict: true)
50
+ @strict = strict
51
+
43
52
  @state = STATE_START
44
53
  @temp_state = nil
45
54
 
@@ -101,11 +110,7 @@ module DotStrings
101
110
  @buffer << ch
102
111
  end
103
112
  when STATE_COMMENT_END
104
- if ch == TOK_QUOTE
105
- @state = STATE_KEY
106
- else
107
- raise_error("Unexpected character '#{ch}'") unless whitespace?(ch)
108
- end
113
+ comment_end(ch)
109
114
  when STATE_KEY
110
115
  parse_string(ch) do |key|
111
116
  @current_key = key
@@ -188,6 +193,8 @@ module DotStrings
188
193
  end
189
194
  end
190
195
 
196
+ # rubocop:disable Metrics/CyclomaticComplexity
197
+
191
198
  def parse_escaped_character(ch)
192
199
  @escaping = false
193
200
 
@@ -206,11 +213,15 @@ module DotStrings
206
213
  when TOK_ZERO
207
214
  @buffer << "\0"
208
215
  else
209
- raise_error("Unexpected character '#{ch}'")
216
+ raise_error("Unexpected character '#{ch}'") if @strict
217
+ @buffer << ch
210
218
  end
211
219
  end
212
220
 
221
+ # rubocop:enable Metrics/CyclomaticComplexity
222
+
213
223
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
224
+
214
225
  def parse_unicode(ch, &block)
215
226
  raise_error("Unexpected character '#{ch}', expecting a hex digit") unless ch =~ TOK_HEX_DIGIT
216
227
 
@@ -267,19 +278,33 @@ module DotStrings
267
278
  end
268
279
  end
269
280
 
270
- def start_value(ch)
281
+ def start_value(ch, resets: true)
271
282
  case ch
272
283
  when TOK_SLASH
273
284
  @state = STATE_COMMENT_START
274
- reset_state
285
+ reset_state if resets
275
286
  when TOK_QUOTE
276
287
  @state = STATE_KEY
277
- reset_state
288
+ reset_state if resets
278
289
  else
279
290
  raise_error("Unexpected character '#{ch}'") unless whitespace?(ch)
280
291
  end
281
292
  end
282
293
 
294
+ def comment_end(ch)
295
+ if @strict
296
+ # In strict mode, we expect a key to follow the comment.
297
+ if ch == TOK_QUOTE
298
+ @state = STATE_KEY
299
+ else
300
+ raise_error("Unexpected character '#{ch}'") unless whitespace?(ch)
301
+ end
302
+ else
303
+ # In lenient mode, we allow comments to be followed by anything.
304
+ start_value(ch, resets: false)
305
+ end
306
+ end
307
+
283
308
  def reset_state
284
309
  @current_comment = nil
285
310
  @current_key = nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DotStrings
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/dotstrings.rb CHANGED
@@ -12,9 +12,10 @@ module DotStrings
12
12
  # This is a convenience method for {DotStrings::File.parse}.
13
13
  #
14
14
  # @param io [IO] The IO object to parse.
15
+ # @param strict [Boolean] Whether to parse in strict mode.
15
16
  # @return [DotStrings::File] The parsed file.
16
- def self.parse(io)
17
- File.parse(io)
17
+ def self.parse(io, strict: true)
18
+ File.parse(io, strict: strict)
18
19
  end
19
20
 
20
21
  ##
@@ -23,8 +24,9 @@ module DotStrings
23
24
  # This is a convenience method for {DotStrings::File.parse_file}.
24
25
  #
25
26
  # @param path [String] The path to the .strings file to parse.
27
+ # @param strict [Boolean] Whether to parse in strict mode.
26
28
  # @return [DotStrings::File] The parsed file.
27
- def self.parse_file(path)
28
- File.parse_file(path)
29
+ def self.parse_file(path, strict: true)
30
+ File.parse_file(path, strict: strict)
29
31
  end
30
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotstrings
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ramon Torres
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-07 00:00:00.000000000 Z
11
+ date: 2022-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -116,44 +116,15 @@ executables: []
116
116
  extensions: []
117
117
  extra_rdoc_files: []
118
118
  files:
119
- - ".editorconfig"
120
- - ".github/workflows/ci.yml"
121
- - ".gitignore"
122
- - ".rubocop.yml"
123
119
  - CHANGELOG.md
124
- - Gemfile
125
- - Gemfile.lock
126
120
  - LICENSE
127
121
  - README.md
128
- - Rakefile
129
- - dotstrings.gemspec
130
122
  - lib/dotstrings.rb
131
123
  - lib/dotstrings/errors.rb
132
124
  - lib/dotstrings/file.rb
133
125
  - lib/dotstrings/item.rb
134
126
  - lib/dotstrings/parser.rb
135
127
  - lib/dotstrings/version.rb
136
- - test/fixtures/escaped_backslashes.strings
137
- - test/fixtures/escaped_carriage_returns.strings
138
- - test/fixtures/escaped_new_lines.strings
139
- - test/fixtures/escaped_nil.strings
140
- - test/fixtures/escaped_quotes.strings
141
- - test/fixtures/escaped_single_quotes.strings
142
- - test/fixtures/escaped_tabs.strings
143
- - test/fixtures/escaped_unicode.strings
144
- - test/fixtures/escaped_unicode~bad_surrogate_order.strings
145
- - test/fixtures/escaped_unicode~duplicated_high_surrogate.strings
146
- - test/fixtures/escaped_unicode~incomplete_surrogate_pair.strings
147
- - test/fixtures/escaped_unicode~non_surrogate_after_high_surrogate.strings
148
- - test/fixtures/utf16be_bom.strings
149
- - test/fixtures/utf16le_bom.strings
150
- - test/fixtures/utf8_bom.strings
151
- - test/fixtures/valid.strings
152
- - test/test_dotstrings.rb
153
- - test/test_file.rb
154
- - test/test_helper.rb
155
- - test/test_item.rb
156
- - test/test_parser.rb
157
128
  homepage: https://github.com/raymondjavaxx/dotstrings
158
129
  licenses:
159
130
  - MIT
@@ -174,29 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
145
  - !ruby/object:Gem::Version
175
146
  version: '0'
176
147
  requirements: []
177
- rubygems_version: 3.0.1
148
+ rubygems_version: 3.1.2
178
149
  signing_key:
179
150
  specification_version: 4
180
151
  summary: Parse and create .strings files used in localization of iOS and macOS apps.
181
- test_files:
182
- - test/fixtures/escaped_backslashes.strings
183
- - test/fixtures/escaped_carriage_returns.strings
184
- - test/fixtures/escaped_new_lines.strings
185
- - test/fixtures/escaped_nil.strings
186
- - test/fixtures/escaped_quotes.strings
187
- - test/fixtures/escaped_single_quotes.strings
188
- - test/fixtures/escaped_tabs.strings
189
- - test/fixtures/escaped_unicode.strings
190
- - test/fixtures/escaped_unicode~bad_surrogate_order.strings
191
- - test/fixtures/escaped_unicode~duplicated_high_surrogate.strings
192
- - test/fixtures/escaped_unicode~incomplete_surrogate_pair.strings
193
- - test/fixtures/escaped_unicode~non_surrogate_after_high_surrogate.strings
194
- - test/fixtures/utf16be_bom.strings
195
- - test/fixtures/utf16le_bom.strings
196
- - test/fixtures/utf8_bom.strings
197
- - test/fixtures/valid.strings
198
- - test/test_dotstrings.rb
199
- - test/test_file.rb
200
- - test/test_helper.rb
201
- - test/test_item.rb
202
- - test/test_parser.rb
152
+ test_files: []
data/.editorconfig DELETED
@@ -1,13 +0,0 @@
1
- # EditorConfig is awesome: https://EditorConfig.org
2
-
3
- # top-most EditorConfig file
4
- root = true
5
-
6
- [*]
7
- end_of_line = lf
8
- insert_final_newline = true
9
-
10
- [{*.rb,Gemfile,Rakefile,*.yml}]
11
- charset = utf-8
12
- indent_style = space
13
- indent_size = 2
@@ -1,33 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: "*"
6
- pull_request:
7
- branches: "*"
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- ruby-version:
15
- - "2.5"
16
- - "2.6"
17
- - "2.7"
18
- - "3.0"
19
- - "3.1"
20
-
21
- steps:
22
- - uses: actions/checkout@v2
23
- - name: Set up Ruby
24
- uses: ruby/setup-ruby@v1
25
- with:
26
- ruby-version: ${{ matrix.ruby-version }}
27
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
28
- - name: Lint
29
- run: bundle exec rake rubocop
30
- - name: Run tests
31
- run: bundle exec rake
32
- - name: Install
33
- run: bundle exec rake install
data/.gitignore DELETED
@@ -1,5 +0,0 @@
1
- pkg
2
- .vscode
3
- coverage
4
- doc
5
- .yardoc
data/.rubocop.yml DELETED
@@ -1,41 +0,0 @@
1
- require:
2
- - rubocop-rake
3
- - rubocop-minitest
4
-
5
- AllCops:
6
- TargetRubyVersion: 2.5
7
- NewCops: enable
8
-
9
- Style/StringLiterals:
10
- Enabled: true
11
- EnforcedStyle: single_quotes
12
-
13
- Layout/FirstHashElementIndentation:
14
- Enabled: true
15
- EnforcedStyle: consistent
16
-
17
- Layout/FirstArrayElementIndentation:
18
- Enabled: true
19
- EnforcedStyle: consistent
20
-
21
- Metrics/ClassLength:
22
- Enabled: true
23
- Max: 150
24
-
25
- Naming/MethodParameterName:
26
- Enabled: false
27
-
28
- Metrics/MethodLength:
29
- Enabled: false
30
-
31
- Metrics/AbcSize:
32
- Enabled: false
33
-
34
- Style/Documentation:
35
- Enabled: false
36
-
37
- Style/ParallelAssignment:
38
- Enabled: false
39
-
40
- Minitest/AssertInDelta:
41
- Enabled: false
data/Gemfile DELETED
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gemspec
data/Gemfile.lock DELETED
@@ -1,57 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- dotstrings (0.3.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.4.2)
10
- docile (1.4.0)
11
- minitest (5.15.0)
12
- parallel (1.22.1)
13
- parser (3.1.2.0)
14
- ast (~> 2.4.1)
15
- rainbow (3.1.1)
16
- rake (13.0.6)
17
- regexp_parser (2.5.0)
18
- rexml (3.2.5)
19
- rubocop (1.28.2)
20
- parallel (~> 1.10)
21
- parser (>= 3.1.0.0)
22
- rainbow (>= 2.2.2, < 4.0)
23
- regexp_parser (>= 1.8, < 3.0)
24
- rexml
25
- rubocop-ast (>= 1.17.0, < 2.0)
26
- ruby-progressbar (~> 1.7)
27
- unicode-display_width (>= 1.4.0, < 3.0)
28
- rubocop-ast (1.17.0)
29
- parser (>= 3.1.1.0)
30
- rubocop-minitest (0.19.1)
31
- rubocop (>= 0.90, < 2.0)
32
- rubocop-rake (0.6.0)
33
- rubocop (~> 1.0)
34
- ruby-progressbar (1.11.0)
35
- simplecov (0.21.2)
36
- docile (~> 1.1)
37
- simplecov-html (~> 0.11)
38
- simplecov_json_formatter (~> 0.1)
39
- simplecov-html (0.12.3)
40
- simplecov_json_formatter (0.1.4)
41
- unicode-display_width (2.2.0)
42
-
43
- PLATFORMS
44
- ruby
45
-
46
- DEPENDENCIES
47
- bundler
48
- dotstrings!
49
- minitest (~> 5.14)
50
- rake
51
- rubocop
52
- rubocop-minitest
53
- rubocop-rake
54
- simplecov
55
-
56
- BUNDLED WITH
57
- 2.2.18
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
5
- require 'rubocop/rake_task'
6
-
7
- Rake::TestTask.new
8
- RuboCop::RakeTask.new
9
-
10
- task default: :test
data/dotstrings.gemspec DELETED
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require './lib/dotstrings/version'
4
-
5
- Gem::Specification.new do |s|
6
- s.name = 'dotstrings'
7
- s.version = DotStrings::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ['Ramon Torres']
10
- s.email = ['raymondjavaxx@gmail.com']
11
- s.homepage = 'https://github.com/raymondjavaxx/dotstrings'
12
- s.description = s.summary = 'Parse and create .strings files used in localization of iOS and macOS apps.'
13
- s.files = `git ls-files`.split("\n")
14
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
- s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
- s.require_paths = ['lib']
17
- s.license = 'MIT'
18
-
19
- s.add_development_dependency 'bundler'
20
- s.add_development_dependency 'minitest', '~> 5.14'
21
- s.add_development_dependency 'rake'
22
- s.add_development_dependency 'rubocop'
23
- s.add_development_dependency 'rubocop-minitest'
24
- s.add_development_dependency 'rubocop-rake'
25
- s.add_development_dependency 'simplecov'
26
-
27
- s.required_ruby_version = '>= 2.5.0'
28
- s.metadata['rubygems_mfa_required'] = 'true'
29
- end
@@ -1,2 +0,0 @@
1
- /* Escaped carriage backslashes */
2
- "some\\key" = "some\\value";
@@ -1,2 +0,0 @@
1
- /* Escaped carriage returns */
2
- "some\rkey" = "some\rvalue";
@@ -1,2 +0,0 @@
1
- /* Escaped new lines */
2
- "some\nkey" = "some\nvalue";
@@ -1,2 +0,0 @@
1
- /* Escaped nil */
2
- "key\0" = "value\0";
@@ -1,2 +0,0 @@
1
- /* Escaped quotes */
2
- "some \"key\"" = "some \"value\"";
@@ -1,2 +0,0 @@
1
- /* Escaped quotes */
2
- "some \'key\'" = "some \'value\'";
@@ -1,2 +0,0 @@
1
- /* Escaped tabs */
2
- "some\tkey" = "some\tvalue";
@@ -1,2 +0,0 @@
1
- /* Unicode characters. Key is a dollar sign, value is: ⚡👻 */
2
- "\U0024" = "\U26A1\UD83D\UDC7B";
@@ -1 +0,0 @@
1
- "key" = "\UDC7B\UD83D";
@@ -1 +0,0 @@
1
- "key" = "\UD83D\UD83D";
@@ -1 +0,0 @@
1
- "key" = "\UD83D";
@@ -1 +0,0 @@
1
- "key" = "\UD83D\U26A1";
Binary file
Binary file
@@ -1,2 +0,0 @@
1
- /* Comment */
2
- "key" = "value";
@@ -1,8 +0,0 @@
1
- // Single line comment
2
- "key 1" = "value 1";
3
-
4
- /* Multi line
5
- comment */
6
- "key 2" = "value 2";
7
-
8
- "key 3" = "value 3";
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'test_helper'
4
-
5
- class TestDotStrings < MiniTest::Test
6
- def test_parse_can_parse_valid_files
7
- file = DotStrings.parse_file('test/fixtures/valid.strings')
8
-
9
- assert_equal 3, file.items.size
10
-
11
- assert_equal 'Single line comment', file.items[0].comment
12
- assert_equal 'key 1', file.items[0].key
13
- assert_equal 'value 1', file.items[0].value
14
-
15
- assert_equal "Multi line\ncomment", file.items[1].comment
16
- assert_equal 'key 2', file.items[1].key
17
- assert_equal 'value 2', file.items[1].value
18
- end
19
-
20
- def test_can_parse_file_with_escaped_quotes
21
- file = DotStrings.parse_file('test/fixtures/escaped_quotes.strings')
22
-
23
- assert_equal 1, file.items.size
24
- assert_equal 'some "key"', file.items[0].key
25
- assert_equal 'some "value"', file.items[0].value
26
- end
27
-
28
- def test_can_parse_file_with_escaped_single_quotes
29
- file = DotStrings.parse_file('test/fixtures/escaped_single_quotes.strings')
30
-
31
- assert_equal 1, file.items.size
32
- assert_equal 'some \'key\'', file.items[0].key
33
- assert_equal 'some \'value\'', file.items[0].value
34
- end
35
-
36
- def test_can_parse_file_with_escaped_tabs
37
- file = DotStrings.parse_file('test/fixtures/escaped_tabs.strings')
38
-
39
- assert_equal 1, file.items.size
40
- assert_equal "some\tkey", file.items[0].key
41
- assert_equal "some\tvalue", file.items[0].value
42
- end
43
-
44
- def test_can_parse_files_with_escaped_carriage_returns
45
- file = DotStrings.parse_file('test/fixtures/escaped_carriage_returns.strings')
46
-
47
- assert_equal 1, file.items.size
48
- assert_equal "some\rkey", file.items[0].key
49
- assert_equal "some\rvalue", file.items[0].value
50
- end
51
-
52
- def test_can_parse_files_with_escaped_nil
53
- file = DotStrings.parse_file('test/fixtures/escaped_nil.strings')
54
-
55
- assert_equal 1, file.items.size
56
- assert_equal "key\0", file.items[0].key
57
- assert_equal "value\0", file.items[0].value
58
- end
59
-
60
- def test_can_parse_files_with_escaped_new_lines
61
- file = DotStrings.parse_file('test/fixtures/escaped_new_lines.strings')
62
-
63
- assert_equal 1, file.items.size
64
- assert_equal "some\nkey", file.items[0].key
65
- assert_equal "some\nvalue", file.items[0].value
66
- end
67
-
68
- def test_can_parse_files_with_escaped_backslashes
69
- file = DotStrings.parse_file('test/fixtures/escaped_backslashes.strings')
70
-
71
- assert_equal 1, file.items.size
72
- assert_equal 'some\\key', file.items[0].key
73
- assert_equal 'some\\value', file.items[0].value
74
- end
75
-
76
- def test_can_parse_files_with_escaped_unicode
77
- file = DotStrings.parse_file('test/fixtures/escaped_unicode.strings')
78
-
79
- assert_equal 1, file.items.size
80
- assert_equal '$', file.items[0].key
81
- assert_equal '⚡👻', file.items[0].value
82
- end
83
-
84
- def test_raises_error_when_bad_surrogate_pair_is_found
85
- # rubocop:disable Layout/LineLength
86
- test_cases = {
87
- 'escaped_unicode~bad_surrogate_order.strings' => 'Found a low surrogate code point before a high surrogate at line 1, column 15 (offset: 14)',
88
- 'escaped_unicode~duplicated_high_surrogate.strings' => 'Found a high surrogate code point after another high surrogate at line 1, column 21 (offset: 20)',
89
- 'escaped_unicode~incomplete_surrogate_pair.strings' => 'Unexpected character \'"\', expecting another unicode codepoint at line 1, column 16 (offset: 15)',
90
- 'escaped_unicode~non_surrogate_after_high_surrogate.strings' => 'Invalid unicode codepoint \'\U26A1\' after a high surrogate code point at line 1, column 21 (offset: 20)'
91
- }
92
- # rubocop:enable Layout/LineLength
93
-
94
- test_cases.each do |filename, error_message|
95
- error = assert_raises DotStrings::ParsingError do
96
- DotStrings.parse_file("test/fixtures/#{filename}")
97
- end
98
-
99
- assert_equal error_message, error.message
100
- end
101
- end
102
-
103
- def test_can_parse_utf16le_files_with_bom
104
- file = DotStrings.parse_file('test/fixtures/utf16le_bom.strings')
105
-
106
- assert_equal 1, file.items.size
107
- assert_equal 'key', file.items[0].key
108
- assert_equal 'value', file.items[0].value
109
- end
110
-
111
- def test_can_parse_utf16be_files_with_bom
112
- file = DotStrings.parse_file('test/fixtures/utf16be_bom.strings')
113
-
114
- assert_equal 1, file.items.size
115
- assert_equal 'key', file.items[0].key
116
- assert_equal 'value', file.items[0].value
117
- end
118
-
119
- def test_can_parse_utf8_files_with_bom
120
- file = DotStrings.parse_file('test/fixtures/utf8_bom.strings')
121
-
122
- assert_equal 1, file.items.size
123
- assert_equal 'key', file.items[0].key
124
- assert_equal 'value', file.items[0].value
125
- end
126
- end
data/test/test_file.rb DELETED
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'test_helper'
4
-
5
- class TestFile < MiniTest::Test
6
- def test_sort
7
- file = DotStrings::File.new([
8
- DotStrings::Item.new(key: 'key 3', value: 'value 3'),
9
- DotStrings::Item.new(key: 'key 2', value: 'value 2'),
10
- DotStrings::Item.new(key: 'key 1', value: 'value 1')
11
- ])
12
-
13
- sorted = file.sort
14
- assert_equal ['key 1', 'key 2', 'key 3'], sorted.keys
15
- end
16
-
17
- def test_delete
18
- items = [
19
- DotStrings::Item.new(key: 'key 1', value: 'value 1'),
20
- DotStrings::Item.new(key: 'key 2', value: 'value 2'),
21
- DotStrings::Item.new(key: 'key 3', value: 'value 3')
22
- ]
23
-
24
- file = DotStrings::File.new(items)
25
-
26
- file.delete('key 2')
27
- assert_equal 2, file.items.size
28
- assert_equal ['key 1', 'key 3'], file.keys
29
- end
30
-
31
- def test_delete_if
32
- file = DotStrings::File.new([
33
- DotStrings::Item.new(key: 'key 1', value: 'value 1'),
34
- DotStrings::Item.new(key: 'key 2', value: 'value 2'),
35
- DotStrings::Item.new(key: 'key 3', value: 'value 3')
36
- ])
37
-
38
- file.delete_if { |item| item.key == 'key 2' }
39
- assert_equal ['key 1', 'key 3'], file.keys
40
- end
41
-
42
- def test_access_by_key
43
- items = [
44
- DotStrings::Item.new(key: 'key 1', value: 'value 1'),
45
- DotStrings::Item.new(key: 'key 2', value: 'value 2'),
46
- DotStrings::Item.new(key: 'key 3', value: 'value 3')
47
- ]
48
-
49
- file = DotStrings::File.new(items)
50
-
51
- assert_equal 'value 1', file['key 1'].value
52
- assert_equal 'value 2', file['key 2'].value
53
- assert_equal 'value 3', file['key 3'].value
54
- end
55
-
56
- def test_append
57
- file = DotStrings::File.new
58
- file.append(DotStrings::Item.new(key: 'key 1', value: 'value 1'))
59
- assert_equal 1, file.items.size
60
- end
61
-
62
- def test_to_string
63
- file = DotStrings::File.new([
64
- DotStrings::Item.new(comment: 'Comment 1', key: 'key 1', value: 'value 1'),
65
- DotStrings::Item.new(comment: 'Comment 2', key: 'key 2', value: 'value 2'),
66
- DotStrings::Item.new(comment: 'Comment 3', key: 'key 3', value: '👻'),
67
- DotStrings::Item.new(comment: 'Comment 4', key: "\"'\t\n\r\0", value: "\"'\t\n\r\0")
68
- ])
69
-
70
- expected = <<~'END_OF_DOCUMENT'
71
- /* Comment 1 */
72
- "key 1" = "value 1";
73
-
74
- /* Comment 2 */
75
- "key 2" = "value 2";
76
-
77
- /* Comment 3 */
78
- "key 3" = "👻";
79
-
80
- /* Comment 4 */
81
- "\"'\t\n\r\0" = "\"'\t\n\r\0";
82
- END_OF_DOCUMENT
83
-
84
- assert_equal expected, file.to_s
85
- end
86
-
87
- def test_to_string_no_comments
88
- file = DotStrings::File.new([
89
- DotStrings::Item.new(comment: 'Comment 1', key: 'key 1', value: 'value 1'),
90
- DotStrings::Item.new(comment: 'Comment 2', key: 'key 2', value: 'value 2')
91
- ])
92
-
93
- expected = <<~'END_OF_DOCUMENT'
94
- "key 1" = "value 1";
95
-
96
- "key 2" = "value 2";
97
- END_OF_DOCUMENT
98
-
99
- assert_equal expected, file.to_s(comments: false)
100
- end
101
-
102
- def test_to_string_can_escape_single_quotes
103
- items = [
104
- DotStrings::Item.new(comment: 'Comment', key: "key'", value: "value'")
105
- ]
106
-
107
- file = DotStrings::File.new(items)
108
-
109
- expected = <<~'END_OF_DOCUMENT'
110
- /* Comment */
111
- "key\'" = "value\'";
112
- END_OF_DOCUMENT
113
-
114
- assert_equal expected, file.to_s(escape_single_quotes: true)
115
- end
116
- end
data/test/test_helper.rb DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'simplecov'
4
- SimpleCov.start
5
-
6
- require 'minitest'
7
- require 'minitest/pride'
8
- require 'minitest/autorun'
9
-
10
- require_relative '../lib/dotstrings'
data/test/test_item.rb DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'test_helper'
4
-
5
- class TestFile < MiniTest::Test
6
- def test_to_s
7
- item = DotStrings::Item.new(comment: 'Comment', key: 'key 1', value: 'value 1')
8
- assert_equal "/* Comment */\n\"key 1\" = \"value 1\";", item.to_s
9
- end
10
-
11
- def test_to_s_with_nil_comment
12
- item = DotStrings::Item.new(comment: nil, key: 'key 1', value: 'value 1')
13
- assert_equal '"key 1" = "value 1";', item.to_s
14
- end
15
- end
data/test/test_parser.rb DELETED
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'test_helper'
4
-
5
- class TestParser < MiniTest::Test
6
- def test_handles_extraneous_characters_at_start_of_file
7
- error = assert_raises DotStrings::ParsingError do
8
- parser = DotStrings::Parser.new
9
- parser << '$'
10
- end
11
-
12
- assert_equal "Unexpected character '$' at line 1, column 1 (offset: 0)", error.message
13
- end
14
-
15
- def test_handles_malformed_comments
16
- error = assert_raises DotStrings::ParsingError do
17
- parser = DotStrings::Parser.new
18
- parser << '/@ test'
19
- end
20
-
21
- assert_equal "Unexpected character '@' at line 1, column 2 (offset: 1)", error.message
22
- end
23
-
24
- def test_raises_error_when_escaping_invalid_character
25
- error = assert_raises DotStrings::ParsingError do
26
- parser = DotStrings::Parser.new
27
- parser << '"\\z" = "value";'
28
- end
29
-
30
- assert_equal "Unexpected character 'z' at line 1, column 3 (offset: 2)", error.message
31
- end
32
-
33
- def test_raises_error_when_items_are_not_separated_by_semicolon
34
- error = assert_raises DotStrings::ParsingError do
35
- parser = DotStrings::Parser.new
36
- parser << '"key_1" = "value_1" "key_2" = "value_2"'
37
- end
38
-
39
- assert_equal "Unexpected character '\"', expecting ';' at line 1, column 21 (offset: 20)", error.message
40
- end
41
-
42
- def test_raises_error_if_low_surrogate_is_not_formatted_correctly
43
- error = assert_raises DotStrings::ParsingError do
44
- parser = DotStrings::Parser.new
45
- parser << '"key" = "\UD83D\$DC7B";'
46
- end
47
-
48
- assert_equal "Unexpected character '$', expecting 'U' at line 1, column 17 (offset: 16)", error.message
49
- end
50
- end