From 310634c695a6514ea726a511c126d5cf92105cb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Apr 2026 14:03:30 +0000 Subject: [PATCH] Replace CSV plugin with ZON (Zig Object Notation) parser Converts this jsonic plugin from parsing CSV to parsing Zig Object Notation (ZON), the format used by build.zig.zon and similar Zig configuration files. The package is renamed @jsonic/csv to @jsonic/zon, and the Go module path becomes github.com/jsonicjs/zon/go. Key design points: - `.{` is disambiguated at lex time into #OB (struct/map) or #OS (tuple/list) by peeking ahead for `.ident =`, so a single two-token grammar lookahead is sufficient and `}` (lexed as #CB) closes both struct and tuple literals. - A custom lex matcher emits `.identifier` as a #TX token whose val is the identifier (sans dot) and whose use flag marks it as an enum literal for optional wrapping via the `enumTag` option. - Zig multi-line strings (`\\`-prefixed consecutive lines) and Zig character literals (including `\x..`, `\u{...}`) have dedicated matchers; the `charAsNumber` option returns character literals as code points instead of one-char strings. - The grammar text embedded into both the TS and Go sources only prepends ZON-specific alts to the base json rules and swaps the list terminator from #CS to #CB; `rule.exclude: 'jsonic,imp'` strips the implicit-top-level behaviours that are not valid ZON. Both TypeScript (19 tests) and Go (20 tests) test suites pass. Pinned to jsonic@2.24.0 (npm) and github.com/jsonicjs/jsonic/go@v0.1.18. https://claude.ai/code/session_01TLYwCQbxYQNqzzZstKtRCx --- Makefile | 4 +- README.md | 88 +- coverage/lcov.info | 340 ------- csv-grammar.jsonic | 52 -- doc/csv-go.md | 264 ------ doc/csv-ts.md | 286 ------ embed-grammar.js | 12 +- go/csv.go | 780 ---------------- go/csv_test.go | 527 ----------- go/go.mod | 2 +- go/zon.go | 596 ++++++++++++ go/zon_test.go | 205 ++++ package.json | 26 +- src/csv.ts | 562 ----------- src/zon.ts | 381 ++++++++ test/csv.test.ts | 392 -------- test/fixtures/basic-array.json | 1 - test/fixtures/basic-noheader-names.json | 1 - test/fixtures/basic-noheader.json | 1 - test/fixtures/basic.csv | 3 - test/fixtures/basic.json | 1 - test/fixtures/comment-empty.csv | 8 - test/fixtures/comment-empty.json | 1 - test/fixtures/comment-inline.csv | 3 - test/fixtures/comment-inline.json | 1 - test/fixtures/comment-line.csv | 5 - test/fixtures/comment-line.json | 1 - test/fixtures/crlf.csv | 3 - test/fixtures/crlf.json | 1 - test/fixtures/empty-fields.csv | 5 - test/fixtures/empty-fields.json | 1 - test/fixtures/empty-records-default.json | 1 - test/fixtures/empty-records.csv | 8 - test/fixtures/empty-records.json | 1 - test/fixtures/happy.csv | 3 - test/fixtures/happy.json | 12 - test/fixtures/leading-newline.csv | 3 - test/fixtures/leading-newline.json | 1 - test/fixtures/manifest.json | 872 ------------------ test/fixtures/multi-char-separator.csv | 3 - test/fixtures/multi-char-separator.json | 1 - test/fixtures/multirow.csv | 4 - test/fixtures/multirow.json | 1 - test/fixtures/notrim.csv | 5 - test/fixtures/notrim.json | 22 - test/fixtures/number.csv | 3 - test/fixtures/number.json | 1 - test/fixtures/papa-ascii-30-delimiter.csv | 2 - test/fixtures/papa-ascii-30-delimiter.json | 12 - test/fixtures/papa-ascii-31-delimiter.csv | 2 - test/fixtures/papa-ascii-31-delimiter.json | 12 - .../papa-bad-comments-value-specified.csv | 3 - .../papa-bad-comments-value-specified.json | 15 - ...r-inside-quotes-with-line-feed-endings.csv | 6 - ...-inside-quotes-with-line-feed-endings.json | 22 - ...apa-comment-with-non-default-character.csv | 3 - ...pa-comment-with-non-default-character.json | 12 - .../papa-commented-line-at-beginning.csv | 2 - .../papa-commented-line-at-beginning.json | 7 - test/fixtures/papa-commented-line-at-end.csv | 2 - test/fixtures/papa-commented-line-at-end.json | 7 - .../papa-commented-line-in-middle.csv | 3 - .../papa-commented-line-in-middle.json | 12 - ...apa-custom-escape-character-at-the-end.csv | 1 - ...pa-custom-escape-character-at-the-end.json | 7 - ...-custom-escape-character-in-the-middle.csv | 1 - ...custom-escape-character-in-the-middle.json | 7 - ...namic-typing-converts-boolean-literals.csv | 1 - ...amic-typing-converts-boolean-literals.json | 12 - ...mic-typing-doesn-t-convert-other-types.csv | 3 - ...ic-typing-doesn-t-convert-other-types.json | 17 - test/fixtures/papa-empty-input-string-2.csv | 0 test/fixtures/papa-empty-input-string-2.json | 1 - test/fixtures/papa-empty-input-string.csv | 0 test/fixtures/papa-empty-input-string.json | 1 - test/fixtures/papa-empty-lines.csv | 5 - test/fixtures/papa-empty-lines.json | 15 - ...apa-empty-quoted-field-at-eof-is-empty.csv | 2 - ...pa-empty-quoted-field-at-eof-is-empty.json | 12 - .../papa-entire-file-is-comment-lines.csv | 3 - .../papa-entire-file-is-comment-lines.json | 1 - .../papa-first-field-of-a-line-is-empty.csv | 2 - .../papa-first-field-of-a-line-is-empty.json | 12 - test/fixtures/papa-header-row-only.csv | 1 - test/fixtures/papa-header-row-only.json | 1 - .../papa-header-row-with-one-row-of-data.csv | 2 - .../papa-header-row-with-one-row-of-data.json | 7 - ...papa-header-row-with-preceding-comment.csv | 3 - ...apa-header-row-with-preceding-comment.json | 6 - ...nput-is-just-a-string-a-single-field-2.csv | 1 - ...put-is-just-a-string-a-single-field-2.json | 5 - ...-input-is-just-a-string-a-single-field.csv | 1 - ...input-is-just-a-string-a-single-field.json | 5 - .../papa-input-is-just-empty-fields.csv | 2 - .../papa-input-is-just-empty-fields.json | 13 - ...is-just-the-delimiter-2-empty-fields-2.csv | 1 - ...s-just-the-delimiter-2-empty-fields-2.json | 6 - ...t-is-just-the-delimiter-2-empty-fields.csv | 1 - ...-is-just-the-delimiter-2-empty-fields.json | 6 - ...ommented-line-without-comments-enabled.csv | 1 - ...mmented-line-without-comments-enabled.json | 5 - .../papa-input-with-only-a-commented-line.csv | 1 - ...papa-input-with-only-a-commented-line.json | 1 - ...nts-with-line-starting-with-whitespace.csv | 3 - ...ts-with-line-starting-with-whitespace.json | 11 - .../papa-jsonic-blank-line-at-beginning.csv | 3 - .../papa-jsonic-blank-line-at-beginning.json | 13 - ...c-blank-line-in-middle-with-whitespace.csv | 3 - ...-blank-line-in-middle-with-whitespace.json | 15 - .../papa-jsonic-blank-line-in-middle.csv | 3 - .../papa-jsonic-blank-line-in-middle.json | 13 - .../papa-jsonic-blank-lines-at-end.csv | 3 - .../papa-jsonic-blank-lines-at-end.json | 13 - ...-a-commented-line-and-blank-line-after.csv | 1 - ...a-commented-line-and-blank-line-after.json | 1 - ...nic-one-column-input-with-empty-fields.csv | 7 - ...ic-one-column-input-with-empty-fields.json | 19 - .../papa-last-field-of-a-line-is-empty.csv | 2 - .../papa-last-field-of-a-line-is-empty.json | 12 - ...ld-first-field-of-next-line-is-empty-n.csv | 4 - ...d-first-field-of-next-line-is-empty-n.json | 22 - .../papa-line-ends-with-quoted-field.csv | 4 - .../papa-line-ends-with-quoted-field.json | 22 - .../papa-line-starts-with-quoted-field.csv | 2 - .../papa-line-starts-with-quoted-field.json | 12 - ...-line-starts-with-unquoted-empty-field.csv | 2 - ...line-starts-with-unquoted-empty-field.json | 12 - ...d-quotes-in-data-not-as-opening-quotes.csv | 1 - ...-quotes-in-data-not-as-opening-quotes.json | 7 - ...es-in-data-twice-not-as-opening-quotes.csv | 2 - ...s-in-data-twice-not-as-opening-quotes.json | 12 - .../papa-multi-character-comment-string.csv | 3 - .../papa-multi-character-comment-string.json | 12 - ...r-delimiter-length-2-with-quoted-field.csv | 1 - ...-delimiter-length-2-with-quoted-field.json | 8 - .../papa-multi-character-delimiter.csv | 1 - .../papa-multi-character-delimiter.json | 7 - ...papa-multiple-consecutive-empty-fields.csv | 2 - ...apa-multiple-consecutive-empty-fields.json | 18 - ...ple-rows-one-column-no-delimiter-found.csv | 5 - ...le-rows-one-column-no-delimiter-found.json | 17 - test/fixtures/papa-one-row.csv | 1 - test/fixtures/papa-one-row.json | 7 - test/fixtures/papa-other-fields-are-empty.csv | 2 - .../fixtures/papa-other-fields-are-empty.json | 12 - test/fixtures/papa-pipe-delimiter.csv | 2 - test/fixtures/papa-pipe-delimiter.json | 12 - ...t-end-of-row-but-not-at-eof-has-quotes.csv | 2 - ...-end-of-row-but-not-at-eof-has-quotes.json | 12 - ...miters-and-n-with-valid-trailing-quote.csv | 2 - ...iters-and-n-with-valid-trailing-quote.json | 6 - ...te-after-delimiter-with-a-valid-closer.csv | 2 - ...invalid-trailing-quote-after-delimiter.csv | 2 - ...-invalid-trailing-quote-after-new-line.csv | 2 - ...nvalid-trailing-quote-before-delimiter.csv | 2 - ...papa-quoted-field-has-no-closing-quote.csv | 2 - ...has-valid-trailing-quote-via-delimiter.csv | 2 - ...as-valid-trailing-quote-via-delimiter.json | 12 - ...field-has-valid-trailing-quote-via-eof.csv | 2 - ...ield-has-valid-trailing-quote-via-eof.json | 12 - ...d-field-has-valid-trailing-quote-via-n.csv | 2 - ...-field-has-valid-trailing-quote-via-n.json | 12 - ...-in-a-row-and-a-delimiter-in-there-too.csv | 1 - ...in-a-row-and-a-delimiter-in-there-too.json | 7 - .../papa-quoted-field-with-delimiter.csv | 1 - .../papa-quoted-field-with-delimiter.json | 7 - ...ield-with-escaped-quotes-at-boundaries.csv | 1 - ...eld-with-escaped-quotes-at-boundaries.json | 7 - .../papa-quoted-field-with-escaped-quotes.csv | 1 - ...papa-quoted-field-with-escaped-quotes.json | 7 - ...d-field-with-extra-whitespace-on-edges.csv | 1 - ...-field-with-extra-whitespace-on-edges.json | 7 - .../papa-quoted-field-with-line-break.csv | 2 - .../papa-quoted-field-with-line-break.json | 7 - test/fixtures/papa-quoted-field-with-n.csv | 2 - test/fixtures/papa-quoted-field-with-n.json | 7 - ...ted-field-with-quotes-around-delimiter.csv | 1 - ...ed-field-with-quotes-around-delimiter.json | 7 - ...-with-quotes-on-left-side-of-delimiter.csv | 1 - ...with-quotes-on-left-side-of-delimiter.json | 7 - ...with-quotes-on-right-side-of-delimiter.csv | 1 - ...ith-quotes-on-right-side-of-delimiter.json | 7 - test/fixtures/papa-quoted-field-with-r-n.csv | 2 - test/fixtures/papa-quoted-field-with-r-n.json | 7 - test/fixtures/papa-quoted-field-with-r.csv | 1 - test/fixtures/papa-quoted-field-with-r.json | 7 - ...ed-field-with-whitespace-around-quotes.csv | 1 - ...d-field-with-whitespace-around-quotes.json | 7 - test/fixtures/papa-quoted-field.csv | 1 - test/fixtures/papa-quoted-field.json | 7 - ...d-of-row-with-delimiter-and-line-break.csv | 3 - ...-of-row-with-delimiter-and-line-break.json | 12 - .../papa-quoted-fields-with-line-breaks.csv | 4 - .../papa-quoted-fields-with-line-breaks.json | 7 - ...fields-with-spaces-after-closing-quote.csv | 3 - ...ields-with-spaces-after-closing-quote.json | 20 - ...quote-and-newline-and-contains-newline.csv | 3 - ...uote-and-newline-and-contains-newline.json | 12 - ...-next-delimiter-and-contains-delimiter.csv | 2 - ...next-delimiter-and-contains-delimiter.json | 14 - ...tween-closing-quote-and-next-delimiter.csv | 2 - ...ween-closing-quote-and-next-delimiter.json | 14 - ...etween-closing-quote-and-next-new-line.csv | 3 - ...tween-closing-quote-and-next-new-line.json | 20 - ...h-enough-fields-but-blank-field-at-end.csv | 2 - ...-enough-fields-but-blank-field-at-end.json | 7 - ...nk-field-in-the-begining-using-headers.csv | 3 - ...k-field-in-the-begining-using-headers.json | 12 - ...fields-but-blank-field-in-the-begining.csv | 3 - ...ields-but-blank-field-in-the-begining.json | 17 - .../fixtures/papa-row-with-too-few-fields.csv | 2 - .../papa-row-with-too-many-fields.csv | 3 - .../papa-single-quote-as-quote-character.csv | 1 - .../papa-single-quote-as-quote-character.json | 7 - ...papa-skip-empty-lines-with-empty-input.csv | 0 ...apa-skip-empty-lines-with-empty-input.json | 1 - ...-lines-with-first-line-only-whitespace.csv | 2 - ...lines-with-first-line-only-whitespace.json | 10 - ...pty-lines-with-newline-at-end-of-input.csv | 3 - ...ty-lines-with-newline-at-end-of-input.json | 12 - test/fixtures/papa-skip-empty-lines.csv | 3 - test/fixtures/papa-skip-empty-lines.json | 12 - test/fixtures/papa-tab-delimiter.csv | 2 - test/fixtures/papa-tab-delimiter.json | 12 - ...nes-consecutively-at-beginning-of-file.csv | 4 - ...es-consecutively-at-beginning-of-file.json | 7 - test/fixtures/papa-three-rows.csv | 3 - test/fixtures/papa-three-rows.json | 17 - ...ent-lines-consecutively-at-end-of-file.csv | 3 - ...nt-lines-consecutively-at-end-of-file.json | 7 - .../papa-two-comment-lines-consecutively.csv | 4 - .../papa-two-comment-lines-consecutively.json | 12 - test/fixtures/papa-two-rows-just-r.csv | 1 - test/fixtures/papa-two-rows-just-r.json | 12 - test/fixtures/papa-two-rows-r-n.csv | 2 - test/fixtures/papa-two-rows-r-n.json | 12 - test/fixtures/papa-two-rows.csv | 2 - test/fixtures/papa-two-rows.json | 12 - ...oted-field-with-quotes-at-end-of-field.csv | 1 - ...ted-field-with-quotes-at-end-of-field.json | 7 - .../papa-using-n-endings-uses-n-linebreak.csv | 5 - ...papa-using-n-endings-uses-n-linebreak.json | 22 - ...h-r-n-in-header-field-uses-n-linebreak.csv | 6 - ...-r-n-in-header-field-uses-n-linebreak.json | 22 - ...a-using-r-n-endings-uses-r-n-linebreak.csv | 5 - ...-using-r-n-endings-uses-r-n-linebreak.json | 22 - ...h-n-in-header-field-uses-r-n-linebreak.csv | 6 - ...-n-in-header-field-uses-r-n-linebreak.json | 22 - ...th-skip-empty-lines-uses-r-n-linebreak.csv | 6 - ...h-skip-empty-lines-uses-r-n-linebreak.json | 22 - ...r-n-in-header-field-uses-r-n-linebreak.csv | 6 - ...-n-in-header-field-uses-r-n-linebreak.json | 22 - ...ved-regex-character-as-quote-character.csv | 6 - ...ed-regex-character-as-quote-character.json | 22 - ...-whitespace-at-edges-of-unquoted-field.csv | 1 - ...whitespace-at-edges-of-unquoted-field.json | 7 - test/fixtures/pipe-separator.csv | 3 - test/fixtures/pipe-separator.json | 1 - test/fixtures/quote.csv | 3 - test/fixtures/quote.json | 12 - test/fixtures/quoted-escape.csv | 6 - test/fixtures/quoted-escape.json | 1 - test/fixtures/quoted-newline.csv | 5 - test/fixtures/quoted-newline.json | 1 - test/fixtures/quoted-simple.csv | 3 - test/fixtures/quoted-simple.json | 1 - test/fixtures/record-separator.csv | 1 - test/fixtures/record-separator.json | 1 - test/fixtures/trailing-newline.csv | 3 - test/fixtures/trailing-newline.json | 1 - test/fixtures/trim.csv | 3 - test/fixtures/trim.json | 22 - test/fixtures/value.csv | 3 - test/fixtures/value.json | 1 - test/quick.js | 136 --- test/zon.test.ts | 162 ++++ zon-grammar.jsonic | 50 + 277 files changed, 1472 insertions(+), 5799 deletions(-) delete mode 100644 coverage/lcov.info delete mode 100644 csv-grammar.jsonic delete mode 100644 doc/csv-go.md delete mode 100644 doc/csv-ts.md delete mode 100644 go/csv.go delete mode 100644 go/csv_test.go create mode 100644 go/zon.go create mode 100644 go/zon_test.go delete mode 100644 src/csv.ts create mode 100644 src/zon.ts delete mode 100644 test/csv.test.ts delete mode 100644 test/fixtures/basic-array.json delete mode 100644 test/fixtures/basic-noheader-names.json delete mode 100644 test/fixtures/basic-noheader.json delete mode 100644 test/fixtures/basic.csv delete mode 100644 test/fixtures/basic.json delete mode 100644 test/fixtures/comment-empty.csv delete mode 100644 test/fixtures/comment-empty.json delete mode 100644 test/fixtures/comment-inline.csv delete mode 100644 test/fixtures/comment-inline.json delete mode 100644 test/fixtures/comment-line.csv delete mode 100644 test/fixtures/comment-line.json delete mode 100644 test/fixtures/crlf.csv delete mode 100644 test/fixtures/crlf.json delete mode 100644 test/fixtures/empty-fields.csv delete mode 100644 test/fixtures/empty-fields.json delete mode 100644 test/fixtures/empty-records-default.json delete mode 100644 test/fixtures/empty-records.csv delete mode 100644 test/fixtures/empty-records.json delete mode 100644 test/fixtures/happy.csv delete mode 100644 test/fixtures/happy.json delete mode 100644 test/fixtures/leading-newline.csv delete mode 100644 test/fixtures/leading-newline.json delete mode 100644 test/fixtures/manifest.json delete mode 100644 test/fixtures/multi-char-separator.csv delete mode 100644 test/fixtures/multi-char-separator.json delete mode 100644 test/fixtures/multirow.csv delete mode 100644 test/fixtures/multirow.json delete mode 100644 test/fixtures/notrim.csv delete mode 100644 test/fixtures/notrim.json delete mode 100644 test/fixtures/number.csv delete mode 100644 test/fixtures/number.json delete mode 100644 test/fixtures/papa-ascii-30-delimiter.csv delete mode 100644 test/fixtures/papa-ascii-30-delimiter.json delete mode 100644 test/fixtures/papa-ascii-31-delimiter.csv delete mode 100644 test/fixtures/papa-ascii-31-delimiter.json delete mode 100644 test/fixtures/papa-bad-comments-value-specified.csv delete mode 100644 test/fixtures/papa-bad-comments-value-specified.json delete mode 100644 test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.csv delete mode 100644 test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.json delete mode 100644 test/fixtures/papa-comment-with-non-default-character.csv delete mode 100644 test/fixtures/papa-comment-with-non-default-character.json delete mode 100644 test/fixtures/papa-commented-line-at-beginning.csv delete mode 100644 test/fixtures/papa-commented-line-at-beginning.json delete mode 100644 test/fixtures/papa-commented-line-at-end.csv delete mode 100644 test/fixtures/papa-commented-line-at-end.json delete mode 100644 test/fixtures/papa-commented-line-in-middle.csv delete mode 100644 test/fixtures/papa-commented-line-in-middle.json delete mode 100644 test/fixtures/papa-custom-escape-character-at-the-end.csv delete mode 100644 test/fixtures/papa-custom-escape-character-at-the-end.json delete mode 100644 test/fixtures/papa-custom-escape-character-in-the-middle.csv delete mode 100644 test/fixtures/papa-custom-escape-character-in-the-middle.json delete mode 100644 test/fixtures/papa-dynamic-typing-converts-boolean-literals.csv delete mode 100644 test/fixtures/papa-dynamic-typing-converts-boolean-literals.json delete mode 100644 test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.csv delete mode 100644 test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.json delete mode 100644 test/fixtures/papa-empty-input-string-2.csv delete mode 100644 test/fixtures/papa-empty-input-string-2.json delete mode 100644 test/fixtures/papa-empty-input-string.csv delete mode 100644 test/fixtures/papa-empty-input-string.json delete mode 100644 test/fixtures/papa-empty-lines.csv delete mode 100644 test/fixtures/papa-empty-lines.json delete mode 100644 test/fixtures/papa-empty-quoted-field-at-eof-is-empty.csv delete mode 100644 test/fixtures/papa-empty-quoted-field-at-eof-is-empty.json delete mode 100644 test/fixtures/papa-entire-file-is-comment-lines.csv delete mode 100644 test/fixtures/papa-entire-file-is-comment-lines.json delete mode 100644 test/fixtures/papa-first-field-of-a-line-is-empty.csv delete mode 100644 test/fixtures/papa-first-field-of-a-line-is-empty.json delete mode 100644 test/fixtures/papa-header-row-only.csv delete mode 100644 test/fixtures/papa-header-row-only.json delete mode 100644 test/fixtures/papa-header-row-with-one-row-of-data.csv delete mode 100644 test/fixtures/papa-header-row-with-one-row-of-data.json delete mode 100644 test/fixtures/papa-header-row-with-preceding-comment.csv delete mode 100644 test/fixtures/papa-header-row-with-preceding-comment.json delete mode 100644 test/fixtures/papa-input-is-just-a-string-a-single-field-2.csv delete mode 100644 test/fixtures/papa-input-is-just-a-string-a-single-field-2.json delete mode 100644 test/fixtures/papa-input-is-just-a-string-a-single-field.csv delete mode 100644 test/fixtures/papa-input-is-just-a-string-a-single-field.json delete mode 100644 test/fixtures/papa-input-is-just-empty-fields.csv delete mode 100644 test/fixtures/papa-input-is-just-empty-fields.json delete mode 100644 test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.csv delete mode 100644 test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.json delete mode 100644 test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.csv delete mode 100644 test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.json delete mode 100644 test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.csv delete mode 100644 test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.json delete mode 100644 test/fixtures/papa-input-with-only-a-commented-line.csv delete mode 100644 test/fixtures/papa-input-with-only-a-commented-line.json delete mode 100644 test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.csv delete mode 100644 test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.json delete mode 100644 test/fixtures/papa-jsonic-blank-line-at-beginning.csv delete mode 100644 test/fixtures/papa-jsonic-blank-line-at-beginning.json delete mode 100644 test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.csv delete mode 100644 test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.json delete mode 100644 test/fixtures/papa-jsonic-blank-line-in-middle.csv delete mode 100644 test/fixtures/papa-jsonic-blank-line-in-middle.json delete mode 100644 test/fixtures/papa-jsonic-blank-lines-at-end.csv delete mode 100644 test/fixtures/papa-jsonic-blank-lines-at-end.json delete mode 100644 test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.csv delete mode 100644 test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.json delete mode 100644 test/fixtures/papa-jsonic-one-column-input-with-empty-fields.csv delete mode 100644 test/fixtures/papa-jsonic-one-column-input-with-empty-fields.json delete mode 100644 test/fixtures/papa-last-field-of-a-line-is-empty.csv delete mode 100644 test/fixtures/papa-last-field-of-a-line-is-empty.json delete mode 100644 test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.csv delete mode 100644 test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.json delete mode 100644 test/fixtures/papa-line-ends-with-quoted-field.csv delete mode 100644 test/fixtures/papa-line-ends-with-quoted-field.json delete mode 100644 test/fixtures/papa-line-starts-with-quoted-field.csv delete mode 100644 test/fixtures/papa-line-starts-with-quoted-field.json delete mode 100644 test/fixtures/papa-line-starts-with-unquoted-empty-field.csv delete mode 100644 test/fixtures/papa-line-starts-with-unquoted-empty-field.json delete mode 100644 test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.csv delete mode 100644 test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.json delete mode 100644 test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.csv delete mode 100644 test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.json delete mode 100644 test/fixtures/papa-multi-character-comment-string.csv delete mode 100644 test/fixtures/papa-multi-character-comment-string.json delete mode 100644 test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.csv delete mode 100644 test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.json delete mode 100644 test/fixtures/papa-multi-character-delimiter.csv delete mode 100644 test/fixtures/papa-multi-character-delimiter.json delete mode 100644 test/fixtures/papa-multiple-consecutive-empty-fields.csv delete mode 100644 test/fixtures/papa-multiple-consecutive-empty-fields.json delete mode 100644 test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.csv delete mode 100644 test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.json delete mode 100644 test/fixtures/papa-one-row.csv delete mode 100644 test/fixtures/papa-one-row.json delete mode 100644 test/fixtures/papa-other-fields-are-empty.csv delete mode 100644 test/fixtures/papa-other-fields-are-empty.json delete mode 100644 test/fixtures/papa-pipe-delimiter.csv delete mode 100644 test/fixtures/papa-pipe-delimiter.json delete mode 100644 test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.csv delete mode 100644 test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.json delete mode 100644 test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.csv delete mode 100644 test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.json delete mode 100644 test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter-with-a-valid-closer.csv delete mode 100644 test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-new-line.csv delete mode 100644 test/fixtures/papa-quoted-field-has-invalid-trailing-quote-before-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-field-has-no-closing-quote.csv delete mode 100644 test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.json delete mode 100644 test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.csv delete mode 100644 test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.json delete mode 100644 test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.csv delete mode 100644 test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.json delete mode 100644 test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.csv delete mode 100644 test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.json delete mode 100644 test/fixtures/papa-quoted-field-with-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-field-with-delimiter.json delete mode 100644 test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.csv delete mode 100644 test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.json delete mode 100644 test/fixtures/papa-quoted-field-with-escaped-quotes.csv delete mode 100644 test/fixtures/papa-quoted-field-with-escaped-quotes.json delete mode 100644 test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.csv delete mode 100644 test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.json delete mode 100644 test/fixtures/papa-quoted-field-with-line-break.csv delete mode 100644 test/fixtures/papa-quoted-field-with-line-break.json delete mode 100644 test/fixtures/papa-quoted-field-with-n.csv delete mode 100644 test/fixtures/papa-quoted-field-with-n.json delete mode 100644 test/fixtures/papa-quoted-field-with-quotes-around-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-field-with-quotes-around-delimiter.json delete mode 100644 test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.json delete mode 100644 test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.json delete mode 100644 test/fixtures/papa-quoted-field-with-r-n.csv delete mode 100644 test/fixtures/papa-quoted-field-with-r-n.json delete mode 100644 test/fixtures/papa-quoted-field-with-r.csv delete mode 100644 test/fixtures/papa-quoted-field-with-r.json delete mode 100644 test/fixtures/papa-quoted-field-with-whitespace-around-quotes.csv delete mode 100644 test/fixtures/papa-quoted-field-with-whitespace-around-quotes.json delete mode 100644 test/fixtures/papa-quoted-field.csv delete mode 100644 test/fixtures/papa-quoted-field.json delete mode 100644 test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.csv delete mode 100644 test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.json delete mode 100644 test/fixtures/papa-quoted-fields-with-line-breaks.csv delete mode 100644 test/fixtures/papa-quoted-fields-with-line-breaks.json delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.csv delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.json delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.csv delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.json delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.json delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.csv delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.json delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.csv delete mode 100644 test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.json delete mode 100644 test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.csv delete mode 100644 test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.json delete mode 100644 test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.csv delete mode 100644 test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.json delete mode 100644 test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.csv delete mode 100644 test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.json delete mode 100644 test/fixtures/papa-row-with-too-few-fields.csv delete mode 100644 test/fixtures/papa-row-with-too-many-fields.csv delete mode 100644 test/fixtures/papa-single-quote-as-quote-character.csv delete mode 100644 test/fixtures/papa-single-quote-as-quote-character.json delete mode 100644 test/fixtures/papa-skip-empty-lines-with-empty-input.csv delete mode 100644 test/fixtures/papa-skip-empty-lines-with-empty-input.json delete mode 100644 test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.csv delete mode 100644 test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.json delete mode 100644 test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.csv delete mode 100644 test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.json delete mode 100644 test/fixtures/papa-skip-empty-lines.csv delete mode 100644 test/fixtures/papa-skip-empty-lines.json delete mode 100644 test/fixtures/papa-tab-delimiter.csv delete mode 100644 test/fixtures/papa-tab-delimiter.json delete mode 100644 test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.csv delete mode 100644 test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.json delete mode 100644 test/fixtures/papa-three-rows.csv delete mode 100644 test/fixtures/papa-three-rows.json delete mode 100644 test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.csv delete mode 100644 test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.json delete mode 100644 test/fixtures/papa-two-comment-lines-consecutively.csv delete mode 100644 test/fixtures/papa-two-comment-lines-consecutively.json delete mode 100644 test/fixtures/papa-two-rows-just-r.csv delete mode 100644 test/fixtures/papa-two-rows-just-r.json delete mode 100644 test/fixtures/papa-two-rows-r-n.csv delete mode 100644 test/fixtures/papa-two-rows-r-n.json delete mode 100644 test/fixtures/papa-two-rows.csv delete mode 100644 test/fixtures/papa-two-rows.json delete mode 100644 test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.csv delete mode 100644 test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.json delete mode 100644 test/fixtures/papa-using-n-endings-uses-n-linebreak.csv delete mode 100644 test/fixtures/papa-using-n-endings-uses-n-linebreak.json delete mode 100644 test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.csv delete mode 100644 test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.json delete mode 100644 test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.csv delete mode 100644 test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.json delete mode 100644 test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.csv delete mode 100644 test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.json delete mode 100644 test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.csv delete mode 100644 test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.json delete mode 100644 test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.csv delete mode 100644 test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.json delete mode 100644 test/fixtures/papa-using-reserved-regex-character-as-quote-character.csv delete mode 100644 test/fixtures/papa-using-reserved-regex-character-as-quote-character.json delete mode 100644 test/fixtures/papa-whitespace-at-edges-of-unquoted-field.csv delete mode 100644 test/fixtures/papa-whitespace-at-edges-of-unquoted-field.json delete mode 100644 test/fixtures/pipe-separator.csv delete mode 100644 test/fixtures/pipe-separator.json delete mode 100644 test/fixtures/quote.csv delete mode 100644 test/fixtures/quote.json delete mode 100644 test/fixtures/quoted-escape.csv delete mode 100644 test/fixtures/quoted-escape.json delete mode 100644 test/fixtures/quoted-newline.csv delete mode 100644 test/fixtures/quoted-newline.json delete mode 100644 test/fixtures/quoted-simple.csv delete mode 100644 test/fixtures/quoted-simple.json delete mode 100644 test/fixtures/record-separator.csv delete mode 100644 test/fixtures/record-separator.json delete mode 100644 test/fixtures/trailing-newline.csv delete mode 100644 test/fixtures/trailing-newline.json delete mode 100644 test/fixtures/trim.csv delete mode 100644 test/fixtures/trim.json delete mode 100644 test/fixtures/value.csv delete mode 100644 test/fixtures/value.json delete mode 100644 test/quick.js create mode 100644 test/zon.test.ts create mode 100644 zon-grammar.jsonic diff --git a/Makefile b/Makefile index 55547ce..aaa4ebe 100644 --- a/Makefile +++ b/Makefile @@ -31,8 +31,8 @@ clean-go: # Publish Go module: make publish-go V=0.1.7 publish-go: test-go @test -n "$(V)" || (echo "Usage: make publish-go V=x.y.z" && exit 1) - sed -i '' 's/^const Version = ".*"/const Version = "$(V)"/' go/csv.go - git add go/csv .go + sed -i '' 's/^const Version = ".*"/const Version = "$(V)"/' go/zon.go + git add go/zon.go git commit -m "go: v$(V)" git tag go/v$(V) git push origin main go/v$(V) diff --git a/README.md b/README.md index 928ccdb..7685447 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,30 @@ -# @jsonic/csv +# @jsonic/zon A [Jsonic](https://jsonic.senecajs.org) syntax plugin that parses -CSV text into objects or arrays, with support for headers, quoted -fields, custom delimiters, streaming, and strict/non-strict modes. -Available for TypeScript and Go. - - -[![npm version](https://img.shields.io/npm/v/@jsonic/csv.svg)](https://npmjs.com/package/@jsonic/csv) -[![build](https://github.com/jsonicjs/csv/actions/workflows/build.yml/badge.svg)](https://github.com/jsonicjs/csv/actions/workflows/build.yml) -[![Coverage Status](https://coveralls.io/repos/github/jsonicjs/csv/badge.svg?branch=main)](https://coveralls.io/github/jsonicjs/csv?branch=main) -[![Known Vulnerabilities](https://snyk.io/test/github/jsonicjs/csv/badge.svg)](https://snyk.io/test/github/jsonicjs/csv) -[![DeepScan grade](https://deepscan.io/api/teams/5016/projects/22466/branches/663906/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=5016&pid=22466&bid=663906) -[![Maintainability](https://api.codeclimate.com/v1/badges/10e9bede600896c77ce8/maintainability)](https://codeclimate.com/github/jsonicjs/csv/maintainability) - -| ![Voxgig](https://www.voxgig.com/res/img/vgt01r.png) | This open source module is sponsored and supported by [Voxgig](https://www.voxgig.com). | -| ---------------------------------------------------- | --------------------------------------------------------------------------------------- | - +[Zig Object Notation (ZON)](https://ziglang.org/documentation/master/#ZON) +text into objects, arrays, and scalar values. Available for +TypeScript and Go. + +ZON is the data format used for Zig `build.zig.zon` manifests and +similar configuration files. It is based on Zig anonymous struct +literals, and looks like this: + +```zon +.{ + .name = "example", + .version = "0.0.1", + .dependencies = .{ + .foo = .{ + .url = "https://example.com/foo.tar.gz", + .hash = "1220deadbeef", + }, + }, + .paths = .{ + "build.zig", + "src", + }, +} +``` ## Quick example @@ -23,37 +32,52 @@ Available for TypeScript and Go. ```typescript import { Jsonic } from 'jsonic' -import { Csv } from '@jsonic/csv' +import { Zon } from '@jsonic/zon' -const parse = Jsonic.make().use(Csv) +const parse = Jsonic.make().use(Zon) -parse("name,age\nAlice,30\nBob,25") -// [{ name: 'Alice', age: '30' }, { name: 'Bob', age: '25' }] +parse('.{ .name = "Alice", .age = 30 }') +// { name: 'Alice', age: 30 } -parse('a,b\n1,"hello, world"') -// [{ a: '1', b: 'hello, world' }] +parse('.{ 1, 2, 3 }') +// [1, 2, 3] ``` **Go** ```go -import csv "github.com/jsonicjs/csv/go" +import zon "github.com/jsonicjs/zon/go" +import jsonic "github.com/jsonicjs/jsonic/go" -result, _ := csv.Parse("name,age\nAlice,30\nBob,25") -// [{name:Alice age:30} {name:Bob age:25}] -``` +j := jsonic.Make() +j.UseDefaults(zon.Zon, zon.Defaults) +result, _ := j.Parse(`.{ .name = "Alice", .age = 30 }`) +// map[string]any{"name": "Alice", "age": 30} +``` -## Documentation +## Supported syntax -Full documentation following the [Diataxis](https://diataxis.fr) -framework (tutorials, how-to guides, explanation, reference): +- Anonymous struct literals: `.{ .field = value, ... }` +- Tuple / array literals: `.{ value, value, ... }` +- Field names: `.identifier` +- Enum literals (used as values): `.identifier` (parsed as bare strings) +- Strings: `"..."` with Zig escape sequences (`\n`, `\r`, `\t`, `\\`, `\"`, `\'`) +- Multi-line strings: consecutive lines starting with `\\` +- Numbers: decimal, `0x` hex, `0o` octal, `0b` binary, with `_` separators +- Character literals: `'x'`, `'\n'`, `'\x41'`, `'\u{1F600}'` +- Keywords: `true`, `false`, `null` +- Line comments: `// ...` +- Trailing commas allowed -- [TypeScript documentation](doc/csv-ts.md) -- [Go documentation](doc/csv-go.md) +## Options +| Option | Default | Description | +| -------------- | ------- | ---------------------------------------------------- | +| `charAsNumber` | `false` | Parse character literals as numeric code points. | +| `enumTag` | `null` | If set, wrap enum literals in `{ [enumTag]: name }`. | ## License -Copyright (c) 2021-2025 Richard Rodger and other contributors, +Copyright (c) 2025 Richard Rodger and other contributors, [MIT License](LICENSE). diff --git a/coverage/lcov.info b/coverage/lcov.info deleted file mode 100644 index b0b03ab..0000000 --- a/coverage/lcov.info +++ /dev/null @@ -1,340 +0,0 @@ -TN: -SF:csv.js -FN:7,(anonymous_0) -FN:60,(anonymous_1) -FN:137,(anonymous_2) -FN:138,(anonymous_3) -FN:154,(anonymous_4) -FN:160,(anonymous_5) -FN:175,(anonymous_6) -FN:188,(anonymous_7) -FN:243,(anonymous_8) -FN:255,(anonymous_9) -FN:262,(anonymous_10) -FN:273,(anonymous_11) -FN:279,(anonymous_12) -FN:290,(anonymous_13) -FN:301,(anonymous_14) -FN:312,(anonymous_15) -FN:326,(anonymous_16) -FN:337,(anonymous_17) -FN:350,(anonymous_18) -FN:359,buildCsvStringMatcher -FN:360,makeCsvStringMatcher -FN:361,csvStringMatcher -FNF:22 -FNH:20 -FNDA:137,(anonymous_0) -FNDA:0,(anonymous_1) -FNDA:137,(anonymous_2) -FNDA:216,(anonymous_3) -FNDA:208,(anonymous_4) -FNDA:137,(anonymous_5) -FNDA:137,(anonymous_6) -FNDA:457,(anonymous_7) -FNDA:137,(anonymous_8) -FNDA:137,(anonymous_9) -FNDA:30,(anonymous_10) -FNDA:13,(anonymous_11) -FNDA:137,(anonymous_12) -FNDA:137,(anonymous_13) -FNDA:28,(anonymous_14) -FNDA:63,(anonymous_15) -FNDA:52,(anonymous_16) -FNDA:0,(anonymous_17) -FNDA:71,(anonymous_18) -FNDA:132,buildCsvStringMatcher -FNDA:396,makeCsvStringMatcher -FNDA:1488,csvStringMatcher -DA:3,1 -DA:4,1 -DA:5,1 -DA:7,1 -DA:10,137 -DA:11,137 -DA:12,137 -DA:14,137 -DA:15,137 -DA:16,137 -DA:17,137 -DA:18,137 -DA:19,137 -DA:21,137 -DA:22,134 -DA:24,132 -DA:32,134 -DA:38,3 -DA:40,0 -DA:48,3 -DA:49,3 -DA:50,3 -DA:51,3 -DA:52,3 -DA:57,137 -DA:58,1 -DA:59,1 -DA:60,1 -DA:61,0 -DA:62,0 -DA:65,0 -DA:69,137 -DA:70,137 -DA:72,134 -DA:81,137 -DA:82,8 -DA:85,137 -DA:87,137 -DA:134,137 -DA:135,137 -DA:137,137 -DA:138,137 -DA:139,216 -DA:140,216 -DA:141,216 -DA:155,208 -DA:157,137 -DA:160,137 -DA:161,137 -DA:175,137 -DA:176,137 -DA:190,457 -DA:192,457 -DA:193,107 -DA:197,350 -DA:199,350 -DA:200,137 -DA:201,137 -DA:202,137 -DA:203,135 -DA:204,2 -DA:205,2 -DA:210,133 -DA:211,133 -DA:212,234 -DA:215,133 -DA:218,135 -DA:219,12 -DA:220,12 -DA:223,135 -DA:227,213 -DA:228,537 -DA:232,348 -DA:233,3 -DA:236,345 -DA:239,455 -DA:241,137 -DA:243,137 -DA:244,137 -DA:255,137 -DA:256,137 -DA:263,30 -DA:264,30 -DA:273,13 -DA:279,137 -DA:280,137 -DA:290,137 -DA:291,137 -DA:303,28 -DA:304,28 -DA:314,63 -DA:315,63 -DA:328,52 -DA:329,52 -DA:338,0 -DA:339,0 -DA:340,0 -DA:351,71 -DA:355,1 -DA:360,132 -DA:361,396 -DA:362,1488 -DA:363,1488 -DA:364,1488 -DA:365,1488 -DA:366,1488 -DA:367,102 -DA:368,102 -DA:369,102 -DA:370,102 -DA:371,102 -DA:372,102 -DA:374,102 -DA:375,291 -DA:376,291 -DA:378,291 -DA:379,148 -DA:380,148 -DA:381,148 -DA:382,47 -DA:385,101 -DA:390,143 -DA:392,143 -DA:393,143 -DA:394,143 -DA:395,263 -DA:396,263 -DA:398,143 -DA:399,143 -DA:400,28 -DA:401,22 -DA:403,28 -DA:404,28 -DA:406,115 -DA:407,0 -DA:408,0 -DA:409,0 -DA:412,115 -DA:413,115 -DA:417,102 -DA:418,1 -DA:419,1 -DA:421,101 -DA:424,101 -DA:425,101 -DA:426,101 -DA:427,101 -DA:433,1 -LF:148 -LH:138 -BRDA:18,0,0,0 -BRDA:18,0,1,137 -BRDA:18,1,0,137 -BRDA:18,1,1,137 -BRDA:21,2,0,134 -BRDA:21,2,1,3 -BRDA:22,3,0,132 -BRDA:22,3,1,2 -BRDA:38,4,0,0 -BRDA:38,4,1,3 -BRDA:48,5,0,3 -BRDA:48,5,1,0 -BRDA:49,6,0,3 -BRDA:49,6,1,0 -BRDA:50,7,0,3 -BRDA:50,7,1,0 -BRDA:51,8,0,3 -BRDA:51,8,1,0 -BRDA:57,9,0,1 -BRDA:57,9,1,136 -BRDA:70,10,0,134 -BRDA:70,10,1,3 -BRDA:81,11,0,8 -BRDA:81,11,1,129 -BRDA:98,12,0,134 -BRDA:98,12,1,3 -BRDA:117,13,0,136 -BRDA:117,13,1,1 -BRDA:120,14,0,136 -BRDA:120,14,1,1 -BRDA:140,15,0,216 -BRDA:140,15,1,1 -BRDA:148,16,0,129 -BRDA:148,16,1,8 -BRDA:155,17,0,208 -BRDA:155,17,1,1 -BRDA:186,18,0,8 -BRDA:186,18,1,129 -BRDA:190,19,0,457 -BRDA:190,19,1,313 -BRDA:192,20,0,107 -BRDA:192,20,1,350 -BRDA:192,21,0,457 -BRDA:192,21,1,201 -BRDA:193,22,0,0 -BRDA:193,22,1,107 -BRDA:197,23,0,350 -BRDA:197,23,1,0 -BRDA:199,24,0,137 -BRDA:199,24,1,213 -BRDA:202,25,0,135 -BRDA:202,25,1,2 -BRDA:203,26,0,2 -BRDA:203,26,1,133 -BRDA:204,27,0,2 -BRDA:204,27,1,0 -BRDA:205,28,0,1 -BRDA:205,28,1,1 -BRDA:213,29,0,6 -BRDA:213,29,1,228 -BRDA:221,30,0,0 -BRDA:221,30,1,12 -BRDA:229,31,0,0 -BRDA:229,31,1,537 -BRDA:232,32,0,3 -BRDA:232,32,1,345 -BRDA:303,33,0,28 -BRDA:303,33,1,0 -BRDA:304,34,0,28 -BRDA:304,34,1,0 -BRDA:314,35,0,41 -BRDA:314,35,1,22 -BRDA:316,36,0,41 -BRDA:316,36,1,22 -BRDA:317,37,0,45 -BRDA:317,37,1,18 -BRDA:317,38,0,63 -BRDA:317,38,1,41 -BRDA:328,39,0,2 -BRDA:328,39,1,50 -BRDA:330,40,0,2 -BRDA:330,40,1,50 -BRDA:330,41,0,23 -BRDA:330,41,1,29 -BRDA:338,42,0,0 -BRDA:338,42,1,0 -BRDA:339,43,0,0 -BRDA:339,43,1,0 -BRDA:341,44,0,0 -BRDA:341,44,1,0 -BRDA:341,45,0,0 -BRDA:341,45,1,0 -BRDA:344,46,0,134 -BRDA:344,46,1,3 -BRDA:351,47,0,71 -BRDA:351,47,1,0 -BRDA:366,48,0,102 -BRDA:366,48,1,1386 -BRDA:378,49,0,148 -BRDA:378,49,1,143 -BRDA:381,50,0,47 -BRDA:381,50,1,101 -BRDA:394,51,0,406 -BRDA:394,51,1,405 -BRDA:394,51,2,377 -BRDA:399,52,0,28 -BRDA:399,52,1,115 -BRDA:400,53,0,22 -BRDA:400,53,1,6 -BRDA:406,54,0,0 -BRDA:406,54,1,115 -BRDA:417,55,0,1 -BRDA:417,55,1,101 -BRDA:417,56,0,102 -BRDA:417,56,1,101 -BRF:115 -BRH:92 -end_of_record -TN: -SF:test/csv-fixtures.js -FN:381,(anonymous_0) -FN:400,(anonymous_1) -FN:647,(anonymous_2) -FN:661,(anonymous_3) -FNF:4 -FNH:4 -FNDA:1,(anonymous_0) -FNDA:1,(anonymous_1) -FNDA:1,(anonymous_2) -FNDA:1,(anonymous_3) -DA:4,1 -DA:5,1 -DA:7,1 -DA:382,1 -DA:401,1 -DA:648,1 -DA:662,1 -LF:7 -LH:7 -BRF:0 -BRH:0 -end_of_record diff --git a/csv-grammar.jsonic b/csv-grammar.jsonic deleted file mode 100644 index b7c599b..0000000 --- a/csv-grammar.jsonic +++ /dev/null @@ -1,52 +0,0 @@ -# CSV Grammar Definition -# Parsed by a standard Jsonic instance and passed to jsonic.grammar() -# Function references (@ prefixed) are resolved against the refs map -# -# Token naming: -# #LN - line ending (removed from per-instance IGNORE set) -# #SP - whitespace (removed from per-instance IGNORE set in strict mode) -# #CA - comma / field separator -# #ZZ - end of input -# #VAL - token set: text, string, number, value literals -# -# Rules csv, newline, record, text are fully defined here. -# Rules list, elem, val are modified in code (strict mode defines from scratch; -# non-strict prepends to existing defaults to preserve JSON parsing). - -{ - rule: csv: open: [ - { s: '#ZZ' } - { s: '#LN' p: newline c: '@not-record-empty' } - { p: record } - ] - - rule: newline: open: [ - { s: '#LN #LN' r: newline } - { s: '#LN' r: newline } - { s: '#ZZ' } - { r: record } - ] - rule: newline: close: [ - { s: '#LN #LN' r: newline } - { s: '#LN' r: newline } - { s: '#ZZ' } - { r: record } - ] - - rule: record: open: [ - { p: list } - ] - rule: record: close: [ - { s: '#ZZ' } - { s: '#LN #ZZ' b: 1 } - { s: '#LN' r: '@record-close-next' } - ] - - rule: text: open: [ - { s: ['#VAL' '#SP'] b: 1 r: text n: { text: 1 } g: 'csv,space,follows' a: '@text-follows' } - { s: ['#SP' '#VAL'] r: text n: { text: 1 } g: 'csv,space,leads' a: '@text-leads' } - { s: ['#SP' '#CA #LN #ZZ'] b: 1 n: { text: 1 } g: 'csv,end' a: '@text-end' } - { s: '#SP' n: { text: 1 } g: 'csv,space' a: '@text-space' p: '@text-space-push' } - {} - ] -} diff --git a/doc/csv-go.md b/doc/csv-go.md deleted file mode 100644 index 9c7322f..0000000 --- a/doc/csv-go.md +++ /dev/null @@ -1,264 +0,0 @@ -# CSV plugin for Jsonic (Go) - -A Jsonic syntax plugin that parses CSV text into Go slices of maps -or slices, with support for headers, quoted fields, custom -delimiters, streaming, and strict/non-strict modes. - -```bash -go get github.com/jsonicjs/csv/go@latest -``` - - -## Tutorials - -### Parse a basic CSV file - -Parse CSV text with a header row into a slice of ordered maps: - -```go -package main - -import ( - "fmt" - csv "github.com/jsonicjs/csv/go" -) - -func main() { - result, _ := csv.Parse("name,age\nAlice,30\nBob,25") - fmt.Println(result) - // [{name:Alice age:30} {name:Bob age:25}] -} -``` - -### Parse CSV without headers - -Return rows as slices instead of maps, with no header row: - -```go -result, _ := csv.Parse("a,b,c\n1,2,3", csv.CsvOptions{ - Header: boolPtr(false), - Object: boolPtr(false), -}) -// [[a b c] [1 2 3]] -``` - -### Parse CSV with quoted fields - -Double-quoted fields handle commas, newlines, and escaped quotes: - -```go -result, _ := csv.Parse(`name,bio -Alice,"Likes ""cats"" and dogs" -Bob,"Line1 -Line2"`) -// [{name:Alice bio:Likes "cats" and dogs} {name:Bob bio:Line1\nLine2}] -``` - - -## How-to guides - -### Use a custom field delimiter - -Set `Field.Separation` to use a delimiter other than comma: - -```go -result, _ := csv.Parse("name\tage\nAlice\t30", csv.CsvOptions{ - Field: &csv.FieldOptions{Separation: "\t"}, -}) -// [{name:Alice age:30}] -``` - -### Enable number and value parsing - -By default in strict mode, all values are strings. Enable `Number` -and `Value` to parse numeric and boolean values: - -```go -result, _ := csv.Parse("a,b,c\n1,true,null", csv.CsvOptions{ - Number: boolPtr(true), - Value: boolPtr(true), -}) -// [{a:1 b:true c:}] -``` - -### Trim whitespace from fields - -Enable `Trim` to remove leading and trailing whitespace from field -values: - -```go -result, _ := csv.Parse("a , b \n 1 , 2 ", csv.CsvOptions{ - Trim: boolPtr(true), -}) -// [{a:1 b:2}] -``` - -### Stream records as they are parsed - -Use the `Stream` callback to receive records one at a time: - -```go -var records []any - -result, _ := csv.Parse("a,b\n1,2\n3,4", csv.CsvOptions{ - Stream: func(what string, record any) { - if what == "record" { - records = append(records, record) - } - }, -}) -// result is [] (empty, records were streamed) -// records contains [{a:1 b:2}, {a:3 b:4}] -``` - -### Provide explicit field names - -Set `Field.Names` when the CSV has no header row but you want -map output with named fields: - -```go -result, _ := csv.Parse("1,2,3\n4,5,6", csv.CsvOptions{ - Header: boolPtr(false), - Field: &csv.FieldOptions{Names: []string{"x", "y", "z"}}, -}) -// [{x:1 y:2 z:3} {x:4 y:5 z:6}] -``` - -### Enforce exact field counts - -Set `Field.Exact` to error when a row has more or fewer fields -than the header: - -```go -_, err := csv.Parse("a,b\n1,2,3", csv.CsvOptions{ - Field: &csv.FieldOptions{Exact: true}, -}) -// err: unexpected extra field value -``` - -### Create a reusable parser - -Use `MakeJsonic` to create a configured Jsonic instance you can -call repeatedly: - -```go -j := csv.MakeJsonic(csv.CsvOptions{ - Number: boolPtr(true), -}) - -r1, _ := j.Parse("a,b\n1,2") -r2, _ := j.Parse("x,y\n3,4") -``` - -### Enable comment lines - -Enable `Comment` to skip lines starting with `#`: - -```go -result, _ := csv.Parse("a,b\n# skip\n1,2", csv.CsvOptions{ - Comment: boolPtr(true), -}) -// [{a:1 b:2}] -``` - - -## Explanation - -### Strict vs non-strict mode - -In **strict mode** (default), the CSV plugin disables Jsonic's -built-in JSON parsing. All field values are treated as raw strings -unless `Number` or `Value` options are enabled. This matches the -behaviour of standard CSV parsers. - -In **non-strict mode** (`Strict: boolPtr(false)`), the plugin -preserves Jsonic's ability to parse JSON values. Fields can contain -objects, arrays, booleans, numbers, and quoted strings using Jsonic -syntax. Non-strict mode enables `Trim`, `Comment`, and `Number` by -default. - -### How quoted fields work - -The plugin includes a custom CSV string matcher that handles the -RFC 4180 double-quote escaping convention: - -- A field wrapped in double quotes can contain commas, newlines, - and quotes. -- A literal quote inside a quoted field is represented as `""`. -- For example: `"a""b"` parses to `a"b`. - - -## Reference - -### `Parse` (Function) - -```go -func Parse(src string, opts ...CsvOptions) ([]any, error) -``` - -Parse CSV text with the given options. Returns a slice of records. - -### `MakeJsonic` (Function) - -```go -func MakeJsonic(opts ...CsvOptions) *jsonic.Jsonic -``` - -Create a reusable Jsonic instance configured for CSV parsing. - -### `CsvOptions` - -```go -type CsvOptions struct { - Object *bool // Return maps (true) or slices (false). Default: true - Header *bool // First row is header. Default: true - Trim *bool // Trim whitespace. Default: nil (false strict, true non-strict) - Comment *bool // Enable # comments. Default: nil (false strict, true non-strict) - Number *bool // Parse numbers. Default: nil (false strict, true non-strict) - Value *bool // Parse true/false/null. Default: nil - Strict *bool // Strict CSV mode. Default: true - Field *FieldOptions - Record *RecordOptions - String *StringOptions - Stream StreamFunc -} -``` - -### `FieldOptions` - -```go -type FieldOptions struct { - Separation string // Field separator. Default: "," - NonamePrefix string // Prefix for unnamed extra fields. Default: "field~" - Empty string // Value for empty fields. Default: "" - Names []string // Explicit field names. - Exact bool // Error on field count mismatch. Default: false -} -``` - -### `RecordOptions` - -```go -type RecordOptions struct { - Separators string // Custom record separator characters. - Empty bool // Preserve empty lines as records. Default: false -} -``` - -### `StringOptions` - -```go -type StringOptions struct { - Quote string // Quote character. Default: `"` - Csv *bool // Force CSV string mode (nil=auto). -} -``` - -### `StreamFunc` - -```go -type StreamFunc func(what string, record any) -``` - -Callback for streaming CSV parsing. Called with `"start"`, `"record"`, -`"end"`, or `"error"`. diff --git a/doc/csv-ts.md b/doc/csv-ts.md deleted file mode 100644 index 2e8f9b5..0000000 --- a/doc/csv-ts.md +++ /dev/null @@ -1,286 +0,0 @@ -# CSV plugin for Jsonic (TypeScript) - -A Jsonic syntax plugin that parses CSV text into JavaScript arrays -of objects or arrays, with support for headers, quoted fields, -custom delimiters, streaming, and strict/non-strict modes. - -```bash -npm install @jsonic/csv -``` - -Requires `jsonic` >= 2 as a peer dependency. - - -## Tutorials - -### Parse a basic CSV file - -Parse CSV text with a header row into an array of objects: - -```typescript -import { Jsonic } from 'jsonic' -import { Csv } from '@jsonic/csv' - -const j = Jsonic.make().use(Csv) - -j("name,age\nAlice,30\nBob,25") -// [{ name: 'Alice', age: '30' }, { name: 'Bob', age: '25' }] -``` - -### Parse CSV without headers - -Return rows as arrays instead of objects, with no header row: - -```typescript -import { Jsonic } from 'jsonic' -import { Csv } from '@jsonic/csv' - -const j = Jsonic.make().use(Csv, { header: false, object: false }) - -j("a,b,c\n1,2,3") -// [['a', 'b', 'c'], ['1', '2', '3']] -``` - -### Parse CSV with quoted fields - -Double-quoted fields handle commas, newlines, and escaped quotes: - -```typescript -import { Jsonic } from 'jsonic' -import { Csv } from '@jsonic/csv' - -const j = Jsonic.make().use(Csv) - -j('name,bio\nAlice,"Likes ""cats"" and dogs"\nBob,"Line1\nLine2"') -// [ -// { name: 'Alice', bio: 'Likes "cats" and dogs' }, -// { name: 'Bob', bio: 'Line1\nLine2' } -// ] -``` - - -## How-to guides - -### Use a custom field delimiter - -Set `field.separation` to use a delimiter other than comma: - -```typescript -const j = Jsonic.make().use(Csv, { - field: { separation: '\t' } -}) - -j("name\tage\nAlice\t30") -// [{ name: 'Alice', age: '30' }] -``` - -### Enable number and value parsing - -By default in strict mode, all values are strings. Enable `number` -and `value` to parse numeric and boolean values: - -```typescript -const j = Jsonic.make().use(Csv, { - number: true, - value: true, -}) - -j("a,b,c\n1,true,null") -// [{ a: 1, b: true, c: null }] -``` - -### Trim whitespace from fields - -Enable `trim` to remove leading and trailing whitespace from field -values: - -```typescript -const j = Jsonic.make().use(Csv, { trim: true }) - -j("a , b \n 1 , 2 ") -// [{ a: '1', b: '2' }] -``` - -### Stream records as they are parsed - -Use the `stream` callback to receive records one at a time without -storing them all in memory: - -```typescript -const records: any[] = [] - -const j = Jsonic.make().use(Csv, { - stream: (what, record) => { - if (what === 'record') records.push(record) - }, -}) - -j("a,b\n1,2\n3,4") -// returns [] (empty, records were streamed) -// records === [{ a: '1', b: '2' }, { a: '3', b: '4' }] -``` - -### Provide explicit field names - -Set `field.names` when the CSV has no header row but you want -object output with named fields: - -```typescript -const j = Jsonic.make().use(Csv, { - header: false, - field: { names: ['x', 'y', 'z'] }, -}) - -j("1,2,3\n4,5,6") -// [{ x: '1', y: '2', z: '3' }, { x: '4', y: '5', z: '6' }] -``` - -### Enforce exact field counts - -Set `field.exact` to error when a row has more or fewer fields -than the header: - -```typescript -const j = Jsonic.make().use(Csv, { - field: { exact: true }, -}) - -// j("a,b\n1,2,3") // throws: unexpected extra field value -// j("a,b\n1") // throws: missing field -``` - -### Use non-strict mode for embedded JSON - -Disable `strict` to allow Jsonic syntax inside CSV fields, -including JSON objects, arrays, and expressions: - -```typescript -const j = Jsonic.make().use(Csv, { strict: false }) - -j("a,b\ntrue,[1,2]") -// [{ a: true, b: [1, 2] }] -``` - -### Enable comment lines - -Enable `comment` to skip lines starting with `#`: - -```typescript -const j = Jsonic.make().use(Csv, { comment: true }) - -j("a,b\n# skip this\n1,2") -// [{ a: '1', b: '2' }] -``` - -### Preserve empty records - -By default, blank lines are skipped. Set `record.empty` to -preserve them as empty-field records: - -```typescript -const j = Jsonic.make().use(Csv, { record: { empty: true } }) - -j("a\n1\n\n2") -// [{ a: '1' }, { a: '' }, { a: '2' }] -``` - - -## Explanation - -### Strict vs non-strict mode - -In **strict mode** (default), the CSV plugin disables Jsonic's -built-in JSON parsing. All field values are treated as raw strings -unless `number` or `value` options are enabled. This matches the -behaviour of standard CSV parsers. - -In **non-strict mode** (`strict: false`), the plugin preserves -Jsonic's ability to parse JSON values. Fields can contain objects -(`{x:1}`), arrays (`[1,2]`), booleans, numbers, and quoted strings -using Jsonic syntax. Non-strict mode enables `trim`, `comment`, and -`number` by default. - -### How quoted fields work - -The plugin includes a custom CSV string matcher that handles the -RFC 4180 double-quote escaping convention: - -- A field wrapped in double quotes can contain commas, newlines, - and quotes. -- A literal quote inside a quoted field is represented as `""`. -- For example: `"a""b"` parses to `a"b`. - - -## Reference - -### `Csv` (Plugin) - -The plugin function. Register with `Jsonic.make().use(Csv, options)`. - -### `CsvOptions` - -```typescript -type CsvOptions = { - // Trim surrounding whitespace. Default: null (false in strict, true in non-strict) - trim: boolean | null - - // Enable # line comments. Default: null (false in strict, true in non-strict) - comment: boolean | null - - // Parse numeric values. Default: null (false in strict, true in non-strict) - number: boolean | null - - // Parse value keywords (true/false/null). Default: null (false in strict, false in non-strict) - value: boolean | null - - // First row is a header row. Default: true - header: boolean - - // Return records as objects (true) or arrays (false). Default: true - object: boolean - - // Stream callback. Default: null - stream: null | ((what: string, record?: Record | Error) => void) - - // Strict CSV mode (disables Jsonic syntax). Default: true - strict: boolean - - field: { - // Field separator string. Default: null (uses comma) - separation: null | string - - // Prefix for unnamed extra fields. Default: 'field~' - nonameprefix: string - - // Value for empty fields. Default: '' - empty: any - - // Explicit field names (overrides header). Default: undefined - names: undefined | string[] - - // Error on field count mismatch. Default: false - exact: boolean - } - - record: { - // Custom record separator characters. Default: null - separators: null | string - - // Preserve empty lines as records. Default: false - empty: boolean - } - - string: { - // Quote character. Default: '"' - quote: string - - // Force CSV string mode (null=auto). Default: null - csv: null | boolean - } -} -``` - -### `buildCsvStringMatcher` (Function) - -Exported for advanced use. Creates the custom CSV double-quote -string matcher used internally by the plugin. diff --git a/embed-grammar.js b/embed-grammar.js index 499715e..f5ece29 100644 --- a/embed-grammar.js +++ b/embed-grammar.js @@ -1,17 +1,17 @@ #!/usr/bin/env node -// Embed csv-grammar.jsonic into TypeScript and Go source files. +// Embed zon-grammar.jsonic into TypeScript and Go source files. // Run via: npm run embed (or: node embed-grammar.js) const fs = require('fs') const path = require('path') -const GRAMMAR_FILE = path.join(__dirname, 'csv-grammar.jsonic') -const TS_FILE = path.join(__dirname, 'src', 'csv.ts') -const GO_FILE = path.join(__dirname, 'go', 'csv.go') +const GRAMMAR_FILE = path.join(__dirname, 'zon-grammar.jsonic') +const TS_FILE = path.join(__dirname, 'src', 'zon.ts') +const GO_FILE = path.join(__dirname, 'go', 'zon.go') -const BEGIN = '// --- BEGIN EMBEDDED csv-grammar.jsonic ---' -const END = '// --- END EMBEDDED csv-grammar.jsonic ---' +const BEGIN = '// --- BEGIN EMBEDDED zon-grammar.jsonic ---' +const END = '// --- END EMBEDDED zon-grammar.jsonic ---' const grammar = fs.readFileSync(GRAMMAR_FILE, 'utf8') diff --git a/go/csv.go b/go/csv.go deleted file mode 100644 index 05686ba..0000000 --- a/go/csv.go +++ /dev/null @@ -1,780 +0,0 @@ -/* Copyright (c) 2021-2025 Richard Rodger, MIT License */ - -package csv - -import ( - "fmt" - "strconv" - "strings" - - jsonic "github.com/jsonicjs/jsonic/go" -) - -const Version = "0.1.3" - -// --- BEGIN EMBEDDED csv-grammar.jsonic --- -const grammarText = ` -# CSV Grammar Definition -# Parsed by a standard Jsonic instance and passed to jsonic.grammar() -# Function references (@ prefixed) are resolved against the refs map -# -# Token naming: -# #LN - line ending (removed from per-instance IGNORE set) -# #SP - whitespace (removed from per-instance IGNORE set in strict mode) -# #CA - comma / field separator -# #ZZ - end of input -# #VAL - token set: text, string, number, value literals -# -# Rules csv, newline, record, text are fully defined here. -# Rules list, elem, val are modified in code (strict mode defines from scratch; -# non-strict prepends to existing defaults to preserve JSON parsing). - -{ - rule: csv: open: [ - { s: '#ZZ' } - { s: '#LN' p: newline c: '@not-record-empty' } - { p: record } - ] - - rule: newline: open: [ - { s: '#LN #LN' r: newline } - { s: '#LN' r: newline } - { s: '#ZZ' } - { r: record } - ] - rule: newline: close: [ - { s: '#LN #LN' r: newline } - { s: '#LN' r: newline } - { s: '#ZZ' } - { r: record } - ] - - rule: record: open: [ - { p: list } - ] - rule: record: close: [ - { s: '#ZZ' } - { s: '#LN #ZZ' b: 1 } - { s: '#LN' r: '@record-close-next' } - ] - - rule: text: open: [ - { s: ['#VAL' '#SP'] b: 1 r: text n: { text: 1 } g: 'csv,space,follows' a: '@text-follows' } - { s: ['#SP' '#VAL'] r: text n: { text: 1 } g: 'csv,space,leads' a: '@text-leads' } - { s: ['#SP' '#CA #LN #ZZ'] b: 1 n: { text: 1 } g: 'csv,end' a: '@text-end' } - { s: '#SP' n: { text: 1 } g: 'csv,space' a: '@text-space' p: '@text-space-push' } - {} - ] -} -` -// --- END EMBEDDED csv-grammar.jsonic --- - -// Csv is a jsonic plugin that adds CSV parsing support. -// Options are pre-merged with Defaults by jsonic.UseDefaults. -func Csv(j *jsonic.Jsonic, options map[string]any) error { - // Guard against re-invocation: Use() re-runs plugins on SetOptions calls. - if j.Decoration("csv-init") != nil { - return nil - } - j.Decorate("csv-init", true) - - strict := toBool(options["strict"]) - objres := toBool(options["object"]) - header := toBool(options["header"]) - - trim := toBool(options["trim"]) - comment := toBool(options["comment"]) - opt_number := toBool(options["number"]) - opt_value := toBool(options["value"]) - - fieldOpts, _ := options["field"].(map[string]any) - recordOpts, _ := options["record"].(map[string]any) - stringOpts, _ := options["string"].(map[string]any) - - record_empty := toBool(recordOpts["empty"]) - - stream, _ := options["stream"].(func(string, any)) - - // In strict mode, Jsonic field content is not parsed. - if strict { - if stringOpts["csv"] != false { - j.SetOptions(jsonic.Options{Lex: &jsonic.LexOptions{ - Match: map[string]*jsonic.MatchSpec{ - "stringcsv": {Order: 1e5, Make: buildCsvStringMatcher(stringOpts)}, - }, - }}) - } - j.SetOptions(jsonic.Options{Rule: &jsonic.RuleOptions{Exclude: "jsonic,imp"}}) - } else { - // Fields may contain Jsonic content. - if stringOpts["csv"] == true { - j.SetOptions(jsonic.Options{Lex: &jsonic.LexOptions{ - Match: map[string]*jsonic.MatchSpec{ - "stringcsv": {Order: 1e5, Make: buildCsvStringMatcher(stringOpts)}, - }, - }}) - } - if options["trim"] == nil { - trim = true - } - if options["comment"] == nil { - comment = true - } - if options["number"] == nil { - opt_number = true - } - if options["value"] == nil { - opt_value = true - } - j.SetOptions(jsonic.Options{Rule: &jsonic.RuleOptions{Exclude: "imp"}}) - } - - fieldSep := toString(fieldOpts["separation"]) - recordSep := toString(recordOpts["separators"]) - - // Jsonic option overrides (matching TS jsonicOptions). - jsonicOptions := jsonic.Options{ - Rule: &jsonic.RuleOptions{Start: "csv"}, - Number: &jsonic.NumberOptions{ - Lex: boolPtr(opt_number), - }, - Value: &jsonic.ValueOptions{ - Lex: boolPtr(opt_value), - }, - Comment: &jsonic.CommentOptions{ - Lex: boolPtr(comment), - }, - Lex: &jsonic.LexOptions{ - EmptyResult: []any{}, - }, - Line: &jsonic.LineOptions{ - Single: boolPtr(record_empty), - }, - Error: map[string]string{ - "csv_extra_field": "unexpected extra field value: $fsrc", - "csv_missing_field": "missing field", - }, - Hint: map[string]string{ - "csv_extra_field": "Row $row has too many fields (the first of which is: $fsrc). Only $len\nfields per row are expected.", - "csv_missing_field": "Row $row has too few fields. $len fields per row are expected.", - }, - } - - if strict { - csvStringOpt := stringOpts["csv"] - if csvStringOpt == nil || csvStringOpt == true { - jsonicOptions.String = &jsonic.StringOptions{ - Lex: boolPtr(false), - Chars: "", - } - } - } - - if recordSep != "" { - jsonicOptions.Line.Chars = recordSep - jsonicOptions.Line.RowChars = recordSep - } - - // Fixed-token overrides: in strict mode disable JSON structural tokens - // and the ':' key separator; swap the field separator when configured. - if strict || fieldSep != "" { - jsonicOptions.Fixed = &jsonic.FixedOptions{Token: map[string]*string{}} - if strict { - jsonicOptions.Fixed.Token["#OB"] = nil - jsonicOptions.Fixed.Token["#CB"] = nil - jsonicOptions.Fixed.Token["#OS"] = nil - jsonicOptions.Fixed.Token["#CS"] = nil - jsonicOptions.Fixed.Token["#CL"] = nil - } - if fieldSep != "" { - sep := fieldSep - jsonicOptions.Fixed.Token["#CA"] = &sep - } - } - - // IGNORE set: drop #LN so row breaks are significant; in strict mode - // also drop #SP so whitespace inside fields is preserved. - if strict { - jsonicOptions.TokenSet = map[string][]string{"IGNORE": {"#CM"}} - } else { - jsonicOptions.TokenSet = map[string][]string{"IGNORE": {"#SP", "#CM"}} - } - - j.SetOptions(jsonicOptions) - - // Named function references for declarative grammar definition. - emptyField := toString(fieldOpts["empty"]) - nonameprefix := toString(fieldOpts["nonameprefix"]) - fieldExact := toBool(fieldOpts["exact"]) - var fieldNames []string - if names, ok := fieldOpts["names"].([]string); ok { - fieldNames = names - } else if names, ok := fieldOpts["names"].([]any); ok { - for _, n := range names { - if s, ok := n.(string); ok { - fieldNames = append(fieldNames, s) - } - } - } - - refs := map[jsonic.FuncRef]any{ - - "@csv-bo": jsonic.StateAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - if ctx.Meta == nil { - ctx.Meta = make(map[string]any) - } - ctx.Meta["recordI"] = 0 - if stream != nil { - stream("start", nil) - } - r.Node = make([]any, 0) - }), - - "@csv-ac": jsonic.StateAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - if stream != nil { - stream("end", nil) - } - }), - - "@record-bc": jsonic.StateAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - recordI, _ := ctx.Meta["recordI"].(int) - var fields []string - if fs, ok := ctx.Meta["fields"].([]string); ok { - fields = fs - } - if fields == nil { - fields = fieldNames - } - - if recordI == 0 && header { - if childArr, ok := r.Child.Node.([]any); ok { - names := make([]string, len(childArr)) - for i, v := range childArr { - names[i], _ = v.(string) - } - ctx.Meta["fields"] = names - } else { - ctx.Meta["fields"] = []string{} - } - } else { - record, _ := r.Child.Node.([]any) - if record == nil { - record = []any{} - } - - if objres { - obj := make(map[string]any) - var keys []string - i := 0 - - if fields != nil { - if fieldExact && len(record) != len(fields) { - errCode := "csv_missing_field" - if len(record) > len(fields) { - errCode = "csv_extra_field" - } - ctx.ParseErr = &jsonic.Token{ - Name: "#BD", Tin: jsonic.TinBD, - Why: errCode, Src: errCode, - } - return - } - - for fI := 0; fI < len(fields); fI++ { - var val any = emptyField - if fI < len(record) && !jsonic.IsUndefined(record[fI]) { - val = record[fI] - } - obj[fields[fI]] = val - keys = append(keys, fields[fI]) - } - i = len(fields) - } - - for ; i < len(record); i++ { - fname := nonameprefix + strconv.Itoa(i) - val := record[i] - if jsonic.IsUndefined(val) { - val = emptyField - } - obj[fname] = val - keys = append(keys, fname) - } - - out := orderedMap{keys: keys, m: obj} - if stream != nil { - stream("record", out) - } else if arr, ok := r.Node.([]any); ok { - r.Node = append(arr, out) - if r.Parent != jsonic.NoRule && r.Parent != nil { - r.Parent.Node = r.Node - } - } - } else { - for i := range record { - if jsonic.IsUndefined(record[i]) { - record[i] = emptyField - } - } - if stream != nil { - stream("record", record) - } else if arr, ok := r.Node.([]any); ok { - r.Node = append(arr, record) - if r.Parent != jsonic.NoRule && r.Parent != nil { - r.Parent.Node = r.Node - } - } - } - } - ctx.Meta["recordI"] = recordI + 1 - }), - - "@text-bc": jsonic.StateAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - if !jsonic.IsUndefined(r.Child.Node) { - r.Parent.Node = r.Child.Node - } else { - r.Parent.Node = r.Node - } - }), - - "@text-follows": jsonic.AltAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - prev := "" - if r.N["text"] != 1 && r.Prev != nil && r.Prev != jsonic.NoRule { - prev, _ = r.Prev.Node.(string) - } - result := prev + tokenStr(r.O0) - r.Node = result - if r.N["text"] == 1 { - } else if r.Prev != nil && r.Prev != jsonic.NoRule { - r.Prev.Node = result - } - }), - - "@text-leads": jsonic.AltAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - prev := "" - if r.N["text"] != 1 && r.Prev != nil && r.Prev != jsonic.NoRule { - prev, _ = r.Prev.Node.(string) - } - sp := "" - if r.N["text"] >= 2 || !trim { - sp = r.O0.Src - } - result := prev + sp + r.O1.Src - r.Node = result - if r.N["text"] == 1 { - } else if r.Prev != nil && r.Prev != jsonic.NoRule { - r.Prev.Node = result - } - }), - - "@text-end": jsonic.AltAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - prev := "" - if r.N["text"] != 1 && r.Prev != nil && r.Prev != jsonic.NoRule { - prev, _ = r.Prev.Node.(string) - } - sp := "" - if !trim { - sp = r.O0.Src - } - result := prev + sp - r.Node = result - if r.N["text"] == 1 { - } else if r.Prev != nil && r.Prev != jsonic.NoRule { - r.Prev.Node = result - } - }), - - "@text-space": jsonic.AltAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - if strict { - prev := "" - if r.N["text"] != 1 && r.Prev != nil && r.Prev != jsonic.NoRule { - prev, _ = r.Prev.Node.(string) - } - sp := "" - if !trim { - sp = r.O0.Src - } - result := prev + sp - r.Node = result - if r.N["text"] == 1 { - } else if r.Prev != nil && r.Prev != jsonic.NoRule { - r.Prev.Node = result - } - } - }), - - "@not-record-empty": jsonic.AltCond(func(r *jsonic.Rule, ctx *jsonic.Context) bool { - return !record_empty - }), - - "@record-close-next": func(r *jsonic.Rule, ctx *jsonic.Context) string { - if record_empty { - return "record" - } - return "newline" - }, - - "@text-space-push": func(r *jsonic.Rule, ctx *jsonic.Context) string { - if strict { - return "" - } - return "val" - }, - } - - // Parse embedded grammar definition using a separate standard Jsonic instance. - gs, err := parseGrammarText(grammarText, refs) - if err != nil { - return err - } - if err := j.Grammar(gs); err != nil { - return fmt.Errorf("failed to apply csv grammar: %w", err) - } - - // Rules list, elem, val are modified in code rather than the grammar file, - // because in non-strict mode the default jsonic alternatives must be preserved - // to support embedded JSON values like [1,2] and {x:1}. - - LN := j.Token("#LN") - CA := j.Token("#CA") - SP := j.Token("#SP") - ZZ := j.Token("#ZZ") - VAL := j.TokenSet("VAL") - - j.Rule("list", func(rs *jsonic.RuleSpec) { - rs.Clear() - rs.AddBO(func(r *jsonic.Rule, ctx *jsonic.Context) { - r.Node = make([]any, 0) - }) - rs.Open = []*jsonic.AltSpec{ - {S: [][]jsonic.Tin{{LN}}, B: 1}, - {P: "elem"}, - } - rs.Close = []*jsonic.AltSpec{ - {S: [][]jsonic.Tin{{LN}}, B: 1}, - {S: [][]jsonic.Tin{{ZZ}}}, - } - }) - - j.Rule("elem", func(rs *jsonic.RuleSpec) { - rs.Clear() - rs.Open = []*jsonic.AltSpec{ - {S: [][]jsonic.Tin{{CA}}, B: 1, - A: jsonic.AltAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - if arr, ok := r.Node.([]any); ok { - r.Node = append(arr, emptyField) - if r.Parent != jsonic.NoRule && r.Parent != nil { - r.Parent.Node = r.Node - } - } - r.U["done"] = true - })}, - {P: "val"}, - } - rs.Close = []*jsonic.AltSpec{ - {S: [][]jsonic.Tin{{CA}, {LN, ZZ}}, B: 1, - A: jsonic.AltAction(func(r *jsonic.Rule, ctx *jsonic.Context) { - if arr, ok := r.Node.([]any); ok { - r.Node = append(arr, emptyField) - if r.Parent != jsonic.NoRule && r.Parent != nil { - r.Parent.Node = r.Node - } - } - })}, - {S: [][]jsonic.Tin{{CA}}, R: "elem"}, - {S: [][]jsonic.Tin{{LN}}, B: 1}, - {S: [][]jsonic.Tin{{ZZ}}}, - } - rs.AddBC(func(r *jsonic.Rule, ctx *jsonic.Context) { - done, _ := r.U["done"].(bool) - if !done && !jsonic.IsUndefined(r.Child.Node) { - if arr, ok := r.Node.([]any); ok { - r.Node = append(arr, r.Child.Node) - if r.Parent != jsonic.NoRule && r.Parent != nil { - r.Parent.Node = r.Node - } - } - } - }) - }) - - j.Rule("val", func(rs *jsonic.RuleSpec) { - rs.Clear() - rs.AddBO(func(r *jsonic.Rule, ctx *jsonic.Context) { - r.Node = jsonic.Undefined - }) - rs.Open = []*jsonic.AltSpec{ - {S: [][]jsonic.Tin{VAL, {SP}}, B: 2, P: "text"}, - {S: [][]jsonic.Tin{{SP}}, B: 1, P: "text"}, - {S: [][]jsonic.Tin{VAL}}, - {S: [][]jsonic.Tin{{LN}}, B: 1}, - } - rs.AddBC(func(r *jsonic.Rule, ctx *jsonic.Context) { - if jsonic.IsUndefined(r.Node) { - if jsonic.IsUndefined(r.Child.Node) { - if r.OS == 0 { - r.Node = jsonic.Undefined - } else { - r.Node = r.O0.ResolveVal() - } - } else { - r.Node = r.Child.Node - } - } - }) - }) - - return nil -} - -// Custom CSV String matcher factory. -// Handles "a""b" -> a"b quoting. -// Matches TS: buildCsvStringMatcher(options) returns make(cfg, opts) => matcher(lex). -func buildCsvStringMatcher(stringOpts map[string]any) jsonic.MakeLexMatcher { - quote := toString(stringOpts["quote"]) - return func(cfg *jsonic.LexConfig, opts *jsonic.Options) jsonic.LexMatcher { - return func(lex *jsonic.Lex, rule *jsonic.Rule) *jsonic.Token { - pnt := lex.Cursor() - src := lex.Src - sI := pnt.SI - srclen := len(src) - - if sI >= srclen || !strings.HasPrefix(src[sI:], quote) { - return nil - } - - // Only match when quote is at the start of a field. - if sI > 0 { - prev := rune(src[sI-1]) - _, isFixed := cfg.FixedTokens[string(prev)] - if !isFixed && !cfg.LineChars[prev] && !cfg.SpaceChars[prev] { - return nil - } - } - - q := quote - qLen := len(q) - rI := pnt.RI - cI := pnt.CI - sI += qLen - cI += qLen - - var s strings.Builder - for sI < srclen { - cI++ - if strings.HasPrefix(src[sI:], q) { - sI += qLen - cI += qLen - 1 - if sI < srclen && strings.HasPrefix(src[sI:], q) { - s.WriteString(q) - sI += qLen - cI += qLen - continue - } - val := s.String() - ssrc := src[pnt.SI:sI] - tkn := lex.Token("#ST", jsonic.TinST, val, ssrc) - pnt.SI = sI - pnt.RI = rI - pnt.CI = cI - return tkn - } - - ch := src[sI] - if cfg.LineChars[rune(ch)] { - if cfg.RowChars[rune(ch)] { - rI++ - pnt.RI = rI - } - cI = 1 - s.WriteByte(ch) - sI++ - continue - } - if ch < 32 { - return nil - } - - bI := sI - qFirst := q[0] - for sI < srclen && src[sI] >= 32 && src[sI] != qFirst { - if cfg.LineChars[rune(src[sI])] { - break - } - sI++ - cI++ - } - cI-- - s.WriteString(src[bI:sI]) - } - - badSrc := src[pnt.SI:sI] - tkn := lex.Token("#BD", jsonic.TinBD, nil, badSrc) - tkn.Why = "unterminated_string" - pnt.SI = sI - pnt.RI = rI - pnt.CI = cI - return tkn - } - } -} - -// Defaults matches the TS Csv.defaults. Used with jsonic.UseDefaults. -var Defaults = map[string]any{ - "trim": nil, - "comment": nil, - "number": nil, - "value": nil, - "header": true, - "object": true, - "stream": nil, - "strict": true, - "field": map[string]any{ - "separation": nil, - "nonameprefix": "field~", - "empty": "", - "names": nil, - "exact": false, - }, - "record": map[string]any{ - "separators": nil, - "empty": false, - }, - "string": map[string]any{ - "quote": `"`, - "csv": nil, - }, -} - -// parseGrammarText parses grammar text and builds a GrammarSpec with Ref support. -func parseGrammarText(text string, refs map[jsonic.FuncRef]any) (*jsonic.GrammarSpec, error) { - parsed, err := jsonic.Make().Parse(text) - if err != nil { - return nil, fmt.Errorf("failed to parse grammar text: %w", err) - } - parsedMap, ok := parsed.(map[string]any) - if !ok { - return nil, fmt.Errorf("grammar text did not parse to a map") - } - gs := &jsonic.GrammarSpec{Ref: refs} - ruleMap, ok := parsedMap["rule"].(map[string]any) - if !ok { - return gs, nil - } - gs.Rule = make(map[string]*jsonic.GrammarRuleSpec, len(ruleMap)) - for name, rDef := range ruleMap { - rd, ok := rDef.(map[string]any) - if !ok { - continue - } - grs := &jsonic.GrammarRuleSpec{} - if openDef, ok := rd["open"]; ok { - grs.Open = buildGrammarAlts(openDef) - } - if closeDef, ok := rd["close"]; ok { - grs.Close = buildGrammarAlts(closeDef) - } - gs.Rule[name] = grs - } - return gs, nil -} - -func buildGrammarAlts(def any) []*jsonic.GrammarAltSpec { - arr, ok := def.([]any) - if !ok { - return nil - } - alts := make([]*jsonic.GrammarAltSpec, 0, len(arr)) - for _, item := range arr { - m, ok := item.(map[string]any) - if !ok { - alts = append(alts, &jsonic.GrammarAltSpec{}) - continue - } - ga := &jsonic.GrammarAltSpec{} - if s, ok := m["s"]; ok { - switch sv := s.(type) { - case string: - ga.S = sv - case []any: - strs := make([]string, len(sv)) - for i, v := range sv { - strs[i], _ = v.(string) - } - ga.S = strs - } - } - if b, ok := m["b"]; ok { - switch bv := b.(type) { - case float64: - ga.B = int(bv) - case int: - ga.B = bv - } - } - if p, ok := m["p"].(string); ok { - ga.P = p - } - if r, ok := m["r"].(string); ok { - ga.R = r - } - if a, ok := m["a"].(string); ok { - ga.A = jsonic.FuncRef(a) - } - if c, ok := m["c"]; ok { - switch cv := c.(type) { - case string: - ga.C = cv - case map[string]any: - ga.C = cv - } - } - if n, ok := m["n"].(map[string]any); ok { - ga.N = make(map[string]int, len(n)) - for k, v := range n { - if nv, ok := v.(float64); ok { - ga.N[k] = int(nv) - } else if nv, ok := v.(int); ok { - ga.N[k] = nv - } - } - } - if g, ok := m["g"].(string); ok { - ga.G = g - } - alts = append(alts, ga) - } - return alts -} - -func tokenStr(t *jsonic.Token) string { - if t == nil || t.IsNoToken() { - return "" - } - if t.Tin == jsonic.TinST { - if s, ok := t.Val.(string); ok { - return s - } - } - return t.Src -} - -func toBool(v any) bool { - b, _ := v.(bool) - return b -} - -func toString(v any) string { - s, _ := v.(string) - return s -} - -func boolPtr(b bool) *bool { - return &b -} - -// orderedMap maintains insertion order for JSON serialization comparison. -type orderedMap struct { - keys []string - m map[string]any -} diff --git a/go/csv_test.go b/go/csv_test.go deleted file mode 100644 index 2e14b3a..0000000 --- a/go/csv_test.go +++ /dev/null @@ -1,527 +0,0 @@ -package csv - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "reflect" - "testing" - - jsonic "github.com/jsonicjs/jsonic/go" -) - -// fixtureEntry represents one entry in the test manifest. -type fixtureEntry struct { - Name string `json:"name"` - CsvFile string `json:"csvFile,omitempty"` - Opt map[string]any `json:"opt,omitempty"` - JsonicOpt map[string]any `json:"jsonicOpt,omitempty"` - Err string `json:"err,omitempty"` -} - -func fixturesDir() string { - return filepath.Join("..", "test", "fixtures") -} - -// csvParse creates a jsonic instance with the Csv plugin and parses src. -func csvParse(src string, opts ...map[string]any) ([]any, error) { - j := jsonic.Make() - j.UseDefaults(Csv, Defaults, opts...) - - result, err := j.Parse(src) - if err != nil { - return nil, err - } - if result == nil { - return []any{}, nil - } - if arr, ok := result.([]any); ok { - return arr, nil - } - return []any{}, nil -} - -func TestFixtures(t *testing.T) { - dir := fixturesDir() - manifestPath := filepath.Join(dir, "manifest.json") - - manifestData, err := os.ReadFile(manifestPath) - if err != nil { - t.Fatalf("Failed to read manifest: %v", err) - } - - var manifest map[string]fixtureEntry - if err := json.Unmarshal(manifestData, &manifest); err != nil { - t.Fatalf("Failed to parse manifest: %v", err) - } - - for key, entry := range manifest { - t.Run(entry.Name, func(t *testing.T) { - csvFile := entry.CsvFile - if csvFile == "" { - csvFile = key - } - - csvData, err := os.ReadFile(filepath.Join(dir, csvFile+".csv")) - if err != nil { - t.Fatalf("Failed to read CSV file %s: %v", csvFile, err) - } - - result, err := parseFixture(string(csvData), entry.Opt, entry.JsonicOpt) - if err != nil { - if entry.Err != "" { - return // expected error - } - t.Fatalf("Unexpected error: %v", err) - } - - if entry.Err != "" { - t.Fatalf("Expected error %s but got none", entry.Err) - } - - expectedData, err := os.ReadFile(filepath.Join(dir, key+".json")) - if err != nil { - t.Fatalf("Failed to read expected JSON: %v", err) - } - - var expected []any - if err := json.Unmarshal(expectedData, &expected); err != nil { - t.Fatalf("Failed to parse expected JSON: %v", err) - } - - resultNorm := normalizeResult(result) - expectedNorm := normalizeJSON(expected) - - if !reflect.DeepEqual(resultNorm, expectedNorm) { - resultJSON, _ := json.MarshalIndent(resultNorm, "", " ") - expectedJSON, _ := json.MarshalIndent(expectedNorm, "", " ") - t.Errorf("Fixture %q mismatch:\nGot: %s\nExpected: %s", - entry.Name, string(resultJSON), string(expectedJSON)) - } - }) - } -} - -func TestPlugin(t *testing.T) { - j := jsonic.Make() - j.UseDefaults(Csv, Defaults) - - result, err := j.Parse("a,b\n1,2\n3,4") - if err != nil { - t.Fatalf("Plugin parse error: %v", err) - } - - arr, ok := result.([]any) - if !ok { - t.Fatalf("Expected []any, got %T", result) - } - - if len(arr) != 2 { - t.Fatalf("Expected 2 records, got %d", len(arr)) - } - - r0 := toMap(arr[0]) - if r0["a"] != "1" || r0["b"] != "2" { - t.Errorf("Record 0: expected {a:1,b:2}, got %v", r0) - } -} - -func TestPluginWithOptions(t *testing.T) { - j := jsonic.Make() - j.UseDefaults(Csv, Defaults, map[string]any{"object": false}) - - result, err := j.Parse("a,b\n1,2") - if err != nil { - t.Fatalf("Plugin parse error: %v", err) - } - - arr, ok := result.([]any) - if !ok { - t.Fatalf("Expected []any, got %T", result) - } - - if len(arr) != 1 { - t.Fatalf("Expected 1 record, got %d", len(arr)) - } - - inner, ok := arr[0].([]any) - if !ok { - t.Fatalf("Expected inner []any, got %T", arr[0]) - } - - if inner[0] != "1" || inner[1] != "2" { - t.Errorf("Expected [1,2], got %v", inner) - } -} - -func TestPluginEmpty(t *testing.T) { - j := jsonic.Make() - j.UseDefaults(Csv, Defaults) - - result, err := j.Parse("") - if err != nil { - t.Fatalf("Plugin parse error: %v", err) - } - - arr, ok := result.([]any) - if !ok { - t.Fatalf("Expected []any, got %T: %v", result, result) - } - - if len(arr) != 0 { - t.Errorf("Expected empty array, got %v", arr) - } -} - -func TestUsePlugin(t *testing.T) { - j := jsonic.Make() - j.Use(Csv, nil) - - result, err := j.Parse("a,b\n1,2") - if err != nil { - t.Logf("Plugin parse returned error (expected with basic plugin): %v", err) - } - _ = result -} - -func TestEmptyRecords(t *testing.T) { - result, _ := csvParse("a\n1\n\n2\n3\n\n\n4\n") - assertRecords(t, "empty-ignored", result, []map[string]any{ - {"a": "1"}, {"a": "2"}, {"a": "3"}, {"a": "4"}, - }) - - result2, _ := csvParse("a\n1\n\n2\n3\n\n\n4\n", - map[string]any{"record": map[string]any{"empty": true}}) - assertRecords(t, "empty-preserved", result2, []map[string]any{ - {"a": "1"}, {"a": ""}, {"a": "2"}, {"a": "3"}, - {"a": ""}, {"a": ""}, {"a": "4"}, - }) -} - -func TestHeader(t *testing.T) { - result, _ := csvParse("\na,b\nA,B") - assertRecords(t, "header-skip-leading", result, []map[string]any{ - {"a": "A", "b": "B"}, - }) - - result2, _ := csvParse("\na,b\nA,B", map[string]any{"header": false}) - assertRecords(t, "no-header", result2, []map[string]any{ - {"field~0": "a", "field~1": "b"}, - {"field~0": "A", "field~1": "B"}, - }) -} - -func TestDoubleQuotes(t *testing.T) { - tests := []struct { - input string - expected string - }{ - {`a` + "\n" + `"b"`, "b"}, - {`a` + "\n" + `"""b"`, `"b`}, - {`a` + "\n" + `"b"""`, `b"`}, - {`a` + "\n" + `"""b"""`, `"b"`}, - {`a` + "\n" + `"b""c"`, `b"c`}, - {`a` + "\n" + `"b""c""d"`, `b"c"d`}, - {`a` + "\n" + `"""""b"`, `""b`}, - {`a` + "\n" + `"b"""""`, `b""`}, - {`a` + "\n" + `"""""b"""""`, `""b""`}, - } - - for _, tt := range tests { - result, err := csvParse(tt.input) - if err != nil { - t.Errorf("Parse(%q): error: %v", tt.input, err) - continue - } - if len(result) != 1 { - t.Errorf("Parse(%q): expected 1 record, got %d", tt.input, len(result)) - continue - } - m := toMap(result[0]) - if m["a"] != tt.expected { - t.Errorf("Parse(%q): expected a=%q, got a=%q", tt.input, tt.expected, m["a"]) - } - } -} - -func TestTrim(t *testing.T) { - r1, _ := csvParse("a\n b") - assertField(t, "no-trim-leading", r1, "a", " b") - - r2, _ := csvParse("a\nb ") - assertField(t, "no-trim-trailing", r2, "a", "b ") - - r3, _ := csvParse("a\n b ") - assertField(t, "no-trim-both", r3, "a", " b ") - - r4, _ := csvParse("a\n b", map[string]any{"trim": true}) - assertField(t, "trim-leading", r4, "a", "b") - - r5, _ := csvParse("a\nb ", map[string]any{"trim": true}) - assertField(t, "trim-trailing", r5, "a", "b") - - r6, _ := csvParse("a\n b c ", map[string]any{"trim": true}) - assertField(t, "trim-internal", r6, "a", "b c") -} - -func TestComment(t *testing.T) { - r1, _ := csvParse("a\n# b") - assertField(t, "no-comment", r1, "a", "# b") - - r2, _ := csvParse("a\n# b", map[string]any{"comment": true}) - if len(r2) != 0 { - t.Errorf("comment-line: expected 0 records, got %d", len(r2)) - } - - r3, _ := csvParse("a\n b #c", map[string]any{"comment": true}) - assertField(t, "comment-inline", r3, "a", " b ") -} - -func TestNumber(t *testing.T) { - r1, _ := csvParse("a\n1") - assertField(t, "no-number", r1, "a", "1") - - r2, _ := csvParse("a\n1", map[string]any{"number": true}) - m := toMap(r2[0]) - if m["a"] != float64(1) { - t.Errorf("number: expected 1 (float64), got %v (%T)", m["a"], m["a"]) - } -} - -func TestValue(t *testing.T) { - r1, _ := csvParse("a\ntrue") - assertField(t, "no-value", r1, "a", "true") - - r2, _ := csvParse("a\ntrue", map[string]any{"value": true}) - m := toMap(r2[0]) - if m["a"] != true { - t.Errorf("value-true: expected true, got %v (%T)", m["a"], m["a"]) - } - - r3, _ := csvParse("a\nfalse", map[string]any{"value": true}) - m3 := toMap(r3[0]) - if m3["a"] != false { - t.Errorf("value-false: expected false, got %v (%T)", m3["a"], m3["a"]) - } - - r4, _ := csvParse("a\nnull", map[string]any{"value": true}) - m4 := toMap(r4[0]) - if m4["a"] != nil { - t.Errorf("value-null: expected nil, got %v (%T)", m4["a"], m4["a"]) - } -} - -func TestStream(t *testing.T) { - var events []string - var records []any - - j := jsonic.Make() - j.UseDefaults(Csv, Defaults, map[string]any{ - "stream": func(what string, record any) { - events = append(events, what) - if what == "record" { - records = append(records, record) - } - }, - }) - j.Parse("a,b\n1,2\n3,4\n5,6") - - if len(events) < 3 { - t.Fatalf("Expected at least 3 events, got %d", len(events)) - } - if events[0] != "start" { - t.Errorf("First event should be 'start', got %q", events[0]) - } - if events[len(events)-1] != "end" { - t.Errorf("Last event should be 'end', got %q", events[len(events)-1]) - } - if len(records) != 3 { - t.Errorf("Expected 3 records, got %d", len(records)) - } -} - -func TestSeparators(t *testing.T) { - result, _ := csvParse("a|b|c\nA|B|C\nAA|BB|CC", - map[string]any{"field": map[string]any{"separation": "|"}}) - assertRecords(t, "pipe", result, []map[string]any{ - {"a": "A", "b": "B", "c": "C"}, - {"a": "AA", "b": "BB", "c": "CC"}, - }) - - result2, _ := csvParse("a~~b~~c\nA~~B~~C", - map[string]any{"field": map[string]any{"separation": "~~"}}) - assertRecords(t, "multi-char", result2, []map[string]any{ - {"a": "A", "b": "B", "c": "C"}, - }) -} - -func TestRecordSeparators(t *testing.T) { - result, _ := csvParse("a,b,c%A,B,C%AA,BB,CC", - map[string]any{"record": map[string]any{"separators": "%"}}) - assertRecords(t, "record-sep", result, []map[string]any{ - {"a": "A", "b": "B", "c": "C"}, - {"a": "AA", "b": "BB", "c": "CC"}, - }) -} - -// parseFixture parses CSV with optional jsonic-level options for fixtures. -func parseFixture(src string, pluginOpts map[string]any, jsonicOpts map[string]any) ([]any, error) { - if len(jsonicOpts) == 0 { - return csvParse(src, pluginOpts) - } - - j := jsonic.Make() - - // Apply jsonicOpt: value.def - if valOpt, ok := jsonicOpts["value"].(map[string]any); ok { - if defMap, ok := valOpt["def"].(map[string]any); ok { - vopts := jsonic.Options{Value: &jsonic.ValueOptions{ - Def: map[string]*jsonic.ValueDef{ - "true": {Val: true}, - "false": {Val: false}, - "null": {Val: nil}, - }, - }} - for k, v := range defMap { - if v == nil { - delete(vopts.Value.Def, k) - } else if vm, ok := v.(map[string]any); ok { - vopts.Value.Def[k] = &jsonic.ValueDef{Val: vm["val"]} - } - } - j.SetOptions(vopts) - } - } - - // Apply jsonicOpt: comment.def - if cmtOpt, ok := jsonicOpts["comment"].(map[string]any); ok { - if defMap, ok := cmtOpt["def"].(map[string]any); ok { - copts := jsonic.Options{Comment: &jsonic.CommentOptions{ - Def: make(map[string]*jsonic.CommentDef), - }} - for name, v := range defMap { - if cm, ok := v.(map[string]any); ok { - def := &jsonic.CommentDef{} - if start, ok := cm["start"].(string); ok { - def.Start = start - } - if end, ok := cm["end"].(string); ok { - def.End = end - } else { - def.Line = true - } - copts.Comment.Def[name] = def - } - } - j.SetOptions(copts) - } - } - - j.UseDefaults(Csv, Defaults, pluginOpts) - - result, err := j.Parse(src) - if err != nil { - return nil, err - } - if result == nil { - return []any{}, nil - } - if arr, ok := result.([]any); ok { - return arr, nil - } - return []any{}, nil -} - -// Helpers - -func assertRecords(t *testing.T, name string, result []any, expected []map[string]any) { - t.Helper() - if len(result) != len(expected) { - t.Errorf("%s: expected %d records, got %d: %v", name, len(expected), len(result), result) - return - } - for i, exp := range expected { - m := toMap(result[i]) - for k, v := range exp { - if fmt.Sprintf("%v", m[k]) != fmt.Sprintf("%v", v) { - t.Errorf("%s: record %d, field %q: expected %v, got %v", name, i, k, v, m[k]) - } - } - } -} - -func assertField(t *testing.T, name string, result []any, key string, expected string) { - t.Helper() - if len(result) != 1 { - t.Errorf("%s: expected 1 record, got %d", name, len(result)) - return - } - m := toMap(result[0]) - if m[key] != expected { - t.Errorf("%s: expected %q=%q, got %q=%q", name, key, expected, key, m[key]) - } -} - -func toMap(v any) map[string]any { - switch m := v.(type) { - case map[string]any: - return m - case orderedMap: - return m.m - default: - return nil - } -} - -func normalizeResult(result []any) []any { - out := make([]any, len(result)) - for i, r := range result { - out[i] = normalizeValue(r) - } - return out -} - -func normalizeValue(v any) any { - switch val := v.(type) { - case orderedMap: - m := make(map[string]any) - for k, v := range val.m { - m[k] = normalizeValue(v) - } - return m - case map[string]any: - m := make(map[string]any) - for k, v := range val { - m[k] = normalizeValue(v) - } - return m - case []any: - out := make([]any, len(val)) - for i, v := range val { - out[i] = normalizeValue(v) - } - return out - default: - return v - } -} - -func normalizeJSON(v any) any { - switch val := v.(type) { - case []any: - out := make([]any, len(val)) - for i, item := range val { - out[i] = normalizeJSON(item) - } - return out - case map[string]any: - m := make(map[string]any) - for k, v := range val { - m[k] = normalizeJSON(v) - } - return m - default: - return v - } -} diff --git a/go/go.mod b/go/go.mod index 55ab5a2..8191f07 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,4 +1,4 @@ -module github.com/jsonicjs/csv/go +module github.com/jsonicjs/zon/go go 1.24.7 diff --git a/go/zon.go b/go/zon.go new file mode 100644 index 0000000..6a75663 --- /dev/null +++ b/go/zon.go @@ -0,0 +1,596 @@ +/* Copyright (c) 2025 Richard Rodger, MIT License */ + +// Package zon is a jsonic plugin that parses Zig Object Notation (ZON) +// syntax. ZON is a data format based on Zig anonymous struct literals. +// +// Example: +// +// .{ +// .name = "example", +// .version = "0.0.1", +// .deps = .{ .foo = .{ .url = "https://..." } }, +// .paths = .{ "build.zig", "src" }, +// } +package zon + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" + + jsonic "github.com/jsonicjs/jsonic/go" +) + +const Version = "0.1.0" + +// --- BEGIN EMBEDDED zon-grammar.jsonic --- +const grammarText = ` +# ZON Grammar Definition +# Parses Zig Object Notation (ZON) - a data format based on Zig anonymous +# struct literals. +# +# Example: +# .{ +# .name = "example", +# .version = "0.0.1", +# .deps = .{ .foo = .{ .url = "https://..." } }, +# .paths = .{ "build.zig", "src" }, +# } +# +# The custom zon-dot lex matcher distinguishes struct (map) and tuple (list) +# openings at lex time by peeking ahead: +# .{ followed by .ident = -> emits #OB (struct / map) +# .{ otherwise -> emits #OS (tuple / list) +# Both close on } which lexes as #CB. The list rules below use #CB (not +# the default #CS) as the list terminator so that the single } character +# closes both struct and tuple literals. +# +# A bare .identifier emits #TX with val = identifier (the leading dot is +# stripped). This token is both a valid KEY (when followed by =) and a +# valid VAL (when used as an enum literal). + +{ + rule: val: open: [ + # Empty .{} -> empty list. + { s: '#OS #CB' b: 2 p: list g: 'zon,list,empty' } + ] + + rule: list: open: [ + { s: '#OS #CB' b: 1 g: 'zon,list,empty' } + { s: '#OS' p: elem g: 'zon,list,open' } + ] + rule: list: close: [ + { s: '#CB' g: 'zon,list,close' } + ] + + rule: elem: close: [ + { s: '#CA #CB' b: 1 g: 'zon,elem,trailing' } + { s: '#CA' r: elem g: 'zon,elem,next' } + { s: '#CB' b: 1 g: 'zon,elem,end' } + ] + + rule: pair: close: [ + { s: '#CA #CB' b: 1 g: 'zon,pair,trailing' } + { s: '#CA' r: pair g: 'zon,pair,next' } + { s: '#CB' b: 1 g: 'zon,pair,end' } + ] +} +` +// --- END EMBEDDED zon-grammar.jsonic --- + +// Zon is a jsonic plugin that adds ZON parsing support. +// Options are pre-merged with Defaults by jsonic.UseDefaults. +func Zon(j *jsonic.Jsonic, options map[string]any) error { + // Guard against re-invocation: SetOptions triggers plugin re-application. + if j.Decoration("zon-init") != nil { + return nil + } + j.Decorate("zon-init", true) + + charAsNumber := toBool(options["charAsNumber"]) + enumTag := toString(options["enumTag"]) + + eqSrc := "=" + jsonicOptions := jsonic.Options{ + Rule: &jsonic.RuleOptions{ + // Remove jsonic extensions (implicit maps/lists, top-level commas, + // path dives). ZON uses explicit struct literals only. + Exclude: "jsonic,imp", + Start: "val", + }, + Fixed: &jsonic.FixedOptions{ + Token: map[string]*string{ + // Bare `{`, `[`, `]` are not valid in ZON. `.{` is handled by + // the custom zonDot lex matcher below. + "#OB": nil, + "#OS": nil, + "#CS": nil, + // `=` replaces `:` as the key/value separator. + "#CL": &eqSrc, + }, + }, + TokenSet: map[string][]string{ + // ZON field names are identifiers only. + "KEY": {"#TX"}, + }, + String: &jsonic.StringOptions{ + Chars: "\"", + MultiChars: "", + Escape: map[string]string{"n": "\n", "r": "\r", "t": "\t", "\\": "\\", "\"": "\"", "'": "'"}, + AllowUnknown: boolPtr(false), + }, + Number: &jsonic.NumberOptions{ + Lex: boolPtr(true), + Hex: boolPtr(true), + Oct: boolPtr(true), + Bin: boolPtr(true), + Sep: "_", + }, + Comment: &jsonic.CommentOptions{ + Lex: boolPtr(true), + Def: map[string]*jsonic.CommentDef{ + "hash": {Line: true, Start: "#", Lex: boolPtr(false)}, + "slash": {Line: true, Start: "//", Lex: boolPtr(true)}, + "multi": {Line: false, Start: "/*", End: "*/", Lex: boolPtr(false)}, + }, + }, + Value: &jsonic.ValueOptions{ + Lex: boolPtr(true), + Def: map[string]*jsonic.ValueDef{ + "true": {Val: true}, + "false": {Val: false}, + "null": {Val: nil}, + }, + }, + Text: &jsonic.TextOptions{ + // Disabled: the default text matcher would consume identifiers, + // but in ZON identifiers only appear as `.ident` and are handled + // by the custom zonDot matcher. + Lex: boolPtr(false), + }, + Lex: &jsonic.LexOptions{ + Match: map[string]*jsonic.MatchSpec{ + "zonDot": {Order: 100000, Make: buildZonDotMatcher()}, + "zonMultiString": {Order: 110000, Make: buildZonMultiStringMatcher()}, + "zonChar": {Order: 120000, Make: buildZonCharMatcher(charAsNumber)}, + }, + }, + } + + j.SetOptions(jsonicOptions) + + // If enumTag is set, wrap enum-literal values into `{ [enumTag]: name }`. + // Runs before the default `@val-bc` (via /prepend) so it takes precedence. + refs := map[jsonic.FuncRef]any{} + if enumTag != "" { + refs["@val-bc/prepend"] = jsonic.StateAction(func(r *jsonic.Rule, _ *jsonic.Context) { + if !jsonic.IsUndefined(r.Node) { + return + } + if r.Child != nil && !jsonic.IsUndefined(r.Child.Node) { + return + } + if r.OS == 0 || r.O0 == nil { + return + } + tkn := r.O0 + if tkn.Use == nil { + return + } + if _, ok := tkn.Use["zonEnum"]; !ok { + return + } + if name, ok := tkn.Val.(string); ok { + r.Node = map[string]any{enumTag: name} + } + }) + } + + gs, err := parseGrammarText(grammarText, refs) + if err != nil { + return err + } + if err := j.Grammar(gs); err != nil { + return fmt.Errorf("zon: failed to apply grammar: %w", err) + } + + return nil +} + +// Defaults matches the TS Zon.defaults. Used with jsonic.UseDefaults. +var Defaults = map[string]any{ + "charAsNumber": false, + "enumTag": "", +} + +// Custom lex matcher for `.`-prefixed tokens: +// +// `.{` -> #OB if followed by `.ident =`, else #OS +// `.identifier` -> #TX (Val = identifier, Use["zonEnum"] = true) +// +// Runs ahead of the fixed-token matcher so it reliably owns the `.` prefix. +func buildZonDotMatcher() jsonic.MakeLexMatcher { + return func(_ *jsonic.LexConfig, _ *jsonic.Options) jsonic.LexMatcher { + return func(lex *jsonic.Lex, _ *jsonic.Rule) *jsonic.Token { + pnt := lex.Cursor() + src := lex.Src + sI := pnt.SI + if sI >= len(src) || src[sI] != '.' { + return nil + } + + // `.{` opens a struct literal. Decide map vs list by peeking. + if sI+1 < len(src) && src[sI+1] == '{' { + var tkn *jsonic.Token + if peekIsMapOpen(src, sI+2) { + tkn = lex.Token("#OB", jsonic.TinOB, nil, ".{") + } else { + tkn = lex.Token("#OS", jsonic.TinOS, nil, ".{") + } + pnt.SI = sI + 2 + pnt.CI += 2 + return tkn + } + + // `.identifier` - field name or enum literal. + if sI+1 >= len(src) || !isIdStart(src[sI+1]) { + return nil + } + eI := sI + 1 + for eI < len(src) && isIdCont(src[eI]) { + eI++ + } + + srcText := src[sI:eI] + name := src[sI+1 : eI] + tkn := lex.Token("#TX", jsonic.TinTX, name, srcText) + tkn.Use = map[string]any{"zonEnum": true} + pnt.SI = eI + pnt.CI += eI - sI + return tkn + } + } +} + +// peekIsMapOpen returns true if the source position inside `.{ ... }` begins +// with `.ident=`, meaning a struct/map literal rather than a tuple. +func peekIsMapOpen(src string, start int) bool { + i := skipInsig(src, start) + if i >= len(src) || src[i] != '.' { + return false + } + i++ + if i >= len(src) || !isIdStart(src[i]) { + return false + } + i++ + for i < len(src) && isIdCont(src[i]) { + i++ + } + i = skipInsig(src, i) + return i < len(src) && src[i] == '=' +} + +// skipInsig advances past whitespace, newlines, and `//` line comments. +func skipInsig(src string, i int) int { + for i < len(src) { + c := src[i] + if c == ' ' || c == '\t' || c == '\r' || c == '\n' { + i++ + } else if c == '/' && i+1 < len(src) && src[i+1] == '/' { + i += 2 + for i < len(src) && src[i] != '\n' && src[i] != '\r' { + i++ + } + } else { + break + } + } + return i +} + +func isIdStart(c byte) bool { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' +} + +func isIdCont(c byte) bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' +} + +// Multi-line Zig strings: consecutive lines starting with `\\`. Each `\\` +// line contributes its content verbatim (after the `\\`); lines join with `\n`. +func buildZonMultiStringMatcher() jsonic.MakeLexMatcher { + return func(cfg *jsonic.LexConfig, _ *jsonic.Options) jsonic.LexMatcher { + return func(lex *jsonic.Lex, _ *jsonic.Rule) *jsonic.Token { + pnt := lex.Cursor() + src := lex.Src + if pnt.SI+1 >= len(src) || src[pnt.SI] != '\\' || src[pnt.SI+1] != '\\' { + return nil + } + + startI := pnt.SI + startCI := pnt.CI + sI := pnt.SI + rI := pnt.RI + var parts []string + + for sI+1 < len(src) && src[sI] == '\\' && src[sI+1] == '\\' { + sI += 2 + lineStart := sI + for sI < len(src) && !cfg.LineChars[rune(src[sI])] { + sI++ + } + parts = append(parts, src[lineStart:sI]) + + // Consume line terminator (handle \r\n as one). + if sI < len(src) && cfg.LineChars[rune(src[sI])] { + ch := src[sI] + if cfg.RowChars[rune(ch)] { + rI++ + } + sI++ + if sI < len(src) && ch == '\r' && src[sI] == '\n' { + sI++ + } + } + + // Look for another `\\` continuation after whitespace. + peek := sI + for peek < len(src) && (src[peek] == ' ' || src[peek] == '\t') { + peek++ + } + if peek+1 >= len(src) || src[peek] != '\\' || src[peek+1] != '\\' { + break + } + sI = peek + } + + val := strings.Join(parts, "\n") + tsrc := src[startI:sI] + tkn := lex.Token("#ST", jsonic.TinST, val, tsrc) + pnt.SI = sI + pnt.RI = rI + pnt.CI = startCI + (sI - startI) + return tkn + } + } +} + +// Zig character literal: `'x'`, `'\n'`, `'\x41'`, `'\u{1F600}'`. +// Produces a numeric code point (if charAsNumber) or a one-char string. +func buildZonCharMatcher(charAsNumber bool) jsonic.MakeLexMatcher { + return func(_ *jsonic.LexConfig, _ *jsonic.Options) jsonic.LexMatcher { + return func(lex *jsonic.Lex, _ *jsonic.Rule) *jsonic.Token { + pnt := lex.Cursor() + src := lex.Src + sI := pnt.SI + if sI >= len(src) || src[sI] != '\'' { + return nil + } + + i := sI + 1 + if i >= len(src) { + return nil + } + + var codepoint int + + if src[i] == '\\' { + i++ + if i >= len(src) { + return nil + } + switch src[i] { + case 'n': + codepoint = '\n' + i++ + case 'r': + codepoint = '\r' + i++ + case 't': + codepoint = '\t' + i++ + case '\\': + codepoint = '\\' + i++ + case '\'': + codepoint = '\'' + i++ + case '"': + codepoint = '"' + i++ + case '0': + codepoint = 0 + i++ + case 'x': + i++ + if i+2 > len(src) { + return nil + } + hex := src[i : i+2] + if !isHex(hex) { + return nil + } + n, err := strconv.ParseInt(hex, 16, 32) + if err != nil { + return nil + } + codepoint = int(n) + i += 2 + case 'u': + i++ + if i >= len(src) || src[i] != '{' { + return nil + } + i++ + end := strings.IndexByte(src[i:], '}') + if end < 0 { + return nil + } + end += i + hex := src[i:end] + if !isHex(hex) { + return nil + } + n, err := strconv.ParseInt(hex, 16, 32) + if err != nil { + return nil + } + codepoint = int(n) + i = end + 1 + default: + return nil + } + } else if src[i] != '\'' { + r, size := utf8.DecodeRuneInString(src[i:]) + if r == utf8.RuneError && size <= 1 { + return nil + } + codepoint = int(r) + i += size + } else { + return nil + } + + if i >= len(src) || src[i] != '\'' { + return nil + } + i++ + + var val any + if charAsNumber { + val = float64(codepoint) + } else { + val = string(rune(codepoint)) + } + tsrc := src[sI:i] + tkn := lex.Token("#NR", jsonic.TinNR, val, tsrc) + pnt.SI = i + pnt.CI += i - sI + return tkn + } + } +} + +func isHex(s string) bool { + if len(s) == 0 { + return false + } + for i := 0; i < len(s); i++ { + c := s[i] + if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + return false + } + } + return true +} + +// parseGrammarText parses grammar text into a GrammarSpec with refs attached. +func parseGrammarText(text string, refs map[jsonic.FuncRef]any) (*jsonic.GrammarSpec, error) { + parsed, err := jsonic.Make().Parse(text) + if err != nil { + return nil, fmt.Errorf("zon: failed to parse grammar text: %w", err) + } + parsedMap, ok := parsed.(map[string]any) + if !ok { + return nil, fmt.Errorf("zon: grammar text did not parse to a map") + } + gs := &jsonic.GrammarSpec{Ref: refs} + ruleMap, ok := parsedMap["rule"].(map[string]any) + if !ok { + return gs, nil + } + gs.Rule = make(map[string]*jsonic.GrammarRuleSpec, len(ruleMap)) + for name, rDef := range ruleMap { + rd, ok := rDef.(map[string]any) + if !ok { + continue + } + grs := &jsonic.GrammarRuleSpec{} + if openDef, ok := rd["open"]; ok { + grs.Open = buildGrammarAlts(openDef) + } + if closeDef, ok := rd["close"]; ok { + grs.Close = buildGrammarAlts(closeDef) + } + gs.Rule[name] = grs + } + return gs, nil +} + +// buildGrammarAlts converts a parsed-jsonic alt array into []*GrammarAltSpec. +func buildGrammarAlts(def any) []*jsonic.GrammarAltSpec { + arr, ok := def.([]any) + if !ok { + return nil + } + alts := make([]*jsonic.GrammarAltSpec, 0, len(arr)) + for _, item := range arr { + m, ok := item.(map[string]any) + if !ok { + alts = append(alts, &jsonic.GrammarAltSpec{}) + continue + } + ga := &jsonic.GrammarAltSpec{} + if s, ok := m["s"]; ok { + switch sv := s.(type) { + case string: + ga.S = sv + case []any: + strs := make([]string, len(sv)) + for i, v := range sv { + strs[i], _ = v.(string) + } + ga.S = strs + } + } + if b, ok := m["b"]; ok { + switch bv := b.(type) { + case float64: + ga.B = int(bv) + case int: + ga.B = bv + } + } + if p, ok := m["p"].(string); ok { + ga.P = p + } + if r, ok := m["r"].(string); ok { + ga.R = r + } + if a, ok := m["a"].(string); ok { + ga.A = jsonic.FuncRef(a) + } + if c, ok := m["c"]; ok { + switch cv := c.(type) { + case string: + ga.C = cv + case map[string]any: + ga.C = cv + } + } + if u, ok := m["u"].(map[string]any); ok { + ga.U = u + } + if g, ok := m["g"].(string); ok { + ga.G = g + } + alts = append(alts, ga) + } + return alts +} + +func toBool(v any) bool { + b, _ := v.(bool) + return b +} + +func toString(v any) string { + s, _ := v.(string) + return s +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/go/zon_test.go b/go/zon_test.go new file mode 100644 index 0000000..ab4c061 --- /dev/null +++ b/go/zon_test.go @@ -0,0 +1,205 @@ +/* Copyright (c) 2025 Richard Rodger, MIT License */ + +package zon + +import ( + "reflect" + "testing" + + jsonic "github.com/jsonicjs/jsonic/go" +) + +// parse creates a jsonic instance with the Zon plugin and parses src. +func parse(t *testing.T, src string, opts ...map[string]any) any { + t.Helper() + j := jsonic.Make() + j.UseDefaults(Zon, Defaults, opts...) + result, err := j.Parse(src) + if err != nil { + t.Fatalf("parse(%q) unexpected error: %v", src, err) + } + return result +} + +func parseErr(t *testing.T, src string, opts ...map[string]any) error { + t.Helper() + j := jsonic.Make() + j.UseDefaults(Zon, Defaults, opts...) + _, err := j.Parse(src) + return err +} + +func assertEqual(t *testing.T, label string, got, want any) { + t.Helper() + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: got %#v, want %#v", label, got, want) + } +} + +func TestScalars(t *testing.T) { + assertEqual(t, "int", parse(t, "42"), float64(42)) + assertEqual(t, "float", parse(t, "3.14"), float64(3.14)) + assertEqual(t, "true", parse(t, "true"), true) + assertEqual(t, "false", parse(t, "false"), false) + assertEqual(t, "null", parse(t, "null"), nil) + assertEqual(t, "string", parse(t, `"hello"`), "hello") +} + +func TestNumericBases(t *testing.T) { + assertEqual(t, "hex", parse(t, "0x2a"), float64(42)) + assertEqual(t, "oct", parse(t, "0o52"), float64(42)) + assertEqual(t, "bin", parse(t, "0b101010"), float64(42)) + assertEqual(t, "sep", parse(t, "1_000_000"), float64(1000000)) +} + +func TestEnumLiteralAsValue(t *testing.T) { + assertEqual(t, "single", parse(t, ".foo"), "foo") + assertEqual(t, "snake_case", parse(t, ".bar_baz"), "bar_baz") +} + +func TestEmptyStruct(t *testing.T) { + got := parse(t, ".{}") + if !reflect.DeepEqual(got, []any{}) { + t.Errorf("empty .{} got %#v, want []any{}", got) + } +} + +func TestSimpleStruct(t *testing.T) { + assertEqual(t, "single", parse(t, ".{ .a = 1 }"), + map[string]any{"a": float64(1)}) + assertEqual(t, "two", parse(t, ".{ .a = 1, .b = 2 }"), + map[string]any{"a": float64(1), "b": float64(2)}) +} + +func TestTrailingCommaStruct(t *testing.T) { + assertEqual(t, "single", parse(t, ".{ .a = 1, }"), + map[string]any{"a": float64(1)}) + assertEqual(t, "two", parse(t, ".{ .a = 1, .b = 2, }"), + map[string]any{"a": float64(1), "b": float64(2)}) +} + +func TestTuple(t *testing.T) { + assertEqual(t, "numbers", parse(t, ".{ 1, 2, 3 }"), + []any{float64(1), float64(2), float64(3)}) + assertEqual(t, "strings", parse(t, `.{ "a", "b" }`), + []any{"a", "b"}) +} + +func TestTrailingCommaTuple(t *testing.T) { + assertEqual(t, "trailing", parse(t, ".{ 1, 2, 3, }"), + []any{float64(1), float64(2), float64(3)}) +} + +func TestNestedStruct(t *testing.T) { + assertEqual(t, "nested", parse(t, ".{ .a = .{ .b = 1 } }"), + map[string]any{"a": map[string]any{"b": float64(1)}}) +} + +func TestNestedTuple(t *testing.T) { + assertEqual(t, "nested", + parse(t, ".{ .{ 1, 2 }, .{ 3, 4 } }"), + []any{ + []any{float64(1), float64(2)}, + []any{float64(3), float64(4)}, + }) +} + +func TestMixedNesting(t *testing.T) { + got := parse(t, ".{ .xs = .{ 1, 2, 3 }, .y = .{ .z = true } }") + want := map[string]any{ + "xs": []any{float64(1), float64(2), float64(3)}, + "y": map[string]any{"z": true}, + } + assertEqual(t, "mixed", got, want) +} + +func TestStringEscapes(t *testing.T) { + assertEqual(t, "newline", parse(t, `"a\nb"`), "a\nb") + assertEqual(t, "tab", parse(t, `"a\tb"`), "a\tb") + assertEqual(t, "backslash", parse(t, `"a\\b"`), `a\b`) +} + +func TestEnumAsFieldValue(t *testing.T) { + assertEqual(t, "field", parse(t, ".{ .kind = .red }"), + map[string]any{"kind": "red"}) +} + +func TestComments(t *testing.T) { + src := `.{ + // a comment + .name = "x", // trailing comment + .version = "1.0", // version + }` + got := parse(t, src) + want := map[string]any{"name": "x", "version": "1.0"} + assertEqual(t, "with comments", got, want) +} + +func TestRealisticZon(t *testing.T) { + src := `.{ + .name = "example", + .version = "0.0.1", + .minimum_zig_version = "0.14.0", + .dependencies = .{ + .foo = .{ + .url = "https://example.com/foo.tar.gz", + .hash = "1220deadbeef", + }, + }, + .paths = .{ + "build.zig", + "src", + "", + }, + }` + got := parse(t, src) + want := map[string]any{ + "name": "example", + "version": "0.0.1", + "minimum_zig_version": "0.14.0", + "dependencies": map[string]any{ + "foo": map[string]any{ + "url": "https://example.com/foo.tar.gz", + "hash": "1220deadbeef", + }, + }, + "paths": []any{"build.zig", "src", ""}, + } + assertEqual(t, "build.zig.zon", got, want) +} + +func TestCharLiteralAsNumber(t *testing.T) { + assertEqual(t, "A", parse(t, "'A'", map[string]any{"charAsNumber": true}), float64(65)) + assertEqual(t, "n", parse(t, `'\n'`, map[string]any{"charAsNumber": true}), float64(10)) + assertEqual(t, "emoji", + parse(t, `'\u{1F600}'`, map[string]any{"charAsNumber": true}), + float64(0x1F600)) +} + +func TestCharLiteralAsString(t *testing.T) { + assertEqual(t, "A", parse(t, "'A'"), "A") +} + +func TestMultiLineString(t *testing.T) { + src := ".{\n" + + "\t.text = \\\\hello\n" + + "\t\t\\\\world\n" + + "\t,\n" + + "}" + got := parse(t, src) + want := map[string]any{"text": "hello\nworld"} + assertEqual(t, "multi-line", got, want) +} + +func TestEnumTagOption(t *testing.T) { + got := parse(t, ".{ .kind = .red }", map[string]any{"enumTag": "$enum"}) + want := map[string]any{"kind": map[string]any{"$enum": "red"}} + assertEqual(t, "enum-tagged", got, want) +} + +func TestSyntaxError(t *testing.T) { + // `{` without `.` is not valid ZON. + if err := parseErr(t, "{ a = 1 }"); err == nil { + t.Error("expected error for bare { but got none") + } +} diff --git a/package.json b/package.json index 129bded..bb5b350 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,29 @@ { - "name": "@jsonic/csv", - "version": "0.10.0", - "description": "This plugin allows the [Jsonic](https://jsonic.senecajs.org) JSON parser to support csv syntax.", - "main": "dist/csv.js", + "name": "@jsonic/zon", + "version": "0.1.0", + "description": "This plugin allows the [Jsonic](https://jsonic.senecajs.org) JSON parser to support Zig Object Notation (ZON) syntax.", + "main": "dist/zon.js", "type": "commonjs", - "browser": "csv.min.js", - "types": "dist/csv.d.ts", - "homepage": "https://github.com/jsonicjs/csv", + "browser": "zon.min.js", + "types": "dist/zon.d.ts", + "homepage": "https://github.com/jsonicjs/zon", "keywords": [ - "pattern", - "matcher", + "zon", + "zig", + "jsonic", + "parser", "object", - "property", "json" ], "author": "Richard Rodger (http://richardrodger.com)", "repository": { "type": "git", - "url": "git://github.com/jsonicjs/csv.git" + "url": "git://github.com/jsonicjs/zon.git" }, "scripts": { "test": "node --enable-source-maps --test \"dist-test/*.test.js\"", "test-some": "node --enable-source-maps --test-name-pattern=\"$npm_config_pattern\" --test \"dist-test/*.test.js\"", - "test-watch": "node --test --watch dist-test/csv.test.js", + "test-watch": "node --test --watch dist-test/zon.test.js", "embed": "node embed-grammar.js", "watch": "tsc --build src test -w", "build": "node embed-grammar.js && tsc --build src test", @@ -40,7 +41,6 @@ ], "devDependencies": { "@types/node": "^25.6.0", - "csv-spectrum": "^2.0.0", "typescript": "^5.9.3" }, "peerDependencies": { diff --git a/src/csv.ts b/src/csv.ts deleted file mode 100644 index c4eaf01..0000000 --- a/src/csv.ts +++ /dev/null @@ -1,562 +0,0 @@ -/* Copyright (c) 2021-2025 Richard Rodger, MIT License */ - -// Import Jsonic types used by plugins. -import { - Jsonic, - Rule, - RuleSpec, - Plugin, - Context, - Config, - Options, - Lex, -} from 'jsonic' - -// See defaults below for commentary. -type CsvOptions = { - trim: boolean | null - comment: boolean | null - number: boolean | null - value: boolean | null - header: boolean - object: boolean - stream: null | ((what: string, record?: Record | Error) => void) - strict: boolean - field: { - separation: null | string - nonameprefix: string - empty: any - names: undefined | string[] - exact: boolean - } - record: { - separators: null | string - empty: boolean - } - string: { - quote: string - csv: null | boolean - } -} - -// --- BEGIN EMBEDDED csv-grammar.jsonic --- -const grammarText = ` -# CSV Grammar Definition -# Parsed by a standard Jsonic instance and passed to jsonic.grammar() -# Function references (@ prefixed) are resolved against the refs map -# -# Token naming: -# #LN - line ending (removed from per-instance IGNORE set) -# #SP - whitespace (removed from per-instance IGNORE set in strict mode) -# #CA - comma / field separator -# #ZZ - end of input -# #VAL - token set: text, string, number, value literals -# -# Rules csv, newline, record, text are fully defined here. -# Rules list, elem, val are modified in code (strict mode defines from scratch; -# non-strict prepends to existing defaults to preserve JSON parsing). - -{ - rule: csv: open: [ - { s: '#ZZ' } - { s: '#LN' p: newline c: '@not-record-empty' } - { p: record } - ] - - rule: newline: open: [ - { s: '#LN #LN' r: newline } - { s: '#LN' r: newline } - { s: '#ZZ' } - { r: record } - ] - rule: newline: close: [ - { s: '#LN #LN' r: newline } - { s: '#LN' r: newline } - { s: '#ZZ' } - { r: record } - ] - - rule: record: open: [ - { p: list } - ] - rule: record: close: [ - { s: '#ZZ' } - { s: '#LN #ZZ' b: 1 } - { s: '#LN' r: '@record-close-next' } - ] - - rule: text: open: [ - { s: ['#VAL' '#SP'] b: 1 r: text n: { text: 1 } g: 'csv,space,follows' a: '@text-follows' } - { s: ['#SP' '#VAL'] r: text n: { text: 1 } g: 'csv,space,leads' a: '@text-leads' } - { s: ['#SP' '#CA #LN #ZZ'] b: 1 n: { text: 1 } g: 'csv,end' a: '@text-end' } - { s: '#SP' n: { text: 1 } g: 'csv,space' a: '@text-space' p: '@text-space-push' } - {} - ] -} -` -// --- END EMBEDDED csv-grammar.jsonic --- - -// Plugin implementation. -const Csv: Plugin = (jsonic: Jsonic, options: CsvOptions) => { - // Normalize boolean options. - const strict = !!options.strict - const objres = !!options.object - const header = !!options.header - - // These may be changed below by superior options. - let trim = !!options.trim - let comment = !!options.comment - let opt_number = !!options.number - let opt_value = !!options.value - let record_empty = !!options.record?.empty - - const stream = options.stream - - // In strict mode, Jsonic field content is not parsed. - if (strict) { - if (false !== options.string.csv) { - jsonic.options({ - lex: { - match: { - stringcsv: { order: 1e5, make: buildCsvStringMatcher(options) }, - }, - }, - }) - } - jsonic.options({ - rule: { exclude: 'jsonic,imp' }, - }) - } - - // Fields may contain Jsonic content. - else { - if (true === options.string.csv) { - jsonic.options({ - lex: { - match: { - stringcsv: { order: 1e5, make: buildCsvStringMatcher(options) }, - }, - }, - }) - } - trim = null === options.trim ? true : trim - comment = null === options.comment ? true : comment - opt_number = null === options.number ? true : opt_number - opt_value = null === options.value ? true : opt_value - jsonic.options({ - rule: { exclude: 'imp' }, - }) - } - - // Stream rows as they are parsed, do not store in result. - if (stream) { - let parser = jsonic.internal().parser - let origStart = parser.start.bind(parser) - parser.start = (...args: any[]) => { - try { - return origStart(...args) - } catch (e: any) { - stream('error', e) - } - } - } - - let token: Record = {} - if (strict) { - // Disable JSON structure tokens - token = { - '#OB': null, - '#CB': null, - '#OS': null, - '#CS': null, - '#CL': null, - } - } - - // Custom "comma" - if (options.field.separation) { - token['#CA'] = options.field.separation - } - - // Jsonic option overrides. - let jsonicOptions: any = { - rule: { - start: 'csv', - }, - fixed: { - token, - }, - tokenSet: { - IGNORE: [ - strict ? null : undefined, // Handle #SP space - null, // Handle #LN newlines - undefined, // Still ignore #CM comments - ], - }, - number: { - lex: opt_number, - }, - value: { - lex: opt_value, - }, - comment: { - lex: comment, - }, - lex: { - emptyResult: [], - }, - line: { - single: record_empty, - chars: - null == options.record.separators - ? undefined - : options.record.separators, - rowChars: - null == options.record.separators - ? undefined - : options.record.separators, - }, - error: { - csv_extra_field: 'unexpected extra field value: $fsrc', - csv_missing_field: 'missing field', - }, - hint: { - csv_extra_field: `Row $row has too many fields (the first of which is: $fsrc). Only $len -fields per row are expected.`, - csv_missing_field: `Row $row has too few fields. $len fields per row are expected.`, - }, - } - - jsonic.options(jsonicOptions) - - - // Named function references for declarative grammar definition. - const refs: Record = { - - // === State actions (auto-wired by @rulename-{bo,ao,bc,ac} convention) === - - '@csv-bo': (r: Rule, ctx: Context) => { - ctx.u.recordI = 0 - stream && stream('start') - r.node = [] - }, - - '@csv-ac': (_r: Rule) => { - stream && stream('end') - }, - - '@record-bc': (r: Rule, ctx: Context) => { - let fields: string[] = ctx.u.fields || options.field.names - - if (0 === ctx.u.recordI && header) { - ctx.u.fields = undefined === r.child.node ? [] : r.child.node - } else { - let record: any = r.child.node || [] - - if (objres) { - let obj: Record = {} - let i = 0 - - if (fields) { - if (options.field.exact) { - if (record.length !== fields.length) { - return ctx.t0.bad( - record.length > fields.length - ? 'csv_extra_field' - : 'csv_missing_field', - ) - } - } - - let fI = 0 - for (; fI < fields.length; fI++) { - obj[fields[fI]] = - undefined === record[fI] ? options.field.empty : record[fI] - } - i = fI - } - - for (; i < record.length; i++) { - let field_name = options.field.nonameprefix + i - obj[field_name] = - undefined === record[i] ? options.field.empty : record[i] - } - - record = obj - } else { - for (let i = 0; i < record.length; i++) { - record[i] = - undefined === record[i] ? options.field.empty : record[i] - } - } - - if (stream) { - stream('record', record) - } else { - r.node.push(record) - } - } - - ctx.u.recordI++ - }, - - '@text-bc': (r: Rule) => { - r.parent.node = undefined === r.child.node ? r.node : r.child.node - }, - - - // === Alt actions === - - '@elem-open-empty': (r: Rule) => { - r.node.push(options.field.empty) - r.u.done = true - }, - - '@elem-close-trailing': (r: Rule) => { - r.node.push(options.field.empty) - }, - - '@text-follows': (r: Rule) => { - let v = 1 === r.n.text ? r : r.prev - r.node = v.node = (1 === r.n.text ? '' : r.prev.node) + r.o0.val - }, - - '@text-leads': (r: Rule) => { - let v = 1 === r.n.text ? r : r.prev - r.node = v.node = - (1 === r.n.text ? '' : r.prev.node) + - (2 <= r.n.text || !trim ? r.o0.src : '') + - r.o1.src - }, - - '@text-end': (r: Rule) => { - let v = 1 === r.n.text ? r : r.prev - r.node = v.node = - (1 === r.n.text ? '' : r.prev.node) + (!trim ? r.o0.src : '') - }, - - '@text-space': (r: Rule) => { - if (strict) { - let v = 1 === r.n.text ? r : r.prev - r.node = v.node = - (1 === r.n.text ? '' : r.prev.node) + (!trim ? r.o0.src : '') - } - }, - - - // === Condition refs === - - '@not-record-empty': () => !record_empty, - - - // === FuncRef for dynamic rule names === - - '@record-close-next': () => record_empty ? 'record' : 'newline', - - '@text-space-push': () => strict ? '' : 'val', - } - - - // Usually [#TX, #ST, #NR, #VL] - let VAL = jsonic.tokenSet.VAL - - let { LN, CA, SP, ZZ } = jsonic.token - - // Parse embedded grammar definition using a separate standard Jsonic instance. - const grammarDef = Jsonic.make()(grammarText) - grammarDef.ref = refs - jsonic.grammar(grammarDef) - - - // Rules list, elem, val are modified in code rather than the grammar file, - // because in non-strict mode the default jsonic alternatives must be preserved - // to support embedded JSON values like [1,2] and {x:1}. - - jsonic.rule('list', (rs: RuleSpec) => { - return rs - .open([ - // If not ignoring empty fields, don't consume LN used to close empty record. - { s: [LN], b: 1 }, - ]) - // Unconditional fallback to push elem — the default jsonic list rule gates - // its elem push on prev.u.implist which CSV's record rule does not set. - .open([{ p: 'elem' }], { append: true }) - .close([ - // LN ends record - { s: [LN], b: 1 }, - - { s: [ZZ] }, - ]) - }) - - jsonic.rule('elem', (rs: RuleSpec) => { - return rs - .open( - [ - // An empty element - { - s: [CA], - b: 1, - a: (r: Rule) => { - r.node.push(options.field.empty) - r.u.done = true - }, - }, - ], - ) - - .close( - [ - // An empty element at the end of the line - { - s: [CA, [LN, ZZ]], - b: 1, - a: (r: Rule) => r.node.push(options.field.empty), - }, - - // LN ends record - { s: [LN], b: 1 }, - ], - ) - }) - - jsonic.rule('val', (rs: RuleSpec) => { - return rs.open( - [ - // Handle text and space concatentation - { s: [VAL, SP], b: 2, p: 'text' }, - { s: [SP], b: 1, p: 'text' }, - - // LN ends record - { s: [LN], b: 1 }, - ], - ) - }) - - // Close is called on final rule - set parent val node - jsonic.rule('text', (rs: RuleSpec) => { - rs.bc((r: Rule) => { - r.parent.node = undefined === r.child.node ? r.node : r.child.node - }) - }) -} - -// Custom CSV String matcher. -// Handles "a""b" -> "a"b" quoting wierdness. -// This is a reduced copy of the standard Jsonic string matcher. -function buildCsvStringMatcher(csvopts: CsvOptions) { - return function makeCsvStringMatcher(cfg: Config, _opts: Options) { - return function csvStringMatcher(lex: Lex) { - let quoteMap: any = { [csvopts.string.quote]: true } - - let { pnt, src } = lex - let { sI, rI, cI } = pnt - let srclen = src.length - - if (quoteMap[src[sI]]) { - const q = src[sI] // Quote character - const qI = sI - const qrI = rI - ++sI - ++cI - - let s: string[] = [] - - for (sI; sI < srclen; sI++) { - cI++ - let c = src[sI] - - // Quote char. - if (q === c) { - sI++ - cI++ - - if (q === src[sI]) { - s.push(q) - } else { - break // String finished. - } - } - - // Body part of string. - else { - let bI = sI - - let qc = q.charCodeAt(0) - let cc = src.charCodeAt(sI) - - while (sI < srclen && 32 <= cc && qc !== cc) { - cc = src.charCodeAt(++sI) - cI++ - } - cI-- - - if (cfg.line.chars[src[sI]]) { - if (cfg.line.rowChars[src[sI]]) { - pnt.rI = ++rI - } - - cI = 1 - s.push(src.substring(bI, sI + 1)) - } else if (cc < 32) { - pnt.sI = sI - pnt.cI = cI - return lex.bad('unprintable', sI, sI + 1) - } else { - s.push(src.substring(bI, sI)) - sI-- - } - } - } - - if (src[sI - 1] !== q || pnt.sI === sI - 1) { - pnt.rI = qrI - return lex.bad('unterminated_string', qI, sI) - } - - const tkn = lex.token( - '#ST', - s.join(''), - src.substring(pnt.sI, sI), - pnt, - ) - - pnt.sI = sI - pnt.rI = rI - pnt.cI = cI - return tkn - } - } - } -} - -// Default option values. -Csv.defaults = { - trim: null, - comment: null, - number: null, - value: null, - header: true, - object: true, - stream: null, - strict: true, - field: { - separation: null, - nonameprefix: 'field~', - empty: '', - names: undefined, - exact: false, - }, - record: { - separators: null, - empty: false, - }, - string: { - quote: '"', - csv: null, - }, -} as CsvOptions - -export { Csv, buildCsvStringMatcher } - -export type { CsvOptions } diff --git a/src/zon.ts b/src/zon.ts new file mode 100644 index 0000000..51890f0 --- /dev/null +++ b/src/zon.ts @@ -0,0 +1,381 @@ +/* Copyright (c) 2025 Richard Rodger, MIT License */ + +// Import Jsonic types used by plugins. +import { + Jsonic, + Rule, + Plugin, + Context, + Config, + Options, + Lex, +} from 'jsonic' + +// Plugin options. +type ZonOptions = { + // When true, parse Zig char literals ('x') as numeric code points. + // When false, parse them as single-character strings. + charAsNumber: boolean + // When set, wrap enum literals (.foo used as value) in `{ [enumTag]: 'foo' }` + // instead of producing the bare string 'foo'. + enumTag: null | string +} + +// --- BEGIN EMBEDDED zon-grammar.jsonic --- +const grammarText = ` +# ZON Grammar Definition +# Parses Zig Object Notation (ZON) - a data format based on Zig anonymous +# struct literals. +# +# Example: +# .{ +# .name = "example", +# .version = "0.0.1", +# .deps = .{ .foo = .{ .url = "https://..." } }, +# .paths = .{ "build.zig", "src" }, +# } +# +# The custom zon-dot lex matcher distinguishes struct (map) and tuple (list) +# openings at lex time by peeking ahead: +# .{ followed by .ident = -> emits #OB (struct / map) +# .{ otherwise -> emits #OS (tuple / list) +# Both close on } which lexes as #CB. The list rules below use #CB (not +# the default #CS) as the list terminator so that the single } character +# closes both struct and tuple literals. +# +# A bare .identifier emits #TX with val = identifier (the leading dot is +# stripped). This token is both a valid KEY (when followed by =) and a +# valid VAL (when used as an enum literal). + +{ + rule: val: open: [ + # Empty .{} -> empty list. + { s: '#OS #CB' b: 2 p: list g: 'zon,list,empty' } + ] + + rule: list: open: [ + { s: '#OS #CB' b: 1 g: 'zon,list,empty' } + { s: '#OS' p: elem g: 'zon,list,open' } + ] + rule: list: close: [ + { s: '#CB' g: 'zon,list,close' } + ] + + rule: elem: close: [ + { s: '#CA #CB' b: 1 g: 'zon,elem,trailing' } + { s: '#CA' r: elem g: 'zon,elem,next' } + { s: '#CB' b: 1 g: 'zon,elem,end' } + ] + + rule: pair: close: [ + { s: '#CA #CB' b: 1 g: 'zon,pair,trailing' } + { s: '#CA' r: pair g: 'zon,pair,next' } + { s: '#CB' b: 1 g: 'zon,pair,end' } + ] +} +` +// --- END EMBEDDED zon-grammar.jsonic --- + +// Plugin implementation. +const Zon: Plugin = (jsonic: Jsonic, options: ZonOptions) => { + const charAsNumber = !!options.charAsNumber + const enumTag = options.enumTag || null + + // Configure jsonic for ZON syntax. + jsonic.options({ + rule: { + // Remove jsonic extensions (implicit maps/lists, top-level commas, + // path dives). ZON uses explicit struct literals only. + exclude: 'jsonic,imp', + start: 'val', + }, + fixed: { + token: { + // Bare `{`, `[`, `]` are not valid in ZON. Struct opening is `.{` + // which is handled by the custom zonDot lex matcher below. + '#OB': null, + '#OS': null, + '#CS': null, + // `=` replaces `:` as the key/value separator. + '#CL': '=', + }, + }, + tokenSet: { + // ZON field names are identifiers only. + KEY: ['#TX'], + }, + string: { + chars: '"', + multiChars: '', + // Zig-flavoured escape sequences. + escape: { + n: '\n', + r: '\r', + t: '\t', + '\\': '\\', + '"': '"', + '\'': '\'', + }, + allowUnknown: false, + }, + number: { + lex: true, + }, + // Only `//` line comments in ZON. + comment: { + lex: true, + def: { + hash: { lex: false }, + slash: { line: true, start: '//', lex: true, eatline: false }, + multi: { lex: false }, + }, + }, + value: { + lex: true, + def: { + true: { val: true }, + false: { val: false }, + null: { val: null }, + }, + }, + // The default jsonic text matcher is disabled; identifiers are only + // produced by the custom zonDot matcher below. + text: { + lex: false, + }, + lex: { + match: { + zonDot: { order: 1e5, make: buildZonDotMatcher() }, + zonMultiString: { order: 1.1e5, make: buildZonMultiStringMatcher() }, + zonChar: { order: 1.2e5, make: buildZonCharMatcher(charAsNumber) }, + }, + }, + }) + + // If enumTag is set, wrap enum-literal values (produced by zonDot) into + // `{ [enumTag]: name }` objects. The `/prepend` form runs before the + // default `@val-bc` handler sets r.node from the token. + const refs: Record = { + '@val-bc/prepend': (r: Rule, _ctx: Context) => { + if (!enumTag) return + if (undefined !== r.node) return + if (undefined !== r.child.node) return + if (0 === r.os) return + const tkn: any = r.o0 + if (tkn && tkn.use && tkn.use.zonEnum) { + r.node = { [enumTag]: tkn.val } + } + }, + } + + const grammarDef = Jsonic.make()(grammarText) + grammarDef.ref = refs + jsonic.grammar(grammarDef) +} + +// Custom lex matcher for `.`-prefixed tokens. +// `.{` -> #OB if followed by `.ident =`, otherwise #OS +// `.identifier` -> #TX (val = identifier, use.zonEnum = true) +// Runs ahead of the fixed-token matcher so it reliably owns the `.` prefix. +function buildZonDotMatcher() { + return function makeZonDotMatcher(_cfg: Config, _opts: Options) { + return function zonDotMatcher(lex: Lex) { + const { pnt } = lex + const src: string = lex.src as unknown as string + const { sI, cI } = pnt + if ('.' !== src[sI]) return undefined + + // `.{` opens a struct literal. Decide map vs list by peeking ahead. + if ('{' === src[sI + 1]) { + const isMap = peekIsMapOpen(src, sI + 2) + const tin = isMap ? '#OB' : '#OS' + const tkn = lex.token(tin, undefined, '.{', pnt) + pnt.sI = sI + 2 + pnt.cI = cI + 2 + return tkn + } + + // `.identifier` - field name or enum literal. + if (!isIdStart(src.charCodeAt(sI + 1))) return undefined + let eI = sI + 1 + while (eI < src.length && isIdCont(src.charCodeAt(eI))) eI++ + + const srcText = src.substring(sI, eI) + const name = src.substring(sI + 1, eI) + const tkn = lex.token('#TX', name, srcText, pnt, { zonEnum: true }) + pnt.sI = eI + pnt.cI = cI + (eI - sI) + return tkn + } + } +} + +// Returns true if the position inside `.{ ... }` starts with `.ident=`, +// indicating a struct/map literal rather than a tuple/list. +function peekIsMapOpen(src: string, start: number): boolean { + let i = skipInsig(src, start) + if ('.' !== src[i]) return false + i++ + if (!isIdStart(src.charCodeAt(i))) return false + i++ + while (i < src.length && isIdCont(src.charCodeAt(i))) i++ + i = skipInsig(src, i) + return '=' === src[i] +} + +// Skip whitespace, newlines, and `//` line comments. +function skipInsig(src: string, i: number): number { + while (i < src.length) { + const c = src[i] + if (' ' === c || '\t' === c || '\r' === c || '\n' === c) { + i++ + } else if ('/' === c && '/' === src[i + 1]) { + i += 2 + while (i < src.length && '\n' !== src[i] && '\r' !== src[i]) i++ + } else { + break + } + } + return i +} + +function isIdStart(c: number): boolean { + return ( + (65 <= c && c <= 90) || // A-Z + (97 <= c && c <= 122) || // a-z + 95 === c // _ + ) +} + +function isIdCont(c: number): boolean { + return ( + (48 <= c && c <= 57) || // 0-9 + (65 <= c && c <= 90) || + (97 <= c && c <= 122) || + 95 === c + ) +} + +// Multi-line Zig strings: consecutive lines starting with `\\`. +// Each `\\` line contributes its content verbatim (after the `\\`); lines +// are joined with `\n`. +function buildZonMultiStringMatcher() { + return function makeZonMultiStringMatcher(cfg: Config, _opts: Options) { + return function zonMultiStringMatcher(lex: Lex) { + const { pnt } = lex; const src: string = lex.src as unknown as string + if ('\\' !== src[pnt.sI] || '\\' !== src[pnt.sI + 1]) return undefined + + const startI = pnt.sI + const startCI = pnt.cI + let sI = pnt.sI + let rI = pnt.rI + const parts: string[] = [] + + while ('\\' === src[sI] && '\\' === src[sI + 1]) { + sI += 2 + const lineStart = sI + while (sI < src.length && !cfg.line.chars[src[sI]]) sI++ + parts.push(src.substring(lineStart, sI)) + + // Consume line terminator (handle \r\n as one). + if (sI < src.length && cfg.line.chars[src[sI]]) { + const ch = src[sI] + if (cfg.line.rowChars[ch]) rI++ + sI++ + if (sI < src.length && '\r' === ch && '\n' === src[sI]) sI++ + } + + // Look for another `\\` continuation after inter-line whitespace. + let peek = sI + while (peek < src.length && (src[peek] === ' ' || src[peek] === '\t')) { + peek++ + } + if ('\\' !== src[peek] || '\\' !== src[peek + 1]) break + sI = peek + } + + const val = parts.join('\n') + const tsrc = src.substring(startI, sI) + const tkn = lex.token('#ST', val, tsrc, pnt) + pnt.sI = sI + pnt.rI = rI + pnt.cI = startCI + (sI - startI) + return tkn + } + } +} + +// Zig character literal: `'x'`, `'\n'`, `'\x41'`, `'\u{1F600}'`. +// Produces a numeric code point (if charAsNumber) or a one-char string. +function buildZonCharMatcher(charAsNumber: boolean) { + return function makeZonCharMatcher(_cfg: Config, _opts: Options) { + return function zonCharMatcher(lex: Lex) { + const { pnt } = lex; const src: string = lex.src as unknown as string + const { sI, cI } = pnt + if ('\'' !== src[sI]) return undefined + + let i = sI + 1 + let codepoint: number | null = null + + if ('\\' === src[i]) { + i++ + const esc = src[i] + switch (esc) { + case 'n': codepoint = 10; i++; break + case 'r': codepoint = 13; i++; break + case 't': codepoint = 9; i++; break + case '\\': codepoint = 92; i++; break + case '\'': codepoint = 39; i++; break + case '"': codepoint = 34; i++; break + case '0': codepoint = 0; i++; break + case 'x': { + i++ + const hex = src.substring(i, i + 2) + if (!/^[0-9a-fA-F]{2}$/.test(hex)) return undefined + codepoint = parseInt(hex, 16) + i += 2 + break + } + case 'u': { + i++ + if ('{' !== src[i]) return undefined + i++ + const endI = src.indexOf('}', i) + if (-1 === endI) return undefined + const hex = src.substring(i, endI) + if (!/^[0-9a-fA-F]+$/.test(hex)) return undefined + codepoint = parseInt(hex, 16) + i = endI + 1 + break + } + default: + return undefined + } + } else if (src[i] && '\'' !== src[i]) { + codepoint = src.codePointAt(i) as number + i += codepoint > 0xffff ? 2 : 1 + } else { + return undefined + } + + if ('\'' !== src[i]) return undefined + i++ + + const tsrc = src.substring(sI, i) + const val = charAsNumber ? codepoint : String.fromCodePoint(codepoint!) + const tkn = lex.token('#NR', val, tsrc, pnt) + pnt.sI = i + pnt.cI = cI + (i - sI) + return tkn + } + } +} + +// Default option values. +Zon.defaults = { + charAsNumber: false, + enumTag: null, +} as ZonOptions + +export { Zon } +export type { ZonOptions } diff --git a/test/csv.test.ts b/test/csv.test.ts deleted file mode 100644 index 1e19bb2..0000000 --- a/test/csv.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -/* Copyright (c) 2021-2024 Richard Rodger and other contributors, MIT License */ - -import { describe, test } from 'node:test' -import assert from 'node:assert' -import { readFileSync } from 'node:fs' -import { join } from 'node:path' - -import Util from 'util' - -import { Jsonic } from 'jsonic' -import { Csv } from '../dist/csv' - -const Spectrum = require('csv-spectrum') - -const fixturesDir = join(__dirname, '..', 'test', 'fixtures') -const manifest = JSON.parse( - readFileSync(join(fixturesDir, 'manifest.json'), 'utf8'), -) - -describe('csv', () => { - test('empty-records', async () => { - // ignored by default - - const jo = Jsonic.make().use(Csv) - assert.deepEqual(jo('\n'), []) - assert.deepEqual(jo('a\n1\n\n2\n3\n\n\n4\n'), [ - { a: '1' }, - { a: '2' }, - { a: '3' }, - { a: '4' }, - ]) - - const ja = Jsonic.make().use(Csv, { object: false }) - assert.deepEqual(ja('\n'), []) - assert.deepEqual(ja('a\n1\n\n2\n3\n\n\n4\n'), [['1'], ['2'], ['3'], ['4']]) - - // start and end also ignored - - assert.deepEqual(jo('\r\na,b\r\nA,B\r\n'), [{ a: 'A', b: 'B' }]) - assert.deepEqual(jo('\r\n\r\na,b\r\nA,B\r\n\r\n'), [{ a: 'A', b: 'B' }]) - assert.deepEqual(ja('\r\na,b\r\nA,B\r\n'), [['A', 'B']]) - assert.deepEqual(ja('\r\n\r\na,b\r\nA,B\r\n\r\n'), [['A', 'B']]) - - // with option, empty creates record - - const jon = Jsonic.make().use(Csv, { record: { empty: true } }) - assert.deepEqual(jon('\n'), []) - assert.deepEqual(jon('a\n1\n\n2\n3\n\n\n4\n'), [ - { a: '1' }, - { a: '' }, - { a: '2' }, - { a: '3' }, - { a: '' }, - { a: '' }, - { a: '4' }, - ]) - - // with comments - - const joc = Jsonic.make().use(Csv, { comment: true }) - // console.log(joc('a#X\n1\n#Y\n2\n3\n\n#Z\n4\n#Q')) - assert.deepEqual(joc('a#X\n1\n#Y\n2\n3\n\n#Z\n4\n#Q'), [ - { a: '1' }, - { a: '2' }, - { a: '3' }, - { a: '4' }, - ]) - - const jocn = Jsonic.make().use(Csv, { - comment: true, - record: { empty: true }, - }) - assert.deepEqual(jocn('a#X\n1\n#Y\n2\n3\n\n#Z\n4\n#Q'), [ - { a: '1' }, - { a: '' }, - { a: '2' }, - { a: '3' }, - { a: '' }, - { a: '' }, - { a: '4' }, - ]) - }) - - test('header', async () => { - const jo = Jsonic.make().use(Csv) - assert.deepEqual(jo('\n'), []) - assert.deepEqual(jo('\na,b\nA,B'), [{ a: 'A', b: 'B' }]) - - const ja = Jsonic.make().use(Csv, { object: false }) - assert.deepEqual(ja('\n'), []) - assert.deepEqual(ja('\na,b\nA,B'), [['A', 'B']]) - - const jon = Jsonic.make().use(Csv, { header: false }) - assert.deepEqual(jon('\n'), []) - assert.deepEqual(jon('\na,b\nA,B'), [ - { - 'field~0': 'a', - 'field~1': 'b', - }, - { - 'field~0': 'A', - 'field~1': 'B', - }, - ]) - - const jan = Jsonic.make().use(Csv, { header: false, object: false }) - assert.deepEqual(jan('\n'), []) - assert.deepEqual(jan('\na,b\nA,B'), [ - ['a', 'b'], - ['A', 'B'], - ]) - - const jonf = Jsonic.make().use(Csv, { - header: false, - field: { names: ['a', 'b'] }, - }) - assert.deepEqual(jonf('\n'), []) - assert.deepEqual(jonf('\na,b\nA,B'), [ - { - a: 'a', - b: 'b', - }, - { - a: 'A', - b: 'B', - }, - ]) - }) - - test('comma', async () => { - const jo = Jsonic.make().use(Csv) - - assert.deepEqual(jo('\na'), []) - assert.deepEqual(jo('a\n1,'), [{ a: '1', 'field~1': '' }]) - assert.deepEqual(jo('a\n,1'), [{ a: '', 'field~1': '1' }]) - assert.deepEqual(jo('a,b\n1,2,'), [{ a: '1', b: '2', 'field~2': '' }]) - assert.deepEqual(jo('a,b\n,1,2'), [{ a: '', b: '1', 'field~2': '2' }]) - - assert.deepEqual(jo('a\n1,\n'), [{ a: '1', 'field~1': '' }]) - assert.deepEqual(jo('a\n,1\n'), [{ a: '', 'field~1': '1' }]) - assert.deepEqual(jo('a,b\n1,2,\n'), [{ a: '1', b: '2', 'field~2': '' }]) - assert.deepEqual(jo('a,b\n,1,2\n'), [{ a: '', b: '1', 'field~2': '2' }]) - assert.deepEqual(jo('\na\n'), []) - - const ja = Jsonic.make().use(Csv, { object: false }) - - assert.deepEqual(ja('a\n1,'), [['1', '']]) - assert.deepEqual(ja('a\n,1'), [['', '1']]) - assert.deepEqual(ja('a,b\n1,2,'), [['1', '2', '']]) - assert.deepEqual(ja('a,b\n,1,2'), [['', '1', '2']]) - assert.deepEqual(ja('\n1'), []) - }) - - test('separators', async () => { - const jd = Jsonic.make().use(Csv, { - field: { - separation: '|', - }, - }) - - assert.deepEqual(jd('a|b|c\nA|B|C\nAA|BB|CC'), [ - { a: 'A', b: 'B', c: 'C' }, - { a: 'AA', b: 'BB', c: 'CC' }, - ]) - - const jD = Jsonic.make().use(Csv, { - field: { - separation: '~~', - }, - }) - - assert.deepEqual(jD('a~~b~~c\nA~~B~~C\nAA~~BB~~CC'), [ - { a: 'A', b: 'B', c: 'C' }, - { a: 'AA', b: 'BB', c: 'CC' }, - ]) - - const jn = Jsonic.make().use(Csv, { - record: { - separators: '%', - }, - }) - - assert.deepEqual(jn('a,b,c%A,B,C%AA,BB,CC'), [ - { a: 'A', b: 'B', c: 'C' }, - { a: 'AA', b: 'BB', c: 'CC' }, - ]) - }) - - test('double-quote', async () => { - const j = Jsonic.make().use(Csv) - - assert.deepEqual(j('a\n"b"'), [{ a: 'b' }]) - - assert.deepEqual(j('a\n"""b"'), [{ a: '"b' }]) - assert.deepEqual(j('a\n"b"""'), [{ a: 'b"' }]) - assert.deepEqual(j('a\n"""b"""'), [{ a: '"b"' }]) - assert.deepEqual(j('a\n"b""c"'), [{ a: 'b"c' }]) - - assert.deepEqual(j('a\n"b""c""d"'), [{ a: 'b"c"d' }]) - assert.deepEqual(j('a\n"b""c""d""e"'), [{ a: 'b"c"d"e' }]) - - assert.deepEqual(j('a\n"""b"'), [{ a: '"b' }]) - assert.deepEqual(j('a\n"b"""'), [{ a: 'b"' }]) - assert.deepEqual(j('a\n"""b"""'), [{ a: '"b"' }]) - - assert.deepEqual(j('a\n"""""b"'), [{ a: '""b' }]) - assert.deepEqual(j('a\n"b"""""'), [{ a: 'b""' }]) - assert.deepEqual(j('a\n"""""b"""""'), [{ a: '""b""' }]) - }) - - test('trim', async () => { - const j = Jsonic.make().use(Csv) - - assert.deepEqual(j('a\n b'), [{ a: ' b' }]) - assert.deepEqual(j('a\nb '), [{ a: 'b ' }]) - assert.deepEqual(j('a\n b '), [{ a: ' b ' }]) - assert.deepEqual(j('a\n b '), [{ a: ' b ' }]) - assert.deepEqual(j('a\n \tb \t '), [{ a: ' \tb \t ' }]) - - assert.deepEqual(j('a\n b c'), [{ a: ' b c' }]) - assert.deepEqual(j('a\nb c '), [{ a: 'b c ' }]) - assert.deepEqual(j('a\n b c '), [{ a: ' b c ' }]) - assert.deepEqual(j('a\n b c '), [{ a: ' b c ' }]) - assert.deepEqual(j('a\n \tb c \t '), [{ a: ' \tb c \t ' }]) - - const jt = Jsonic.make().use(Csv, { trim: true }) - - assert.deepEqual(jt('a\n b'), [{ a: 'b' }]) - assert.deepEqual(jt('a\nb '), [{ a: 'b' }]) - assert.deepEqual(jt('a\n b '), [{ a: 'b' }]) - assert.deepEqual(jt('a\n b '), [{ a: 'b' }]) - assert.deepEqual(jt('a\n \tb \t '), [{ a: 'b' }]) - - assert.deepEqual(jt('a\n b c'), [{ a: 'b c' }]) - assert.deepEqual(jt('a\nb c '), [{ a: 'b c' }]) - assert.deepEqual(jt('a\n b c '), [{ a: 'b c' }]) - assert.deepEqual(jt('a\n b c '), [{ a: 'b c' }]) - assert.deepEqual(jt('a\n \tb c \t '), [{ a: 'b c' }]) - }) - - test('comment', async () => { - const j = Jsonic.make().use(Csv) - assert.deepEqual(j('a\n# b'), [{ a: '# b' }]) - assert.deepEqual(j('a\n b #c'), [{ a: ' b #c' }]) - - const jc = Jsonic.make().use(Csv, { comment: true }) - assert.deepEqual(jc('a\n# b'), []) - assert.deepEqual(jc('a\n b #c'), [{ a: ' b ' }]) - - const jt = Jsonic.make().use(Csv, { strict: false }) - assert.deepEqual(jt('a\n# b'), []) - assert.deepEqual(jt('a\n b '), [{ a: 'b' }]) - }) - - test('number', async () => { - const j = Jsonic.make().use(Csv) - assert.deepEqual(j('a\n1'), [{ a: '1' }]) - assert.deepEqual(j('a\n1e2'), [{ a: '1e2' }]) - - const jn = Jsonic.make().use(Csv, { number: true }) - assert.deepEqual(jn('a\n1'), [{ a: 1 }]) - assert.deepEqual(jn('a\n1e2'), [{ a: 100 }]) - - const jt = Jsonic.make().use(Csv, { strict: false }) - assert.deepEqual(jt('a\n1'), [{ a: 1 }]) - assert.deepEqual(jt('a\n1e2'), [{ a: 100 }]) - }) - - test('value', async () => { - const j = Jsonic.make().use(Csv) - assert.deepEqual(j('a\ntrue'), [{ a: 'true' }]) - assert.deepEqual(j('a\nfalse'), [{ a: 'false' }]) - assert.deepEqual(j('a\nnull'), [{ a: 'null' }]) - - const jv = Jsonic.make().use(Csv, { value: true }) - assert.deepEqual(jv('a\ntrue'), [{ a: true }]) - assert.deepEqual(jv('a\nfalse'), [{ a: false }]) - assert.deepEqual(jv('a\nnull'), [{ a: null }]) - }) - - test('stream', () => { - return new Promise((resolve) => { - let tmp: any = {} - let data: any[] - const j = Jsonic.make().use(Csv, { - stream: (what: string, record?: any[]) => { - if ('start' === what) { - data = [] - tmp.start = Date.now() - } else if ('record' === what) { - data.push(record) - } else if ('end' === what) { - tmp.end = Date.now() - - assert.deepEqual(data, [ - { a: '1', b: '2' }, - { a: '3', b: '4' }, - { a: '5', b: '6' }, - ]) - - assert.ok(tmp.start <= tmp.end) - - resolve() - } - }, - }) - - j('a,b\n1,2\n3,4\n5,6') - }) - }) - - test('unstrict', async () => { - const j = Jsonic.make().use(Csv, { strict: false }) - let d0 = j(`a,b,c -true,[1,2],{x:{y:"q\\"w"}} - x , 'y\\'y', "z\\"z" -`) - assert.deepEqual(d0, [ - { - a: true, - b: [1, 2], - c: { - x: { - y: 'q"w', - }, - }, - }, - { - a: 'x', - b: "y'y", - c: 'z"z', - }, - ]) - - assert.throws(() => j('a\n{x:1}y'), /unexpected/) - }) - - test('spectrum', async () => { - const j = Jsonic.make().use(Csv) - const tests = await Util.promisify(Spectrum)() - for (let i = 0; i < tests.length; i++) { - let test = tests[i] - let name = test.name - let json = JSON.parse(test.json.toString()) - let csv = test.csv.toString() - let res = j(csv) - let testname = name + ' ' + (i + 1) + '/' + tests.length - - // Broken test, reenable when fixed - if (5 === i) { - continue - } - - assert.deepEqual({ [testname]: res }, { [testname]: json }) - } - }) - - test('fixtures', async () => { - const csv = Jsonic.make().use(Csv) - for (const [key, entry] of Object.entries(manifest) as [string, any][]) { - const name: string = entry.name - - let parser = csv - if (entry.opt) { - let j = entry.jsonicOpt ? Jsonic.make(entry.jsonicOpt) : Jsonic.make() - parser = j.use(Csv, entry.opt) - } - const csvFile = entry.csvFile || key - const raw = readFileSync(join(fixturesDir, csvFile + '.csv'), 'utf8') - - if (entry.err) { - try { - parser(raw) - assert.fail('Expected error ' + entry.err + ' for fixture: ' + name) - } catch (e: any) { - assert.deepEqual(entry.err, e.code) - } - } else { - try { - const expected = JSON.parse( - readFileSync(join(fixturesDir, key + '.json'), 'utf8'), - ) - const out = parser(raw) - assert.deepEqual(out, expected) - } catch (e: any) { - console.error('FIXTURE: ' + name) - throw e - } - } - } - }) -}) diff --git a/test/fixtures/basic-array.json b/test/fixtures/basic-array.json deleted file mode 100644 index e8a1b12..0000000 --- a/test/fixtures/basic-array.json +++ /dev/null @@ -1 +0,0 @@ -[["1","2"],["3","4"]] diff --git a/test/fixtures/basic-noheader-names.json b/test/fixtures/basic-noheader-names.json deleted file mode 100644 index 7df9a6f..0000000 --- a/test/fixtures/basic-noheader-names.json +++ /dev/null @@ -1 +0,0 @@ -[{"x":"a","y":"b"},{"x":"1","y":"2"},{"x":"3","y":"4"}] diff --git a/test/fixtures/basic-noheader.json b/test/fixtures/basic-noheader.json deleted file mode 100644 index 86c86e2..0000000 --- a/test/fixtures/basic-noheader.json +++ /dev/null @@ -1 +0,0 @@ -[{"field~0":"a","field~1":"b"},{"field~0":"1","field~1":"2"},{"field~0":"3","field~1":"4"}] diff --git a/test/fixtures/basic.csv b/test/fixtures/basic.csv deleted file mode 100644 index 0099ae9..0000000 --- a/test/fixtures/basic.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b -1,2 -3,4 diff --git a/test/fixtures/basic.json b/test/fixtures/basic.json deleted file mode 100644 index 8db32a8..0000000 --- a/test/fixtures/basic.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1","b":"2"},{"a":"3","b":"4"}] diff --git a/test/fixtures/comment-empty.csv b/test/fixtures/comment-empty.csv deleted file mode 100644 index 28a3f77..0000000 --- a/test/fixtures/comment-empty.csv +++ /dev/null @@ -1,8 +0,0 @@ -a -1 -#comment -2 -3 - -#another comment -4 diff --git a/test/fixtures/comment-empty.json b/test/fixtures/comment-empty.json deleted file mode 100644 index ebc5b1c..0000000 --- a/test/fixtures/comment-empty.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1"},{"a":""},{"a":"2"},{"a":"3"},{"a":""},{"a":""},{"a":"4"}] diff --git a/test/fixtures/comment-inline.csv b/test/fixtures/comment-inline.csv deleted file mode 100644 index f927089..0000000 --- a/test/fixtures/comment-inline.csv +++ /dev/null @@ -1,3 +0,0 @@ -a#X -1 - b #c diff --git a/test/fixtures/comment-inline.json b/test/fixtures/comment-inline.json deleted file mode 100644 index 8a46826..0000000 --- a/test/fixtures/comment-inline.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1"},{"a":" b "}] diff --git a/test/fixtures/comment-line.csv b/test/fixtures/comment-line.csv deleted file mode 100644 index 82875ca..0000000 --- a/test/fixtures/comment-line.csv +++ /dev/null @@ -1,5 +0,0 @@ -a -1 -#this is a comment -2 -3 diff --git a/test/fixtures/comment-line.json b/test/fixtures/comment-line.json deleted file mode 100644 index 071af2a..0000000 --- a/test/fixtures/comment-line.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1"},{"a":"2"},{"a":"3"}] diff --git a/test/fixtures/crlf.csv b/test/fixtures/crlf.csv deleted file mode 100644 index 4ba71dc..0000000 --- a/test/fixtures/crlf.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b -A,B -C,D diff --git a/test/fixtures/crlf.json b/test/fixtures/crlf.json deleted file mode 100644 index c2872a6..0000000 --- a/test/fixtures/crlf.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"A","b":"B"},{"a":"C","b":"D"}] diff --git a/test/fixtures/empty-fields.csv b/test/fixtures/empty-fields.csv deleted file mode 100644 index 0970345..0000000 --- a/test/fixtures/empty-fields.csv +++ /dev/null @@ -1,5 +0,0 @@ -a,b -1, -,1 -1,2, -,1,2 diff --git a/test/fixtures/empty-fields.json b/test/fixtures/empty-fields.json deleted file mode 100644 index 1da5613..0000000 --- a/test/fixtures/empty-fields.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1","b":""},{"a":"","b":"1"},{"a":"1","b":"2","field~2":""},{"a":"","b":"1","field~2":"2"}] diff --git a/test/fixtures/empty-records-default.json b/test/fixtures/empty-records-default.json deleted file mode 100644 index 7561320..0000000 --- a/test/fixtures/empty-records-default.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1"},{"a":"2"},{"a":"3"},{"a":"4"}] diff --git a/test/fixtures/empty-records.csv b/test/fixtures/empty-records.csv deleted file mode 100644 index bbeb6f4..0000000 --- a/test/fixtures/empty-records.csv +++ /dev/null @@ -1,8 +0,0 @@ -a -1 - -2 -3 - - -4 diff --git a/test/fixtures/empty-records.json b/test/fixtures/empty-records.json deleted file mode 100644 index ebc5b1c..0000000 --- a/test/fixtures/empty-records.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1"},{"a":""},{"a":"2"},{"a":"3"},{"a":""},{"a":""},{"a":"4"}] diff --git a/test/fixtures/happy.csv b/test/fixtures/happy.csv deleted file mode 100644 index 89a52c0..0000000 --- a/test/fixtures/happy.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -1,B,true -2,BB,false diff --git a/test/fixtures/happy.json b/test/fixtures/happy.json deleted file mode 100644 index c0ec104..0000000 --- a/test/fixtures/happy.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "a": "1", - "b": "B", - "c": "true" - }, - { - "a": "2", - "b": "BB", - "c": "false" - } -] diff --git a/test/fixtures/leading-newline.csv b/test/fixtures/leading-newline.csv deleted file mode 100644 index d555e5e..0000000 --- a/test/fixtures/leading-newline.csv +++ /dev/null @@ -1,3 +0,0 @@ - -a,b -A,B diff --git a/test/fixtures/leading-newline.json b/test/fixtures/leading-newline.json deleted file mode 100644 index e265c5e..0000000 --- a/test/fixtures/leading-newline.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"A","b":"B"}] diff --git a/test/fixtures/manifest.json b/test/fixtures/manifest.json deleted file mode 100644 index b7f0b22..0000000 --- a/test/fixtures/manifest.json +++ /dev/null @@ -1,872 +0,0 @@ -{ - "happy": { - "name": "happy" - }, - "quote": { - "name": "quote" - }, - "notrim": { - "name": "notrim" - }, - "trim": { - "name": "trim", - "csvFile": "notrim", - "opt": { - "trim": true - } - }, - "papa-one-row": { - "name": "papa-One row", - "opt": { - "header": false, - "object": false - } - }, - "papa-two-rows": { - "name": "papa-Two rows", - "opt": { - "header": false, - "object": false - } - }, - "papa-three-rows": { - "name": "papa-Three rows", - "opt": { - "header": false, - "object": false - } - }, - "papa-whitespace-at-edges-of-unquoted-field": { - "name": "papa-Whitespace at edges of unquoted field", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field": { - "name": "papa-Quoted field", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-extra-whitespace-on-edges": { - "name": "papa-Quoted field with extra whitespace on edges", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-delimiter": { - "name": "papa-Quoted field with delimiter", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-line-break": { - "name": "papa-Quoted field with line break", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-fields-with-line-breaks": { - "name": "papa-Quoted fields with line breaks", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break": { - "name": "papa-Quoted fields at end of row with delimiter and line break", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-escaped-quotes": { - "name": "papa-Quoted field with escaped quotes", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-escaped-quotes-at-boundaries": { - "name": "papa-Quoted field with escaped quotes at boundaries", - "opt": { - "header": false, - "object": false - } - }, - "papa-unquoted-field-with-quotes-at-end-of-field": { - "name": "papa-Unquoted field with quotes at end of field", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-quotes-around-delimiter": { - "name": "papa-Quoted field with quotes around delimiter", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-quotes-on-right-side-of-delimiter": { - "name": "papa-Quoted field with quotes on right side of delimiter", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-quotes-on-left-side-of-delimiter": { - "name": "papa-Quoted field with quotes on left side of delimiter", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too": { - "name": "papa-Quoted field with 5 quotes in a row and a delimiter in there: too", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-whitespace-around-quotes": { - "name": "papa-Quoted field with whitespace around quotes", - "opt": { - "header": false, - "object": false - } - }, - "papa-misplaced-quotes-in-data-not-as-opening-quotes": { - "name": "papa-Misplaced quotes in data: not as opening quotes", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-has-no-closing-quote": { - "name": "papa-Quoted field has no closing quote", - "opt": { - "header": false, - "object": false - }, - "err": "unterminated_string" - }, - "papa-quoted-field-has-invalid-trailing-quote-after-delimiter-with-a-valid-closer": { - "name": "papa-Quoted field has invalid trailing quote after delimiter with a valid closer", - "opt": { - "header": false, - "object": false - }, - "err": "unexpected" - }, - "papa-quoted-field-has-invalid-trailing-quote-after-delimiter": { - "name": "papa-Quoted field has invalid trailing quote after delimiter", - "opt": { - "header": false, - "object": false - }, - "err": "unexpected" - }, - "papa-quoted-field-has-invalid-trailing-quote-before-delimiter": { - "name": "papa-Quoted field has invalid trailing quote before delimiter", - "opt": { - "header": false, - "object": false - }, - "err": "unexpected" - }, - "papa-quoted-field-has-invalid-trailing-quote-after-new-line": { - "name": "papa-Quoted field has invalid trailing quote after new line", - "opt": { - "header": false, - "object": false - }, - "err": "unexpected" - }, - "papa-quoted-field-has-valid-trailing-quote-via-delimiter": { - "name": "papa-Quoted field has valid trailing quote via delimiter", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-has-valid-trailing-quote-via-n": { - "name": "papa-Quoted field has valid trailing quote via \\n", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-has-valid-trailing-quote-via-eof": { - "name": "papa-Quoted field has valid trailing quote via EOF", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote": { - "name": "papa-Quoted field contains delimiters and \\n with valid trailing quote", - "opt": { - "header": false, - "object": false - } - }, - "papa-line-starts-with-quoted-field": { - "name": "papa-Line starts with quoted field", - "opt": { - "header": false, - "object": false - } - }, - "papa-line-starts-with-unquoted-empty-field": { - "name": "papa-Line starts with unquoted empty field", - "opt": { - "header": false, - "object": false - } - }, - "papa-line-ends-with-quoted-field": { - "name": "papa-Line ends with quoted field", - "opt": { - "header": false, - "object": false - } - }, - "papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n": { - "name": "papa-Line ends with quoted field: first field of next line is empty, \\n", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes": { - "name": "papa-Quoted field at end of row (but not at EOF) has quotes", - "opt": { - "header": false, - "object": false - } - }, - "papa-empty-quoted-field-at-eof-is-empty": { - "name": "papa-Empty quoted field at EOF is empty", - "opt": { - "header": false, - "object": false - } - }, - "papa-multiple-consecutive-empty-fields": { - "name": "papa-Multiple consecutive empty fields", - "opt": { - "header": false, - "object": false - } - }, - "papa-empty-input-string": { - "name": "papa-Empty input string", - "opt": { - "header": false, - "object": false - } - }, - "papa-input-is-just-the-delimiter-2-empty-fields": { - "name": "papa-Input is just the delimiter (2 empty fields)", - "opt": { - "header": false, - "object": false - } - }, - "papa-input-is-just-empty-fields": { - "name": "papa-Input is just empty fields", - "opt": { - "header": false, - "object": false - } - }, - "papa-input-is-just-a-string-a-single-field": { - "name": "papa-Input is just a string (a single field)", - "opt": { - "header": false, - "object": false - } - }, - "papa-commented-line-at-beginning": { - "name": "papa-Commented line at beginning", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-commented-line-in-middle": { - "name": "papa-Commented line in middle", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-commented-line-at-end": { - "name": "papa-Commented line at end", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-two-comment-lines-consecutively": { - "name": "papa-Two comment lines consecutively", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-two-comment-lines-consecutively-at-end-of-file": { - "name": "papa-Two comment lines consecutively at end of file", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-three-comment-lines-consecutively-at-beginning-of-file": { - "name": "papa-Three comment lines consecutively at beginning of file", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-entire-file-is-comment-lines": { - "name": "papa-Entire file is comment lines", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-comment-with-non-default-character": { - "name": "papa-Comment with non-default character", - "opt": { - "header": false, - "object": false, - "comment": true - }, - "jsonicOpt": { - "comment": { - "def": { - "hash": { - "start": "!" - } - } - } - } - }, - "papa-bad-comments-value-specified": { - "name": "papa-Bad comments value specified", - "opt": { - "header": false, - "object": false - } - }, - "papa-multi-character-comment-string": { - "name": "papa-Multi-character comment string", - "opt": { - "header": false, - "object": false, - "comment": true - }, - "jsonicOpt": { - "comment": { - "def": { - "hash": { - "start": "=N(" - } - } - } - } - }, - "papa-input-with-only-a-commented-line": { - "name": "papa-Input with only a commented line", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-jsonic-input-with-only-a-commented-line-and-blank-line-after": { - "name": "papa-jsonic-Input with only a commented line and blank line after", - "opt": { - "header": false, - "object": false, - "comment": true - } - }, - "papa-input-with-only-a-commented-line-without-comments-enabled": { - "name": "papa-Input with only a commented line: without comments enabled", - "opt": { - "header": false, - "object": false - } - }, - "papa-input-without-comments-with-line-starting-with-whitespace": { - "name": "papa-Input without comments with line starting with whitespace", - "opt": { - "header": false, - "object": false - } - }, - "papa-multiple-rows-one-column-no-delimiter-found": { - "name": "papa-Multiple rows: one column (no delimiter found)", - "opt": { - "header": false, - "object": false - } - }, - "papa-jsonic-one-column-input-with-empty-fields": { - "name": "papa-jsonic-One column input with empty fields", - "opt": { - "header": false, - "object": false, - "record": { - "empty": true - } - } - }, - "papa-two-rows-just-r": { - "name": "papa-Two rows: just \\r", - "opt": { - "header": false, - "object": false - } - }, - "papa-two-rows-r-n": { - "name": "papa-Two rows: \\r\\n", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-r-n": { - "name": "papa-Quoted field with \\r\\n", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-r": { - "name": "papa-Quoted field with \\r", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-field-with-n": { - "name": "papa-Quoted field with \\n", - "opt": { - "header": false, - "object": false - } - }, - "papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter": { - "name": "papa-Quoted fields with spaces between closing quote and next delimiter", - "opt": { - "header": false, - "object": false, - "trim": true - } - }, - "papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line": { - "name": "papa-Quoted fields with spaces between closing quote and next new line", - "opt": { - "header": false, - "object": false, - "trim": true - } - }, - "papa-quoted-fields-with-spaces-after-closing-quote": { - "name": "papa-Quoted fields with spaces after closing quote", - "opt": { - "header": false, - "object": false, - "trim": true - } - }, - "papa-misplaced-quotes-in-data-twice-not-as-opening-quotes": { - "name": "papa-Misplaced quotes in data twice: not as opening quotes", - "opt": { - "header": false, - "object": false - } - }, - "papa-header-row-with-one-row-of-data": { - "name": "papa-Header row with one row of data", - "opt": { - "header": true - } - }, - "papa-header-row-only": { - "name": "papa-Header row only" - }, - "papa-row-with-too-few-fields": { - "name": "papa-Row with too few fields", - "opt": { - "field": { - "exact": true - } - }, - "err": "csv_missing_field" - }, - "papa-row-with-too-many-fields": { - "name": "papa-Row with too many fields", - "opt": { - "field": { - "exact": true - } - }, - "err": "csv_extra_field" - }, - "papa-row-with-enough-fields-but-blank-field-in-the-begining": { - "name": "papa-Row with enough fields but blank field in the begining", - "opt": { - "header": false, - "object": false - } - }, - "papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers": { - "name": "papa-Row with enough fields but blank field in the begining using headers" - }, - "papa-row-with-enough-fields-but-blank-field-at-end": { - "name": "papa-Row with enough fields but blank field at end" - }, - "papa-tab-delimiter": { - "name": "papa-Tab delimiter", - "opt": { - "header": false, - "object": false, - "field": { - "separation": "\t" - } - } - }, - "papa-pipe-delimiter": { - "name": "papa-Pipe delimiter", - "opt": { - "header": false, - "object": false, - "field": { - "separation": "|" - } - } - }, - "papa-ascii-30-delimiter": { - "name": "papa-ASCII 30 delimiter", - "opt": { - "header": false, - "object": false, - "field": { - "separation": "\u001e" - } - } - }, - "papa-ascii-31-delimiter": { - "name": "papa-ASCII 31 delimiter", - "opt": { - "header": false, - "object": false, - "field": { - "separation": "\u001f" - } - } - }, - "papa-multi-character-delimiter": { - "name": "papa-Multi-character delimiter", - "opt": { - "header": false, - "object": false, - "field": { - "separation": ", " - } - } - }, - "papa-multi-character-delimiter-length-2-with-quoted-field": { - "name": "papa-Multi-character delimiter (length 2) with quoted field", - "opt": { - "header": false, - "object": false, - "field": { - "separation": ", " - } - } - }, - "papa-dynamic-typing-converts-boolean-literals": { - "name": "papa-Dynamic typing converts boolean literals", - "opt": { - "header": false, - "object": false, - "value": true - }, - "jsonicOpt": { - "value": { - "def": { - "TRUE": { - "val": true - }, - "FALSE": { - "val": false - } - } - } - } - }, - "papa-dynamic-typing-doesn-t-convert-other-types": { - "name": "papa-Dynamic typing doesn't convert other types", - "opt": { - "header": false, - "object": false, - "value": true - }, - "jsonicOpt": { - "value": { - "def": { - "null": null - } - } - } - }, - "papa-jsonic-blank-line-at-beginning": { - "name": "papa-jsonic-Blank line at beginning", - "opt": { - "header": false, - "object": false, - "record": { - "empty": true - } - } - }, - "papa-jsonic-blank-line-in-middle": { - "name": "papa-jsonic-Blank line in middle", - "opt": { - "header": false, - "object": false, - "record": { - "empty": true - } - } - }, - "papa-jsonic-blank-lines-at-end": { - "name": "papa-jsonic-Blank lines at end", - "opt": { - "header": false, - "object": false, - "record": { - "empty": true - } - } - }, - "papa-jsonic-blank-line-in-middle-with-whitespace": { - "name": "papa-jsonic-Blank line in middle with whitespace", - "opt": { - "header": false, - "object": false, - "record": { - "empty": true - } - } - }, - "papa-first-field-of-a-line-is-empty": { - "name": "papa-First field of a line is empty", - "opt": { - "header": false, - "object": false - } - }, - "papa-last-field-of-a-line-is-empty": { - "name": "papa-Last field of a line is empty", - "opt": { - "header": false, - "object": false - } - }, - "papa-other-fields-are-empty": { - "name": "papa-Other fields are empty", - "opt": { - "header": false, - "object": false - } - }, - "papa-empty-input-string-2": { - "name": "papa-Empty input string 2", - "opt": { - "header": false, - "object": false - } - }, - "papa-input-is-just-the-delimiter-2-empty-fields-2": { - "name": "papa-Input is just the delimiter (2 empty fields) 2", - "opt": { - "header": false, - "object": false - } - }, - "papa-input-is-just-a-string-a-single-field-2": { - "name": "papa-Input is just a string (a single field) 2", - "opt": { - "header": false, - "object": false - } - }, - "papa-empty-lines": { - "name": "papa-Empty lines", - "opt": { - "header": false, - "object": false, - "record": { - "empty": true - } - } - }, - "papa-skip-empty-lines": { - "name": "papa-Skip empty lines", - "opt": { - "header": false, - "object": false - } - }, - "papa-skip-empty-lines-with-newline-at-end-of-input": { - "name": "papa-Skip empty lines: with newline at end of input", - "opt": { - "header": false, - "object": false - } - }, - "papa-skip-empty-lines-with-empty-input": { - "name": "papa-Skip empty lines: with empty input", - "opt": { - "header": false, - "object": false - } - }, - "papa-skip-empty-lines-with-first-line-only-whitespace": { - "name": "papa-Skip empty lines: with first line only whitespace", - "opt": { - "header": false, - "object": false - } - }, - "papa-single-quote-as-quote-character": { - "name": "papa-Single quote as quote character", - "opt": { - "header": false, - "object": false, - "string": { - "quote": "'" - } - } - }, - "papa-custom-escape-character-in-the-middle": { - "name": "papa-Custom escape character in the middle", - "opt": { - "header": false, - "object": false, - "string": { - "csv": false - } - } - }, - "papa-custom-escape-character-at-the-end": { - "name": "papa-Custom escape character at the end", - "opt": { - "header": false, - "object": false, - "string": { - "csv": false - } - } - }, - "papa-header-row-with-preceding-comment": { - "name": "papa-Header row with preceding comment", - "opt": { - "comment": true - } - }, - "papa-carriage-return-in-header-inside-quotes-with-line-feed-endings": { - "name": "papa-Carriage return in header inside quotes: with line feed endings", - "opt": { - "header": false, - "object": false - } - }, - "papa-using-r-n-endings-uses-r-n-linebreak": { - "name": "papa-Using \\r\\n endings uses \\r\\n linebreak", - "opt": { - "header": false, - "object": false - } - }, - "papa-using-n-endings-uses-n-linebreak": { - "name": "papa-Using \\n endings uses \\n linebreak", - "opt": { - "header": false, - "object": false - } - }, - "papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak": { - "name": "papa-Using \\r\\n endings with \\r\\n in header field uses \\r\\n linebreak", - "opt": { - "header": false, - "object": false - } - }, - "papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak": { - "name": "papa-Using \\r\\n endings with \\n in header field uses \\r\\n linebreak", - "opt": { - "header": false, - "object": false - } - }, - "papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak": { - "name": "papa-Using \\r\\n endings with \\n in header field with skip empty lines uses \\r\\n linebreak", - "opt": { - "header": false, - "object": false - } - }, - "papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak": { - "name": "papa-Using \\n endings with \\r\\n in header field uses \\n linebreak", - "opt": { - "header": false, - "object": false - } - }, - "papa-using-reserved-regex-character-as-quote-character": { - "name": "papa-Using reserved regex character | as quote character", - "opt": { - "header": false, - "object": false, - "string": { - "quote": "|" - } - } - }, - "papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter": { - "name": "papa-Quoted fields with spaces between closing quote and next delimiter and contains delimiter", - "opt": { - "header": false, - "object": false, - "trim": true - } - }, - "papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline": { - "name": "papa-Quoted fields with spaces between closing quote and newline and contains newline", - "opt": { - "header": false, - "object": false, - "trim": true - } - } -} diff --git a/test/fixtures/multi-char-separator.csv b/test/fixtures/multi-char-separator.csv deleted file mode 100644 index a2f41fb..0000000 --- a/test/fixtures/multi-char-separator.csv +++ /dev/null @@ -1,3 +0,0 @@ -a~~b~~c -A~~B~~C -AA~~BB~~CC diff --git a/test/fixtures/multi-char-separator.json b/test/fixtures/multi-char-separator.json deleted file mode 100644 index f4668e4..0000000 --- a/test/fixtures/multi-char-separator.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"A","b":"B","c":"C"},{"a":"AA","b":"BB","c":"CC"}] diff --git a/test/fixtures/multirow.csv b/test/fixtures/multirow.csv deleted file mode 100644 index b532031..0000000 --- a/test/fixtures/multirow.csv +++ /dev/null @@ -1,4 +0,0 @@ -a,b,c -A,B,C -AA,BB,CC -AAA,BBB,CCC diff --git a/test/fixtures/multirow.json b/test/fixtures/multirow.json deleted file mode 100644 index 2128c0b..0000000 --- a/test/fixtures/multirow.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"A","b":"B","c":"C"},{"a":"AA","b":"BB","c":"CC"},{"a":"AAA","b":"BBB","c":"CCC"}] diff --git a/test/fixtures/notrim.csv b/test/fixtures/notrim.csv deleted file mode 100644 index 155dee7..0000000 --- a/test/fixtures/notrim.csv +++ /dev/null @@ -1,5 +0,0 @@ -a,b,c -1 , 2 , 3 - 11 , 22 , 33 -4 , 5 , 6 - 44 , 55 , 66 diff --git a/test/fixtures/notrim.json b/test/fixtures/notrim.json deleted file mode 100644 index 50f6466..0000000 --- a/test/fixtures/notrim.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "a": "1 ", - "b": " 2 ", - "c": " 3" - }, - { - "a": " 11 ", - "b": " 22 ", - "c": " 33 " - }, - { - "a": "4\t", - "b": "\t5\t", - "c": "\t6" - }, - { - "a": "\t44\t", - "b": "\t\t55\t\t\t", - "c": "\t66\t" - } -] diff --git a/test/fixtures/number.csv b/test/fixtures/number.csv deleted file mode 100644 index f4f3001..0000000 --- a/test/fixtures/number.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b -1,2.5 -1e2,abc diff --git a/test/fixtures/number.json b/test/fixtures/number.json deleted file mode 100644 index dcd2454..0000000 --- a/test/fixtures/number.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":1,"b":2.5},{"a":100,"b":"abc"}] diff --git a/test/fixtures/papa-ascii-30-delimiter.csv b/test/fixtures/papa-ascii-30-delimiter.csv deleted file mode 100644 index 0024b0a..0000000 --- a/test/fixtures/papa-ascii-30-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -abc -def \ No newline at end of file diff --git a/test/fixtures/papa-ascii-30-delimiter.json b/test/fixtures/papa-ascii-30-delimiter.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-ascii-30-delimiter.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-ascii-31-delimiter.csv b/test/fixtures/papa-ascii-31-delimiter.csv deleted file mode 100644 index ee8afcf..0000000 --- a/test/fixtures/papa-ascii-31-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -abc -def \ No newline at end of file diff --git a/test/fixtures/papa-ascii-31-delimiter.json b/test/fixtures/papa-ascii-31-delimiter.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-ascii-31-delimiter.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-bad-comments-value-specified.csv b/test/fixtures/papa-bad-comments-value-specified.csv deleted file mode 100644 index 164bb60..0000000 --- a/test/fixtures/papa-bad-comments-value-specified.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -5comment -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-bad-comments-value-specified.json b/test/fixtures/papa-bad-comments-value-specified.json deleted file mode 100644 index c8c02a3..0000000 --- a/test/fixtures/papa-bad-comments-value-specified.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "5comment" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.csv b/test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.csv deleted file mode 100644 index cbcc8a5..0000000 --- a/test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.csv +++ /dev/null @@ -1,6 +0,0 @@ -"a -a","b" -"c","d" -"e","f" -"g","h" -"i","j" \ No newline at end of file diff --git a/test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.json b/test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.json deleted file mode 100644 index 8e09d82..0000000 --- a/test/fixtures/papa-carriage-return-in-header-inside-quotes-with-line-feed-endings.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a\r\na", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-comment-with-non-default-character.csv b/test/fixtures/papa-comment-with-non-default-character.csv deleted file mode 100644 index 6bacc65..0000000 --- a/test/fixtures/papa-comment-with-non-default-character.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -!Comment goes here -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-comment-with-non-default-character.json b/test/fixtures/papa-comment-with-non-default-character.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-comment-with-non-default-character.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-commented-line-at-beginning.csv b/test/fixtures/papa-commented-line-at-beginning.csv deleted file mode 100644 index 9d5dd4e..0000000 --- a/test/fixtures/papa-commented-line-at-beginning.csv +++ /dev/null @@ -1,2 +0,0 @@ -# Comment! -a,b,c \ No newline at end of file diff --git a/test/fixtures/papa-commented-line-at-beginning.json b/test/fixtures/papa-commented-line-at-beginning.json deleted file mode 100644 index f47beff..0000000 --- a/test/fixtures/papa-commented-line-at-beginning.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "b", - "c" - ] -] diff --git a/test/fixtures/papa-commented-line-at-end.csv b/test/fixtures/papa-commented-line-at-end.csv deleted file mode 100644 index 42497fd..0000000 --- a/test/fixtures/papa-commented-line-at-end.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,true,false -# Comment \ No newline at end of file diff --git a/test/fixtures/papa-commented-line-at-end.json b/test/fixtures/papa-commented-line-at-end.json deleted file mode 100644 index 2cb707c..0000000 --- a/test/fixtures/papa-commented-line-at-end.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "true", - "false" - ] -] diff --git a/test/fixtures/papa-commented-line-in-middle.csv b/test/fixtures/papa-commented-line-in-middle.csv deleted file mode 100644 index 53df74c..0000000 --- a/test/fixtures/papa-commented-line-in-middle.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -# Comment -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-commented-line-in-middle.json b/test/fixtures/papa-commented-line-in-middle.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-commented-line-in-middle.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-custom-escape-character-at-the-end.csv b/test/fixtures/papa-custom-escape-character-at-the-end.csv deleted file mode 100644 index 69ea0dd..0000000 --- a/test/fixtures/papa-custom-escape-character-at-the-end.csv +++ /dev/null @@ -1 +0,0 @@ -a,b,"c\"d\"" \ No newline at end of file diff --git a/test/fixtures/papa-custom-escape-character-at-the-end.json b/test/fixtures/papa-custom-escape-character-at-the-end.json deleted file mode 100644 index e4033d0..0000000 --- a/test/fixtures/papa-custom-escape-character-at-the-end.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "b", - "c\"d\"" - ] -] diff --git a/test/fixtures/papa-custom-escape-character-in-the-middle.csv b/test/fixtures/papa-custom-escape-character-in-the-middle.csv deleted file mode 100644 index b37a6fd..0000000 --- a/test/fixtures/papa-custom-escape-character-in-the-middle.csv +++ /dev/null @@ -1 +0,0 @@ -a,b,"c\"d\"f" \ No newline at end of file diff --git a/test/fixtures/papa-custom-escape-character-in-the-middle.json b/test/fixtures/papa-custom-escape-character-in-the-middle.json deleted file mode 100644 index 85cd7d5..0000000 --- a/test/fixtures/papa-custom-escape-character-in-the-middle.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "b", - "c\"d\"f" - ] -] diff --git a/test/fixtures/papa-dynamic-typing-converts-boolean-literals.csv b/test/fixtures/papa-dynamic-typing-converts-boolean-literals.csv deleted file mode 100644 index d36c135..0000000 --- a/test/fixtures/papa-dynamic-typing-converts-boolean-literals.csv +++ /dev/null @@ -1 +0,0 @@ -true,false,T,F,TRUE,FALSE,True,False \ No newline at end of file diff --git a/test/fixtures/papa-dynamic-typing-converts-boolean-literals.json b/test/fixtures/papa-dynamic-typing-converts-boolean-literals.json deleted file mode 100644 index 0c34701..0000000 --- a/test/fixtures/papa-dynamic-typing-converts-boolean-literals.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - true, - false, - "T", - "F", - true, - false, - "True", - "False" - ] -] diff --git a/test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.csv b/test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.csv deleted file mode 100644 index 7e39851..0000000 --- a/test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.csv +++ /dev/null @@ -1,3 +0,0 @@ -A,B,C -undefined,null,[ -var,float,if \ No newline at end of file diff --git a/test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.json b/test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.json deleted file mode 100644 index 160d79c..0000000 --- a/test/fixtures/papa-dynamic-typing-doesn-t-convert-other-types.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - [ - "A", - "B", - "C" - ], - [ - "undefined", - "null", - "[" - ], - [ - "var", - "float", - "if" - ] -] diff --git a/test/fixtures/papa-empty-input-string-2.csv b/test/fixtures/papa-empty-input-string-2.csv deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/papa-empty-input-string-2.json b/test/fixtures/papa-empty-input-string-2.json deleted file mode 100644 index fe51488..0000000 --- a/test/fixtures/papa-empty-input-string-2.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/fixtures/papa-empty-input-string.csv b/test/fixtures/papa-empty-input-string.csv deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/papa-empty-input-string.json b/test/fixtures/papa-empty-input-string.json deleted file mode 100644 index fe51488..0000000 --- a/test/fixtures/papa-empty-input-string.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/fixtures/papa-empty-lines.csv b/test/fixtures/papa-empty-lines.csv deleted file mode 100644 index 4247d50..0000000 --- a/test/fixtures/papa-empty-lines.csv +++ /dev/null @@ -1,5 +0,0 @@ - -a,b,c - -d,e,f - diff --git a/test/fixtures/papa-empty-lines.json b/test/fixtures/papa-empty-lines.json deleted file mode 100644 index 963e246..0000000 --- a/test/fixtures/papa-empty-lines.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - [], - [ - "a", - "b", - "c" - ], - [], - [ - "d", - "e", - "f" - ], - [] -] diff --git a/test/fixtures/papa-empty-quoted-field-at-eof-is-empty.csv b/test/fixtures/papa-empty-quoted-field-at-eof-is-empty.csv deleted file mode 100644 index 2d02206..0000000 --- a/test/fixtures/papa-empty-quoted-field-at-eof-is-empty.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b,"" -a,b,"" \ No newline at end of file diff --git a/test/fixtures/papa-empty-quoted-field-at-eof-is-empty.json b/test/fixtures/papa-empty-quoted-field-at-eof-is-empty.json deleted file mode 100644 index 98b5299..0000000 --- a/test/fixtures/papa-empty-quoted-field-at-eof-is-empty.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "" - ], - [ - "a", - "b", - "" - ] -] diff --git a/test/fixtures/papa-entire-file-is-comment-lines.csv b/test/fixtures/papa-entire-file-is-comment-lines.csv deleted file mode 100644 index f77b825..0000000 --- a/test/fixtures/papa-entire-file-is-comment-lines.csv +++ /dev/null @@ -1,3 +0,0 @@ -#comment1 -#comment2 -#comment3 \ No newline at end of file diff --git a/test/fixtures/papa-entire-file-is-comment-lines.json b/test/fixtures/papa-entire-file-is-comment-lines.json deleted file mode 100644 index fe51488..0000000 --- a/test/fixtures/papa-entire-file-is-comment-lines.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/fixtures/papa-first-field-of-a-line-is-empty.csv b/test/fixtures/papa-first-field-of-a-line-is-empty.csv deleted file mode 100644 index df89dba..0000000 --- a/test/fixtures/papa-first-field-of-a-line-is-empty.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b,c -,e,f \ No newline at end of file diff --git a/test/fixtures/papa-first-field-of-a-line-is-empty.json b/test/fixtures/papa-first-field-of-a-line-is-empty.json deleted file mode 100644 index 7ab352a..0000000 --- a/test/fixtures/papa-first-field-of-a-line-is-empty.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-header-row-only.csv b/test/fixtures/papa-header-row-only.csv deleted file mode 100644 index 8ae723e..0000000 --- a/test/fixtures/papa-header-row-only.csv +++ /dev/null @@ -1 +0,0 @@ -A,B,C \ No newline at end of file diff --git a/test/fixtures/papa-header-row-only.json b/test/fixtures/papa-header-row-only.json deleted file mode 100644 index fe51488..0000000 --- a/test/fixtures/papa-header-row-only.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/fixtures/papa-header-row-with-one-row-of-data.csv b/test/fixtures/papa-header-row-with-one-row-of-data.csv deleted file mode 100644 index fea02ce..0000000 --- a/test/fixtures/papa-header-row-with-one-row-of-data.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,B,C -a,b,c \ No newline at end of file diff --git a/test/fixtures/papa-header-row-with-one-row-of-data.json b/test/fixtures/papa-header-row-with-one-row-of-data.json deleted file mode 100644 index 79cd368..0000000 --- a/test/fixtures/papa-header-row-with-one-row-of-data.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "A": "a", - "B": "b", - "C": "c" - } -] diff --git a/test/fixtures/papa-header-row-with-preceding-comment.csv b/test/fixtures/papa-header-row-with-preceding-comment.csv deleted file mode 100644 index 37801e2..0000000 --- a/test/fixtures/papa-header-row-with-preceding-comment.csv +++ /dev/null @@ -1,3 +0,0 @@ -#Comment -a,b -c,d diff --git a/test/fixtures/papa-header-row-with-preceding-comment.json b/test/fixtures/papa-header-row-with-preceding-comment.json deleted file mode 100644 index 8812637..0000000 --- a/test/fixtures/papa-header-row-with-preceding-comment.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "a": "c", - "b": "d" - } -] diff --git a/test/fixtures/papa-input-is-just-a-string-a-single-field-2.csv b/test/fixtures/papa-input-is-just-a-string-a-single-field-2.csv deleted file mode 100644 index 7203e92..0000000 --- a/test/fixtures/papa-input-is-just-a-string-a-single-field-2.csv +++ /dev/null @@ -1 +0,0 @@ -Abc def \ No newline at end of file diff --git a/test/fixtures/papa-input-is-just-a-string-a-single-field-2.json b/test/fixtures/papa-input-is-just-a-string-a-single-field-2.json deleted file mode 100644 index a5f44c8..0000000 --- a/test/fixtures/papa-input-is-just-a-string-a-single-field-2.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - [ - "Abc def" - ] -] diff --git a/test/fixtures/papa-input-is-just-a-string-a-single-field.csv b/test/fixtures/papa-input-is-just-a-string-a-single-field.csv deleted file mode 100644 index 7203e92..0000000 --- a/test/fixtures/papa-input-is-just-a-string-a-single-field.csv +++ /dev/null @@ -1 +0,0 @@ -Abc def \ No newline at end of file diff --git a/test/fixtures/papa-input-is-just-a-string-a-single-field.json b/test/fixtures/papa-input-is-just-a-string-a-single-field.json deleted file mode 100644 index a5f44c8..0000000 --- a/test/fixtures/papa-input-is-just-a-string-a-single-field.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - [ - "Abc def" - ] -] diff --git a/test/fixtures/papa-input-is-just-empty-fields.csv b/test/fixtures/papa-input-is-just-empty-fields.csv deleted file mode 100644 index f6f13f5..0000000 --- a/test/fixtures/papa-input-is-just-empty-fields.csv +++ /dev/null @@ -1,2 +0,0 @@ -,, -,,, \ No newline at end of file diff --git a/test/fixtures/papa-input-is-just-empty-fields.json b/test/fixtures/papa-input-is-just-empty-fields.json deleted file mode 100644 index 46e6f81..0000000 --- a/test/fixtures/papa-input-is-just-empty-fields.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - [ - "", - "", - "" - ], - [ - "", - "", - "", - "" - ] -] diff --git a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.csv b/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.csv deleted file mode 100644 index 41622b4..0000000 --- a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.csv +++ /dev/null @@ -1 +0,0 @@ -, \ No newline at end of file diff --git a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.json b/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.json deleted file mode 100644 index 3a9e22f..0000000 --- a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields-2.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - [ - "", - "" - ] -] diff --git a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.csv b/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.csv deleted file mode 100644 index 41622b4..0000000 --- a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.csv +++ /dev/null @@ -1 +0,0 @@ -, \ No newline at end of file diff --git a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.json b/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.json deleted file mode 100644 index 3a9e22f..0000000 --- a/test/fixtures/papa-input-is-just-the-delimiter-2-empty-fields.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - [ - "", - "" - ] -] diff --git a/test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.csv b/test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.csv deleted file mode 100644 index 65b570c..0000000 --- a/test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.csv +++ /dev/null @@ -1 +0,0 @@ -#commented line \ No newline at end of file diff --git a/test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.json b/test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.json deleted file mode 100644 index 66808d4..0000000 --- a/test/fixtures/papa-input-with-only-a-commented-line-without-comments-enabled.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - [ - "#commented line" - ] -] diff --git a/test/fixtures/papa-input-with-only-a-commented-line.csv b/test/fixtures/papa-input-with-only-a-commented-line.csv deleted file mode 100644 index 65b570c..0000000 --- a/test/fixtures/papa-input-with-only-a-commented-line.csv +++ /dev/null @@ -1 +0,0 @@ -#commented line \ No newline at end of file diff --git a/test/fixtures/papa-input-with-only-a-commented-line.json b/test/fixtures/papa-input-with-only-a-commented-line.json deleted file mode 100644 index fe51488..0000000 --- a/test/fixtures/papa-input-with-only-a-commented-line.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.csv b/test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.csv deleted file mode 100644 index 2395318..0000000 --- a/test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.csv +++ /dev/null @@ -1,3 +0,0 @@ -a - b -c \ No newline at end of file diff --git a/test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.json b/test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.json deleted file mode 100644 index d4c6aea..0000000 --- a/test/fixtures/papa-input-without-comments-with-line-starting-with-whitespace.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - [ - "a" - ], - [ - " b" - ], - [ - "c" - ] -] diff --git a/test/fixtures/papa-jsonic-blank-line-at-beginning.csv b/test/fixtures/papa-jsonic-blank-line-at-beginning.csv deleted file mode 100644 index 155c206..0000000 --- a/test/fixtures/papa-jsonic-blank-line-at-beginning.csv +++ /dev/null @@ -1,3 +0,0 @@ - -a,b,c -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-jsonic-blank-line-at-beginning.json b/test/fixtures/papa-jsonic-blank-line-at-beginning.json deleted file mode 100644 index 3c9bfa4..0000000 --- a/test/fixtures/papa-jsonic-blank-line-at-beginning.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - [], - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.csv b/test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.csv deleted file mode 100644 index 4b98566..0000000 --- a/test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c - -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.json b/test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.json deleted file mode 100644 index d7f6c55..0000000 --- a/test/fixtures/papa-jsonic-blank-line-in-middle-with-whitespace.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - " " - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-jsonic-blank-line-in-middle.csv b/test/fixtures/papa-jsonic-blank-line-in-middle.csv deleted file mode 100644 index c02e652..0000000 --- a/test/fixtures/papa-jsonic-blank-line-in-middle.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c - -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-jsonic-blank-line-in-middle.json b/test/fixtures/papa-jsonic-blank-line-in-middle.json deleted file mode 100644 index 281e7bc..0000000 --- a/test/fixtures/papa-jsonic-blank-line-in-middle.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-jsonic-blank-lines-at-end.csv b/test/fixtures/papa-jsonic-blank-lines-at-end.csv deleted file mode 100644 index e076fcb..0000000 --- a/test/fixtures/papa-jsonic-blank-lines-at-end.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -d,e,f - diff --git a/test/fixtures/papa-jsonic-blank-lines-at-end.json b/test/fixtures/papa-jsonic-blank-lines-at-end.json deleted file mode 100644 index dfbca40..0000000 --- a/test/fixtures/papa-jsonic-blank-lines-at-end.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ], - [] -] diff --git a/test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.csv b/test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.csv deleted file mode 100644 index 9355ec2..0000000 --- a/test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.csv +++ /dev/null @@ -1 +0,0 @@ -#commented line diff --git a/test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.json b/test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.json deleted file mode 100644 index fe51488..0000000 --- a/test/fixtures/papa-jsonic-input-with-only-a-commented-line-and-blank-line-after.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/fixtures/papa-jsonic-one-column-input-with-empty-fields.csv b/test/fixtures/papa-jsonic-one-column-input-with-empty-fields.csv deleted file mode 100644 index 30b315b..0000000 --- a/test/fixtures/papa-jsonic-one-column-input-with-empty-fields.csv +++ /dev/null @@ -1,7 +0,0 @@ -a -b - - -c -d -e diff --git a/test/fixtures/papa-jsonic-one-column-input-with-empty-fields.json b/test/fixtures/papa-jsonic-one-column-input-with-empty-fields.json deleted file mode 100644 index 42ccc45..0000000 --- a/test/fixtures/papa-jsonic-one-column-input-with-empty-fields.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - [ - "a" - ], - [ - "b" - ], - [], - [], - [ - "c" - ], - [ - "d" - ], - [ - "e" - ] -] diff --git a/test/fixtures/papa-last-field-of-a-line-is-empty.csv b/test/fixtures/papa-last-field-of-a-line-is-empty.csv deleted file mode 100644 index 81e726d..0000000 --- a/test/fixtures/papa-last-field-of-a-line-is-empty.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b, -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-last-field-of-a-line-is-empty.json b/test/fixtures/papa-last-field-of-a-line-is-empty.json deleted file mode 100644 index bf859ad..0000000 --- a/test/fixtures/papa-last-field-of-a-line-is-empty.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.csv b/test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.csv deleted file mode 100644 index 59e9ea6..0000000 --- a/test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.csv +++ /dev/null @@ -1,4 +0,0 @@ -a,b,c -,e,f -,"h","i" -,"k","l" \ No newline at end of file diff --git a/test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.json b/test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.json deleted file mode 100644 index 6174c3e..0000000 --- a/test/fixtures/papa-line-ends-with-quoted-field-first-field-of-next-line-is-empty-n.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "", - "e", - "f" - ], - [ - "", - "h", - "i" - ], - [ - "", - "k", - "l" - ] -] diff --git a/test/fixtures/papa-line-ends-with-quoted-field.csv b/test/fixtures/papa-line-ends-with-quoted-field.csv deleted file mode 100644 index abc5889..0000000 --- a/test/fixtures/papa-line-ends-with-quoted-field.csv +++ /dev/null @@ -1,4 +0,0 @@ -a,b,c -d,e,f -"g","h","i" -"j","k","l" \ No newline at end of file diff --git a/test/fixtures/papa-line-ends-with-quoted-field.json b/test/fixtures/papa-line-ends-with-quoted-field.json deleted file mode 100644 index 815c73e..0000000 --- a/test/fixtures/papa-line-ends-with-quoted-field.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ], - [ - "g", - "h", - "i" - ], - [ - "j", - "k", - "l" - ] -] diff --git a/test/fixtures/papa-line-starts-with-quoted-field.csv b/test/fixtures/papa-line-starts-with-quoted-field.csv deleted file mode 100644 index 35700a8..0000000 --- a/test/fixtures/papa-line-starts-with-quoted-field.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b,c -"d",e,f \ No newline at end of file diff --git a/test/fixtures/papa-line-starts-with-quoted-field.json b/test/fixtures/papa-line-starts-with-quoted-field.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-line-starts-with-quoted-field.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-line-starts-with-unquoted-empty-field.csv b/test/fixtures/papa-line-starts-with-unquoted-empty-field.csv deleted file mode 100644 index ac81806..0000000 --- a/test/fixtures/papa-line-starts-with-unquoted-empty-field.csv +++ /dev/null @@ -1,2 +0,0 @@ -,b,c -"d",e,f \ No newline at end of file diff --git a/test/fixtures/papa-line-starts-with-unquoted-empty-field.json b/test/fixtures/papa-line-starts-with-unquoted-empty-field.json deleted file mode 100644 index 7079a92..0000000 --- a/test/fixtures/papa-line-starts-with-unquoted-empty-field.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.csv b/test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.csv deleted file mode 100644 index 4f93485..0000000 --- a/test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.csv +++ /dev/null @@ -1 +0,0 @@ -A,B "B",C \ No newline at end of file diff --git a/test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.json b/test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.json deleted file mode 100644 index de57929..0000000 --- a/test/fixtures/papa-misplaced-quotes-in-data-not-as-opening-quotes.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B \"B\"", - "C" - ] -] diff --git a/test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.csv b/test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.csv deleted file mode 100644 index 2fc1844..0000000 --- a/test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,B",C -D,E",F \ No newline at end of file diff --git a/test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.json b/test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.json deleted file mode 100644 index eeff7d3..0000000 --- a/test/fixtures/papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "A", - "B\"", - "C" - ], - [ - "D", - "E\"", - "F" - ] -] diff --git a/test/fixtures/papa-multi-character-comment-string.csv b/test/fixtures/papa-multi-character-comment-string.csv deleted file mode 100644 index 149256a..0000000 --- a/test/fixtures/papa-multi-character-comment-string.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -=N(Comment) -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-multi-character-comment-string.json b/test/fixtures/papa-multi-character-comment-string.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-multi-character-comment-string.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.csv b/test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.csv deleted file mode 100644 index 037f991..0000000 --- a/test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.csv +++ /dev/null @@ -1 +0,0 @@ -a, b, "c, e", d \ No newline at end of file diff --git a/test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.json b/test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.json deleted file mode 100644 index 441bdb9..0000000 --- a/test/fixtures/papa-multi-character-delimiter-length-2-with-quoted-field.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - [ - "a", - "b", - "c, e", - "d" - ] -] diff --git a/test/fixtures/papa-multi-character-delimiter.csv b/test/fixtures/papa-multi-character-delimiter.csv deleted file mode 100644 index 5f2665b..0000000 --- a/test/fixtures/papa-multi-character-delimiter.csv +++ /dev/null @@ -1 +0,0 @@ -a, b, c \ No newline at end of file diff --git a/test/fixtures/papa-multi-character-delimiter.json b/test/fixtures/papa-multi-character-delimiter.json deleted file mode 100644 index f47beff..0000000 --- a/test/fixtures/papa-multi-character-delimiter.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "b", - "c" - ] -] diff --git a/test/fixtures/papa-multiple-consecutive-empty-fields.csv b/test/fixtures/papa-multiple-consecutive-empty-fields.csv deleted file mode 100644 index 6b5f5c6..0000000 --- a/test/fixtures/papa-multiple-consecutive-empty-fields.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b,,,c,d -,,e,,,f \ No newline at end of file diff --git a/test/fixtures/papa-multiple-consecutive-empty-fields.json b/test/fixtures/papa-multiple-consecutive-empty-fields.json deleted file mode 100644 index f46e882..0000000 --- a/test/fixtures/papa-multiple-consecutive-empty-fields.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - [ - "a", - "b", - "", - "", - "c", - "d" - ], - [ - "", - "", - "e", - "", - "", - "f" - ] -] diff --git a/test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.csv b/test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.csv deleted file mode 100644 index 0fec236..0000000 --- a/test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.csv +++ /dev/null @@ -1,5 +0,0 @@ -a -b -c -d -e \ No newline at end of file diff --git a/test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.json b/test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.json deleted file mode 100644 index 40c4651..0000000 --- a/test/fixtures/papa-multiple-rows-one-column-no-delimiter-found.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - [ - "a" - ], - [ - "b" - ], - [ - "c" - ], - [ - "d" - ], - [ - "e" - ] -] diff --git a/test/fixtures/papa-one-row.csv b/test/fixtures/papa-one-row.csv deleted file mode 100644 index 341e344..0000000 --- a/test/fixtures/papa-one-row.csv +++ /dev/null @@ -1 +0,0 @@ -A,b,c \ No newline at end of file diff --git a/test/fixtures/papa-one-row.json b/test/fixtures/papa-one-row.json deleted file mode 100644 index a462c67..0000000 --- a/test/fixtures/papa-one-row.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "b", - "c" - ] -] diff --git a/test/fixtures/papa-other-fields-are-empty.csv b/test/fixtures/papa-other-fields-are-empty.csv deleted file mode 100644 index 528105e..0000000 --- a/test/fixtures/papa-other-fields-are-empty.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,,c -,, \ No newline at end of file diff --git a/test/fixtures/papa-other-fields-are-empty.json b/test/fixtures/papa-other-fields-are-empty.json deleted file mode 100644 index 0490600..0000000 --- a/test/fixtures/papa-other-fields-are-empty.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "", - "c" - ], - [ - "", - "", - "" - ] -] diff --git a/test/fixtures/papa-pipe-delimiter.csv b/test/fixtures/papa-pipe-delimiter.csv deleted file mode 100644 index 224ccfe..0000000 --- a/test/fixtures/papa-pipe-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -a|b|c -d|e|f \ No newline at end of file diff --git a/test/fixtures/papa-pipe-delimiter.json b/test/fixtures/papa-pipe-delimiter.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-pipe-delimiter.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.csv b/test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.csv deleted file mode 100644 index c5e50ef..0000000 --- a/test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b,"c""c""" -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.json b/test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.json deleted file mode 100644 index 426ccb4..0000000 --- a/test/fixtures/papa-quoted-field-at-end-of-row-but-not-at-eof-has-quotes.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c\"c\"" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.csv b/test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.csv deleted file mode 100644 index 2029076..0000000 --- a/test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,"b,c -d,e,f" \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.json b/test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.json deleted file mode 100644 index e0176ec..0000000 --- a/test/fixtures/papa-quoted-field-contains-delimiters-and-n-with-valid-trailing-quote.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - [ - "a", - "b,c\nd,e,f" - ] -] diff --git a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter-with-a-valid-closer.csv b/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter-with-a-valid-closer.csv deleted file mode 100644 index 25dc50f..0000000 --- a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter-with-a-valid-closer.csv +++ /dev/null @@ -1,2 +0,0 @@ -"a,"b,c" -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter.csv b/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter.csv deleted file mode 100644 index 3e8fdd3..0000000 --- a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,"b,"c -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-new-line.csv b/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-new-line.csv deleted file mode 100644 index 42aef64..0000000 --- a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-after-new-line.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,"b,c -d"e,f,g \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-before-delimiter.csv b/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-before-delimiter.csv deleted file mode 100644 index 0a9d31d..0000000 --- a/test/fixtures/papa-quoted-field-has-invalid-trailing-quote-before-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,"b"c,d -e,f,g \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-no-closing-quote.csv b/test/fixtures/papa-quoted-field-has-no-closing-quote.csv deleted file mode 100644 index c9d316a..0000000 --- a/test/fixtures/papa-quoted-field-has-no-closing-quote.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,"b,c -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.csv b/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.csv deleted file mode 100644 index b2f34ae..0000000 --- a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,"b",c -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.json b/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-delimiter.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.csv b/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.csv deleted file mode 100644 index 7c3866f..0000000 --- a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b,c -d,e,"f" \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.json b/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-eof.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.csv b/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.csv deleted file mode 100644 index 05aca5b..0000000 --- a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.csv +++ /dev/null @@ -1,2 +0,0 @@ -a,b,"c" -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.json b/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-quoted-field-has-valid-trailing-quote-via-n.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.csv b/test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.csv deleted file mode 100644 index 01d8e62..0000000 --- a/test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.csv +++ /dev/null @@ -1 +0,0 @@ -"1","cnonce="""",nc=""""","2" \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.json b/test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.json deleted file mode 100644 index 9da6a71..0000000 --- a/test/fixtures/papa-quoted-field-with-5-quotes-in-a-row-and-a-delimiter-in-there-too.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "1", - "cnonce=\"\",nc=\"\"", - "2" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-delimiter.csv b/test/fixtures/papa-quoted-field-with-delimiter.csv deleted file mode 100644 index 9382cff..0000000 --- a/test/fixtures/papa-quoted-field-with-delimiter.csv +++ /dev/null @@ -1 +0,0 @@ -A,"B,B",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-delimiter.json b/test/fixtures/papa-quoted-field-with-delimiter.json deleted file mode 100644 index 2bc5490..0000000 --- a/test/fixtures/papa-quoted-field-with-delimiter.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B,B", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.csv b/test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.csv deleted file mode 100644 index fbbb934..0000000 --- a/test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.csv +++ /dev/null @@ -1 +0,0 @@ -A,"""B""",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.json b/test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.json deleted file mode 100644 index 3cdf91b..0000000 --- a/test/fixtures/papa-quoted-field-with-escaped-quotes-at-boundaries.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "\"B\"", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-escaped-quotes.csv b/test/fixtures/papa-quoted-field-with-escaped-quotes.csv deleted file mode 100644 index 00dec07..0000000 --- a/test/fixtures/papa-quoted-field-with-escaped-quotes.csv +++ /dev/null @@ -1 +0,0 @@ -A,"B""B""B",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-escaped-quotes.json b/test/fixtures/papa-quoted-field-with-escaped-quotes.json deleted file mode 100644 index 25b324e..0000000 --- a/test/fixtures/papa-quoted-field-with-escaped-quotes.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B\"B\"B", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.csv b/test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.csv deleted file mode 100644 index 96c142a..0000000 --- a/test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.csv +++ /dev/null @@ -1 +0,0 @@ -A," B ",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.json b/test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.json deleted file mode 100644 index 4b42a7c..0000000 --- a/test/fixtures/papa-quoted-field-with-extra-whitespace-on-edges.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - " B ", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-line-break.csv b/test/fixtures/papa-quoted-field-with-line-break.csv deleted file mode 100644 index c98c6ec..0000000 --- a/test/fixtures/papa-quoted-field-with-line-break.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,"B -B",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-line-break.json b/test/fixtures/papa-quoted-field-with-line-break.json deleted file mode 100644 index cc396ac..0000000 --- a/test/fixtures/papa-quoted-field-with-line-break.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B\nB", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-n.csv b/test/fixtures/papa-quoted-field-with-n.csv deleted file mode 100644 index c98c6ec..0000000 --- a/test/fixtures/papa-quoted-field-with-n.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,"B -B",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-n.json b/test/fixtures/papa-quoted-field-with-n.json deleted file mode 100644 index cc396ac..0000000 --- a/test/fixtures/papa-quoted-field-with-n.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B\nB", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-quotes-around-delimiter.csv b/test/fixtures/papa-quoted-field-with-quotes-around-delimiter.csv deleted file mode 100644 index a72e39d..0000000 --- a/test/fixtures/papa-quoted-field-with-quotes-around-delimiter.csv +++ /dev/null @@ -1 +0,0 @@ -A,""",""",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-quotes-around-delimiter.json b/test/fixtures/papa-quoted-field-with-quotes-around-delimiter.json deleted file mode 100644 index d80f7d4..0000000 --- a/test/fixtures/papa-quoted-field-with-quotes-around-delimiter.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "\",\"", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.csv b/test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.csv deleted file mode 100644 index 4bade34..0000000 --- a/test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.csv +++ /dev/null @@ -1 +0,0 @@ -A,""",",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.json b/test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.json deleted file mode 100644 index dc50e4a..0000000 --- a/test/fixtures/papa-quoted-field-with-quotes-on-left-side-of-delimiter.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "\",", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.csv b/test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.csv deleted file mode 100644 index f0256e9..0000000 --- a/test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.csv +++ /dev/null @@ -1 +0,0 @@ -A,",""",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.json b/test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.json deleted file mode 100644 index c174154..0000000 --- a/test/fixtures/papa-quoted-field-with-quotes-on-right-side-of-delimiter.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - ",\"", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-r-n.csv b/test/fixtures/papa-quoted-field-with-r-n.csv deleted file mode 100644 index 1ad0a44..0000000 --- a/test/fixtures/papa-quoted-field-with-r-n.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,"B -B",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-r-n.json b/test/fixtures/papa-quoted-field-with-r-n.json deleted file mode 100644 index 242a6dc..0000000 --- a/test/fixtures/papa-quoted-field-with-r-n.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B\r\nB", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-r.csv b/test/fixtures/papa-quoted-field-with-r.csv deleted file mode 100644 index eeb695a..0000000 --- a/test/fixtures/papa-quoted-field-with-r.csv +++ /dev/null @@ -1 +0,0 @@ -A,"B B",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-r.json b/test/fixtures/papa-quoted-field-with-r.json deleted file mode 100644 index d70ec72..0000000 --- a/test/fixtures/papa-quoted-field-with-r.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B\rB", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field-with-whitespace-around-quotes.csv b/test/fixtures/papa-quoted-field-with-whitespace-around-quotes.csv deleted file mode 100644 index 1053b10..0000000 --- a/test/fixtures/papa-quoted-field-with-whitespace-around-quotes.csv +++ /dev/null @@ -1 +0,0 @@ -A, "B" ,C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field-with-whitespace-around-quotes.json b/test/fixtures/papa-quoted-field-with-whitespace-around-quotes.json deleted file mode 100644 index 6525263..0000000 --- a/test/fixtures/papa-quoted-field-with-whitespace-around-quotes.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - " \"B\" ", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-field.csv b/test/fixtures/papa-quoted-field.csv deleted file mode 100644 index 6eadde4..0000000 --- a/test/fixtures/papa-quoted-field.csv +++ /dev/null @@ -1 +0,0 @@ -A,"B",C \ No newline at end of file diff --git a/test/fixtures/papa-quoted-field.json b/test/fixtures/papa-quoted-field.json deleted file mode 100644 index f860470..0000000 --- a/test/fixtures/papa-quoted-field.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B", - "C" - ] -] diff --git a/test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.csv b/test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.csv deleted file mode 100644 index b96a397..0000000 --- a/test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,"c,c -c" -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.json b/test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.json deleted file mode 100644 index e1b467d..0000000 --- a/test/fixtures/papa-quoted-fields-at-end-of-row-with-delimiter-and-line-break.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c,c\nc" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-quoted-fields-with-line-breaks.csv b/test/fixtures/papa-quoted-fields-with-line-breaks.csv deleted file mode 100644 index 88a3644..0000000 --- a/test/fixtures/papa-quoted-fields-with-line-breaks.csv +++ /dev/null @@ -1,4 +0,0 @@ -A,"B -B","C -C -C" \ No newline at end of file diff --git a/test/fixtures/papa-quoted-fields-with-line-breaks.json b/test/fixtures/papa-quoted-fields-with-line-breaks.json deleted file mode 100644 index c396f92..0000000 --- a/test/fixtures/papa-quoted-fields-with-line-breaks.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B\nB", - "C\nC\nC" - ] -] diff --git a/test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.csv b/test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.csv deleted file mode 100644 index 87f53ab..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.csv +++ /dev/null @@ -1,3 +0,0 @@ -A,"B" ,C,"D" -E,F,"G" ,"H" -Q,W,"E" ,R \ No newline at end of file diff --git a/test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.json b/test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.json deleted file mode 100644 index dbcf350..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-after-closing-quote.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - [ - "A", - "B", - "C", - "D" - ], - [ - "E", - "F", - "G", - "H" - ], - [ - "Q", - "W", - "E", - "R" - ] -] diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.csv b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.csv deleted file mode 100644 index 3621fb5..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,"c -" -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.json b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.json deleted file mode 100644 index a811254..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-newline-and-contains-newline.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c\n" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.csv b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.csv deleted file mode 100644 index e63c960..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,",B" ,C,D -E,F,G,H \ No newline at end of file diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.json b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.json deleted file mode 100644 index e5958d5..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter-and-contains-delimiter.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - [ - "A", - ",B", - "C", - "D" - ], - [ - "E", - "F", - "G", - "H" - ] -] diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.csv b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.csv deleted file mode 100644 index 3d789d1..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,"B" ,C,D -E,F,"G" ,H \ No newline at end of file diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.json b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.json deleted file mode 100644 index 420b102..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-delimiter.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - [ - "A", - "B", - "C", - "D" - ], - [ - "E", - "F", - "G", - "H" - ] -] diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.csv b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.csv deleted file mode 100644 index 9e3acdf..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.csv +++ /dev/null @@ -1,3 +0,0 @@ -A,B,C,"D" -E,F,G,"H" -Q,W,E,R \ No newline at end of file diff --git a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.json b/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.json deleted file mode 100644 index dbcf350..0000000 --- a/test/fixtures/papa-quoted-fields-with-spaces-between-closing-quote-and-next-new-line.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - [ - "A", - "B", - "C", - "D" - ], - [ - "E", - "F", - "G", - "H" - ], - [ - "Q", - "W", - "E", - "R" - ] -] diff --git a/test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.csv b/test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.csv deleted file mode 100644 index 7573f21..0000000 --- a/test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,B,C -a,b, \ No newline at end of file diff --git a/test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.json b/test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.json deleted file mode 100644 index 3c5cd20..0000000 --- a/test/fixtures/papa-row-with-enough-fields-but-blank-field-at-end.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "A": "a", - "B": "b", - "C": "" - } -] diff --git a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.csv b/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.csv deleted file mode 100644 index aa16fa0..0000000 --- a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.csv +++ /dev/null @@ -1,3 +0,0 @@ -A,B,C -,b1,c1 -,b2,c2 \ No newline at end of file diff --git a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.json b/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.json deleted file mode 100644 index bceacae..0000000 --- a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining-using-headers.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "A": "", - "B": "b1", - "C": "c1" - }, - { - "A": "", - "B": "b2", - "C": "c2" - } -] diff --git a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.csv b/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.csv deleted file mode 100644 index b50700a..0000000 --- a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.csv +++ /dev/null @@ -1,3 +0,0 @@ -A,B,C -,b1,c1 -a2,b2,c2 \ No newline at end of file diff --git a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.json b/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.json deleted file mode 100644 index 433465c..0000000 --- a/test/fixtures/papa-row-with-enough-fields-but-blank-field-in-the-begining.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - [ - "A", - "B", - "C" - ], - [ - "", - "b1", - "c1" - ], - [ - "a2", - "b2", - "c2" - ] -] diff --git a/test/fixtures/papa-row-with-too-few-fields.csv b/test/fixtures/papa-row-with-too-few-fields.csv deleted file mode 100644 index 8b51576..0000000 --- a/test/fixtures/papa-row-with-too-few-fields.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,B,C -a,b \ No newline at end of file diff --git a/test/fixtures/papa-row-with-too-many-fields.csv b/test/fixtures/papa-row-with-too-many-fields.csv deleted file mode 100644 index 4f38565..0000000 --- a/test/fixtures/papa-row-with-too-many-fields.csv +++ /dev/null @@ -1,3 +0,0 @@ -A,B,C -a,b,c,d,e -f,g,h \ No newline at end of file diff --git a/test/fixtures/papa-single-quote-as-quote-character.csv b/test/fixtures/papa-single-quote-as-quote-character.csv deleted file mode 100644 index 01ec509..0000000 --- a/test/fixtures/papa-single-quote-as-quote-character.csv +++ /dev/null @@ -1 +0,0 @@ -a,b,'c,d' \ No newline at end of file diff --git a/test/fixtures/papa-single-quote-as-quote-character.json b/test/fixtures/papa-single-quote-as-quote-character.json deleted file mode 100644 index 948722a..0000000 --- a/test/fixtures/papa-single-quote-as-quote-character.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "b", - "c,d" - ] -] diff --git a/test/fixtures/papa-skip-empty-lines-with-empty-input.csv b/test/fixtures/papa-skip-empty-lines-with-empty-input.csv deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/papa-skip-empty-lines-with-empty-input.json b/test/fixtures/papa-skip-empty-lines-with-empty-input.json deleted file mode 100644 index fe51488..0000000 --- a/test/fixtures/papa-skip-empty-lines-with-empty-input.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.csv b/test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.csv deleted file mode 100644 index 5e73edc..0000000 --- a/test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.csv +++ /dev/null @@ -1,2 +0,0 @@ - -a,b,c \ No newline at end of file diff --git a/test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.json b/test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.json deleted file mode 100644 index 1c206a1..0000000 --- a/test/fixtures/papa-skip-empty-lines-with-first-line-only-whitespace.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - [ - " " - ], - [ - "a", - "b", - "c" - ] -] diff --git a/test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.csv b/test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.csv deleted file mode 100644 index 29a42cc..0000000 --- a/test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c - -d,e,f diff --git a/test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.json b/test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-skip-empty-lines-with-newline-at-end-of-input.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-skip-empty-lines.csv b/test/fixtures/papa-skip-empty-lines.csv deleted file mode 100644 index 68eacbe..0000000 --- a/test/fixtures/papa-skip-empty-lines.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c - -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-skip-empty-lines.json b/test/fixtures/papa-skip-empty-lines.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-skip-empty-lines.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-tab-delimiter.csv b/test/fixtures/papa-tab-delimiter.csv deleted file mode 100644 index 34caaac..0000000 --- a/test/fixtures/papa-tab-delimiter.csv +++ /dev/null @@ -1,2 +0,0 @@ -a b c -d e f \ No newline at end of file diff --git a/test/fixtures/papa-tab-delimiter.json b/test/fixtures/papa-tab-delimiter.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-tab-delimiter.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.csv b/test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.csv deleted file mode 100644 index 3d5f757..0000000 --- a/test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.csv +++ /dev/null @@ -1,4 +0,0 @@ -#comment1 -#comment2 -#comment3 -a,b,c \ No newline at end of file diff --git a/test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.json b/test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.json deleted file mode 100644 index f47beff..0000000 --- a/test/fixtures/papa-three-comment-lines-consecutively-at-beginning-of-file.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "b", - "c" - ] -] diff --git a/test/fixtures/papa-three-rows.csv b/test/fixtures/papa-three-rows.csv deleted file mode 100644 index a7c9397..0000000 --- a/test/fixtures/papa-three-rows.csv +++ /dev/null @@ -1,3 +0,0 @@ -A,b,c -d,E,f -G,h,i \ No newline at end of file diff --git a/test/fixtures/papa-three-rows.json b/test/fixtures/papa-three-rows.json deleted file mode 100644 index ce1c97e..0000000 --- a/test/fixtures/papa-three-rows.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - [ - "A", - "b", - "c" - ], - [ - "d", - "E", - "f" - ], - [ - "G", - "h", - "i" - ] -] diff --git a/test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.csv b/test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.csv deleted file mode 100644 index 9d0af26..0000000 --- a/test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -#comment1 -#comment2 \ No newline at end of file diff --git a/test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.json b/test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.json deleted file mode 100644 index f47beff..0000000 --- a/test/fixtures/papa-two-comment-lines-consecutively-at-end-of-file.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "b", - "c" - ] -] diff --git a/test/fixtures/papa-two-comment-lines-consecutively.csv b/test/fixtures/papa-two-comment-lines-consecutively.csv deleted file mode 100644 index 2d63911..0000000 --- a/test/fixtures/papa-two-comment-lines-consecutively.csv +++ /dev/null @@ -1,4 +0,0 @@ -a,b,c -#comment1 -#comment2 -d,e,f \ No newline at end of file diff --git a/test/fixtures/papa-two-comment-lines-consecutively.json b/test/fixtures/papa-two-comment-lines-consecutively.json deleted file mode 100644 index d0b6253..0000000 --- a/test/fixtures/papa-two-comment-lines-consecutively.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "a", - "b", - "c" - ], - [ - "d", - "e", - "f" - ] -] diff --git a/test/fixtures/papa-two-rows-just-r.csv b/test/fixtures/papa-two-rows-just-r.csv deleted file mode 100644 index da7eef8..0000000 --- a/test/fixtures/papa-two-rows-just-r.csv +++ /dev/null @@ -1 +0,0 @@ -A,b,c d,E,f \ No newline at end of file diff --git a/test/fixtures/papa-two-rows-just-r.json b/test/fixtures/papa-two-rows-just-r.json deleted file mode 100644 index 142ca69..0000000 --- a/test/fixtures/papa-two-rows-just-r.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "A", - "b", - "c" - ], - [ - "d", - "E", - "f" - ] -] diff --git a/test/fixtures/papa-two-rows-r-n.csv b/test/fixtures/papa-two-rows-r-n.csv deleted file mode 100644 index 5a60fa0..0000000 --- a/test/fixtures/papa-two-rows-r-n.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,b,c -d,E,f \ No newline at end of file diff --git a/test/fixtures/papa-two-rows-r-n.json b/test/fixtures/papa-two-rows-r-n.json deleted file mode 100644 index 142ca69..0000000 --- a/test/fixtures/papa-two-rows-r-n.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "A", - "b", - "c" - ], - [ - "d", - "E", - "f" - ] -] diff --git a/test/fixtures/papa-two-rows.csv b/test/fixtures/papa-two-rows.csv deleted file mode 100644 index ea46d36..0000000 --- a/test/fixtures/papa-two-rows.csv +++ /dev/null @@ -1,2 +0,0 @@ -A,b,c -d,E,f \ No newline at end of file diff --git a/test/fixtures/papa-two-rows.json b/test/fixtures/papa-two-rows.json deleted file mode 100644 index 142ca69..0000000 --- a/test/fixtures/papa-two-rows.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - "A", - "b", - "c" - ], - [ - "d", - "E", - "f" - ] -] diff --git a/test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.csv b/test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.csv deleted file mode 100644 index a420a04..0000000 --- a/test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.csv +++ /dev/null @@ -1 +0,0 @@ -A,B",C \ No newline at end of file diff --git a/test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.json b/test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.json deleted file mode 100644 index b1ca572..0000000 --- a/test/fixtures/papa-unquoted-field-with-quotes-at-end-of-field.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "A", - "B\"", - "C" - ] -] diff --git a/test/fixtures/papa-using-n-endings-uses-n-linebreak.csv b/test/fixtures/papa-using-n-endings-uses-n-linebreak.csv deleted file mode 100644 index 2e11fcd..0000000 --- a/test/fixtures/papa-using-n-endings-uses-n-linebreak.csv +++ /dev/null @@ -1,5 +0,0 @@ -a,b -c,d -e,f -g,h -i,j \ No newline at end of file diff --git a/test/fixtures/papa-using-n-endings-uses-n-linebreak.json b/test/fixtures/papa-using-n-endings-uses-n-linebreak.json deleted file mode 100644 index c670851..0000000 --- a/test/fixtures/papa-using-n-endings-uses-n-linebreak.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.csv b/test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.csv deleted file mode 100644 index abd33f3..0000000 --- a/test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.csv +++ /dev/null @@ -1,6 +0,0 @@ -"a -a",b -c,d -e,f -g,h -i,j \ No newline at end of file diff --git a/test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.json b/test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.json deleted file mode 100644 index 8e09d82..0000000 --- a/test/fixtures/papa-using-n-endings-with-r-n-in-header-field-uses-n-linebreak.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a\r\na", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.csv b/test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.csv deleted file mode 100644 index 3a6870e..0000000 --- a/test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.csv +++ /dev/null @@ -1,5 +0,0 @@ -a,b -c,d -e,f -g,h -i,j \ No newline at end of file diff --git a/test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.json b/test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.json deleted file mode 100644 index c670851..0000000 --- a/test/fixtures/papa-using-r-n-endings-uses-r-n-linebreak.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.csv b/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.csv deleted file mode 100644 index 607dafe..0000000 --- a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.csv +++ /dev/null @@ -1,6 +0,0 @@ -"a -a",b -c,d -e,f -g,h -i,j \ No newline at end of file diff --git a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.json b/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.json deleted file mode 100644 index fed2e68..0000000 --- a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-uses-r-n-linebreak.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a\na", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.csv b/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.csv deleted file mode 100644 index a6be125..0000000 --- a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.csv +++ /dev/null @@ -1,6 +0,0 @@ -"a -a",b -c,d -e,f -g,h -i,j diff --git a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.json b/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.json deleted file mode 100644 index fed2e68..0000000 --- a/test/fixtures/papa-using-r-n-endings-with-n-in-header-field-with-skip-empty-lines-uses-r-n-linebreak.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a\na", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.csv b/test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.csv deleted file mode 100644 index 2c0ca0b..0000000 --- a/test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.csv +++ /dev/null @@ -1,6 +0,0 @@ -"a -a",b -c,d -e,f -g,h -i,j \ No newline at end of file diff --git a/test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.json b/test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.json deleted file mode 100644 index 8e09d82..0000000 --- a/test/fixtures/papa-using-r-n-endings-with-r-n-in-header-field-uses-r-n-linebreak.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a\r\na", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-using-reserved-regex-character-as-quote-character.csv b/test/fixtures/papa-using-reserved-regex-character-as-quote-character.csv deleted file mode 100644 index a43df71..0000000 --- a/test/fixtures/papa-using-reserved-regex-character-as-quote-character.csv +++ /dev/null @@ -1,6 +0,0 @@ -|a -a|,b -c,d -e,f -g,h -i,j \ No newline at end of file diff --git a/test/fixtures/papa-using-reserved-regex-character-as-quote-character.json b/test/fixtures/papa-using-reserved-regex-character-as-quote-character.json deleted file mode 100644 index fed2e68..0000000 --- a/test/fixtures/papa-using-reserved-regex-character-as-quote-character.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - [ - "a\na", - "b" - ], - [ - "c", - "d" - ], - [ - "e", - "f" - ], - [ - "g", - "h" - ], - [ - "i", - "j" - ] -] diff --git a/test/fixtures/papa-whitespace-at-edges-of-unquoted-field.csv b/test/fixtures/papa-whitespace-at-edges-of-unquoted-field.csv deleted file mode 100644 index 64954bc..0000000 --- a/test/fixtures/papa-whitespace-at-edges-of-unquoted-field.csv +++ /dev/null @@ -1 +0,0 @@ -a, b ,c \ No newline at end of file diff --git a/test/fixtures/papa-whitespace-at-edges-of-unquoted-field.json b/test/fixtures/papa-whitespace-at-edges-of-unquoted-field.json deleted file mode 100644 index a165586..0000000 --- a/test/fixtures/papa-whitespace-at-edges-of-unquoted-field.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - [ - "a", - "\tb ", - "c" - ] -] diff --git a/test/fixtures/pipe-separator.csv b/test/fixtures/pipe-separator.csv deleted file mode 100644 index e8d53ce..0000000 --- a/test/fixtures/pipe-separator.csv +++ /dev/null @@ -1,3 +0,0 @@ -a|b|c -A|B|C -AA|BB|CC diff --git a/test/fixtures/pipe-separator.json b/test/fixtures/pipe-separator.json deleted file mode 100644 index f4668e4..0000000 --- a/test/fixtures/pipe-separator.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"A","b":"B","c":"C"},{"a":"AA","b":"BB","c":"CC"}] diff --git a/test/fixtures/quote.csv b/test/fixtures/quote.csv deleted file mode 100644 index 57a9c05..0000000 --- a/test/fixtures/quote.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -"1","B","true" -"2","B""B","false" diff --git a/test/fixtures/quote.json b/test/fixtures/quote.json deleted file mode 100644 index da71f01..0000000 --- a/test/fixtures/quote.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "a": "1", - "b": "B", - "c": "true" - }, - { - "a": "2", - "b": "B\"B", - "c": "false" - } -] diff --git a/test/fixtures/quoted-escape.csv b/test/fixtures/quoted-escape.csv deleted file mode 100644 index 8c30b29..0000000 --- a/test/fixtures/quoted-escape.csv +++ /dev/null @@ -1,6 +0,0 @@ -a -"""b" -"b""" -"""b""" -"b""c" -"b""c""d" diff --git a/test/fixtures/quoted-escape.json b/test/fixtures/quoted-escape.json deleted file mode 100644 index 0d2bdd2..0000000 --- a/test/fixtures/quoted-escape.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"\"b"},{"a":"b\""},{"a":"\"b\""},{"a":"b\"c"},{"a":"b\"c\"d"}] diff --git a/test/fixtures/quoted-newline.csv b/test/fixtures/quoted-newline.csv deleted file mode 100644 index 734a55c..0000000 --- a/test/fixtures/quoted-newline.csv +++ /dev/null @@ -1,5 +0,0 @@ -a,b -"line1 -line2",simple -"hello","world -!" diff --git a/test/fixtures/quoted-newline.json b/test/fixtures/quoted-newline.json deleted file mode 100644 index c3c8894..0000000 --- a/test/fixtures/quoted-newline.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"line1\nline2","b":"simple"},{"a":"hello","b":"world\n!"}] diff --git a/test/fixtures/quoted-simple.csv b/test/fixtures/quoted-simple.csv deleted file mode 100644 index bc880e6..0000000 --- a/test/fixtures/quoted-simple.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b -"hello","world" -"foo",bar diff --git a/test/fixtures/quoted-simple.json b/test/fixtures/quoted-simple.json deleted file mode 100644 index 031e677..0000000 --- a/test/fixtures/quoted-simple.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"hello","b":"world"},{"a":"foo","b":"bar"}] diff --git a/test/fixtures/record-separator.csv b/test/fixtures/record-separator.csv deleted file mode 100644 index 830e627..0000000 --- a/test/fixtures/record-separator.csv +++ /dev/null @@ -1 +0,0 @@ -a,b,c%A,B,C%AA,BB,CC \ No newline at end of file diff --git a/test/fixtures/record-separator.json b/test/fixtures/record-separator.json deleted file mode 100644 index f4668e4..0000000 --- a/test/fixtures/record-separator.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"A","b":"B","c":"C"},{"a":"AA","b":"BB","c":"CC"}] diff --git a/test/fixtures/trailing-newline.csv b/test/fixtures/trailing-newline.csv deleted file mode 100644 index 9255cff..0000000 --- a/test/fixtures/trailing-newline.csv +++ /dev/null @@ -1,3 +0,0 @@ -a -1 -2 diff --git a/test/fixtures/trailing-newline.json b/test/fixtures/trailing-newline.json deleted file mode 100644 index 94d1615..0000000 --- a/test/fixtures/trailing-newline.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":"1"},{"a":"2"}] diff --git a/test/fixtures/trim.csv b/test/fixtures/trim.csv deleted file mode 100644 index 7998755..0000000 --- a/test/fixtures/trim.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b - hello , world - foo ,bar diff --git a/test/fixtures/trim.json b/test/fixtures/trim.json deleted file mode 100644 index f8f2ee7..0000000 --- a/test/fixtures/trim.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "a": "1", - "b": "2", - "c": "3" - }, - { - "a": "11", - "b": "22", - "c": "33" - }, - { - "a": "4", - "b": "5", - "c": "6" - }, - { - "a": "44", - "b": "55", - "c": "66" - } -] diff --git a/test/fixtures/value.csv b/test/fixtures/value.csv deleted file mode 100644 index 97c34a5..0000000 --- a/test/fixtures/value.csv +++ /dev/null @@ -1,3 +0,0 @@ -a,b,c -true,false,null -hello,true,1 diff --git a/test/fixtures/value.json b/test/fixtures/value.json deleted file mode 100644 index 22ed72b..0000000 --- a/test/fixtures/value.json +++ /dev/null @@ -1 +0,0 @@ -[{"a":true,"b":false,"c":null},{"a":"hello","b":true,"c":"1"}] diff --git a/test/quick.js b/test/quick.js deleted file mode 100644 index 63047f3..0000000 --- a/test/quick.js +++ /dev/null @@ -1,136 +0,0 @@ -const { Jsonic } = require('@jsonic/jsonic-next') -const { Debug } = require('@jsonic/jsonic-next/debug') -const { Csv } = require('..') - -const tlog = [] - -// const c0 = Jsonic.make() -// .use(Debug,{trace:true}) -// .use(Csv,{comment:true,object:false,header:false}) - -// const u0 = Jsonic.make() -// // .use(Debug,{trace:true}) -// .use(Csv,{ -// strict:false, -// }) - -const csv = Jsonic.make() - .use(Debug, { trace: true }) - .use(Csv, { - // line: {empty:true}, - // header: false, - // object: false, - // trim: true, - // value: true, - // comment: true, - // record: { empty: true } - }) - // .sub({lex:(t)=>console.log(t)}) - .sub({ lex: (t) => tlog.push(t) }) - -// console.log(csv.options.tokenSet) -// console.log(csv.internal().config.lex.match) - -// console.log(csv(`a,b -// 1,2,`,{xlog:-1})) - -console.log( - csv( - `a -,1`, - { xlog: -1 }, - ), -) - -// console.log(csv(`a,b -// 1, 2 -// 11 ,{22 -// 3 3, "a" -// `,{xlog:-1})) - -// console.log(csv(`a,b -// 1,2 -// 3,"x""y" -// 4,5 -// `,{xlog:-1})) - -// console.log(csv(`a,b -// 1, 2 3 -// 4, 5 6 -// 7, 8 9 -// 10, 11 12 13 -// `,{xlog:-1})) - -// const u0 = Jsonic.make() -// .use(Debug,{trace:true}) -// .use(Csv, {strict:false}) - -// console.dir(u0(`a,b -// 1 , 2 -// `),{depth:null}) - -// console.dir(u0(`a,b,c -// true,[1,2],{x:{y:"q\\"w"}} -// null,'Q\\r\\nA',1e2 -// `),{depth:null}) - -// console.log(c0(`a,b,c -// 1 , 2 , 3 -// 11 , 22 , 33 -// 4\t,\t5\t,\t6 -// \t44\t,\t\t55\t\t\t,\t6\t -// `)) - -// console.log(c0(`a,b,c,d,e,f -// 1 ,2 , 3 ,4 5 , 6 7,8 9 0 -// `)) - -// console.log(c0(`a,b -// "x"y,z`)) - -// console.log(u0(`a -// b `)) - -// console.log(c0(` -// 1`)) - -// console.log(c0('')) - -// console.log(c0(`#foo -// #bar -// 1,2 -// #a -// #b - -// 3,4 - -// #c - -// `)) - -// console.log(csv(`a,b -// A,B -// #X - -// AA,BB`)) - -// console.log(csv(` -// #X -// #XX -// a,b -// #Y -// #YY -// A,B -// #Z -// #ZZ -// `)) - -// console.log(csv('\n')) - -// console.log(csv('a,b\nA,"""B"')) - -// console.log(csv('true')) - -// console.log(csv('\na\n')) - -// console.log(tlog) diff --git a/test/zon.test.ts b/test/zon.test.ts new file mode 100644 index 0000000..d180e3b --- /dev/null +++ b/test/zon.test.ts @@ -0,0 +1,162 @@ +/* Copyright (c) 2025 Richard Rodger and other contributors, MIT License */ + +import { describe, test } from 'node:test' +import assert from 'node:assert' + +import { Jsonic } from 'jsonic' +import { Zon } from '../dist/zon' + +// Jsonic builds maps with Object.create(null); normalise to plain objects so +// assert.deepStrictEqual can compare against JSON-style literals. +function plain(v: any): any { + if (v === null || typeof v !== 'object') return v + if (Array.isArray(v)) return v.map(plain) + const out: Record = {} + for (const k of Object.keys(v)) out[k] = plain((v as any)[k]) + return out +} + +function parse(src: string, opts?: any) { + const j = Jsonic.make().use(Zon, opts || {}) + return plain(j(src)) +} + +describe('zon', () => { + test('scalar values', () => { + assert.strictEqual(parse('42'), 42) + assert.strictEqual(parse('3.14'), 3.14) + assert.strictEqual(parse('true'), true) + assert.strictEqual(parse('false'), false) + assert.strictEqual(parse('null'), null) + assert.strictEqual(parse('"hello"'), 'hello') + }) + + test('numeric bases', () => { + assert.strictEqual(parse('0x2a'), 42) + assert.strictEqual(parse('0o52'), 42) + assert.strictEqual(parse('0b101010'), 42) + assert.strictEqual(parse('1_000_000'), 1000000) + }) + + test('enum literal as bare string', () => { + assert.strictEqual(parse('.foo'), 'foo') + assert.strictEqual(parse('.bar_baz'), 'bar_baz') + }) + + test('empty struct', () => { + assert.deepStrictEqual(parse('.{}'), []) + }) + + test('simple struct', () => { + assert.deepStrictEqual(parse('.{ .a = 1 }'), { a: 1 }) + assert.deepStrictEqual(parse('.{ .a = 1, .b = 2 }'), { a: 1, b: 2 }) + }) + + test('trailing comma in struct', () => { + assert.deepStrictEqual(parse('.{ .a = 1, }'), { a: 1 }) + assert.deepStrictEqual(parse('.{ .a = 1, .b = 2, }'), { a: 1, b: 2 }) + }) + + test('tuple literal', () => { + assert.deepStrictEqual(parse('.{ 1, 2, 3 }'), [1, 2, 3]) + assert.deepStrictEqual(parse('.{ "a", "b" }'), ['a', 'b']) + }) + + test('trailing comma in tuple', () => { + assert.deepStrictEqual(parse('.{ 1, 2, 3, }'), [1, 2, 3]) + }) + + test('nested struct', () => { + assert.deepStrictEqual(parse('.{ .a = .{ .b = 1 } }'), { a: { b: 1 } }) + }) + + test('nested tuple', () => { + assert.deepStrictEqual(parse('.{ .{ 1, 2 }, .{ 3, 4 } }'), [ + [1, 2], + [3, 4], + ]) + }) + + test('mixed nesting', () => { + assert.deepStrictEqual( + parse('.{ .xs = .{ 1, 2, 3 }, .y = .{ .z = true } }'), + { xs: [1, 2, 3], y: { z: true } }, + ) + }) + + test('string escapes', () => { + assert.strictEqual(parse('"a\\nb"'), 'a\nb') + assert.strictEqual(parse('"a\\tb"'), 'a\tb') + assert.strictEqual(parse('"a\\\\b"'), 'a\\b') + }) + + test('enum literal as value', () => { + assert.deepStrictEqual(parse('.{ .kind = .red }'), { kind: 'red' }) + }) + + test('comments', () => { + const src = `.{ + // a comment + .name = "x", // trailing comment + .version = "1.0", // version + }` + assert.deepStrictEqual(parse(src), { name: 'x', version: '1.0' }) + }) + + test('realistic build.zig.zon', () => { + const src = `.{ + .name = "example", + .version = "0.0.1", + .minimum_zig_version = "0.14.0", + .dependencies = .{ + .foo = .{ + .url = "https://example.com/foo.tar.gz", + .hash = "1220deadbeef", + }, + }, + .paths = .{ + "build.zig", + "src", + "", + }, + }` + assert.deepStrictEqual(parse(src), { + name: 'example', + version: '0.0.1', + minimum_zig_version: '0.14.0', + dependencies: { + foo: { + url: 'https://example.com/foo.tar.gz', + hash: '1220deadbeef', + }, + }, + paths: ['build.zig', 'src', ''], + }) + }) + + test('char literal as number', () => { + assert.strictEqual(parse("'A'", { charAsNumber: true }), 65) + assert.strictEqual(parse("'\\n'", { charAsNumber: true }), 10) + assert.strictEqual(parse("'\\u{1F600}'", { charAsNumber: true }), 0x1f600) + }) + + test('char literal as string', () => { + assert.strictEqual(parse("'A'"), 'A') + }) + + test('multi-line string', () => { + const src = `.{ + .text = \\\\hello + \\\\world + , + }` + assert.deepStrictEqual(parse(src), { text: 'hello\nworld' }) + }) + + test('enumTag option', () => { + const opts = { enumTag: '$enum' } + assert.deepStrictEqual(parse('.{ .kind = .red }', opts), { + kind: { $enum: 'red' }, + }) + }) +}) diff --git a/zon-grammar.jsonic b/zon-grammar.jsonic new file mode 100644 index 0000000..7eb3ae9 --- /dev/null +++ b/zon-grammar.jsonic @@ -0,0 +1,50 @@ +# ZON Grammar Definition +# Parses Zig Object Notation (ZON) - a data format based on Zig anonymous +# struct literals. +# +# Example: +# .{ +# .name = "example", +# .version = "0.0.1", +# .deps = .{ .foo = .{ .url = "https://..." } }, +# .paths = .{ "build.zig", "src" }, +# } +# +# The custom zon-dot lex matcher distinguishes struct (map) and tuple (list) +# openings at lex time by peeking ahead: +# .{ followed by .ident = -> emits #OB (struct / map) +# .{ otherwise -> emits #OS (tuple / list) +# Both close on } which lexes as #CB. The list rules below use #CB (not +# the default #CS) as the list terminator so that the single } character +# closes both struct and tuple literals. +# +# A bare .identifier emits #TX with val = identifier (the leading dot is +# stripped). This token is both a valid KEY (when followed by =) and a +# valid VAL (when used as an enum literal). + +{ + rule: val: open: [ + # Empty .{} -> empty list. + { s: '#OS #CB' b: 2 p: list g: 'zon,list,empty' } + ] + + rule: list: open: [ + { s: '#OS #CB' b: 1 g: 'zon,list,empty' } + { s: '#OS' p: elem g: 'zon,list,open' } + ] + rule: list: close: [ + { s: '#CB' g: 'zon,list,close' } + ] + + rule: elem: close: [ + { s: '#CA #CB' b: 1 g: 'zon,elem,trailing' } + { s: '#CA' r: elem g: 'zon,elem,next' } + { s: '#CB' b: 1 g: 'zon,elem,end' } + ] + + rule: pair: close: [ + { s: '#CA #CB' b: 1 g: 'zon,pair,trailing' } + { s: '#CA' r: pair g: 'zon,pair,next' } + { s: '#CB' b: 1 g: 'zon,pair,end' } + ] +}