Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: #69 Dynamic Type Inference and User-Defined Types from CSV Headers #166

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d0072de
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Mar 17, 2024
c7ea7b6
fix: #69 fix join
nagasawaryoya Mar 21, 2024
b8383b6
add: #69 add test
nagasawaryoya Mar 21, 2024
1575365
Merge branch 'main' into refactor/#69-infer-csv-header-type
nagasawaryoya Apr 13, 2024
2beb545
update: enable typecheck
nagasawaryoya Apr 13, 2024
bcfa3b6
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Apr 13, 2024
3943467
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Apr 13, 2024
3365587
refactor: #69 test
nagasawaryoya Apr 14, 2024
fd6345b
refactor: #69 parse type
nagasawaryoya Apr 14, 2024
40b8ab5
refactor: #69 `PickHeader` -> `PickCSVHeader`
nagasawaryoya Apr 14, 2024
4f3684c
refactor: #69 fix newline
nagasawaryoya Apr 15, 2024
9f5f20c
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Apr 15, 2024
1f22cfe
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Apr 15, 2024
c798fef
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Apr 15, 2024
95e78ee
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Apr 15, 2024
1f75f8d
refactor: #69 Dynamic Type Inference and User-Defined Types from CSV …
nagasawaryoya Apr 15, 2024
154b570
fix: #69 escape delimiter
nagasawaryoya Apr 17, 2024
95363b3
fix: pick header type
nagasawaryoya Apr 19, 2024
0af4859
Merge branch 'main' into refactor/#69-infer-csv-header-type
nagasawaryoya Apr 19, 2024
9ba937e
Merge branch 'refactor/#69-infer-csv-header-type' into refactor/#69-i…
nagasawaryoya Apr 19, 2024
abfeade
fix
nagasawaryoya Apr 19, 2024
607128f
fix
nagasawaryoya Apr 19, 2024
ba20570
Merge branch 'refactor/#69-infer-csv-header-type' into refactor/#69-i…
nagasawaryoya Apr 19, 2024
8d0cad6
Merge pull request #1 from nagasawaryoya/refactor/#69-infer-csv-heade…
nagasawaryoya Apr 19, 2024
2bac115
Revert "Refactor/#69 infer csv header type 2"
nagasawaryoya Apr 19, 2024
4137f01
Merge pull request #2 from nagasawaryoya/revert-1-refactor/#69-infer-…
nagasawaryoya Apr 19, 2024
abd310d
fix: pick header type
nagasawaryoya Apr 19, 2024
3a4ecea
Merge branch 'refactor/#69-infer-csv-header-type' into update/#69-sim…
nagasawaryoya Apr 19, 2024
b4dd115
support escape quotation
nagasawaryoya May 11, 2024
11cd07f
fix
nagasawaryoya May 11, 2024
2587397
common -> utils
nagasawaryoya May 12, 2024
c711e6b
update test
nagasawaryoya May 12, 2024
90801ae
default delimiter and quotation
nagasawaryoya May 12, 2024
66133c0
default delimiter and quotation
nagasawaryoya May 12, 2024
ccda684
add test
nagasawaryoya May 12, 2024
abee538
tuning
nagasawaryoya May 12, 2024
841f104
Merge branch 'update/#69-simplify-the-type' of https://github.com/nag…
nagasawaryoya May 12, 2024
8b097db
Merge pull request #3 from nagasawaryoya/update/#69-simplify-the-type
nagasawaryoya May 12, 2024
2459b2a
Merge branch 'main' into refactor/#69-infer-csv-header-type
nagasawaryoya May 12, 2024
252c0c0
fix
nagasawaryoya May 12, 2024
64dfc6c
remove biome ignore comment
nagasawaryoya May 12, 2024
026ea71
Merge branch 'main' into refactor/#69-infer-csv-header-type
nagasawaryoya Aug 12, 2024
69118c0
remove vitest typecheck
nagasawaryoya Aug 18, 2024
1ea6439
fix type check
nagasawaryoya Aug 18, 2024
7b3dd36
Merge branch 'main' into refactor/#69-infer-csv-header-type
kamiazya Aug 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/Lexer.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { assertCommonOptions } from "./assertCommonOptions.ts";
import { Field, FieldDelimiter, RecordDelimiter } from "./common/constants.ts";
import { ParseError } from "./common/errors.ts";
import type { Position, RecordDelimiterToken } from "./common/types.ts";


