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 +4 -4
- data/CHANGELOG.md +136 -40
- data/README.md +71 -60
- data/lib/nestedtext/decode.rb +78 -3
- data/lib/nestedtext/errors_internal.rb +34 -8
- data/lib/nestedtext/parser.rb +9 -3
- data/lib/nestedtext/scanners.rb +13 -8
- data/lib/nestedtext/version.rb +1 -1
- data/nestedtext.gemspec +1 -1
- metadata +7 -7
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,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
|
-
|
|
42
|
+
|
|
43
|
+
* Guard filesystem watch and test executor.
|
|
12
44
|
|
|
13
45
|
### Changed
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
|
|
59
|
+
* rubydoc.info: try remove unused module require.
|
|
23
60
|
|
|
24
61
|
## [4.4.4] - 2022-02-17
|
|
62
|
+
|
|
25
63
|
### Fixed
|
|
26
|
-
|
|
64
|
+
|
|
65
|
+
* rubydoc.info: revert reject instead of select
|
|
27
66
|
|
|
28
67
|
## [4.4.3] - 2022-02-17
|
|
68
|
+
|
|
29
69
|
### Fixed
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
|
|
83
|
+
* rubydoc.info: try fix missing class methods.
|
|
39
84
|
|
|
40
85
|
## [4.4.0] - 2022-02-17
|
|
86
|
+
|
|
41
87
|
### Fixed
|
|
42
|
-
|
|
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
|
-
|
|
94
|
+
|
|
95
|
+
* rubydoc.info: Include .yardopts in gem
|
|
47
96
|
|
|
48
97
|
## [4.3.0] - 2022-02-17
|
|
98
|
+
|
|
49
99
|
### Fixed
|
|
50
|
-
|
|
100
|
+
|
|
101
|
+
* rubydoc.info: try fix missing class methods.
|
|
51
102
|
|
|
52
103
|
## [4.2.2] - 2022-02-12
|
|
104
|
+
|
|
53
105
|
### Fixed
|
|
54
|
-
|
|
106
|
+
|
|
107
|
+
* Better module documentation fix.
|
|
55
108
|
|
|
56
109
|
## [4.2.1] - 2022-02-12
|
|
110
|
+
|
|
57
111
|
### Fixed
|
|
58
|
-
|
|
112
|
+
|
|
113
|
+
* Better module documentation.
|
|
59
114
|
|
|
60
115
|
## [4.2.0] - 2022-02-08
|
|
116
|
+
|
|
61
117
|
### Fixed
|
|
62
|
-
|
|
118
|
+
|
|
119
|
+
* Proper Unicode character name lookup.
|
|
63
120
|
|
|
64
121
|
## [4.1.1] - 2022-01-28
|
|
122
|
+
|
|
65
123
|
### Fixed
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
143
|
+
|
|
144
|
+
* Fix logo at rubydoc.info
|
|
80
145
|
|
|
81
146
|
## [3.2.0] - 2022-01-27
|
|
147
|
+
|
|
82
148
|
### Changed
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
|
|
162
|
+
* API documentation generated with rdoc.
|
|
92
163
|
|
|
93
164
|
### Fixed
|
|
94
|
-
|
|
165
|
+
|
|
166
|
+
* Removed leaked `NT_MIXIN` constant in core extensions.
|
|
95
167
|
|
|
96
168
|
### Changed
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
+
|
|
203
|
+
* Renamed `ToNTMixing` to `ToNTMixin` .
|
|
121
204
|
|
|
122
205
|
## [1.1.0] - 2022-01-25
|
|
206
|
+
|
|
123
207
|
### Added
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
+
|
|
223
|
+
* Move runtime dependencies from Gemfile to .gemspec.
|
|
135
224
|
|
|
136
225
|
## [0.5.0] - 2022-01-24
|
|
226
|
+
|
|
137
227
|
### Added
|
|
138
|
-
|
|
228
|
+
|
|
229
|
+
* Publish Gem to GitHub Packages
|
|
139
230
|
|
|
140
231
|
## [0.4.0] - 2022-01-24
|
|
141
|
-
|
|
232
|
+
|
|
233
|
+
* Iteration on CD GitHub Actions workflow.
|
|
142
234
|
|
|
143
235
|
## [0.3.0] - 2022-01-24
|
|
144
|
-
|
|
236
|
+
|
|
237
|
+
* Iteration on CD GitHub Actions workflow.
|
|
145
238
|
|
|
146
239
|
## [0.2.0] - 2022-01-24
|
|
147
|
-
|
|
240
|
+
|
|
241
|
+
* Iteration on CD GitHub Actions workflow.
|
|
148
242
|
|
|
149
243
|
## [0.1.0] - 2022-01-24
|
|
244
|
+
|
|
150
245
|
### Added
|
|
151
|
-
|
|
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 [](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
|
[](https://badge.fury.io/rb/nestedtext)
|
|
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)
|
|
9
9
|
[](https://github.com/erikw/nestedtext-ruby/actions/workflows/codeql-analysis.yml)
|
|
10
|
-
[](https://qlty.sh/gh/erikw/projects/nestedtext-ruby)
|
|
11
|
+
[](https://qlty.sh/gh/erikw/projects/nestedtext-ruby)
|
|
12
|
+
[](#)
|
|
13
13
|
[](LICENSE.txt)
|
|
14
14
|
[](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
|
|
21
|
+
A Ruby library for the human-friendly data format [NestedText](https://nestedtext.org/).
|
|
18
22
|
|
|
19
|
-
<!-- Use URL to
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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*`
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
```
|
|
333
|
-
|
|
338
|
+
```shell
|
|
339
|
+
git clone https://github.com/erikw/nestedtext-ruby.git && cd $(basename "$_" .git)
|
|
334
340
|
```
|
|
335
|
-
1. Install a supported
|
|
336
|
-
1. run `$
|
|
337
|
-
1. run `$
|
|
338
|
-
1. You can also run `$
|
|
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
|
-
```
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
```
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
```
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
```
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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
|
|
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.
|
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}’.")
|
|
@@ -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(
|
|
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
|
-
# [
|
|
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
|
|
@@ -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
|
-
"
|
|
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
|
|
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)
|
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
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@
|
|
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
|
+
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:
|
|
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@
|
|
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.
|
|
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: []
|