nestedtext 1.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +210 -30
- data/SECURITY.md +2 -2
- data/lib/nestedtext/core_ext.rb +6 -5
- data/lib/nestedtext/core_ext_internal.rb +1 -1
- data/lib/nestedtext/decode.rb +23 -3
- data/lib/nestedtext/dumper.rb +15 -17
- data/lib/nestedtext/encode.rb +25 -15
- data/lib/nestedtext/encode_helpers.rb +7 -19
- data/lib/nestedtext/error.rb +8 -0
- data/lib/nestedtext/{errors.rb → errors_internal.rb} +28 -33
- data/lib/nestedtext/parser.rb +34 -34
- data/lib/nestedtext/scanners.rb +4 -5
- data/lib/nestedtext/version.rb +2 -1
- data/lib/nestedtext.rb +9 -2
- data/nestedtext.gemspec +4 -4
- metadata +10 -16
- data/.editorconfig +0 -24
- data/.gitignore +0 -21
- data/.rubocop.yml +0 -143
- data/.ruby-version +0 -1
- data/Gemfile +0 -27
- data/OSSMETADATA +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfafdf3e9638459777da2f7209954955473758103c72533b58a66cc23f4020e3
|
4
|
+
data.tar.gz: 400fd879811f4d09c193da219ffa0ff61853ee0e51baac386c58b7016dfbe354
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 708025814b4038b1b5c6eea70378762e1155ae73e933cc902a17d26d4ce3efbb2a5b52f7d535c385818e34484fe12278f981c253469d80a875e97aa8f25b20c1
|
7
|
+
data.tar.gz: fe87505719301bf79a71cdef1c9d89d71f9058318f5f59f1305ac79b0b69007b79976d6e288a4846824c6cc89323021b4b29df56b8f14f534cd59906d55b0843
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [3.0.0] - 2022-01-27
|
10
|
+
### Added
|
11
|
+
- API documentation generated with rdoc.
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
- Removed leaked `NT_MIXIN` constant in core extensions.
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
- **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 CustomObjects and calling the method on the array.
|
18
|
+
- Internal clean-up and simplifications on helper classes and methods.
|
19
|
+
|
20
|
+
## [2.1.0] - 2022-01-27
|
21
|
+
### Changed
|
22
|
+
- Slim down Gem by using include instead of block list.
|
23
|
+
|
24
|
+
## [2.0.1] - 2022-01-26
|
25
|
+
### Fixed
|
26
|
+
- README issue with logo showing up on Rdoc (out-commented HTML).
|
27
|
+
|
28
|
+
## [2.0.0] - 2022-01-26
|
29
|
+
### Changed
|
30
|
+
- **Breaking change**: strict mode now defaults to false for both the `load` and `dump` methods.
|
31
|
+
- Internal rename of error classes to be more consistent.
|
32
|
+
- Internal simplification of argument passing.
|
33
|
+
|
9
34
|
## [1.2.0] - 2022-01-25
|
10
35
|
### Changed
|
11
36
|
- Hide core extension `String.normalize_line_endings` from users.
|
data/CONTRIBUTING.md
CHANGED
data/README.md
CHANGED
@@ -1,43 +1,116 @@
|
|
1
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)
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/nestedtext.svg)](https://badge.fury.io/rb/nestedtext)
|
3
3
|
[![Gem Downloads](https://ruby-gem-downloads-badge.herokuapp.com/nestedtext?color=brightgreen&type=total&label=gem%20downloads)](https://rubygems.org/gems/nestedtext)
|
4
|
+
[![Documentation](https://img.shields.io/badge/docs-API-informational?logo=readthedocs&logoColor=violet)](https://www.rubydoc.info/gems/nestedtext/)
|
4
5
|
[![Data Format Version Supported](https://img.shields.io/badge/%F0%9F%84%BD%F0%9F%85%83%20Version%20Supported-3.2.1-blueviolet)](https://nestedtext.org/en/v3.2/)
|
5
|
-
[![Official Tests](https://img.shields.io/badge
|
6
|
+
[![Official Tests](https://img.shields.io/badge/Official%20Tests-Passing-success?logo=cachet)](https://github.com/KenKundert/nestedtext_tests/tree/585e95a73d94ac1f48e71a154e2db0ab67cf30fa)
|
6
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)
|
7
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)
|
8
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)
|
9
10
|
[![Code Climate Maintainability](https://api.codeclimate.com/v1/badges/8409b6cdc3dc62a33f6f/maintainability)](https://codeclimate.com/github/erikw/nestedtext-ruby/maintainability)
|
10
11
|
[![Code Climate Test Coverage](https://api.codeclimate.com/v1/badges/8409b6cdc3dc62a33f6f/test_coverage)](https://codeclimate.com/github/erikw/nestedtext-ruby/test_coverage)
|
11
|
-
[![SLOC](https://img.shields.io/tokei/lines/github/erikw/nestedtext-ruby)](#)
|
12
|
-
[![License](https://img.shields.io/github/license/erikw/nestedtext-ruby)](LICENSE.txt)
|
12
|
+
[![SLOC](https://img.shields.io/tokei/lines/github/erikw/nestedtext-ruby?logo=codefactor&logoColor=lightgrey)](#)
|
13
|
+
[![License](https://img.shields.io/github/license/erikw/nestedtext-ruby?color=informational)](LICENSE.txt)
|
13
14
|
[![OSS Lifecycle](https://img.shields.io/osslifecycle/erikw/nestedtext-ruby)](https://github.com/Netflix/osstracker)
|
14
15
|
|
15
16
|
|
16
|
-
|
17
|
+
A ruby library for the human friendly data format [NestedText](https://nestedtext.org/).
|
17
18
|
|
18
|
-
|
19
|
+
<a href="#" ><img src="https://raw.githubusercontent.com/erikw/nestedtext-ruby/main/img/logo.webp" align="right" width="420px" alt="Project logo" /></a>
|
19
20
|
|
20
|
-
|
21
|
+
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 see in the badge above. This implementation pass all the [official tests](https://github.com/KenKundert/nestedtext_tests).
|
22
|
+
|
23
|
+
This library is inspired Ruby 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.
|
21
24
|
|
22
25
|
# What is NestedText?
|
23
|
-
|
26
|
+
Citing from the official [introduction](https://nestedtext.org/en/latest/index.html) page:
|
27
|
+
> 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.
|
28
|
+
>
|
29
|
+
> 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.
|
24
30
|
|
25
|
-
https://nestedtext.org/en/latest/alternatives.html
|
31
|
+
*"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).
|
26
32
|
|
27
|
-
##
|
28
|
-
|
33
|
+
## Example
|
34
|
+
Here's a full-fledged example of an address book (from the official docs):
|
35
|
+
```nestedtext
|
36
|
+
# Contact information for our officers
|
29
37
|
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
president:
|
39
|
+
name: Katheryn McDaniel
|
40
|
+
address:
|
41
|
+
> 138 Almond Street
|
42
|
+
> Topeka, Kansas 20697
|
43
|
+
phone:
|
44
|
+
cell: 1-210-555-5297
|
45
|
+
home: 1-210-555-8470
|
46
|
+
# Katheryn prefers that we always call her on her cell phone.
|
47
|
+
email: KateMcD@aol.com
|
48
|
+
additional roles:
|
49
|
+
- board member
|
50
|
+
|
51
|
+
vice president:
|
52
|
+
name: Margaret Hodge
|
53
|
+
...
|
54
|
+
```
|
33
55
|
|
56
|
+
See the [language introduction](https://nestedtext.org/en/latest/basic_syntax.html) for more details.
|
34
57
|
|
35
58
|
# Usage
|
59
|
+
The full API documentation can be found at [**rubydocs.info**](https://www.rubydoc.info/gems/nestedtext/). A minimal & fully working example of a project using this library can be found at [erikw/nestedtext-ruby-test](https://github.com/erikw/nestedtext-ruby-test).
|
60
|
+
|
36
61
|
## Decoding (reading NT)
|
62
|
+
This is how you can decode NestedText from a string or directly from a file (`*.nt`) to Ruby object instances:
|
63
|
+
|
64
|
+
### Any Top Level Type
|
65
|
+
```ruby
|
66
|
+
require 'nestedtext'
|
67
|
+
|
68
|
+
ntstr = "- objitem1\n- list item 2"
|
69
|
+
obj1 = NestedText::load(ntstr)
|
70
|
+
|
71
|
+
obj2 = NestedText::load_file("path/to/data.nt")
|
72
|
+
```
|
73
|
+
|
74
|
+
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.
|
75
|
+
|
76
|
+
```
|
77
|
+
key1: value1
|
78
|
+
key2: value2
|
79
|
+
```
|
80
|
+
|
81
|
+
Thus you must know what you're parsing, or test what you decoded.
|
82
|
+
|
83
|
+
### Explicit Top Level Type
|
84
|
+
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.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
require 'nestedtext'
|
88
|
+
|
89
|
+
ntstr = "- objitem1\n- list item 2"
|
90
|
+
array = NestedText::load(ntstr, top_class: Array)
|
91
|
+
|
92
|
+
hash = NestedText::load_file("path/to/data.nt", top_class: Hash)
|
93
|
+
|
94
|
+
# will raise NestedText::Error as we specify top level String but it will be Array.
|
95
|
+
NestedText::load(ntstr, top_class: String)
|
96
|
+
```
|
37
97
|
|
38
98
|
## Encoding (writing NT)
|
99
|
+
This is how you can decode Ruby objects to a NestedText string or file:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
require 'nestedtext'
|
103
|
+
|
104
|
+
data = ["i1", "i2"]
|
105
|
+
|
106
|
+
ntstr = NestedText::dump(data)
|
107
|
+
|
108
|
+
NestedText::dump_file(data, "path/to/data.nt")
|
109
|
+
```
|
110
|
+
|
111
|
+
### `#to_nt` Convenience
|
112
|
+
To make it more convenient, the Ruby Core is extended with a `#to_nt` method on the supported types that will dump a String of the data structure. Here's an IRB session showing how it works:
|
39
113
|
|
40
|
-
`#to_nt` method:
|
41
114
|
```irb
|
42
115
|
irb> require 'nestedtext'
|
43
116
|
irb> puts "a\nstring".to_nt
|
@@ -47,30 +120,136 @@ irb> puts ["i1", "i2", "i3"].to_nt
|
|
47
120
|
- i1
|
48
121
|
- i2
|
49
122
|
- i3
|
50
|
-
irb> puts({"k1" => "v1", "multiline\nkey" => "v2", "k3" => "
|
123
|
+
irb> puts({"k1" => "v1", "multiline\nkey" => "v2", "k3" => ["a", "list"]}.to_nt)
|
51
124
|
k1: v1
|
52
125
|
: multiline
|
53
126
|
: key
|
54
127
|
> v2
|
55
128
|
k3:
|
56
|
-
|
57
|
-
|
129
|
+
- a
|
130
|
+
- list
|
58
131
|
```
|
132
|
+
|
133
|
+
## Types
|
134
|
+
Ruby classes maps like this to NestedText types:
|
135
|
+
Ruby | [NestedText](https://nestedtext.org/en/latest/basic_syntax.html)
|
136
|
+
---|---
|
137
|
+
`String` |`String`
|
138
|
+
`Array` |`List`
|
139
|
+
`Hash` |`Dictionary`
|
140
|
+
|
141
|
+
|
142
|
+
### Strict Mode
|
143
|
+
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 turned **off**.
|
144
|
+
|
145
|
+
With `strict: true`
|
146
|
+
Ruby | NestedText | Comment
|
147
|
+
---|---|---
|
148
|
+
`nil` |*empty* | (1.)
|
149
|
+
`Symbol` |`String` | Raises `NestedText::Error`
|
150
|
+
Other Class | -- | Raises `NestedText::Error`
|
151
|
+
|
152
|
+
|
153
|
+
With `strict: false`
|
154
|
+
Ruby | NestedText | Comment
|
155
|
+
---|---|---
|
156
|
+
`nil` | *Custom Class Encoding* | (1.)
|
157
|
+
`Symbol` | `String` |
|
158
|
+
Custom Class | *Custom Class Encoding* | If the [Custom Class](#custom-classes-serialization) implements `#encode_nt_with` (2.)
|
159
|
+
Other Class | String | `#to_s` will be called if there is no `#encode_nt_with`
|
160
|
+
|
161
|
+
|
162
|
+
* (1.) How empty strings and nil are handled depends on where it is used. This library follows how the official implementation does it.
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
|
59
168
|
## Custom Classes Serialization
|
60
|
-
This library has support for serialization/deserialization of custom classes as well.
|
61
|
-
|
169
|
+
This library has support for serialization/deserialization of custom classes as well. This is done by letting the objects tell NestedText what data should be used to represent the object instance with the `#encode_nt_with` method (inspired by `YAML`'s `#encode_with` method). All objects being recursively referenced from a root object being serialized must either implement this method or be one of the core supported NestedText data types from the table above.
|
170
|
+
|
171
|
+
A class implementing `#encode_nt_with` is refered to as `Custom Class` in this document.
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
class Apple
|
175
|
+
def initialize(type, weight)
|
176
|
+
@type = type
|
177
|
+
@weight = weight
|
178
|
+
end
|
179
|
+
|
180
|
+
def encode_nt_with
|
181
|
+
[@type, @weight]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
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.
|
187
|
+
|
188
|
+
|
189
|
+
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`.
|
190
|
+
|
191
|
+
In full, the `Apple` class should look like:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class Apple
|
195
|
+
def self.nt_create(data)
|
196
|
+
new(*data)
|
197
|
+
end
|
198
|
+
|
199
|
+
def initialize(type, weight)
|
200
|
+
@type = type
|
201
|
+
@weight = weight
|
202
|
+
end
|
203
|
+
|
204
|
+
def encode_nt_with
|
205
|
+
[@type, @weight]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
An instance of this class would be encoded like this:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
irb> puts NestedText::dump(Apple.new("granny smith", 12))
|
214
|
+
__nestedtext_class__: Apple
|
215
|
+
data:
|
216
|
+
- granny smith
|
217
|
+
- 12
|
218
|
+
```
|
219
|
+
|
220
|
+
If you want to add some more super powers to your custom class, you can add the `#to_nt` shortcut by including the `NTEncodeMixin`:
|
221
|
+
```ruby
|
222
|
+
class Apple
|
223
|
+
include NestedText::NTEncodeMixin
|
224
|
+
...
|
225
|
+
end
|
226
|
+
|
227
|
+
Apple.new("granny smith", 12).to_nt
|
228
|
+
```
|
229
|
+
|
230
|
+
|
231
|
+
**Important notes**:
|
232
|
+
* The special key to denote the class name is subject to change in future versions and you **must not** rely on it.
|
233
|
+
* Custom Classes **can not be a key** in a Hash. Trying to do this will raise an Error.
|
234
|
+
* When deserializing a custom class, this custom class must be available when calling the `#dump*` methods e.g.
|
235
|
+
```ruby
|
236
|
+
require 'nestedtext'
|
237
|
+
require_relative 'apple' # This is needed if Apple is defined in apple.rb and not in this scope already.
|
238
|
+
|
239
|
+
NestedText::load_file('path/to/apple_dump.nt')
|
240
|
+
```
|
241
|
+
|
62
242
|
See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.rb) for more real working examples.
|
63
243
|
|
64
244
|
|
65
245
|
# Installation
|
66
246
|
1. Add this gem to your ruby project's Gemfile
|
67
|
-
- Simply with `$ bundle add nestedtext` when standing
|
247
|
+
- Simply with `$ bundle add nestedtext` when standing inside your project
|
68
248
|
- Or manually by adding to `Gemfile`
|
69
249
|
```ruby
|
70
250
|
gem 'nestedtext'
|
71
251
|
```
|
72
252
|
and then running `$ bundle install`.
|
73
|
-
```
|
74
253
|
1. Require the library and start using it!
|
75
254
|
```ruby
|
76
255
|
require 'nestedtext'
|
@@ -89,20 +268,20 @@ See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.r
|
|
89
268
|
$ git clone https://github.com/erikw/nestedtext-ruby.git && cd $(basename "$_" .git)
|
90
269
|
```
|
91
270
|
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)
|
92
|
-
1. run `$ script/setup` to install dependencies
|
93
|
-
1. run `$ script/test` to run the tests
|
271
|
+
1. run `$ script/setup` or `$ bundle install` to install dependencies
|
272
|
+
1. run `$ script/test` or `bundle exec rake test` to run the tests
|
94
273
|
1. You can also run `$ script/console` for an interactive prompt that will allow you to experiment.
|
95
274
|
1. For local testing, install the gem on local machine with: `$ bundle exec rake install`.
|
96
|
-
* or
|
275
|
+
* or manually with `$ gem build *.gemscpec && gem install *.gem`
|
97
276
|
|
98
|
-
Make sure that only intended constants and methods are exposed from the module `NestedText`. Check with
|
277
|
+
Make sure that only intended constants and methods are exposed publicly from the module `NestedText`. Check with
|
99
278
|
```
|
100
279
|
irb> require 'nestedtext'
|
101
280
|
irb> NestedText.constants
|
102
281
|
irb> NestedText.methods(false)
|
103
282
|
```
|
104
283
|
|
105
|
-
|
284
|
+
# Releasing
|
106
285
|
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.
|
107
286
|
|
108
287
|
## (manually) Using bundler/gem_tasks rake tasks
|
@@ -131,7 +310,7 @@ $ git commit -am "Prepare vX.Y.Z" && git push
|
|
131
310
|
$ git tag x.y.z && git push --tags
|
132
311
|
```
|
133
312
|
|
134
|
-
or combined with gem-release
|
313
|
+
or combined with gem-release:
|
135
314
|
```console
|
136
315
|
$ vi CHANGELOG.md
|
137
316
|
$ git commit -am "Update CHANGELOG.md" && git push
|
@@ -140,10 +319,11 @@ $ gem bump --version minor --tag --sign --push
|
|
140
319
|
|
141
320
|
|
142
321
|
# Contributing
|
143
|
-
Bug reports and pull requests are welcome on GitHub at [
|
322
|
+
Bug reports and pull requests are welcome on GitHub at [erikw/nestedtext-ruby](https://github.com/erikw/nestedtext-ruby).
|
144
323
|
|
145
324
|
# License
|
146
325
|
The gem is available as open source with the [License](./LICENSE.txt).
|
147
326
|
|
148
|
-
#
|
149
|
-
Thanks to the data format authors making it easier making new implementations by providing an [official test suite](https://github.com/KenKundert/nestedtext_tests).
|
327
|
+
# Acknowledgments
|
328
|
+
* Thanks to the data format authors making it easier making new implementations by providing an [official test suite](https://github.com/KenKundert/nestedtext_tests).
|
329
|
+
* Thanks to [pixteller](https://pixteller.com/) & [mp4.to](https://mp4.to/webp/) for offering the tools needed for creating an animated logo.
|
data/SECURITY.md
CHANGED
data/lib/nestedtext/core_ext.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
require "nestedtext/encode_helpers"
|
2
2
|
|
3
|
+
# Extension of Ruby core types with the NestedText::NTEncodeMixin.
|
4
|
+
#
|
3
5
|
# TODO: add encoding of more Ruby native classes like Integer, Float etc plus commons like Set,....? Not covered in NestedText language.
|
4
6
|
# Or leave this to a schema validator 3rd party plugin maybe? And replace my custom class decoding (and also encoding?)?
|
5
7
|
# Or both: add encoding/decoding of more native classes, and allow decoding + applying a schema with 3rd party.
|
6
8
|
# Or encourage using Marshal from core?
|
7
9
|
|
8
|
-
|
9
|
-
class
|
10
|
-
class
|
11
|
-
class Hash include NT_MIXIN end
|
10
|
+
class String include NestedText::NTEncodeMixin; end
|
11
|
+
class Array include NestedText::NTEncodeMixin; end
|
12
|
+
class Hash include NestedText::NTEncodeMixin; end
|
12
13
|
|
13
14
|
class NilClass
|
14
|
-
include
|
15
|
+
include NestedText::NTEncodeMixin
|
15
16
|
|
16
17
|
def self.nt_create(_data) = nil
|
17
18
|
|
data/lib/nestedtext/decode.rb
CHANGED
@@ -1,19 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "nestedtext/parser"
|
4
|
-
require "nestedtext/
|
4
|
+
require "nestedtext/errors_internal"
|
5
5
|
|
6
6
|
require "logger"
|
7
7
|
require "stringio"
|
8
8
|
|
9
9
|
module NestedText
|
10
|
-
|
10
|
+
# Decode a NestedText string to Ruby objects.
|
11
|
+
#
|
12
|
+
# [ntstring] The string containing NestedText to be decoded.
|
13
|
+
# [top_class] Force the top level returned object to be of this type. Supported values are +Object+, +Array+, +Hash+ and +String+. Default is +Object+.
|
14
|
+
# [strict] If strict mode should be used. +true+ or +false+. Default is +false+
|
15
|
+
#
|
16
|
+
# Returns the parsed object.
|
17
|
+
#
|
18
|
+
# Raises NestedText::Error if anything went wrong.
|
19
|
+
def self.load(ntstring, top_class: Object, strict: false)
|
11
20
|
raise Errors::WrongInputTypeError.new([String], ntstring) unless ntstring.nil? || ntstring.is_a?(String)
|
12
21
|
|
13
22
|
Parser.new(StringIO.new(ntstring), top_class, strict: strict).parse
|
14
23
|
end
|
15
24
|
|
16
|
-
|
25
|
+
# Decode a NestedText stored in a given file.
|
26
|
+
|
27
|
+
# [filename] The file path to read NestedText to decode from.
|
28
|
+
# [top_class] Force the top level returned object to be of this type. Supported values are +Object+, +Array+, +Hash+ and +String+. Default is +Object+.
|
29
|
+
# [strict] If strict mode should be used. +true+ or +false+. Default is +false+
|
30
|
+
#
|
31
|
+
# Returns the parsed object.
|
32
|
+
#
|
33
|
+
# Raises NestedText::Error if anything went wrong.
|
34
|
+
#
|
35
|
+
# Raises +IOError+ on issue opening +filename+ for reading in text mode.
|
36
|
+
def self.load_file(filename, top_class: Object, strict: false)
|
17
37
|
raise Errors::WrongInputTypeError.new([String], filename) unless !filename.nil? && filename.is_a?(String)
|
18
38
|
|
19
39
|
# Open explicitly in text mode to detect \r as line ending.
|
data/lib/nestedtext/dumper.rb
CHANGED
@@ -3,17 +3,18 @@ require "nestedtext/core_ext_internal"
|
|
3
3
|
module NestedText
|
4
4
|
using NestedText.const_get(:CoreExtInternal)
|
5
5
|
|
6
|
+
# Dumping with recursive DFS traversal of the object references.
|
6
7
|
class Dumper
|
7
|
-
def initialize(
|
8
|
-
@indentation =
|
9
|
-
@strict =
|
10
|
-
@
|
11
|
-
@
|
8
|
+
def initialize(indentation, strict)
|
9
|
+
@indentation = indentation
|
10
|
+
@strict = strict
|
11
|
+
@traced_cycles = nil
|
12
|
+
@traced_keys = nil
|
12
13
|
end
|
13
14
|
|
14
15
|
def dump(obj)
|
15
|
-
@
|
16
|
-
@
|
16
|
+
@traced_cycles = []
|
17
|
+
@traced_keys = []
|
17
18
|
dump_any obj
|
18
19
|
end
|
19
20
|
|
@@ -47,7 +48,7 @@ module NestedText
|
|
47
48
|
elsif !@strict
|
48
49
|
key.to_s
|
49
50
|
else
|
50
|
-
raise Errors::
|
51
|
+
raise Errors::DumpHashKeyStrictStringError, key
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -57,26 +58,24 @@ module NestedText
|
|
57
58
|
target.replace indented
|
58
59
|
end
|
59
60
|
|
60
|
-
# TODO: different name on method and instance var...
|
61
61
|
def trace_cycles(obj)
|
62
|
-
raise Errors::
|
62
|
+
raise Errors::DumpCyclicReferencesDetectedError, traced_key if @traced_cycles.include?(obj)
|
63
63
|
|
64
|
-
@
|
64
|
+
@traced_cycles << obj
|
65
65
|
yield
|
66
66
|
ensure
|
67
|
-
@
|
67
|
+
@traced_cycles.pop
|
68
68
|
end
|
69
69
|
|
70
|
-
# TODO: different name on method and instance var...
|
71
70
|
def trace_keys(key)
|
72
|
-
@
|
71
|
+
@traced_keys << key
|
73
72
|
yield
|
74
73
|
ensure
|
75
|
-
@
|
74
|
+
@traced_keys.pop
|
76
75
|
end
|
77
76
|
|
78
77
|
def traced_key
|
79
|
-
@
|
78
|
+
@traced_keys.last
|
80
79
|
end
|
81
80
|
|
82
81
|
def dump_any(obj, depth: 0, **kwargs)
|
@@ -93,7 +92,6 @@ module NestedText
|
|
93
92
|
end
|
94
93
|
end
|
95
94
|
|
96
|
-
# TODO: document that @strict: false allows to_s on key object
|
97
95
|
def dump_hash(obj, depth: 0, **kwargs)
|
98
96
|
rep = if obj.empty?
|
99
97
|
"{}"
|
data/lib/nestedtext/encode.rb
CHANGED
@@ -1,32 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "nestedtext/errors"
|
4
|
-
require "nestedtext/encode_helpers"
|
5
3
|
require "nestedtext/dumper"
|
6
|
-
|
7
|
-
# Model after JSON
|
8
|
-
# NestedText.dump(obj, io=nil) => dumps to string, or to IO if given
|
9
|
-
# NestedText.dump_file(obj, filename)
|
4
|
+
require "nestedtext/errors_internal"
|
10
5
|
|
11
6
|
module NestedText
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
# Encode a Ruby object to a NestedText string.
|
8
|
+
#
|
9
|
+
# [obj] The object to encode to NestedText.
|
10
|
+
# [io] Additionally write the output to this IO object. The caller is responsible for that the IO is closed after the call to this method.
|
11
|
+
# [indentation] The indentation of nested levels to use.
|
12
|
+
# [strict] If strict mode should be used. +true+ or +false+. Default is +false+
|
13
|
+
#
|
14
|
+
# Returns a String containing NestedText data.
|
15
|
+
#
|
16
|
+
# Raises NestedText::Error if anything went wrong.
|
17
|
+
#
|
18
|
+
# Raises whatever the passed +io+ can raise.
|
19
|
+
def self.dump(obj, io: nil, indentation: 4, strict: false)
|
20
|
+
raise Errors::DumpBadIOError, io unless io.nil? || io.respond_to?(:write) && io.respond_to?(:fsync)
|
17
21
|
|
18
|
-
|
19
|
-
dumper = Dumper.new(opts)
|
22
|
+
dumper = Dumper.new(indentation, strict)
|
20
23
|
result = dumper.dump obj
|
21
24
|
unless io.nil?
|
22
25
|
io.write(result)
|
23
26
|
io.fsync
|
24
27
|
end
|
25
|
-
|
28
|
+
dumper.dump obj
|
26
29
|
end
|
27
30
|
|
31
|
+
# Encode a Ruby object to a NestedText file.
|
32
|
+
#
|
33
|
+
# [filename] The file path to write the NestedText result to. The conventional file extension is +.nt+.
|
34
|
+
#
|
35
|
+
# Raises +IOError+ on issues opening the +filename+ for writing in text mode.
|
36
|
+
#
|
37
|
+
# Apart from +filename+, this method behaves exactly like dump (taking same arguments, returning and raising the same values).
|
28
38
|
def self.dump_file(obj, filename, **kwargs)
|
29
|
-
raise Errors::
|
39
|
+
raise Errors::DumpFileBadPathError, filename unless filename.is_a? String
|
30
40
|
|
31
41
|
File.open(filename, mode = "wt") do |file|
|
32
42
|
dump(obj, io: file, **kwargs)
|
@@ -1,26 +1,14 @@
|
|
1
1
|
require "nestedtext/dumper"
|
2
2
|
|
3
3
|
module NestedText
|
4
|
-
|
5
|
-
|
6
|
-
Dumper.new(EncodeOptions.new(indentation, strict)).dump self
|
7
|
-
end
|
8
|
-
end
|
9
|
-
private_constant :NTEncodeStrictMixin
|
10
|
-
|
4
|
+
# A mixin for Custom Classes to get the to_nt shortcut.
|
5
|
+
# TODO rename to: ToNTMixin
|
11
6
|
module NTEncodeMixin
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
class EncodeOptions
|
18
|
-
attr_reader :indentation, :strict
|
19
|
-
|
20
|
-
def initialize(indentation = 4, strict = true)
|
21
|
-
@indentation = indentation
|
22
|
-
@strict = strict
|
7
|
+
# Encode this object to a NestedText string.
|
8
|
+
#
|
9
|
+
# This method takes the same arguments as NestedText::dump.
|
10
|
+
def to_nt(**kwargs)
|
11
|
+
NestedText.dump(self, strict: false, **kwargs)
|
23
12
|
end
|
24
13
|
end
|
25
|
-
private_constant :EncodeOptions
|
26
14
|
end
|