summaryrefslogtreecommitdiff
path: root/std/encoding/csv_stringify.ts
diff options
context:
space:
mode:
Diffstat (limited to 'std/encoding/csv_stringify.ts')
-rw-r--r--std/encoding/csv_stringify.ts172
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;
-}