import type {
AbortSignalOptions,
CommonOptions,
Position,
RecordDelimiterToken,
Token,
} from "./common/types.ts";
import { COMMA, CRLF, DOUBLE_QUOTE, LF } from "./constants.ts";
import { COMMA, CRLF, DEFAULT_DELIMITER, DEFAULT_QUOTATION, DOUBLE_QUOTE, LF } from "./constants.ts";
import { escapeRegExp } from "./utils/escapeRegExp.ts";

/**
Expand Down Expand Up @@ -38,8 +41,8 @@ export class Lexer {
* @param options - The common options for the lexer.
*/
constructor({
delimiter = COMMA,
quotation = DOUBLE_QUOTE,
delimiter = DEFAULT_DELIMITER,
quotation = DEFAULT_QUOTATION,
signal,
}: CommonOptions & AbortSignalOptions = {}) {
assertCommonOptions({ delimiter, quotation });
Expand Down
20 changes: 18 additions & 2 deletions src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { DEFAULT_DELIMITER, DEFAULT_QUOTATION } from "../constants.ts";
import type { Join } from "../utils/types.ts";
import type { Field, FieldDelimiter, RecordDelimiter } from "./constants.ts";

kamiazya marked this conversation as resolved.
Show resolved Hide resolved
/**
Expand Down Expand Up @@ -285,7 +287,15 @@ export type CSVRecord<Header extends ReadonlyArray<string>> = Record<
*
* @category Types
*/
export type CSVString = string | ReadableStream<string>;
export type CSVString<
Header extends ReadonlyArray<string> = [],
Delimiter extends string = DEFAULT_DELIMITER,
Quotation extends string = DEFAULT_QUOTATION,
> = Header extends readonly [string, ...string[]]
?
| Join<Header, Delimiter, Quotation>
| ReadableStream<Join<Header, Delimiter, Quotation>>
: string | ReadableStream<string>;

/**
* CSV Binary.
Expand All @@ -303,4 +313,10 @@ export type CSVBinary =
*
* @category Types
*/
export type CSV = CSVString | CSVBinary;
export type CSV<
Header extends ReadonlyArray<string> = [],
Delimiter extends string = DEFAULT_DELIMITER,
Quotation extends string = DEFAULT_QUOTATION,
> = Header extends []
? CSVString | CSVBinary
: CSVString<Header, Delimiter, Quotation>;
13 changes: 13 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
export const CR = "\r";
export type CR = typeof CR;

export const CRLF = "\r\n";
export type CRLF = typeof CRLF;

export const LF = "\n";
export type LF = typeof LF;

export type Newline = CRLF | CR | LF;

/**
* COMMA is a symbol for comma(,).
Expand All @@ -11,3 +18,9 @@ export const COMMA = ",";
* DOUBLE_QUOTE is a symbol for double quote(").
*/
export const DOUBLE_QUOTE = '"';

export const DEFAULT_DELIMITER = COMMA;
export type DEFAULT_DELIMITER = typeof DEFAULT_DELIMITER;

export const DEFAULT_QUOTATION = DOUBLE_QUOTE;
export type DEFAULT_QUOTATION = typeof DEFAULT_QUOTATION;
6 changes: 3 additions & 3 deletions src/escapeField.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { assertCommonOptions } from "./assertCommonOptions.ts";
import type { CommonOptions } from "./common/types.ts";
import { COMMA, DOUBLE_QUOTE } from "./constants.ts";
import { DEFAULT_DELIMITER, DEFAULT_QUOTATION } from "./constants.ts";
import { occurrences } from "./utils/occurrences.ts";

export interface EscapeFieldOptions extends CommonOptions {
Expand All @@ -20,8 +20,8 @@ const REPLACED_PATTERN_CACHE = new Map<string, string>();
export function escapeField(
value: string,
{
quotation = DOUBLE_QUOTE,
delimiter = COMMA,
delimiter = DEFAULT_DELIMITER,
quotation = DEFAULT_QUOTATION,
quote,
}: EscapeFieldOptions = {},
): string {
Expand Down
234 changes: 234 additions & 0 deletions src/parse.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { describe, expectTypeOf, it } from "vitest";
import { parse } from "./parse.ts";
import type {
CSV,
CSVBinary,
CSVRecord,
CSVString,
ParseOptions,
} from "./web-csv-toolbox.ts";

describe("parse function", () => {
it("parse should be a function with expected parameter types", () => {
expectTypeOf(parse).toBeFunction();
expectTypeOf(parse).parameter(0).toMatchTypeOf<CSV>();
expectTypeOf(parse)
.parameter(1)
.toMatchTypeOf<ParseOptions<readonly string[]> | undefined>();
});
});

describe("binary parsing", () => {
it("should CSV header of the parsed result will be string array", () => {
expectTypeOf(parse({} as CSVBinary)).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly string[]>>
>();
});
});

describe("string parsing", () => {
it("should CSV header of the parsed result will be string array", () => {
expectTypeOf(parse("" as string)).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly string[]>>
>();

expectTypeOf(parse({} as ReadableStream)).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly string[]>>
>();

expectTypeOf(parse({} as ReadableStream<string>)).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly string[]>>
>();

expectTypeOf(parse("" as CSVString)).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly string[]>>
>();
});
});

describe("csv literal string parsing", () => {
const csv1 = `name,age,city,zip
Alice,24,New York,10001
Bob,36,Los Angeles,90001`;

it("should csv header of the parsed result will be header's tuple", () => {
expectTypeOf(parse(csv1)).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse("" as CSVString<readonly ["name", "age", "city", "zip"]>),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse("" as CSV<readonly ["name", "age", "city", "zip"]>),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(parse(new ReadableStream<typeof csv1>())).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();
});
});

describe("csv literal string parsing with line breaks, quotation, newline", () => {
const csv1 = `$name$*$*ag
e
$*$city$*$z*i
p*$
Alice*24*New York*$1000
$1$
Bob*$36$*$Los$
Angeles$*90001`;

it("should csv header of the parsed result will be header's tuple", () => {
expectTypeOf(parse(csv1, { delimiter: "*", quotation: "$" })).toEqualTypeOf<
AsyncIterableIterator<
CSVRecord<readonly ["name", "*ag\ne\n", "city", "z*i\np*"]>
>
>();

expectTypeOf(
parse("" as CSVString<readonly ["name", "age\n", "city", "zi\np"]>),
).toEqualTypeOf<
AsyncIterableIterator<
CSVRecord<readonly ["name", "age\n", "city", "zi\np"]>
>
>();

expectTypeOf(
parse("" as CSV<readonly ["name", "age\n", "city", "zi\np"]>),
).toEqualTypeOf<
AsyncIterableIterator<
CSVRecord<readonly ["name", "age\n", "city", "zi\np"]>
>
>();

expectTypeOf(
parse(new ReadableStream<typeof csv1>(), {
delimiter: "*",
quotation: "$",
}),
).toEqualTypeOf<
AsyncIterableIterator<
CSVRecord<readonly ["name", "*ag\ne\n", "city", "z*i\np*"]>
>
>();
});
});

describe("generics", () => {
it("should CSV header of the parsed result should be the one specified in generics", () => {
expectTypeOf(
parse<readonly ["name", "age", "city", "zip"]>(""),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<readonly ["name", "age", "city", "zip"]>({} as CSVBinary),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<readonly ["name", "age", "city", "zip"]>({} as ReadableStream),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<readonly ["name", "age", "city", "zip"]>(
{} as ReadableStream<string>,
),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<readonly ["name", "age", "city", "zip"]>("" as CSVString),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<string, readonly ["name", "age", "city", "zip"]>(""),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<ReadableStream, readonly ["name", "age", "city", "zip"]>(
{} as ReadableStream,
),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<ReadableStream<string>, readonly ["name", "age", "city", "zip"]>(
{} as ReadableStream<string>,
),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<CSVString, readonly ["name", "age", "city", "zip"]>(
"" as CSVString,
),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<string, "#", "$", readonly ["name", "age", "city", "zip"]>("", {
delimiter: "#",
quotation: "$",
}),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<ReadableStream, "#", "$", readonly ["name", "age", "city", "zip"]>(
{} as ReadableStream,
{
delimiter: "#",
quotation: "$",
},
),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<
ReadableStream<string>,
"#",
"$",
readonly ["name", "age", "city", "zip"]
>({} as ReadableStream<string>, {
delimiter: "#",
quotation: "$",
}),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();

expectTypeOf(
parse<CSVString, "#", "$", readonly ["name", "age", "city", "zip"]>(
"" as CSVString,
{
delimiter: "#",
quotation: "$",
},
),
).toEqualTypeOf<
AsyncIterableIterator<CSVRecord<readonly ["name", "age", "city", "zip"]>>
>();
});
});
25 changes: 25 additions & 0 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import type {
ParseBinaryOptions,
ParseOptions,
} from "./common/types.ts";
import type { DEFAULT_DELIMITER, DEFAULT_QUOTATION } from "./constants.ts";
import { parseBinary } from "./parseBinary.ts";
import { parseResponse } from "./parseResponse.ts";
import { parseString } from "./parseString.ts";
import { parseStringStream } from "./parseStringStream.ts";
import { parseUint8ArrayStream } from "./parseUint8ArrayStream.ts";
import * as internal from "./utils/convertThisAsyncIterableIteratorToArray.ts";
import type { PickCSVHeader } from "./utils/types.ts";

/**
* Parse CSV to records.
Expand Down Expand Up @@ -118,6 +120,29 @@ import * as internal from "./utils/convertThisAsyncIterableIteratorToArray.ts";
* // { name: 'Bob', age: '69' }
* ```
*/
export function parse<
CSVSource extends CSVString,
Delimiter extends string = DEFAULT_DELIMITER,
Quotation extends string = DEFAULT_QUOTATION,
Header extends ReadonlyArray<string> = PickCSVHeader<
CSVSource,
Delimiter,
Quotation
>,
>(
csv: CSVSource,
options: ParseOptions<Header> & {
delimiter?: Delimiter;
quotation?: Quotation;
},
): AsyncIterableIterator<CSVRecord<Header>>;
export function parse<
CSVSource extends CSVString,
Header extends ReadonlyArray<string> = PickCSVHeader<CSVSource>,
>(
csv: CSVSource,
options?: ParseOptions<Header>,
): AsyncIterableIterator<CSVRecord<Header>>;
export function parse<Header extends ReadonlyArray<string>>(
csv: CSVString,
options?: ParseOptions<Header>,
Expand Down
2 changes: 1 addition & 1 deletion src/parseString.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe("parseString function", () => {

it("should throw an error if options is invalid", () => {
expect(async () => {
for await (const _ of parseString("", { delimiter: "" })) {
for await (const _ of parseString<"">("", { delimiter: "" })) {
// Do nothing.
}
}).rejects.toThrowErrorMatchingInlineSnapshot(
Expand Down
Loading
Loading