utilful 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -7
- package/dist/{csv.d.ts → csv.d.mts} +13 -3
- package/dist/csv.mjs +119 -0
- package/dist/{defu.d.ts → defu.d.mts} +0 -1
- package/dist/{emitter.d.ts → emitter.d.mts} +0 -4
- package/dist/index.d.mts +12 -0
- package/dist/{index.js → index.mjs} +10 -10
- package/dist/{types.d.ts → types.d.mts} +5 -1
- package/dist/types.mjs +1 -0
- package/package.json +9 -9
- package/dist/csv.js +0 -98
- package/dist/index.d.ts +0 -12
- package/dist/types.js +0 -0
- /package/dist/{array.d.ts → array.d.mts} +0 -0
- /package/dist/{array.js → array.mjs} +0 -0
- /package/dist/{defu.js → defu.mjs} +0 -0
- /package/dist/{emitter.js → emitter.mjs} +0 -0
- /package/dist/{json.d.ts → json.d.mts} +0 -0
- /package/dist/{json.js → json.mjs} +0 -0
- /package/dist/{module.d.ts → module.d.mts} +0 -0
- /package/dist/{module.js → module.mjs} +0 -0
- /package/dist/{object.d.ts → object.d.mts} +0 -0
- /package/dist/{object.js → object.mjs} +0 -0
- /package/dist/{path.d.ts → path.d.mts} +0 -0
- /package/dist/{path.js → path.mjs} +0 -0
- /package/dist/{result.d.ts → result.d.mts} +0 -0
- /package/dist/{result.js → result.mjs} +0 -0
- /package/dist/{string.d.ts → string.d.mts} +0 -0
- /package/dist/{string.js → string.mjs} +0 -0
package/README.md
CHANGED
|
@@ -55,14 +55,16 @@ Converts an array of objects to a comma-separated values (CSV) string that conta
|
|
|
55
55
|
```ts
|
|
56
56
|
declare function createCSV<T extends Record<string, unknown>>(
|
|
57
57
|
data: T[],
|
|
58
|
-
columns: (keyof T)[],
|
|
58
|
+
columns: readonly (keyof T)[],
|
|
59
59
|
options?: {
|
|
60
60
|
/** @default ',' */
|
|
61
61
|
delimiter?: string
|
|
62
62
|
/** @default true */
|
|
63
|
-
|
|
63
|
+
addHeader?: boolean
|
|
64
64
|
/** @default false */
|
|
65
65
|
quoteAll?: boolean
|
|
66
|
+
/** @default '\n' */
|
|
67
|
+
lineEnding?: string
|
|
66
68
|
}
|
|
67
69
|
): string
|
|
68
70
|
```
|
|
@@ -94,10 +96,18 @@ type CSVRow<T extends string = string> = Record<T, string>
|
|
|
94
96
|
declare function parseCSV<Header extends string>(
|
|
95
97
|
csv?: string | null | undefined,
|
|
96
98
|
options?: {
|
|
97
|
-
|
|
99
|
+
/** @default ',' */
|
|
98
100
|
delimiter?: string
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Trim whitespace from headers and values.
|
|
103
|
+
* @default true
|
|
104
|
+
*/
|
|
105
|
+
trim?: boolean
|
|
106
|
+
/**
|
|
107
|
+
* Throw error if row has more fields than headers.
|
|
108
|
+
* @default true
|
|
109
|
+
*/
|
|
110
|
+
strict?: boolean
|
|
101
111
|
}
|
|
102
112
|
): CSVRow<Header>[]
|
|
103
113
|
```
|
|
@@ -105,9 +115,11 @@ declare function parseCSV<Header extends string>(
|
|
|
105
115
|
**Example:**
|
|
106
116
|
|
|
107
117
|
```ts
|
|
108
|
-
const csv = `
|
|
118
|
+
const csv = `
|
|
119
|
+
name,age
|
|
109
120
|
John,30
|
|
110
|
-
Jane,25
|
|
121
|
+
Jane,25
|
|
122
|
+
`
|
|
111
123
|
|
|
112
124
|
const data = parseCSV<'name' | 'age'>(csv) // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
113
125
|
```
|
|
@@ -18,13 +18,15 @@ type CSVRow<T extends string = string> = Record<T, string>;
|
|
|
18
18
|
* // John,30
|
|
19
19
|
* // Jane,25
|
|
20
20
|
*/
|
|
21
|
-
declare function createCSV<T extends Record<string, unknown>>(data: T[], columns: (keyof T)[], options?: {
|
|
21
|
+
declare function createCSV<T extends Record<string, unknown>>(data: T[], columns: readonly (keyof T)[], options?: {
|
|
22
22
|
/** @default ',' */
|
|
23
23
|
delimiter?: string;
|
|
24
24
|
/** @default true */
|
|
25
25
|
addHeader?: boolean;
|
|
26
26
|
/** @default false */
|
|
27
27
|
quoteAll?: boolean;
|
|
28
|
+
/** @default '\n' */
|
|
29
|
+
lineEnding?: string;
|
|
28
30
|
}): string;
|
|
29
31
|
/**
|
|
30
32
|
* Escapes a value for a CSV string.
|
|
@@ -61,8 +63,16 @@ declare function escapeCSVValue(value: unknown, options?: {
|
|
|
61
63
|
declare function parseCSV<Header extends string>(csv?: string | null | undefined, options?: {
|
|
62
64
|
/** @default ',' */
|
|
63
65
|
delimiter?: string;
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Trim whitespace from headers and values.
|
|
68
|
+
* @default true
|
|
69
|
+
*/
|
|
70
|
+
trim?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Throw error if row has more fields than headers.
|
|
73
|
+
* @default true
|
|
74
|
+
*/
|
|
75
|
+
strict?: boolean;
|
|
66
76
|
}): CSVRow<Header>[];
|
|
67
77
|
//#endregion
|
|
68
78
|
export { CSVRow, createCSV, escapeCSVValue, parseCSV };
|
package/dist/csv.mjs
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
//#region src/csv.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts an array of objects to a comma-separated values (CSV) string
|
|
4
|
+
* that contains only the `columns` specified.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const data = [
|
|
8
|
+
* { name: 'John', age: '30', city: 'New York' },
|
|
9
|
+
* { name: 'Jane', age: '25', city: 'Boston' }
|
|
10
|
+
* ]
|
|
11
|
+
*
|
|
12
|
+
* const csv = createCSV(data, ['name', 'age'])
|
|
13
|
+
* // name,age
|
|
14
|
+
* // John,30
|
|
15
|
+
* // Jane,25
|
|
16
|
+
*/
|
|
17
|
+
function createCSV(data, columns, options = {}) {
|
|
18
|
+
const { delimiter = ",", addHeader = true, quoteAll = false, lineEnding = "\n" } = options;
|
|
19
|
+
const formatCell = (value) => escapeCSVValue(value, {
|
|
20
|
+
delimiter,
|
|
21
|
+
quoteAll
|
|
22
|
+
});
|
|
23
|
+
const rows = data.map((obj) => columns.map((key) => formatCell(obj[key])).join(delimiter));
|
|
24
|
+
if (addHeader) rows.unshift(columns.map(formatCell).join(delimiter));
|
|
25
|
+
return rows.join(lineEnding);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Escapes a value for a CSV string.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Returns an empty string if the value is `null` or `undefined`.
|
|
32
|
+
* Values containing delimiters, quotes, or line breaks are quoted.
|
|
33
|
+
* Within quoted values, double quotes are escaped by doubling them.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* escapeCSVValue('hello, world') // "hello, world"
|
|
37
|
+
* escapeCSVValue('contains "quotes"') // "contains ""quotes"""
|
|
38
|
+
*/
|
|
39
|
+
function escapeCSVValue(value, options = {}) {
|
|
40
|
+
const { delimiter = ",", quoteAll = false } = options;
|
|
41
|
+
if (value == null) return "";
|
|
42
|
+
const coercedValue = String(value);
|
|
43
|
+
if (quoteAll || coercedValue.includes(delimiter) || coercedValue.includes("\"") || coercedValue.includes("\n") || coercedValue.includes("\r")) return `"${coercedValue.replaceAll("\"", "\"\"")}"`;
|
|
44
|
+
return coercedValue;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parses a comma-separated values (CSV) string into an array of objects.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* The first row of the CSV string is used as the header row.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const csv = `name,age
|
|
54
|
+
* John,30
|
|
55
|
+
* Jane,25`
|
|
56
|
+
*
|
|
57
|
+
* const data = parseCSV<'name' | 'age'>(csv)
|
|
58
|
+
* // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
59
|
+
*/
|
|
60
|
+
function parseCSV(csv, options = {}) {
|
|
61
|
+
if (!csv?.trim()) return [];
|
|
62
|
+
const rows = [];
|
|
63
|
+
let currentRow = [];
|
|
64
|
+
let currentField = "";
|
|
65
|
+
let inQuotes = false;
|
|
66
|
+
const { delimiter = ",", trim = true, strict = true } = options;
|
|
67
|
+
const appendField = () => {
|
|
68
|
+
currentRow.push(currentField);
|
|
69
|
+
currentField = "";
|
|
70
|
+
};
|
|
71
|
+
const appendRow = () => {
|
|
72
|
+
appendField();
|
|
73
|
+
rows.push(currentRow);
|
|
74
|
+
currentRow = [];
|
|
75
|
+
};
|
|
76
|
+
for (let i = 0; i < csv.length; i++) {
|
|
77
|
+
const character = csv[i];
|
|
78
|
+
const nextCharacter = i + 1 < csv.length ? csv[i + 1] : "";
|
|
79
|
+
if (character === "\"") if (inQuotes && nextCharacter === "\"") {
|
|
80
|
+
currentField += "\"";
|
|
81
|
+
i++;
|
|
82
|
+
} else inQuotes = !inQuotes;
|
|
83
|
+
else if (character === delimiter && !inQuotes) appendField();
|
|
84
|
+
else if ((character === "\n" || character === "\r" && nextCharacter === "\n") && !inQuotes) {
|
|
85
|
+
if (character === "\r") i++;
|
|
86
|
+
appendRow();
|
|
87
|
+
} else currentField += character;
|
|
88
|
+
}
|
|
89
|
+
if (currentField || currentRow.length > 0) appendRow();
|
|
90
|
+
if (rows.length <= 1) return [];
|
|
91
|
+
const [headerRow] = rows;
|
|
92
|
+
if (!headerRow) return [];
|
|
93
|
+
const headers = trim ? headerRow.map((h) => h.trim()) : headerRow;
|
|
94
|
+
if (headers.filter((h) => h.length === 0).length > 0) {
|
|
95
|
+
const positions = headers.map((h, i) => h.length === 0 ? i + 1 : -1).filter((i) => i > 0).join(", ");
|
|
96
|
+
throw new SyntaxError(`CSV header row contains empty column name(s) at position(s): ${positions}`);
|
|
97
|
+
}
|
|
98
|
+
const duplicateHeaderNames = headers.filter((h, i) => headers.indexOf(h) !== i);
|
|
99
|
+
if (duplicateHeaderNames.length > 0) throw new SyntaxError(`CSV header row contains duplicate column name(s): ${[...new Set(duplicateHeaderNames)].join(", ")}`);
|
|
100
|
+
const isFieldPopulated = trim ? (field) => field.trim().length > 0 : (field) => field.length > 0;
|
|
101
|
+
return rows.slice(1).filter((row) => row.length > 1 || row.some(isFieldPopulated)).map((fieldValues, rowIndex) => {
|
|
102
|
+
if (fieldValues.length > headers.length) {
|
|
103
|
+
const containsNonEmptyOverflow = fieldValues.slice(headers.length).some(isFieldPopulated);
|
|
104
|
+
if (strict && containsNonEmptyOverflow) {
|
|
105
|
+
const expectedCount = headers.length;
|
|
106
|
+
const actualCount = fieldValues.length;
|
|
107
|
+
const excessCount = actualCount - expectedCount;
|
|
108
|
+
throw new SyntaxError(`CSV row ${rowIndex + 2} has ${excessCount} extra field(s): expected ${expectedCount} column(s), found ${actualCount}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return Object.fromEntries(headers.map((header, columnIndex) => {
|
|
112
|
+
const untrimmedValue = columnIndex < fieldValues.length ? fieldValues[columnIndex] ?? "" : "";
|
|
113
|
+
return [header, trim ? untrimmedValue.trim() : untrimmedValue];
|
|
114
|
+
}));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
export { createCSV, escapeCSVValue, parseCSV };
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
//#region src/emitter.d.ts
|
|
2
|
-
/* eslint-disable ts/method-signature-style */
|
|
3
2
|
type EventType = string | symbol;
|
|
4
|
-
// An event handler can take an optional event argument
|
|
5
|
-
// and should not return a value
|
|
6
3
|
type Handler<T = unknown> = (event: T) => void;
|
|
7
4
|
type WildcardHandler<T = Record<string, unknown>> = (type: keyof T, event: T[keyof T]) => void;
|
|
8
5
|
type EventHandlerList<T = unknown> = Handler<T>[];
|
|
9
6
|
type WildCardEventHandlerList<T = Record<string, unknown>> = WildcardHandler<T>[];
|
|
10
|
-
// A map of event types and their corresponding event handlers.
|
|
11
7
|
type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<keyof Events | "*", EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>>;
|
|
12
8
|
interface Emitter<Events extends Record<EventType, unknown>> {
|
|
13
9
|
events: EventHandlerMap<Events>;
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MaybeArray, toArray } from "./array.mjs";
|
|
2
|
+
import { CSVRow, createCSV, escapeCSVValue, parseCSV } from "./csv.mjs";
|
|
3
|
+
import { DefuFn, DefuMerger, createDefu, defu } from "./defu.mjs";
|
|
4
|
+
import { Emitter, EventHandlerList, EventHandlerMap, EventType, Handler, WildCardEventHandlerList, WildcardHandler, createEmitter } from "./emitter.mjs";
|
|
5
|
+
import { cloneJSON, tryParseJSON } from "./json.mjs";
|
|
6
|
+
import { interopDefault } from "./module.mjs";
|
|
7
|
+
import { deepApply, memoize, objectEntries, objectKeys } from "./object.mjs";
|
|
8
|
+
import { QueryObject, QueryValue, getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.mjs";
|
|
9
|
+
import { Err, ErrData, Ok, OkData, Result, ResultData, err, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
|
|
10
|
+
import { generateRandomId, template } from "./string.mjs";
|
|
11
|
+
import { AutocompletableString, BrandedType, LooseAutocomplete, UnifyIntersection } from "./types.mjs";
|
|
12
|
+
export { AutocompletableString, BrandedType, CSVRow, DefuFn, DefuMerger, Emitter, Err, ErrData, EventHandlerList, EventHandlerMap, EventType, Handler, LooseAutocomplete, MaybeArray, Ok, OkData, QueryObject, QueryValue, Result, ResultData, UnifyIntersection, WildCardEventHandlerList, WildcardHandler, cloneJSON, createCSV, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { toArray } from "./array.
|
|
2
|
-
import { createCSV, escapeCSVValue, parseCSV } from "./csv.
|
|
3
|
-
import { createDefu, defu } from "./defu.
|
|
4
|
-
import { createEmitter } from "./emitter.
|
|
5
|
-
import { cloneJSON, tryParseJSON } from "./json.
|
|
6
|
-
import { interopDefault } from "./module.
|
|
7
|
-
import { deepApply, memoize, objectEntries, objectKeys } from "./object.
|
|
8
|
-
import { getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.
|
|
9
|
-
import { Err, Ok, err, ok, toResult, tryCatch, unwrapResult } from "./result.
|
|
10
|
-
import { generateRandomId, template } from "./string.
|
|
1
|
+
import { toArray } from "./array.mjs";
|
|
2
|
+
import { createCSV, escapeCSVValue, parseCSV } from "./csv.mjs";
|
|
3
|
+
import { createDefu, defu } from "./defu.mjs";
|
|
4
|
+
import { createEmitter } from "./emitter.mjs";
|
|
5
|
+
import { cloneJSON, tryParseJSON } from "./json.mjs";
|
|
6
|
+
import { interopDefault } from "./module.mjs";
|
|
7
|
+
import { deepApply, memoize, objectEntries, objectKeys } from "./object.mjs";
|
|
8
|
+
import { getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.mjs";
|
|
9
|
+
import { Err, Ok, err, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
|
|
10
|
+
import { generateRandomId, template } from "./string.mjs";
|
|
11
11
|
|
|
12
12
|
export { Err, Ok, cloneJSON, createCSV, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|
|
@@ -3,5 +3,9 @@ type AutocompletableString = string & {};
|
|
|
3
3
|
type LooseAutocomplete<T extends string> = T | AutocompletableString;
|
|
4
4
|
/** Also commonly referred to as `Prettify` */
|
|
5
5
|
type UnifyIntersection<T> = { [K in keyof T]: T[K] } & {};
|
|
6
|
+
declare const brand: unique symbol;
|
|
7
|
+
type BrandedType<T, B> = T & {
|
|
8
|
+
[brand]: B;
|
|
9
|
+
};
|
|
6
10
|
//#endregion
|
|
7
|
-
export { AutocompletableString, LooseAutocomplete, UnifyIntersection };
|
|
11
|
+
export { AutocompletableString, BrandedType, LooseAutocomplete, UnifyIntersection };
|
package/dist/types.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "utilful",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
5
|
-
"packageManager": "pnpm@10.
|
|
4
|
+
"version": "2.2.0",
|
|
5
|
+
"packageManager": "pnpm@10.21.0",
|
|
6
6
|
"description": "A collection of TypeScript utilities",
|
|
7
7
|
"author": "Johann Schopplich <hello@johannschopplich.com>",
|
|
8
8
|
"license": "MIT",
|
|
@@ -78,13 +78,13 @@
|
|
|
78
78
|
"release": "bumpp"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@antfu/eslint-config": "^
|
|
82
|
-
"@types/node": "^
|
|
83
|
-
"bumpp": "^10.
|
|
84
|
-
"eslint": "^9.
|
|
85
|
-
"tsdown": "^0.
|
|
86
|
-
"typescript": "^5.
|
|
87
|
-
"vitest": "^
|
|
81
|
+
"@antfu/eslint-config": "^6.2.0",
|
|
82
|
+
"@types/node": "^24.10.0",
|
|
83
|
+
"bumpp": "^10.3.1",
|
|
84
|
+
"eslint": "^9.39.1",
|
|
85
|
+
"tsdown": "^0.16.1",
|
|
86
|
+
"typescript": "^5.9.3",
|
|
87
|
+
"vitest": "^4.0.8"
|
|
88
88
|
},
|
|
89
89
|
"pnpm": {
|
|
90
90
|
"onlyBuiltDependencies": [
|
package/dist/csv.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
//#region src/csv.ts
|
|
2
|
-
/**
|
|
3
|
-
* Converts an array of objects to a comma-separated values (CSV) string
|
|
4
|
-
* that contains only the `columns` specified.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* const data = [
|
|
8
|
-
* { name: 'John', age: '30', city: 'New York' },
|
|
9
|
-
* { name: 'Jane', age: '25', city: 'Boston' }
|
|
10
|
-
* ]
|
|
11
|
-
*
|
|
12
|
-
* const csv = createCSV(data, ['name', 'age'])
|
|
13
|
-
* // name,age
|
|
14
|
-
* // John,30
|
|
15
|
-
* // Jane,25
|
|
16
|
-
*/
|
|
17
|
-
function createCSV(data, columns, options = {}) {
|
|
18
|
-
const { delimiter = ",", addHeader = true, quoteAll = false } = options;
|
|
19
|
-
const escapeAndQuote = (value) => escapeCSVValue(value, {
|
|
20
|
-
delimiter,
|
|
21
|
-
quoteAll
|
|
22
|
-
});
|
|
23
|
-
const rows = data.map((obj) => columns.map((key) => escapeAndQuote(obj[key])).join(delimiter));
|
|
24
|
-
if (addHeader) rows.unshift(columns.map(escapeAndQuote).join(delimiter));
|
|
25
|
-
return rows.join("\n");
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Escapes a value for a CSV string.
|
|
29
|
-
*
|
|
30
|
-
* @remarks
|
|
31
|
-
* Returns an empty string if the value is `null` or `undefined`.
|
|
32
|
-
* Values containing delimiters, quotes, or line breaks are quoted.
|
|
33
|
-
* Within quoted values, double quotes are escaped by doubling them.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* escapeCSVValue('hello, world') // "hello, world"
|
|
37
|
-
* escapeCSVValue('contains "quotes"') // "contains ""quotes"""
|
|
38
|
-
*/
|
|
39
|
-
function escapeCSVValue(value, options = {}) {
|
|
40
|
-
const { delimiter = ",", quoteAll = false } = options;
|
|
41
|
-
if (value == null) return "";
|
|
42
|
-
const stringValue = String(value);
|
|
43
|
-
const needsQuoting = quoteAll || stringValue.includes(delimiter) || stringValue.includes("\"") || stringValue.includes("\n") || stringValue.includes("\r");
|
|
44
|
-
if (needsQuoting) return `"${stringValue.replaceAll("\"", "\"\"")}"`;
|
|
45
|
-
return stringValue;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Parses a comma-separated values (CSV) string into an array of objects.
|
|
49
|
-
*
|
|
50
|
-
* @remarks
|
|
51
|
-
* The first row of the CSV string is used as the header row.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* const csv = `name,age
|
|
55
|
-
* John,30
|
|
56
|
-
* Jane,25`
|
|
57
|
-
*
|
|
58
|
-
* const data = parseCSV<'name' | 'age'>(csv)
|
|
59
|
-
* // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
60
|
-
*/
|
|
61
|
-
function parseCSV(csv, options = {}) {
|
|
62
|
-
if (!csv?.trim()) return [];
|
|
63
|
-
const rows = [];
|
|
64
|
-
let currentRow = [];
|
|
65
|
-
let currentField = "";
|
|
66
|
-
let inQuotes = false;
|
|
67
|
-
const { delimiter = ",", trimValues = true } = options;
|
|
68
|
-
for (let i = 0; i < csv.length; i++) {
|
|
69
|
-
const char = csv[i];
|
|
70
|
-
const nextChar = i + 1 < csv.length ? csv[i + 1] : "";
|
|
71
|
-
if (char === "\"") if (inQuotes && nextChar === "\"") {
|
|
72
|
-
currentField += "\"";
|
|
73
|
-
i++;
|
|
74
|
-
} else inQuotes = !inQuotes;
|
|
75
|
-
else if (char === delimiter && !inQuotes) {
|
|
76
|
-
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
77
|
-
currentField = "";
|
|
78
|
-
} else if ((char === "\n" || char === "\r" && nextChar === "\n") && !inQuotes) {
|
|
79
|
-
if (char === "\r") i++;
|
|
80
|
-
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
81
|
-
rows.push(currentRow);
|
|
82
|
-
currentRow = [];
|
|
83
|
-
currentField = "";
|
|
84
|
-
} else currentField += char;
|
|
85
|
-
}
|
|
86
|
-
if (currentField || currentRow.length > 0) {
|
|
87
|
-
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
88
|
-
rows.push(currentRow);
|
|
89
|
-
}
|
|
90
|
-
if (rows.length <= 1) return [];
|
|
91
|
-
const headers = rows[0];
|
|
92
|
-
return rows.slice(1).filter((row) => row.some((field) => field.trim().length > 0)).map((values) => {
|
|
93
|
-
return Object.fromEntries(headers.map((header, index) => [header, index < values.length ? values[index] : ""]));
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
//#endregion
|
|
98
|
-
export { createCSV, escapeCSVValue, parseCSV };
|
package/dist/index.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { MaybeArray, toArray } from "./array.js";
|
|
2
|
-
import { CSVRow, createCSV, escapeCSVValue, parseCSV } from "./csv.js";
|
|
3
|
-
import { DefuFn, DefuMerger, createDefu, defu } from "./defu.js";
|
|
4
|
-
import { Emitter, EventHandlerList, EventHandlerMap, EventType, Handler, WildCardEventHandlerList, WildcardHandler, createEmitter } from "./emitter.js";
|
|
5
|
-
import { cloneJSON, tryParseJSON } from "./json.js";
|
|
6
|
-
import { interopDefault } from "./module.js";
|
|
7
|
-
import { deepApply, memoize, objectEntries, objectKeys } from "./object.js";
|
|
8
|
-
import { QueryObject, QueryValue, getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.js";
|
|
9
|
-
import { Err, ErrData, Ok, OkData, Result, ResultData, err, ok, toResult, tryCatch, unwrapResult } from "./result.js";
|
|
10
|
-
import { generateRandomId, template } from "./string.js";
|
|
11
|
-
import { AutocompletableString, LooseAutocomplete, UnifyIntersection } from "./types.js";
|
|
12
|
-
export { AutocompletableString, CSVRow, DefuFn, DefuMerger, Emitter, Err, ErrData, EventHandlerList, EventHandlerMap, EventType, Handler, LooseAutocomplete, MaybeArray, Ok, OkData, QueryObject, QueryValue, Result, ResultData, UnifyIntersection, WildCardEventHandlerList, WildcardHandler, cloneJSON, createCSV, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|
package/dist/types.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|