From 4a17ad57d3426684d3c5335ad61b923eff0e91bb Mon Sep 17 00:00:00 2001 From: Jeongik Cha Date: Thu, 14 Sep 2023 16:35:19 +0900 Subject: Import toml_edit Bug: 277909042 Test: build Change-Id: I493bcfcd368bcb2ffbf23ad7a36ddd4b1094b9e9 --- .cargo_vcs_info.json | 6 + Android.bp | 24 + Cargo.lock | 898 ++++++++++++ Cargo.toml | 156 ++ Cargo.toml.orig | 68 + LICENSE | 228 +++ LICENSE-APACHE | 201 +++ LICENSE-MIT | 21 + METADATA | 20 + MODULE_LICENSE_APACHE2 | 0 MODULE_LICENSE_MIT | 0 OWNERS | 1 + README.md | 59 + examples/visit.rs | 284 ++++ src/array.rs | 403 ++++++ src/array_of_tables.rs | 166 +++ src/de/array.rs | 97 ++ src/de/datetime.rs | 43 + src/de/key.rs | 140 ++ src/de/mod.rs | 289 ++++ src/de/spanned.rs | 70 + src/de/table.rs | 213 +++ src/de/table_enum.rs | 160 +++ src/de/value.rs | 252 ++++ src/document.rs | 113 ++ src/encode.rs | 569 ++++++++ src/index.rs | 156 ++ src/inline_table.rs | 679 +++++++++ src/internal_string.rs | 183 +++ src/item.rs | 393 ++++++ src/key.rs | 344 +++++ src/lib.rs | 124 ++ src/parser/array.rs | 146 ++ src/parser/datetime.rs | 446 ++++++ src/parser/document.rs | 141 ++ src/parser/errors.rs | 316 +++++ src/parser/inline_table.rs | 181 +++ src/parser/key.rs | 112 ++ src/parser/mod.rs | 265 ++++ src/parser/numbers.rs | 397 ++++++ src/parser/state.rs | 323 +++++ src/parser/strings.rs | 478 +++++++ src/parser/table.rs | 89 ++ src/parser/trivia.rs | 156 ++ src/parser/value.rs | 155 ++ src/raw_string.rs | 182 +++ src/repr.rs | 253 ++++ src/ser/array.rs | 84 ++ src/ser/key.rs | 173 +++ src/ser/map.rs | 405 ++++++ src/ser/mod.rs | 165 +++ src/ser/pretty.rs | 45 + src/ser/value.rs | 243 ++++ src/table.rs | 757 ++++++++++ src/value.rs | 372 +++++ src/visit.rs | 236 ++++ src/visit_mut.rs | 252 ++++ tests/decoder.rs | 100 ++ tests/decoder_compliance.rs | 17 + tests/encoder.rs | 111 ++ tests/encoder_compliance.rs | 14 + tests/fixtures/invalid/array/double-comma-1.stderr | 6 + tests/fixtures/invalid/array/double-comma-2.stderr | 6 + .../fixtures/invalid/array/extending-table.stderr | 6 + .../invalid/array/missing-separator.stderr | 6 + tests/fixtures/invalid/array/no-close-2.stderr | 6 + .../fixtures/invalid/array/no-close-table-2.stderr | 6 + tests/fixtures/invalid/array/no-close-table.stderr | 6 + tests/fixtures/invalid/array/no-close.stderr | 6 + tests/fixtures/invalid/array/tables-1.stderr | 6 + tests/fixtures/invalid/array/tables-2.stderr | 6 + .../invalid/array/text-after-array-entries.stderr | 6 + .../array/text-before-array-separator.stderr | 6 + tests/fixtures/invalid/array/text-in-array.stderr | 6 + .../invalid/bool/almost-false-with-extra.stderr | 6 + tests/fixtures/invalid/bool/almost-false.stderr | 6 + .../invalid/bool/almost-true-with-extra.stderr | 6 + tests/fixtures/invalid/bool/almost-true.stderr | 6 + tests/fixtures/invalid/bool/just-f.stderr | 6 + tests/fixtures/invalid/bool/just-t.stderr | 6 + tests/fixtures/invalid/bool/mixed-case.stderr | 6 + .../invalid/bool/starting-same-false.stderr | 5 + .../invalid/bool/starting-same-true.stderr | 5 + .../fixtures/invalid/bool/wrong-case-false.stderr | 6 + tests/fixtures/invalid/bool/wrong-case-true.stderr | 6 + tests/fixtures/invalid/control/bare-cr.stderr | 5 + .../fixtures/invalid/control/bare-formfeed.stderr | 6 + tests/fixtures/invalid/control/bare-null.stderr | Bin 0 -> 126 bytes .../invalid/control/bare-vertical-tab.stderr | 6 + tests/fixtures/invalid/control/comment-cr.stderr | 6 + tests/fixtures/invalid/control/comment-del.stderr | 5 + tests/fixtures/invalid/control/comment-lf.stderr | 5 + tests/fixtures/invalid/control/comment-null.stderr | Bin 0 -> 124 bytes tests/fixtures/invalid/control/comment-us.stderr | 5 + tests/fixtures/invalid/control/control.stderr | 6 + tests/fixtures/invalid/control/multi-del.stderr | 5 + tests/fixtures/invalid/control/multi-lf.stderr | 5 + tests/fixtures/invalid/control/multi-null.stderr | Bin 0 -> 128 bytes tests/fixtures/invalid/control/multi-us.stderr | 5 + tests/fixtures/invalid/control/rawmulti-del.stderr | 5 + tests/fixtures/invalid/control/rawmulti-lf.stderr | 5 + .../fixtures/invalid/control/rawmulti-null.stderr | Bin 0 -> 136 bytes tests/fixtures/invalid/control/rawmulti-us.stderr | 5 + .../fixtures/invalid/control/rawstring-del.stderr | 5 + tests/fixtures/invalid/control/rawstring-lf.stderr | 5 + .../fixtures/invalid/control/rawstring-null.stderr | Bin 0 -> 122 bytes tests/fixtures/invalid/control/rawstring-us.stderr | 5 + tests/fixtures/invalid/control/string-bs.stderr | 5 + tests/fixtures/invalid/control/string-del.stderr | 5 + tests/fixtures/invalid/control/string-lf.stderr | 5 + tests/fixtures/invalid/control/string-null.stderr | Bin 0 -> 114 bytes tests/fixtures/invalid/control/string-us.stderr | 5 + tests/fixtures/invalid/datetime/hour-over.stderr | 5 + tests/fixtures/invalid/datetime/mday-over.stderr | 6 + tests/fixtures/invalid/datetime/mday-under.stderr | 6 + tests/fixtures/invalid/datetime/minute-over.stderr | 6 + tests/fixtures/invalid/datetime/month-over.stderr | 6 + tests/fixtures/invalid/datetime/month-under.stderr | 6 + .../invalid/datetime/no-leads-with-milli.stderr | 5 + tests/fixtures/invalid/datetime/no-leads.stderr | 5 + tests/fixtures/invalid/datetime/no-secs.stderr | 5 + tests/fixtures/invalid/datetime/no-t.stderr | 5 + tests/fixtures/invalid/datetime/second-over.stderr | 6 + .../invalid/datetime/time-no-leads-2.stderr | 5 + .../fixtures/invalid/datetime/time-no-leads.stderr | 5 + tests/fixtures/invalid/datetime/trailing-t.stderr | 5 + .../invalid/encoding/bad-utf8-at-end.stderr | 1 + .../invalid/encoding/bad-utf8-in-comment.stderr | 1 + .../encoding/bad-utf8-in-multiline-literal.stderr | 1 + .../invalid/encoding/bad-utf8-in-multiline.stderr | 1 + .../encoding/bad-utf8-in-string-literal.stderr | 1 + .../invalid/encoding/bad-utf8-in-string.stderr | 1 + .../invalid/encoding/bom-not-at-start-1.stderr | 1 + .../invalid/encoding/bom-not-at-start-2.stderr | 1 + tests/fixtures/invalid/encoding/utf16-bom.stderr | 1 + tests/fixtures/invalid/encoding/utf16.stderr | Bin 0 -> 105 bytes tests/fixtures/invalid/float/double-point-1.stderr | 6 + tests/fixtures/invalid/float/double-point-2.stderr | 5 + tests/fixtures/invalid/float/exp-double-e-1.stderr | 5 + tests/fixtures/invalid/float/exp-double-e-2.stderr | 5 + tests/fixtures/invalid/float/exp-double-us.stderr | 5 + tests/fixtures/invalid/float/exp-leading-us.stderr | 5 + tests/fixtures/invalid/float/exp-point-1.stderr | 5 + tests/fixtures/invalid/float/exp-point-2.stderr | 6 + .../fixtures/invalid/float/exp-trailing-us.stderr | 5 + tests/fixtures/invalid/float/float.stderr | 5 + .../fixtures/invalid/float/inf-incomplete-1.stderr | 6 + .../fixtures/invalid/float/inf-incomplete-2.stderr | 5 + .../fixtures/invalid/float/inf-incomplete-3.stderr | 5 + tests/fixtures/invalid/float/inf_underscore.stderr | 6 + .../invalid/float/leading-point-neg.stderr | 5 + .../invalid/float/leading-point-plus.stderr | 5 + tests/fixtures/invalid/float/leading-point.stderr | 6 + tests/fixtures/invalid/float/leading-us.stderr | 6 + .../fixtures/invalid/float/leading-zero-neg.stderr | 5 + .../invalid/float/leading-zero-plus.stderr | 5 + tests/fixtures/invalid/float/leading-zero.stderr | 5 + .../fixtures/invalid/float/nan-incomplete-1.stderr | 6 + .../fixtures/invalid/float/nan-incomplete-2.stderr | 5 + .../fixtures/invalid/float/nan-incomplete-3.stderr | 5 + tests/fixtures/invalid/float/nan_underscore.stderr | 6 + .../invalid/float/trailing-point-min.stderr | 6 + .../invalid/float/trailing-point-plus.stderr | 6 + tests/fixtures/invalid/float/trailing-point.stderr | 6 + .../fixtures/invalid/float/trailing-us-exp.stderr | 6 + tests/fixtures/invalid/float/trailing-us.stderr | 6 + tests/fixtures/invalid/float/us-after-point.stderr | 6 + .../fixtures/invalid/float/us-before-point.stderr | 6 + tests/fixtures/invalid/inline-table/add.stderr | 6 + .../invalid/inline-table/double-comma.stderr | 6 + .../invalid/inline-table/duplicate-key.stderr | 5 + tests/fixtures/invalid/inline-table/empty.stderr | 6 + .../invalid/inline-table/linebreak-1.stderr | 6 + .../invalid/inline-table/linebreak-2.stderr | 6 + .../invalid/inline-table/linebreak-3.stderr | 6 + .../invalid/inline-table/linebreak-4.stderr | 6 + .../fixtures/invalid/inline-table/no-comma.stderr | 6 + .../fixtures/invalid/inline-table/overwrite.stderr | 5 + .../invalid/inline-table/trailing-comma.stderr | 6 + tests/fixtures/invalid/integer/capital-bin.stderr | 5 + tests/fixtures/invalid/integer/capital-hex.stderr | 5 + tests/fixtures/invalid/integer/capital-oct.stderr | 5 + .../invalid/integer/double-sign-nex.stderr | 5 + .../invalid/integer/double-sign-plus.stderr | 5 + tests/fixtures/invalid/integer/double-us.stderr | 6 + .../fixtures/invalid/integer/incomplete-bin.stderr | 5 + .../fixtures/invalid/integer/incomplete-hex.stderr | 5 + .../fixtures/invalid/integer/incomplete-oct.stderr | 5 + tests/fixtures/invalid/integer/integer.stderr | 5 + tests/fixtures/invalid/integer/invalid-bin.stderr | 5 + tests/fixtures/invalid/integer/invalid-hex.stderr | 5 + tests/fixtures/invalid/integer/invalid-oct.stderr | 5 + .../fixtures/invalid/integer/leading-us-bin.stderr | 6 + .../fixtures/invalid/integer/leading-us-hex.stderr | 6 + .../fixtures/invalid/integer/leading-us-oct.stderr | 6 + tests/fixtures/invalid/integer/leading-us.stderr | 6 + .../fixtures/invalid/integer/leading-zero-1.stderr | 5 + .../fixtures/invalid/integer/leading-zero-2.stderr | 5 + .../fixtures/invalid/integer/leading-zero-3.stderr | 5 + .../invalid/integer/leading-zero-sign-1.stderr | 5 + .../invalid/integer/leading-zero-sign-2.stderr | 5 + .../invalid/integer/leading-zero-sign-3.stderr | 5 + tests/fixtures/invalid/integer/negative-bin.stderr | 5 + tests/fixtures/invalid/integer/negative-hex.stderr | 5 + tests/fixtures/invalid/integer/negative-oct.stderr | 5 + tests/fixtures/invalid/integer/positive-bin.stderr | 5 + tests/fixtures/invalid/integer/positive-hex.stderr | 5 + tests/fixtures/invalid/integer/positive-oct.stderr | 5 + .../invalid/integer/text-after-integer.stderr | 5 + .../invalid/integer/trailing-us-bin.stderr | 6 + .../invalid/integer/trailing-us-hex.stderr | 6 + .../invalid/integer/trailing-us-oct.stderr | 6 + tests/fixtures/invalid/integer/trailing-us.stderr | 6 + tests/fixtures/invalid/integer/us-after-bin.stderr | 5 + tests/fixtures/invalid/integer/us-after-hex.stderr | 5 + tests/fixtures/invalid/integer/us-after-oct.stderr | 5 + tests/fixtures/invalid/key/after-array.stderr | 6 + tests/fixtures/invalid/key/after-table.stderr | 6 + tests/fixtures/invalid/key/after-value.stderr | 5 + .../invalid/key/bare-invalid-character.stderr | 5 + .../invalid/key/dotted-redefine-table.stderr | 5 + tests/fixtures/invalid/key/duplicate-keys.stderr | 5 + tests/fixtures/invalid/key/duplicate.stderr | 5 + tests/fixtures/invalid/key/empty.stderr | 5 + tests/fixtures/invalid/key/escape.stderr | 5 + tests/fixtures/invalid/key/hash.stderr | 5 + tests/fixtures/invalid/key/multiline.stderr | 5 + tests/fixtures/invalid/key/newline.stderr | 5 + tests/fixtures/invalid/key/no-eol.stderr | 5 + tests/fixtures/invalid/key/open-bracket.stderr | 6 + tests/fixtures/invalid/key/partial-quoted.stderr | 5 + .../fixtures/invalid/key/quoted-unclosed-1.stderr | 5 + .../fixtures/invalid/key/quoted-unclosed-2.stderr | 5 + .../invalid/key/single-open-bracket.stderr | 5 + tests/fixtures/invalid/key/space.stderr | 5 + .../fixtures/invalid/key/special-character.stderr | 5 + tests/fixtures/invalid/key/start-bracket.stderr | 6 + tests/fixtures/invalid/key/start-dot.stderr | 5 + tests/fixtures/invalid/key/two-equals.stderr | 6 + tests/fixtures/invalid/key/two-equals2.stderr | 6 + tests/fixtures/invalid/key/two-equals3.stderr | 6 + tests/fixtures/invalid/key/without-value-1.stderr | 5 + tests/fixtures/invalid/key/without-value-2.stderr | 6 + tests/fixtures/invalid/key/without-value-3.stderr | 5 + tests/fixtures/invalid/key/without-value-4.stderr | 6 + .../fixtures/invalid/spec/inline-table-2-0.stderr | 5 + .../fixtures/invalid/spec/inline-table-3-0.stderr | 5 + .../fixtures/invalid/spec/key-value-pair-1.stderr | 6 + tests/fixtures/invalid/spec/keys-2.stderr | 5 + tests/fixtures/invalid/spec/string-4-0.stderr | 5 + tests/fixtures/invalid/spec/string-7-0.stderr | 5 + tests/fixtures/invalid/spec/table-9-0.stderr | 6 + tests/fixtures/invalid/spec/table-9-1.stderr | 6 + .../fixtures/invalid/string/bad-byte-escape.stderr | 6 + tests/fixtures/invalid/string/bad-codepoint.stderr | 6 + tests/fixtures/invalid/string/bad-concat.stderr | 5 + tests/fixtures/invalid/string/bad-escape-1.stderr | 6 + tests/fixtures/invalid/string/bad-escape-2.stderr | 6 + tests/fixtures/invalid/string/bad-hex-esc-1.stderr | 6 + tests/fixtures/invalid/string/bad-hex-esc-2.stderr | 6 + tests/fixtures/invalid/string/bad-hex-esc-3.stderr | 6 + tests/fixtures/invalid/string/bad-hex-esc-4.stderr | 6 + tests/fixtures/invalid/string/bad-hex-esc-5.stderr | 6 + tests/fixtures/invalid/string/bad-hex-esc.stderr | 6 + tests/fixtures/invalid/string/bad-multiline.stderr | 5 + .../invalid/string/bad-slash-escape.stderr | 6 + tests/fixtures/invalid/string/bad-uni-esc-1.stderr | 5 + tests/fixtures/invalid/string/bad-uni-esc-2.stderr | 5 + tests/fixtures/invalid/string/bad-uni-esc-3.stderr | 5 + tests/fixtures/invalid/string/bad-uni-esc-4.stderr | 5 + tests/fixtures/invalid/string/bad-uni-esc-5.stderr | 5 + .../invalid/string/basic-byte-escapes.stderr | 6 + ...-multiline-out-of-range-unicode-escape-1.stderr | 6 + ...-multiline-out-of-range-unicode-escape-2.stderr | 6 + .../invalid/string/basic-multiline-quotes.stderr | 5 + .../string/basic-multiline-unknown-escape.stderr | 6 + .../basic-out-of-range-unicode-escape-1.stderr | 6 + .../basic-out-of-range-unicode-escape-2.stderr | 6 + .../invalid/string/basic-unknown-escape.stderr | 6 + .../string/literal-multiline-quotes-1.stderr | 5 + .../string/literal-multiline-quotes-2.stderr | 5 + .../fixtures/invalid/string/missing-quotes.stderr | 6 + .../invalid/string/multiline-bad-escape-1.stderr | 6 + .../invalid/string/multiline-bad-escape-2.stderr | 6 + .../invalid/string/multiline-bad-escape-3.stderr | 6 + .../invalid/string/multiline-escape-space.stderr | 6 + .../invalid/string/multiline-no-close-2.stderr | 5 + .../invalid/string/multiline-no-close.stderr | 5 + .../invalid/string/multiline-quotes-1.stderr | 5 + tests/fixtures/invalid/string/no-close.stderr | 5 + .../invalid/string/text-after-string.stderr | 5 + tests/fixtures/invalid/string/wrong-close.stderr | 5 + .../invalid/table/append-with-dotted-keys-1.stderr | 5 + .../invalid/table/append-with-dotted-keys-2.stderr | 5 + tests/fixtures/invalid/table/array-empty.stderr | 5 + tests/fixtures/invalid/table/array-implicit.stderr | 6 + .../invalid/table/array-missing-bracket.stderr | 6 + .../table/duplicate-key-dotted-table.stderr | 6 + .../table/duplicate-key-dotted-table2.stderr | 6 + .../invalid/table/duplicate-key-table.stderr | 6 + .../invalid/table/duplicate-table-array.stderr | 6 + .../invalid/table/duplicate-table-array2.stderr | 6 + tests/fixtures/invalid/table/duplicate.stderr | 6 + .../invalid/table/empty-implicit-table.stderr | 6 + tests/fixtures/invalid/table/empty.stderr | 5 + tests/fixtures/invalid/table/equals-sign.stderr | 6 + tests/fixtures/invalid/table/llbrace.stderr | 5 + .../invalid/table/nested-brackets-close.stderr | 6 + .../invalid/table/nested-brackets-open.stderr | 6 + .../fixtures/invalid/table/quoted-no-close.stderr | 5 + tests/fixtures/invalid/table/redefine.stderr | 6 + tests/fixtures/invalid/table/rrbrace.stderr | 6 + .../fixtures/invalid/table/text-after-table.stderr | 6 + tests/fixtures/invalid/table/whitespace.stderr | 6 + tests/fixtures/invalid/table/with-pound.stderr | 6 + tests/invalid.rs | 26 + tests/testsuite/convert.rs | 79 ++ tests/testsuite/datetime.rs | 256 ++++ tests/testsuite/edit.rs | 855 +++++++++++ tests/testsuite/invalid.rs | 211 +++ tests/testsuite/main.rs | 8 + tests/testsuite/parse.rs | 1490 ++++++++++++++++++++ tests/testsuite/stackoverflow.rs | 54 + 323 files changed, 17269 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 Android.bp create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 METADATA create mode 100644 MODULE_LICENSE_APACHE2 create mode 100644 MODULE_LICENSE_MIT create mode 100644 OWNERS create mode 100644 README.md create mode 100644 examples/visit.rs create mode 100644 src/array.rs create mode 100644 src/array_of_tables.rs create mode 100644 src/de/array.rs create mode 100644 src/de/datetime.rs create mode 100644 src/de/key.rs create mode 100644 src/de/mod.rs create mode 100644 src/de/spanned.rs create mode 100644 src/de/table.rs create mode 100644 src/de/table_enum.rs create mode 100644 src/de/value.rs create mode 100644 src/document.rs create mode 100644 src/encode.rs create mode 100644 src/index.rs create mode 100644 src/inline_table.rs create mode 100644 src/internal_string.rs create mode 100644 src/item.rs create mode 100644 src/key.rs create mode 100644 src/lib.rs create mode 100644 src/parser/array.rs create mode 100644 src/parser/datetime.rs create mode 100644 src/parser/document.rs create mode 100644 src/parser/errors.rs create mode 100644 src/parser/inline_table.rs create mode 100644 src/parser/key.rs create mode 100644 src/parser/mod.rs create mode 100644 src/parser/numbers.rs create mode 100644 src/parser/state.rs create mode 100644 src/parser/strings.rs create mode 100644 src/parser/table.rs create mode 100644 src/parser/trivia.rs create mode 100644 src/parser/value.rs create mode 100644 src/raw_string.rs create mode 100644 src/repr.rs create mode 100644 src/ser/array.rs create mode 100644 src/ser/key.rs create mode 100644 src/ser/map.rs create mode 100644 src/ser/mod.rs create mode 100644 src/ser/pretty.rs create mode 100644 src/ser/value.rs create mode 100644 src/table.rs create mode 100644 src/value.rs create mode 100644 src/visit.rs create mode 100644 src/visit_mut.rs create mode 100644 tests/decoder.rs create mode 100644 tests/decoder_compliance.rs create mode 100644 tests/encoder.rs create mode 100644 tests/encoder_compliance.rs create mode 100644 tests/fixtures/invalid/array/double-comma-1.stderr create mode 100644 tests/fixtures/invalid/array/double-comma-2.stderr create mode 100644 tests/fixtures/invalid/array/extending-table.stderr create mode 100644 tests/fixtures/invalid/array/missing-separator.stderr create mode 100644 tests/fixtures/invalid/array/no-close-2.stderr create mode 100644 tests/fixtures/invalid/array/no-close-table-2.stderr create mode 100644 tests/fixtures/invalid/array/no-close-table.stderr create mode 100644 tests/fixtures/invalid/array/no-close.stderr create mode 100644 tests/fixtures/invalid/array/tables-1.stderr create mode 100644 tests/fixtures/invalid/array/tables-2.stderr create mode 100644 tests/fixtures/invalid/array/text-after-array-entries.stderr create mode 100644 tests/fixtures/invalid/array/text-before-array-separator.stderr create mode 100644 tests/fixtures/invalid/array/text-in-array.stderr create mode 100644 tests/fixtures/invalid/bool/almost-false-with-extra.stderr create mode 100644 tests/fixtures/invalid/bool/almost-false.stderr create mode 100644 tests/fixtures/invalid/bool/almost-true-with-extra.stderr create mode 100644 tests/fixtures/invalid/bool/almost-true.stderr create mode 100644 tests/fixtures/invalid/bool/just-f.stderr create mode 100644 tests/fixtures/invalid/bool/just-t.stderr create mode 100644 tests/fixtures/invalid/bool/mixed-case.stderr create mode 100644 tests/fixtures/invalid/bool/starting-same-false.stderr create mode 100644 tests/fixtures/invalid/bool/starting-same-true.stderr create mode 100644 tests/fixtures/invalid/bool/wrong-case-false.stderr create mode 100644 tests/fixtures/invalid/bool/wrong-case-true.stderr create mode 100644 tests/fixtures/invalid/control/bare-cr.stderr create mode 100644 tests/fixtures/invalid/control/bare-formfeed.stderr create mode 100644 tests/fixtures/invalid/control/bare-null.stderr create mode 100644 tests/fixtures/invalid/control/bare-vertical-tab.stderr create mode 100644 tests/fixtures/invalid/control/comment-cr.stderr create mode 100644 tests/fixtures/invalid/control/comment-del.stderr create mode 100644 tests/fixtures/invalid/control/comment-lf.stderr create mode 100644 tests/fixtures/invalid/control/comment-null.stderr create mode 100644 tests/fixtures/invalid/control/comment-us.stderr create mode 100644 tests/fixtures/invalid/control/control.stderr create mode 100644 tests/fixtures/invalid/control/multi-del.stderr create mode 100644 tests/fixtures/invalid/control/multi-lf.stderr create mode 100644 tests/fixtures/invalid/control/multi-null.stderr create mode 100644 tests/fixtures/invalid/control/multi-us.stderr create mode 100644 tests/fixtures/invalid/control/rawmulti-del.stderr create mode 100644 tests/fixtures/invalid/control/rawmulti-lf.stderr create mode 100644 tests/fixtures/invalid/control/rawmulti-null.stderr create mode 100644 tests/fixtures/invalid/control/rawmulti-us.stderr create mode 100644 tests/fixtures/invalid/control/rawstring-del.stderr create mode 100644 tests/fixtures/invalid/control/rawstring-lf.stderr create mode 100644 tests/fixtures/invalid/control/rawstring-null.stderr create mode 100644 tests/fixtures/invalid/control/rawstring-us.stderr create mode 100644 tests/fixtures/invalid/control/string-bs.stderr create mode 100644 tests/fixtures/invalid/control/string-del.stderr create mode 100644 tests/fixtures/invalid/control/string-lf.stderr create mode 100644 tests/fixtures/invalid/control/string-null.stderr create mode 100644 tests/fixtures/invalid/control/string-us.stderr create mode 100644 tests/fixtures/invalid/datetime/hour-over.stderr create mode 100644 tests/fixtures/invalid/datetime/mday-over.stderr create mode 100644 tests/fixtures/invalid/datetime/mday-under.stderr create mode 100644 tests/fixtures/invalid/datetime/minute-over.stderr create mode 100644 tests/fixtures/invalid/datetime/month-over.stderr create mode 100644 tests/fixtures/invalid/datetime/month-under.stderr create mode 100644 tests/fixtures/invalid/datetime/no-leads-with-milli.stderr create mode 100644 tests/fixtures/invalid/datetime/no-leads.stderr create mode 100644 tests/fixtures/invalid/datetime/no-secs.stderr create mode 100644 tests/fixtures/invalid/datetime/no-t.stderr create mode 100644 tests/fixtures/invalid/datetime/second-over.stderr create mode 100644 tests/fixtures/invalid/datetime/time-no-leads-2.stderr create mode 100644 tests/fixtures/invalid/datetime/time-no-leads.stderr create mode 100644 tests/fixtures/invalid/datetime/trailing-t.stderr create mode 100644 tests/fixtures/invalid/encoding/bad-utf8-at-end.stderr create mode 100644 tests/fixtures/invalid/encoding/bad-utf8-in-comment.stderr create mode 100644 tests/fixtures/invalid/encoding/bad-utf8-in-multiline-literal.stderr create mode 100644 tests/fixtures/invalid/encoding/bad-utf8-in-multiline.stderr create mode 100644 tests/fixtures/invalid/encoding/bad-utf8-in-string-literal.stderr create mode 100644 tests/fixtures/invalid/encoding/bad-utf8-in-string.stderr create mode 100644 tests/fixtures/invalid/encoding/bom-not-at-start-1.stderr create mode 100644 tests/fixtures/invalid/encoding/bom-not-at-start-2.stderr create mode 100644 tests/fixtures/invalid/encoding/utf16-bom.stderr create mode 100644 tests/fixtures/invalid/encoding/utf16.stderr create mode 100644 tests/fixtures/invalid/float/double-point-1.stderr create mode 100644 tests/fixtures/invalid/float/double-point-2.stderr create mode 100644 tests/fixtures/invalid/float/exp-double-e-1.stderr create mode 100644 tests/fixtures/invalid/float/exp-double-e-2.stderr create mode 100644 tests/fixtures/invalid/float/exp-double-us.stderr create mode 100644 tests/fixtures/invalid/float/exp-leading-us.stderr create mode 100644 tests/fixtures/invalid/float/exp-point-1.stderr create mode 100644 tests/fixtures/invalid/float/exp-point-2.stderr create mode 100644 tests/fixtures/invalid/float/exp-trailing-us.stderr create mode 100644 tests/fixtures/invalid/float/float.stderr create mode 100644 tests/fixtures/invalid/float/inf-incomplete-1.stderr create mode 100644 tests/fixtures/invalid/float/inf-incomplete-2.stderr create mode 100644 tests/fixtures/invalid/float/inf-incomplete-3.stderr create mode 100644 tests/fixtures/invalid/float/inf_underscore.stderr create mode 100644 tests/fixtures/invalid/float/leading-point-neg.stderr create mode 100644 tests/fixtures/invalid/float/leading-point-plus.stderr create mode 100644 tests/fixtures/invalid/float/leading-point.stderr create mode 100644 tests/fixtures/invalid/float/leading-us.stderr create mode 100644 tests/fixtures/invalid/float/leading-zero-neg.stderr create mode 100644 tests/fixtures/invalid/float/leading-zero-plus.stderr create mode 100644 tests/fixtures/invalid/float/leading-zero.stderr create mode 100644 tests/fixtures/invalid/float/nan-incomplete-1.stderr create mode 100644 tests/fixtures/invalid/float/nan-incomplete-2.stderr create mode 100644 tests/fixtures/invalid/float/nan-incomplete-3.stderr create mode 100644 tests/fixtures/invalid/float/nan_underscore.stderr create mode 100644 tests/fixtures/invalid/float/trailing-point-min.stderr create mode 100644 tests/fixtures/invalid/float/trailing-point-plus.stderr create mode 100644 tests/fixtures/invalid/float/trailing-point.stderr create mode 100644 tests/fixtures/invalid/float/trailing-us-exp.stderr create mode 100644 tests/fixtures/invalid/float/trailing-us.stderr create mode 100644 tests/fixtures/invalid/float/us-after-point.stderr create mode 100644 tests/fixtures/invalid/float/us-before-point.stderr create mode 100644 tests/fixtures/invalid/inline-table/add.stderr create mode 100644 tests/fixtures/invalid/inline-table/double-comma.stderr create mode 100644 tests/fixtures/invalid/inline-table/duplicate-key.stderr create mode 100644 tests/fixtures/invalid/inline-table/empty.stderr create mode 100644 tests/fixtures/invalid/inline-table/linebreak-1.stderr create mode 100644 tests/fixtures/invalid/inline-table/linebreak-2.stderr create mode 100644 tests/fixtures/invalid/inline-table/linebreak-3.stderr create mode 100644 tests/fixtures/invalid/inline-table/linebreak-4.stderr create mode 100644 tests/fixtures/invalid/inline-table/no-comma.stderr create mode 100644 tests/fixtures/invalid/inline-table/overwrite.stderr create mode 100644 tests/fixtures/invalid/inline-table/trailing-comma.stderr create mode 100644 tests/fixtures/invalid/integer/capital-bin.stderr create mode 100644 tests/fixtures/invalid/integer/capital-hex.stderr create mode 100644 tests/fixtures/invalid/integer/capital-oct.stderr create mode 100644 tests/fixtures/invalid/integer/double-sign-nex.stderr create mode 100644 tests/fixtures/invalid/integer/double-sign-plus.stderr create mode 100644 tests/fixtures/invalid/integer/double-us.stderr create mode 100644 tests/fixtures/invalid/integer/incomplete-bin.stderr create mode 100644 tests/fixtures/invalid/integer/incomplete-hex.stderr create mode 100644 tests/fixtures/invalid/integer/incomplete-oct.stderr create mode 100644 tests/fixtures/invalid/integer/integer.stderr create mode 100644 tests/fixtures/invalid/integer/invalid-bin.stderr create mode 100644 tests/fixtures/invalid/integer/invalid-hex.stderr create mode 100644 tests/fixtures/invalid/integer/invalid-oct.stderr create mode 100644 tests/fixtures/invalid/integer/leading-us-bin.stderr create mode 100644 tests/fixtures/invalid/integer/leading-us-hex.stderr create mode 100644 tests/fixtures/invalid/integer/leading-us-oct.stderr create mode 100644 tests/fixtures/invalid/integer/leading-us.stderr create mode 100644 tests/fixtures/invalid/integer/leading-zero-1.stderr create mode 100644 tests/fixtures/invalid/integer/leading-zero-2.stderr create mode 100644 tests/fixtures/invalid/integer/leading-zero-3.stderr create mode 100644 tests/fixtures/invalid/integer/leading-zero-sign-1.stderr create mode 100644 tests/fixtures/invalid/integer/leading-zero-sign-2.stderr create mode 100644 tests/fixtures/invalid/integer/leading-zero-sign-3.stderr create mode 100644 tests/fixtures/invalid/integer/negative-bin.stderr create mode 100644 tests/fixtures/invalid/integer/negative-hex.stderr create mode 100644 tests/fixtures/invalid/integer/negative-oct.stderr create mode 100644 tests/fixtures/invalid/integer/positive-bin.stderr create mode 100644 tests/fixtures/invalid/integer/positive-hex.stderr create mode 100644 tests/fixtures/invalid/integer/positive-oct.stderr create mode 100644 tests/fixtures/invalid/integer/text-after-integer.stderr create mode 100644 tests/fixtures/invalid/integer/trailing-us-bin.stderr create mode 100644 tests/fixtures/invalid/integer/trailing-us-hex.stderr create mode 100644 tests/fixtures/invalid/integer/trailing-us-oct.stderr create mode 100644 tests/fixtures/invalid/integer/trailing-us.stderr create mode 100644 tests/fixtures/invalid/integer/us-after-bin.stderr create mode 100644 tests/fixtures/invalid/integer/us-after-hex.stderr create mode 100644 tests/fixtures/invalid/integer/us-after-oct.stderr create mode 100644 tests/fixtures/invalid/key/after-array.stderr create mode 100644 tests/fixtures/invalid/key/after-table.stderr create mode 100644 tests/fixtures/invalid/key/after-value.stderr create mode 100644 tests/fixtures/invalid/key/bare-invalid-character.stderr create mode 100644 tests/fixtures/invalid/key/dotted-redefine-table.stderr create mode 100644 tests/fixtures/invalid/key/duplicate-keys.stderr create mode 100644 tests/fixtures/invalid/key/duplicate.stderr create mode 100644 tests/fixtures/invalid/key/empty.stderr create mode 100644 tests/fixtures/invalid/key/escape.stderr create mode 100644 tests/fixtures/invalid/key/hash.stderr create mode 100644 tests/fixtures/invalid/key/multiline.stderr create mode 100644 tests/fixtures/invalid/key/newline.stderr create mode 100644 tests/fixtures/invalid/key/no-eol.stderr create mode 100644 tests/fixtures/invalid/key/open-bracket.stderr create mode 100644 tests/fixtures/invalid/key/partial-quoted.stderr create mode 100644 tests/fixtures/invalid/key/quoted-unclosed-1.stderr create mode 100644 tests/fixtures/invalid/key/quoted-unclosed-2.stderr create mode 100644 tests/fixtures/invalid/key/single-open-bracket.stderr create mode 100644 tests/fixtures/invalid/key/space.stderr create mode 100644 tests/fixtures/invalid/key/special-character.stderr create mode 100644 tests/fixtures/invalid/key/start-bracket.stderr create mode 100644 tests/fixtures/invalid/key/start-dot.stderr create mode 100644 tests/fixtures/invalid/key/two-equals.stderr create mode 100644 tests/fixtures/invalid/key/two-equals2.stderr create mode 100644 tests/fixtures/invalid/key/two-equals3.stderr create mode 100644 tests/fixtures/invalid/key/without-value-1.stderr create mode 100644 tests/fixtures/invalid/key/without-value-2.stderr create mode 100644 tests/fixtures/invalid/key/without-value-3.stderr create mode 100644 tests/fixtures/invalid/key/without-value-4.stderr create mode 100644 tests/fixtures/invalid/spec/inline-table-2-0.stderr create mode 100644 tests/fixtures/invalid/spec/inline-table-3-0.stderr create mode 100644 tests/fixtures/invalid/spec/key-value-pair-1.stderr create mode 100644 tests/fixtures/invalid/spec/keys-2.stderr create mode 100644 tests/fixtures/invalid/spec/string-4-0.stderr create mode 100644 tests/fixtures/invalid/spec/string-7-0.stderr create mode 100644 tests/fixtures/invalid/spec/table-9-0.stderr create mode 100644 tests/fixtures/invalid/spec/table-9-1.stderr create mode 100644 tests/fixtures/invalid/string/bad-byte-escape.stderr create mode 100644 tests/fixtures/invalid/string/bad-codepoint.stderr create mode 100644 tests/fixtures/invalid/string/bad-concat.stderr create mode 100644 tests/fixtures/invalid/string/bad-escape-1.stderr create mode 100644 tests/fixtures/invalid/string/bad-escape-2.stderr create mode 100644 tests/fixtures/invalid/string/bad-hex-esc-1.stderr create mode 100644 tests/fixtures/invalid/string/bad-hex-esc-2.stderr create mode 100644 tests/fixtures/invalid/string/bad-hex-esc-3.stderr create mode 100644 tests/fixtures/invalid/string/bad-hex-esc-4.stderr create mode 100644 tests/fixtures/invalid/string/bad-hex-esc-5.stderr create mode 100644 tests/fixtures/invalid/string/bad-hex-esc.stderr create mode 100644 tests/fixtures/invalid/string/bad-multiline.stderr create mode 100644 tests/fixtures/invalid/string/bad-slash-escape.stderr create mode 100644 tests/fixtures/invalid/string/bad-uni-esc-1.stderr create mode 100644 tests/fixtures/invalid/string/bad-uni-esc-2.stderr create mode 100644 tests/fixtures/invalid/string/bad-uni-esc-3.stderr create mode 100644 tests/fixtures/invalid/string/bad-uni-esc-4.stderr create mode 100644 tests/fixtures/invalid/string/bad-uni-esc-5.stderr create mode 100644 tests/fixtures/invalid/string/basic-byte-escapes.stderr create mode 100644 tests/fixtures/invalid/string/basic-multiline-out-of-range-unicode-escape-1.stderr create mode 100644 tests/fixtures/invalid/string/basic-multiline-out-of-range-unicode-escape-2.stderr create mode 100644 tests/fixtures/invalid/string/basic-multiline-quotes.stderr create mode 100644 tests/fixtures/invalid/string/basic-multiline-unknown-escape.stderr create mode 100644 tests/fixtures/invalid/string/basic-out-of-range-unicode-escape-1.stderr create mode 100644 tests/fixtures/invalid/string/basic-out-of-range-unicode-escape-2.stderr create mode 100644 tests/fixtures/invalid/string/basic-unknown-escape.stderr create mode 100644 tests/fixtures/invalid/string/literal-multiline-quotes-1.stderr create mode 100644 tests/fixtures/invalid/string/literal-multiline-quotes-2.stderr create mode 100644 tests/fixtures/invalid/string/missing-quotes.stderr create mode 100644 tests/fixtures/invalid/string/multiline-bad-escape-1.stderr create mode 100644 tests/fixtures/invalid/string/multiline-bad-escape-2.stderr create mode 100644 tests/fixtures/invalid/string/multiline-bad-escape-3.stderr create mode 100644 tests/fixtures/invalid/string/multiline-escape-space.stderr create mode 100644 tests/fixtures/invalid/string/multiline-no-close-2.stderr create mode 100644 tests/fixtures/invalid/string/multiline-no-close.stderr create mode 100644 tests/fixtures/invalid/string/multiline-quotes-1.stderr create mode 100644 tests/fixtures/invalid/string/no-close.stderr create mode 100644 tests/fixtures/invalid/string/text-after-string.stderr create mode 100644 tests/fixtures/invalid/string/wrong-close.stderr create mode 100644 tests/fixtures/invalid/table/append-with-dotted-keys-1.stderr create mode 100644 tests/fixtures/invalid/table/append-with-dotted-keys-2.stderr create mode 100644 tests/fixtures/invalid/table/array-empty.stderr create mode 100644 tests/fixtures/invalid/table/array-implicit.stderr create mode 100644 tests/fixtures/invalid/table/array-missing-bracket.stderr create mode 100644 tests/fixtures/invalid/table/duplicate-key-dotted-table.stderr create mode 100644 tests/fixtures/invalid/table/duplicate-key-dotted-table2.stderr create mode 100644 tests/fixtures/invalid/table/duplicate-key-table.stderr create mode 100644 tests/fixtures/invalid/table/duplicate-table-array.stderr create mode 100644 tests/fixtures/invalid/table/duplicate-table-array2.stderr create mode 100644 tests/fixtures/invalid/table/duplicate.stderr create mode 100644 tests/fixtures/invalid/table/empty-implicit-table.stderr create mode 100644 tests/fixtures/invalid/table/empty.stderr create mode 100644 tests/fixtures/invalid/table/equals-sign.stderr create mode 100644 tests/fixtures/invalid/table/llbrace.stderr create mode 100644 tests/fixtures/invalid/table/nested-brackets-close.stderr create mode 100644 tests/fixtures/invalid/table/nested-brackets-open.stderr create mode 100644 tests/fixtures/invalid/table/quoted-no-close.stderr create mode 100644 tests/fixtures/invalid/table/redefine.stderr create mode 100644 tests/fixtures/invalid/table/rrbrace.stderr create mode 100644 tests/fixtures/invalid/table/text-after-table.stderr create mode 100644 tests/fixtures/invalid/table/whitespace.stderr create mode 100644 tests/fixtures/invalid/table/with-pound.stderr create mode 100644 tests/invalid.rs create mode 100644 tests/testsuite/convert.rs create mode 100644 tests/testsuite/datetime.rs create mode 100644 tests/testsuite/edit.rs create mode 100644 tests/testsuite/invalid.rs create mode 100644 tests/testsuite/main.rs create mode 100644 tests/testsuite/parse.rs create mode 100644 tests/testsuite/stackoverflow.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..fbf4047 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "5753e4fd8ddf78980c41aabaec78a935eaba93e1" + }, + "path_in_vcs": "crates/toml_edit" +} \ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..e023edc --- /dev/null +++ b/Android.bp @@ -0,0 +1,24 @@ +// This file is generated by cargo2android.py --run --features default,serde. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_library_host { + name: "libtoml_edit", + crate_name: "toml_edit", + cargo_env_compat: true, + cargo_pkg_version: "0.19.14", + srcs: ["src/lib.rs"], + edition: "2021", + features: [ + "default", + "serde", + ], + rustlibs: [ + "libindexmap", + "libserde", + "libserde_spanned", + "libtoml_datetime", + "libwinnow", + ], +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5dfc8c2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,898 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "clap" +version = "4.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.105", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "kstring" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" + +[[package]] +name = "libtest-mimic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b603516767d1ab23d0de09d023e62966c3322f7148297c35cf3d97aa8b37fa" +dependencies = [ + "clap", + "termcolor", + "threadpool", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi 0.1.19", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.105", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rustix" +version = "0.37.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "similar" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" + +[[package]] +name = "snapbox" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bccd62078347f89a914e3004d94582e13824d4e3d8a816317862884c423835" +dependencies = [ + "anstream", + "anstyle", + "ignore", + "libtest-mimic", + "normalize-line-endings", + "similar", + "snapbox-macros", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaaf09df9f0eeae82be96290918520214530e738a7fe5a351b0f24cf77c0ca31" +dependencies = [ + "anstream", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "toml-test" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37351256790aa1dbd6d60f4ff08e55e7f372e292f3e9040d6e077463d9a779c3" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + +[[package]] +name = "toml-test-data" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f351b6d6005ee802b0d4a53ca1cdf05636f441df4d299e62cba57f1da52646" +dependencies = [ + "include_dir", +] + +[[package]] +name = "toml-test-harness" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e00fda5710922fe6b3005bf6a5050c303d6f9625249c37b7386e8818f4af675" +dependencies = [ + "ignore", + "libtest-mimic", + "toml-test", + "toml-test-data", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.14" +dependencies = [ + "indexmap", + "kstring", + "libtest-mimic", + "serde", + "serde_json", + "serde_spanned", + "snapbox", + "toml-test-data", + "toml-test-harness", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..26929ff --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,156 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.64.0" +name = "toml_edit" +version = "0.19.14" +authors = [ + "Andronik Ordian ", + "Ed Page ", +] +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "Cargo.lock", + "LICENSE*", + "README.md", + "benches/**/*", + "examples/**/*", + "tests/**/*", +] +description = "Yet another format-preserving TOML parser." +readme = "README.md" +keywords = [ + "encoding", + "toml", +] +categories = [ + "encoding", + "parser-implementations", + "parsing", + "config", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/toml-rs/toml" + +[package.metadata.docs.rs] +features = ["serde"] +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[package.metadata.release] +tag-name = "v{{version}}" + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{version}}" +search = "Unreleased" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = "...{{tag_name}}" +search = '\.\.\.HEAD' + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{date}}" +search = "ReleaseDate" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +## [Unreleased] - ReleaseDate +""" +search = "" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +[Unreleased]: https://github.com/toml-rs/toml/compare/{{tag_name}}...HEAD""" +search = "" + +[[example]] +name = "visit" +test = true + +[[test]] +name = "decoder_compliance" +harness = false + +[[test]] +name = "encoder_compliance" +harness = false + +[[test]] +name = "invalid" +harness = false + +[dependencies.indexmap] +version = "2.0.0" +features = ["std"] + +[dependencies.kstring] +version = "2.0.0" +features = ["max_inline"] +optional = true + +[dependencies.serde] +version = "1.0.145" +optional = true + +[dependencies.serde_spanned] +version = "0.6.3" +features = ["serde"] +optional = true + +[dependencies.toml_datetime] +version = "0.6.3" + +[dependencies.winnow] +version = "0.5.0" + +[dev-dependencies.libtest-mimic] +version = "0.6.0" + +[dev-dependencies.serde_json] +version = "1.0.96" + +[dev-dependencies.snapbox] +version = "0.4.11" +features = ["harness"] + +[dev-dependencies.toml-test-data] +version = "1.3.0" + +[dev-dependencies.toml-test-harness] +version = "0.4.3" + +[features] +default = [] +perf = ["dep:kstring"] +serde = [ + "dep:serde", + "toml_datetime/serde", + "dep:serde_spanned", +] +unbounded = [] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..cda679f --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,68 @@ +[package] +name = "toml_edit" +version = "0.19.14" +keywords = ["encoding", "toml"] +categories = ["encoding", "parser-implementations", "parsing", "config"] +description = "Yet another format-preserving TOML parser." +authors = ["Andronik Ordian ", "Ed Page "] +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +features = ["serde"] + +[package.metadata.release] +tag-name = "v{{version}}" +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/toml-rs/toml/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = [] +perf = ["dep:kstring"] +serde = ["dep:serde", "toml_datetime/serde", "dep:serde_spanned"] +# Provide a method disable_recursion_limit to parse arbitrarily deep structures +# without any consideration for overflowing the stack. Additionally you will +# need to be careful around other recursive operations on the parsed result +# which may overflow the stack after deserialization has completed, including, +# but not limited to, Display and Debug and Drop impls. +unbounded = [] + +[dependencies] +indexmap = { version = "2.0.0", features = ["std"] } +winnow = "0.5.0" +serde = { version = "1.0.145", optional = true } +kstring = { version = "2.0.0", features = ["max_inline"], optional = true } +toml_datetime = { version = "0.6.3", path = "../toml_datetime" } +serde_spanned = { version = "0.6.3", path = "../serde_spanned", features = ["serde"], optional = true } + +[dev-dependencies] +serde_json = "1.0.96" +toml-test-harness = "0.4.3" +toml-test-data = "1.3.0" +libtest-mimic = "0.6.0" +snapbox = { version = "0.4.11", features = ["harness"] } + +[[test]] +name = "decoder_compliance" +harness = false + +[[test]] +name = "encoder_compliance" +harness = false + +[[test]] +name = "invalid" +harness = false + +[[example]] +name = "visit" +test = true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b73c718 --- /dev/null +++ b/LICENSE @@ -0,0 +1,228 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--- + +MIT License + +Copyright (c) 2017 Andronik Ordian + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..b9e61a2 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Andronik Ordian + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..6a96df6 --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "toml_edit" +description: "Yet another format-preserving TOML parser." +third_party { + identifier { + type: "crates.io" + value: "https://crates.io/crates/toml_edit" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/toml_edit/toml_edit-0.19.14.crate" + } + version: "0.19.14" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2023 + month: 8 + day: 23 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..45dc4dd --- /dev/null +++ b/OWNERS @@ -0,0 +1 @@ +include platform/prebuilts/rust:master:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1c74ee --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# toml_edit + +[![Build Status](https://github.com/ordian/toml_edit/workflows/Continuous%20integration/badge.svg)](https://github.com/ordian/toml_edit/actions) +[![codecov](https://codecov.io/gh/ordian/toml_edit/branch/master/graph/badge.svg)](https://codecov.io/gh/ordian/toml_edit) +[![crates.io](https://img.shields.io/crates/v/toml_edit.svg)](https://crates.io/crates/toml_edit) +[![docs](https://docs.rs/toml_edit/badge.svg)](https://docs.rs/toml_edit) +[![Join the chat at https://gitter.im/toml_edit/Lobby](https://badges.gitter.im/a.svg)](https://gitter.im/toml_edit/Lobby) + + +This crate allows you to parse and modify toml +documents, while preserving comments, spaces *and +relative order* or items. + +`toml_edit` is primarily tailored for [cargo-edit](https://github.com/killercup/cargo-edit/) needs. + +## Example + +```rust +use toml_edit::{Document, value}; + +fn main() { + let toml = r#" +"hello" = 'toml!' # comment +['a'.b] + "#; + let mut doc = toml.parse::().expect("invalid doc"); + assert_eq!(doc.to_string(), toml); + // let's add a new key/value pair inside a.b: c = {d = "hello"} + doc["a"]["b"]["c"]["d"] = value("hello"); + // autoformat inline table a.b.c: { d = "hello" } + doc["a"]["b"]["c"].as_inline_table_mut().map(|t| t.fmt()); + let expected = r#" +"hello" = 'toml!' # comment +['a'.b] +c = { d = "hello" } + "#; + assert_eq!(doc.to_string(), expected); +} +``` + +## Limitations + +Things it does not preserve: + +* Scattered array of tables (tables are reordered by default, see [test]). +* Order of dotted keys, see [issue](https://github.com/ordian/toml_edit/issues/163). + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[test]: https://github.com/ordian/toml_edit/blob/f09bd5d075fdb7d2ef8d9bb3270a34506c276753/tests/test_valid.rs#L84 diff --git a/examples/visit.rs b/examples/visit.rs new file mode 100644 index 0000000..cd7f851 --- /dev/null +++ b/examples/visit.rs @@ -0,0 +1,284 @@ +//! Example for how to use `VisitMut` to iterate over a table. + +use std::collections::BTreeSet; +use toml_edit::visit::*; +use toml_edit::visit_mut::*; +use toml_edit::{Array, Document, InlineTable, Item, KeyMut, Table, Value}; + +/// This models the visit state for dependency keys in a `Cargo.toml`. +/// +/// Dependencies can be specified as: +/// +/// ```toml +/// [dependencies] +/// dep1 = "0.2" +/// +/// [build-dependencies] +/// dep2 = "0.3" +/// +/// [dev-dependencies] +/// dep3 = "0.4" +/// +/// [target.'cfg(windows)'.dependencies] +/// dep4 = "0.5" +/// +/// # and target build- and dev-dependencies +/// ``` +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum VisitState { + /// Represents the root of the table. + Root, + /// Represents "dependencies", "build-dependencies" or "dev-dependencies", or the target + /// forms of these. + Dependencies, + /// A table within dependencies. + SubDependencies, + /// Represents "target". + Target, + /// "target.[TARGET]". + TargetWithSpec, + /// Represents some other state. + Other, +} + +impl VisitState { + /// Figures out the next visit state, given the current state and the given key. + fn descend(self, key: &str) -> Self { + match (self, key) { + ( + VisitState::Root | VisitState::TargetWithSpec, + "dependencies" | "build-dependencies" | "dev-dependencies", + ) => VisitState::Dependencies, + (VisitState::Root, "target") => VisitState::Target, + (VisitState::Root | VisitState::TargetWithSpec, _) => VisitState::Other, + (VisitState::Target, _) => VisitState::TargetWithSpec, + (VisitState::Dependencies, _) => VisitState::SubDependencies, + (VisitState::SubDependencies, _) => VisitState::SubDependencies, + (VisitState::Other, _) => VisitState::Other, + } + } +} + +/// Collect the names of every dependency key. +#[derive(Debug)] +struct DependencyNameVisitor<'doc> { + state: VisitState, + names: BTreeSet<&'doc str>, +} + +impl<'doc> Visit<'doc> for DependencyNameVisitor<'doc> { + fn visit_table_like_kv(&mut self, key: &'doc str, node: &'doc Item) { + if self.state == VisitState::Dependencies { + self.names.insert(key); + } else { + // Since we're only interested in collecting the top-level keys right under + // [dependencies], don't recurse unconditionally. + + let old_state = self.state; + + // Figure out the next state given the key. + self.state = self.state.descend(key); + + // Recurse further into the document tree. + visit_table_like_kv(self, key, node); + + // Restore the old state after it's done. + self.state = old_state; + } + } +} + +/// Normalize all dependency tables into the format: +/// +/// ```toml +/// [dependencies] +/// dep = { version = "1.0", features = ["foo", "bar"], ... } +/// ``` +/// +/// leaving other tables untouched. +#[derive(Debug)] +struct NormalizeDependencyTablesVisitor { + state: VisitState, +} + +impl VisitMut for NormalizeDependencyTablesVisitor { + fn visit_table_mut(&mut self, node: &mut Table) { + visit_table_mut(self, node); + + // The conversion from regular tables into inline ones might leave some explicit parent + // tables hanging, so convert them to implicit. + if matches!(self.state, VisitState::Target | VisitState::TargetWithSpec) { + node.set_implicit(true); + } + } + + fn visit_table_like_kv_mut(&mut self, mut key: KeyMut<'_>, node: &mut Item) { + let old_state = self.state; + + // Figure out the next state given the key. + self.state = self.state.descend(key.get()); + + match self.state { + VisitState::Target | VisitState::TargetWithSpec | VisitState::Dependencies => { + // Top-level dependency row, or above: turn inline tables into regular ones. + if let Item::Value(Value::InlineTable(inline_table)) = node { + let inline_table = std::mem::replace(inline_table, InlineTable::new()); + let table = inline_table.into_table(); + key.fmt(); + *node = Item::Table(table); + } + } + VisitState::SubDependencies => { + // Individual dependency: turn regular tables into inline ones. + if let Item::Table(table) = node { + // Turn the table into an inline table. + let table = std::mem::replace(table, Table::new()); + let inline_table = table.into_inline_table(); + key.fmt(); + *node = Item::Value(Value::InlineTable(inline_table)); + } + } + _ => {} + } + + // Recurse further into the document tree. + visit_table_like_kv_mut(self, key, node); + + // Restore the old state after it's done. + self.state = old_state; + } + + fn visit_array_mut(&mut self, node: &mut Array) { + // Format any arrays within dependencies to be on the same line. + if matches!( + self.state, + VisitState::Dependencies | VisitState::SubDependencies + ) { + node.fmt(); + } + } +} + +/// This is the input provided to visit_mut_example. +static INPUT: &str = r#" +[package] +name = "my-package" + +[package.metadata.foo] +bar = 42 + +[dependencies] +atty = "0.2" +cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" } + +[dependencies.pretty_env_logger] +version = "0.4" +optional = true + +[target.'cfg(windows)'.dependencies] +fwdansi = "1.1.0" + +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3" +features = [ +"handleapi", +"jobapi", +] + +[target.'cfg(unix)'] +dev-dependencies = { miniz_oxide = "0.5" } + +[dev-dependencies.cargo-test-macro] +path = "crates/cargo-test-macro" + +[build-dependencies.flate2] +version = "0.4" +"#; + +/// This is the output produced by visit_mut_example. +#[cfg(test)] +static VISIT_MUT_OUTPUT: &str = r#" +[package] +name = "my-package" + +[package.metadata.foo] +bar = 42 + +[dependencies] +atty = "0.2" +cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" } +pretty_env_logger = { version = "0.4", optional = true } + +[target.'cfg(windows)'.dependencies] +fwdansi = "1.1.0" +winapi = { version = "0.3", features = ["handleapi", "jobapi"] } + +[target.'cfg(unix)'.dev-dependencies] +miniz_oxide = "0.5" + +[dev-dependencies] +cargo-test-macro = { path = "crates/cargo-test-macro" } + +[build-dependencies] +flate2 = { version = "0.4" } +"#; + +fn visit_example(document: &Document) -> BTreeSet<&str> { + let mut visitor = DependencyNameVisitor { + state: VisitState::Root, + names: BTreeSet::new(), + }; + + visitor.visit_document(document); + + visitor.names +} + +fn visit_mut_example(document: &mut Document) { + let mut visitor = NormalizeDependencyTablesVisitor { + state: VisitState::Root, + }; + + visitor.visit_document_mut(document); +} + +fn main() { + let mut document: Document = INPUT.parse().expect("input is valid TOML"); + + println!("** visit example"); + println!("{:?}", visit_example(&document)); + + println!("** visit_mut example"); + visit_mut_example(&mut document); + println!("{}", document); +} + +#[cfg(test)] +#[test] +fn visit_correct() { + let document: Document = INPUT.parse().expect("input is valid TOML"); + + let names = visit_example(&document); + let expected = vec![ + "atty", + "cargo-platform", + "pretty_env_logger", + "fwdansi", + "winapi", + "miniz_oxide", + "cargo-test-macro", + "flate2", + ] + .into_iter() + .collect(); + assert_eq!(names, expected); +} + +#[cfg(test)] +#[test] +fn visit_mut_correct() { + let mut document: Document = INPUT.parse().expect("input is valid TOML"); + + visit_mut_example(&mut document); + assert_eq!(format!("{}", document), VISIT_MUT_OUTPUT); +} diff --git a/src/array.rs b/src/array.rs new file mode 100644 index 0000000..045b451 --- /dev/null +++ b/src/array.rs @@ -0,0 +1,403 @@ +use std::iter::FromIterator; +use std::mem; + +use crate::repr::Decor; +use crate::value::{DEFAULT_LEADING_VALUE_DECOR, DEFAULT_VALUE_DECOR}; +use crate::{Item, RawString, Value}; + +/// Type representing a TOML array, +/// payload of the `Value::Array` variant's value +#[derive(Debug, Default, Clone)] +pub struct Array { + // `trailing` represents whitespaces, newlines + // and comments in an empty array or after the trailing comma + trailing: RawString, + trailing_comma: bool, + // prefix before `[` and suffix after `]` + decor: Decor, + pub(crate) span: Option>, + // always Vec + pub(crate) values: Vec, +} + +/// An owned iterator type over `Table`'s key/value pairs. +pub type ArrayIntoIter = Box>; +/// An iterator type over `Array`'s values. +pub type ArrayIter<'a> = Box + 'a>; +/// An iterator type over `Array`'s values. +pub type ArrayIterMut<'a> = Box + 'a>; + +/// Constructors +/// +/// See also `FromIterator` +impl Array { + /// Create an empty `Array` + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// ``` + pub fn new() -> Self { + Default::default() + } + + pub(crate) fn with_vec(values: Vec) -> Self { + Self { + values, + ..Default::default() + } + } +} + +/// Formatting +impl Array { + /// Auto formats the array. + pub fn fmt(&mut self) { + decorate_array(self); + } + + /// Set whether the array will use a trailing comma + pub fn set_trailing_comma(&mut self, yes: bool) { + self.trailing_comma = yes; + } + + /// Whether the array will use a trailing comma + pub fn trailing_comma(&self) -> bool { + self.trailing_comma + } + + /// Set whitespace after last element + pub fn set_trailing(&mut self, trailing: impl Into) { + self.trailing = trailing.into(); + } + + /// Whitespace after last element + pub fn trailing(&self) -> &RawString { + &self.trailing + } + + /// Returns the surrounding whitespace + pub fn decor_mut(&mut self) -> &mut Decor { + &mut self.decor + } + + /// Returns the surrounding whitespace + pub fn decor(&self) -> &Decor { + &self.decor + } + + /// Returns the location within the original document + pub(crate) fn span(&self) -> Option> { + self.span.clone() + } + + pub(crate) fn despan(&mut self, input: &str) { + self.span = None; + self.decor.despan(input); + self.trailing.despan(input); + for value in &mut self.values { + value.despan(input); + } + } +} + +impl Array { + /// Returns an iterator over all values. + pub fn iter(&self) -> ArrayIter<'_> { + Box::new(self.values.iter().filter_map(Item::as_value)) + } + + /// Returns an iterator over all values. + pub fn iter_mut(&mut self) -> ArrayIterMut<'_> { + Box::new(self.values.iter_mut().filter_map(Item::as_value_mut)) + } + + /// Returns the length of the underlying Vec. + /// + /// In some rare cases, placeholder elements will exist. For a more accurate count, call + /// `a.iter().count()` + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// arr.push(1); + /// arr.push("foo"); + /// assert_eq!(arr.len(), 2); + /// ``` + pub fn len(&self) -> usize { + self.values.len() + } + + /// Return true iff `self.len() == 0`. + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// assert!(arr.is_empty()); + /// + /// arr.push(1); + /// arr.push("foo"); + /// assert!(! arr.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Clears the array, removing all values. Keeps the allocated memory for reuse. + pub fn clear(&mut self) { + self.values.clear() + } + + /// Returns a reference to the value at the given index, or `None` if the index is out of + /// bounds. + pub fn get(&self, index: usize) -> Option<&Value> { + self.values.get(index).and_then(Item::as_value) + } + + /// Returns a reference to the value at the given index, or `None` if the index is out of + /// bounds. + pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> { + self.values.get_mut(index).and_then(Item::as_value_mut) + } + + /// Appends a new value to the end of the array, applying default formatting to it. + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// arr.push(1); + /// arr.push("foo"); + /// ``` + pub fn push>(&mut self, v: V) { + self.value_op(v.into(), true, |items, value| { + items.push(Item::Value(value)) + }) + } + + /// Appends a new, already formatted value to the end of the array. + /// + /// # Examples + /// + /// ```rust + /// let formatted_value = "'literal'".parse::().unwrap(); + /// let mut arr = toml_edit::Array::new(); + /// arr.push_formatted(formatted_value); + /// ``` + pub fn push_formatted(&mut self, v: Value) { + self.values.push(Item::Value(v)); + } + + /// Inserts an element at the given position within the array, applying default formatting to + /// it and shifting all values after it to the right. + /// + /// # Panics + /// + /// Panics if `index > len`. + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// arr.push(1); + /// arr.push("foo"); + /// + /// arr.insert(0, "start"); + /// ``` + pub fn insert>(&mut self, index: usize, v: V) { + self.value_op(v.into(), true, |items, value| { + items.insert(index, Item::Value(value)) + }) + } + + /// Inserts an already formatted value at the given position within the array, shifting all + /// values after it to the right. + /// + /// # Panics + /// + /// Panics if `index > len`. + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// arr.push(1); + /// arr.push("foo"); + /// + /// let formatted_value = "'start'".parse::().unwrap(); + /// arr.insert_formatted(0, formatted_value); + /// ``` + pub fn insert_formatted(&mut self, index: usize, v: Value) { + self.values.insert(index, Item::Value(v)) + } + + /// Replaces the element at the given position within the array, preserving existing formatting. + /// + /// # Panics + /// + /// Panics if `index >= len`. + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// arr.push(1); + /// arr.push("foo"); + /// + /// arr.replace(0, "start"); + /// ``` + pub fn replace>(&mut self, index: usize, v: V) -> Value { + // Read the existing value's decor and preserve it. + let existing_decor = self + .get(index) + .unwrap_or_else(|| panic!("index {} out of bounds (len = {})", index, self.len())) + .decor(); + let mut value = v.into(); + *value.decor_mut() = existing_decor.clone(); + self.replace_formatted(index, value) + } + + /// Replaces the element at the given position within the array with an already formatted value. + /// + /// # Panics + /// + /// Panics if `index >= len`. + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// arr.push(1); + /// arr.push("foo"); + /// + /// let formatted_value = "'start'".parse::().unwrap(); + /// arr.replace_formatted(0, formatted_value); + /// ``` + pub fn replace_formatted(&mut self, index: usize, v: Value) -> Value { + match mem::replace(&mut self.values[index], Item::Value(v)) { + Item::Value(old_value) => old_value, + x => panic!("non-value item {:?} in an array", x), + } + } + + /// Removes the value at the given index. + /// + /// # Examples + /// + /// ```rust + /// let mut arr = toml_edit::Array::new(); + /// arr.push(1); + /// arr.push("foo"); + /// + /// arr.remove(0); + /// assert_eq!(arr.len(), 1); + /// ``` + pub fn remove(&mut self, index: usize) -> Value { + let removed = self.values.remove(index); + match removed { + Item::Value(v) => v, + x => panic!("non-value item {:?} in an array", x), + } + } + + /// Retains only the values specified by the `keep` predicate. + /// + /// In other words, remove all values for which `keep(&value)` returns `false`. + /// + /// This method operates in place, visiting each element exactly once in the + /// original order, and preserves the order of the retained elements. + pub fn retain(&mut self, mut keep: F) + where + F: FnMut(&Value) -> bool, + { + self.values + .retain(|item| item.as_value().map(&mut keep).unwrap_or(false)); + } + + fn value_op( + &mut self, + v: Value, + decorate: bool, + op: impl FnOnce(&mut Vec, Value) -> T, + ) -> T { + let mut value = v; + if !self.is_empty() && decorate { + value.decorate(" ", ""); + } else if decorate { + value.decorate("", ""); + } + op(&mut self.values, value) + } +} + +impl std::fmt::Display for Array { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + crate::encode::Encode::encode(self, f, None, ("", "")) + } +} + +impl> Extend for Array { + fn extend>(&mut self, iter: T) { + for value in iter { + self.push_formatted(value.into()); + } + } +} + +impl> FromIterator for Array { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let v = iter.into_iter().map(|a| Item::Value(a.into())); + Array { + values: v.collect(), + ..Default::default() + } + } +} + +impl IntoIterator for Array { + type Item = Value; + type IntoIter = ArrayIntoIter; + + fn into_iter(self) -> Self::IntoIter { + Box::new( + self.values + .into_iter() + .filter(|v| v.is_value()) + .map(|v| v.into_value().unwrap()), + ) + } +} + +impl<'s> IntoIterator for &'s Array { + type Item = &'s Value; + type IntoIter = ArrayIter<'s>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +fn decorate_array(array: &mut Array) { + for (i, value) in array + .values + .iter_mut() + .filter_map(Item::as_value_mut) + .enumerate() + { + // [value1, value2, value3] + if i == 0 { + value.decorate(DEFAULT_LEADING_VALUE_DECOR.0, DEFAULT_LEADING_VALUE_DECOR.1); + } else { + value.decorate(DEFAULT_VALUE_DECOR.0, DEFAULT_VALUE_DECOR.1); + } + } + // Since everything is now on the same line, remove trailing commas and whitespace. + array.set_trailing_comma(false); + array.set_trailing(""); +} diff --git a/src/array_of_tables.rs b/src/array_of_tables.rs new file mode 100644 index 0000000..c4d7194 --- /dev/null +++ b/src/array_of_tables.rs @@ -0,0 +1,166 @@ +use std::iter::FromIterator; + +use crate::{Array, Item, Table}; + +/// Type representing a TOML array of tables +#[derive(Clone, Debug, Default)] +pub struct ArrayOfTables { + // Always Vec, just `Item` to make `Index` work + pub(crate) span: Option>, + pub(crate) values: Vec, +} + +/// Constructors +/// +/// See also `FromIterator` +impl ArrayOfTables { + /// Creates an empty array of tables. + pub fn new() -> Self { + Default::default() + } +} + +/// Formatting +impl ArrayOfTables { + /// Convert to an inline array + pub fn into_array(mut self) -> Array { + for value in self.values.iter_mut() { + value.make_value(); + } + let mut a = Array::with_vec(self.values); + a.fmt(); + a + } + + /// Returns the location within the original document + pub(crate) fn span(&self) -> Option> { + self.span.clone() + } + + pub(crate) fn despan(&mut self, input: &str) { + self.span = None; + for value in &mut self.values { + value.despan(input); + } + } +} + +impl ArrayOfTables { + /// Returns an iterator over tables. + pub fn iter(&self) -> ArrayOfTablesIter<'_> { + Box::new(self.values.iter().filter_map(Item::as_table)) + } + + /// Returns an iterator over tables. + pub fn iter_mut(&mut self) -> ArrayOfTablesIterMut<'_> { + Box::new(self.values.iter_mut().filter_map(Item::as_table_mut)) + } + + /// Returns the length of the underlying Vec. + /// To get the actual number of items use `a.iter().count()`. + pub fn len(&self) -> usize { + self.values.len() + } + + /// Returns true iff `self.len() == 0`. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Removes all the tables. + pub fn clear(&mut self) { + self.values.clear() + } + + /// Returns an optional reference to the table. + pub fn get(&self, index: usize) -> Option<&Table> { + self.values.get(index).and_then(Item::as_table) + } + + /// Returns an optional mutable reference to the table. + pub fn get_mut(&mut self, index: usize) -> Option<&mut Table> { + self.values.get_mut(index).and_then(Item::as_table_mut) + } + + /// Appends a table to the array. + pub fn push(&mut self, table: Table) { + self.values.push(Item::Table(table)); + } + + /// Removes a table with the given index. + pub fn remove(&mut self, index: usize) { + self.values.remove(index); + } + + /// Retains only the elements specified by the `keep` predicate. + /// + /// In other words, remove all tables for which `keep(&table)` returns `false`. + /// + /// This method operates in place, visiting each element exactly once in the + /// original order, and preserves the order of the retained elements. + pub fn retain(&mut self, mut keep: F) + where + F: FnMut(&Table) -> bool, + { + self.values + .retain(|item| item.as_table().map(&mut keep).unwrap_or(false)); + } +} + +/// An iterator type over `ArrayOfTables`'s values. +pub type ArrayOfTablesIter<'a> = Box + 'a>; +/// An iterator type over `ArrayOfTables`'s values. +pub type ArrayOfTablesIterMut<'a> = Box + 'a>; +/// An iterator type over `ArrayOfTables`'s values. +pub type ArrayOfTablesIntoIter = Box>; + +impl Extend for ArrayOfTables { + fn extend>(&mut self, iter: T) { + for value in iter { + self.push(value); + } + } +} + +impl FromIterator
for ArrayOfTables { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let v = iter.into_iter().map(Item::Table); + ArrayOfTables { + values: v.collect(), + span: None, + } + } +} + +impl IntoIterator for ArrayOfTables { + type Item = Table; + type IntoIter = ArrayOfTablesIntoIter; + + fn into_iter(self) -> Self::IntoIter { + Box::new( + self.values + .into_iter() + .filter(|v| v.is_table()) + .map(|v| v.into_table().unwrap()), + ) + } +} + +impl<'s> IntoIterator for &'s ArrayOfTables { + type Item = &'s Table; + type IntoIter = ArrayOfTablesIter<'s>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl std::fmt::Display for ArrayOfTables { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // HACK: Without the header, we don't really have a proper way of printing this + self.clone().into_array().fmt(f) + } +} diff --git a/src/de/array.rs b/src/de/array.rs new file mode 100644 index 0000000..adc5401 --- /dev/null +++ b/src/de/array.rs @@ -0,0 +1,97 @@ +use crate::de::Error; + +pub(crate) struct ArrayDeserializer { + input: Vec, + span: Option>, +} + +impl ArrayDeserializer { + pub(crate) fn new(input: Vec, span: Option>) -> Self { + Self { input, span } + } +} + +// Note: this is wrapped by `ValueDeserializer` and any trait methods +// implemented here need to be wrapped there +impl<'de> serde::Deserializer<'de> for ArrayDeserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_seq(ArraySeqAccess::new(self.input)) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if serde_spanned::__unstable::is_spanned(name, fields) { + if let Some(span) = self.span.clone() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + + self.deserialize_any(visitor) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map option unit newtype_struct + ignored_any unit_struct tuple_struct tuple enum identifier + } +} + +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for ArrayDeserializer { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl crate::Array { + pub(crate) fn into_deserializer(self) -> ArrayDeserializer { + ArrayDeserializer::new(self.values, self.span) + } +} + +impl crate::ArrayOfTables { + pub(crate) fn into_deserializer(self) -> ArrayDeserializer { + ArrayDeserializer::new(self.values, self.span) + } +} + +pub(crate) struct ArraySeqAccess { + iter: std::vec::IntoIter, +} + +impl ArraySeqAccess { + pub(crate) fn new(input: Vec) -> Self { + Self { + iter: input.into_iter(), + } + } +} + +impl<'de> serde::de::SeqAccess<'de> for ArraySeqAccess { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: serde::de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some(v) => seed + .deserialize(crate::de::ValueDeserializer::new(v)) + .map(Some), + None => Ok(None), + } + } +} diff --git a/src/de/datetime.rs b/src/de/datetime.rs new file mode 100644 index 0000000..14de28b --- /dev/null +++ b/src/de/datetime.rs @@ -0,0 +1,43 @@ +use serde::de::value::BorrowedStrDeserializer; +use serde::de::IntoDeserializer; + +use crate::de::Error; + +pub(crate) struct DatetimeDeserializer { + date: Option, +} + +impl DatetimeDeserializer { + pub(crate) fn new(date: crate::Datetime) -> Self { + Self { date: Some(date) } + } +} + +impl<'de> serde::de::MapAccess<'de> for DatetimeDeserializer { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Error> + where + K: serde::de::DeserializeSeed<'de>, + { + if self.date.is_some() { + seed.deserialize(BorrowedStrDeserializer::new( + toml_datetime::__unstable::FIELD, + )) + .map(Some) + } else { + Ok(None) + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: serde::de::DeserializeSeed<'de>, + { + if let Some(date) = self.date.take() { + seed.deserialize(date.to_string().into_deserializer()) + } else { + panic!("next_value_seed called before next_key_seed") + } + } +} diff --git a/src/de/key.rs b/src/de/key.rs new file mode 100644 index 0000000..3da41df --- /dev/null +++ b/src/de/key.rs @@ -0,0 +1,140 @@ +use serde::de::IntoDeserializer; + +use super::Error; + +pub(crate) struct KeyDeserializer { + span: Option>, + key: crate::InternalString, +} + +impl KeyDeserializer { + pub(crate) fn new(key: crate::InternalString, span: Option>) -> Self { + KeyDeserializer { span, key } + } +} + +impl<'de> serde::de::IntoDeserializer<'de, Error> for KeyDeserializer { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl<'de> serde::de::Deserializer<'de> for KeyDeserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.key.into_deserializer().deserialize_any(visitor) + } + + fn deserialize_enum( + self, + name: &str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let _ = name; + let _ = variants; + visitor.visit_enum(self) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if serde_spanned::__unstable::is_spanned(name, fields) { + if let Some(span) = self.span.clone() { + return visitor.visit_map(super::SpannedDeserializer::new(self.key.as_str(), span)); + } + } + self.deserialize_any(visitor) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map option unit newtype_struct + ignored_any unit_struct tuple_struct tuple identifier + } +} + +impl<'de> serde::de::EnumAccess<'de> for KeyDeserializer { + type Error = super::Error; + type Variant = UnitOnly; + + fn variant_seed(self, seed: T) -> Result<(T::Value, Self::Variant), Self::Error> + where + T: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self).map(unit_only) + } +} + +pub(crate) struct UnitOnly { + marker: std::marker::PhantomData, +} + +fn unit_only(t: T) -> (T, UnitOnly) { + ( + t, + UnitOnly { + marker: std::marker::PhantomData, + }, + ) +} + +impl<'de, E> serde::de::VariantAccess<'de> for UnitOnly +where + E: serde::de::Error, +{ + type Error = E; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where + T: serde::de::DeserializeSeed<'de>, + { + Err(serde::de::Error::invalid_type( + serde::de::Unexpected::UnitVariant, + &"newtype variant", + )) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + Err(serde::de::Error::invalid_type( + serde::de::Unexpected::UnitVariant, + &"tuple variant", + )) + } + + fn struct_variant( + self, + _fields: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + Err(serde::de::Error::invalid_type( + serde::de::Unexpected::UnitVariant, + &"struct variant", + )) + } +} diff --git a/src/de/mod.rs b/src/de/mod.rs new file mode 100644 index 0000000..09ea120 --- /dev/null +++ b/src/de/mod.rs @@ -0,0 +1,289 @@ +//! Deserializing TOML into Rust structures. +//! +//! This module contains all the Serde support for deserializing TOML documents into Rust structures. + +use serde::de::DeserializeOwned; + +mod array; +mod datetime; +mod key; +mod spanned; +mod table; +mod table_enum; +mod value; + +use array::ArrayDeserializer; +use datetime::DatetimeDeserializer; +use key::KeyDeserializer; +use spanned::SpannedDeserializer; +use table::TableMapAccess; +use table_enum::TableEnumDeserializer; + +pub use value::ValueDeserializer; + +/// Errors that can occur when deserializing a type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Error { + inner: crate::TomlError, +} + +impl Error { + pub(crate) fn custom(msg: T, span: Option>) -> Self + where + T: std::fmt::Display, + { + Error { + inner: crate::TomlError::custom(msg.to_string(), span), + } + } + + /// Add key while unwinding + pub fn add_key(&mut self, key: String) { + self.inner.add_key(key) + } + + /// What went wrong + pub fn message(&self) -> &str { + self.inner.message() + } + + /// The start/end index into the original document where the error occurred + pub fn span(&self) -> Option> { + self.inner.span() + } + + pub(crate) fn set_span(&mut self, span: Option>) { + self.inner.set_span(span); + } +} + +impl serde::de::Error for Error { + fn custom(msg: T) -> Self + where + T: std::fmt::Display, + { + Error::custom(msg, None) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +impl From for Error { + fn from(e: crate::TomlError) -> Error { + Self { inner: e } + } +} + +impl From for crate::TomlError { + fn from(e: Error) -> crate::TomlError { + e.inner + } +} + +impl std::error::Error for Error {} + +/// Convert a value into `T`. +pub fn from_str(s: &'_ str) -> Result +where + T: DeserializeOwned, +{ + let de = s.parse::()?; + T::deserialize(de) +} + +/// Convert a value into `T`. +pub fn from_slice(s: &'_ [u8]) -> Result +where + T: DeserializeOwned, +{ + let s = std::str::from_utf8(s).map_err(|e| Error::custom(e, None))?; + from_str(s) +} + +/// Convert a document into `T`. +pub fn from_document(d: crate::Document) -> Result +where + T: DeserializeOwned, +{ + let deserializer = Deserializer::new(d); + T::deserialize(deserializer) +} + +/// Deserialization for TOML [documents][crate::Document]. +pub struct Deserializer { + input: crate::Document, +} + +impl Deserializer { + /// Deserialization implementation for TOML. + pub fn new(input: crate::Document) -> Self { + Self { input } + } +} + +impl std::str::FromStr for Deserializer { + type Err = Error; + + /// Parses a document from a &str + fn from_str(s: &str) -> Result { + let d = crate::parser::parse_document(s).map_err(Error::from)?; + Ok(Self::new(d)) + } +} + +// Note: this is wrapped by `toml::de::Deserializer` and any trait methods +// implemented here need to be wrapped there +impl<'de> serde::Deserializer<'de> for Deserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let original = self.input.original; + self.input + .root + .into_deserializer() + .deserialize_any(visitor) + .map_err(|mut e: Self::Error| { + e.inner.set_original(original); + e + }) + } + + // `None` is interpreted as a missing field so be sure to implement `Some` + // as a present field. + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let original = self.input.original; + self.input + .root + .into_deserializer() + .deserialize_option(visitor) + .map_err(|mut e: Self::Error| { + e.inner.set_original(original); + e + }) + } + + fn deserialize_newtype_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let original = self.input.original; + self.input + .root + .into_deserializer() + .deserialize_newtype_struct(name, visitor) + .map_err(|mut e: Self::Error| { + e.inner.set_original(original); + e + }) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let original = self.input.original; + self.input + .root + .into_deserializer() + .deserialize_struct(name, fields, visitor) + .map_err(|mut e: Self::Error| { + e.inner.set_original(original); + e + }) + } + + // Called when the type to deserialize is an enum, as opposed to a field in the type. + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let original = self.input.original; + self.input + .root + .into_deserializer() + .deserialize_enum(name, variants, visitor) + .map_err(|mut e: Self::Error| { + e.inner.set_original(original); + e + }) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map unit + ignored_any unit_struct tuple_struct tuple identifier + } +} + +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for Deserializer { + type Deserializer = Deserializer; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::Document { + type Deserializer = Deserializer; + + fn into_deserializer(self) -> Self::Deserializer { + Deserializer::new(self) + } +} + +pub(crate) fn validate_struct_keys( + table: &crate::table::KeyValuePairs, + fields: &'static [&'static str], +) -> Result<(), Error> { + let extra_fields = table + .iter() + .filter_map(|(key, val)| { + if !fields.contains(&key.as_str()) { + Some(val.clone()) + } else { + None + } + }) + .collect::>(); + + if extra_fields.is_empty() { + Ok(()) + } else { + Err(Error::custom( + format!( + "unexpected keys in table: {}, available keys: {}", + extra_fields + .iter() + .map(|k| k.key.get()) + .collect::>() + .join(", "), + fields.join(", "), + ), + extra_fields[0].key.span(), + )) + } +} diff --git a/src/de/spanned.rs b/src/de/spanned.rs new file mode 100644 index 0000000..7ce5864 --- /dev/null +++ b/src/de/spanned.rs @@ -0,0 +1,70 @@ +use serde::de::value::BorrowedStrDeserializer; +use serde::de::IntoDeserializer as _; + +use super::Error; + +pub(crate) struct SpannedDeserializer<'de, T: serde::de::IntoDeserializer<'de, Error>> { + phantom_data: std::marker::PhantomData<&'de ()>, + start: Option, + end: Option, + value: Option, +} + +impl<'de, T> SpannedDeserializer<'de, T> +where + T: serde::de::IntoDeserializer<'de, Error>, +{ + pub(crate) fn new(value: T, span: std::ops::Range) -> Self { + Self { + phantom_data: Default::default(), + start: Some(span.start), + end: Some(span.end), + value: Some(value), + } + } +} + +impl<'de, T> serde::de::MapAccess<'de> for SpannedDeserializer<'de, T> +where + T: serde::de::IntoDeserializer<'de, Error>, +{ + type Error = Error; + fn next_key_seed(&mut self, seed: K) -> Result, Error> + where + K: serde::de::DeserializeSeed<'de>, + { + if self.start.is_some() { + seed.deserialize(BorrowedStrDeserializer::new( + serde_spanned::__unstable::START_FIELD, + )) + .map(Some) + } else if self.end.is_some() { + seed.deserialize(BorrowedStrDeserializer::new( + serde_spanned::__unstable::END_FIELD, + )) + .map(Some) + } else if self.value.is_some() { + seed.deserialize(BorrowedStrDeserializer::new( + serde_spanned::__unstable::VALUE_FIELD, + )) + .map(Some) + } else { + Ok(None) + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: serde::de::DeserializeSeed<'de>, + { + if let Some(start) = self.start.take() { + seed.deserialize(start.into_deserializer()) + } else if let Some(end) = self.end.take() { + seed.deserialize(end.into_deserializer()) + } else if let Some(value) = self.value.take() { + seed.deserialize(value.into_deserializer()) + } else { + panic!("next_value_seed called before next_key_seed") + } + } +} diff --git a/src/de/table.rs b/src/de/table.rs new file mode 100644 index 0000000..0b6183e --- /dev/null +++ b/src/de/table.rs @@ -0,0 +1,213 @@ +use serde::de::IntoDeserializer; + +use crate::de::Error; + +pub(crate) struct TableDeserializer { + span: Option>, + items: crate::table::KeyValuePairs, +} + +// Note: this is wrapped by `Deserializer` and `ValueDeserializer` and any trait methods +// implemented here need to be wrapped there +impl<'de> serde::Deserializer<'de> for TableDeserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_map(crate::de::TableMapAccess::new(self)) + } + + // `None` is interpreted as a missing field so be sure to implement `Some` + // as a present field. + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if serde_spanned::__unstable::is_spanned(name, fields) { + if let Some(span) = self.span.clone() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + + self.deserialize_any(visitor) + } + + // Called when the type to deserialize is an enum, as opposed to a field in the type. + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.items.is_empty() { + Err(crate::de::Error::custom( + "wanted exactly 1 element, found 0 elements", + self.span, + )) + } else if self.items.len() != 1 { + Err(crate::de::Error::custom( + "wanted exactly 1 element, more than 1 element", + self.span, + )) + } else { + visitor.visit_enum(crate::de::TableMapAccess::new(self)) + } + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map unit + ignored_any unit_struct tuple_struct tuple identifier + } +} + +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for TableDeserializer { + type Deserializer = TableDeserializer; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl crate::Table { + pub(crate) fn into_deserializer(self) -> TableDeserializer { + TableDeserializer { + span: self.span(), + items: self.items, + } + } +} + +impl crate::InlineTable { + pub(crate) fn into_deserializer(self) -> TableDeserializer { + TableDeserializer { + span: self.span(), + items: self.items, + } + } +} + +pub(crate) struct TableMapAccess { + iter: indexmap::map::IntoIter, + span: Option>, + value: Option<(crate::InternalString, crate::Item)>, +} + +impl TableMapAccess { + pub(crate) fn new(input: TableDeserializer) -> Self { + Self { + iter: input.items.into_iter(), + span: input.span, + value: None, + } + } +} + +impl<'de> serde::de::MapAccess<'de> for TableMapAccess { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: serde::de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some((k, v)) => { + let ret = seed + .deserialize(super::KeyDeserializer::new(k, v.key.span())) + .map(Some) + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(v.key.span()); + } + e + }); + self.value = Some((v.key.into(), v.value)); + ret + } + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: serde::de::DeserializeSeed<'de>, + { + match self.value.take() { + Some((k, v)) => { + let span = v.span(); + seed.deserialize(crate::de::ValueDeserializer::new(v)) + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(span); + } + e.add_key(k.as_str().to_owned()); + e + }) + } + None => { + panic!("no more values in next_value_seed, internal error in ValueDeserializer") + } + } + } +} + +impl<'de> serde::de::EnumAccess<'de> for TableMapAccess { + type Error = Error; + type Variant = super::TableEnumDeserializer; + + fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: serde::de::DeserializeSeed<'de>, + { + let (key, value) = match self.iter.next() { + Some(pair) => pair, + None => { + return Err(Error::custom( + "expected table with exactly 1 entry, found empty table", + self.span, + )); + } + }; + + let val = seed + .deserialize(key.into_deserializer()) + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(value.key.span()); + } + e + })?; + + let variant = super::TableEnumDeserializer::new(value.value); + + Ok((val, variant)) + } +} diff --git a/src/de/table_enum.rs b/src/de/table_enum.rs new file mode 100644 index 0000000..197ad6e --- /dev/null +++ b/src/de/table_enum.rs @@ -0,0 +1,160 @@ +use crate::de::Error; + +/// Deserializes table values into enum variants. +pub(crate) struct TableEnumDeserializer { + value: crate::Item, +} + +impl TableEnumDeserializer { + pub(crate) fn new(value: crate::Item) -> Self { + TableEnumDeserializer { value } + } +} + +impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { + type Error = Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + match self.value { + crate::Item::Table(values) => { + if values.is_empty() { + Ok(()) + } else { + Err(Error::custom("expected empty table", values.span())) + } + } + crate::Item::Value(crate::Value::InlineTable(values)) => { + if values.is_empty() { + Ok(()) + } else { + Err(Error::custom("expected empty table", values.span())) + } + } + e => Err(Error::custom( + format!("expected table, found {}", e.type_name()), + e.span(), + )), + } + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(super::ValueDeserializer::new(self.value)) + } + + fn tuple_variant(self, len: usize, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.value { + crate::Item::Table(values) => { + let values_span = values.span(); + let tuple_values = values + .items + .into_iter() + .enumerate() + .map( + |(index, (_, value))| match value.key.get().parse::() { + Ok(key_index) if key_index == index => Ok(value.value), + Ok(_) | Err(_) => Err(Error::custom( + format!( + "expected table key `{}`, but was `{}`", + index, + value.key.get() + ), + value.key.span(), + )), + }, + ) + // Fold all values into a `Vec`, or return the first error. + .fold(Ok(Vec::with_capacity(len)), |result, value_result| { + result.and_then(move |mut tuple_values| match value_result { + Ok(value) => { + tuple_values.push(value); + Ok(tuple_values) + } + // `Result` to `Result, Self::Error>` + Err(e) => Err(e), + }) + })?; + + if tuple_values.len() == len { + serde::de::Deserializer::deserialize_seq( + super::ArrayDeserializer::new(tuple_values, values_span), + visitor, + ) + } else { + Err(Error::custom( + format!("expected tuple with length {}", len), + values_span, + )) + } + } + crate::Item::Value(crate::Value::InlineTable(values)) => { + let values_span = values.span(); + let tuple_values = values + .items + .into_iter() + .enumerate() + .map( + |(index, (_, value))| match value.key.get().parse::() { + Ok(key_index) if key_index == index => Ok(value.value), + Ok(_) | Err(_) => Err(Error::custom( + format!( + "expected table key `{}`, but was `{}`", + index, + value.key.get() + ), + value.key.span(), + )), + }, + ) + // Fold all values into a `Vec`, or return the first error. + .fold(Ok(Vec::with_capacity(len)), |result, value_result| { + result.and_then(move |mut tuple_values| match value_result { + Ok(value) => { + tuple_values.push(value); + Ok(tuple_values) + } + // `Result` to `Result, Self::Error>` + Err(e) => Err(e), + }) + })?; + + if tuple_values.len() == len { + serde::de::Deserializer::deserialize_seq( + super::ArrayDeserializer::new(tuple_values, values_span), + visitor, + ) + } else { + Err(Error::custom( + format!("expected tuple with length {}", len), + values_span, + )) + } + } + e => Err(Error::custom( + format!("expected table, found {}", e.type_name()), + e.span(), + )), + } + } + + fn struct_variant( + self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + serde::de::Deserializer::deserialize_struct( + super::ValueDeserializer::new(self.value).with_struct_key_validation(), + "", // TODO: this should be the variant name + fields, + visitor, + ) + } +} diff --git a/src/de/value.rs b/src/de/value.rs new file mode 100644 index 0000000..3984287 --- /dev/null +++ b/src/de/value.rs @@ -0,0 +1,252 @@ +use serde::de::IntoDeserializer as _; + +use crate::de::DatetimeDeserializer; +use crate::de::Error; + +/// Deserialization implementation for TOML [values][crate::Value]. +/// +/// Can be creater either directly from TOML strings, using [`std::str::FromStr`], +/// or from parsed [values][crate::Value] using [`serde::de::IntoDeserializer::into_deserializer`]. +/// +/// # Example +/// +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct Config { +/// title: String, +/// owner: Owner, +/// } +/// +/// #[derive(Deserialize)] +/// struct Owner { +/// name: String, +/// } +/// +/// let value = r#"{ title = 'TOML Example', owner = { name = 'Lisa' } }"#; +/// let deserializer = value.parse::().unwrap(); +/// let config = Config::deserialize(deserializer).unwrap(); +/// assert_eq!(config.title, "TOML Example"); +/// assert_eq!(config.owner.name, "Lisa"); +/// ``` +pub struct ValueDeserializer { + input: crate::Item, + validate_struct_keys: bool, +} + +impl ValueDeserializer { + pub(crate) fn new(input: crate::Item) -> Self { + Self { + input, + validate_struct_keys: false, + } + } + + pub(crate) fn with_struct_key_validation(mut self) -> Self { + self.validate_struct_keys = true; + self + } +} + +// Note: this is wrapped by `toml::de::ValueDeserializer` and any trait methods +// implemented here need to be wrapped there +impl<'de> serde::Deserializer<'de> for ValueDeserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let span = self.input.span(); + match self.input { + crate::Item::None => visitor.visit_none(), + crate::Item::Value(crate::Value::String(v)) => visitor.visit_string(v.into_value()), + crate::Item::Value(crate::Value::Integer(v)) => visitor.visit_i64(v.into_value()), + crate::Item::Value(crate::Value::Float(v)) => visitor.visit_f64(v.into_value()), + crate::Item::Value(crate::Value::Boolean(v)) => visitor.visit_bool(v.into_value()), + crate::Item::Value(crate::Value::Datetime(v)) => { + visitor.visit_map(DatetimeDeserializer::new(v.into_value())) + } + crate::Item::Value(crate::Value::Array(v)) => { + v.into_deserializer().deserialize_any(visitor) + } + crate::Item::Value(crate::Value::InlineTable(v)) => { + v.into_deserializer().deserialize_any(visitor) + } + crate::Item::Table(v) => v.into_deserializer().deserialize_any(visitor), + crate::Item::ArrayOfTables(v) => v.into_deserializer().deserialize_any(visitor), + } + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(span); + } + e + }) + } + + // `None` is interpreted as a missing field so be sure to implement `Some` + // as a present field. + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let span = self.input.span(); + visitor.visit_some(self).map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(span); + } + e + }) + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let span = self.input.span(); + visitor + .visit_newtype_struct(self) + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(span); + } + e + }) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if serde_spanned::__unstable::is_spanned(name, fields) { + if let Some(span) = self.input.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + + if name == toml_datetime::__unstable::NAME && fields == [toml_datetime::__unstable::FIELD] { + let span = self.input.span(); + if let crate::Item::Value(crate::Value::Datetime(d)) = self.input { + return visitor + .visit_map(DatetimeDeserializer::new(d.into_value())) + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(span); + } + e + }); + } + } + + if self.validate_struct_keys { + let span = self.input.span(); + match &self.input { + crate::Item::Table(values) => super::validate_struct_keys(&values.items, fields), + crate::Item::Value(crate::Value::InlineTable(values)) => { + super::validate_struct_keys(&values.items, fields) + } + _ => Ok(()), + } + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(span); + } + e + })? + } + + self.deserialize_any(visitor) + } + + // Called when the type to deserialize is an enum, as opposed to a field in the type. + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let span = self.input.span(); + match self.input { + crate::Item::Value(crate::Value::String(v)) => { + visitor.visit_enum(v.into_value().into_deserializer()) + } + crate::Item::Value(crate::Value::InlineTable(v)) => { + if v.is_empty() { + Err(crate::de::Error::custom( + "wanted exactly 1 element, found 0 elements", + v.span(), + )) + } else if v.len() != 1 { + Err(crate::de::Error::custom( + "wanted exactly 1 element, more than 1 element", + v.span(), + )) + } else { + v.into_deserializer() + .deserialize_enum(name, variants, visitor) + } + } + crate::Item::Table(v) => v + .into_deserializer() + .deserialize_enum(name, variants, visitor), + e => Err(crate::de::Error::custom("wanted string or table", e.span())), + } + .map_err(|mut e: Self::Error| { + if e.span().is_none() { + e.set_span(span); + } + e + }) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map unit + ignored_any unit_struct tuple_struct tuple identifier + } +} + +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for ValueDeserializer { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::Value { + type Deserializer = ValueDeserializer; + + fn into_deserializer(self) -> Self::Deserializer { + ValueDeserializer::new(crate::Item::Value(self)) + } +} + +impl crate::Item { + pub(crate) fn into_deserializer(self) -> ValueDeserializer { + ValueDeserializer::new(self) + } +} + +impl std::str::FromStr for ValueDeserializer { + type Err = Error; + + /// Parses a value from a &str + fn from_str(s: &str) -> Result { + let v = crate::parser::parse_value(s).map_err(Error::from)?; + Ok(v.into_deserializer()) + } +} diff --git a/src/document.rs b/src/document.rs new file mode 100644 index 0000000..67dd293 --- /dev/null +++ b/src/document.rs @@ -0,0 +1,113 @@ +use std::str::FromStr; + +use crate::parser; +use crate::table::Iter; +use crate::{Item, RawString, Table}; + +/// Type representing a TOML document +#[derive(Debug, Clone)] +pub struct Document { + pub(crate) root: Item, + // Trailing comments and whitespaces + pub(crate) trailing: RawString, + pub(crate) original: Option, + pub(crate) span: Option>, +} + +impl Document { + /// Creates an empty document + pub fn new() -> Self { + Default::default() + } + + /// Returns a reference to the root item. + pub fn as_item(&self) -> &Item { + &self.root + } + + /// Returns a mutable reference to the root item. + pub fn as_item_mut(&mut self) -> &mut Item { + &mut self.root + } + + /// Returns a reference to the root table. + pub fn as_table(&self) -> &Table { + self.root.as_table().expect("root should always be a table") + } + + /// Returns a mutable reference to the root table. + pub fn as_table_mut(&mut self) -> &mut Table { + self.root + .as_table_mut() + .expect("root should always be a table") + } + + /// Returns an iterator over the root table. + pub fn iter(&self) -> Iter<'_> { + self.as_table().iter() + } + + /// Set whitespace after last element + pub fn set_trailing(&mut self, trailing: impl Into) { + self.trailing = trailing.into(); + } + + /// Whitespace after last element + pub fn trailing(&self) -> &RawString { + &self.trailing + } + + /// # Panics + /// + /// If run on on a `Document` not generated by the parser + pub(crate) fn despan(&mut self) { + self.span = None; + self.root.despan(self.original.as_deref().unwrap()); + self.trailing.despan(self.original.as_deref().unwrap()); + } +} + +impl Default for Document { + fn default() -> Self { + Self { + root: Item::Table(Table::with_pos(Some(0))), + trailing: Default::default(), + original: Default::default(), + span: Default::default(), + } + } +} + +impl FromStr for Document { + type Err = crate::TomlError; + + /// Parses a document from a &str + fn from_str(s: &str) -> Result { + let mut d = parser::parse_document(s)?; + d.despan(); + Ok(d) + } +} + +impl std::ops::Deref for Document { + type Target = Table; + + fn deref(&self) -> &Self::Target { + self.as_table() + } +} + +impl std::ops::DerefMut for Document { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_table_mut() + } +} + +impl From
for Document { + fn from(root: Table) -> Self { + Self { + root: Item::Table(root), + ..Default::default() + } + } +} diff --git a/src/encode.rs b/src/encode.rs new file mode 100644 index 0000000..9940f28 --- /dev/null +++ b/src/encode.rs @@ -0,0 +1,569 @@ +use std::borrow::Cow; +use std::fmt::{Display, Formatter, Result, Write}; + +use toml_datetime::*; + +use crate::document::Document; +use crate::inline_table::DEFAULT_INLINE_KEY_DECOR; +use crate::key::Key; +use crate::repr::{Formatted, Repr, ValueRepr}; +use crate::table::{DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_TABLE_DECOR}; +use crate::value::{ + DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR, +}; +use crate::{Array, InlineTable, Item, Table, Value}; + +pub(crate) trait Encode { + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result; +} + +impl Encode for Key { + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result { + let decor = self.decor(); + decor.prefix_encode(buf, input, default_decor.0)?; + + if let Some(input) = input { + let repr = self + .as_repr() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(self.default_repr())); + repr.encode(buf, input)?; + } else { + let repr = self.display_repr(); + write!(buf, "{}", repr)?; + }; + + decor.suffix_encode(buf, input, default_decor.1)?; + Ok(()) + } +} + +impl<'k> Encode for &'k [Key] { + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result { + for (i, key) in self.iter().enumerate() { + let first = i == 0; + let last = i + 1 == self.len(); + + let prefix = if first { + default_decor.0 + } else { + DEFAULT_KEY_PATH_DECOR.0 + }; + let suffix = if last { + default_decor.1 + } else { + DEFAULT_KEY_PATH_DECOR.1 + }; + + if !first { + write!(buf, ".")?; + } + key.encode(buf, input, (prefix, suffix))?; + } + Ok(()) + } +} + +impl<'k> Encode for &'k [&'k Key] { + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result { + for (i, key) in self.iter().enumerate() { + let first = i == 0; + let last = i + 1 == self.len(); + + let prefix = if first { + default_decor.0 + } else { + DEFAULT_KEY_PATH_DECOR.0 + }; + let suffix = if last { + default_decor.1 + } else { + DEFAULT_KEY_PATH_DECOR.1 + }; + + if !first { + write!(buf, ".")?; + } + key.encode(buf, input, (prefix, suffix))?; + } + Ok(()) + } +} + +impl Encode for Formatted +where + T: ValueRepr, +{ + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result { + let decor = self.decor(); + decor.prefix_encode(buf, input, default_decor.0)?; + + if let Some(input) = input { + let repr = self + .as_repr() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(self.default_repr())); + repr.encode(buf, input)?; + } else { + let repr = self.display_repr(); + write!(buf, "{}", repr)?; + }; + + decor.suffix_encode(buf, input, default_decor.1)?; + Ok(()) + } +} + +impl Encode for Array { + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result { + let decor = self.decor(); + decor.prefix_encode(buf, input, default_decor.0)?; + write!(buf, "[")?; + + for (i, elem) in self.iter().enumerate() { + let inner_decor; + if i == 0 { + inner_decor = DEFAULT_LEADING_VALUE_DECOR; + } else { + inner_decor = DEFAULT_VALUE_DECOR; + write!(buf, ",")?; + } + elem.encode(buf, input, inner_decor)?; + } + if self.trailing_comma() && !self.is_empty() { + write!(buf, ",")?; + } + + self.trailing().encode_with_default(buf, input, "")?; + write!(buf, "]")?; + decor.suffix_encode(buf, input, default_decor.1)?; + + Ok(()) + } +} + +impl Encode for InlineTable { + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result { + let decor = self.decor(); + decor.prefix_encode(buf, input, default_decor.0)?; + write!(buf, "{{")?; + self.preamble().encode_with_default(buf, input, "")?; + + let children = self.get_values(); + let len = children.len(); + for (i, (key_path, value)) in children.into_iter().enumerate() { + if i != 0 { + write!(buf, ",")?; + } + let inner_decor = if i == len - 1 { + DEFAULT_TRAILING_VALUE_DECOR + } else { + DEFAULT_VALUE_DECOR + }; + key_path + .as_slice() + .encode(buf, input, DEFAULT_INLINE_KEY_DECOR)?; + write!(buf, "=")?; + value.encode(buf, input, inner_decor)?; + } + + write!(buf, "}}")?; + decor.suffix_encode(buf, input, default_decor.1)?; + + Ok(()) + } +} + +impl Encode for Value { + fn encode( + &self, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), + ) -> Result { + match self { + Value::String(repr) => repr.encode(buf, input, default_decor), + Value::Integer(repr) => repr.encode(buf, input, default_decor), + Value::Float(repr) => repr.encode(buf, input, default_decor), + Value::Boolean(repr) => repr.encode(buf, input, default_decor), + Value::Datetime(repr) => repr.encode(buf, input, default_decor), + Value::Array(array) => array.encode(buf, input, default_decor), + Value::InlineTable(table) => table.encode(buf, input, default_decor), + } + } +} + +impl Display for Document { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let mut path = Vec::new(); + let mut last_position = 0; + let mut tables = Vec::new(); + visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| { + if let Some(pos) = t.position() { + last_position = pos; + } + tables.push((last_position, t, p.clone(), is_array)); + Ok(()) + }) + .unwrap(); + + tables.sort_by_key(|&(id, _, _, _)| id); + let mut first_table = true; + for (_, table, path, is_array) in tables { + visit_table( + f, + self.original.as_deref(), + table, + &path, + is_array, + &mut first_table, + )?; + } + self.trailing() + .encode_with_default(f, self.original.as_deref(), "") + } +} + +fn visit_nested_tables<'t, F>( + table: &'t Table, + path: &mut Vec, + is_array_of_tables: bool, + callback: &mut F, +) -> Result +where + F: FnMut(&'t Table, &Vec, bool) -> Result, +{ + if !table.is_dotted() { + callback(table, path, is_array_of_tables)?; + } + + for kv in table.items.values() { + match kv.value { + Item::Table(ref t) => { + let mut key = kv.key.clone(); + if t.is_dotted() { + // May have newlines and generally isn't written for standard tables + key.decor_mut().clear(); + } + path.push(key); + visit_nested_tables(t, path, false, callback)?; + path.pop(); + } + Item::ArrayOfTables(ref a) => { + for t in a.iter() { + let key = kv.key.clone(); + path.push(key); + visit_nested_tables(t, path, true, callback)?; + path.pop(); + } + } + _ => {} + } + } + Ok(()) +} + +fn visit_table( + buf: &mut dyn Write, + input: Option<&str>, + table: &Table, + path: &[Key], + is_array_of_tables: bool, + first_table: &mut bool, +) -> Result { + let children = table.get_values(); + // We are intentionally hiding implicit tables without any tables nested under them (ie + // `table.is_empty()` which is in contrast to `table.get_values().is_empty()`). We are + // trusting the user that an empty implicit table is not semantically meaningful + // + // This allows a user to delete all tables under this implicit table and the implicit table + // will disappear. + // + // However, this means that users need to take care in deciding what tables get marked as + // implicit. + let is_visible_std_table = !(table.implicit && children.is_empty()); + + if path.is_empty() { + // don't print header for the root node + if !children.is_empty() { + *first_table = false; + } + } else if is_array_of_tables { + let default_decor = if *first_table { + *first_table = false; + ("", DEFAULT_TABLE_DECOR.1) + } else { + DEFAULT_TABLE_DECOR + }; + table.decor.prefix_encode(buf, input, default_decor.0)?; + write!(buf, "[[")?; + path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?; + write!(buf, "]]")?; + table.decor.suffix_encode(buf, input, default_decor.1)?; + writeln!(buf)?; + } else if is_visible_std_table { + let default_decor = if *first_table { + *first_table = false; + ("", DEFAULT_TABLE_DECOR.1) + } else { + DEFAULT_TABLE_DECOR + }; + table.decor.prefix_encode(buf, input, default_decor.0)?; + write!(buf, "[")?; + path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?; + write!(buf, "]")?; + table.decor.suffix_encode(buf, input, default_decor.1)?; + writeln!(buf)?; + } + // print table body + for (key_path, value) in children { + key_path.as_slice().encode(buf, input, DEFAULT_KEY_DECOR)?; + write!(buf, "=")?; + value.encode(buf, input, DEFAULT_VALUE_DECOR)?; + writeln!(buf)?; + } + Ok(()) +} + +impl ValueRepr for String { + fn to_repr(&self) -> Repr { + to_string_repr(self, None, None) + } +} + +pub(crate) fn to_string_repr( + value: &str, + style: Option, + literal: Option, +) -> Repr { + let (style, literal) = match (style, literal) { + (Some(style), Some(literal)) => (style, literal), + (_, Some(literal)) => (infer_style(value).0, literal), + (Some(style), _) => (style, infer_style(value).1), + (_, _) => infer_style(value), + }; + + let mut output = String::with_capacity(value.len() * 2); + if literal { + output.push_str(style.literal_start()); + output.push_str(value); + output.push_str(style.literal_end()); + } else { + output.push_str(style.standard_start()); + for ch in value.chars() { + match ch { + '\u{8}' => output.push_str("\\b"), + '\u{9}' => output.push_str("\\t"), + '\u{a}' => match style { + StringStyle::NewlineTripple => output.push('\n'), + StringStyle::OnelineSingle => output.push_str("\\n"), + _ => unreachable!(), + }, + '\u{c}' => output.push_str("\\f"), + '\u{d}' => output.push_str("\\r"), + '\u{22}' => output.push_str("\\\""), + '\u{5c}' => output.push_str("\\\\"), + c if c <= '\u{1f}' || c == '\u{7f}' => { + write!(output, "\\u{:04X}", ch as u32).unwrap(); + } + ch => output.push(ch), + } + } + output.push_str(style.standard_end()); + } + + Repr::new_unchecked(output) +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum StringStyle { + NewlineTripple, + OnelineTripple, + OnelineSingle, +} + +impl StringStyle { + fn literal_start(self) -> &'static str { + match self { + Self::NewlineTripple => "'''\n", + Self::OnelineTripple => "'''", + Self::OnelineSingle => "'", + } + } + fn literal_end(self) -> &'static str { + match self { + Self::NewlineTripple => "'''", + Self::OnelineTripple => "'''", + Self::OnelineSingle => "'", + } + } + + fn standard_start(self) -> &'static str { + match self { + Self::NewlineTripple => "\"\"\"\n", + // note: OnelineTripple can happen if do_pretty wants to do + // '''it's one line''' + // but literal == false + Self::OnelineTripple | Self::OnelineSingle => "\"", + } + } + + fn standard_end(self) -> &'static str { + match self { + Self::NewlineTripple => "\"\"\"", + // note: OnelineTripple can happen if do_pretty wants to do + // '''it's one line''' + // but literal == false + Self::OnelineTripple | Self::OnelineSingle => "\"", + } + } +} + +fn infer_style(value: &str) -> (StringStyle, bool) { + // For doing pretty prints we store in a new String + // because there are too many cases where pretty cannot + // work. We need to determine: + // - if we are a "multi-line" pretty (if there are \n) + // - if ['''] appears if multi or ['] if single + // - if there are any invalid control characters + // + // Doing it any other way would require multiple passes + // to determine if a pretty string works or not. + let mut out = String::with_capacity(value.len() * 2); + let mut ty = StringStyle::OnelineSingle; + // found consecutive single quotes + let mut max_found_singles = 0; + let mut found_singles = 0; + let mut prefer_literal = false; + let mut can_be_pretty = true; + + for ch in value.chars() { + if can_be_pretty { + if ch == '\'' { + found_singles += 1; + if found_singles >= 3 { + can_be_pretty = false; + } + } else { + if found_singles > max_found_singles { + max_found_singles = found_singles; + } + found_singles = 0 + } + match ch { + '\t' => {} + '\\' => { + prefer_literal = true; + } + '\n' => ty = StringStyle::NewlineTripple, + // Escape codes are needed if any ascii control + // characters are present, including \b \f \r. + c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false, + _ => {} + } + out.push(ch); + } else { + // the string cannot be represented as pretty, + // still check if it should be multiline + if ch == '\n' { + ty = StringStyle::NewlineTripple; + } + } + } + if found_singles > 0 && value.ends_with('\'') { + // We cannot escape the ending quote so we must use """ + can_be_pretty = false; + } + if !prefer_literal { + can_be_pretty = false; + } + if !can_be_pretty { + debug_assert!(ty != StringStyle::OnelineTripple); + return (ty, false); + } + if found_singles > max_found_singles { + max_found_singles = found_singles; + } + debug_assert!(max_found_singles < 3); + if ty == StringStyle::OnelineSingle && max_found_singles >= 1 { + // no newlines, but must use ''' because it has ' in it + ty = StringStyle::OnelineTripple; + } + (ty, true) +} + +impl ValueRepr for i64 { + fn to_repr(&self) -> Repr { + Repr::new_unchecked(self.to_string()) + } +} + +impl ValueRepr for f64 { + fn to_repr(&self) -> Repr { + to_f64_repr(*self) + } +} + +fn to_f64_repr(f: f64) -> Repr { + let repr = match (f.is_sign_negative(), f.is_nan(), f == 0.0) { + (true, true, _) => "-nan".to_owned(), + (false, true, _) => "nan".to_owned(), + (true, false, true) => "-0.0".to_owned(), + (false, false, true) => "0.0".to_owned(), + (_, false, false) => { + if f % 1.0 == 0.0 { + format!("{}.0", f) + } else { + format!("{}", f) + } + } + }; + Repr::new_unchecked(repr) +} + +impl ValueRepr for bool { + fn to_repr(&self) -> Repr { + Repr::new_unchecked(self.to_string()) + } +} + +impl ValueRepr for Datetime { + fn to_repr(&self) -> Repr { + Repr::new_unchecked(self.to_string()) + } +} diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 0000000..276db79 --- /dev/null +++ b/src/index.rs @@ -0,0 +1,156 @@ +use std::ops; + +use crate::document::Document; +use crate::key::Key; +use crate::table::TableKeyValue; +use crate::{value, InlineTable, InternalString, Item, Table, Value}; + +// copied from +// https://github.com/serde-rs/json/blob/master/src/value/index.rs + +pub trait Index: crate::private::Sealed { + #[doc(hidden)] + fn index<'v>(&self, val: &'v Item) -> Option<&'v Item>; + #[doc(hidden)] + fn index_mut<'v>(&self, val: &'v mut Item) -> Option<&'v mut Item>; +} + +impl Index for usize { + fn index<'v>(&self, v: &'v Item) -> Option<&'v Item> { + match *v { + Item::ArrayOfTables(ref aot) => aot.values.get(*self), + Item::Value(ref a) if a.is_array() => a.as_array().and_then(|a| a.values.get(*self)), + _ => None, + } + } + fn index_mut<'v>(&self, v: &'v mut Item) -> Option<&'v mut Item> { + match *v { + Item::ArrayOfTables(ref mut vec) => vec.values.get_mut(*self), + Item::Value(ref mut a) => a.as_array_mut().and_then(|a| a.values.get_mut(*self)), + _ => None, + } + } +} + +impl Index for str { + fn index<'v>(&self, v: &'v Item) -> Option<&'v Item> { + match *v { + Item::Table(ref t) => t.get(self), + Item::Value(ref v) => v + .as_inline_table() + .and_then(|t| t.items.get(self)) + .and_then(|kv| { + if !kv.value.is_none() { + Some(&kv.value) + } else { + None + } + }), + _ => None, + } + } + fn index_mut<'v>(&self, v: &'v mut Item) -> Option<&'v mut Item> { + if let Item::None = *v { + let mut t = InlineTable::default(); + t.items.insert( + InternalString::from(self), + TableKeyValue::new(Key::new(self), Item::None), + ); + *v = value(Value::InlineTable(t)); + } + match *v { + Item::Table(ref mut t) => Some(t.entry(self).or_insert(Item::None)), + Item::Value(ref mut v) => v.as_inline_table_mut().map(|t| { + &mut t + .items + .entry(InternalString::from(self)) + .or_insert_with(|| TableKeyValue::new(Key::new(self), Item::None)) + .value + }), + _ => None, + } + } +} + +impl Index for String { + fn index<'v>(&self, v: &'v Item) -> Option<&'v Item> { + self[..].index(v) + } + fn index_mut<'v>(&self, v: &'v mut Item) -> Option<&'v mut Item> { + self[..].index_mut(v) + } +} + +impl<'a, T: ?Sized> Index for &'a T +where + T: Index, +{ + fn index<'v>(&self, v: &'v Item) -> Option<&'v Item> { + (**self).index(v) + } + fn index_mut<'v>(&self, v: &'v mut Item) -> Option<&'v mut Item> { + (**self).index_mut(v) + } +} + +impl ops::Index for Item +where + I: Index, +{ + type Output = Item; + + fn index(&self, index: I) -> &Item { + index.index(self).expect("index not found") + } +} + +impl ops::IndexMut for Item +where + I: Index, +{ + fn index_mut(&mut self, index: I) -> &mut Item { + index.index_mut(self).expect("index not found") + } +} + +impl<'s> ops::Index<&'s str> for Table { + type Output = Item; + + fn index(&self, key: &'s str) -> &Item { + self.get(key).expect("index not found") + } +} + +impl<'s> ops::IndexMut<&'s str> for Table { + fn index_mut(&mut self, key: &'s str) -> &mut Item { + self.entry(key).or_insert(Item::None) + } +} + +impl<'s> ops::Index<&'s str> for InlineTable { + type Output = Value; + + fn index(&self, key: &'s str) -> &Value { + self.get(key).expect("index not found") + } +} + +impl<'s> ops::IndexMut<&'s str> for InlineTable { + fn index_mut(&mut self, key: &'s str) -> &mut Value { + self.get_mut(key).expect("index not found") + } +} + +impl<'s> ops::Index<&'s str> for Document { + type Output = Item; + + fn index(&self, key: &'s str) -> &Item { + self.root.index(key) + } +} + +impl<'s> ops::IndexMut<&'s str> for Document { + fn index_mut(&mut self, key: &'s str) -> &mut Item { + self.root.index_mut(key) + } +} diff --git a/src/inline_table.rs b/src/inline_table.rs new file mode 100644 index 0000000..3dc6c0c --- /dev/null +++ b/src/inline_table.rs @@ -0,0 +1,679 @@ +use std::iter::FromIterator; + +use crate::key::Key; +use crate::repr::Decor; +use crate::table::{Iter, IterMut, KeyValuePairs, TableKeyValue, TableLike}; +use crate::{InternalString, Item, KeyMut, RawString, Table, Value}; + +/// Type representing a TOML inline table, +/// payload of the `Value::InlineTable` variant +#[derive(Debug, Default, Clone)] +pub struct InlineTable { + // `preamble` represents whitespaces in an empty table + preamble: RawString, + // prefix before `{` and suffix after `}` + decor: Decor, + pub(crate) span: Option>, + // whether this is a proxy for dotted keys + dotted: bool, + pub(crate) items: KeyValuePairs, +} + +/// Constructors +/// +/// See also `FromIterator` +impl InlineTable { + /// Creates an empty table. + pub fn new() -> Self { + Default::default() + } + + pub(crate) fn with_pairs(items: KeyValuePairs) -> Self { + Self { + items, + ..Default::default() + } + } + + /// Convert to a table + pub fn into_table(self) -> Table { + let mut t = Table::with_pairs(self.items); + t.fmt(); + t + } +} + +/// Formatting +impl InlineTable { + /// Get key/values for values that are visually children of this table + /// + /// For example, this will return dotted keys + pub fn get_values(&self) -> Vec<(Vec<&Key>, &Value)> { + let mut values = Vec::new(); + let root = Vec::new(); + self.append_values(&root, &mut values); + values + } + + pub(crate) fn append_values<'s, 'c>( + &'s self, + parent: &[&'s Key], + values: &'c mut Vec<(Vec<&'s Key>, &'s Value)>, + ) { + for value in self.items.values() { + let mut path = parent.to_vec(); + path.push(&value.key); + match &value.value { + Item::Value(Value::InlineTable(table)) if table.is_dotted() => { + table.append_values(&path, values); + } + Item::Value(value) => { + values.push((path, value)); + } + _ => {} + } + } + } + + /// Auto formats the table. + pub fn fmt(&mut self) { + decorate_inline_table(self); + } + + /// Sorts the key/value pairs by key. + pub fn sort_values(&mut self) { + // Assuming standard tables have their position set and this won't negatively impact them + self.items.sort_keys(); + for kv in self.items.values_mut() { + match &mut kv.value { + Item::Value(Value::InlineTable(table)) if table.is_dotted() => { + table.sort_values(); + } + _ => {} + } + } + } + + /// Sort Key/Value Pairs of the table using the using the comparison function `compare`. + /// + /// The comparison function receives two key and value pairs to compare (you can sort by keys or + /// values or their combination as needed). + pub fn sort_values_by(&mut self, mut compare: F) + where + F: FnMut(&Key, &Value, &Key, &Value) -> std::cmp::Ordering, + { + self.sort_values_by_internal(&mut compare); + } + + fn sort_values_by_internal(&mut self, compare: &mut F) + where + F: FnMut(&Key, &Value, &Key, &Value) -> std::cmp::Ordering, + { + let modified_cmp = |_: &InternalString, + val1: &TableKeyValue, + _: &InternalString, + val2: &TableKeyValue| + -> std::cmp::Ordering { + match (val1.value.as_value(), val2.value.as_value()) { + (Some(v1), Some(v2)) => compare(&val1.key, v1, &val2.key, v2), + (Some(_), None) => std::cmp::Ordering::Greater, + (None, Some(_)) => std::cmp::Ordering::Less, + (None, None) => std::cmp::Ordering::Equal, + } + }; + + self.items.sort_by(modified_cmp); + for kv in self.items.values_mut() { + match &mut kv.value { + Item::Value(Value::InlineTable(table)) if table.is_dotted() => { + table.sort_values_by_internal(compare); + } + _ => {} + } + } + } + + /// Change this table's dotted status + pub fn set_dotted(&mut self, yes: bool) { + self.dotted = yes; + } + + /// Check if this is a wrapper for dotted keys, rather than a standard table + pub fn is_dotted(&self) -> bool { + self.dotted + } + + /// Returns the surrounding whitespace + pub fn decor_mut(&mut self) -> &mut Decor { + &mut self.decor + } + + /// Returns the surrounding whitespace + pub fn decor(&self) -> &Decor { + &self.decor + } + + /// Returns the decor associated with a given key of the table. + pub fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> { + self.items.get_mut(key).map(|kv| &mut kv.key.decor) + } + + /// Returns the decor associated with a given key of the table. + pub fn key_decor(&self, key: &str) -> Option<&Decor> { + self.items.get(key).map(|kv| &kv.key.decor) + } + + /// Set whitespace after before element + pub fn set_preamble(&mut self, preamble: impl Into) { + self.preamble = preamble.into(); + } + + /// Whitespace after before element + pub fn preamble(&self) -> &RawString { + &self.preamble + } + + /// Returns the location within the original document + pub(crate) fn span(&self) -> Option> { + self.span.clone() + } + + pub(crate) fn despan(&mut self, input: &str) { + self.span = None; + self.decor.despan(input); + self.preamble.despan(input); + for kv in self.items.values_mut() { + kv.key.despan(input); + kv.value.despan(input); + } + } +} + +impl InlineTable { + /// Returns an iterator over key/value pairs. + pub fn iter(&self) -> InlineTableIter<'_> { + Box::new( + self.items + .iter() + .filter(|&(_, kv)| kv.value.is_value()) + .map(|(k, kv)| (&k[..], kv.value.as_value().unwrap())), + ) + } + + /// Returns an iterator over key/value pairs. + pub fn iter_mut(&mut self) -> InlineTableIterMut<'_> { + Box::new( + self.items + .iter_mut() + .filter(|(_, kv)| kv.value.is_value()) + .map(|(_, kv)| (kv.key.as_mut(), kv.value.as_value_mut().unwrap())), + ) + } + + /// Returns the number of key/value pairs. + pub fn len(&self) -> usize { + self.iter().count() + } + + /// Returns true iff the table is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Clears the table, removing all key-value pairs. Keeps the allocated memory for reuse. + pub fn clear(&mut self) { + self.items.clear() + } + + /// Gets the given key's corresponding entry in the Table for in-place manipulation. + pub fn entry(&'_ mut self, key: impl Into) -> InlineEntry<'_> { + match self.items.entry(key.into()) { + indexmap::map::Entry::Occupied(mut entry) => { + // Ensure it is a `Value` to simplify `InlineOccupiedEntry`'s code. + let scratch = std::mem::take(&mut entry.get_mut().value); + let scratch = Item::Value( + scratch + .into_value() + // HACK: `Item::None` is a corner case of a corner case, let's just pick a + // "safe" value + .unwrap_or_else(|_| Value::InlineTable(Default::default())), + ); + entry.get_mut().value = scratch; + + InlineEntry::Occupied(InlineOccupiedEntry { entry }) + } + indexmap::map::Entry::Vacant(entry) => { + InlineEntry::Vacant(InlineVacantEntry { entry, key: None }) + } + } + } + + /// Gets the given key's corresponding entry in the Table for in-place manipulation. + pub fn entry_format<'a>(&'a mut self, key: &Key) -> InlineEntry<'a> { + // Accept a `&Key` to be consistent with `entry` + match self.items.entry(key.get().into()) { + indexmap::map::Entry::Occupied(mut entry) => { + // Ensure it is a `Value` to simplify `InlineOccupiedEntry`'s code. + let scratch = std::mem::take(&mut entry.get_mut().value); + let scratch = Item::Value( + scratch + .into_value() + // HACK: `Item::None` is a corner case of a corner case, let's just pick a + // "safe" value + .unwrap_or_else(|_| Value::InlineTable(Default::default())), + ); + entry.get_mut().value = scratch; + + InlineEntry::Occupied(InlineOccupiedEntry { entry }) + } + indexmap::map::Entry::Vacant(entry) => InlineEntry::Vacant(InlineVacantEntry { + entry, + key: Some(key.clone()), + }), + } + } + /// Return an optional reference to the value at the given the key. + pub fn get(&self, key: &str) -> Option<&Value> { + self.items.get(key).and_then(|kv| kv.value.as_value()) + } + + /// Return an optional mutable reference to the value at the given the key. + pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { + self.items + .get_mut(key) + .and_then(|kv| kv.value.as_value_mut()) + } + + /// Return references to the key-value pair stored for key, if it is present, else None. + pub fn get_key_value<'a>(&'a self, key: &str) -> Option<(&'a Key, &'a Item)> { + self.items.get(key).and_then(|kv| { + if !kv.value.is_none() { + Some((&kv.key, &kv.value)) + } else { + None + } + }) + } + + /// Return mutable references to the key-value pair stored for key, if it is present, else None. + pub fn get_key_value_mut<'a>(&'a mut self, key: &str) -> Option<(KeyMut<'a>, &'a mut Item)> { + self.items.get_mut(key).and_then(|kv| { + if !kv.value.is_none() { + Some((kv.key.as_mut(), &mut kv.value)) + } else { + None + } + }) + } + + /// Returns true iff the table contains given key. + pub fn contains_key(&self, key: &str) -> bool { + if let Some(kv) = self.items.get(key) { + kv.value.is_value() + } else { + false + } + } + + /// Inserts a key/value pair if the table does not contain the key. + /// Returns a mutable reference to the corresponding value. + pub fn get_or_insert>( + &mut self, + key: impl Into, + value: V, + ) -> &mut Value { + let key = key.into(); + self.items + .entry(key.clone()) + .or_insert(TableKeyValue::new(Key::new(key), Item::Value(value.into()))) + .value + .as_value_mut() + .expect("non-value type in inline table") + } + + /// Inserts a key-value pair into the map. + pub fn insert(&mut self, key: impl Into, value: Value) -> Option { + let key = key.into(); + let kv = TableKeyValue::new(Key::new(key.clone()), Item::Value(value)); + self.items + .insert(key, kv) + .and_then(|kv| kv.value.into_value().ok()) + } + + /// Inserts a key-value pair into the map. + pub fn insert_formatted(&mut self, key: &Key, value: Value) -> Option { + let kv = TableKeyValue::new(key.to_owned(), Item::Value(value)); + self.items + .insert(InternalString::from(key.get()), kv) + .filter(|kv| kv.value.is_value()) + .map(|kv| kv.value.into_value().unwrap()) + } + + /// Removes an item given the key. + pub fn remove(&mut self, key: &str) -> Option { + self.items + .shift_remove(key) + .and_then(|kv| kv.value.into_value().ok()) + } + + /// Removes a key from the map, returning the stored key and value if the key was previously in the map. + pub fn remove_entry(&mut self, key: &str) -> Option<(Key, Value)> { + self.items.shift_remove(key).and_then(|kv| { + let key = kv.key; + kv.value.into_value().ok().map(|value| (key, value)) + }) + } + + /// Retains only the elements specified by the `keep` predicate. + /// + /// In other words, remove all pairs `(key, value)` for which + /// `keep(&key, &mut value)` returns `false`. + /// + /// The elements are visited in iteration order. + pub fn retain(&mut self, mut keep: F) + where + F: FnMut(&str, &mut Value) -> bool, + { + self.items.retain(|key, item| { + item.value + .as_value_mut() + .map(|value| keep(key, value)) + .unwrap_or(false) + }); + } +} + +impl std::fmt::Display for InlineTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + crate::encode::Encode::encode(self, f, None, ("", "")) + } +} + +impl, V: Into> Extend<(K, V)> for InlineTable { + fn extend>(&mut self, iter: T) { + for (key, value) in iter { + let key = key.into(); + let value = Item::Value(value.into()); + let value = TableKeyValue::new(key, value); + self.items + .insert(InternalString::from(value.key.get()), value); + } + } +} + +impl, V: Into> FromIterator<(K, V)> for InlineTable { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut table = InlineTable::new(); + table.extend(iter); + table + } +} + +impl IntoIterator for InlineTable { + type Item = (InternalString, Value); + type IntoIter = InlineTableIntoIter; + + fn into_iter(self) -> Self::IntoIter { + Box::new( + self.items + .into_iter() + .filter(|(_, kv)| kv.value.is_value()) + .map(|(k, kv)| (k, kv.value.into_value().unwrap())), + ) + } +} + +impl<'s> IntoIterator for &'s InlineTable { + type Item = (&'s str, &'s Value); + type IntoIter = InlineTableIter<'s>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +fn decorate_inline_table(table: &mut InlineTable) { + for (key_decor, value) in table + .items + .iter_mut() + .filter(|&(_, ref kv)| kv.value.is_value()) + .map(|(_, kv)| (&mut kv.key.decor, kv.value.as_value_mut().unwrap())) + { + key_decor.clear(); + value.decor_mut().clear(); + } +} + +/// An owned iterator type over key/value pairs of an inline table. +pub type InlineTableIntoIter = Box>; +/// An iterator type over key/value pairs of an inline table. +pub type InlineTableIter<'a> = Box + 'a>; +/// A mutable iterator type over key/value pairs of an inline table. +pub type InlineTableIterMut<'a> = Box, &'a mut Value)> + 'a>; + +impl TableLike for InlineTable { + fn iter(&self) -> Iter<'_> { + Box::new(self.items.iter().map(|(key, kv)| (&key[..], &kv.value))) + } + fn iter_mut(&mut self) -> IterMut<'_> { + Box::new( + self.items + .iter_mut() + .map(|(_, kv)| (kv.key.as_mut(), &mut kv.value)), + ) + } + fn clear(&mut self) { + self.clear(); + } + fn entry<'a>(&'a mut self, key: &str) -> crate::Entry<'a> { + // Accept a `&str` rather than an owned type to keep `InternalString`, well, internal + match self.items.entry(key.into()) { + indexmap::map::Entry::Occupied(entry) => { + crate::Entry::Occupied(crate::OccupiedEntry { entry }) + } + indexmap::map::Entry::Vacant(entry) => { + crate::Entry::Vacant(crate::VacantEntry { entry, key: None }) + } + } + } + fn entry_format<'a>(&'a mut self, key: &Key) -> crate::Entry<'a> { + // Accept a `&Key` to be consistent with `entry` + match self.items.entry(key.get().into()) { + indexmap::map::Entry::Occupied(entry) => { + crate::Entry::Occupied(crate::OccupiedEntry { entry }) + } + indexmap::map::Entry::Vacant(entry) => crate::Entry::Vacant(crate::VacantEntry { + entry, + key: Some(key.to_owned()), + }), + } + } + fn get<'s>(&'s self, key: &str) -> Option<&'s Item> { + self.items.get(key).map(|kv| &kv.value) + } + fn get_mut<'s>(&'s mut self, key: &str) -> Option<&'s mut Item> { + self.items.get_mut(key).map(|kv| &mut kv.value) + } + fn get_key_value<'a>(&'a self, key: &str) -> Option<(&'a Key, &'a Item)> { + self.get_key_value(key) + } + fn get_key_value_mut<'a>(&'a mut self, key: &str) -> Option<(KeyMut<'a>, &'a mut Item)> { + self.get_key_value_mut(key) + } + fn contains_key(&self, key: &str) -> bool { + self.contains_key(key) + } + fn insert(&mut self, key: &str, value: Item) -> Option { + self.insert(key, value.into_value().unwrap()) + .map(Item::Value) + } + fn remove(&mut self, key: &str) -> Option { + self.remove(key).map(Item::Value) + } + + fn get_values(&self) -> Vec<(Vec<&Key>, &Value)> { + self.get_values() + } + fn fmt(&mut self) { + self.fmt() + } + fn sort_values(&mut self) { + self.sort_values() + } + fn set_dotted(&mut self, yes: bool) { + self.set_dotted(yes) + } + fn is_dotted(&self) -> bool { + self.is_dotted() + } + + fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> { + self.key_decor_mut(key) + } + fn key_decor(&self, key: &str) -> Option<&Decor> { + self.key_decor(key) + } +} + +// `{ key1 = value1, ... }` +pub(crate) const DEFAULT_INLINE_KEY_DECOR: (&str, &str) = (" ", " "); + +/// A view into a single location in a map, which may be vacant or occupied. +pub enum InlineEntry<'a> { + /// An occupied Entry. + Occupied(InlineOccupiedEntry<'a>), + /// A vacant Entry. + Vacant(InlineVacantEntry<'a>), +} + +impl<'a> InlineEntry<'a> { + /// Returns the entry key + /// + /// # Examples + /// + /// ``` + /// use toml_edit::Table; + /// + /// let mut map = Table::new(); + /// + /// assert_eq!("hello", map.entry("hello").key()); + /// ``` + pub fn key(&self) -> &str { + match self { + InlineEntry::Occupied(e) => e.key(), + InlineEntry::Vacant(e) => e.key(), + } + } + + /// Ensures a value is in the entry by inserting the default if empty, and returns + /// a mutable reference to the value in the entry. + pub fn or_insert(self, default: Value) -> &'a mut Value { + match self { + InlineEntry::Occupied(entry) => entry.into_mut(), + InlineEntry::Vacant(entry) => entry.insert(default), + } + } + + /// Ensures a value is in the entry by inserting the result of the default function if empty, + /// and returns a mutable reference to the value in the entry. + pub fn or_insert_with Value>(self, default: F) -> &'a mut Value { + match self { + InlineEntry::Occupied(entry) => entry.into_mut(), + InlineEntry::Vacant(entry) => entry.insert(default()), + } + } +} + +/// A view into a single occupied location in a `IndexMap`. +pub struct InlineOccupiedEntry<'a> { + entry: indexmap::map::OccupiedEntry<'a, InternalString, TableKeyValue>, +} + +impl<'a> InlineOccupiedEntry<'a> { + /// Gets a reference to the entry key + /// + /// # Examples + /// + /// ``` + /// use toml_edit::Table; + /// + /// let mut map = Table::new(); + /// + /// assert_eq!("foo", map.entry("foo").key()); + /// ``` + pub fn key(&self) -> &str { + self.entry.key().as_str() + } + + /// Gets a mutable reference to the entry key + pub fn key_mut(&mut self) -> KeyMut<'_> { + self.entry.get_mut().key.as_mut() + } + + /// Gets a reference to the value in the entry. + pub fn get(&self) -> &Value { + self.entry.get().value.as_value().unwrap() + } + + /// Gets a mutable reference to the value in the entry. + pub fn get_mut(&mut self) -> &mut Value { + self.entry.get_mut().value.as_value_mut().unwrap() + } + + /// Converts the OccupiedEntry into a mutable reference to the value in the entry + /// with a lifetime bound to the map itself + pub fn into_mut(self) -> &'a mut Value { + self.entry.into_mut().value.as_value_mut().unwrap() + } + + /// Sets the value of the entry, and returns the entry's old value + pub fn insert(&mut self, value: Value) -> Value { + let mut value = Item::Value(value); + std::mem::swap(&mut value, &mut self.entry.get_mut().value); + value.into_value().unwrap() + } + + /// Takes the value out of the entry, and returns it + pub fn remove(self) -> Value { + self.entry.shift_remove().value.into_value().unwrap() + } +} + +/// A view into a single empty location in a `IndexMap`. +pub struct InlineVacantEntry<'a> { + entry: indexmap::map::VacantEntry<'a, InternalString, TableKeyValue>, + key: Option, +} + +impl<'a> InlineVacantEntry<'a> { + /// Gets a reference to the entry key + /// + /// # Examples + /// + /// ``` + /// use toml_edit::Table; + /// + /// let mut map = Table::new(); + /// + /// assert_eq!("foo", map.entry("foo").key()); + /// ``` + pub fn key(&self) -> &str { + self.entry.key().as_str() + } + + /// Sets the value of the entry with the VacantEntry's key, + /// and returns a mutable reference to it + pub fn insert(self, value: Value) -> &'a mut Value { + let entry = self.entry; + let key = self.key.unwrap_or_else(|| Key::new(entry.key().as_str())); + let value = Item::Value(value); + entry + .insert(TableKeyValue::new(key, value)) + .value + .as_value_mut() + .unwrap() + } +} diff --git a/src/internal_string.rs b/src/internal_string.rs new file mode 100644 index 0000000..d4347d2 --- /dev/null +++ b/src/internal_string.rs @@ -0,0 +1,183 @@ +use std::borrow::Borrow; +use std::str::FromStr; + +/// Opaque string storage internal to `toml_edit` +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InternalString(Inner); + +#[cfg(feature = "kstring")] +type Inner = kstring::KString; +#[cfg(not(feature = "kstring"))] +type Inner = String; + +impl InternalString { + /// Create an empty string + pub fn new() -> Self { + InternalString(Inner::new()) + } + + /// Access the underlying string + #[inline] + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl std::fmt::Debug for InternalString { + #[inline] + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + self.0.fmt(formatter) + } +} + +impl std::ops::Deref for InternalString { + type Target = str; + + #[inline] + fn deref(&self) -> &str { + self.as_str() + } +} + +impl Borrow for InternalString { + #[inline] + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl AsRef for InternalString { + #[inline] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl From<&str> for InternalString { + #[inline] + fn from(s: &str) -> Self { + #[cfg(feature = "kstring")] + let inner = kstring::KString::from_ref(s); + #[cfg(not(feature = "kstring"))] + let inner = String::from(s); + + InternalString(inner) + } +} + +impl From for InternalString { + #[inline] + fn from(s: String) -> Self { + #[allow(clippy::useless_conversion)] // handle any string type + InternalString(s.into()) + } +} + +impl From<&String> for InternalString { + #[inline] + fn from(s: &String) -> Self { + InternalString(s.into()) + } +} + +impl From<&InternalString> for InternalString { + #[inline] + fn from(s: &InternalString) -> Self { + s.clone() + } +} + +impl From> for InternalString { + #[inline] + fn from(s: Box) -> Self { + InternalString(s.into()) + } +} + +impl FromStr for InternalString { + type Err = core::convert::Infallible; + #[inline] + fn from_str(s: &str) -> Result { + Ok(Self::from(s)) + } +} + +impl std::fmt::Display for InternalString { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_str().fmt(f) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for InternalString { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for InternalString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_string(StringVisitor) + } +} + +#[cfg(feature = "serde")] +struct StringVisitor; + +#[cfg(feature = "serde")] +impl<'de> serde::de::Visitor<'de> for StringVisitor { + type Value = InternalString; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(InternalString::from(v)) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + Ok(InternalString::from(v)) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + match std::str::from_utf8(v) { + Ok(s) => Ok(InternalString::from(s)), + Err(_) => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Bytes(v), + &self, + )), + } + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: serde::de::Error, + { + match String::from_utf8(v) { + Ok(s) => Ok(InternalString::from(s)), + Err(e) => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Bytes(&e.into_bytes()), + &self, + )), + } + } +} diff --git a/src/item.rs b/src/item.rs new file mode 100644 index 0000000..2025fd9 --- /dev/null +++ b/src/item.rs @@ -0,0 +1,393 @@ +use std::str::FromStr; + +use toml_datetime::*; + +use crate::array_of_tables::ArrayOfTables; +use crate::table::TableLike; +use crate::{Array, InlineTable, Table, Value}; + +/// Type representing either a value, a table, an array of tables, or none. +#[derive(Debug)] +pub enum Item { + /// Type representing none. + None, + /// Type representing value. + Value(Value), + /// Type representing table. + Table(Table), + /// Type representing array of tables. + ArrayOfTables(ArrayOfTables), +} + +impl Item { + /// Sets `self` to the given item iff `self` is none and + /// returns a mutable reference to `self`. + pub fn or_insert(&mut self, item: Item) -> &mut Item { + if self.is_none() { + *self = item + } + self + } +} + +// TODO: This should be generated by macro or derive +/// Downcasting +impl Item { + /// Text description of value type + pub fn type_name(&self) -> &'static str { + match self { + Item::None => "none", + Item::Value(v) => v.type_name(), + Item::Table(..) => "table", + Item::ArrayOfTables(..) => "array of tables", + } + } + + /// Index into a TOML array or map. A string index can be used to access a + /// value in a map, and a usize index can be used to access an element of an + /// array. + /// + /// Returns `None` if: + /// - The type of `self` does not match the type of the + /// index, for example if the index is a string and `self` is an array or a + /// number. + /// - The given key does not exist in the map + /// or the given index is not within the bounds of the array. + pub fn get(&self, index: I) -> Option<&Item> { + index.index(self) + } + + /// Mutably index into a TOML array or map. A string index can be used to + /// access a value in a map, and a usize index can be used to access an + /// element of an array. + /// + /// Returns `None` if: + /// - The type of `self` does not match the type of the + /// index, for example if the index is a string and `self` is an array or a + /// number. + /// - The given key does not exist in the map + /// or the given index is not within the bounds of the array. + pub fn get_mut(&mut self, index: I) -> Option<&mut Item> { + index.index_mut(self) + } + + /// Casts `self` to value. + pub fn as_value(&self) -> Option<&Value> { + match *self { + Item::Value(ref v) => Some(v), + _ => None, + } + } + /// Casts `self` to table. + pub fn as_table(&self) -> Option<&Table> { + match *self { + Item::Table(ref t) => Some(t), + _ => None, + } + } + /// Casts `self` to array of tables. + pub fn as_array_of_tables(&self) -> Option<&ArrayOfTables> { + match *self { + Item::ArrayOfTables(ref a) => Some(a), + _ => None, + } + } + /// Casts `self` to mutable value. + pub fn as_value_mut(&mut self) -> Option<&mut Value> { + match *self { + Item::Value(ref mut v) => Some(v), + _ => None, + } + } + /// Casts `self` to mutable table. + pub fn as_table_mut(&mut self) -> Option<&mut Table> { + match *self { + Item::Table(ref mut t) => Some(t), + _ => None, + } + } + /// Casts `self` to mutable array of tables. + pub fn as_array_of_tables_mut(&mut self) -> Option<&mut ArrayOfTables> { + match *self { + Item::ArrayOfTables(ref mut a) => Some(a), + _ => None, + } + } + /// Casts `self` to value. + pub fn into_value(self) -> Result { + match self { + Item::None => Err(self), + Item::Value(v) => Ok(v), + Item::Table(v) => { + let v = v.into_inline_table(); + Ok(Value::InlineTable(v)) + } + Item::ArrayOfTables(v) => { + let v = v.into_array(); + Ok(Value::Array(v)) + } + } + } + /// In-place convert to a value + pub fn make_value(&mut self) { + let other = std::mem::take(self); + let other = other.into_value().map(Item::Value).unwrap_or(Item::None); + *self = other; + } + /// Casts `self` to table. + pub fn into_table(self) -> Result { + match self { + Item::Table(t) => Ok(t), + Item::Value(Value::InlineTable(t)) => Ok(t.into_table()), + _ => Err(self), + } + } + /// Casts `self` to array of tables. + pub fn into_array_of_tables(self) -> Result { + match self { + Item::ArrayOfTables(a) => Ok(a), + Item::Value(Value::Array(a)) => { + if a.is_empty() { + Err(Item::Value(Value::Array(a))) + } else if a.iter().all(|v| v.is_inline_table()) { + let mut aot = ArrayOfTables::new(); + aot.values = a.values; + for value in aot.values.iter_mut() { + value.make_item(); + } + Ok(aot) + } else { + Err(Item::Value(Value::Array(a))) + } + } + _ => Err(self), + } + } + // Starting private because the name is unclear + pub(crate) fn make_item(&mut self) { + let other = std::mem::take(self); + let other = match other.into_table().map(crate::Item::Table) { + Ok(i) => i, + Err(i) => i, + }; + let other = match other.into_array_of_tables().map(crate::Item::ArrayOfTables) { + Ok(i) => i, + Err(i) => i, + }; + *self = other; + } + /// Returns true iff `self` is a value. + pub fn is_value(&self) -> bool { + self.as_value().is_some() + } + /// Returns true iff `self` is a table. + pub fn is_table(&self) -> bool { + self.as_table().is_some() + } + /// Returns true iff `self` is an array of tables. + pub fn is_array_of_tables(&self) -> bool { + self.as_array_of_tables().is_some() + } + /// Returns true iff `self` is `None`. + pub fn is_none(&self) -> bool { + matches!(*self, Item::None) + } + + // Duplicate Value downcasting API + + /// Casts `self` to integer. + pub fn as_integer(&self) -> Option { + self.as_value().and_then(Value::as_integer) + } + + /// Returns true iff `self` is an integer. + pub fn is_integer(&self) -> bool { + self.as_integer().is_some() + } + + /// Casts `self` to float. + pub fn as_float(&self) -> Option { + self.as_value().and_then(Value::as_float) + } + + /// Returns true iff `self` is a float. + pub fn is_float(&self) -> bool { + self.as_float().is_some() + } + + /// Casts `self` to boolean. + pub fn as_bool(&self) -> Option { + self.as_value().and_then(Value::as_bool) + } + + /// Returns true iff `self` is a boolean. + pub fn is_bool(&self) -> bool { + self.as_bool().is_some() + } + + /// Casts `self` to str. + pub fn as_str(&self) -> Option<&str> { + self.as_value().and_then(Value::as_str) + } + + /// Returns true iff `self` is a string. + pub fn is_str(&self) -> bool { + self.as_str().is_some() + } + + /// Casts `self` to date-time. + pub fn as_datetime(&self) -> Option<&Datetime> { + self.as_value().and_then(Value::as_datetime) + } + + /// Returns true iff `self` is a date-time. + pub fn is_datetime(&self) -> bool { + self.as_datetime().is_some() + } + + /// Casts `self` to array. + pub fn as_array(&self) -> Option<&Array> { + self.as_value().and_then(Value::as_array) + } + + /// Casts `self` to mutable array. + pub fn as_array_mut(&mut self) -> Option<&mut Array> { + self.as_value_mut().and_then(Value::as_array_mut) + } + + /// Returns true iff `self` is an array. + pub fn is_array(&self) -> bool { + self.as_array().is_some() + } + + /// Casts `self` to inline table. + pub fn as_inline_table(&self) -> Option<&InlineTable> { + self.as_value().and_then(Value::as_inline_table) + } + + /// Casts `self` to mutable inline table. + pub fn as_inline_table_mut(&mut self) -> Option<&mut InlineTable> { + self.as_value_mut().and_then(Value::as_inline_table_mut) + } + + /// Returns true iff `self` is an inline table. + pub fn is_inline_table(&self) -> bool { + self.as_inline_table().is_some() + } + + /// Casts `self` to either a table or an inline table. + pub fn as_table_like(&self) -> Option<&dyn TableLike> { + self.as_table() + .map(|t| t as &dyn TableLike) + .or_else(|| self.as_inline_table().map(|t| t as &dyn TableLike)) + } + + /// Casts `self` to either a table or an inline table. + pub fn as_table_like_mut(&mut self) -> Option<&mut dyn TableLike> { + match self { + Item::Table(t) => Some(t as &mut dyn TableLike), + Item::Value(Value::InlineTable(t)) => Some(t as &mut dyn TableLike), + _ => None, + } + } + + /// Returns true iff `self` is either a table, or an inline table. + pub fn is_table_like(&self) -> bool { + self.as_table_like().is_some() + } + + /// Returns the location within the original document + pub(crate) fn span(&self) -> Option> { + match self { + Item::None => None, + Item::Value(v) => v.span(), + Item::Table(v) => v.span(), + Item::ArrayOfTables(v) => v.span(), + } + } + + pub(crate) fn despan(&mut self, input: &str) { + match self { + Item::None => {} + Item::Value(v) => v.despan(input), + Item::Table(v) => v.despan(input), + Item::ArrayOfTables(v) => v.despan(input), + } + } +} + +impl Clone for Item { + #[inline(never)] + fn clone(&self) -> Self { + match self { + Item::None => Item::None, + Item::Value(v) => Item::Value(v.clone()), + Item::Table(v) => Item::Table(v.clone()), + Item::ArrayOfTables(v) => Item::ArrayOfTables(v.clone()), + } + } +} + +impl Default for Item { + fn default() -> Self { + Item::None + } +} + +impl FromStr for Item { + type Err = crate::TomlError; + + /// Parses a value from a &str + fn from_str(s: &str) -> Result { + let value = s.parse::()?; + Ok(Item::Value(value)) + } +} + +impl std::fmt::Display for Item { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Item::None => Ok(()), + Item::Value(v) => v.fmt(f), + Item::Table(v) => v.fmt(f), + Item::ArrayOfTables(v) => v.fmt(f), + } + } +} + +/// Returns a formatted value. +/// +/// Since formatting is part of a `Value`, the right hand side of the +/// assignment needs to be decorated with a space before the value. +/// The `value` function does just that. +/// +/// # Examples +/// ```rust +/// # use snapbox::assert_eq; +/// # use toml_edit::*; +/// let mut table = Table::default(); +/// let mut array = Array::default(); +/// array.push("hello"); +/// array.push("\\, world"); // \ is only allowed in a literal string +/// table["key1"] = value("value1"); +/// table["key2"] = value(42); +/// table["key3"] = value(array); +/// assert_eq(table.to_string(), +/// r#"key1 = "value1" +/// key2 = 42 +/// key3 = ["hello", '\, world'] +/// "#); +/// ``` +pub fn value>(v: V) -> Item { + Item::Value(v.into()) +} + +/// Returns an empty table. +pub fn table() -> Item { + Item::Table(Table::new()) +} + +/// Returns an empty array of tables. +pub fn array() -> Item { + Item::ArrayOfTables(ArrayOfTables::new()) +} diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 0000000..c1ee165 --- /dev/null +++ b/src/key.rs @@ -0,0 +1,344 @@ +use std::borrow::Cow; +use std::str::FromStr; + +use crate::encode::{to_string_repr, StringStyle}; +use crate::parser; +use crate::parser::key::is_unquoted_char; +use crate::repr::{Decor, Repr}; +use crate::InternalString; + +/// Key as part of a Key/Value Pair or a table header. +/// +/// # Examples +/// +/// ```notrust +/// [dependencies."nom"] +/// version = "5.0" +/// 'literal key' = "nonsense" +/// "basic string key" = 42 +/// ``` +/// +/// There are 3 types of keys: +/// +/// 1. Bare keys (`version` and `dependencies`) +/// +/// 2. Basic quoted keys (`"basic string key"` and `"nom"`) +/// +/// 3. Literal quoted keys (`'literal key'`) +/// +/// For details see [toml spec](https://github.com/toml-lang/toml/#keyvalue-pair). +/// +/// To parse a key use `FromStr` trait implementation: `"string".parse::()`. +#[derive(Debug)] +pub struct Key { + key: InternalString, + pub(crate) repr: Option, + pub(crate) decor: Decor, +} + +impl Key { + /// Create a new table key + pub fn new(key: impl Into) -> Self { + Self { + key: key.into(), + repr: None, + decor: Default::default(), + } + } + + /// Parse a TOML key expression + /// + /// Unlike `"".parse()`, this supports dotted keys. + pub fn parse(repr: &str) -> Result, crate::TomlError> { + Self::try_parse_path(repr) + } + + pub(crate) fn with_repr_unchecked(mut self, repr: Repr) -> Self { + self.repr = Some(repr); + self + } + + /// While creating the `Key`, add `Decor` to it + pub fn with_decor(mut self, decor: Decor) -> Self { + self.decor = decor; + self + } + + /// Access a mutable proxy for the `Key`. + pub fn as_mut(&mut self) -> KeyMut<'_> { + KeyMut { key: self } + } + + /// Returns the parsed key value. + pub fn get(&self) -> &str { + &self.key + } + + pub(crate) fn get_internal(&self) -> &InternalString { + &self.key + } + + /// Returns key raw representation, if available. + pub fn as_repr(&self) -> Option<&Repr> { + self.repr.as_ref() + } + + /// Returns the default raw representation. + pub fn default_repr(&self) -> Repr { + to_key_repr(&self.key) + } + + /// Returns a raw representation. + pub fn display_repr(&self) -> Cow<'_, str> { + self.as_repr() + .and_then(|r| r.as_raw().as_str()) + .map(Cow::Borrowed) + .unwrap_or_else(|| { + Cow::Owned(self.default_repr().as_raw().as_str().unwrap().to_owned()) + }) + } + + /// Returns the surrounding whitespace + pub fn decor_mut(&mut self) -> &mut Decor { + &mut self.decor + } + + /// Returns the surrounding whitespace + pub fn decor(&self) -> &Decor { + &self.decor + } + + /// Returns the location within the original document + #[cfg(feature = "serde")] + pub(crate) fn span(&self) -> Option> { + self.repr.as_ref().and_then(|r| r.span()) + } + + pub(crate) fn despan(&mut self, input: &str) { + self.decor.despan(input); + if let Some(repr) = &mut self.repr { + repr.despan(input) + } + } + + /// Auto formats the key. + pub fn fmt(&mut self) { + self.repr = Some(to_key_repr(&self.key)); + self.decor.clear(); + } + + fn try_parse_simple(s: &str) -> Result { + let mut key = parser::parse_key(s)?; + key.despan(s); + Ok(key) + } + + fn try_parse_path(s: &str) -> Result, crate::TomlError> { + let mut keys = parser::parse_key_path(s)?; + for key in &mut keys { + key.despan(s); + } + Ok(keys) + } +} + +impl Clone for Key { + #[inline(never)] + fn clone(&self) -> Self { + Self { + key: self.key.clone(), + repr: self.repr.clone(), + decor: self.decor.clone(), + } + } +} + +impl std::ops::Deref for Key { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +impl std::hash::Hash for Key { + fn hash(&self, state: &mut H) { + self.get().hash(state); + } +} + +impl Ord for Key { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.get().cmp(other.get()) + } +} + +impl PartialOrd for Key { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Eq for Key {} + +impl PartialEq for Key { + #[inline] + fn eq(&self, other: &Key) -> bool { + PartialEq::eq(self.get(), other.get()) + } +} + +impl PartialEq for Key { + #[inline] + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.get(), other) + } +} + +impl<'s> PartialEq<&'s str> for Key { + #[inline] + fn eq(&self, other: &&str) -> bool { + PartialEq::eq(self.get(), *other) + } +} + +impl PartialEq for Key { + #[inline] + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.get(), other.as_str()) + } +} + +impl std::fmt::Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + crate::encode::Encode::encode(self, f, None, ("", "")) + } +} + +impl FromStr for Key { + type Err = crate::TomlError; + + /// Tries to parse a key from a &str, + /// if fails, tries as basic quoted key (surrounds with "") + /// and then literal quoted key (surrounds with '') + fn from_str(s: &str) -> Result { + Key::try_parse_simple(s) + } +} + +fn to_key_repr(key: &str) -> Repr { + if key.as_bytes().iter().copied().all(is_unquoted_char) && !key.is_empty() { + Repr::new_unchecked(key) + } else { + to_string_repr(key, Some(StringStyle::OnelineSingle), Some(false)) + } +} + +impl<'b> From<&'b str> for Key { + fn from(s: &'b str) -> Self { + Key::new(s) + } +} + +impl<'b> From<&'b String> for Key { + fn from(s: &'b String) -> Self { + Key::new(s) + } +} + +impl From for Key { + fn from(s: String) -> Self { + Key::new(s) + } +} + +impl From for Key { + fn from(s: InternalString) -> Self { + Key::new(s) + } +} + +#[doc(hidden)] +impl From for InternalString { + fn from(key: Key) -> InternalString { + key.key + } +} + +/// A mutable reference to a `Key` +#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct KeyMut<'k> { + key: &'k mut Key, +} + +impl<'k> KeyMut<'k> { + /// Returns the parsed key value. + pub fn get(&self) -> &str { + self.key.get() + } + + /// Returns the raw representation, if available. + pub fn as_repr(&self) -> Option<&Repr> { + self.key.as_repr() + } + + /// Returns the default raw representation. + pub fn default_repr(&self) -> Repr { + self.key.default_repr() + } + + /// Returns a raw representation. + pub fn display_repr(&self) -> Cow { + self.key.display_repr() + } + + /// Returns the surrounding whitespace + pub fn decor_mut(&mut self) -> &mut Decor { + self.key.decor_mut() + } + + /// Returns the surrounding whitespace + pub fn decor(&self) -> &Decor { + self.key.decor() + } + + /// Auto formats the key. + pub fn fmt(&mut self) { + self.key.fmt() + } +} + +impl<'k> std::ops::Deref for KeyMut<'k> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +impl<'s> PartialEq for KeyMut<'s> { + #[inline] + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.get(), other) + } +} + +impl<'s> PartialEq<&'s str> for KeyMut<'s> { + #[inline] + fn eq(&self, other: &&str) -> bool { + PartialEq::eq(self.get(), *other) + } +} + +impl<'s> PartialEq for KeyMut<'s> { + #[inline] + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.get(), other.as_str()) + } +} + +impl<'k> std::fmt::Display for KeyMut<'k> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.key, f) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..80c0ddd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,124 @@ +#![deny(missing_docs)] +// https://github.com/Marwes/combine/issues/172 +#![recursion_limit = "256"] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +//! # `toml_edit` +//! +//! This crate allows you to parse and modify toml +//! documents, while preserving comments, spaces *and +//! relative order* or items. +//! +//! If you also need the ease of a more traditional API, see the [`toml`] crate. +//! +//! # Example +//! +//! ```rust +//! use toml_edit::{Document, value}; +//! +//! let toml = r#" +//! "hello" = 'toml!' # comment +//! ['a'.b] +//! "#; +//! let mut doc = toml.parse::().expect("invalid doc"); +//! assert_eq!(doc.to_string(), toml); +//! // let's add a new key/value pair inside a.b: c = {d = "hello"} +//! doc["a"]["b"]["c"]["d"] = value("hello"); +//! // autoformat inline table a.b.c: { d = "hello" } +//! doc["a"]["b"]["c"].as_inline_table_mut().map(|t| t.fmt()); +//! let expected = r#" +//! "hello" = 'toml!' # comment +//! ['a'.b] +//! c = { d = "hello" } +//! "#; +//! assert_eq!(doc.to_string(), expected); +//! ``` +//! +//! ## Controlling formatting +//! +//! By default, values are created with default formatting +//! ```rust +//! let mut doc = toml_edit::Document::new(); +//! doc["foo"] = toml_edit::value("bar"); +//! let expected = r#"foo = "bar" +//! "#; +//! assert_eq!(doc.to_string(), expected); +//! ``` +//! +//! You can choose a custom TOML representation by parsing the value. +//! ```rust +//! let mut doc = toml_edit::Document::new(); +//! doc["foo"] = "'bar'".parse::().unwrap(); +//! let expected = r#"foo = 'bar' +//! "#; +//! assert_eq!(doc.to_string(), expected); +//! ``` +//! +//! ## Limitations +//! +//! Things it does not preserve: +//! +//! * Scattered array of tables (tables are reordered by default, see [test]). +//! * Order of dotted keys, see [issue](https://github.com/ordian/toml_edit/issues/163). +//! +//! [`toml`]: https://docs.rs/toml/latest/toml/ +//! [test]: https://github.com/ordian/toml_edit/blob/f09bd5d075fdb7d2ef8d9bb3270a34506c276753/tests/test_valid.rs#L84 + +mod array; +mod array_of_tables; +mod document; +mod encode; +mod index; +mod inline_table; +mod internal_string; +mod item; +mod key; +mod parser; +mod raw_string; +mod repr; +mod table; +mod value; + +#[cfg(feature = "serde")] +pub mod de; +#[cfg(feature = "serde")] +pub mod ser; + +pub mod visit; +pub mod visit_mut; + +pub use crate::array::{Array, ArrayIntoIter, ArrayIter, ArrayIterMut}; +pub use crate::array_of_tables::{ + ArrayOfTables, ArrayOfTablesIntoIter, ArrayOfTablesIter, ArrayOfTablesIterMut, +}; +pub use crate::document::Document; +pub use crate::inline_table::{ + InlineEntry, InlineOccupiedEntry, InlineTable, InlineTableIntoIter, InlineTableIter, + InlineTableIterMut, InlineVacantEntry, +}; +pub use crate::internal_string::InternalString; +pub use crate::item::{array, table, value, Item}; +pub use crate::key::{Key, KeyMut}; +pub use crate::parser::TomlError; +pub use crate::raw_string::RawString; +pub use crate::repr::{Decor, Formatted, Repr}; +pub use crate::table::{ + Entry, IntoIter, Iter, IterMut, OccupiedEntry, Table, TableLike, VacantEntry, +}; +pub use crate::value::Value; +pub use toml_datetime::*; + +// Prevent users from some traits. +pub(crate) mod private { + pub trait Sealed {} + impl Sealed for usize {} + impl Sealed for str {} + impl Sealed for String {} + impl Sealed for i64 {} + impl Sealed for f64 {} + impl Sealed for bool {} + impl Sealed for crate::Datetime {} + impl<'a, T: ?Sized> Sealed for &'a T where T: Sealed {} + impl Sealed for crate::Table {} + impl Sealed for crate::InlineTable {} +} diff --git a/src/parser/array.rs b/src/parser/array.rs new file mode 100644 index 0000000..e3b1f3f --- /dev/null +++ b/src/parser/array.rs @@ -0,0 +1,146 @@ +use winnow::combinator::cut_err; +use winnow::combinator::delimited; +use winnow::combinator::opt; +use winnow::combinator::separated1; +use winnow::trace::trace; + +use crate::parser::trivia::ws_comment_newline; +use crate::parser::value::value; +use crate::{Array, Item, RawString, Value}; + +use crate::parser::prelude::*; + +// ;; Array + +// array = array-open array-values array-close +pub(crate) fn array<'i>(check: RecursionCheck) -> impl Parser, Array, ContextError> { + trace("array", move |input: &mut Input<'i>| { + delimited( + ARRAY_OPEN, + cut_err(array_values(check)), + cut_err(ARRAY_CLOSE) + .context(StrContext::Label("array")) + .context(StrContext::Expected(StrContextValue::CharLiteral(']'))), + ) + .parse_next(input) + }) +} + +// note: we're omitting ws and newlines here, because +// they should be part of the formatted values +// array-open = %x5B ws-newline ; [ +pub(crate) const ARRAY_OPEN: u8 = b'['; +// array-close = ws-newline %x5D ; ] +const ARRAY_CLOSE: u8 = b']'; +// array-sep = ws %x2C ws ; , Comma +const ARRAY_SEP: u8 = b','; + +// note: this rule is modified +// array-values = [ ( array-value array-sep array-values ) / +// array-value / ws-comment-newline ] +pub(crate) fn array_values<'i>( + check: RecursionCheck, +) -> impl Parser, Array, ContextError> { + move |input: &mut Input<'i>| { + let check = check.recursing(input)?; + ( + opt( + (separated1(array_value(check), ARRAY_SEP), opt(ARRAY_SEP)).map( + |(v, trailing): (Vec, Option)| { + ( + Array::with_vec(v.into_iter().map(Item::Value).collect()), + trailing.is_some(), + ) + }, + ), + ), + ws_comment_newline.span(), + ) + .try_map::<_, _, std::str::Utf8Error>(|(array, trailing)| { + let (mut array, comma) = array.unwrap_or_default(); + array.set_trailing_comma(comma); + array.set_trailing(RawString::with_span(trailing)); + Ok(array) + }) + .parse_next(input) + } +} + +pub(crate) fn array_value<'i>( + check: RecursionCheck, +) -> impl Parser, Value, ContextError> { + move |input: &mut Input<'i>| { + ( + ws_comment_newline.span(), + value(check), + ws_comment_newline.span(), + ) + .map(|(ws1, v, ws2)| v.decorated(RawString::with_span(ws1), RawString::with_span(ws2))) + .parse_next(input) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn arrays() { + let inputs = [ + r#"[]"#, + r#"[ ]"#, + r#"[ + 1, 2, 3 +]"#, + r#"[ + 1, + 2, # this is ok +]"#, + r#"[# comment +# comment2 + + + ]"#, + r#"[# comment +# comment2 + 1 + +#sd +, +# comment3 + + ]"#, + r#"[1]"#, + r#"[1,]"#, + r#"[ "all", 'strings', """are the same""", '''type''']"#, + r#"[ 100, -2,]"#, + r#"[1, 2, 3]"#, + r#"[1.1, 2.1, 3.1]"#, + r#"["a", "b", "c"]"#, + r#"[ [ 1, 2 ], [3, 4, 5] ]"#, + r#"[ [ 1, 2 ], ["a", "b", "c"] ]"#, + r#"[ { x = 1, a = "2" }, {a = "a",b = "b", c = "c"} ]"#, + ]; + for input in inputs { + dbg!(input); + let mut parsed = array(Default::default()).parse(new_input(input)); + if let Ok(parsed) = &mut parsed { + parsed.despan(input); + } + assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned())); + } + } + + #[test] + fn invalid_arrays() { + let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#]; + for input in invalid_inputs { + dbg!(input); + let mut parsed = array(Default::default()).parse(new_input(input)); + if let Ok(parsed) = &mut parsed { + parsed.despan(input); + } + assert!(parsed.is_err()); + } + } +} diff --git a/src/parser/datetime.rs b/src/parser/datetime.rs new file mode 100644 index 0000000..6e89b97 --- /dev/null +++ b/src/parser/datetime.rs @@ -0,0 +1,446 @@ +use std::ops::RangeInclusive; + +use crate::parser::errors::CustomError; +use crate::parser::prelude::*; +use crate::parser::trivia::from_utf8_unchecked; + +use toml_datetime::*; +use winnow::combinator::alt; +use winnow::combinator::cut_err; +use winnow::combinator::opt; +use winnow::combinator::preceded; +use winnow::token::one_of; +use winnow::token::take_while; +use winnow::trace::trace; + +// ;; Date and Time (as defined in RFC 3339) + +// date-time = offset-date-time / local-date-time / local-date / local-time +// offset-date-time = full-date time-delim full-time +// local-date-time = full-date time-delim partial-time +// local-date = full-date +// local-time = partial-time +// full-time = partial-time time-offset +pub(crate) fn date_time(input: &mut Input<'_>) -> PResult { + trace( + "date-time", + alt(( + (full_date, opt((time_delim, partial_time, opt(time_offset)))) + .map(|(date, opt)| { + match opt { + // Offset Date-Time + Some((_, time, offset)) => Datetime { + date: Some(date), + time: Some(time), + offset, + }, + // Local Date + None => Datetime { + date: Some(date), + time: None, + offset: None, + }, + } + }) + .context(StrContext::Label("date-time")), + partial_time + .map(|t| t.into()) + .context(StrContext::Label("time")), + )), + ) + .parse_next(input) +} + +// full-date = date-fullyear "-" date-month "-" date-mday +pub(crate) fn full_date(input: &mut Input<'_>) -> PResult { + trace( + "full-date", + (date_fullyear, b'-', cut_err((date_month, b'-', date_mday))) + .map(|(year, _, (month, _, day))| Date { year, month, day }), + ) + .parse_next(input) +} + +// partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] +pub(crate) fn partial_time(input: &mut Input<'_>) -> PResult