purify-objects
Version:
A powerful TypeScript library for cleaning objects by removing empty values, with support for YAML and CSV formats
157 lines (156 loc) • 6.83 kB
JavaScript
;
const fs = require("fs");
const path = require("path");
const chalk = require("chalk");
const index = require("./index.js");
const VALID_FORMATS = ["json", "yaml", "csv"];
const FORMAT_EXTENSIONS = {
json: ["json"],
yaml: ["yaml", "yml"],
csv: ["csv"]
};
const detectFileFormat = (filename) => {
const ext = path.extname(filename).toLowerCase().slice(1);
for (const [format, extensions] of Object.entries(FORMAT_EXTENSIONS)) {
if (extensions.includes(ext)) {
return format;
}
}
return "json";
};
const getDefaultOutputFilename = (inputFile, format) => {
const parsedPath = path.parse(inputFile);
const extension = format === "yaml" ? ".yaml" : `.${format}`;
return path.join(parsedPath.dir, `${parsedPath.name}${extension}`);
};
const extractCliOptions = (args) => {
const inputFile = args[0];
const formatIndex = args.indexOf("--format");
const formatValue = formatIndex !== -1 ? args[formatIndex + 1] : detectFileFormat(inputFile);
const convertToIndex = args.indexOf("--convert-to");
const convertToValue = convertToIndex !== -1 ? args[convertToIndex + 1] : null;
if (formatValue && !VALID_FORMATS.includes(formatValue)) {
console.error(chalk.red(`Error: Invalid format "${formatValue}". Valid formats are: ${VALID_FORMATS.join(", ")}`));
process.exit(1);
}
if (convertToValue && !VALID_FORMATS.includes(convertToValue)) {
console.error(chalk.red(`Error: Invalid conversion format "${convertToValue}". Valid formats are: ${VALID_FORMATS.join(", ")}`));
process.exit(1);
}
const outputIndex = args.indexOf("--output");
let outputFile = outputIndex !== -1 ? args[outputIndex + 1] : null;
if (convertToValue && !outputFile) {
outputFile = getDefaultOutputFilename(inputFile, convertToValue);
}
return {
inputFile,
outputFile,
compareMode: args.includes("--compare"),
safeMode: args.includes("--safe"),
format: formatValue,
convertTo: convertToValue,
delimiter: args.indexOf("--delimiter") !== -1 ? args[args.indexOf("--delimiter") + 1] : ",",
headers: !args.includes("--no-headers"),
removeZeroValues: args.includes("--remove-zero-values"),
noClean: args.includes("--noclean")
};
};
const parseContent = (content, format, options) => {
try {
switch (format) {
case "yaml":
return index.parseYAML(content);
case "csv":
return index.parseCSV(content, { delimiter: options.delimiter || ",", headers: options.headers !== false });
default:
return JSON.parse(content);
}
} catch (error) {
console.error(chalk.red(`Error parsing ${format.toUpperCase()} content: ${error.message}`));
process.exit(1);
}
};
const stringifyContent = (data, format, options) => {
try {
switch (format) {
case "yaml":
return index.stringifyYAML(data);
case "csv":
return index.stringifyCSV(Array.isArray(data) ? data : [data], {
delimiter: options.delimiter || ",",
headers: options.headers !== false
});
default:
return JSON.stringify(data, null, 2);
}
} catch (error) {
console.error(chalk.red(`Error converting to ${format.toUpperCase()}: ${error.message}`));
process.exit(1);
}
};
const generateFieldMap = (original, cleaned, prefix = "") => Object.entries(original).reduce((acc, [key, value]) => {
const currentPath = prefix ? `${prefix}.${key}` : key;
if (!(key in cleaned)) {
return [...acc, currentPath];
}
if (typeof value === "object" && value !== null && typeof cleaned[key] === "object" && cleaned[key] !== null) {
return [...acc, ...generateFieldMap(value, cleaned[key], currentPath)];
}
return acc;
}, []);
const executeCliOperation = (options) => {
const { inputFile, outputFile, compareMode, safeMode, format, convertTo, delimiter, headers, removeZeroValues, noClean } = options;
if (!inputFile) {
console.error(chalk.red("Please provide an input file"));
console.log(chalk.yellow(
`Usage: npx purify-objects input.file [--output cleaned.file] [--compare] [--safe] [--format ${VALID_FORMATS.join("|")}] [--convert-to ${VALID_FORMATS.join("|")}] [--delimiter ","] [--no-headers] [--remove-zero-values] [--noclean]`
));
process.exit(1);
}
try {
const inputPath = path.resolve(process.cwd(), inputFile);
if (!fs.existsSync(inputPath)) {
console.error(chalk.red(`Error: File not found: ${inputFile}`));
process.exit(1);
}
const content = fs.readFileSync(inputPath, "utf8");
const sourceData = parseContent(content, format, { delimiter, headers });
const customCleaner = removeZeroValues ? (key, value) => value === 0 : void 0;
const processedData = noClean ? sourceData : Array.isArray(sourceData) ? sourceData.map((item) => index.cleanObject(item, customCleaner, [], { safe: safeMode })) : index.cleanObject(sourceData, customCleaner, [], { safe: safeMode });
if (compareMode) {
safeMode && console.log(chalk.blue("\nSafe Mode: Original file will not be modified"));
console.log(chalk.yellow("\nOriginal data:"), stringifyContent(sourceData, format, { delimiter, headers }));
console.log(chalk.green("\nCleaned data (preview):"), stringifyContent(processedData, format, { delimiter, headers }));
if (!Array.isArray(sourceData)) {
const modifications = generateFieldMap(sourceData, processedData);
if (modifications.length) {
console.log(chalk.red("\nFields to be removed:"));
modifications.forEach((f) => console.log(chalk.red(`- ${f}`)));
}
}
safeMode && console.log(chalk.blue("\nNo changes were made to the original file (Safe Mode)"));
process.exit(0);
}
if (outputFile) {
const outputPath = path.resolve(process.cwd(), outputFile);
const outputFormat = convertTo || format;
fs.writeFileSync(outputPath, stringifyContent(processedData, outputFormat, { delimiter, headers }));
if (convertTo) {
console.log(chalk.green(`
File successfully ${noClean ? "converted" : "converted and cleaned"}:`));
console.log(chalk.blue(`Input: ${inputFile} (${format.toUpperCase()})`));
console.log(chalk.blue(`Output: ${outputFile} (${convertTo.toUpperCase()})`));
} else {
safeMode && console.log(chalk.blue("Safe Mode: Created new file without modifying original"));
console.log(chalk.green(`${noClean ? "Data" : "Cleaned data"} saved to ${outputFile}`));
}
return;
}
console.log(stringifyContent(processedData, format, { delimiter, headers }));
} catch (error) {
console.error(chalk.red("Error:"), (error == null ? void 0 : error.message) || "Unknown error occurred");
process.exit(1);
}
};
executeCliOperation(extractCliOptions(process.argv.slice(2)));