UNPKG

@bryoung/oak

Version:

A cli tool for nicely visualizing directories

224 lines (206 loc) 6.29 kB
#!/usr/bin/env node import { program } from "commander"; import chalkRainbow from "chalk-rainbow"; import { getRandomColor } from "./colors.js"; import fs from "fs-extra"; import path from "path"; import chalk from "chalk"; let defaultExclude = ["node_modules", ".git", ".vscode"]; let dirCount = 0; let fileCount = 0; let color = false; let depth = 0; let maxDepth = Infinity; let rootDir; let depths = []; let prevDepths = []; const verticalLine = " │ "; const lastFileLine = " └── "; const connectedFileLine = " ├── "; export const printDirectoryContents = ({ directory, indent = "", exclude = "", depth, size, }) => { const contents = fs.readdirSync(directory); if (contents.length > size) { if (path.resolve(directory) === rootDir) { console.log( chalk.cyan( "Make sure your size input is not smaller than the root directory size" ) ); } console.log("size exceeded, dont print dir contents"); return; } if (depth > maxDepth) return; contents.forEach((file, idx) => { if (exclude.includes(file)) return; let prefix = ""; depths = []; const fullPath = path.join(directory, file); const stats = fs.statSync(fullPath); const isDir = stats.isDirectory(); // to build the prefix you have to use a for loop that iterates through the depth // and adds verticalLine if we have not reached the end of a dir at that depth, // according to the prevDepths array and add indent to prefix // once we reach the current depth, we check if the file is the last file in the dir and // alter the prefix accordingly, then log the prefix + filename to the console for (let i = 0; i <= depth; i++) { if (depth === 0) { if (idx === contents.length - 1) { prefix = lastFileLine; depths[i] = "last"; continue; } else { prefix = connectedFileLine; depths[i] = "connected"; continue; } } if (i === depth) { if (idx === contents.length - 1) { prefix += lastFileLine; depths[i] = "last"; break; } else { prefix += connectedFileLine; depths[i] = "connected"; break; } } // check if file at current depth is last file in dir, if not, add prefix of │ if (prevDepths[i] === "connected") { prefix += `${verticalLine} `; depths[i] = "connected"; } else { prefix += ` `; } prefix += " "; } prevDepths = depths; if (color) { isDir ? console.log( chalk.hex(getRandomColor())(`${prefix}`), chalk.hex(getRandomColor())(`${file}/`) ) : console.log( chalk.hex(getRandomColor())(`${prefix}`), chalk.hex(getRandomColor())(`${file}`) ); } else { depth % 2 === 0 ? console.log( `${prefix}`, isDir ? chalk.hex("#ecb133")(`${file}/`) : chalk.hex("#17a762")(`${file}`) ) : console.log( `${prefix}`, isDir ? chalk.hex("#ff6b8c")(`${file}/`) : chalk.hex("#ff3d37")(`${file}`) ); } if (isDir) { dirCount++; printDirectoryContents({ directory: fullPath, indent: `${indent}`, exclude, depth: depth + 1, size, }); } else { fileCount++; } }); }; // Logical flow of the program: // 1. Get the directory from the user // 2. Get the options from the user // 3. assign options to variables // 4. if the user wants to print all files and directories, call printDirectoryContents with exclude = [] // 5. if the user wants to print with random colors, call printDirectoryContents with color = true // 6. otherwise, call printDirectoryContents with whatever is excluded - including default excludes - and the size and depth program .version("1.0.0") .arguments("<directory>") .description( chalk.greenBright( "Print a colorful tree representation of the structure of a given directory" ) ) .option("-e, --exclude <s>", "Exclude folders matching the given pattern") .option("-d, --depth <n>", "Maximum depth to print", parseInt) .option("-s, --size <n>", "Maximum size directory to print", parseInt) .option("-c, --color", "Colorize output with random color scheme") .option("-a, --all", "Print all files and directories") .action((directory) => { try { rootDir = path.resolve(directory); console.log(directory); let { exclude, depth: inputDepth, size: inputSize, color: chosenColor, all, } = program.opts(); if (all !== undefined && exclude !== undefined) { console.log( chalk.redBright( "You cannot use the --all option with the --exclude option" ) ); return; } if (all !== undefined && inputSize !== undefined) { console.log( chalk.redBright( "You cannot use the --all option with the --size option" ) ); return; } if (chosenColor) color = chosenColor; if (inputDepth !== undefined) maxDepth = Math.floor(Math.abs(inputDepth)); if (all !== undefined) { printDirectoryContents({ directory, exclude: [], depth }); return; } const excludeArr = exclude ? defaultExclude.concat(exclude.split()) : defaultExclude; if (color) { printDirectoryContents({ directory, exclude: excludeArr, size: inputSize ? inputSize : Infinity, depth, }); return; } printDirectoryContents({ directory, exclude: excludeArr, size: inputSize ? inputSize : Infinity, depth, }); console.log(chalk.cyan(`${dirCount} folders, ${fileCount} files \n`)); } catch (error) { console.error( chalk.redBright(`Error printing directory structure: ${error.message}`) ); } }); program.on("--help", () => { console.log("Example:"); console.log(" $ oak ./my-folder"); }); program.parse(process.argv);