sort-files-cli
Version:
A CLI tool to sort files into predefined categories
199 lines (177 loc) • 4.27 kB
JavaScript
import fs from "fs-extra";
import path from "path";
import { program } from "commander";
import chalk from "chalk";
import ora from "ora";
// Predefined categories and their extensions
const CATEGORIES = {
images: [
"jpg",
"jpeg",
"png",
"gif",
"bmp",
"svg",
"webp",
"ico",
"tiff",
"raw",
],
videos: [
"mp4",
"mov",
"avi",
"mkv",
"wmv",
"flv",
"webm",
"m4v",
"mpeg",
"3gp",
],
documents: [
"pdf",
"doc",
"docx",
"txt",
"rtf",
"odt",
"xls",
"xlsx",
"ppt",
"pptx",
"csv",
],
archives: ["zip", "rar", "7z", "tar", "gz", "bz2", "xz", "iso"],
music: [
"mp3",
"wav",
"flac",
"m4a",
"aac",
"ogg",
"wma",
"aiff",
"alac",
"mid",
"midi",
],
};
program
.version("1.0.0")
.description("A CLI tool to sort files into predefined categories")
.argument("[dir]", "Directory to sort (defaults to current directory)")
.option("-d, --dry-run", "Show what would be done without making changes")
.option("-r, --revert", "Revert files back to original directory")
.parse(process.argv);
function getCategoryForExtension(extension) {
for (const [category, extensions] of Object.entries(CATEGORIES)) {
if (extensions.includes(extension.toLowerCase())) {
return category;
}
}
return null;
}
async function sortFiles() {
try {
const targetDir = program.args[0] || process.cwd();
const spinner = ora(
program.opts().revert ? "Reverting files..." : "Sorting files..."
).start();
if (!(await fs.pathExists(targetDir))) {
spinner.fail(chalk.red(`Directory not found: ${targetDir}`));
process.exit(1);
}
const files = await fs.readdir(targetDir, { withFileTypes: true });
let moveCount = 0;
let skipCount = 0;
if (program.opts().revert) {
// Only revert predefined category folders
for (const category of Object.keys(CATEGORIES)) {
const categoryPath = path.join(targetDir, category);
// Skip if category folder doesn't exist
if (!(await fs.pathExists(categoryPath))) {
continue;
}
const categoryFiles = await fs.readdir(categoryPath);
for (const file of categoryFiles) {
const sourcePath = path.join(categoryPath, file);
const destinationPath = path.join(targetDir, file);
if (program.opts().dryRun) {
console.log(
chalk.blue(
`Would move back: ${path.join(category, file)} → ${file}`
)
);
moveCount++;
continue;
}
// Move file back to root directory
await fs.move(sourcePath, destinationPath, { overwrite: true });
moveCount++;
}
// Remove the empty category folder
if (!program.opts().dryRun) {
await fs.remove(categoryPath);
}
}
} else {
// Sorting logic
for (const file of files) {
// Skip if it's not a file or if it's inside a directory
if (!file.isFile()) {
skipCount++;
continue;
}
const fileExtension = path.extname(file.name).slice(1).toLowerCase();
const category = getCategoryForExtension(fileExtension);
// Skip if no matching category or if it's our script
if (!category || file.name === "sort-cli.js") {
skipCount++;
continue;
}
const categoryFolder = path.join(targetDir, category);
const sourcePath = path.join(targetDir, file.name);
const destinationPath = path.join(categoryFolder, file.name);
if (program.opts().dryRun) {
console.log(
chalk.blue(
`Would move: ${file.name} → ${path.join(category, file.name)}`
)
);
moveCount++;
continue;
}
await fs.ensureDir(categoryFolder);
if (sourcePath !== destinationPath) {
await fs.move(sourcePath, destinationPath, { overwrite: true });
moveCount++;
} else {
skipCount++;
}
}
}
if (program.opts().dryRun) {
spinner.succeed(
chalk.green(
`Dry run complete. Would ${
program.opts().revert ? "revert" : "move"
} ${moveCount} files.`
)
);
} else {
spinner.succeed(
chalk.green(
`Successfully ${
program.opts().revert ? "reverted" : "sorted"
} ${moveCount} files (${skipCount} skipped)`
)
);
}
} catch (error) {
console.error(chalk.red("Error:", error.message));
process.exit(1);
}
}
sortFiles();