nestedtext 4.5.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: 22ac73186b0d9ba1785e2b84808b57345348a1e2a4cb9ca114b59062895664df
4
- data.tar.gz: dfd5bf7254e65aecf7507bd7c43d084dd47e196dd84126f2c6fcb688dc70d53d
3
+ metadata.gz: 3779aae06776d786682bb0406184692a06a1587c78b31b4e7a808daa3fde8c65
4
+ data.tar.gz: b382d463a220cd64c8935767c2ce75f4aedc12184f913eb5b5109e16b850ad86
5
5
  SHA512:
6
- metadata.gz: f3e646f14b4e81fe9211b797deec8bce368438a2f9d980d1f1279f908be84928fbc033df5415a716e37e5c6ed7ed4a66c53941d645540e044680f0185f218497
7
- data.tar.gz: 03c9c3f9242c1b749a4fc3ecdbd5c84ff6f4eff832a6c2272e8fe2af1188a8244566ebf383b4dfb0149a9008ec11322d5910f2634e79c70c0756f52da3813eae
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,146 +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
+
33
+ ## [4.6.0] - 2025-10-31
34
+
35
+ ### Changed
36
+
37
+ * Migrated Code Climate to qlty.
38
+
9
39
  ## [4.5.0] - 2022-06-09
40
+
10
41
  ### Added
11
- - Guard filesystem watch and test executor.
42
+
43
+ * Guard filesystem watch and test executor.
12
44
 
13
45
  ### Changed
14
- - 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.
15
48
 
16
49
  ## [4.4.6] - 2022-02-17
50
+
17
51
  ### Fixed
18
- - 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.
19
54
 
20
55
  ## [4.4.5] - 2022-02-17
56
+
21
57
  ### Fixed
22
- - rubydoc.info: try remove unused module require.
58
+
59
+ * rubydoc.info: try remove unused module require.
23
60
 
24
61
  ## [4.4.4] - 2022-02-17
62
+
25
63
  ### Fixed
26
- - rubydoc.info: revert reject instead of select
64
+
65
+ * rubydoc.info: revert reject instead of select
27
66
 
28
67
  ## [4.4.3] - 2022-02-17
68
+
29
69
  ### Fixed
30
- - 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
31
72
 
32
73
  ## [4.4.2] - 2022-02-17
74
+
33
75
  ### Fixed
34
- - rubydoc.info: try includ all of lib/**/*.rb
76
+
77
+ * rubydoc.info: try includ all of lib/**/*.rb
35
78
 
36
79
  ## [4.4.1] - 2022-02-17
80
+
37
81
  ### Fixed
38
- - rubydoc.info: try fix missing class methods.
82
+
83
+ * rubydoc.info: try fix missing class methods.
39
84
 
40
85
  ## [4.4.0] - 2022-02-17
86
+
41
87
  ### Fixed
42
- - rubydoc.info: not re-generating for patch versions?
88
+
89
+ * rubydoc.info: not re-generating for patch versions?
43
90
 
44
91
  ## [4.3.1] - 2022-02-17
92
+
45
93
  ### Fixed
46
- - rubydoc.info: Include .yardopts in gem
94
+
95
+ * rubydoc.info: Include .yardopts in gem
47
96
 
48
97
  ## [4.3.0] - 2022-02-17
98
+
49
99
  ### Fixed
50
- - rubydoc.info: try fix missing class methods.
100
+
101
+ * rubydoc.info: try fix missing class methods.
51
102
 
52
103
  ## [4.2.2] - 2022-02-12
104
+
53
105
  ### Fixed
54
- - Better module documentation fix.
106
+
107
+ * Better module documentation fix.
55
108
 
56
109
  ## [4.2.1] - 2022-02-12
110
+
57
111
  ### Fixed
58
- - Better module documentation.
112
+
113
+ * Better module documentation.
59
114
 
60
115
  ## [4.2.0] - 2022-02-08
116
+
61
117
  ### Fixed
62
- - Proper Unicode character name lookup.
118
+
119
+ * Proper Unicode character name lookup.
63
120
 
64
121
  ## [4.1.1] - 2022-01-28
122
+
65
123
  ### Fixed
66
- - Don't trigger CI when CD will run all tests anyways.
124
+
125
+ * Don't trigger CI when CD will run all tests anyways.
67
126
 
68
127
  ## [4.1.0] - 2022-01-28
128
+
69
129
  ### Changed
