nestedtext 4.6.0 → 5.0.2

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
2
  SHA256:
3
- metadata.gz: 313e16f9cbbf553ff5b3afaee739150c9fad09ddbbca2aa7b73c02bb7e6dc422
4
- data.tar.gz: 4179c0d01df5da768be52317ac2b96f236d659e25cbe84436334f3cc46c42240
3
+ metadata.gz: 3779aae06776d786682bb0406184692a06a1587c78b31b4e7a808daa3fde8c65
4
+ data.tar.gz: b382d463a220cd64c8935767c2ce75f4aedc12184f913eb5b5109e16b850ad86
5
5
  SHA512:
6
- metadata.gz: be6167b0b8f56c2e6df9e3964a09e082046a666d2aa1ebccb4a53a03e0bc209d9e4df9387ea7173fac7a50abe8ffefc07b5240d5452f5e730180b443de035678
7
- data.tar.gz: 870f1f5504ce8b998c6f1c5308eea9536fd82b740e91074768509cdbd8912b6771dc3219434ae468ffa85ca0373fc7bc5a2e106c8083d4159c135100445ae32b
6
+ metadata.gz: a76972ff3e809e9d676d05161123051455de6b1297abd5e9cbc297500fecaa87715233254a814d6f925911305825394391aea26c8b0b1a2493a8a57526a42f5f
7
+ data.tar.gz: b5a5a1af978d02ed55d90bec3a89ce74e855d9766937fb2528e0053a6510116caa34dc7e1de87854c05b98450884c366ae65e928a33f2cd1aec8087986ce81c9
data/CHANGELOG.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Changelog
2
+
2
3
  All notable changes to this project will be documented in this file.
3
4
 
4
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
@@ -6,150 +7,241 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
7
 
7
8
  ## [Unreleased]
8
9
 
10
+ ## [5.0.1] - 2026-05-29
11
+
12
+ ### Fixed
13
+
14
+ * rubocop errors
15
+
16
+ ## [5.0.0] - 2026-05-29
17
+
18
+ ### Added
19
+
20
+ * Support for NestedText specification v3.8 (official test suite updated).
21
+ * UTF-8 BOM is now silently stripped before parsing.
22
+ * CR-only and CRLF line endings are normalised to LF, so files from all platforms are accepted.
23
+ * Parser now raises a dedicated error when a multi-line key has no following indented value.
24
+ * Parser now raises "extra content" when valid content is followed by unexpected additional lines.
25
+ * Unicode whitespace characters (e.g. U+00A0 NO-BREAK SPACE) at the start of a line are correctly rejected as invalid indentation.
26
+ * Unicode whitespace is now trimmed from the end of dict keys before the `:` separator, consistent with the spec.
27
+
28
+ ### Fixed
29
+
30
+ * `key:` (dict item with a single trailing space) is now correctly treated as having no inline value, allowing an indented block value to follow.
31
+ * `-` (list item with a single trailing space) is now correctly treated as having no inline value.
32
+
9
33
  ## [4.6.0] - 2025-10-31
34
+
10
35
  ### Changed
11
- - Migrated Code Climate to qlty.
36
+
37
+ * Migrated Code Climate to qlty.
12
38
 
13
39
  ## [4.5.0] - 2022-06-09
40
+
14
41
  ### Added
15
- - Guard filesystem watch and test executor.
42
+
43
+ * Guard filesystem watch and test executor.
16
44
 
17
45
  ### Changed
18
- - Dumped file content should end with newline, following updated version 3.3.0 of the NestedText specification.
46
+
47
+ * Dumped file content should end with newline, following updated version 3.3.0 of the NestedText specification.
19
48
 
20
49
  ## [4.4.6] - 2022-02-17
50
+
21
51
  ### Fixed
22
- - rubydoc.info: don't force use hash value omission with rubycop. rubydoc.info is not on ruby 3.1 yet.
52
+
53
+ * rubydoc.info: don't force use hash value omission with rubycop. rubydoc.info is not on ruby 3.1 yet.
23
54
 
24
55
  ## [4.4.5] - 2022-02-17
56
+
25
57
  ### Fixed
26
- - rubydoc.info: try remove unused module require.
58
+
59
+ * rubydoc.info: try remove unused module require.
27
60
 
28
61
  ## [4.4.4] - 2022-02-17
62
+
29
63
  ### Fixed
30
- - rubydoc.info: revert reject instead of select
64
+
65
+ * rubydoc.info: revert reject instead of select
31
66
 
