nestedtext 1.1.1 → 2.1.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 +21 -3
- data/CONTRIBUTING.md +1 -1
- data/README.md +181 -28
- data/SECURITY.md +2 -2
- data/lib/nestedtext/core_ext_internal.rb +13 -0
- data/lib/nestedtext/decode.rb +2 -6
- data/lib/nestedtext/dumper.rb +19 -24
- data/lib/nestedtext/encode.rb +5 -8
- data/lib/nestedtext/encode_helpers.rb +4 -14
- data/lib/nestedtext/errors.rb +28 -30
- data/lib/nestedtext/parser.rb +33 -32
- data/lib/nestedtext/scanners.rb +3 -4
- data/lib/nestedtext/version.rb +1 -1
- data/nestedtext.gemspec +4 -4
- metadata +9 -15
- 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: 830cd05483344cec4a5d4132a7b6a373c81e6e48494dbf22dd29ff5ff5985599
|
4
|
+
data.tar.gz: 8ff4109630f482c63ddc9aac99de1a34f3dc3977c12af5a94eae266ad8a8a7d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e10ec7ae7e7d48c5a708be79803e8048322e474301d3e499c4bdee33199e7e99c4b1d3c8de2fd9ad6055d12332745c93c108d178ff829bdf1ddee1fad243d2a
|
7
|
+
data.tar.gz: 247bd8a03e0c4433f10811523b1e7da9b2ac747f98c1d735a1a7efdeef23b6b8b38be360a88c82acdedba66c90f177102a04ecd85777c77cac9f9a092a3f86e2
|
data/CHANGELOG.md
CHANGED
@@ -6,19 +6,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.1.0] - date
|
10
|
+
### Changed
|
11
|
+
- Slim down Gem by using include instead of block list.
|
12
|
+
|
13
|
+
## [2.0.1] - 2022-01-26
|
14
|
+
### Fixed
|
15
|
+
- README issue with logo showing up on Rdoc (out-commented HTML).
|
16
|
+
|
17
|
+
## [2.0.0] - 2022-01-26
|
18
|
+
### Changed
|
19
|
+
- **Breaking change**: strict mode now defaults to false for both the `load` and `dump` methods.
|
20
|
+
- Internal rename of error classes to be more consistent.
|
21
|
+
- Internal simplification of argument passing.
|
22
|
+
|
23
|
+
## [1.2.0] - 2022-01-25
|
24
|
+
### Changed
|
25
|
+
- Hide core extension `String.normalize_line_endings` from users.
|
26
|
+
|
9
27
|
## [1.1.1] - 2022-01-25
|
10
28
|
### Fixed
|
11
29
|
- Renamed `NTEncodeMixing` to `NTEncodeMixin` .
|
12
30
|
|
13
31
|
## [1.1.0] - 2022-01-25
|
14
32
|
### Added
|
15
|
-
- Expose `NestedText::VERSION` for convenience to the
|
33
|
+
- Expose `NestedText::VERSION` for convenience to the users.
|
16
34
|
|
17
35
|
## [1.0.0] - 2022-01-25
|
18
|
-
The library is now useful for
|
36
|
+
The library is now useful for users!
|
19
37
|
|
20
38
|
### Changed
|
21
|
-
- Hide all internals in the module from
|
39
|
+
- Hide all internals in the module from users.
|
22
40
|
|
23
41
|
## [0.6.0] - 2022-01-24
|
24
42
|
### Fixed
|
data/CONTRIBUTING.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
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
6
|
[![Official Tests](https://img.shields.io/badge/%F0%9F%8F%81%20Official%20Tests-Passing-success)](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)
|
@@ -9,35 +10,119 @@
|
|
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
12
|
[![SLOC](https://img.shields.io/tokei/lines/github/erikw/nestedtext-ruby)](#)
|
12
|
-
[![License](https://img.shields.io/github/license/erikw/nestedtext-ruby)](LICENSE.txt)
|
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 documentation can be found at [TODO](TODO). 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
|
+
The NestedText types maps like this to Ruby:
|
82
|
+
|
83
|
+
[NestedText](https://nestedtext.org/en/latest/basic_syntax.html) | Ruby | Comment
|
84
|
+
---|---|---
|
85
|
+
`String` | `String` |
|
86
|
+
`List` | `Array` |
|
87
|
+
`Dictionary`| `Hash` |
|
88
|
+
`String` | `Symbol` | when `strict: true`, otherwise Ruby Symbols are encoded as Custom Class (see below).
|
89
|
+
*empty* | `nil` | when `strict: true`, otherwise as Custom Class. How empty strings and nil are handled depends on where it is used. This library follows how the official implementation does it.
|
90
|
+
|
91
|
+
|
92
|
+
Thus you must know what you're parsing, or test what you decoded.
|
93
|
+
|
94
|
+
### Explicit Top Level Type
|
95
|
+
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.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'nestedtext'
|
99
|
+
|
100
|
+
ntstr = "- objitem1\n- list item 2"
|
101
|
+
array = NestedText::load(ntstr, top_class: Array)
|
102
|
+
|
103
|
+
hash = NestedText::load_file("path/to/data.nt", top_class: Hash)
|
104
|
+
|
105
|
+
# will raise NestedText::Error as we specify top level String but it will be Array.
|
106
|
+
NestedText::load(ntstr, top_class: String)
|
107
|
+
```
|
37
108
|
|
38
109
|
## Encoding (writing NT)
|
110
|
+
This is how you can decode Ruby objects to a NestedText string or file:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
require 'nestedtext'
|
114
|
+
|
115
|
+
data = ["i1", "i2"]
|
116
|
+
|
117
|
+
ntstr = NestedText::dump(data)
|
118
|
+
|
119
|
+
NestedText::dump_file(data, "path/to/data.nt")
|
120
|
+
```
|
121
|
+
|
122
|
+
|
123
|
+
### `#to_nt` Convenience
|
124
|
+
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
125
|
|
40
|
-
`#to_nt` method:
|
41
126
|
```irb
|
42
127
|
irb> require 'nestedtext'
|
43
128
|
irb> puts "a\nstring".to_nt
|
@@ -47,30 +132,97 @@ irb> puts ["i1", "i2", "i3"].to_nt
|
|
47
132
|
- i1
|
48
133
|
- i2
|
49
134
|
- i3
|
50
|
-
irb> puts({"k1" => "v1", "multiline\nkey" => "v2", "k3" => "
|
135
|
+
irb> puts({"k1" => "v1", "multiline\nkey" => "v2", "k3" => ["a", "list"]}.to_nt)
|
51
136
|
k1: v1
|
52
137
|
: multiline
|
53
138
|
: key
|
54
139
|
> v2
|
55
140
|
k3:
|
56
|
-
|
57
|
-
|
141
|
+
- a
|
142
|
+
- list
|
58
143
|
```
|
144
|
+
|
59
145
|
## Custom Classes Serialization
|
60
|
-
This library has support for serialization/deserialization of custom classes as well.
|
61
|
-
|
146
|
+
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.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Apple
|
150
|
+
def initialize(type, weight)
|
151
|
+
@type = type
|
152
|
+
@weight = weight
|
153
|
+
end
|
154
|
+
|
155
|
+
def encode_nt_with
|
156
|
+
[@type, @weight]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
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.
|
162
|
+
|
163
|
+
|
164
|
+
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`.
|
165
|
+
|
166
|
+
In full, the `Apple` class should look like:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class Apple
|
170
|
+
def self.nt_create(data)
|
171
|
+
new(*data)
|
172
|
+
end
|
173
|
+
|
174
|
+
def initialize(type, weight)
|
175
|
+
@type = type
|
176
|
+
@weight = weight
|
177
|
+
end
|
178
|
+
|
179
|
+
def encode_nt_with
|
180
|
+
[@type, @weight]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
An instance of this class would be encoded like this:
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
irb> puts NestedText::dump(Apple.new("granny smith", 12))
|
189
|
+
__nestedtext_class__: Apple
|
190
|
+
data:
|
191
|
+
- granny smith
|
192
|
+
- 12
|
193
|
+
```
|
194
|
+
Note that the special key to denote the class name is subject to change in future versions and you **must not** rely on it.
|
195
|
+
|
196
|
+
If you want to add some more super powers to your custom class, you can add the `#to_nt` shortcut by including the `NTEncodeMixin`:
|
197
|
+
```ruby
|
198
|
+
class Apple
|
199
|
+
include NestedText::NTEncodeMixin
|
200
|
+
...
|
201
|
+
end
|
202
|
+
|
203
|
+
Apple.new("granny smith", 12).to_nt
|
204
|
+
```
|
205
|
+
|
206
|
+
|
207
|
+
**NOTE** that when deserializing a custom class, this custom class must be available when calling the `#dump*` methods e.g.
|
208
|
+
```ruby
|
209
|
+
require 'nestedtext'
|
210
|
+
require_relative 'apple' # This is needed if Apple is defined in apple.rb and not in this scope already.
|
211
|
+
|
212
|
+
NestedText::load_file('path/to/apple_dump.nt')
|
213
|
+
```
|
214
|
+
|
62
215
|
See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.rb) for more real working examples.
|
63
216
|
|
64
217
|
|
65
218
|
# Installation
|
66
219
|
1. Add this gem to your ruby project's Gemfile
|
67
|
-
- Simply with `$ bundle add nestedtext` when standing
|
220
|
+
- Simply with `$ bundle add nestedtext` when standing inside your project
|
68
221
|
- Or manually by adding to `Gemfile`
|
69
222
|
```ruby
|
70
223
|
gem 'nestedtext'
|
71
224
|
```
|
72
225
|
and then running `$ bundle install`.
|
73
|
-
```
|
74
226
|
1. Require the library and start using it!
|
75
227
|
```ruby
|
76
228
|
require 'nestedtext'
|
@@ -89,20 +241,20 @@ See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.r
|
|
89
241
|
$ git clone https://github.com/erikw/nestedtext-ruby.git && cd $(basename "$_" .git)
|
90
242
|
```
|
91
243
|
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
|
244
|
+
1. run `$ script/setup` or `$ bundle install` to install dependencies
|
245
|
+
1. run `$ script/test` or `bundle exec rake test` to run the tests
|
94
246
|
1. You can also run `$ script/console` for an interactive prompt that will allow you to experiment.
|
95
247
|
1. For local testing, install the gem on local machine with: `$ bundle exec rake install`.
|
96
|
-
* or
|
248
|
+
* or manually with `$ gem build *.gemscpec && gem install *.gem`
|
97
249
|
|
98
|
-
Make sure that only intended constants and methods are exposed from the module `NestedText`. Check with
|
250
|
+
Make sure that only intended constants and methods are exposed publicly from the module `NestedText`. Check with
|
99
251
|
```
|
100
252
|
irb> require 'nestedtext'
|
101
253
|
irb> NestedText.constants
|
102
254
|
irb> NestedText.methods(false)
|
103
255
|
```
|
104
256
|
|
105
|
-
|
257
|
+
# Releasing
|
106
258
|
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
259
|
|
108
260
|
## (manually) Using bundler/gem_tasks rake tasks
|
@@ -131,7 +283,7 @@ $ git commit -am "Prepare vX.Y.Z" && git push
|
|
131
283
|
$ git tag x.y.z && git push --tags
|
132
284
|
```
|
133
285
|
|
134
|
-
or combined with gem-release
|
286
|
+
or combined with gem-release:
|
135
287
|
```console
|
136
288
|
$ vi CHANGELOG.md
|
137
289
|
$ git commit -am "Update CHANGELOG.md" && git push
|
@@ -140,10 +292,11 @@ $ gem bump --version minor --tag --sign --push
|
|
140
292
|
|
141
293
|
|
142
294
|
# Contributing
|
143
|
-
Bug reports and pull requests are welcome on GitHub at [
|
295
|
+
Bug reports and pull requests are welcome on GitHub at [erikw/nestedtext-ruby](https://github.com/erikw/nestedtext-ruby).
|
144
296
|
|
145
297
|
# License
|
146
298
|
The gem is available as open source with the [License](./LICENSE.txt).
|
147
299
|
|
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).
|
300
|
+
# Acknowledgments
|
301
|
+
* Thanks to the data format authors making it easier making new implementations by providing an [official test suite](https://github.com/KenKundert/nestedtext_tests).
|
302
|
+
* 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
@@ -0,0 +1,13 @@
|
|
1
|
+
module NestedText
|
2
|
+
# Hiding extensions for Kernel here away from users.
|
3
|
+
# Reference: https://ruby-doc.org/core-3.1.0/doc/syntax/refinements_rdoc.html
|
4
|
+
module CoreExtInternal
|
5
|
+
refine String do
|
6
|
+
def normalize_line_endings
|
7
|
+
# windows/mac -> unix
|
8
|
+
gsub(/\r\n?/, "\n")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
private_constant :CoreExtInternal
|
13
|
+
end
|
data/lib/nestedtext/decode.rb
CHANGED
@@ -7,17 +7,13 @@ require "logger"
|
|
7
7
|
require "stringio"
|
8
8
|
|
9
9
|
module NestedText
|
10
|
-
def self.load(ntstring, top_class: Object, strict:
|
11
|
-
# logger = Logger.new(STDOUT) # TODO: make this available to other classes in module. How avoid singleton?
|
12
|
-
# logger.info "input=#{raw_input_string}"
|
13
|
-
# logger.info "top=#{top}"
|
14
|
-
|
10
|
+
def self.load(ntstring, top_class: Object, strict: false)
|
15
11
|
raise Errors::WrongInputTypeError.new([String], ntstring) unless ntstring.nil? || ntstring.is_a?(String)
|
16
12
|
|
17
13
|
Parser.new(StringIO.new(ntstring), top_class, strict: strict).parse
|
18
14
|
end
|
19
15
|
|
20
|
-
def self.load_file(filename, top_class: Object, strict:
|
16
|
+
def self.load_file(filename, top_class: Object, strict: false)
|
21
17
|
raise Errors::WrongInputTypeError.new([String], filename) unless !filename.nil? && filename.is_a?(String)
|
22
18
|
|
23
19
|
# Open explicitly in text mode to detect \r as line ending.
|
data/lib/nestedtext/dumper.rb
CHANGED
@@ -1,23 +1,20 @@
|
|
1
|
-
|
2
|
-
class String
|
3
|
-
def normalize_line_endings
|
4
|
-
# windows/mac -> unix
|
5
|
-
gsub(/\r\n?/, "\n")
|
6
|
-
end
|
7
|
-
end
|
1
|
+
require "nestedtext/core_ext_internal"
|
8
2
|
|
9
3
|
module NestedText
|
4
|
+
using NestedText.const_get(:CoreExtInternal)
|
5
|
+
|
6
|
+
# Dumping with recursive DFS traversal of the object references.
|
10
7
|
class Dumper
|
11
|
-
def initialize(
|
12
|
-
@indentation =
|
13
|
-
@strict =
|
14
|
-
@
|
15
|
-
@
|
8
|
+
def initialize(indentation, strict)
|
9
|
+
@indentation = indentation
|
10
|
+
@strict = strict
|
11
|
+
@traced_cycles = nil
|
12
|
+
@traced_keys = nil
|
16
13
|
end
|
17
14
|
|
18
15
|
def dump(obj)
|
19
|
-
@
|
20
|
-
@
|
16
|
+
@traced_cycles = []
|
17
|
+
@traced_keys = []
|
21
18
|
dump_any obj
|
22
19
|
end
|
23
20
|
|
@@ -51,7 +48,7 @@ module NestedText
|
|
51
48
|
elsif !@strict
|
52
49
|
key.to_s
|
53
50
|
else
|
54
|
-
raise Errors::
|
51
|
+
raise Errors::DumpHashKeyStrictStringError, key
|
55
52
|
end
|
56
53
|
end
|
57
54
|
|
@@ -61,26 +58,24 @@ module NestedText
|
|
61
58
|
target.replace indented
|
62
59
|
end
|
63
60
|
|
64
|
-
# TODO: different name on method and instance var...
|
65
61
|
def trace_cycles(obj)
|
66
|
-
raise Errors::
|
62
|
+
raise Errors::DumpCyclicReferencesDetectedError, traced_key if @traced_cycles.include?(obj)
|
67
63
|
|
68
|
-
@
|
64
|
+
@traced_cycles << obj
|
69
65
|
yield
|
70
66
|
ensure
|
71
|
-
@
|
67
|
+
@traced_cycles.pop
|
72
68
|
end
|
73
69
|
|
74
|
-
# TODO: different name on method and instance var...
|
75
70
|
def trace_keys(key)
|
76
|
-
@
|
71
|
+
@traced_keys << key
|
77
72
|
yield
|
78
73
|
ensure
|
79
|
-
@
|
74
|
+
@traced_keys.pop
|
80
75
|
end
|
81
76
|
|
82
77
|
def traced_key
|
83
|
-
@
|
78
|
+
@traced_keys.last
|
84
79
|
end
|
85
80
|
|
86
81
|
def dump_any(obj, depth: 0, **kwargs)
|
@@ -97,7 +92,7 @@ module NestedText
|
|
97
92
|
end
|
98
93
|
end
|
99
94
|
|
100
|
-
# TODO: document that @strict
|
95
|
+
# TODO: document that @strict==false allows to_s on key object
|
101
96
|
def dump_hash(obj, depth: 0, **kwargs)
|
102
97
|
rep = if obj.empty?
|
103
98
|
"{}"
|
data/lib/nestedtext/encode.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "nestedtext/errors"
|
4
|
-
require "nestedtext/encode_helpers"
|
5
4
|
require "nestedtext/dumper"
|
6
5
|
|
7
6
|
# Model after JSON
|
@@ -9,24 +8,22 @@ require "nestedtext/dumper"
|
|
9
8
|
# NestedText.dump_file(obj, filename)
|
10
9
|
|
11
10
|
module NestedText
|
12
|
-
|
13
|
-
def self.dump(obj, io: nil, indentation: 4, strict: true)
|
11
|
+
def self.dump(obj, io: nil, indentation: 4, strict: false)
|
14
12
|
# io - additionaly write the out result to IO and still return result.
|
15
13
|
|
16
|
-
raise Errors::
|
14
|
+
raise Errors::DumpBadIOError, io unless io.nil? || io.respond_to?(:write) && io.respond_to?(:fsync)
|
17
15
|
|
18
|
-
|
19
|
-
dumper = Dumper.new(opts)
|
16
|
+
dumper = Dumper.new(indentation, strict)
|
20
17
|
result = dumper.dump obj
|
21
18
|
unless io.nil?
|
22
19
|
io.write(result)
|
23
20
|
io.fsync
|
24
21
|
end
|
25
|
-
|
22
|
+
dumper.dump obj
|
26
23
|
end
|
27
24
|
|
28
25
|
def self.dump_file(obj, filename, **kwargs)
|
29
|
-
raise Errors::
|
26
|
+
raise Errors::DumpFileBadPathError, filename unless filename.is_a? String
|
30
27
|
|
31
28
|
File.open(filename, mode = "wt") do |file|
|
32
29
|
dump(obj, io: file, **kwargs)
|
@@ -2,25 +2,15 @@ require "nestedtext/dumper"
|
|
2
2
|
|
3
3
|
module NestedText
|
4
4
|
module NTEncodeStrictMixin
|
5
|
-
def to_nt(
|
6
|
-
|
5
|
+
def to_nt(**kwargs)
|
6
|
+
NestedText.dump(self, strict: true, **kwargs)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
private_constant :NTEncodeStrictMixin
|
10
10
|
|
11
11
|
module NTEncodeMixin
|
12
|
-
def to_nt(
|
13
|
-
|
12
|
+
def to_nt(**kwargs)
|
13
|
+
NestedText.dump(self, **kwargs)
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
class EncodeOptions
|
18
|
-
attr_reader :indentation, :strict
|
19
|
-
|
20
|
-
def initialize(indentation = 4, strict = true)
|
21
|
-
@indentation = indentation
|
22
|
-
@strict = strict
|
23
|
-
end
|
24
|
-
end
|
25
|
-
private_constant :EncodeOptions
|
26
16
|
end
|
data/lib/nestedtext/errors.rb
CHANGED
@@ -6,18 +6,16 @@ require "word_wrap/core_ext"
|
|
6
6
|
require "nestedtext/constants"
|
7
7
|
|
8
8
|
module NestedText
|
9
|
-
# Top level error for
|
10
|
-
# TODO hide #new so that client's cant create instance of it. Unit test this. https://ruby-doc.org/core-3.1.0/Module.html#private_class_method-method
|
9
|
+
# Top level error for users to rescue on.
|
11
10
|
class Error < StandardError
|
12
11
|
private_class_method :new
|
13
12
|
end
|
14
13
|
|
15
14
|
module Errors
|
16
15
|
class InternalError < Error
|
17
|
-
public_class_method :new # Prevent
|
16
|
+
public_class_method :new # Prevent users from instansiating.
|
18
17
|
end
|
19
18
|
|
20
|
-
# TODO: rename all Subclasses to ParseXError, just like for Dump
|
21
19
|
class ParseError < InternalError
|
22
20
|
attr_reader :lineno, :colno, :message_raw
|
23
21
|
|
@@ -55,68 +53,68 @@ module NestedText
|
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
|
-
class
|
56
|
+
class ParseLineTagUnknownError < ParseError
|
59
57
|
def initialize(line, tag)
|
60
58
|
super(line, line.indentation, "The Line tag #{tag} is not among the allowed ones #{Line::ALLOWED_LINE_TAGS}")
|
61
59
|
end
|
62
60
|
end
|
63
61
|
|
64
|
-
class
|
62
|
+
class ParseLineTagNotDetectedError < ParseError
|
65
63
|
def initialize(line)
|
66
64
|
super(line, line.indentation, "unrecognized line.")
|
67
65
|
end
|
68
66
|
end
|
69
67
|
|
70
|
-
class
|
68
|
+
class ParseLineTypeExpectedListItemError < ParseError
|
71
69
|
def initialize(line)
|
72
70
|
super(line, line.indentation, "expected list item.")
|
73
71
|
end
|
74
72
|
end
|
75
73
|
|
76
|
-
class
|
74
|
+
class ParseMultilineKeyNoValueError < ParseError
|
77
75
|
def initialize(line)
|
78
76
|
super(line, line.indentation, "multiline key requires a value.")
|
79
77
|
end
|
80
78
|
end
|
81
79
|
|
82
|
-
class
|
80
|
+
class ParseInlineDictSyntaxError < ParseError
|
83
81
|
def initialize(line, colno, wrong_char)
|
84
82
|
super(line, line.indentation + colno, "expected ‘,’ or ‘}’, found ‘#{wrong_char}’.")
|
85
83
|
end
|
86
84
|
end
|
87
85
|
|
88
|
-
class
|
86
|
+
class ParseInlineDictKeySyntaxError < ParseError
|
89
87
|
def initialize(line, colno, wrong_char)
|
90
88
|
super(line, line.indentation + colno, "expected ‘:’, found ‘#{wrong_char}’.")
|
91
89
|
end
|
92
90
|
end
|
93
91
|
|
94
|
-
class
|
92
|
+
class ParseInlineMissingValueError < ParseError
|
95
93
|
def initialize(line, colno)
|
96
94
|
super(line, line.indentation + colno, "expected value.")
|
97
95
|
end
|
98
96
|
end
|
99
97
|
|
100
|
-
class
|
98
|
+
class ParseInlineListSyntaxError < ParseError
|
101
99
|
def initialize(line, colno, wrong_char)
|
102
100
|
super(line, line.indentation + colno, "expected ‘,’ or ‘]’, found ‘#{wrong_char}’.")
|
103
101
|
end
|
104
102
|
end
|
105
103
|
|
106
|
-
class
|
104
|
+
class ParseInlineNoClosingDelimiterError < ParseError
|
107
105
|
def initialize(line, colno)
|
108
106
|
super(line, line.indentation + colno, "line ended without closing delimiter.")
|
109
107
|
end
|
110
108
|
end
|
111
109
|
|
112
|
-
class
|
110
|
+
class ParseInlineExtraCharactersAfterDelimiterError < ParseError
|
113
111
|
def initialize(line, colno, extra_chars)
|
114
112
|
character_str = extra_chars.length > 1 ? "characters" : "character"
|
115
113
|
super(line, line.indentation + colno, "extra #{character_str} after closing delimiter: ‘#{extra_chars}’.")
|
116
114
|
end
|
117
115
|
end
|
118
116
|
|
119
|
-
class
|
117
|
+
class ParseInvalidIndentationError < ParseError
|
120
118
|
def initialize(line, ind_exp)
|
121
119
|
prev_line = line.prev
|
122
120
|
if prev_line.nil? && ind_exp == 0
|
@@ -138,19 +136,19 @@ module NestedText
|
|
138
136
|
end
|
139
137
|
end
|
140
138
|
|
141
|
-
class
|
139
|
+
class ParseLineTypeNotExpectedError < ParseError
|
142
140
|
def initialize(line, type_exps, type_act)
|
143
141
|
super(line, line.indentation, "The current line was detected to be #{type_act}, but we expected to see any of [#{type_exps.join(", ")}] here.")
|
144
142
|
end
|
145
143
|
end
|
146
144
|
|
147
|
-
class
|
145
|
+
class ParseLineTypeExpectedDictItemError < ParseError
|
148
146
|
def initialize(line)
|
149
147
|
super(line, line.indentation, "expected dictionary item.")
|
150
148
|
end
|
151
149
|
end
|
152
150
|
|
153
|
-
class
|
151
|
+
class ParseInvalidIndentationCharError < ParseError
|
154
152
|
def initialize(line)
|
155
153
|
printable_char = line.content[0].dump.gsub(/"/, "")
|
156
154
|
|
@@ -166,19 +164,19 @@ module NestedText
|
|
166
164
|
end
|
167
165
|
end
|
168
166
|
|
169
|
-
class
|
167
|
+
class ParseDictDuplicateKeyError < ParseError
|
170
168
|
def initialize(line)
|
171
169
|
super(line, line.indentation, "duplicate key: #{line.attribs["key"]}.")
|
172
170
|
end
|
173
171
|
end
|
174
172
|
|
175
|
-
class
|
173
|
+
class ParseCustomClassNotFoundError < ParseError
|
176
174
|
def initialize(line, class_name)
|
177
175
|
super(line, line.indentation, "Detected an encode custom class #{class_name} however we can't find it, so it can't be deserialzied.")
|
178
176
|
end
|
179
177
|
end
|
180
178
|
|
181
|
-
class
|
179
|
+
class ParseCustomClassNoCreateMethodError < ParseError
|
182
180
|
def initialize(line, class_name)
|
183
181
|
super(line, line.indentation, "Detected an encode custom class #{class_name} but it does not have a #nt_create method, so it can't be deserialzied.")
|
184
182
|
end
|
@@ -186,13 +184,13 @@ module NestedText
|
|
186
184
|
|
187
185
|
class AssertionError < InternalError; end
|
188
186
|
|
189
|
-
class
|
187
|
+
class AssertionLineScannerIsEmptyError < AssertionError
|
190
188
|
def initialize
|
191
189
|
super("There is no more input to consume. You should have checked this with #empty? before calling.")
|
192
190
|
end
|
193
191
|
end
|
194
192
|
|
195
|
-
class
|
193
|
+
class AssertionInlineScannerIsEmptyError < AssertionError
|
196
194
|
def initialize
|
197
195
|
super("There is no more input to consume. You should have checked this with #empty? before calling.")
|
198
196
|
end
|
@@ -217,13 +215,13 @@ module NestedText
|
|
217
215
|
end
|
218
216
|
end
|
219
217
|
|
220
|
-
class
|
218
|
+
class DumpCyclicReferencesDetectedError < DumpError
|
221
219
|
def initialize(culprit)
|
222
220
|
super(culprit, "cyclic reference found: cannot be dumped.")
|
223
221
|
end
|
224
222
|
end
|
225
223
|
|
226
|
-
class
|
224
|
+
class DumpHashKeyStrictStringError < DumpError
|
227
225
|
def initialize(obj)
|
228
226
|
super(obj, "keys must be strings.")
|
229
227
|
end
|
@@ -231,9 +229,9 @@ module NestedText
|
|
231
229
|
|
232
230
|
def self.raise_unrecognized_line(line)
|
233
231
|
# [[:space:]] include all Unicode spaces e.g. non-breakable space which \s does not.
|
234
|
-
raise
|
232
|
+
raise ParseInvalidIndentationCharError, line if line.content.chr =~ /[[:space:]]/
|
235
233
|
|
236
|
-
raise
|
234
|
+
raise ParseLineTagNotDetectedError, line
|
237
235
|
end
|
238
236
|
|
239
237
|
class UnsupportedTopLevelTypeError < InternalError
|
@@ -248,19 +246,19 @@ module NestedText
|
|
248
246
|
end
|
249
247
|
end
|
250
248
|
|
251
|
-
class
|
249
|
+
class TopLevelTypeMismatchParsedTypeError < InternalError
|
252
250
|
def initialize(class_exp, class_act)
|
253
251
|
super("The requested top level class #{class_exp.name} is not the same as the actual parsed top level class #{class_act}.")
|
254
252
|
end
|
255
253
|
end
|
256
254
|
|
257
|
-
class
|
255
|
+
class DumpBadIOError < InternalError
|
258
256
|
def initialize(io)
|
259
257
|
super("When giving the io argument, it must be of type IO (respond to #write, #fsync). Given: #{io.class.name}")
|
260
258
|
end
|
261
259
|
end
|
262
260
|
|
263
|
-
class
|
261
|
+
class DumpFileBadPathError < InternalError
|
264
262
|
def initialize(path)
|
265
263
|
super("Must supply a string to a file path that can be written to. Given: #{path}")
|
266
264
|
end
|
data/lib/nestedtext/parser.rb
CHANGED
@@ -7,6 +7,7 @@ require "nestedtext/scanners"
|
|
7
7
|
require "nestedtext/constants"
|
8
8
|
|
9
9
|
module NestedText
|
10
|
+
# A LL(1) recursive descent parser for NT.
|
10
11
|
class Parser
|
11
12
|
def self.assert_valid_top_level_type(top_class)
|
12
13
|
unless !top_class.nil? && top_class.is_a?(Class) && TOP_LEVEL_TYPES.map(&:object_id).include?(top_class.object_id)
|
@@ -15,7 +16,7 @@ module NestedText
|
|
15
16
|
end
|
16
17
|
|
17
18
|
# TODO: document that caller is responsible for closing IO after done with Parser.
|
18
|
-
def initialize(io, top_class, strict:
|
19
|
+
def initialize(io, top_class, strict: false)
|
19
20
|
assert_valid_input_type io
|
20
21
|
Parser.assert_valid_top_level_type(top_class)
|
21
22
|
@top_class = top_class
|
@@ -32,13 +33,13 @@ module NestedText
|
|
32
33
|
!result.nil? && ![Hash, Array, String].include?(result.class) && @strict
|
33
34
|
when Hash.object_id
|
34
35
|
result = {} if result.nil?
|
35
|
-
raise Errors::
|
36
|
+
raise Errors::TopLevelTypeMismatchParsedTypeError.new(@top_class, result) unless result.instance_of?(Hash)
|
36
37
|
when Array.object_id
|
37
38
|
result = [] if result.nil?
|
38
|
-
raise Errors::
|
39
|
+
raise Errors::TopLevelTypeMismatchParsedTypeError.new(@top_class, result) unless result.instance_of?(Array)
|
39
40
|
when String.object_id
|
40
41
|
result = "" if result.nil?
|
41
|
-
raise Errors::
|
42
|
+
raise Errors::TopLevelTypeMismatchParsedTypeError.new(@top_class, result) unless result.instance_of?(String)
|
42
43
|
else
|
43
44
|
raise Errors::UnsupportedTopLevelTypeError, @top_class
|
44
45
|
end
|
@@ -80,8 +81,8 @@ module NestedText
|
|
80
81
|
line = @line_scanner.read_next
|
81
82
|
|
82
83
|
Errors.raise_unrecognized_line(line) if line.tag == :unrecognized
|
83
|
-
raise Errors::
|
84
|
-
raise Errors::
|
84
|
+
raise Errors::ParseLineTypeExpectedListItemError, line unless line.tag == :list_item
|
85
|
+
raise Errors::ParseInvalidIndentationError.new(line, indentation) if line.indentation != indentation
|
85
86
|
|
86
87
|
value = line.attribs["value"]
|
87
88
|
if value.nil?
|
@@ -104,8 +105,8 @@ module NestedText
|
|
104
105
|
line = @line_scanner.read_next
|
105
106
|
first_line = line if first_line.nil?
|
106
107
|
Errors.raise_unrecognized_line(line) if line.tag == :unrecognized
|
107
|
-
raise Errors::
|
108
|
-
raise Errors::
|
108
|
+
raise Errors::ParseInvalidIndentationError.new(line, indentation) if line.indentation != indentation
|
109
|
+
raise Errors::ParseLineTypeExpectedDictItemError, line unless %i[dict_item key_item].include? line.tag
|
109
110
|
|
110
111
|
value = nil
|
111
112
|
key = nil
|
@@ -129,14 +130,14 @@ module NestedText
|
|
129
130
|
value = ""
|
130
131
|
else
|
131
132
|
unless exp_types.member?(@line_scanner.peek.tag)
|
132
|
-
raise Errors::
|
133
|
+
raise Errors::ParseLineTypeNotExpectedError.new(line, exp_types, line.tag)
|
133
134
|
end
|
134
|
-
raise Errors::
|
135
|
+
raise Errors::ParseMultilineKeyNoValueError, line unless @line_scanner.peek.indentation > indentation
|
135
136
|
|
136
137
|
value = parse_any(@line_scanner.peek.indentation)
|
137
138
|
end
|
138
139
|
end
|
139
|
-
raise Errors::
|
140
|
+
raise Errors::ParseDictDuplicateKeyError, line if result.key? key
|
140
141
|
|
141
142
|
result[key] = value
|
142
143
|
end
|
@@ -147,12 +148,12 @@ module NestedText
|
|
147
148
|
begin
|
148
149
|
clazz = class_name == "nil" ? NilClass : Object.const_get(class_name, false)
|
149
150
|
rescue NameError
|
150
|
-
raise Errors::
|
151
|
+
raise Errors::ParseCustomClassNotFoundError.new(first_line, class_name)
|
151
152
|
end
|
152
153
|
if clazz.respond_to? :nt_create
|
153
154
|
result = clazz.nt_create(result["data"])
|
154
155
|
else
|
155
|
-
raise Errors::
|
156
|
+
raise Errors::ParseCustomClassNoCreateMethodError.new(first_line, class_name)
|
156
157
|
end
|
157
158
|
end
|
158
159
|
|
@@ -163,8 +164,8 @@ module NestedText
|
|
163
164
|
result = []
|
164
165
|
while !@line_scanner.peek.nil? && @line_scanner.peek.indentation >= indentation
|
165
166
|
line = @line_scanner.read_next
|
166
|
-
raise Errors::
|
167
|
-
raise Errors::
|
167
|
+
raise Errors::ParseInvalidIndentationError.new(line, indentation) if line.indentation != indentation
|
168
|
+
raise Errors::ParseLineTypeNotExpectedError.new(line, %i[string_item], line.tag) unless line.tag == :string_item
|
168
169
|
|
169
170
|
value = line.attribs["value"]
|
170
171
|
result << value
|
@@ -178,16 +179,16 @@ module NestedText
|
|
178
179
|
key << @inline_scanner.read_next
|
179
180
|
end
|
180
181
|
if @inline_scanner.empty?
|
181
|
-
raise Errors::
|
182
|
-
|
182
|
+
raise Errors::ParseInlineNoClosingDelimiterError.new(@inline_scanner.line,
|
183
|
+
@inline_scanner.pos)
|
183
184
|
end
|
184
185
|
|
185
186
|
last_char = @inline_scanner.read_next
|
186
187
|
if last_char == "}" && key.empty?
|
187
|
-
raise Errors::
|
188
|
+
raise Errors::ParseInlineMissingValueError.new(@inline_scanner.line, @inline_scanner.pos - 1)
|
188
189
|
end
|
189
190
|
unless last_char == ":"
|
190
|
-
raise Errors::
|
191
|
+
raise Errors::ParseInlineDictKeySyntaxError.new(@inline_scanner.line, @inline_scanner.pos - 1, last_char)
|
191
192
|
end
|
192
193
|
|
193
194
|
key.join.strip
|
@@ -214,13 +215,13 @@ module NestedText
|
|
214
215
|
break unless @inline_scanner.peek == ","
|
215
216
|
end
|
216
217
|
if @inline_scanner.empty?
|
217
|
-
raise Errors::
|
218
|
-
|
218
|
+
raise Errors::ParseInlineNoClosingDelimiterError.new(@inline_scanner.line,
|
219
|
+
@inline_scanner.pos)
|
219
220
|
end
|
220
221
|
last_char = @inline_scanner.read_next
|
221
222
|
unless last_char == "}"
|
222
|
-
raise Errors::
|
223
|
-
|
223
|
+
raise Errors::ParseInlineDictSyntaxError.new(@inline_scanner.line, @inline_scanner.pos - 1,
|
224
|
+
last_char)
|
224
225
|
end
|
225
226
|
|
226
227
|
when "["
|
@@ -235,17 +236,17 @@ module NestedText
|
|
235
236
|
break unless @inline_scanner.peek == ","
|
236
237
|
end
|
237
238
|
if @inline_scanner.empty?
|
238
|
-
raise Errors::
|
239
|
-
|
239
|
+
raise Errors::ParseInlineNoClosingDelimiterError.new(@inline_scanner.line,
|
240
|
+
@inline_scanner.pos)
|
240
241
|
end
|
241
242
|
last_char = @inline_scanner.read_next
|
242
243
|
|
243
244
|
if last_char != "]"
|
244
245
|
if result[-1] == ""
|
245
|
-
raise Errors::
|
246
|
+
raise Errors::ParseInlineMissingValueError.new(@inline_scanner.line, @inline_scanner.pos - 1)
|
246
247
|
else
|
247
|
-
raise Errors::
|
248
|
-
|
248
|
+
raise Errors::ParseInlineListSyntaxError.new(@inline_scanner.line, @inline_scanner.pos - 1,
|
249
|
+
last_char)
|
249
250
|
end
|
250
251
|
end
|
251
252
|
else # Inline string
|
@@ -264,8 +265,8 @@ module NestedText
|
|
264
265
|
@inline_scanner = InlineScanner.new(@line_scanner.read_next)
|
265
266
|
result = parse_inline
|
266
267
|
unless @inline_scanner.empty?
|
267
|
-
raise Errors::
|
268
|
-
|
268
|
+
raise Errors::ParseInlineExtraCharactersAfterDelimiterError.new(@inline_scanner.line, @inline_scanner.pos,
|
269
|
+
@inline_scanner.remaining)
|
269
270
|
end
|
270
271
|
unless result.is_a? Hash
|
271
272
|
raise Errors::AssertionError,
|
@@ -279,8 +280,8 @@ module NestedText
|
|
279
280
|
@inline_scanner = InlineScanner.new(@line_scanner.read_next)
|
280
281
|
result = parse_inline
|
281
282
|
unless @inline_scanner.empty?
|
282
|
-
raise Errors::
|
283
|
-
|
283
|
+
raise Errors::ParseInlineExtraCharactersAfterDelimiterError.new(@inline_scanner.line, @inline_scanner.pos,
|
284
|
+
@inline_scanner.remaining)
|
284
285
|
end
|
285
286
|
unless result.is_a? Array
|
286
287
|
raise Errors::AssertionError,
|
data/lib/nestedtext/scanners.rb
CHANGED
@@ -15,7 +15,7 @@ module NestedText
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def read_next
|
18
|
-
raise Errors::
|
18
|
+
raise Errors::AssertionLineScannerIsEmptyError if empty?
|
19
19
|
|
20
20
|
line = @next_line
|
21
21
|
prepare_next_line
|
@@ -59,7 +59,7 @@ module NestedText
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def read_next
|
62
|
-
raise Errors::
|
62
|
+
raise Errors::AssertionInlineScannerIsEmptyError if empty?
|
63
63
|
|
64
64
|
@pos += 1
|
65
65
|
@line.content[@pos - 1]
|
@@ -108,7 +108,7 @@ module NestedText
|
|
108
108
|
|
109
109
|
def tag=(tag)
|
110
110
|
@tag = tag
|
111
|
-
raise Errors::
|
111
|
+
raise Errors::ParseLineTagUnknownError.new(self, tag) unless ALLOWED_LINE_TAGS.include?(@tag)
|
112
112
|
end
|
113
113
|
|
114
114
|
def to_s
|
@@ -147,7 +147,6 @@ module NestedText
|
|
147
147
|
elsif @content[0] == "{"
|
148
148
|
self.tag = :inline_dict
|
149
149
|
elsif @content[0] == "["
|
150
|
-
# TODO: merge path of inline dict and list and just set :inline?
|
151
150
|
self.tag = :inline_list
|
152
151
|
elsif @content =~ PATTERN_DICT_ITEM
|
153
152
|
self.tag = :dict_item
|
data/lib/nestedtext/version.rb
CHANGED
data/nestedtext.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["Erik Westrup"]
|
9
9
|
spec.email = ["erik.westrup@gmail.com"]
|
10
10
|
|
11
|
-
spec.summary = "A ruby library
|
12
|
-
spec.description = "A ruby library
|
11
|
+
spec.summary = "A ruby library for the human friendly data format NestedText (https://nestedtext.org/)"
|
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."
|
13
13
|
spec.homepage = "https://github.com/erikw/nestedtext-ruby/"
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = [">= 3.0", "< 4"]
|
@@ -25,8 +25,8 @@ Gem::Specification.new do |spec|
|
|
25
25
|
}
|
26
26
|
|
27
27
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
28
|
-
`git ls-files -z`.split("\x0").
|
29
|
-
f.match(%r{\A(?:
|
28
|
+
`git ls-files -z`.split("\x0").select do |f|
|
29
|
+
f.match(%r{\A(?:lib/|CHANGELOG.md|CONTRIBUTING.md|LICENSE.txt|README.md|SECURITY.md|nestedtext.gemspec)})
|
30
30
|
end
|
31
31
|
end
|
32
32
|
spec.require_paths = ["lib"]
|
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: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik Westrup
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: warning
|
@@ -38,31 +38,26 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.0'
|
41
|
-
description: A ruby library
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
description: A ruby library for the human friendly data format NestedText (https://nestedtext.org/).
|
42
|
+
There is support for decoding a NestedText file or string to Ruby data structures,
|
43
|
+
as well as encoding Ruby objects to a NestedText file or string. Furthermore there
|
44
|
+
is support for serialization and deserialization of custom classes. Support for
|
45
|
+
v3.2.1 of the data format will all official tests passing.
|
46
46
|
email:
|
47
47
|
- erik.westrup@gmail.com
|
48
48
|
executables: []
|
49
49
|
extensions: []
|
50
50
|
extra_rdoc_files: []
|
51
51
|
files:
|
52
|
-
- ".editorconfig"
|
53
|
-
- ".gitignore"
|
54
|
-
- ".rubocop.yml"
|
55
|
-
- ".ruby-version"
|
56
52
|
- CHANGELOG.md
|
57
53
|
- CONTRIBUTING.md
|
58
|
-
- Gemfile
|
59
54
|
- LICENSE.txt
|
60
|
-
- OSSMETADATA
|
61
55
|
- README.md
|
62
56
|
- SECURITY.md
|
63
57
|
- lib/nestedtext.rb
|
64
58
|
- lib/nestedtext/constants.rb
|
65
59
|
- lib/nestedtext/core_ext.rb
|
60
|
+
- lib/nestedtext/core_ext_internal.rb
|
66
61
|
- lib/nestedtext/decode.rb
|
67
62
|
- lib/nestedtext/dumper.rb
|
68
63
|
- lib/nestedtext/encode.rb
|
@@ -98,6 +93,5 @@ requirements: []
|
|
98
93
|
rubygems_version: 3.3.3
|
99
94
|
signing_key:
|
100
95
|
specification_version: 4
|
101
|
-
summary: A ruby library
|
102
|
-
(https://nestedtext.org/)
|
96
|
+
summary: A ruby library for the human friendly data format NestedText (https://nestedtext.org/)
|
103
97
|
test_files: []
|
data/.editorconfig
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
# Modified version of https://github.com/ruby/ruby/blob/master/.editorconfig
|
2
|
-
|
3
|
-
root = true
|
4
|
-
|
5
|
-
[*]
|
6
|
-
end_of_line = lf
|
7
|
-
indent_size = 4
|
8
|
-
indent_style = space
|
9
|
-
insert_final_newline = true
|
10
|
-
tab_width = 4
|
11
|
-
trim_trailing_whitespace = true
|
12
|
-
|
13
|
-
[*.rb]
|
14
|
-
indent_size = 2
|
15
|
-
|
16
|
-
[*.gemspec]
|
17
|
-
indent_size = 2
|
18
|
-
|
19
|
-
[*.yml]
|
20
|
-
indent_size = 2
|
21
|
-
|
22
|
-
[test/nestedtext/encode_test.rb]
|
23
|
-
# So we can test trailing whitespace strings.
|
24
|
-
trim_trailing_whitespace = unset
|
data/.gitignore
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# Contrary to ruby apps, ruby gems should not check in Gemfile.lock.
|
2
|
-
# Reference: https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
|
3
|
-
/Gemfile.lock
|
4
|
-
|
5
|
-
# Bundle local config
|
6
|
-
/.bundle/
|
7
|
-
|
8
|
-
# Package gem from $(rake install)
|
9
|
-
/pkg/
|
10
|
-
# When built from $(gem build *.gemspec)
|
11
|
-
/*.gem
|
12
|
-
|
13
|
-
# minitest
|
14
|
-
/test/html_reports/
|
15
|
-
/test/reports/
|
16
|
-
|
17
|
-
# simplecov
|
18
|
-
/coverage/
|
19
|
-
|
20
|
-
# byebug
|
21
|
-
/.byebug_history
|
data/.rubocop.yml
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
# See pages relaed at https://docs.rubocop.org/rubocop/1.12/cops_layout.html
|
2
|
-
|
3
|
-
|
4
|
-
# TODO clean this upand remove Excludes not needed.
|
5
|
-
|
6
|
-
# Rubocop extensions
|
7
|
-
require:
|
8
|
-
- rubocop-rake
|
9
|
-
|
10
|
-
inherit_mode:
|
11
|
-
merge:
|
12
|
-
- Exclude # Merge my AllCops.Excllude with default exludes from https://github.com/rubocop/rubocop/blob/master/config/default.yml
|
13
|
-
|
14
|
-
AllCops:
|
15
|
-
TargetRubyVersion: 3.0
|
16
|
-
Include:
|
17
|
-
- lib/**/*.rb
|
18
|
-
- spec/**/*.rb
|
19
|
-
Exclude:
|
20
|
-
# Travis: during build there will be a lot of rubocop config files in this pat which will cause build failure as the refer to gems which are not installed by this project.
|
21
|
-
# See https://github.com/rubocop/rubocop/issues/9832
|
22
|
-
- gemfiles/vendor/bundle/**/*
|
23
|
-
|
24
|
-
Gemspec/DateAssignment:
|
25
|
-
Enabled: true
|
26
|
-
|
27
|
-
Layout/LineEndStringConcatenationIndentation:
|
28
|
-
Enabled: true
|
29
|
-
|
30
|
-
Lint/AmbiguousAssignment:
|
31
|
-
Enabled: true
|
32
|
-
Lint/DeprecatedConstants:
|
33
|
-
Enabled: true
|
34
|
-
Lint/DuplicateBranch:
|
35
|
-
Enabled: true
|
36
|
-
Lint/DuplicateRegexpCharacterClassElement:
|
37
|
-
Enabled: true
|
38
|
-
Lint/EmptyBlock:
|
39
|
-
Enabled: true
|
40
|
-
Lint/EmptyClass:
|
41
|
-
Enabled: true
|
42
|
-
Layout/EmptyLineBetweenDefs:
|
43
|
-
Enabled: true
|
44
|
-
Exclude:
|
45
|
-
- lib/nestedtext-ruby/errors.rb
|
46
|
-
Lint/EmptyInPattern:
|
47
|
-
Enabled: true
|
48
|
-
Lint/LambdaWithoutLiteralBlock:
|
49
|
-
Enabled: true
|
50
|
-
Layout/LineLength:
|
51
|
-
Max: 120
|
52
|
-
Lint/NoReturnInBeginEndBlocks:
|
53
|
-
Enabled: true
|
54
|
-
Lint/NumberedParameterAssignment:
|
55
|
-
Enabled: true
|
56
|
-
Lint/OrAssignmentToConstant:
|
57
|
-
Enabled: true
|
58
|
-
Lint/RedundantDirGlobSort:
|
59
|
-
Enabled: true
|
60
|
-
Layout/SpaceBeforeBrackets:
|
61
|
-
Enabled: true
|
62
|
-
Lint/SymbolConversion:
|
63
|
-
Enabled: true
|
64
|
-
Lint/ToEnumArguments:
|
65
|
-
Enabled: true
|
66
|
-
Lint/TripleQuotes:
|
67
|
-
Enabled: true
|
68
|
-
Lint/UnexpectedBlockArity:
|
69
|
-
Enabled: true
|
70
|
-
Lint/UnmodifiedReduceAccumulator:
|
71
|
-
Enabled: true
|
72
|
-
Lint/UnreachableCode:
|
73
|
-
Severity: error
|
74
|
-
Lint/UselessAccessModifier:
|
75
|
-
Enabled: false
|
76
|
-
|
77
|
-
Metrics/AbcSize:
|
78
|
-
Enabled: true
|
79
|
-
Metrics/BlockLength:
|
80
|
-
Enabled: true
|
81
|
-
Max: 100
|
82
|
-
Metrics/MethodLength:
|
83
|
-
Enabled: true
|
84
|
-
Max: 25
|
85
|
-
|
86
|
-
Naming/FileName:
|
87
|
-
Enabled: false
|
88
|
-
Exclude:
|
89
|
-
- lib/nestedtext-ruby.rb
|
90
|
-
Naming/InclusiveLanguage:
|
91
|
-
Enabled: true
|
92
|
-
|
93
|
-
Style/ArgumentsForwarding:
|
94
|
-
Enabled: true
|
95
|
-
Style/CollectionCompact:
|
96
|
-
Enabled: true
|
97
|
-
Style/Documentation:
|
98
|
-
Enabled: true
|
99
|
-
Exclude:
|
100
|
-
- lib/nestedtext-ruby/errors.rb
|
101
|
-
Style/DocumentDynamicEvalDefinition:
|
102
|
-
Enabled: true
|
103
|
-
Style/EndlessMethod:
|
104
|
-
Enabled: true
|
105
|
-
Style/HashConversion:
|
106
|
-
Enabled: true
|
107
|
-
Style/HashExcept:
|
108
|
-
Enabled: true
|
109
|
-
Style/IfWithBooleanLiteralBranches:
|
110
|
-
Enabled: true
|
111
|
-
Style/InPatternThen:
|
112
|
-
Enabled: true
|
113
|
-
Style/MultilineInPatternThen:
|
114
|
-
Enabled: true
|
115
|
-
Style/NegatedIfElseCondition:
|
116
|
-
Enabled: true
|
117
|
-
Style/NilLambda:
|
118
|
-
Enabled: true
|
119
|
-
Style/QuotedSymbols:
|
120
|
-
Enabled: true
|
121
|
-
Style/RedundantArgument:
|
122
|
-
Enabled: true
|
123
|
-
Style/RegexpLiteral:
|
124
|
-
Enabled: false
|
125
|
-
Style/SingleLineMethods:
|
126
|
-
Enabled: true
|
127
|
-
Exclude:
|
128
|
-
- lib/nestedtext-ruby/errors.rb
|
129
|
-
Style/StringChars:
|
130
|
-
Enabled: true
|
131
|
-
Style/StringLiterals:
|
132
|
-
Enabled: true
|
133
|
-
EnforcedStyle: double_quotes
|
134
|
-
Style/StringLiteralsInInterpolation:
|
135
|
-
Enabled: true
|
136
|
-
EnforcedStyle: double_quotes
|
137
|
-
Style/SwapValues:
|
138
|
-
Enabled: true
|
139
|
-
|
140
|
-
|
141
|
-
# Reference: https://github.com/rubocop/rubocop-rake/blob/master/config/default.yml
|
142
|
-
Rake:
|
143
|
-
Enabled: true
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
3.1.0
|
data/Gemfile
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Put require=false on gem's that we don't need to import in code (cli exec only)
|
4
|
-
|
5
|
-
source "https://rubygems.org"
|
6
|
-
|
7
|
-
# Include dependencies from the .gemspec
|
8
|
-
gemspec
|
9
|
-
|
10
|
-
# Development dependencies
|
11
|
-
# Should rather be here than in the .gemspec
|
12
|
-
# Reference: https://github.com/rubygems/bundler/pull/7222
|
13
|
-
# However there's an argument for using gemspec too: https://bundler.io/guides/creating_gem.html#testing-our-gem
|
14
|
-
group :development, :test do
|
15
|
-
gem "gem-release", "~> 2.0", require: false
|
16
|
-
gem "pry-byebug", "~> 3.9"
|
17
|
-
gem "rake", "~> 13.0", require: false
|
18
|
-
gem "solargraph", require: false
|
19
|
-
end
|
20
|
-
|
21
|
-
group :test do
|
22
|
-
gem "minitest-byebug", "~> 0.0.3"
|
23
|
-
gem "minitest-reporters", "~> 1.4", require: false
|
24
|
-
gem "rubocop", "~> 1.18", require: false
|
25
|
-
gem "rubocop-rake", "~> 0.6", require: false
|
26
|
-
gem "simplecov", "~> 0.21", require: false
|
27
|
-
end
|
data/OSSMETADATA
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
osslifecycle=active
|