70
- - 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.
71
132
 
72
133
  ## [4.0.0] - 2022-01-28
134
+
73
135
  ### Changed
74
- - **Breaking change**: Renamed `NTEncodeMixin` to `ToNTMixin`.
75
- - All code linted with RuboCop
136
+
137
+ * **Breaking change**: Renamed `NTEncodeMixin` to `ToNTMixin`.
138
+ * All code linted with RuboCop
76
139
 
77
140
  ## [3.2.1] - 2022-01-27
141
+
78
142
  ### Fixed
79
- - Fix logo at rubydoc.info
143
+
144
+ * Fix logo at rubydoc.info
80
145
 
81
146
  ## [3.2.0] - 2022-01-27
147
+
82
148
  ### Changed
83
- - 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.
84
151
 
85
152
  ## [3.1.0] - 2022-01-27
153
+
86
154
  ### Changed
87
- - 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.
88
157
 
89
158
  ## [3.0.0] - 2022-01-27
159
+
90
160
  ### Added
91
- - API documentation generated with rdoc.
161
+
162
+ * API documentation generated with rdoc.
92
163
 
93
164
  ### Fixed
94
- - Removed leaked `NT_MIXIN` constant in core extensions.
165
+
166
+ * Removed leaked `NT_MIXIN` constant in core extensions.
95
167
 
96
168
  ### Changed
97
- - **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.
98
- - 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.
99
172
 
100
173
  ## [2.1.0] - 2022-01-27
174
+
101
175
  ### Changed
102
- - Slim down Gem by using include instead of block list.
176
+
177
+ * Slim down Gem by using include instead of block list.
103
178
 
104
179
  ## [2.0.1] - 2022-01-26
180
+
105
181
  ### Fixed
106
- - README issue with logo showing up on Rdoc (out-commented HTML).
182
+
183
+ * README issue with logo showing up on Rdoc (out-commented HTML).
107
184
 
108
185
  ## [2.0.0] - 2022-01-26
186
+
109
187
  ### Changed
110
- - **Breaking change**: strict mode now defaults to false for both the `load` and `dump` methods.
111
- - Internal rename of error classes to be more consistent.
112
- - 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.
113
192
 
114
193
  ## [1.2.0] - 2022-01-25
194
+
115
195
  ### Changed
116
- - Hide core extension `String.normalize_line_endings` from users.
196
+
197
+ * Hide core extension `String.normalize_line_endings` from users.
117
198
 
118
199
  ## [1.1.1] - 2022-01-25
200
+
119
201
  ### Fixed
120
- - Renamed `ToNTMixing` to `ToNTMixin` .
202
+
203
+ * Renamed `ToNTMixing` to `ToNTMixin` .
121
204
 
122
205
  ## [1.1.0] - 2022-01-25
206
+
123
207
  ### Added
124
- - Expose `NestedText::VERSION` for convenience to the users.
208
+
209
+ * Expose `NestedText::VERSION` for convenience to the users.
125
210
 
126
211
  ## [1.0.0] - 2022-01-25
212
+
127
213
  The library is now useful for users!
128
214
 
129
215
  ### Changed
130
- - Hide all internals in the module from users.
216
+
217
+ * Hide all internals in the module from users.
131
218
 
132
219
  ## [0.6.0] - 2022-01-24
220
+
133
221
  ### Fixed
134
- - Move runtime dependencies from Gemfile to .gemspec.
222
+
223
+ * Move runtime dependencies from Gemfile to .gemspec.
135
224
 
136
225
  ## [0.5.0] - 2022-01-24
226
+
137
227
  ### Added
138
- - Publish Gem to GitHub Packages
228
+
229
+ * Publish Gem to GitHub Packages
139
230
 
140
231
  ## [0.4.0] - 2022-01-24
141
- - Iteration on CD GitHub Actions workflow.
232
+
233
+ * Iteration on CD GitHub Actions workflow.
142
234
 
143
235
  ## [0.3.0] - 2022-01-24
144
- - Iteration on CD GitHub Actions workflow.
236
+
237
+ * Iteration on CD GitHub Actions workflow.
145
238
 
146
239
  ## [0.2.0] - 2022-01-24
147
- - Iteration on CD GitHub Actions workflow.
240
+
241
+ * Iteration on CD GitHub Actions workflow.
148
242
 
149
243
  ## [0.1.0] - 2022-01-24
244
+
150
245
  ### Added