32
67
  ## [4.4.3] - 2022-02-17
68
+
33
69
  ### Fixed
34
- - rubydoc.info: try building gem from git-ls | reject instead of select
70
+
71
+ * rubydoc.info: try building gem from git-ls | reject instead of select
35
72
 
36
73
  ## [4.4.2] - 2022-02-17
74
+
37
75
  ### Fixed
38
- - rubydoc.info: try includ all of lib/**/*.rb
76
+
77
+ * rubydoc.info: try includ all of lib/**/*.rb
39
78
 
40
79
  ## [4.4.1] - 2022-02-17
80
+
41
81
  ### Fixed
42
- - rubydoc.info: try fix missing class methods.
82
+
83
+ * rubydoc.info: try fix missing class methods.
43
84
 
44
85
  ## [4.4.0] - 2022-02-17
86
+
45
87
  ### Fixed
46
- - rubydoc.info: not re-generating for patch versions?
88
+
89
+ * rubydoc.info: not re-generating for patch versions?
47
90
 
48
91
  ## [4.3.1] - 2022-02-17
92
+
49
93
  ### Fixed
50
- - rubydoc.info: Include .yardopts in gem
94
+
95
+ * rubydoc.info: Include .yardopts in gem
51
96
 
52
97
  ## [4.3.0] - 2022-02-17
98
+
53
99
  ### Fixed
54
- - rubydoc.info: try fix missing class methods.
100
+
101
+ * rubydoc.info: try fix missing class methods.
55
102
 
56
103
  ## [4.2.2] - 2022-02-12
104
+
57
105
  ### Fixed
58
- - Better module documentation fix.
106
+
107
+ * Better module documentation fix.
59
108
 
60
109
  ## [4.2.1] - 2022-02-12
110
+
61
111
  ### Fixed
62
- - Better module documentation.
112
+
113
+ * Better module documentation.
63
114
 
64
115
  ## [4.2.0] - 2022-02-08
116
+
65
117
  ### Fixed
66
- - Proper Unicode character name lookup.
118
+
119
+ * Proper Unicode character name lookup.
67
120
 
68
121
  ## [4.1.1] - 2022-01-28
122
+
69
123
  ### Fixed
70
- - Don't trigger CI when CD will run all tests anyways.
124
+
125
+ * Don't trigger CI when CD will run all tests anyways.
71
126
 
72
127
  ## [4.1.0] - 2022-01-28
128
+
73
129
  ### Changed
74
- - cd.yml now runs full tests before releasing new version, by using reusable workflows.
130
+
131
+ * cd.yml now runs full tests before releasing new version, by using reusable workflows.
75
132
 
76
133
  ## [4.0.0] - 2022-01-28
134
+
77
135
  ### Changed
78
- - **Breaking change**: Renamed `NTEncodeMixin` to `ToNTMixin`.
79
- - All code linted with RuboCop
136
+
137
+ * **Breaking change**: Renamed `NTEncodeMixin` to `ToNTMixin`.
138
+ * All code linted with RuboCop
80
139
 
81
140
  ## [3.2.1] - 2022-01-27
141
+
82
142
  ### Fixed
83
- - Fix logo at rubydoc.info
143
+
144
+ * Fix logo at rubydoc.info
84
145
 
85
146
  ## [3.2.0] - 2022-01-27
147
+
86
148
  ### Changed
87
- - Switch from rdoc formatting syntax to Markdown with Redcarpet to be able to render README.md properly.
149
+
150
+ * Switch from rdoc formatting syntax to Markdown with Redcarpet to be able to render README.md properly.
88
151
 
89
152
  ## [3.1.0] - 2022-01-27
153
+
90
154
  ### Changed
91
- - Switch from rdoc to YARD to match rubydoc.info that is used automatically for Gems uploaded to rubygems.org.
155
+
156
+ * Switch from rdoc to YARD to match rubydoc.info that is used automatically for Gems uploaded to rubygems.org.
92
157
 
93
158
  ## [3.0.0] - 2022-01-27
159
+
94
160
  ### Added
95
- - API documentation generated with rdoc.
161
+
162
+ * API documentation generated with rdoc.
96
163
 
97
164
  ### Fixed
98
- - Removed leaked `NT_MIXIN` constant in core extensions.
165
+
166
+ * Removed leaked `NT_MIXIN` constant in core extensions.
99
167
 
100
168
  ### Changed
