nestedtext 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42f5f6f0ac6182999d4036976103b740552796029ffe77749a8cdf5d0d8d0167
4
- data.tar.gz: c06dd5613b6c5296398a838e2419dd2addaf34b9712a1baec36ecd23e5d4c6f9
3
+ metadata.gz: 1b4a864ffef33a9d6138c701deb4e6ba95d27a0bcc40e8a2bdcb724b58db64e7
4
+ data.tar.gz: 78fd068eb2c14b44b1371c4a09e6208acedfefc169dbc72ee79df000797e91a0
5
5
  SHA512:
6
- metadata.gz: 0dc821033f7813441756fcfb04e9b6a442993130462ff2572c7401b634149b538be4a831a7deb5fdb91a432be5f795d37b473fc6ac4dad5eb1e8b9c88762a5f9
7
- data.tar.gz: ba7d69e4bfb3929c5dd5e0f037e88b7b13fac466aa011bccacf8095812f990c1e32b8c449df721514414cc8ce0b1c6f11723644cce13a5ee4330f549c793ee56
6
+ metadata.gz: dd46ca341047384eee141d5abc6d131214b45de60a51d3ab9b43292565f38e88a1c8a387543f9f37f27738fe99c553896458338f059f4da6a6420ca8884474a2
7
+ data.tar.gz: 9c73ab57b33f6d86feb0da4ca8a32402b8fd61e41d9eb0395adb3710a6176ede0ff018481af0655975c160382c23719ef6d4f70766906838453eda9bc46b72b7
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.0.0] - 2022-01-26
10
+ ### Changed
11
+ - **Breaking change**: strict mode now defaults to false for both the `load` and `dump` methods.
12
+ - Internal rename of error classes to be more consistent.
13
+ - Internal simplification of argument passing.
14
+
9
15
  ## [1.2.0] - 2022-01-25
10
16
  ### Changed
11
17
  - Hide core extension `String.normalize_line_endings` from users.
data/README.md CHANGED
@@ -1,3 +1,10 @@
1
+ <!--
2
+ <p align="center">
3
+ <a href="#">
4
+ <img src="https://raw.githubusercontent.com/erikw/nestedtext-ruby/main/img/logo.webp" alt="Project logo" />
5
+ </a>
6
+ </p>
7
+ -->
1
8
  # 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
