nestedtext 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +187 -28
- data/SECURITY.md +2 -2
- data/lib/nestedtext/decode.rb +2 -2
- data/lib/nestedtext/dumper.rb +16 -17
- data/lib/nestedtext/encode.rb +5 -8
- data/lib/nestedtext/encode_helpers.rb +4 -14
- data/lib/nestedtext/errors.rb +26 -27
- data/lib/nestedtext/parser.rb +33 -32
- data/lib/nestedtext/scanners.rb +3 -4
- data/lib/nestedtext/version.rb +1 -1
- data/nestedtext.gemspec +3 -3
- metadata +8 -9
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 1b4a864ffef33a9d6138c701deb4e6ba95d27a0bcc40e8a2bdcb724b58db64e7
         | 
| 4 | 
            +
              data.tar.gz: 78fd068eb2c14b44b1371c4a09e6208acedfefc169dbc72ee79df000797e91a0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 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 [](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 | 
             
            [](https://badge.fury.io/rb/nestedtext)
         | 
| 3 10 | 
             
            [](https://rubygems.org/gems/nestedtext)
         | 
| @@ -9,35 +16,119 @@ | |
| 9 16 | 
             
            [](https://codeclimate.com/github/erikw/nestedtext-ruby/maintainability)
         | 
| 10 17 | 
             
            [](https://codeclimate.com/github/erikw/nestedtext-ruby/test_coverage)
         | 
| 11 18 | 
             
            [](#)
         | 
| 12 | 
            -
            [](LICENSE.txt)
         | 
| 19 | 
            +
            [](LICENSE.txt)
         | 
| 13 20 | 
             
            [](https://github.com/Netflix/osstracker)
         | 
| 14 21 |  | 
| 15 22 |  | 
| 16 | 
            -
             | 
| 23 | 
            +
            A ruby library for the human friendly data format [NestedText](https://nestedtext.org/).
         | 
| 17 24 |  | 
| 18 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
            ##  | 
| 28 | 
            -
             | 
| 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 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 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" => " | 
| 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 | 
            -
                 | 
| 57 | 
            -
                 | 
| 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 | 
            -
             | 
| 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  | 
| 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  | 
| 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 | 
            -
             | 
| 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 [ | 
| 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 | 
            -
            #  | 
| 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
    
    
    
        data/lib/nestedtext/decode.rb
    CHANGED
    
    | @@ -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:  | 
| 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:  | 
| 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.
         | 
    
        data/lib/nestedtext/dumper.rb
    CHANGED
    
    | @@ -3,17 +3,18 @@ require "nestedtext/core_ext_internal" | |
| 3 3 | 
             
            module NestedText
         | 
| 4 4 | 
             
              using NestedText.const_get(:CoreExtInternal)
         | 
| 5 5 |  | 
| 6 | 
            +
              # Dumping with recursive DFS traversal of the object references.
         | 
| 6 7 | 
             
              class Dumper
         | 
| 7 | 
            -
                def initialize( | 
| 8 | 
            -
                  @indentation =  | 
| 9 | 
            -
                  @strict =  | 
| 10 | 
            -
                  @ | 
| 11 | 
            -
                  @ | 
| 8 | 
            +
                def initialize(indentation, strict)
         | 
| 9 | 
            +
                  @indentation = indentation
         | 
| 10 | 
            +
                  @strict = strict
         | 
| 11 | 
            +
                  @traced_cycles = nil
         | 
| 12 | 
            +
                  @traced_keys = nil
         | 
| 12 13 | 
             
                end
         | 
| 13 14 |  | 
| 14 15 | 
             
                def dump(obj)
         | 
| 15 | 
            -
                  @ | 
| 16 | 
            -
                  @ | 
| 16 | 
            +
                  @traced_cycles = []
         | 
| 17 | 
            +
                  @traced_keys = []
         | 
| 17 18 | 
             
                  dump_any obj
         | 
| 18 19 | 
             
                end
         | 
| 19 20 |  | 
| @@ -47,7 +48,7 @@ module NestedText | |
| 47 48 | 
             
                  elsif !@strict
         | 
| 48 49 | 
             
                    key.to_s
         | 
| 49 50 | 
             
                  else
         | 
| 50 | 
            -
                    raise Errors:: | 
| 51 | 
            +
                    raise Errors::DumpHashKeyStrictStringError, key
         | 
| 51 52 | 
             
                  end
         | 
| 52 53 | 
             
                end
         | 
| 53 54 |  | 
| @@ -57,26 +58,24 @@ module NestedText | |
| 57 58 | 
             
                  target.replace indented
         | 
| 58 59 | 
             
                end
         | 
| 59 60 |  | 
| 60 | 
            -
                # TODO: different name on method and instance var...
         | 
| 61 61 | 
             
                def trace_cycles(obj)
         | 
| 62 | 
            -
                  raise Errors:: | 
| 62 | 
            +
                  raise Errors::DumpCyclicReferencesDetectedError, traced_key if @traced_cycles.include?(obj)
         | 
| 63 63 |  | 
| 64 | 
            -
                  @ | 
| 64 | 
            +
                  @traced_cycles << obj
         | 
| 65 65 | 
             
                  yield
         | 
| 66 66 | 
             
                ensure
         | 
| 67 | 
            -
                  @ | 
| 67 | 
            +
                  @traced_cycles.pop
         | 
| 68 68 | 
             
                end
         | 
| 69 69 |  | 
| 70 | 
            -
                # TODO: different name on method and instance var...
         | 
| 71 70 | 
             
                def trace_keys(key)
         | 
| 72 | 
            -
                  @ | 
| 71 | 
            +
                  @traced_keys << key
         | 
| 73 72 | 
             
                  yield
         | 
| 74 73 | 
             
                ensure
         | 
| 75 | 
            -
                  @ | 
| 74 | 
            +
                  @traced_keys.pop
         | 
| 76 75 | 
             
                end
         | 
| 77 76 |  | 
| 78 77 | 
             
                def traced_key
         | 
| 79 | 
            -
                  @ | 
| 78 | 
            +
                  @traced_keys.last
         | 
| 80 79 | 
             
                end
         | 
| 81 80 |  | 
| 82 81 | 
             
                def dump_any(obj, depth: 0, **kwargs)
         | 
| @@ -93,7 +92,7 @@ module NestedText | |
| 93 92 | 
             
                  end
         | 
| 94 93 | 
             
                end
         | 
| 95 94 |  | 
| 96 | 
            -
                # TODO: document that @strict | 
| 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 | 
             
                          "{}"
         | 
    
        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
    
    | @@ -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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 232 | 
            +
                  raise ParseInvalidIndentationCharError, line if line.content.chr =~ /[[:space:]]/
         | 
| 234 233 |  | 
| 235 | 
            -
                  raise  | 
| 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  | 
| 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  | 
| 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  | 
| 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
         | 
    
        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"]
         | 
| @@ -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\. | 
| 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:  | 
| 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- | 
| 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  | 
| 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: []
         | 
| @@ -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  | 
| 103 | 
            -
              (https://nestedtext.org/)
         | 
| 102 | 
            +
            summary: A ruby library for the human friendly data format NestedText (https://nestedtext.org/)
         | 
| 104 103 | 
             
            test_files: []
         |