101
- - **Breaking change**: `#to_nt` on `String`, `Array` and `Hash` is no longer strict by default for consistency an unexpected surprises e.g. when having an array of Custom Objects and calling the method on the array.
102
- - Internal clean-up and simplifications on helper classes and methods.
169
+
170
+ * **Breaking change**: `#to_nt` on `String`, `Array` and `Hash` is no longer strict by default for consistency an unexpected surprises e.g. when having an array of Custom Objects and calling the method on the array.
171
+ * Internal clean-up and simplifications on helper classes and methods.
103
172
 
104
173
  ## [2.1.0] - 2022-01-27
174
+
105
175
  ### Changed
106
- - Slim down Gem by using include instead of block list.
176
+
177
+ * Slim down Gem by using include instead of block list.
107
178
 
108
179
  ## [2.0.1] - 2022-01-26
180
+
109
181
  ### Fixed
110
- - README issue with logo showing up on Rdoc (out-commented HTML).
182
+
183
+ * README issue with logo showing up on Rdoc (out-commented HTML).
111
184
 
112
185
  ## [2.0.0] - 2022-01-26
186
+
113
187
  ### Changed
114
- - **Breaking change**: strict mode now defaults to false for both the `load` and `dump` methods.
115
- - Internal rename of error classes to be more consistent.
116
- - Internal simplification of argument passing.
188
+
189
+ * **Breaking change**: strict mode now defaults to false for both the `load` and `dump` methods.
190
+ * Internal rename of error classes to be more consistent.
191
+ * Internal simplification of argument passing.
117
192
 
118
193
  ## [1.2.0] - 2022-01-25
194
+
119
195
  ### Changed
120
- - Hide core extension `String.normalize_line_endings` from users.
196
+
197
+ * Hide core extension `String.normalize_line_endings` from users.
121
198
 
122
199
  ## [1.1.1] - 2022-01-25
200
+
123
201
  ### Fixed
124
- - Renamed `ToNTMixing` to `ToNTMixin` .
202
+
203
+ * Renamed `ToNTMixing` to `ToNTMixin` .
125
204
 
126
205
  ## [1.1.0] - 2022-01-25
206
+
127
207
  ### Added
128
- - Expose `NestedText::VERSION` for convenience to the users.
208
+
209
+ * Expose `NestedText::VERSION` for convenience to the users.
129
210
 
130
211
  ## [1.0.0] - 2022-01-25
212
+
131
213
  The library is now useful for users!
132
214
 
133
215
  ### Changed
134
- - Hide all internals in the module from users.
216
+
217
+ * Hide all internals in the module from users.
135
218
 
136
219
  ## [0.6.0] - 2022-01-24
220
+
137
221
  ### Fixed
138
- - Move runtime dependencies from Gemfile to .gemspec.
222
+
223
+ * Move runtime dependencies from Gemfile to .gemspec.
139
224
 
140
225
  ## [0.5.0] - 2022-01-24
226
+
141
227
  ### Added
142
- - Publish Gem to GitHub Packages
228
+
229
+ * Publish Gem to GitHub Packages
143
230
 
144
231
  ## [0.4.0] - 2022-01-24
145
- - Iteration on CD GitHub Actions workflow.
232
+
233
+ * Iteration on CD GitHub Actions workflow.
146
234
 
147
235
  ## [0.3.0] - 2022-01-24
148
- - Iteration on CD GitHub Actions workflow.
236
+
237
+ * Iteration on CD GitHub Actions workflow.
149
238
 
150
239
  ## [0.2.0] - 2022-01-24
151
- - Iteration on CD GitHub Actions workflow.
240
+
241
+ * Iteration on CD GitHub Actions workflow.
152
242
 
153
243
  ## [0.1.0] - 2022-01-24
244
+
154
245
  ### Added
