UNPKG

@paulof25/emoji-separated-values

Version:

emoji-separated-values (or simply ESV) is your new favorite npm package for handling text-based data. Forget about boring commas — we use emojis as delimiters. Because why not?

458 lines (449 loc) 14.4 kB
// src/infra/fs/FileSystemEsvRepository.ts import ReadLine from "readline"; import fs from "fs"; var FileSystemEsvRepository = class { //le o arquivo e retorna um stream async readEsvFile(filePath) { const stream = fs.createReadStream(filePath, { encoding: "utf-8" }); const reader = ReadLine.createInterface({ input: stream, crlfDelay: Infinity }); return reader; } //cria um stream de escrita getFileWriteStream(filePath, flags = "a") { const stream = fs.createWriteStream(filePath, { encoding: "utf-8", flags }); return stream; } //escreve um arquivo async writeEsvFile(filePath, data, separator, flags = "a") { const fileExists = await this.fileExists(filePath); const stream = fs.createWriteStream(filePath, { encoding: "utf-8", flags }); let lineCount = 0; if (!fileExists && data.length > 0) { const header = Object.keys(data[0]); const headerLine = header.map(this.escapeField).join(separator); stream.write(headerLine + "\n"); } for (const record of data) { const values = Object.values(record).map((value) => this.escapeField(String(value))); const line = values.join(separator); stream.write(line + "\n"); lineCount++; } stream.end(); return new Promise((resolve, reject) => { stream.on("finish", () => { console.log("Esv file written successfully"); return resolve(); }); stream.on("error", (error) => { return reject(error); }); }); } //funcao que renomeia um arquivo renameFile(oldPath, newPath) { fs.renameSync(oldPath, newPath); } //funcao que deleta um arquivo deleteFile(filePath) { fs.unlinkSync(filePath); } //funcao que escapa um campo que contenha as seguintes carateres: ", \n escapeField(valor) { if (/[",\n]/.test(valor)) { return `"${valor.replace(/"/g, '""')}"`; } return valor; } //função que divide uma linha em campos usando o emoji como separador splitEsvLine(line, separator) { return line.split(separator); } //transforma uma linha em um objeto EsvRecord parseEsvLine(line, header, separator) { const values = this.splitEsvLine(line, separator); const record = {}; for (let i = 0; i < header.length; i++) { record[header[i]] = values[i]; } return record; } //funcao que normaliza um valor sem acento normalizeValue(value) { const trimmedValue = value.trim(); const normalized = trimmedValue.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return normalized; } //funcao que verifica se um arquivo existe async fileExists(filePath) { try { fs.accessSync(filePath); return true; } catch { return false; } } }; // src/core/useCases/ReadEsv.ts var ReadEsv = class { repository; constructor(repository) { this.repository = repository; } async execute(filePath, separator, skip, limit) { const reader = await this.repository.readEsvFile(filePath); let lineCount = 0; let header = []; const records = []; for await (const line of reader) { if (lineCount === 0) { header = this.repository.splitEsvLine(line, separator).map((h) => this.repository.normalizeValue(h)); lineCount++; continue; } if (skip && skip > 0) { skip--; continue; } if (!line) { continue; } if (limit && lineCount > limit) { break; } lineCount++; const record = this.repository.parseEsvLine(line, header, separator); records.push(record); } reader.close(); return records; } }; // src/core/useCases/WriteEsv.ts var WriteEsv = class { repository; constructor(repository) { this.repository = repository; } async execute(filePath, data, separator) { return await this.repository.writeEsvFile(filePath, data, separator); } }; // src/core/entities/EsvFilter.ts var EsvFilterOperator = /* @__PURE__ */ ((EsvFilterOperator2) => { EsvFilterOperator2["Equals"] = "equals"; EsvFilterOperator2["NotEquals"] = "notEquals"; EsvFilterOperator2["Contains"] = "contains"; EsvFilterOperator2["NotContains"] = "notContains"; EsvFilterOperator2["StartsWith"] = "startsWith"; EsvFilterOperator2["NotStartsWith"] = "notStartsWith"; EsvFilterOperator2["EndsWith"] = "endsWith"; EsvFilterOperator2["NotEndsWith"] = "notEndsWith"; EsvFilterOperator2["GreaterThan"] = "greaterThan"; EsvFilterOperator2["LessThan"] = "lessThan"; EsvFilterOperator2["GreaterThanOrEqual"] = "greaterThanOrEqual"; EsvFilterOperator2["LessThanOrEqual"] = "lessThanOrEqual"; return EsvFilterOperator2; })(EsvFilterOperator || {}); // src/infra/ops/Operations.ts var Operations = class { filterRow(row, filters) { if (!filters) { return true; } let filtered = true; for (const filter of filters) { if (!this.switchOperations(filter.operator, filter, row)) { filtered = false; } } return filtered; } switchOperations(operation, filter, row) { switch (operation) { case "equals" /* Equals */: return row[filter.field] === filter.value; break; case "notEquals" /* NotEquals */: return row[filter.field] !== filter.value; break; case "contains" /* Contains */: const value = row[filter.field]; if (typeof value === "string") return value.includes(filter.value.toString()); break; case "notContains" /* NotContains */: const value2 = row[filter.field]; if (typeof value2 === "string") return !value2.includes(filter.value.toString()); break; case "startsWith" /* StartsWith */: const value3 = row[filter.field]; if (typeof value3 === "string") return value3.startsWith(filter.value.toString()); break; case "notStartsWith" /* NotStartsWith */: const value4 = row[filter.field]; if (typeof value4 === "string") return !value4.startsWith(filter.value.toString()); break; case "endsWith" /* EndsWith */: const value5 = row[filter.field]; if (typeof value5 === "string") return value5.endsWith(filter.value.toString()); break; case "notEndsWith" /* NotEndsWith */: const value6 = row[filter.field]; if (typeof value6 === "string") return !value6.endsWith(filter.value.toString()); break; case "greaterThan" /* GreaterThan */: const res = this.verifyNAN(row[filter.field], filter.value); if (!res) return false; return res.first > res.second; break; case "lessThan" /* LessThan */: const res2 = this.verifyNAN(row[filter.field], filter.value); if (!res2) return false; return res2.first < res2.second; break; case "greaterThanOrEqual" /* GreaterThanOrEqual */: const res3 = this.verifyNAN(row[filter.field], filter.value); if (!res3) return false; return res3.first >= res3.second; break; case "lessThanOrEqual" /* LessThanOrEqual */: const res4 = this.verifyNAN(row[filter.field], filter.value); if (!res4) return false; return res4.first <= res4.second; break; } } verifyNAN(first, second) { const firstNumber = parseFloat(first.toString()); const secondNumber = parseFloat(second.toString()); if (isNaN(firstNumber) || isNaN(secondNumber)) { return null; } return { first: firstNumber, second: secondNumber }; } }; // src/core/useCases/FilterEsv.ts var FilterEsv = class { repository; operations; constructor(repository) { this.repository = repository; this.operations = new Operations(); } async execute(filePath, separator, skip, limit, filters) { const reader = await this.repository.readEsvFile(filePath); let lineCount = 0; let header = []; const records = []; for await (const line of reader) { if (lineCount === 0) { header = this.repository.splitEsvLine(line, separator).map((h) => this.repository.normalizeValue(h)); lineCount++; continue; } if (!line) { continue; } const record = this.repository.parseEsvLine(line, header, separator); if (!this.operations.filterRow(record, filters)) { continue; } if (skip && skip > 0) { skip--; continue; } if (limit && lineCount > limit) { break; } records.push(record); lineCount++; } reader.close(); return records; } }; // src/core/useCases/UpdateEsv.ts var UpdateEsv = class { repository; operations; tempPath; constructor(repository) { this.repository = repository; this.operations = new Operations(); this.tempPath = ""; } async execute(filePath, separator, newData, filters) { this.tempPath = `${filePath}.tmp`; const tmpExists = await this.repository.fileExists(this.tempPath); if (tmpExists) { this.repository.deleteFile(this.tempPath); } const reader = await this.repository.readEsvFile(filePath); const writer = this.repository.getFileWriteStream(this.tempPath); let lineCount = 0; let header = []; let updated = false; for await (const line of reader) { if (lineCount === 0) { header = this.repository.splitEsvLine(line, separator).map((h) => this.repository.normalizeValue(h)); writer.write(this.formatHeader(header, separator)); lineCount++; continue; } if (!line) { continue; } const record = this.repository.parseEsvLine(line, header, separator); if (!this.operations.filterRow(record, filters)) { writer.write(line + "\n"); continue; } const updatedLine = this.updateLine(record, newData, separator); writer.write(updatedLine); lineCount++; updated = true; } reader.close(); writer.end(); return new Promise((resolve, reject) => { writer.on("finish", () => { if (!updated) { this.repository.deleteFile(this.tempPath); return reject(new Error("No record updated")); } console.log("Esv file updated successfully"); this.repository.renameFile(this.tempPath, filePath); return resolve(); }); writer.on("error", (error) => { return reject(error); }); }); } updateLine(record, newData, separator) { Object.entries(newData).forEach(([key, value]) => { record[key] = value; }); return this.formatLine(record, separator); } formatLine(record, separator) { const values = Object.values(record).map( (value) => this.repository.escapeField(String(value)) ); const line = values.join(separator); return line + "\n"; } formatHeader(header, separator) { const headerLine = header.map((h) => this.repository.escapeField(h)).join(separator); return headerLine + "\n"; } }; // src/core/useCases/DeleteEsv.ts var DeleteEsv = class { repository; operations; tempPath; constructor(repository) { this.repository = repository; this.operations = new Operations(); this.tempPath = ""; } async execute(filePath, separator, filters) { this.tempPath = `${filePath}.tmp`; const tmpExists = await this.repository.fileExists(this.tempPath); if (tmpExists) { this.repository.deleteFile(this.tempPath); } const reader = await this.repository.readEsvFile(filePath); const writer = this.repository.getFileWriteStream(this.tempPath); let lineCount = 0; let header = []; let updated = false; for await (const line of reader) { if (lineCount === 0) { header = this.repository.splitEsvLine(line, separator).map((h) => this.repository.normalizeValue(h)); writer.write(this.formatHeader(header, separator)); lineCount++; continue; } if (!line) { continue; } const record = this.repository.parseEsvLine(line, header, separator); lineCount++; if (this.operations.filterRow(record, filters)) { updated = true; } else { writer.write(line + "\n"); } } reader.close(); writer.end(); return new Promise((resolve, reject) => { writer.on("finish", () => { if (!updated) { this.repository.deleteFile(this.tempPath); return reject(new Error("No record deleted")); } console.log("Esv record deleted successfully"); this.repository.renameFile(this.tempPath, filePath); return resolve(); }); writer.on("error", (error) => { return reject(error); }); }); } formatHeader(header, separator) { const headerLine = header.map((h) => this.repository.escapeField(h)).join(separator); return headerLine + "\n"; } }; // src/viewNodel/EsvViewModel.ts var EsvViewModel = class { repository; readEsv; writeEsv; filterEsv; updateEsv; deleteEsv; constructor() { this.repository = new FileSystemEsvRepository(); this.readEsv = new ReadEsv(this.repository); this.writeEsv = new WriteEsv(this.repository); this.filterEsv = new FilterEsv(this.repository); this.updateEsv = new UpdateEsv(this.repository); this.deleteEsv = new DeleteEsv(this.repository); } async readEsvFile(filePath, skip, limit, separator = "\u{1F60E}") { return await this.readEsv.execute(filePath, separator, skip, limit); } async writeEsvFile(filePath, data, separator = "\u{1F60E}") { return await this.writeEsv.execute(filePath, data, separator); } async filterEsvFile(filePath, skip, limit, filters, separator = "\u{1F60E}") { return await this.filterEsv.execute(filePath, separator, skip, limit, filters); } async updateEsvFile(filePath, newData, filters, separator = "\u{1F60E}") { return await this.updateEsv.execute(filePath, separator, newData, filters); } async deleteEsvFile(filePath, filters, separator = "\u{1F60E}") { return await this.deleteEsv.execute(filePath, separator, filters); } }; // src/index.ts var QuickEsv = EsvViewModel; var ManualEsv = FileSystemEsvRepository; export { EsvFilterOperator, ManualEsv, Operations, QuickEsv }; //# sourceMappingURL=index.js.map