9
  [![Gem Version](https://badge.fury.io/rb/nestedtext.svg)](https://badge.fury.io/rb/nestedtext)
3
10
  [![Gem Downloads](https://ruby-gem-downloads-badge.herokuapp.com/nestedtext?color=brightgreen&type=total&label=gem%20downloads)](https://rubygems.org/gems/nestedtext)
@@ -9,35 +16,119 @@
9
16
  [![Code Climate Maintainability](https://api.codeclimate.com/v1/badges/8409b6cdc3dc62a33f6f/maintainability)](https://codeclimate.com/github/erikw/nestedtext-ruby/maintainability)
10
17
  [![Code Climate Test Coverage](https://api.codeclimate.com/v1/badges/8409b6cdc3dc62a33f6f/test_coverage)](https://codeclimate.com/github/erikw/nestedtext-ruby/test_coverage)
11
18
  [![SLOC](https://img.shields.io/tokei/lines/github/erikw/nestedtext-ruby)](#)
12
- [![License](https://img.shields.io/github/license/erikw/nestedtext-ruby)](LICENSE.txt)
19
+ [![License](https://img.shields.io/github/license/erikw/nestedtext-ruby?color=informational)](LICENSE.txt)
13
20
  [![OSS Lifecycle](https://img.shields.io/osslifecycle/erikw/nestedtext-ruby)](https://github.com/Netflix/osstracker)
14
21
 
15
22
 
16
- Inspired by the `JSON` and `YAML` modules.
23
+ A ruby library for the human friendly data format [NestedText](https://nestedtext.org/).
17
24
 
18
- This project will soon be released! :tada:
25
+ <a href="#" ><img src="https://raw.githubusercontent.com/erikw/nestedtext-ruby/main/img/logo.webp" align="right" width="420px" alt="Project logo" /></a>
19
26
 
20
- On-going development is at branch [**dev**](https://github.com/erikw/nestedtext-ruby/tree/dev).
27
+ 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).
28
+
29
+ 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
30
 
22
31
  # What is NestedText?
23
- TODO
32
+ Citing from the official [introduction](https://nestedtext.org/en/latest/index.html) page:
33
+ > 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.
34
+ >
35
+ > 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
36
 
25
- https://nestedtext.org/en/latest/alternatives.html
37
+ *"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
38
 
27
- ## Examples
28
- TODO NT examples
39
+ ## Example
40
+ Here's a full-fledged example of an address book (from the official docs):
41
+ ```nestedtext
42
+ # Contact information for our officers
29
43
 
30
- # Usage
31
- TODO Link to lib docs
32
- TODO link to my test repo showin live usage. https://github.com/erikw/nestedtext-ruby-test
44
+ president:
45
+ name: Katheryn McDaniel
46
+ address:
47
+ > 138 Almond Street
48
+ > Topeka, Kansas 20697
49
+ phone:
50
+ cell: 1-210-555-5297
51
+ home: 1-210-555-8470
52
+ # Katheryn prefers that we always call her on her cell phone.
53
+ email: KateMcD@aol.com
54
+ additional roles:
55
+ - board member
56
+
57
+ vice president:
58
+ name: Margaret Hodge
59
+ ...
60
+ ```
33
61
 
62
+ See the [language introduction](https://nestedtext.org/en/latest/basic_syntax.html) for more details.
34
63
 
35
64
  # Usage
65
+ 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).
66
+
36
67
  ## Decoding (reading NT)
68
+ This is how you can decode NestedText from a string or directly from a file (`*.nt`) to Ruby object instances:
69
+
70
+ ### Any Top Level Type
71
+ ```ruby
72
+ require 'nestedtext'
73
+
74
+ ntstr = "- objitem1\n- list item 2"
75
+ obj1 = NestedText::load(ntstr)
76
+
77
+ obj2 = NestedText::load_file("path/to/data.nt")
78
+ ```
79
+
80
+ 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.
81
+
82
+ ```
83
+ key1: value1
84
+ key2: value2
85
+ ```
86
+
87
+ The NestedText types maps like this to Ruby:
88
+
89
+ [NestedText](https://nestedtext.org/en/latest/basic_syntax.html) | Ruby | Comment
90
+ ---|---|---
91
+ `String` | `String` |
92
+ `List` | `Array` |
93
+ `Dictionary`| `Hash` |
94
+ `String` | `Symbol` | when `strict: false`, otherwise Ruby Symbols are encoded as Custom Class (see below).
95
+ *empty* | `nil` | How empty strings and nil are handled depends on where it is used. This library follows how the official implementation does it.
96
+
97
+
98
+ Thus you must know what you're parsing, or test what you decoded.
99
+
100
+ ### Explicit Top Level Type
101
+ 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.
102
+
103
+ ```ruby
104
+ require 'nestedtext'
105
+
106
+ ntstr = "- objitem1\n- list item 2"
107
+ array = NestedText::load(ntstr, top_class: Array)
108
+
109
+ hash = NestedText::load_file("path/to/data.nt", top_class: Hash)
110
+
111
+ # will raise NestedText::Error as we specify top level String but it will be Array.
112
+ NestedText::load(ntstr, top_class: String)
113
+ ```
37
114
 
38
115
  ## Encoding (writing NT)
116
+ This is how you can decode Ruby objects to a NestedText string or file:
117
+
118
+ ```ruby
119
+ require 'nestedtext'
120
+
121
+ data = ["i1", "i2"]
122
+
123
+ ntstr = NestedText::dump(data)
124
+
125
+ NestedText::dump_file(data, "path/to/data.nt")
126
+ ```
127
+
128
+
129
+ ### `#to_nt` Convenience
130
+ 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
131
 
40
- `#to_nt` method:
41
132
  ```irb
42
133
  irb> require 'nestedtext'
43
134
  irb> puts "a\nstring".to_nt
@@ -47,30 +138,97 @@ irb> puts ["i1", "i2", "i3"].to_nt
47
138
  - i1
48
139
  - i2
49
140
  - i3
50
- irb> puts({"k1" => "v1", "multiline\nkey" => "v2", "k3" => "multiline\nvalue"}.to_nt)
141
+ irb> puts({"k1" => "v1", "multiline\nkey" => "v2", "k3" => ["a", "list"]}.to_nt)
51
142
  k1: v1
52
143
  : multiline
53
144
  : key
54
145
  > v2
55
146
  k3:
56
- > multiline
57
- > value
147
+ - a
148
+ - list
58
149
  ```
150
+
59
151
  ## Custom Classes Serialization
60
- This library has support for serialization/deserialization of custom classes as well.
61
- `strict: false` flag needed
152
+ 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.
153
+
154
+ ```ruby
155
+ class Apple
156
+ def initialize(type, weight)
157
+ @type = type
158
+ @weight = weight
159
+ end
160
+
161
+ def encode_nt_with
162
+ [@type, @weight]
163
+ end
164
+ end
165
+ ```
166
+
167
+ 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.
168
+
169
+
170
+ 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`.
171
+
172
+ In full, the `Apple` class should look like:
173
+
174
+ ```ruby
175
+ class Apple
176
+ def self.nt_create(data)
177
+ new(*data)
178
+ end
179
+
180
+ def initialize(type, weight)
181
+ @type = type
182
+ @weight = weight
183
+ end
184
+
185
+ def encode_nt_with
186
+ [@type, @weight]
187
+ end
188
+ end
189
+ ```
190
+
191
+ An instance of this class would be encoded like this:
192
+
193
+ ```ruby
194
+ irb> puts NestedText::dump(Apple.new("granny smith", 12))
195
+ __nestedtext_class__: Apple
196
+ data:
197
+ - granny smith
198
+ - 12
199
+ ```
200
+ Note that the special key to denote the class name is subject to change in future versions and you **must not** rely on it.
201
+
202
+ If you want to add some more super powers to your custom class, you can add the `#to_nt` shortcut by including the `NTEncodeMixin`:
203
+ ```ruby
204
+ class Apple
205
+ include NestedText::NTEncodeMixin
206
+ ...
207
+ end
208
+
209
+ Apple.new("granny smith", 12).to_nt
210
+ ```
211
+
212
+
213
+ **NOTE** that when deserializing a custom class, this custom class must be available when calling the `#dump*` methods e.g.
214
+ ```ruby
215
+ require 'nestedtext'
216
+ require_relative 'apple' # This is needed if Apple is defined in apple.rb and not in this scope already.
217
+
218
+ NestedText::load_file('path/to/apple_dump.nt')
219
+ ```
220
+
62
221
  See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.rb) for more real working examples.
63
222
 
64
223
 
65
224
  # Installation
66
225
  1. Add this gem to your ruby project's Gemfile
67
- - Simply with `$ bundle add nestedtext` when standing in the project root
226
+ - Simply with `$ bundle add nestedtext` when standing inside your project
68
227
  - Or manually by adding to `Gemfile`
69
228
  ```ruby
70
229
  gem 'nestedtext'
71
230
  ```
72
231
  and then running `$ bundle install`.
73
- ```
74
232
  1. Require the library and start using it!
75
233
  ```ruby
76
234
  require 'nestedtext'
@@ -89,20 +247,20 @@ See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.r
89
247
  $ git clone https://github.com/erikw/nestedtext-ruby.git && cd $(basename "$_" .git)
90
248
  ```
91
249
  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
250
+ 1. run `$ script/setup` or `$ bundle install` to install dependencies
251
+ 1. run `$ script/test` or `bundle exec rake test` to run the tests
94
252
  1. You can also run `$ script/console` for an interactive prompt that will allow you to experiment.
95
253
  1. For local testing, install the gem on local machine with: `$ bundle exec rake install`.
96
- * or manuall with `$ gem build *.gemscpec && gem install *.gem`
254
+ * or manually with `$ gem build *.gemscpec && gem install *.gem`
97
255
 
98
- Make sure that only intended constants and methods are exposed from the module `NestedText`. Check with
256
+ Make sure that only intended constants and methods are exposed publicly from the module `NestedText`. Check with
99
257
  ```
100
258
  irb> require 'nestedtext'
101
259
  irb> NestedText.constants
102
260
  irb> NestedText.methods(false)
103
261
  ```
104
262
 
105
- ## Releasing
263
+ # Releasing
106
264
  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
265
 
108
266
  ## (manually) Using bundler/gem_tasks rake tasks
@@ -131,7 +289,7 @@ $ git commit -am "Prepare vX.Y.Z" && git push
131
289
  $ git tag x.y.z && git push --tags
132
290
  ```
133
291
 
134
- or combined with gem-release
292
+ or combined with gem-release:
135
293
  ```console
136
294
  $ vi CHANGELOG.md
137
295
  $ git commit -am "Update CHANGELOG.md" && git push
@@ -140,10 +298,11 @@ $ gem bump --version minor --tag --sign --push
140
298
 
141
299
 
142
300
  # Contributing
143
- Bug reports and pull requests are welcome on GitHub at [https://github.com/erikw/nestedtext-ruby](https://github.com/erikw/nestedtext-ruby).
301
+ Bug reports and pull requests are welcome on GitHub at [erikw/nestedtext-ruby](https://github.com/erikw/nestedtext-ruby).
144
302
 
145
303
  # License
146
304
  The gem is available as open source with the [License](./LICENSE.txt).
147
305
 
148
- # Acknowledgement & Thanks
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).
306
+ # Acknowledgments
307
+ * Thanks to the data format authors making it easier making new implementations by providing an [official test suite](https://github.com/KenKundert/nestedtext_tests).
308
+ * 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
@@ -4,8 +4,8 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------- | ------------------ |
7
- | 1.x.x | :white_check_mark: |
8
- | < 1.1.1 | :x: |
7
+ | 2.x.x | :white_check_mark: |
8
+ | < 2.0.0 | :x: |
9
9
 
10
10
 
11
11
  ## Reporting a Vulnerability
@@ -7,13 +7,13 @@ require "logger"
7
7
  require "stringio"
8
8
 
9
9
  module NestedText
10
- def self.load(ntstring, top_class: Object, strict: true)
10
+ def self.load(ntstring, top_class: Object, strict: false)
11
11
  raise Errors::WrongInputTypeError.new([String], ntstring) unless ntstring.nil? || ntstring.is_a?(String)
12
12
 
13
13
  Parser.new(StringIO.new(ntstring), top_class, strict: strict).parse
14
14
  end
15
15
 
16
- def self.load_file(filename, top_class: Object, strict: true)
16
+ def self.load_file(filename, top_class: Object, strict: false)
17
17
  raise Errors::WrongInputTypeError.new([String], filename) unless !filename.nil? && filename.is_a?(String)
18
18
 
19
19
  # Open explicitly in text mode to detect \r as line ending.
@@ -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(opts = EncodeOptions.new)
8
- @indentation = opts.indentation
9
- @strict = opts.strict
10
- @trace_cycles = nil
11
- @trace_keys = nil
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
- @trace_cycles = []
16
- @trace_keys = []
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::DumpHashKeyStrictString, key
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::DumpCyclicReferencesDetected, traced_key if @trace_cycles.include?(obj)
62
+ raise Errors::DumpCyclicReferencesDetectedError, traced_key if @traced_cycles.include?(obj)
63
63
 
64
- @trace_cycles << obj
64
+ @traced_cycles << obj
65
65
  yield
66
66
  ensure
67
- @trace_cycles.pop
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
- @trace_keys << key
71
+ @traced_keys << key
73
72
  yield
74
73
  ensure
75
- @trace_keys.pop
74
+ @traced_keys.pop
76
75
  end
77
76
 
78
77
  def traced_key
79
- @trace_keys.last
78
+ @traced_keys.last
80
79
  end
81
80
 
82
81
  def dump_any(obj, depth: 0, **kwargs)
@@ -93,7 +92,7 @@ module NestedText
93
92
  end
94
93
  end
95
94
 
96
- # TODO: document that @strict: false allows to_s on key object
95
+ # TODO: document that @strict==false allows to_s on key object
97
96
  def dump_hash(obj, depth: 0, **kwargs)
98
97
  rep = if obj.empty?
99
98
  "{}"
@@ -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
- # TODO: strict should maybe be false by default, as this is what ntpy does. If so, make the same for the load functions.
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::DumpBadIO, io unless io.nil? || io.respond_to?(:write) && io.respond_to?(:fsync)
14
+ raise Errors::DumpBadIOError, io unless io.nil? || io.respond_to?(:write) && io.respond_to?(:fsync)
17
15
 
18
- opts = EncodeOptions.new(indentation, strict)
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
- result
22
+ dumper.dump obj
26
23
  end
27
24
 
28
25
  def self.dump_file(obj, filename, **kwargs)
29
- raise Errors::DumpFileBadPath, filename unless filename.is_a? String
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(indentation: 4, strict: true)
6
- Dumper.new(EncodeOptions.new(indentation, strict)).dump self
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(indentation: 4)
13
- Dumper.new(EncodeOptions.new(indentation, false)).dump self
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
@@ -16,7 +16,6 @@ module NestedText
16
16
  public_class_method :new # Prevent clients from instansiating.
17
17
  end
18
18
 
19
- # TODO: rename all Subclasses to ParseXError, just like for Dump
20
19
  class ParseError < InternalError
21
20
  attr_reader :lineno, :colno, :message_raw
22
21
 
@@ -54,68 +53,68 @@ module NestedText
54
53
  end
55
54
  end
56
55
 
57
- class LineTagUnknown < ParseError
56
+ class ParseLineTagUnknownError < ParseError
58
57
  def initialize(line, tag)
59
58
  super(line, line.indentation, "The Line tag #{tag} is not among the allowed ones #{Line::ALLOWED_LINE_TAGS}")
60
59
  end
61
60
  end
62
61
 
63
- class LineTagNotDetected < ParseError
62
+ class ParseLineTagNotDetectedError < ParseError
64
63
  def initialize(line)
65
64
  super(line, line.indentation, "unrecognized line.")
66
65
  end
67
66
  end
68
67
 
69
- class LineTypeExpectedListItem < ParseError
68
+ class ParseLineTypeExpectedListItemError < ParseError
70
69
  def initialize(line)
71
70
  super(line, line.indentation, "expected list item.")
72
71
  end
73
72
  end
74
73
 
75
- class MultilineKeyNoValue < ParseError
74
+ class ParseMultilineKeyNoValueError < ParseError
76
75
  def initialize(line)
77
76
  super(line, line.indentation, "multiline key requires a value.")
78
77
  end
79
78
  end
80
79
 
81
- class InlineDictSyntaxError < ParseError
80
+ class ParseInlineDictSyntaxError < ParseError
82
81
  def initialize(line, colno, wrong_char)
83
82
  super(line, line.indentation + colno, "expected ‘,’ or ‘}’, found ‘#{wrong_char}’.")
84
83
  end
85
84
  end
86
85
 
87
- class InlineDictKeySyntaxError < ParseError
86
+ class ParseInlineDictKeySyntaxError < ParseError
88
87
  def initialize(line, colno, wrong_char)
89
88
  super(line, line.indentation + colno, "expected ‘:’, found ‘#{wrong_char}’.")
90
89
  end
91
90
  end
92
91
 
93
- class InlineMissingValue < ParseError
92
+ class ParseInlineMissingValueError < ParseError
94
93
  def initialize(line, colno)
95
94
  super(line, line.indentation + colno, "expected value.")
96
95
  end
97
96
  end
98
97
 
99
- class InlineListSyntaxError < ParseError
98
+ class ParseInlineListSyntaxError < ParseError
100
99
  def initialize(line, colno, wrong_char)
101
100
  super(line, line.indentation + colno, "expected ‘,’ or ‘]’, found ‘#{wrong_char}’.")
102
101
  end
103
102
  end
104
103
 
105
- class InlineNoClosingDelimiter < ParseError
104
+ class ParseInlineNoClosingDelimiterError < ParseError
106
105
  def initialize(line, colno)
107
106
  super(line, line.indentation + colno, "line ended without closing delimiter.")
108
107
  end
109
108
  end
110
109
 
111
- class InlineExtraCharactersAfterDelimiter < ParseError
110
+ class ParseInlineExtraCharactersAfterDelimiterError < ParseError
112
111
  def initialize(line, colno, extra_chars)
113
112
  character_str = extra_chars.length > 1 ? "characters" : "character"
114
113
  super(line, line.indentation + colno, "extra #{character_str} after closing delimiter: ‘#{extra_chars}’.")
115
114
  end
116
115
  end
117
116
 
118
- class InvalidIndentation < ParseError
117
+ class ParseInvalidIndentationError < ParseError
119
118
  def initialize(line, ind_exp)
120
119
  prev_line = line.prev
121
120
  if prev_line.nil? && ind_exp == 0
@@ -137,19 +136,19 @@ module NestedText
137
136
  end
138
137
  end
139
138
 
140
- class LineTypeNotExpected < ParseError
139
+ class ParseLineTypeNotExpectedError < ParseError
141
140
  def initialize(line, type_exps, type_act)
142
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.")
143
142
  end
144
143
  end
145
144
 
146
- class LineTypeExpectedDictItem < ParseError
145
+ class ParseLineTypeExpectedDictItemError < ParseError
147
146
  def initialize(line)
148
147
  super(line, line.indentation, "expected dictionary item.")
149
148
  end
150
149
  end
151
150
 
152
- class InvalidIndentationChar < ParseError
151
+ class ParseInvalidIndentationCharError < ParseError
153
152
  def initialize(line)
154
153
  printable_char = line.content[0].dump.gsub(/"/, "")
155
154
 
@@ -165,19 +164,19 @@ module NestedText
165
164
  end
166
165
  end
167
166
 
168
- class DictDuplicateKey < ParseError
167
+ class ParseDictDuplicateKeyError < ParseError
169
168
  def initialize(line)
170
169
  super(line, line.indentation, "duplicate key: #{line.attribs["key"]}.")
171
170
  end
172
171
  end
173
172
 
174
- class ParseCustomClassNotFound < ParseError
173
+ class ParseCustomClassNotFoundError < ParseError
175
174
  def initialize(line, class_name)
176
175
  super(line, line.indentation, "Detected an encode custom class #{class_name} however we can't find it, so it can't be deserialzied.")
177
176
  end
178
177
  end
179
178
 
180
- class ParseCustomClassNoCreateMethod < ParseError
179
+ class ParseCustomClassNoCreateMethodError < ParseError
181
180
  def initialize(line, class_name)
182
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.")
183
182
  end
@@ -185,13 +184,13 @@ module NestedText
185
184
 
186
185
  class AssertionError < InternalError; end
187
186
 
188
- class LineScannerIsEmpty < AssertionError
187
+ class AssertionLineScannerIsEmptyError < AssertionError
189
188
  def initialize
190
189
  super("There is no more input to consume. You should have checked this with #empty? before calling.")
191
190
  end
192
191
  end
193
192
 
194
- class InlineScannerIsEmpty < AssertionError
193
+ class AssertionInlineScannerIsEmptyError < AssertionError
195
194
  def initialize
196
195
  super("There is no more input to consume. You should have checked this with #empty? before calling.")
197
196
  end
@@ -216,13 +215,13 @@ module NestedText
216
215
  end
217
216
  end
218
217
 
219
- class DumpCyclicReferencesDetected < DumpError
218
+ class DumpCyclicReferencesDetectedError < DumpError
220
219
  def initialize(culprit)
221
220
  super(culprit, "cyclic reference found: cannot be dumped.")
222
221
  end
223
222
  end
224
223
 
225
- class DumpHashKeyStrictString < DumpError
224
+ class DumpHashKeyStrictStringError < DumpError
226
225
  def initialize(obj)
227
226
  super(obj, "keys must be strings.")
228
227
  end
@@ -230,9 +229,9 @@ module NestedText
230
229
 
231
230
  def self.raise_unrecognized_line(line)
232
231
  # [[:space:]] include all Unicode spaces e.g. non-breakable space which \s does not.
233
- raise InvalidIndentationChar, line if line.content.chr =~ /[[:space:]]/
232
+ raise ParseInvalidIndentationCharError, line if line.content.chr =~ /[[:space:]]/
234
233
 
235
- raise LineTagNotDetected, line
234
+ raise ParseLineTagNotDetectedError, line
236
235
  end
237
236
 
238
237
  class UnsupportedTopLevelTypeError < InternalError
@@ -247,19 +246,19 @@ module NestedText
247
246
  end
248
247
  end
249
248
 
250
- class TopLevelTypeMismatchParsedType < InternalError
249
+ class TopLevelTypeMismatchParsedTypeError < InternalError
251
250
  def initialize(class_exp, class_act)
252
251
  super("The requested top level class #{class_exp.name} is not the same as the actual parsed top level class #{class_act}.")
253
252
  end
254
253
  end
255
254
 
256
- class DumpBadIO < InternalError
255
+ class DumpBadIOError < InternalError
257
256
  def initialize(io)
258
257
  super("When giving the io argument, it must be of type IO (respond to #write, #fsync). Given: #{io.class.name}")
259
258
  end
260
259
  end
261
260
 
262
- class DumpFileBadPath < InternalError
261
+ class DumpFileBadPathError < InternalError
263
262
  def initialize(path)
264
263
  super("Must supply a string to a file path that can be written to. Given: #{path}")
265
264
  end
@@ -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: true)
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::TopLevelTypeMismatchParsedType.new(@top_class, result) unless result.instance_of?(Hash)
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::TopLevelTypeMismatchParsedType.new(@top_class, result) unless result.instance_of?(Array)
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::TopLevelTypeMismatchParsedType.new(@top_class, result) unless result.instance_of?(String)
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::LineTypeExpectedListItem, line unless line.tag == :list_item
84
- raise Errors::InvalidIndentation.new(line, indentation) if line.indentation != indentation
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::InvalidIndentation.new(line, indentation) if line.indentation != indentation
108
- raise Errors::LineTypeExpectedDictItem, line unless %i[dict_item key_item].include? line.tag
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::LineTypeNotExpected.new(line, exp_types, line.tag)
133
+ raise Errors::ParseLineTypeNotExpectedError.new(line, exp_types, line.tag)
133
134
  end
134
- raise Errors::MultilineKeyNoValue, line unless @line_scanner.peek.indentation > indentation
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::DictDuplicateKey, line if result.key? key
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::ParseCustomClassNotFound.new(first_line, class_name)
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::ParseCustomClassNoCreateMethod.new(first_line, class_name)
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::InvalidIndentation.new(line, indentation) if line.indentation != indentation
167
- raise Errors::LineTypeNotExpected.new(line, %i[string_item], line.tag) unless line.tag == :string_item
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::InlineNoClosingDelimiter.new(@inline_scanner.line,
182
- @inline_scanner.pos)
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::InlineMissingValue.new(@inline_scanner.line, @inline_scanner.pos - 1)
188
+ raise Errors::ParseInlineMissingValueError.new(@inline_scanner.line, @inline_scanner.pos - 1)
188
189
  end
189
190
  unless last_char == ":"
190
- raise Errors::InlineDictKeySyntaxError.new(@inline_scanner.line, @inline_scanner.pos - 1, last_char)
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::InlineNoClosingDelimiter.new(@inline_scanner.line,
218
- @inline_scanner.pos)
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::InlineDictSyntaxError.new(@inline_scanner.line, @inline_scanner.pos - 1,
223
- last_char)
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::InlineNoClosingDelimiter.new(@inline_scanner.line,
239
- @inline_scanner.pos)
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::InlineMissingValue.new(@inline_scanner.line, @inline_scanner.pos - 1)
246
+ raise Errors::ParseInlineMissingValueError.new(@inline_scanner.line, @inline_scanner.pos - 1)
246
247
  else
247
- raise Errors::InlineListSyntaxError.new(@inline_scanner.line, @inline_scanner.pos - 1,
248
- last_char)
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::InlineExtraCharactersAfterDelimiter.new(@inline_scanner.line, @inline_scanner.pos,
268
- @inline_scanner.remaining)
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::InlineExtraCharactersAfterDelimiter.new(@inline_scanner.line, @inline_scanner.pos,
283
- @inline_scanner.remaining)
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,
@@ -15,7 +15,7 @@ module NestedText
15
15
  end
16
16
 
17
17
  def read_next
18
- raise Errors::LineScannerIsEmpty if empty?
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::InlineScannerIsEmpty if empty?
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::LineTagUnknown.new(self, tag) unless ALLOWED_LINE_TAGS.include?(@tag)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NestedText
4
- VERSION = "1.2.0"
4
+ VERSION = "2.0.0"
5
5
  end
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 implementation for the human friendly data format NestedText (https://nestedtext.org/)"
12
- spec.description = "A ruby library implementation 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."
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"]
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
28
28
  `git ls-files -z`.split("\x0").reject do |f|
29
- f.match(%r{\A(?:test/|script/|\.github/|\.gitmodules|Rakefile|TODO\.txt|\.codeclimate\.yml|\.vimlocal|\.simplecov)})
29
+ f.match(%r{\A(?:img/|test/|script/|\.github/|\.gitmodules|Rakefile|TODO\.md|\.codeclimate\.yml|\.vimlocal|\.simplecov)})
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: 1.2.0
4
+ version: 2.0.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-25 00:00:00.000000000 Z
11
+ date: 2022-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: warning
@@ -38,11 +38,11 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.0'
41
- description: A ruby library implementation for the human friendly data format NestedText
42
- (https://nestedtext.org/). There is support for decoding a NestedText file or string
43
- to Ruby data structures, as well as encoding Ruby objects to a NestedText file or
44
- string. Furthermore there is support for serialization and deserialization of custom
45
- classes. Support for v3.2.1 of the data format will all official tests passing.
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: []
@@ -99,6 +99,5 @@ requirements: []
99
99
  rubygems_version: 3.3.3
100
100
  signing_key:
101
101
  specification_version: 4
102
- summary: A ruby library implementation for the human friendly data format NestedText
103
- (https://nestedtext.org/)
102
+ summary: A ruby library for the human friendly data format NestedText (https://nestedtext.org/)
104
103
  test_files: []