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 +4 -4
- data/CHANGELOG.md +133 -41
- data/README.md +1 -1
- data/lib/nestedtext/decode.rb +78 -3
- data/lib/nestedtext/errors_internal.rb +29 -3
- data/lib/nestedtext/parser.rb +8 -2
- data/lib/nestedtext/scanners.rb +12 -7
- data/lib/nestedtext/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3779aae06776d786682bb0406184692a06a1587c78b31b4e7a808daa3fde8c65
|
|
4
|
+
data.tar.gz: b382d463a220cd64c8935767c2ce75f4aedc12184f913eb5b5109e16b850ad86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
36
|
+
|
|
37
|
+
* Migrated Code Climate to qlty.
|
|
12
38
|
|
|
13
39
|
## [4.5.0] - 2022-06-09
|
|
40
|
+
|
|
14
41
|
### Added
|
|
15
|
-
|
|
42
|
+
|
|
43
|
+
* Guard filesystem watch and test executor.
|
|
16
44
|
|
|
17
45
|
### Changed
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
|
|
59
|
+
* rubydoc.info: try remove unused module require.
|
|
27
60
|
|
|
28
61
|
## [4.4.4] - 2022-02-17
|
|
62
|
+
|
|
29
63
|
### Fixed
|
|
30
|
-
|
|
64
|
+
|
|
65
|
+
* rubydoc.info: revert reject instead of select
|
|
31
66
|
|
|
32
67
|
## [4.4.3] - 2022-02-17
|
|
68
|
+
|
|
33
69
|
### Fixed
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
|
|
83
|
+
* rubydoc.info: try fix missing class methods.
|
|
43
84
|
|
|
44
85
|
## [4.4.0] - 2022-02-17
|
|
86
|
+
|
|
45
87
|
### Fixed
|
|
46
|
-
|
|
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
|
-
|
|
94
|
+
|
|
95
|
+
* rubydoc.info: Include .yardopts in gem
|
|
51
96
|
|
|
52
97
|
## [4.3.0] - 2022-02-17
|
|
98
|
+
|
|
53
99
|
### Fixed
|
|
54
|
-
|
|
100
|
+
|
|
101
|
+
* rubydoc.info: try fix missing class methods.
|
|
55
102
|
|
|
56
103
|
## [4.2.2] - 2022-02-12
|
|
104
|
+
|
|
57
105
|
### Fixed
|
|
58
|
-
|
|
106
|
+
|
|
107
|
+
* Better module documentation fix.
|
|
59
108
|
|
|
60
109
|
## [4.2.1] - 2022-02-12
|
|
110
|
+
|
|
61
111
|
### Fixed
|
|
62
|
-
|
|
112
|
+
|
|
113
|
+
* Better module documentation.
|
|
63
114
|
|
|
64
115
|
## [4.2.0] - 2022-02-08
|
|
116
|
+
|
|
65
117
|
### Fixed
|
|
66
|
-
|
|
118
|
+
|
|
119
|
+
* Proper Unicode character name lookup.
|
|
67
120
|
|
|
68
121
|
## [4.1.1] - 2022-01-28
|
|
122
|
+
|
|
69
123
|
### Fixed
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
143
|
+
|
|
144
|
+
* Fix logo at rubydoc.info
|
|
84
145
|
|
|
85
146
|
## [3.2.0] - 2022-01-27
|
|
147
|
+
|
|
86
148
|
### Changed
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
|
|
162
|
+
* API documentation generated with rdoc.
|
|
96
163
|
|
|
97
164
|
### Fixed
|
|
98
|
-
|
|
165
|
+
|
|
166
|
+
* Removed leaked `NT_MIXIN` constant in core extensions.
|
|
99
167
|
|
|
100
168
|
### Changed
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
+
|
|
203
|
+
* Renamed `ToNTMixing` to `ToNTMixin` .
|
|
125
204
|
|
|
126
205
|
## [1.1.0] - 2022-01-25
|
|
206
|
+
|
|
127
207
|
### Added
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
+
|
|
223
|
+
* Move runtime dependencies from Gemfile to .gemspec.
|
|
139
224
|
|
|
140
225
|
## [0.5.0] - 2022-01-24
|
|
226
|
+
|
|
141
227
|
### Added
|
|
142
|
-
|
|
228
|
+
|
|
229
|
+
* Publish Gem to GitHub Packages
|
|
143
230
|
|
|
144
231
|
## [0.4.0] - 2022-01-24
|
|
145
|
-
|
|
232
|
+
|
|
233
|
+
* Iteration on CD GitHub Actions workflow.
|
|
146
234
|
|
|
147
235
|
## [0.3.0] - 2022-01-24
|
|
148
|
-
|
|
236
|
+
|
|
237
|
+
* Iteration on CD GitHub Actions workflow.
|
|
149
238
|
|
|
150
239
|
## [0.2.0] - 2022-01-24
|
|
151
|
-
|
|
240
|
+
|
|
241
|
+
* Iteration on CD GitHub Actions workflow.
|
|
152
242
|
|
|
153
243
|
## [0.1.0] - 2022-01-24
|
|
244
|
+
|
|
154
245
|
### Added
|
|
155
|
-
|
|
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
|
[](https://badge.fury.io/rb/nestedtext)
|
|
3
3
|
[](https://rubygems.org/gems/nestedtext)
|
|
4
4
|
[](https://www.rubydoc.info/gems/nestedtext/NestedText)
|
|
5
|
-
[](https://nestedtext.org/en/v3.8/)
|
|
6
6
|
[](https://github.com/KenKundert/nestedtext_tests/)
|
|
7
7
|
[](https://github.com/erikw/nestedtext-ruby/actions/workflows/ci.yml)
|
|
8
8
|
[](https://github.com/erikw/nestedtext-ruby/actions/workflows/cd.yml)
|
data/lib/nestedtext/decode.rb
CHANGED
|
@@ -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
|
-
#
|
|
38
|
-
File.
|
|
39
|
-
|
|
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
|
-
# [
|
|
250
|
-
|
|
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
|
data/lib/nestedtext/parser.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
data/lib/nestedtext/scanners.rb
CHANGED
|
@@ -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
|
-
\
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
data/lib/nestedtext/version.rb
CHANGED
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
|
+
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:
|
|
11
|
+
date: 2026-05-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: unicode_utils
|