nestedtext 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9ab788532b6de8dfb6b589b789afe4d04a6bc7ef9403aaf69f02306680ff4955
4
+ data.tar.gz: 994fe9aae478fb40d191ea96452a0c247b3385dd4e8fe28a323d271feb3e0f11
5
+ SHA512:
6
+ metadata.gz: f6ce566b64bd8874c8b3fae58ab772177249103bf65176cc2ee02e8c74993fa47bd1ed269a9b69c0e6f7a8fecc667fee0e76ec509ef1f1e5608ce8b4d97859ae
7
+ data.tar.gz: 22dab61d1a2cad3eafa3f097c03bbf802747bbe363269161292a663d06d80e61152025de4d6facafba31006f07fb1a5d847775ca483b809b418536d60ce14ecc
data/.editorconfig ADDED
@@ -0,0 +1,24 @@
1
+ # Modified version of https://github.com/ruby/ruby/blob/master/.editorconfig
2
+
3
+ root = true
4
+
5
+ [*]
6
+ end_of_line = lf
7
+ indent_size = 4
8
+ indent_style = space
9
+ insert_final_newline = true
10
+ tab_width = 4
11
+ trim_trailing_whitespace = true
12
+
13
+ [*.rb]
14
+ indent_size = 2
15
+
16
+ [*.gemspec]
17
+ indent_size = 2
18
+
19
+ [*.yml]
20
+ indent_size = 2
21
+
22
+ [test/nestedtext/encode_test.rb]
23
+ # So we can test trailing whitespace strings.
24
+ trim_trailing_whitespace = unset
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ # Contrary to ruby apps, ruby gems should not check in Gemfile.lock.
2
+ # Reference: https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
3
+ Gemfile.lock
4
+
5
+ # Bundle local config
6
+ .bundle/
7
+
8
+ # Package gem from $(rake install)
9
+ pkg/
10
+
11
+ # minitest
12
+ /test/html_reports/
13
+ /test/reports/
14
+
15
+ # simplecov
16
+ /coverage/
17
+
18
+ # byebug
19
+ /.byebug_history
data/.rubocop.yml ADDED
@@ -0,0 +1,143 @@
1
+ # See pages relaed at https://docs.rubocop.org/rubocop/1.12/cops_layout.html
2
+
3
+
4
+ # TODO clean this upand remove Excludes not needed.
5
+
6
+ # Rubocop extensions
7
+ require:
8
+ - rubocop-rake
9
+
10
+ inherit_mode:
11
+ merge:
12
+ - Exclude # Merge my AllCops.Excllude with default exludes from https://github.com/rubocop/rubocop/blob/master/config/default.yml
13
+
14
+ AllCops:
15
+ TargetRubyVersion: 3.0
16
+ Include:
17
+ - lib/**/*.rb
18
+ - spec/**/*.rb
19
+ Exclude:
20
+ # Travis: during build there will be a lot of rubocop config files in this pat which will cause build failure as the refer to gems which are not installed by this project.
21
+ # See https://github.com/rubocop/rubocop/issues/9832
22
+ - gemfiles/vendor/bundle/**/*
23
+
24
+ Gemspec/DateAssignment:
25
+ Enabled: true
26
+
27
+ Layout/LineEndStringConcatenationIndentation:
28
+ Enabled: true
29
+
30
+ Lint/AmbiguousAssignment:
31
+ Enabled: true
32
+ Lint/DeprecatedConstants:
33
+ Enabled: true
34
+ Lint/DuplicateBranch:
35
+ Enabled: true
36
+ Lint/DuplicateRegexpCharacterClassElement:
37
+ Enabled: true
38
+ Lint/EmptyBlock:
39
+ Enabled: true
40
+ Lint/EmptyClass:
41
+ Enabled: true
42
+ Layout/EmptyLineBetweenDefs:
43
+ Enabled: true
44
+ Exclude:
45
+ - lib/nestedtext-ruby/errors.rb
46
+ Lint/EmptyInPattern:
47
+ Enabled: true
48
+ Lint/LambdaWithoutLiteralBlock:
49
+ Enabled: true
50
+ Layout/LineLength:
51
+ Max: 120
52
+ Lint/NoReturnInBeginEndBlocks:
53
+ Enabled: true
54
+ Lint/NumberedParameterAssignment:
55
+ Enabled: true
56
+ Lint/OrAssignmentToConstant:
57
+ Enabled: true
58
+ Lint/RedundantDirGlobSort:
59
+ Enabled: true
60
+ Layout/SpaceBeforeBrackets:
61
+ Enabled: true
62
+ Lint/SymbolConversion:
63
+ Enabled: true
64
+ Lint/ToEnumArguments:
65
+ Enabled: true
66
+ Lint/TripleQuotes:
67
+ Enabled: true
68
+ Lint/UnexpectedBlockArity:
69
+ Enabled: true
70
+ Lint/UnmodifiedReduceAccumulator:
71
+ Enabled: true
72
+ Lint/UnreachableCode:
73
+ Severity: error
74
+ Lint/UselessAccessModifier:
75
+ Enabled: false
76
+
77
+ Metrics/AbcSize:
78
+ Enabled: true
79
+ Metrics/BlockLength:
80
+ Enabled: true
81
+ Max: 100
82
+ Metrics/MethodLength:
83
+ Enabled: true
84
+ Max: 25
85
+
86
+ Naming/FileName:
87
+ Enabled: false
88
+ Exclude:
89
+ - lib/nestedtext-ruby.rb
90
+ Naming/InclusiveLanguage:
91
+ Enabled: true
92
+
93
+ Style/ArgumentsForwarding:
94
+ Enabled: true
95
+ Style/CollectionCompact:
96
+ Enabled: true
97
+ Style/Documentation:
98
+ Enabled: true
99
+ Exclude:
100
+ - lib/nestedtext-ruby/errors.rb
101
+ Style/DocumentDynamicEvalDefinition:
102
+ Enabled: true
103
+ Style/EndlessMethod:
104
+ Enabled: true
105
+ Style/HashConversion:
106
+ Enabled: true
107
+ Style/HashExcept:
108
+ Enabled: true
109
+ Style/IfWithBooleanLiteralBranches:
110
+ Enabled: true
111
+ Style/InPatternThen:
112
+ Enabled: true
113
+ Style/MultilineInPatternThen:
114
+ Enabled: true
115
+ Style/NegatedIfElseCondition:
116
+ Enabled: true
117
+ Style/NilLambda:
118
+ Enabled: true
119
+ Style/QuotedSymbols:
120
+ Enabled: true
121
+ Style/RedundantArgument:
122
+ Enabled: true
123
+ Style/RegexpLiteral:
124
+ Enabled: false
125
+ Style/SingleLineMethods:
126
+ Enabled: true
127
+ Exclude:
128
+ - lib/nestedtext-ruby/errors.rb
129
+ Style/StringChars:
130
+ Enabled: true
131
+ Style/StringLiterals:
132
+ Enabled: true
133
+ EnforcedStyle: double_quotes
134
+ Style/StringLiteralsInInterpolation:
135
+ Enabled: true
136
+ EnforcedStyle: double_quotes
137
+ Style/SwapValues:
138
+ Enabled: true
139
+
140
+
141
+ # Reference: https://github.com/rubocop/rubocop-rake/blob/master/config/default.yml
142
+ Rake:
143
+ Enabled: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+
10
+ ## [0.1.0] - 2022-01-24
11
+ ### Added
12
+ - Initial release. If this release works, an 1.0.0 will soon follow.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,4 @@
1
+ # How to contribute
2
+ Please use GitHub tooling (issues, PRs) to disucssion and code contributions!
3
+
4
+ When you open an PR, Travis will build your code, run tests, liters and so on.
data/Gemfile ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Put require=false on gem's that we don't need to import in code (cli exec only)
4
+
5
+ source "https://rubygems.org"
6
+
7
+ # Include dependencies from the .gemspec
8
+ gemspec
9
+
10
+ # Development dependencies
11
+ # Should rather be here than in the .gemspec
12
+ # Reference: https://github.com/rubygems/bundler/pull/7222
13
+ # However there's an argument for using gemspec too: https://bundler.io/guides/creating_gem.html#testing-our-gem
14
+ group :development, :test do
15
+ gem "appraisal", "~> 2.4", require: false
16
+ gem "gem-release", "~> 2.0", require: false
17
+ gem "pry-byebug", "~> 3.9"
18
+ gem "rake", "~> 13.0", require: false
19
+ gem "solargraph", require: false
20
+ end
21
+
22
+ group :test do
23
+ gem "minitest-byebug", "~> 0.0.3"
24
+ gem "minitest-reporters", "~> 1.4", require: false
25
+ gem "rubocop", "~> 1.18", require: false
26
+ gem "rubocop-rake", "~> 0.6", require: false
27
+ gem "simplecov", "~> 0.21", require: false
28
+ end
29
+
30
+ gem "warning", "~> 1.2"
31
+ gem "word_wrap", "~> 1.0"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Erik Westrup
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/OSSMETADATA ADDED
@@ -0,0 +1 @@
1
+ osslifecycle=active
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # NestedText Ruby Library [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Get%20a%20nifty%20tooltip%20for%20term%20definitions%20in%20your%20Jekyll%20blog%20with%20this%20plugin&url=https://github.com/erikw/nestedtext-ruby&via=erik_westrup&hashtags=jekyll,plugin)
2
+ [![Gem Version](https://badge.fury.io/rb/nestedtext-ruby.svg)](https://badge.fury.io/rb/nestedtext-ruby)
3
+ [![Gem Downloads](https://ruby-gem-downloads-badge.herokuapp.com/nestedtext-ruby?color=brightgreen&type=total&label=gem%20downloads)](https://rubygems.org/gems/nestedtext-ruby)
4
+ [![Data Format Version Supported](https://img.shields.io/badge/%F0%9F%84%BD%F0%9F%85%83%20Version%20Supported-3.2.1-blueviolet)](https://nestedtext.org/en/v3.2/)
5
+ [![Tests](https://github.com/erikw/nestedtext-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/erikw/nestedtext-ruby/actions/workflows/ci.yml)
6
+ [![Official Tests](https://img.shields.io/badge/%F0%9F%8F%81%20Official%20Tests-Passing-success)](https://github.com/KenKundert/nestedtext_tests/tree/585e95a73d94ac1f48e71a154e2db0ab67cf30fa)
7
+ [![CodeQL](https://github.com/erikw/nestedtext-ruby/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/erikw/nestedtext-ruby/actions/workflows/codeql-analysis.yml)
8
+ [![Code Climate Maintainability](https://api.codeclimate.com/v1/badges/7ffb648ec4b77f3f9eb8/maintainability)](https://codeclimate.com/github/erikw/nestedtext-ruby/maintainability)
9
+ [![Code Climate Test Coverage](https://api.codeclimate.com/v1/badges/7ffb648ec4b77f3f9eb8/test_coverage)](https://codeclimate.com/github/erikw/nestedtext-ruby/test_coverage)
10
+ [![SLOC](https://img.shields.io/tokei/lines/github/erikw/nestedtext-ruby)](#)
11
+ [![License](https://img.shields.io/github/license/erikw/nestedtext-ruby)](LICENSE.txt)
12
+ [![OSS Lifecycle](https://img.shields.io/osslifecycle/erikw/nestedtext-ruby)](https://github.com/Netflix/osstracker)
13
+
14
+
15
+ Inspired by the `JSON` and `YAML` modules.
16
+
17
+ This project will soon be released! :tada:
18
+
19
+ On-going development is at branch [**dev**](https://github.com/erikw/nestedtext-ruby/tree/dev).
20
+
21
+ # What is NestedText?
22
+ TODO
23
+
24
+ ## Examples
25
+ TODO NT examples
26
+
27
+ # Usage
28
+ TODO Link to lib docs
29
+ TODO link to my test repo showin live usage.
30
+
31
+
32
+ # Usage
33
+ ## Decoding (reading NT)
34
+
35
+ ## Encoding (writing NT)
36
+
37
+ ## Custom Classes Serialization
38
+ This library has support for serialization/deserialization of custom classes as well.
39
+ `strict: false` flag needed
40
+ See [encode_custom_classes_test.rb](test/nestedtext/encode_custom_classes_test.rb) for more real working examples.
41
+
42
+
43
+ # Installation
44
+ 1. Add this gem to your ruby project's Gemfile
45
+ - Simply with `$ bundle add nestedtext` when standing in the project root
46
+ - Or manually by adding to `Gemfile`
47
+ ```ruby
48
+ gem 'nestedtext'
49
+ ```
50
+ and then running `$ bundle install`.
51
+ ```
52
+ 1. Require the library and start using it!
53
+ ```ruby
54
+ require 'nestedtext'
55
+
56
+ NestedText::load(...)
57
+ NestedText::dump(...)
58
+ obj.to_nt
59
+ ```
60
+
61
+
62
+
63
+ # Development
64
+
65
+ 1. Clone the repo
66
+ ```console
67
+ $ git clone https://github.com/erikw/nestedtext-ruby.git && cd $(basename "$_" .git)
68
+ ```
69
+ 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)
70
+ 1. run `$ script/setup` to install dependencies
71
+ 1. run `$ script/test` to run the tests
72
+ 1. You can also run `$ script/console` for an interactive prompt that will allow you to experiment.
73
+
74
+ To install this gem onto your local machine, run `$ bundle exec rake install`.
75
+
76
+ ## Releasing
77
+ 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.
78
+
79
+ ## (manually) Using bundler/gem_tasks rake tasks
80
+ Following instructions from [bundler.io](https://bundler.io/guides/creating_gem.html#releasing-the-gem):
81
+ ```console
82
+ $ vi -p lib/nestedtext/version.rb CHANGELOG.md
83
+ $ bundle exec rake build
84
+ $ ver=$(ruby -r ./lib/nestedtext/version.rb -e 'puts NestedText::VERSION')
85
+ $ bundle exec rake release
86
+ ```
87
+
88
+ ## (semi-manually) Using gem-release gem extension
89
+ Using [gem-release](https://github.com/svenfuchs/gem-release):
90
+ ```console
91
+ $ vi CHANGELOG.md && git add CHANGELOG.md && git commit -m "Update CHANGELOG.md" && git push
92
+ $ gem bump --version minor --tag --sign --push --release
93
+ ```
94
+ For `--version`, use `major|minor|patch` as needed.
95
+
96
+ ## (semi-automatic) Using GitHub Actions CD
97
+ Just push a new semver tag and the workflow [cd.yml](.github/workflows/cd.yml) will publish a new release at rubygems.org.
98
+
99
+ ```console
100
+ $ vi -p lib/nestedtext/version.rb CHANGELOG.md
101
+ $ git commit -am "Prepare vX.Y.Z" && git push
102
+ $ git tag x.y.z && git push --tags
103
+ ```
104
+
105
+
106
+ # Contributing
107
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/erikw/nestedtext-ruby](https://github.com/erikw/nestedtext-ruby).
108
+
109
+ # License
110
+ The gem is available as open source with the [License](./LICENSE.txt).
111
+
112
+ # Acknowledgement & Thanks
113
+ Thanks to the data format authors making it easier making new implementations by providing an [official test suite](https://github.com/KenKundert/nestedtext_tests).
data/SECURITY.md ADDED
@@ -0,0 +1,12 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 1.x.x | :white_check_mark: |
8
+ | < 1.0 | :x: |
9
+
10
+
11
+ ## Reporting a Vulnerability
12
+ Please open a GitHub Issue in this repository for any potential issues!
@@ -0,0 +1,5 @@
1
+ require "stringio"
2
+ module NestedText
3
+ TOP_LEVEL_TYPES = [Object, Hash, Array, String]
4
+ CUSTOM_CLASS_KEY = "__nestedtext_class__"
5
+ end
@@ -0,0 +1,18 @@
1
+ require "nestedtext/encode_helpers"
2
+
3
+ # TODO: add encoding of more Ruby native classes like Integer, Float etc plus commons like Set,....? Not covered in NestedText language.
4
+ # Or leave this to a schema validator 3rd party plugin maybe? And replace my custom class decoding (and also encoding?)?
5
+ # Or both: add encoding/decoding of more native classes, and allow decoding + applying a schema with 3rd party.
6
+ # Or encourage using Marshal from core?
7
+
8
+ class String include NestedText::NTEncodeStrictMixing end
9
+ class Array include NestedText::NTEncodeStrictMixing end
10
+ class Hash include NestedText::NTEncodeStrictMixing end
11
+
12
+ class NilClass
13
+ include NestedText::NTEncodeStrictMixing
14
+
15
+ def self.nt_create(_data) = nil
16
+
17
+ def encode_nt_with() = ""
18
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nestedtext/parser"
4
+ require "nestedtext/errors"
5
+ require "nestedtext/helpers"
6
+
7
+ require "logger"
8
+ require "stringio"
9
+
10
+ module NestedText
11
+ def self.load(ntstring, top_class: Object, strict: true)
12
+ # logger = Logger.new(STDOUT) # TODO: make this available to other classes in module. How avoid singleton?
13
+ # logger.info "input=#{raw_input_string}"
14
+ # logger.info "top=#{top}"
15
+
16
+ raise Errors::WrongInputTypeError.new([String], ntstring) unless ntstring.nil? || ntstring.is_a?(String)
17
+
18
+ assert_valid_top_level_type top_class
19
+
20
+ Parser.new(StringIO.new(ntstring), top_class, strict: strict).parse
21
+ end
22
+
23
+ def self.load_file(filename, top_class: Object, strict: true)
24
+ raise Errors::WrongInputTypeError.new([String], filename) unless !filename.nil? && filename.is_a?(String)
25
+
26
+ assert_valid_top_level_type top_class
27
+
28
+ # Open explicitly in text mode to detect \r as line ending.
29
+ File.open(filename, mode = "rt") do |file|
30
+ Parser.new(file, top_class, strict: strict).parse
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,177 @@
1
+ # TODO: is this exposed to client who import this file? If so, hide it!
2
+ class String
3
+ def normalize_line_endings
4
+ # windows/mac -> unix
5
+ gsub(/\r\n?/, "\n")
6
+ end
7
+ end
8
+
9
+ module NestedText
10
+ class Dumper
11
+ def initialize(opts = EncodeOptions.new)
12
+ @indentation = opts.indentation
13
+ @strict = opts.strict
14
+ @trace_cycles = nil
15
+ @trace_keys = nil
16
+ end
17
+
18
+ def dump(obj)
19
+ @trace_cycles = []
20
+ @trace_keys = []
21
+ dump_any obj
22
+ end
23
+
24
+ private
25
+
26
+ def self.add_prefix(prefix, target)
27
+ if target.empty? || target[0] == "\n"
28
+ target.prepend(prefix)
29
+ else
30
+ target.prepend(prefix, " ")
31
+ end
32
+ end
33
+
34
+ def self.multiline_key?(key)
35
+ syntax1 = "{[#"
36
+ syntax2 = ":->"
37
+
38
+ key.empty? ||
39
+ key != key.strip ||
40
+ key.include?("\n") ||
41
+ key.include?(": ") ||
42
+ syntax1.include?(key.lstrip[0]) ||
43
+ syntax2.include?(key.lstrip[0]) && key.lstrip[1] == " "
44
+ end
45
+
46
+ def convert_key(key)
47
+ if key.nil?
48
+ ""
49
+ elsif key.is_a? String
50
+ key.normalize_line_endings
51
+ elsif !@strict
52
+ key.to_s
53
+ else
54
+ raise Errors::DumpHashKeyStrictString, key
55
+ end
56
+ end
57
+
58
+ def indent(target)
59
+ indentstr = " " * @indentation
60
+ indented = "\n" + target.lines.map { |line| indentstr + line }.join
61
+ target.replace indented
62
+ end
63
+
64
+ # TODO: different name on method and instance var...
65
+ def trace_cycles(obj)
66
+ raise Errors::DumpCyclicReferencesDetected, traced_key if @trace_cycles.include?(obj)
67
+
68
+ @trace_cycles << obj
69
+ yield
70
+ ensure
71
+ @trace_cycles.pop
72
+ end
73
+
74
+ # TODO: different name on method and instance var...
75
+ def trace_keys(key)
76
+ @trace_keys << key
77
+ yield
78
+ ensure
79
+ @trace_keys.pop
80
+ end
81
+
82
+ def traced_key
83
+ @trace_keys.last
84
+ end
85
+
86
+ def dump_any(obj, depth: 0, **kwargs)
87
+ trace_cycles(obj) do
88
+ case obj
89
+ when Hash then dump_hash(obj, depth: depth, **kwargs)
90
+ when Array then dump_array(obj, depth: depth, **kwargs)
91
+ when String then dump_string(obj, depth: depth, **kwargs)
92
+ when nil
93
+ @strict ? "" : dump_custom_class(nil, depth: depth, **kwargs)
94
+ else
95
+ dump_custom_class(obj, depth: depth, **kwargs)
96
+ end
97
+ end
98
+ end
99
+
100
+ # TODO: document that @strict: false allows to_s on key object
101
+ def dump_hash(obj, depth: 0, **kwargs)
102
+ rep = if obj.empty?
103
+ "{}"
104
+ else
105
+ obj.map do |key, value|
106
+ trace_keys(key) do
107
+ key = convert_key(key)
108
+
109
+ if Dumper.multiline_key?(key)
110
+ key_lines = key.empty? ? [""] : key.lines
111
+ key_lines << "" if key_lines[-1][-1] =~ /\n|\r/
112
+ rep_key = key_lines.map { |line| Dumper.add_prefix(":", line) }.join
113
+ force_multiline = value.is_a? String
114
+ rep_value = dump_any(value, depth: depth + 1, force_multiline: force_multiline, **kwargs)
115
+ else
116
+ rep_key = "#{key}:"
117
+ rep_value = dump_any(value, depth: depth + 1, **kwargs)
118
+ rep_key += " " unless rep_value.empty? || rep_value.include?("\n")
119
+ end
120
+ "#{rep_key}#{rep_value}"
121
+ end
122
+ end.join("\n")
123
+ end
124
+ indent(rep) if depth > 0
125
+ rep
126
+ end
127
+
128
+ def dump_array(obj, depth: 0, **kwargs)
129
+ rep = if obj.empty?
130
+ # TODO: replace this special case with simply general inline rendering detection.
131
+ "[]"
132
+ else
133
+ obj.each_with_index.map do |e, i|
134
+ trace_keys(i) do
135
+ e_rep = dump_any(e, depth: depth + 1, **kwargs)
136
+ Dumper.add_prefix("-", e_rep)
137
+ end
138
+ end.join("\n")
139
+ end
140
+
141
+ indent(rep) if depth > 0
142
+ rep
143
+ end
144
+
145
+ def dump_string(obj, depth: 0, force_multiline: false)
146
+ obj = obj.normalize_line_endings
147
+ lines = obj.lines
148
+ lines << "\n" if !lines.empty? && lines[-1][-1] == "\n"
149
+ if lines.length > 1 || depth == 0 || force_multiline
150
+ lines.each do |line|
151
+ Dumper.add_prefix(">", line)
152
+ end
153
+ end
154
+
155
+ # Case of empty input string. No space after '>'
156
+ lines << ">" if lines.empty? && (depth == 0 || force_multiline)
157
+
158
+ rep = lines.join.chomp
159
+ indent(rep) if !rep.empty? && depth > 0 && (rep.include?("\n") || force_multiline)
160
+ rep
161
+ end
162
+
163
+ def dump_custom_class(obj, **kwargs)
164
+ raise Errors::DumpUnsupportedTypeError.new(obj, traced_key) if @strict
165
+
166
+ if obj.is_a? Symbol
167
+ dump_string(obj.id2name, **kwargs)
168
+ elsif obj.respond_to? :encode_nt_with
169
+ class_name = obj.nil? ? "nil" : obj.class.name
170
+ enc = { CUSTOM_CLASS_KEY => class_name, "data" => obj.encode_nt_with }
171
+ dump_any(enc, **kwargs)
172
+ else
173
+ dump_string(obj.to_s, **kwargs)
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nestedtext/errors"
4
+ require "nestedtext/encode_helpers"
5
+ require "nestedtext/dumper"
6
+
7
+ # Model after JSON
8
+ # NestedText.dump(obj, io=nil) => dumps to string, or to IO if given
9
+ # NestedText.dump_file(obj, filename)
10
+
11
+ 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)
14
+ # io - additionaly write the out result to IO and still return result.
15
+
16
+ raise Errors::DumpBadIO, io unless io.nil? || io.respond_to?(:write) && io.respond_to?(:fsync)
17
+
18
+ opts = EncodeOptions.new(indentation, strict)
19
+ dumper = Dumper.new(opts)
20
+ result = dumper.dump obj
21
+ unless io.nil?
22
+ io.write(result)
23
+ io.fsync
24
+ end
25
+ result
26
+ end
27
+
28
+ def self.dump_file(obj, filename, **kwargs)
29
+ raise Errors::DumpFileBadPath, filename unless filename.is_a? String
30
+
31
+ File.open(filename, mode = "wt") do |file|
32
+ dump(obj, io: file, **kwargs)
33
+ end
34
+ end
35
+ end