155
- - Initial release. If this release works, an 1.0.0 will soon follow.
246
+
247
+ * Initial release. If this release works, an 1.0.0 will soon follow.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Gem Version](https://badge.fury.io/rb/nestedtext.svg)](https://badge.fury.io/rb/nestedtext)
3
3
  [![Gem Downloads](https://img.shields.io/gem/dt/nestedtext?label=gem%20downloads)](https://rubygems.org/gems/nestedtext)
4
4
  [![Documentation](https://img.shields.io/badge/docs-API-informational?logo=readthedocs&logoColor=violet)](https://www.rubydoc.info/gems/nestedtext/NestedText)
5
- [![Data Format Version Supported](https://img.shields.io/badge/%F0%9F%84%BD%F0%9F%85%83%20Version%20Supported-3.4.0-blueviolet)](https://nestedtext.org/en/v3.3/)
5
+ [![Data Format Version Supported](https://img.shields.io/badge/%F0%9F%84%BD%F0%9F%85%83%20Version%20Supported-3.8-blueviolet)](https://nestedtext.org/en/v3.8/)
6
6
  [![Official Tests](https://img.shields.io/badge/Official%20Tests-Passing-success?logo=cachet)](https://github.com/KenKundert/nestedtext_tests/)
7
7
  [![GitHub Actions: Continuous Integration](https://github.com/erikw/nestedtext-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/erikw/nestedtext-ruby/actions/workflows/ci.yml)
8
8
  [![GitHub Actions: Continuous Deployment](https://github.com/erikw/nestedtext-ruby/actions/workflows/cd.yml/badge.svg)](https://github.com/erikw/nestedtext-ruby/actions/workflows/cd.yml)
@@ -6,6 +6,10 @@ require 'nestedtext/errors_internal'
6
6
  require 'stringio'
7
7
 
8
8
  module NestedText
9
+ # UTF-8 BOM as a binary string for reliable detection regardless of input encoding.
10
+ UTF8_BOM = "\xEF\xBB\xBF".b.freeze
11
+ private_constant :UTF8_BOM
12
+
9
13
  # Decode a NestedText string to Ruby objects.
10
14
  #
11
15
  # @param ntstring [String] The string containing NestedText to be decoded.
@@ -18,6 +22,7 @@ module NestedText
18
22
  def self.load(ntstring, top_class: Object, strict: false)
19
23
  raise Errors::WrongInputTypeError.new([String], ntstring) unless ntstring.nil? || ntstring.is_a?(String)
20
24
 
25
+ ntstring = prepare_string_input(ntstring) unless ntstring.nil?
21
26
  Parser.new(StringIO.new(ntstring), top_class, strict: strict).parse
22
27
  end
23
28
 
@@ -34,9 +39,79 @@ module NestedText
34
39
  def self.load_file(filename, top_class: Object, strict: false)
35
40
  raise Errors::WrongInputTypeError.new([String], filename) unless !filename.nil? && filename.is_a?(String)
36
41
 
37
- # Open explicitly in text mode to detect \r as line ending.
38
- File.open(filename, 'rt') do |file|
39
- Parser.new(file, top_class, strict: strict).parse
42
+ # Read in binary mode to handle BOM detection; we manually ensure UTF-8.
43
+ raw = File.binread(filename)
44
+ ntstring = prepare_string_input(raw)
45
+ Parser.new(StringIO.new(ntstring), top_class, strict: strict).parse
46
+ end
47
+
48
+ # Strips a UTF-8 BOM if present, validates UTF-8 encoding, normalizes line
49
+ # endings (CR-only and CRLF → LF), and returns a clean UTF-8 String ready
50
+ # for the parser.
51
+ def self.prepare_string_input(str)
52
+ binary = str.b
53
+ binary = binary.delete_prefix(UTF8_BOM)
54
+ raise_invalid_utf8_error(binary) unless binary.force_encoding('UTF-8').valid_encoding?
55
+
56
+ # Normalize CR-only and CRLF line endings to LF so the scanner's
57
+ # IO#gets (which splits on \n) works correctly for all platforms.
58
+ binary.force_encoding('UTF-8').gsub(/\r\n?/, "\n")
59
+ end
60
+ private_class_method :prepare_string_input
61
+
62
+ # Scans binary bytes to find the first invalid UTF-8 sequence, then raises a
63
+ # ParseError with the correct lineno and colno.
64
+ def self.raise_invalid_utf8_error(binary)
65
+ bytes = binary.bytes
66
+ lineno, line_start, i = scan_for_invalid_utf8(bytes)
67
+ colno = i - line_start
68
+ line_end = bytes.index(0x0A, i) || bytes.length
69
+ line_content = bytes[line_start...line_end].pack('C*')
70
+ .force_encoding('UTF-8')
71
+ .encode('UTF-8', invalid: :replace, undef: :replace)
72
+ raise Errors::ParseEncodingError.new(line_content, lineno, colno)
73
+ end
74
+ private_class_method :raise_invalid_utf8_error
75
+
76
+ # Returns [lineno, line_start, i] where i is the position of the first
77
+ # invalid byte.
78
+ def self.scan_for_invalid_utf8(bytes)
79
+ lineno = 0
80
+ line_start = 0
81
+ i = 0
82
+ while i < bytes.length
83
+ byte = bytes[i]
84
+ seq_len = utf8_sequence_length(byte)
85
+ break unless seq_len && valid_utf8_continuation?(bytes, i, seq_len)
86
+
87
+ if byte == 0x0A
88
+ lineno += 1
89
+ line_start = i + 1
90
+ end
91
+ i += seq_len
92
+ end
93
+ [lineno, line_start, i]
94
+ end
95
+ private_class_method :scan_for_invalid_utf8
96
+
97
+ # Returns the expected byte-sequence length for a UTF-8 start byte, or nil if invalid.
98
+ def self.utf8_sequence_length(byte)
99
+ if byte < 0x80 then 1 # 0x00–0x7F: ASCII
100
+ elsif byte < 0xC2 then nil # 0x80–0xC1: continuation or overlong (invalid start)
101
+ elsif byte < 0xE0 then 2 # 0xC2–0xDF: 2-byte sequence
102
+ elsif byte < 0xF0 then 3 # 0xE0–0xEF: 3-byte sequence
103
+ elsif byte < 0xF8 then 4 # 0xF0–0xF7: 4-byte sequence
104
+ # 0xF8–0xFF: invalid
105
+ end
106
+ end
107
+ private_class_method :utf8_sequence_length
108
+
109
+ # Checks that the continuation bytes following a multi-byte start are valid.
110
+ def self.valid_utf8_continuation?(bytes, start, seq_len)
111
+ (1...seq_len).all? do |j|
112
+ k = start + j
113
+ k < bytes.length && (bytes[k] & 0xC0) == 0x80
40
114
  end
41
115
  end
116
+ private_class_method :valid_utf8_continuation?
42
117
  end
@@ -14,7 +14,7 @@ module NestedText
14
14
  end
15
15
 
16
16
  class ParseError < InternalError
17
- attr_reader :lineno, :colno, :message_raw
17
+ attr_reader :lineno, :colno, :message_raw, :line
18
18
 
19
19
  def initialize(line, colno, message)
20
20
  # Note, both line and column number are 0-indexed.
@@ -22,6 +22,7 @@ module NestedText
22
22
  @lineno = line.lineno
23
23
  @colno = colno
24
24
  @message_raw = message
25
+ @line = (' ' * line.indentation) + line.content
25
26
  super(pretty_message(line))
26
27
  end
27
28
 
@@ -92,6 +93,30 @@ module NestedText
92
93
  end
93
94
  end
94
95
 
96
+ class ParseMultilineKeyRequiresIndentedValueError < ParseError
97
+ def initialize(line)
98
+ super(line, line.indentation, 'indented value must follow multi-line key.')
99
+ end
100
+ end
101
+
102
+ class ParseExtraContentError < ParseError
103
+ def initialize(line)
104
+ super(line, line.indentation, 'extra content.')
105
+ end
106
+ end
107
+
108
+ # A lightweight line substitute used when reporting encoding errors before
109
+ # the scanner has produced any Line objects.
110
+ EncodingErrorLine = Struct.new(:lineno, :indentation, :content, :prev)
111
+ private_constant :EncodingErrorLine
112
+
113
+ class ParseEncodingError < ParseError
114
+ def initialize(line_content, lineno, colno)
115
+ line = EncodingErrorLine.new(lineno, 0, line_content, nil)
116
+ super(line, colno, 'invalid start byte')
117
+ end
118
+ end
119
+
95
120
  class ParseInlineDictSyntaxError < ParseError
96
121
  def initialize(line, colno, wrong_char)
97
122
  super(line, line.indentation + colno, "expected ‘,’ or ‘}’, found ‘#{wrong_char}’.")
@@ -246,8 +271,9 @@ module NestedText
246
271
  end
247
272
 
248
273
  def self.raise_unrecognized_line(line)
249
- # [[:space:]] include all Unicode spaces e.g. non-breakable space which \s does not.
250
- raise ParseInvalidIndentationCharError, line if line.content.chr =~ /[[:space:]]/
274
+ # Use content[0] (Unicode character) rather than .chr (first byte) so that
275
+ # multi-byte Unicode spaces (e.g. U+00A0 NO-BREAK SPACE) are detected.
276
+ raise ParseInvalidIndentationCharError, line if line.content[0] =~ /[[:space:]]/
251
277
 
252
278
  raise ParseLineTagNotDetectedError, line
253
279
  end
@@ -28,6 +28,8 @@ module NestedText
28
28
 
29
29
  def parse
30
30
  result = parse_any(0)
31
+ raise Errors::ParseExtraContentError, @line_scanner.peek unless @line_scanner.peek.nil?
32
+
31
33
  case @top_class.object_id
32
34
  when Object.object_id
33
35
  return_object(result)
@@ -111,8 +113,8 @@ module NestedText
111
113
 
112
114
  def assert_list_line(line, indentation)
113
115
  Errors.raise_unrecognized_line(line) if line.tag == :unrecognized
114
- raise Errors::ParseLineTypeExpectedListItemError, line unless line.tag == :list_item
115
116
  raise Errors::ParseInvalidIndentationError.new(line, indentation) if line.indentation != indentation
117
+ raise Errors::ParseLineTypeExpectedListItemError, line unless line.tag == :list_item
116
118
  end
117
119
 
118
120
  def parse_list_item(indentation)
@@ -161,7 +163,7 @@ module NestedText
161
163
  end
162
164
 
163
165
  def parse_key_item_value(indentation, line)
164
- return '' if @line_scanner.peek.nil?
166
+ raise Errors::ParseMultilineKeyRequiresIndentedValueError, line if @line_scanner.peek.nil?
165
167
 
166
168
  exp_types = %i[dict_item key_item list_item string_item]
167
169
  unless exp_types.member?(@line_scanner.peek.tag)
@@ -220,6 +222,10 @@ module NestedText
220
222
  def parse_string_item(indentation)
221
223
  result = []
222
224
  while !@line_scanner.peek.nil? && @line_scanner.peek.indentation >= indentation
225
+ # Stop (without consuming) when same-indent non-string line is encountered;
226
+ # the caller handles it (e.g. as "extra content." at top level).
227
+ break if @line_scanner.peek.indentation == indentation && @line_scanner.peek.tag != :string_item
228
+
223
229
  line = @line_scanner.read_next
224
230
  assert_string_line(line, indentation)
225
231
 
@@ -119,7 +119,7 @@ module NestedText
119
119
 
120
120
  PATTERN_DICT_ITEM = /^
121
121
  (?<key>[^\s].*?) # Key must start with a non-whitespace character, and goes until first
122
- \s*: # first optional space, or :-separator
122
+ \p{Space}*: # optional Unicode whitespace then :-separator
123
123
  (?: # Value part is optional
124
124
  \p{Space} # Must have a space after :-separator
125
125
  (?<value>.*) # Value is everything to the end of the line
@@ -141,7 +141,8 @@ module NestedText
141
141
  @attribs['key'] = @content[2..] || ''
142
142
  elsif @content =~ /^-(?: |$)/
143
143
  self.tag = :list_item
144
- @attribs['value'] = @content[2..]
144
+ value = @content[2..]
145
+ @attribs['value'] = value.nil? || value.empty? ? nil : value
145
146
  elsif @content =~ /^>(?: |$)/
146
147
  self.tag = :string_item
147
148
  @attribs['value'] = @content[2..] || ''
@@ -149,14 +150,18 @@ module NestedText
149
150
  self.tag = :inline_dict
150
151
  elsif @content[0] == '['
151
152
  self.tag = :inline_list
152
- elsif @content =~ PATTERN_DICT_ITEM
153
- self.tag = :dict_item
154
- @attribs['key'] = Regexp.last_match(:key)
155
- @attribs['value'] = Regexp.last_match(:value)
156
- else
153
+ elsif @content[0] =~ /[[:space:]]/ || @content !~ PATTERN_DICT_ITEM
154
+ # A non-ASCII Unicode space character at the start is invalid indentation.
155
+ # (ASCII spaces are stripped by fast_forward_indentation, so this can only
156
+ # be a character like U+00A0 NO-BREAK SPACE.)
157
157
  # Don't raise error here, as this line might not have been consumed yet,
158
158
  # thus could hide an error that we detect when parsing the previous line.
159
159
  self.tag = :unrecognized
160
+ else
161
+ self.tag = :dict_item
162
+ @attribs['key'] = Regexp.last_match(:key)
163
+ value = Regexp.last_match(:value)
164
+ @attribs['value'] = value.nil? || value.empty? ? nil : value
160
165
  end
161
166
  end
162
167
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NestedText
4
- VERSION = '4.6.0' # The version of this library.
4
+ VERSION = '5.0.2' # The version of this library.
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nestedtext
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.0
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Westrup
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-31 00:00:00.000000000 Z
11
+ date: 2026-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: unicode_utils