151
- - 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
@@ -1,37 +1,42 @@
1
- # NestedText Ruby Library [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=NestedText,%20the%20human%20friendly%20data%20format,%20has%20a%20now%20a%20ruby%20library%20for%20easy%20encoding%20and%20decoding&url=https://github.com/erikw/nestedtext-ruby&via=erik_westrup&hashtags=nestedtext,ruby,gem)
1
+ # NestedText Ruby Library [![Post on X](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://x.com/intent/tweet?text=NestedText,%20the%20human%20friendly%20data%20format,%20has%20a%20now%20a%20ruby%20library%20for%20easy%20encoding%20and%20decoding&url=https://github.com/erikw/nestedtext-ruby&via=erik_westrup&hashtags=nestedtext,ruby,gem)
2
2
  [![Gem Version](https://badge.fury.io/rb/nestedtext.svg)](https://badge.fury.io/rb/nestedtext)
3
- [![Gem Downloads](https://ruby-gem-downloads-badge.herokuapp.com/nestedtext?color=brightgreen&type=total&label=gem%20downloads)](https://rubygems.org/gems/nestedtext)
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.3.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)
9
9
  [![GitHub Actions: CodeQL Analysis](https://github.com/erikw/nestedtext-ruby/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/erikw/nestedtext-ruby/actions/workflows/codeql-analysis.yml)
10
- [![Code Climate Maintainability](https://api.codeclimate.com/v1/badges/8409b6cdc3dc62a33f6f/maintainability)](https://codeclimate.com/github/erikw/nestedtext-ruby/maintainability)
11
- [![Code Climate Test Coverage](https://api.codeclimate.com/v1/badges/8409b6cdc3dc62a33f6f/test_coverage)](https://codeclimate.com/github/erikw/nestedtext-ruby/test_coverage)
12
- [![SLOC](https://img.shields.io/tokei/lines/github/erikw/nestedtext-ruby?logo=codefactor&logoColor=lightgrey)](#)
10
+ [![Code Climate Maintainability](https://qlty.sh/gh/erikw/projects/nestedtext-ruby/maintainability)](https://qlty.sh/gh/erikw/projects/nestedtext-ruby)
11
+ [![Code Climate Test Coverage](https://qlty.sh/gh/erikw/projects/nestedtext-ruby/test_coverage)](https://qlty.sh/gh/erikw/projects/nestedtext-ruby)
12
+ [![SLOC](https://sloc.xyz/github/erikw/nestedtext-ruby?lower=true)](#)
13
13
  [![License](https://img.shields.io/github/license/erikw/nestedtext-ruby?color=informational)](LICENSE.txt)
14
14
  [![OSS Lifecycle](https://img.shields.io/osslifecycle/erikw/nestedtext-ruby)](https://github.com/Netflix/osstracker)
15
15
 
16
+ <p align="center">
17
+ <!-- Ref: https://dev.to/azure/adding-a-github-codespace-button-to-your-readme-5f6l -->
18
+ <a href="https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=428442325" title="Open in GitHub Codespaces" ><img alt="Open in GitHub Codespaces" src="https://github.com/codespaces/badge.svg"></a>
19
+ </p>
16
20
 
17
- A ruby library for the human friendly data format [NestedText](https://nestedtext.org/).
21
+ A Ruby library for the human-friendly data format [NestedText](https://nestedtext.org/).
18
22
 
19
- <!-- Use URL to hosted image, so that it shows up at rubydocs.info as well. Using relative image and yardoc option "--asset img:img" did not work. -->
23
+ <!-- Use URL to host image, so that it shows up at rubydocs.info as well. Using the relative image and yardoc option "--asset img:img" did not work. -->
20
24
  <a href="#" ><img src="https://raw.githubusercontent.com/erikw/nestedtext-ruby/main/img/logo.webp" align="right" width="420px" alt="nestedtext-ruby logo" /></a>
21
25
 
22
- Provided is support for decoding a NestedText file or string to Ruby data structures, as well as encoding Ruby objects to a NestedText file or string. Furthermore there is support for serialization and deserialization of custom classes. The supported language version of the data format can be seen in the badge above. This implementation pass all the [official tests](https://github.com/KenKundert/nestedtext_tests).
26
+ Provided is support for decoding a NestedText file or string to Ruby data structures, as well as encoding Ruby objects to a NestedText file or string. Furthermore, there is support for serialization and deserialization of custom classes. The supported language version of the data format can be seen in the badge above. This implementation passes all the [official tests](https://github.com/KenKundert/nestedtext_tests).
23
27
 
24
- This library is inspired by Ruby's stdlib modules `JSON` and `YAML` as well as the Python [reference implementation](https://github.com/KenKundert/nestedtext) of NestedText. Parsing is done with a LL(1) recursive descent parser and dumping with a recursive DFS traversal of the object references.
28
+ This library is inspired by Ruby's stdlib modules `JSON` and `YAML` as well as the Python [reference implementation](https://github.com/KenKundert/nestedtext) of NestedText. Parsing is done with an LL(1) recursive descent parser, and dumping is with a recursive DFS traversal of the object references.
25
29
 
26
- To make this library practically useful, you should pair it with a [schema validator](#schema).
30
+ > [!TIP]
31
+ > To make this library practically useful, you should pair it with a [schema validator](#schema).
27
32
 
28
33
  # What is NestedText?
29
34
  Citing from the official [introduction](https://nestedtext.org/en/latest/index.html) page:
30
- > NestedText is a file format for holding structured data to be entered, edited, or viewed by people. It organizes the data into a nested collection of dictionaries, lists, and strings without the need for quoting or escaping. A unique feature of this file format is that it only supports one scalar type: strings. While the decision to eschew integer, real, date, etc. types may seem counter intuitive, it leads to simpler data files and applications that are more robust.
35
+ > NestedText is a file format for holding structured data to be entered, edited, or viewed by people. It organizes the data into a nested collection of dictionaries, lists, and strings without the need for quoting or escaping. A unique feature of this file format is that it only supports one scalar type: strings. While the decision to eschew integer, real, date, etc. types may seem counterintuitive, it leads to simpler data files and applications that are more robust.
31
36
  >
32
37
  > NestedText is convenient for configuration files, address books, account information, and the like. Because there is no need for quoting or escaping, it is particularly nice for holding code fragments.
33
38
 
34
- *"Why do we need another data format?"* is the right question to ask. The answer is that the current popular formats (JSON, YAML, TOML, INI etc.) all have shortcomings which NestedText [addresses](https://nestedtext.org/en/latest/alternatives.html).
39
+ *"Why do we need another data format?"* is the right question to ask. The answer is that the current popular formats (JSON, YAML, TOML, INI, etc.) all have shortcomings which NestedText [addresses](https://nestedtext.org/en/latest/alternatives.html).
35
40
 
36
41
  ## Example
37
42
  Here's a full-fledged example of an address book (from the official docs):
@@ -74,16 +79,16 @@ obj1 = NestedText::load(ntstr)
74
79
  obj2 = NestedText::load_file("path/to/data.nt")
75
80
  ```
76
81
 
77
- The type of the returned object depends on the top level type in the NestedText data and will be of corresponding native Ruby type. In the example above, `obj1` will be an `Array` and `obj2` will be `Hash` if `data.nt` looks like e.g.
82
+ The type of the returned object depends on the top-level type in the NestedText data and will be of the corresponding native Ruby type. In the example above, `obj1` will be an `Array` and `obj2` will be a `Hash` if `data.nt` looks like e.g.
78
83
 
79
84
  ```
80
85
  key1: value1
81
86
  key2: value2
82
87
  ```
83
88
 
84
- Thus you must know what you're parsing, or test what you decoded after.
89
+ Thus, you must know what you're parsing, or test what you decoded after.
85
90
 
86
- ### Explicit Top Level Type
91
+ ### Explicit Top-Level Type
87
92
  If you already know what you expect to have, you can guarantee that this is what you will get by telling either function what the expected top type is. If not, an error will be raised.
88
93
 
89
94
  ```ruby
@@ -94,7 +99,7 @@ array = NestedText::load(ntstr, top_class: Array)
94
99
 
95
100
  hash = NestedText::load_file("path/to/data.nt", top_class: Hash)
96
101
 
97
- # will raise NestedText::Error as we specify top level String but it will be Array.
102
+ # will raise NestedText::Error as we specify top-level String, but it will be an Array.
98
103
  NestedText::load(ntstr, top_class: String)
99
104
  ```
100
105
 
@@ -137,7 +142,7 @@ k3:
137
142
  ```
138
143
 
139
144
  ## Types
140
- Ruby classes maps like this to NestedText types:
145
+ Ruby classes map like this to NestedText types:
141
146
  Ruby | [NestedText](https://nestedtext.org/en/latest/basic_syntax.html)
142
147
  ---|---
143
148
  `String` |`String`
@@ -146,7 +151,7 @@ Ruby | [NestedText](https://nestedtext.org/en/latest/basic_syntax.html)
146
151
 
147
152
 
148
153
  ### Strict Mode
149
- The strict mode determines how classes other than the basic types `String`, `Array` and `Hash` are handled during encoding and decoding. By **default** strict mode is **false**.
154
+ The strict mode determines how classes other than the basic types `String`, `Array`, and `Hash` are handled during encoding and decoding. By **default** strict mode is **false**.
150
155
 
151
156
  With `strict: true`
152
157
  Ruby | NestedText | Comment
@@ -189,10 +194,10 @@ class Apple
189
194
  end
190
195
  ```
191
196
 
192
- When an apple instance will be serialized e.g. by `apple.to_nt`, NestedText will call `Apple.encode_nt_with` if it exist and let the returned data be encoded to represent the instance.
197
+ When an Apple instance will be serialized, e.g., by `apple.to_nt`, NestedText will call `Apple.encode_nt_with` if it exists and let the returned data be encoded to represent the instance.
193
198
 
194
199
 
195
- To be able to get this instance back when deserializing the NestedText there must be a class method `Class.nt_create(data)`. When deserializing NestedText and the class `Apple` is detected, and the method `#nt_create` exist on the class, it will be called with the decoded data belonging to it. This method should create and return a new instance of the class. In the most simple case it's just translating this to a call to `#new`.
200
+ To be able to get this instance back when deserializing the NestedText, there must be a class method `Class.nt_create(data)`. When deserializing NestedText and the class `Apple` is detected, and the method `#nt_create` exists on the class, it will be called with the decoded data belonging to it. This method should create and return a new instance of the class. In the simplest case, it's just translating this to a call to `#new`.
196
201
 
197
202
  In full, the `Apple` class should look like:
198
203
 
@@ -223,7 +228,7 @@ data:
223
228
  - 12
224
229
  ```
225
230
 
226
- If you want to add some more super powers to your custom class, you can add the `#to_nt` shortcut by including the `ToNTMixin`:
231
+ If you want to add some more superpowers to your custom class, you can add the `#to_nt` shortcut by including the `ToNTMixin`:
227
232
  ```ruby
228
233
  class Apple
229
234
  include NestedText::ToNTMixin
@@ -235,9 +240,9 @@ Apple.new("granny smith", 12).to_nt
235
240
 
236
241
 
237
242
  **Important notes**:
238
- * The special key to denote the class name is subject to change in future versions and you **must not** rely on it.
243
+ * The special key to denote the class name is subject to change in future versions, and you **must not** rely on it.
239
244
  * Custom Classes **can not be a key** in a Hash. Trying to do this will raise an Error.
240
- * When deserializing a custom class, this custom class must be available when calling the `#dump*` methods e.g.
245
+ * When deserializing a custom class, this custom class must be available when calling the `#dump*` method,s e.g.
241
246
  ```ruby
242
247
  require 'nestedtext'
243
248
  require_relative 'apple' # This is needed if Apple is defined in apple.rb and not in this scope already.
@@ -245,16 +250,17 @@ Apple.new("granny smith", 12).to_nt
245
250
  NestedText::load_file('path/to/apple_dump.nt')
246
251
  ```
247
252
 
248
- See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.rb) for more real working examples.
253
+ > [!TIP]
254
+ > See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.rb) for more real working examples.
249
255
 
250
256
  # Schema
251
- The point of NestedText is to not get in to business of supporting ambiguous types. That's why all values are simple strings. Having only simple strings is not useful in practice though. This is why NestedText is intended to be paired with a [Schema Validator](https://nestedtext.org/en/latest/schemas.html)!
257
+ The point of NestedText is not to get into to business of supporting ambiguous types. That's why all values are simple strings. Having only simple strings is not useful in practice, though. This is why NestedText is intended to be paired with a [Schema Validator](https://nestedtext.org/en/latest/schemas.html)!
252
258
 
253
259
  A schema validator can:
254
260
  * assert that the parsed values are of the expected types
255
261
  * automatically convert them to Ruby class instances like Integer, Float, etc.
256
262
 
257
- The reference implementation in Python [lists](https://nestedtext.org/en/latest/examples.html) a few examples of Python validators. Here below is an example of how this Ruby implementation of NestedText can be paired it with [RSchema](https://github.com/tomdalling/rschema).
263
+ The reference implementation in Python [lists](https://nestedtext.org/en/latest/examples.html) provides a few examples of Python validators. Below is an example of how this Ruby implementation of NestedText can be paired with [RSchema](https://github.com/tomdalling/rschema).
258
264
 
259
265
  ## Example with RSchema
260
266
  The full and working example can be found at [erikw/nestedtext-ruby-test](https://github.com/erikw/nestedtext-ruby-test/blob/main/parse_validate.rb).
@@ -273,7 +279,7 @@ Let's say that you have a program that should connect to a few servers. The list
273
279
  stable: false
274
280
  ```
275
281
 
276
- After parsing this file with this NestedText library, the values for all keys will be string. But to make practical use of this, we would of course like the values for the `port` keys to be `Integer`, and `stable` should have a value of either `true` or `false`. RSchema can do this conversion for us!
282
+ After parsing this file with the NestedText library, the values for all keys will be strings. But to make practical use of this, we would of course like the values for the `port` keys to be `Integer`, and `stable` should have a value of either `true` or `false`. RSchema can do this conversion for us!
277
283
 
278
284
 
279
285
  ```ruby
@@ -289,7 +295,7 @@ schema = RSchema.define do
289
295
  )
290
296
  end
291
297
 
292
- # The coercer will automatially convert types
298
+ # The coercer will automatically convert types
293
299
  coercer = RSchema::CoercionWrapper::RACK_PARAMS.wrap(schema)
294
300
 
295
301
  # Parse config file with NestedText
@@ -309,7 +315,7 @@ port_sum = servers.map { |server| server['port'] }.sum
309
315
  ```
310
316
 
311
317
  # Installation
312
- 1. Add this gem to your ruby project's Gemfile
318
+ 1. Add this gem to your Ruby project's Gemfile
313
319
  - Simply with `$ bundle add nestedtext` when standing inside your project
314
320
  - Or manually by adding to `Gemfile`
315
321
  ```ruby
@@ -329,16 +335,16 @@ port_sum = servers.map { |server| server['port'] }.sum
329
335
 
330
336
  # Development
331
337
  1. Clone the repo
332
- ```console
333
- $ git clone https://github.com/erikw/nestedtext-ruby.git && cd $(basename "$_" .git)
338
+ ```shell
339
+ git clone https://github.com/erikw/nestedtext-ruby.git && cd $(basename "$_" .git)
334
340
  ```
335
- 1. Install a supported ruby version (see .gemspec) with a ruby version manager e.g. [rbenv](https://github.com/rbenv/rbenv), [asdf](http://asdf-vm.com/) or [RVM](https://rvm.io/rvm/install)
336
- 1. run `$ script/setup` or `$ bundle install` to install dependencies
337
- 1. run `$ script/test` or `bundle exec rake test` to run the tests
338
- 1. You can also run `$ script/console` for an interactive prompt that will allow you to experiment.
339
- 1. For local testing, install the gem on local machine with: `$ bundle exec rake install`.
341
+ 1. Install a supported Ruby version (see .gemspec) with a Ruby version manager e.g., [rbenv](https://github.com/rbenv/rbenv), [asdf](http://asdf-vm.com/) or [RVM](https://rvm.io/rvm/install)
342
+ 1. run `$ scripts/setup` or `$ bundle install` to install dependencies
343
+ 1. run `$ scripts/test` or `bundle exec rake test` to run the tests
344
+ 1. You can also run `$ scripts/console` for an interactive prompt that will allow you to experiment.
345
+ 1. For local testing, install the gem on the local machine with: `$ bundle exec rake install`.
340
346
  * or manually with `$ gem build *.gemscpec && gem install *.gem`
341
- 1. Watch changes on file system and execute tests with `$ bundle exec guard`.
347
+ 1. Watch changes on the file system and execute tests with `$ bundle exec guard`.
342
348
 
343
349
 
344
350
  Extra:
@@ -351,44 +357,49 @@ Extra:
351
357
  * To see undocumented methods with [YARD](https://www.rubydoc.info/gems/yard/file/docs/GettingStarted.md): `$ yard stats --list-undoc`
352
358
 
353
359
  # Releasing
354
- Instructions for releasing on rubygems.org below. Optionally make a GitHub [release](https://github.com/erikw/nestedtext-ruby/releases) after this for the pushed git tag.
360
+ Instructions for releasing on rubygems.org are below. Optionally make a GitHub [release](https://github.com/erikw/nestedtext-ruby/releases) after this for the pushed git tag.
355
361
 
356
362
  ## (manually) Using bundler/gem_tasks rake tasks
357
363
  Following instructions from [bundler.io](https://bundler.io/guides/creating_gem.html#releasing-the-gem):
358
- ```console
359
- $ vi -p lib/nestedtext/version.rb CHANGELOG.md
360
- $ bundle exec rake build
361
- $ ver=$(ruby -r ./lib/nestedtext/version.rb -e 'puts NestedText::VERSION')
362
- $ bundle exec rake release
364
+ ```shell
365
+ vi -p lib/nestedtext/version.rb CHANGELOG.md
366
+ bundle exec rake build
367
+ ver=$(ruby -r ./lib/nestedtext/version.rb -e 'puts NestedText::VERSION')
368
+ bundle exec rake release
363
369
  ```
364
370
 
365
371
  ## (semi-manually) Using gem-release gem extension
366
372
  Using [gem-release](https://github.com/svenfuchs/gem-release):
367
- ```console
368
- $ vi CHANGELOG.md && git commit -am "Update CHANGELOG.md" && git push
369
- $ gem bump --version minor --tag --sign --push --release
373
+ ```shell
374
+ vi CHANGELOG.md && git commit -am "Update CHANGELOG.md" && git push
375
+ gem signin
376
+ gem bump --version minor --tag --sign --push --release
370
377
  ```
371
378
  For `--version`, use `major|minor|patch` as needed.
372
379
 
373
380
  ## (semi-automatic, preferred) Using GitHub Actions CD
374
381
  Just push a new semver tag and the workflow [cd.yml](.github/workflows/cd.yml) will publish a new release at rubygems.org.
375
382
 
376
- ```console
377
- $ vi -p lib/nestedtext/version.rb CHANGELOG.md
378
- $ git commit -am "Prepare vX.Y.Z" && git push
379
- $ git tag vX.Y.Z && git push --tags
383
+ ```shell
384
+ vi -p lib/nestedtext/version.rb CHANGELOG.md
385
+ git commit -am "Prepare vX.Y.Z" && git push
386
+ git tag vX.Y.Z && git push --tags
380
387
  ```
381
388
 
382
- or combined with gem-release:
383
- ```console
384
- $ vi CHANGELOG.md
385
- $ git commit -am "Update CHANGELOG.md" && git push
386
- $ gem bump --version minor --tag --push --sign
389
+ or **preferred** combined with gem-release:
390
+ ```shell
391
+ vi CHANGELOG.md
392
+ git commit -am "Update CHANGELOG.md" && git push
393
+ gem signin
394
+ gem bump --version minor --tag --push --sign
387
395
  ```
388
396
 
389
- then watch progress with [gh](https://cli.github.com/)
390
- ```console
391
- $ gh run watch
397
+ then watch progress with [gh](https://cli.github.com/).
398
+
399
+ For `--version`, use `major|minor|patch` as needed.
400
+
401
+ ```shell
402
+ gh run watch
392
403
  ```
393
404
 
394
405
 
@@ -399,5 +410,5 @@ Bug reports and pull requests are welcome on GitHub at [erikw/nestedtext-ruby](h
399
410
  The gem is available as open source with the [License](./LICENSE.txt).
400
411
 
401
412
  # Acknowledgments
402
- * Thanks to the data format authors making it easier making new implementations by providing an [official test suite](https://github.com/KenKundert/nestedtext_tests).
413
+ * Thanks to the data format authors for making it easier to make new implementations by providing an [official test suite](https://github.com/KenKundert/nestedtext_tests).
403
414
  * Thanks to [pixteller](https://pixteller.com/) & [mp4.to](https://mp4.to/webp/) for offering the tools needed for creating an animated logo.
@@ -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}’.")
@@ -151,7 +176,7 @@ module NestedText
151
176
  class ParseLineTypeNotExpectedError < ParseError
152
177
  def initialize(line, type_exps, type_act)
153
178
  super(line, line.indentation,
154
- "The current line was detected to be #{type_act}, "\
179
+ "The current line was detected to be #{type_act}, " \
155
180
  "but we expected to see any of [#{type_exps.join(', ')}] here.")
156
181
  end
157
182
  end
@@ -166,7 +191,7 @@ module NestedText
166
191
  def initialize(line)
167
192
  char = line.content[0]
168
193
  # Official-tests kludge; Translate rubys \u00 to python's unicodedata.name \x format.
169
- printable_char = char.dump.gsub(/"/, '').gsub(/\\u0*/, '\x').downcase
194
+ printable_char = char.dump.gsub('"', '').gsub(/\\u0*/, '\x').downcase
170
195
 
171
196
  explanation = ''
172
197
  # Official-tests kludge; ASCII chars have printable names too,
@@ -195,7 +220,7 @@ module NestedText
195
220
  class ParseCustomClassNoCreateMethodError < ParseError
196
221
  def initialize(line, class_name)
197
222
  super(line, line.indentation,
198
- "Detected an encode custom class #{class_name} "\
223
+ "Detected an encode custom class #{class_name} " \
199
224
  "but it does not have a #nt_create method, so it can't be deserialzied.")
200
225
  end
201
226
  end
@@ -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
@@ -275,8 +301,8 @@ module NestedText
275
301
 
276
302
  class DumpBadIOError < InternalError
277
303
  def initialize(io)
278
- super('When giving the io argument, it must be of type IO (respond to #write, #fsync).' \
279
- " Given: #{io.class.name}")
304
+ super('When giving the io argument, it must be of type IO (respond to #write, #fsync). ' \
305
+ "Given: #{io.class.name}")
280
306
  end
281
307
  end
282
308
 
@@ -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)
@@ -45,7 +47,7 @@ module NestedText
45
47
  private
46
48
 
47
49
  def return_object(result)
48
- raise Errors::AssertionError, 'Parsed result is of unexpected type.' if \
50
+ raise Errors::AssertionError, 'Parsed result is of unexpected type.' if
49
51
  !result.nil? && ![Hash, Array, String].include?(result.class) && @strict
50
52
 
51
53
  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
@@ -132,7 +132,7 @@ module NestedText
132
132
  end
133
133
 
134
134
  def detect_line_tag
135
- if @content.length.zero?
135
+ if @content.length.zero? # rubocop:disable Style/ZeroLengthPredicate
136
136
  self.tag = :blank
137
137
  elsif @content[0] == '#'
138
138
  self.tag = :comment
@@ -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.5.0' # The version of this library.
4
+ VERSION = '5.0.2' # The version of this library.
5
5
  end
data/nestedtext.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
6
6
  spec.name = 'nestedtext'
7
7
  spec.version = NestedText::VERSION
8
8
  spec.authors = ['Erik Westrup']
9
- spec.email = ['erik.westrup@gmail.com']
9
+ spec.email = ['erik.westrup@icloud.com']
10
10
 
11
11
  spec.summary = 'A ruby library for the human friendly data format NestedText (https://nestedtext.org/)'
12
12
  spec.description = 'A ruby library for the human friendly data format NestedText (https://nestedtext.org/). There is support for decoding a NestedText file or string to Ruby data structures, as well as encoding Ruby objects to a NestedText file or string. Furthermore there is support for serialization and deserialization of custom classes. Support for v3.2.1 of the data format will all official tests passing.'
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.5.0
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Westrup
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-09 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
@@ -58,7 +58,7 @@ description: A ruby library for the human friendly data format NestedText (https
58
58
  is support for serialization and deserialization of custom classes. Support for
59
59
  v3.2.1 of the data format will all official tests passing.
60
60
  email:
61
- - erik.westrup@gmail.com
61
+ - erik.westrup@icloud.com
62
62
  executables: []
63
63
  extensions: []
64
64
  extra_rdoc_files: []
@@ -90,7 +90,7 @@ licenses:
90
90
  metadata:
91
91
  github_repo: git@github.com:erikw/nestedtext-ruby.git
92
92
  rubygems_mfa_required: 'true'
93
- post_install_message:
93
+ post_install_message:
94
94
  rdoc_options: []
95
95
  require_paths:
96
96
  - lib
@@ -108,8 +108,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  requirements: []
111
- rubygems_version: 3.3.7
112
- signing_key:
111
+ rubygems_version: 3.3.3
112
+ signing_key:
113
113
  specification_version: 4
114
114
  summary: A ruby library for the human friendly data format NestedText (https://nestedtext.org/)
115
115
  test_files: []