Files
NFD2NFC/nfd2nfc/normalize.js

144 lines
3.6 KiB
JavaScript
Executable File

#!/usr/bin/env node
const fs = require("fs").promises;
const path = require("path");
const minimist = require("minimist");
const cliProgress = require("cli-progress");
// Parse command-line arguments
const args = minimist(process.argv.slice(2), {
alias: { v: "verbose", h: "help" },
boolean: ["verbose", "help"],
});
// Function to display help message
function displayHelp() {
console.log(`
Usage: node index.js [path] [options]
Options:
-v, --verbose Enable verbose logging with progress bar
-h, --help Display this help message
Provide a path directly as an argument
`);
}
// Check for help flag
if (args.help) {
displayHelp();
process.exit(0);
}
// Main processing logic
async function processPath(targetPath) {
try {
const stats = await fs.lstat(targetPath);
const depth = 0;
if (stats.isDirectory()) {
await processDirectory(targetPath, depth);
} else if (stats.isFile()) {
await normalizeFileName(targetPath);
}
} catch (error) {
console.error(`Error processing path "${targetPath}":`, error);
}
}
// Function to determine if a file/directory should be ignored
function shouldIgnore(itemName) {
const ignoredItems = [".git", "node_modules", ".env"];
return ignoredItems.includes(itemName);
}
function normalizeToNFC(str) {
return str.normalize("NFC");
}
function normalizeToNFD(str) {
return str.normalize("NFD");
}
// Function to normalize file names
async function normalizeFileName(filePath) {
const dir = path.dirname(filePath);
const oldName = path.basename(filePath);
const newName = convertToNFC(oldName);
if (oldName !== newName && !shouldIgnore(oldName)) {
const newPath = path.join(dir, newName);
try {
await fs.rename(filePath, newPath);
return newPath;
} catch (error) {
console.error(`Failed to rename "${oldName}":`, error);
return filePath;
}
}
return filePath;
}
let entriesLength = 0;
// Function to process directories recursively
async function processDirectory(dirPath, depth = 0) {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
const filesToProcess = entries.filter(
(entry) => !entry.isDirectory()
).length;
let processedFiles = 0;
if (args.verbose && depth == 0) {
entriesLength = entries.length;
console.log("Processing directory:", dirPath);
progressBar.start(entriesLength, 0);
}
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
if (!shouldIgnore(entry.name)) {
await processDirectory(fullPath, depth + 1);
}
} else {
await normalizeFileName(fullPath);
}
if (args.verbose && filesToProcess > 0 && depth == 0) {
processedFiles++;
progressBar.update((processedFiles / entriesLength) * entriesLength);
}
}
await normalizeFileName(dirPath);
if (args.verbose && depth == 0) {
progressBar.stop();
}
} catch (error) {
console.error(`Error processing directory "${dirPath}":`, error);
}
}
// Initialize progress bar
const progressBar = new cliProgress.SingleBar(
{
format: "{bar} {percentage}% | {value}/{total}",
clearOnComplete: false,
},
cliProgress.Presets.shades_classic
);
module.exports = {
processPath,
shouldIgnore,
normalizeToNFC,
normalizeToNFD,
normalizeFileName,
};
// Handle input: if no flags, assume first non-flag argument is a path
const nonFlagArgs = args._;
if (nonFlagArgs.length > 0) {
processPath(nonFlagArgs[0]);
} else {
displayHelp();
}