diff options
Diffstat (limited to 'std/encoding/csv_stringify.ts')
-rw-r--r-- | std/encoding/csv_stringify.ts | 172 |
1 files changed, 0 insertions, 172 deletions
diff --git a/std/encoding/csv_stringify.ts b/std/encoding/csv_stringify.ts deleted file mode 100644 index 921631d37..000000000 --- a/std/encoding/csv_stringify.ts +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -// Implements the CSV spec at https://tools.ietf.org/html/rfc4180 - -// This module is browser compatible. - -const QUOTE = '"'; -export const NEWLINE = "\r\n"; - -export class StringifyError extends Error { - readonly name = "StringifyError"; -} - -function getEscapedString(value: unknown, sep: string): string { - if (value === undefined || value === null) return ""; - let str = ""; - - if (typeof value === "object") str = JSON.stringify(value); - else str = String(value); - - // Is regex.test more performant here? If so, how to dynamically create? - // https://stackoverflow.com/questions/3561493/ - if (str.includes(sep) || str.includes(NEWLINE) || str.includes(QUOTE)) { - return `${QUOTE}${str.replaceAll(QUOTE, `${QUOTE}${QUOTE}`)}${QUOTE}`; - } - - return str; -} - -type PropertyAccessor = number | string; - -/** - * @param fn Optional callback for transforming the value - * - * @param header Explicit column header name. If omitted, - * the (final) property accessor is used for this value. - * - * @param prop Property accessor(s) used to access the value on the object - */ -export type ColumnDetails = { - // "unknown" is more type-safe, but inconvenient for user. How to resolve? - // deno-lint-ignore no-explicit-any - fn?: (value: any) => string | Promise<string>; - header?: string; - prop: PropertyAccessor | PropertyAccessor[]; -}; - -export type Column = ColumnDetails | PropertyAccessor | PropertyAccessor[]; - -type NormalizedColumn = Omit<ColumnDetails, "header" | "prop"> & { - header: string; - prop: PropertyAccessor[]; -}; - -function normalizeColumn(column: Column): NormalizedColumn { - let fn: NormalizedColumn["fn"], - header: NormalizedColumn["header"], - prop: NormalizedColumn["prop"]; - - if (typeof column === "object") { - if (Array.isArray(column)) { - header = String(column[column.length - 1]); - prop = column; - } else { - ({ fn } = column); - prop = Array.isArray(column.prop) ? column.prop : [column.prop]; - header = typeof column.header === "string" - ? column.header - : String(prop[prop.length - 1]); - } - } else { - header = String(column); - prop = [column]; - } - - return { fn, header, prop }; -} - -type ObjectWithStringPropertyKeys = Record<string, unknown>; - -/** An object (plain or array) */ -export type DataItem = ObjectWithStringPropertyKeys | unknown[]; - -/** - * Returns an array of values from an object using the property accessors - * (and optional transform function) in each column - */ -async function getValuesFromItem( - item: DataItem, - normalizedColumns: NormalizedColumn[], -): Promise<unknown[]> { - const values: unknown[] = []; - - for (const column of normalizedColumns) { - let value: unknown = item; - - for (const prop of column.prop) { - if (typeof value !== "object" || value === null) continue; - if (Array.isArray(value)) { - if (typeof prop === "number") value = value[prop]; - else { - throw new StringifyError('Property accessor is not of type "number"'); - } - } // I think this assertion is safe. Confirm? - else value = (value as ObjectWithStringPropertyKeys)[prop]; - } - - if (typeof column.fn === "function") value = await column.fn(value); - values.push(value); - } - - return values; -} - -/** - * @param headers Whether or not to include the row of headers. - * Default: `true` - * - * @param separator Delimiter used to separate values. Examples: - * - `","` _comma_ (Default) - * - `"\t"` _tab_ - * - `"|"` _pipe_ - * - etc. - */ -export type StringifyOptions = { - headers?: boolean; - separator?: string; -}; - -/** - * @param data The array of objects to encode - * @param columns Array of values specifying which data to include in the output - * @param options Output formatting options - */ -export async function stringify( - data: DataItem[], - columns: Column[], - options: StringifyOptions = {}, -): Promise<string> { - const { headers, separator: sep } = { - headers: true, - separator: ",", - ...options, - }; - if (sep.includes(QUOTE) || sep.includes(NEWLINE)) { - const message = [ - "Separator cannot include the following strings:", - ' - U+0022: Quotation mark (")', - " - U+000D U+000A: Carriage Return + Line Feed (\\r\\n)", - ].join("\n"); - throw new StringifyError(message); - } - - const normalizedColumns = columns.map(normalizeColumn); - let output = ""; - - if (headers) { - output += normalizedColumns - .map((column) => getEscapedString(column.header, sep)) - .join(sep); - output += NEWLINE; - } - - for (const item of data) { - const values = await getValuesFromItem(item, normalizedColumns); - output += values - .map((value) => getEscapedString(value, sep)) - .join(sep); - output += NEWLINE; - } - - return output; -} |