From 9f3cceb3114fcdf71fe1bfbc6da4acb885609641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Angel=20Gomez?= Date: Sat, 30 Dec 2023 17:15:27 -0500 Subject: [PATCH] Finish version 1.7.1 --- rollup.config.js | 2 +- .../Export/generators/Csv/CsvGenerator.ts | 111 ++++++++++ src/core/Export/generators/Csv/core/types.ts | 205 ++++++++++++++++++ .../Export/generators/Json/JsonGenerator.ts | 77 +++++++ 4 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 src/core/Export/generators/Csv/CsvGenerator.ts create mode 100644 src/core/Export/generators/Csv/core/types.ts create mode 100644 src/core/Export/generators/Json/JsonGenerator.ts diff --git a/rollup.config.js b/rollup.config.js index 3582433..18ec39a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,4 @@ -import { terser } from "rollup-plugin-terser"; +// import { terser } from "rollup-plugin-terser"; import pluginTypescript from "@rollup/plugin-typescript"; import pluginCommonjs from "@rollup/plugin-commonjs"; import pluginNodeResolve from "@rollup/plugin-node-resolve"; diff --git a/src/core/Export/generators/Csv/CsvGenerator.ts b/src/core/Export/generators/Csv/CsvGenerator.ts new file mode 100644 index 0000000..d9c1aed --- /dev/null +++ b/src/core/Export/generators/Csv/CsvGenerator.ts @@ -0,0 +1,111 @@ +import { Generator } from "../Generator/Generator"; +import fs from "fs"; +import path from "path"; +import { ChacaError } from "../../../../errors"; +import { MultiGenerateResolver } from "../../../MultiGenerate/MultiGenerateResolver"; +import { CSVArray, CSVDataType, CSVObject } from "./core/types"; + +interface Props { + fileName: string; + location: string; + zip?: boolean; +} + +export class CsvGenerator extends Generator { + private zip: boolean; + + constructor(config: Props) { + super({ + extension: "csv", + fileName: config.fileName, + location: config.location, + }); + + this.zip = Boolean(config.zip); + } + + public async generateRelationalDataFile( + resolver: MultiGenerateResolver, + ): Promise { + const allResolvers = resolver.getResolvers(); + + if (allResolvers.length === 1) { + const schemaData = allResolvers[0].resolve(); + const route = await this.createFile(this.fileName, schemaData); + + return route; + } else { + const allRoutes = [] as Array; + + for (const r of allResolvers) { + const schemaData = r.resolve(); + const route = await this.createFile(r.getSchemaName(), schemaData); + allRoutes.push(route); + } + + if (this.zip) { + const { zip, zipPath } = this.createZip(); + + for (const route of allRoutes) { + zip.addLocalFile(route); + await fs.promises.unlink(route); + } + + zip.writeZip(zipPath); + + return zipPath; + } else { + return this.baseLocation; + } + } + } + + public async generateFile(data: any): Promise { + const fileRoute = await this.createFile(this.fileName, data); + + if (this.zip) { + const { zip, zipPath } = this.createZip(); + + zip.addLocalFile(fileRoute); + zip.writeZip(zipPath); + + await fs.promises.unlink(fileRoute); + + return zipPath; + } else { + return fileRoute; + } + } + + private async createFile(filename: string, data: any): Promise { + const fileRoute = path.join(this.baseLocation, `${filename}.csv`); + + const dataType = CSVDataType.filterTypeByValue(data); + + let content = ""; + + // array + if (dataType instanceof CSVArray) { + content = dataType.getCSVValue(); + } + + // object + else if (dataType instanceof CSVObject) { + const array = [dataType.object]; + const newArrayType = new CSVArray(array); + + content = newArrayType.getCSVValue(); + } + + // other + else { + throw new ChacaError( + `Your export data must be an array of objects or a single object.`, + ); + } + + await fs.promises.writeFile(fileRoute, content, "utf-8"); + + return fileRoute; + } +} diff --git a/src/core/Export/generators/Csv/core/types.ts b/src/core/Export/generators/Csv/core/types.ts new file mode 100644 index 0000000..7a6a32f --- /dev/null +++ b/src/core/Export/generators/Csv/core/types.ts @@ -0,0 +1,205 @@ +import { ChacaError } from "../../../../../errors"; + +interface ObjectKey { + key: string; + type: CSVDataType; +} + +export abstract class CSVDataType { + public abstract getCSVValue(): string; + + public static filterTypeByValue(value: any): CSVDataType { + let type: CSVDataType = new CSVNull(); + + if (typeof value === "string") { + type = new CSVString(value); + } else if (typeof value === "number") { + type = new CSVNumber(value); + } else if (typeof value === "boolean") { + type = new CSVBoolean(value); + } else if (typeof value === "bigint") { + type = new CSVBigint(value); + } else if (typeof value === "undefined") { + type = new CSVNull(); + } else if (typeof value === "function") { + throw new ChacaError(`You can not export a function in a csv file.`); + } else if (typeof value === "symbol") { + throw new ChacaError(`You can not export a Symbol in a csv file.`); + } else if (typeof value === "object") { + if (value instanceof Date) { + type = new CSVDate(value); + } else if (Array.isArray(value)) { + type = new CSVArray(value); + } else if (value === null) { + type = new CSVNull(); + } else { + type = new CSVObject(value); + } + } + + return type; + } +} + +export class CSVBoolean extends CSVDataType { + constructor(private readonly value: boolean) { + super(); + } + + public getCSVValue(): string { + if (this.value) return "TRUE"; + else return "FALSE"; + } +} + +export class CSVDate extends CSVDataType { + constructor(private readonly value: Date) { + super(); + } + + public getCSVValue(): string { + return this.value.toISOString(); + } +} + +export class CSVNull extends CSVDataType { + public getCSVValue(): string { + return "NULL"; + } +} + +export class CSVString extends CSVDataType { + constructor(private readonly value: string) { + super(); + } + + public getCSVValue(): string { + return JSON.stringify(this.value); + } +} + +export class CSVBigint extends CSVDataType { + constructor(private readonly value: bigint) { + super(); + } + + public getCSVValue(): string { + return this.value.toString(); + } +} + +export class CSVNumber extends CSVDataType { + constructor(private readonly value: number) { + super(); + } + + public getCSVValue(): string { + let retString: string; + + if (Number.isNaN(this.value)) { + retString = "NaN"; + } else if (this.value === Infinity) { + retString = "Infinity"; + } else if (this.value === -Infinity) { + retString = "-Infinity"; + } else { + retString = `${this.value}`; + } + + return retString; + } +} + +export class CSVArray extends CSVDataType { + private values: Array = []; + + constructor(array: Array) { + super(); + for (const v of array) { + const type = CSVDataType.filterTypeByValue(v); + + if ( + type instanceof CSVObject && + this.values.every((v) => type.equal(v)) + ) { + this.values.push(type); + } else { + throw new ChacaError(`Your objects array must have the same keys`); + } + } + } + + public getValues() { + return this.values; + } + + public getCSVValue(): string { + let content = ""; + + if (this.values.length > 0) { + const firstObject = this.values[0]; + const allKeys = firstObject.getKeys(); + + const header = allKeys.join(",") + "\n"; + + let allObjectsContent = ""; + for (const o of this.values) { + allObjectsContent += o.getCSVValue(); + } + + content = header + allObjectsContent; + } + + return content; + } +} + +export class CSVObject extends CSVDataType { + private keys: Array = []; + + constructor(public readonly object: any) { + super(); + + for (const [key, value] of Object.entries(object)) { + const type = CSVDataType.filterTypeByValue(value); + + if (type instanceof CSVArray) { + throw new ChacaError(`You can not insert an array into a CSV File`); + } else if (type instanceof CSVObject) { + throw new ChacaError( + `You can not insert a nested object into a CSV File`, + ); + } + + this.keys.push({ key, type }); + } + } + + public getCSVValue(): string { + const values = this.keys.map((k) => k.type.getCSVValue()); + return values.join(", ") + "\n"; + } + + public equal(otherObject: CSVObject): boolean { + let equal = true; + + if (this.keys.length === otherObject.keys.length) { + for (let i = 0; i < this.keys.length; i++) { + const key = this.keys[i]; + const exists = otherObject.keys.some((k) => k.key === key.key); + + if (!exists) { + equal = false; + } + } + } else { + equal = false; + } + + return equal; + } + + public getKeys() { + return this.keys.map((k) => k.key); + } +} diff --git a/src/core/Export/generators/Json/JsonGenerator.ts b/src/core/Export/generators/Json/JsonGenerator.ts new file mode 100644 index 0000000..209f472 --- /dev/null +++ b/src/core/Export/generators/Json/JsonGenerator.ts @@ -0,0 +1,77 @@ +import { MultiGenerateResolver } from "../../../MultiGenerate/MultiGenerateResolver"; +import { Generator } from "../Generator/Generator"; +import fs from "fs"; + +interface ExtProps { + separate?: boolean; + zip?: boolean; +} + +interface Props { + fileName: string; + location: string; + extConfig: ExtProps; +} + +export class JsonGenerator extends Generator { + private config: ExtProps; + + constructor({ extConfig, fileName, location }: Props) { + super({ + extension: "json", + fileName: fileName, + location: location, + }); + + this.config = extConfig; + } + + private async createFile(route: string, content: any): Promise { + const jsonContent = JSON.stringify(content); + await fs.promises.writeFile(route, jsonContent, "utf-8"); + } + + public async generateFile(data: any): Promise { + await this.createFile(this.route, data); + + if (this.config.zip) { + return this.createFileZip(); + } else { + return this.route; + } + } + + public async generateRelationalDataFile( + resolver: MultiGenerateResolver, + ): Promise { + const objectData = resolver.resolve(); + + if (this.config.separate) { + const allRoutes: Array = []; + + for (const [key, data] of Object.entries(objectData)) { + const route = this.generateRoute(key); + await this.createFile(route, data); + + allRoutes.push(route); + } + + if (this.config.zip) { + const { zip, zipPath } = this.createZip(); + + for (const route of allRoutes) { + zip.addLocalFile(route); + await fs.promises.unlink(route); + } + + zip.writeZip(zipPath); + + return zipPath; + } else { + return this.baseLocation; + } + } else { + return await this.generateFile(objectData); + } + } +}