Created
August 7, 2025 10:35
-
-
Save mokshchadha/c9016846898108c36b94dedcf38f7fa4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
const { exec } = require('child_process'); | |
const util = require('util'); | |
const path = require('path'); | |
const fs = require('fs').promises; | |
const execAsync = util.promisify(exec); | |
// Console colors | |
const colors = { | |
reset: '\x1b[0m', | |
bright: '\x1b[1m', | |
dim: '\x1b[2m', | |
red: '\x1b[31m', | |
green: '\x1b[32m', | |
yellow: '\x1b[33m', | |
blue: '\x1b[34m', | |
magenta: '\x1b[35m', | |
cyan: '\x1b[36m', | |
white: '\x1b[37m', | |
gray: '\x1b[90m' | |
}; | |
// Parse command line arguments | |
function parseArgs() { | |
const args = process.argv.slice(2); | |
const options = { | |
srcPath: './src', | |
sortBy: 'physical', | |
order: 'desc', | |
limit: null, | |
showHelp: false, | |
output: null, | |
pretty: false | |
}; | |
for (let i = 0; i < args.length; i++) { | |
const arg = args[i]; | |
switch (arg) { | |
case '-h': | |
case '--help': | |
options.showHelp = true; | |
break; | |
case '-p': | |
case '--path': | |
if (i + 1 < args.length) { | |
options.srcPath = args[++i]; | |
} | |
break; | |
case '-s': | |
case '--sort': | |
if (i + 1 < args.length) { | |
const sortBy = args[++i].toLowerCase(); | |
if (['physical', 'source', 'comment', 'empty'].includes(sortBy)) { | |
options.sortBy = sortBy; | |
} else { | |
console.error(`Invalid sort option: ${sortBy}. Use: physical, source, comment, empty`); | |
process.exit(1); | |
} | |
} | |
break; | |
case '-o': | |
case '--order': | |
if (i + 1 < args.length) { | |
const order = args[++i].toLowerCase(); | |
if (['asc', 'desc'].includes(order)) { | |
options.order = order; | |
} else { | |
console.error(`Invalid order: ${order}. Use: asc or desc`); | |
process.exit(1); | |
} | |
} | |
break; | |
case '-l': | |
case '--limit': | |
if (i + 1 < args.length) { | |
const limit = parseInt(args[++i]); | |
if (!isNaN(limit) && limit > 0) { | |
options.limit = limit; | |
} else { | |
console.error(`Invalid limit: ${args[i]}. Must be a positive number`); | |
process.exit(1); | |
} | |
} | |
break; | |
case '--output': | |
if (i + 1 < args.length) { | |
options.output = args[++i]; | |
// Ensure .csv extension | |
if (!options.output.endsWith('.csv')) { | |
options.output += '.csv'; | |
} | |
} | |
break; | |
case '--pretty': | |
options.pretty = true; | |
break; | |
default: | |
// If no flag is provided, treat as path | |
if (!arg.startsWith('-')) { | |
options.srcPath = arg; | |
} else { | |
console.error(`Unknown option: ${arg}`); | |
showHelp(); | |
process.exit(1); | |
} | |
} | |
} | |
return options; | |
} | |
function showHelp() { | |
console.log(` | |
SLOC File Sorter - Sort files by lines of code statistics | |
Usage: node sort-sloc.js [path] [options] | |
Arguments: | |
path Source directory path (default: ./src) | |
Options: | |
-h, --help Show this help message | |
-p, --path <path> Source directory path | |
-s, --sort <metric> Sort by metric (physical, source, comment, empty) [default: physical] | |
-o, --order <order> Sort order (asc, desc) [default: desc] | |
-l, --limit <number> Limit number of files shown | |
--output <filename> Save results to CSV file (auto-adds .csv extension) | |
--pretty Show colorized single-line output | |
Examples: | |
node sort-sloc.js # Use ./src, sort by physical lines (desc) | |
node sort-sloc.js ./lib # Use ./lib directory | |
node sort-sloc.js -p ./src -s source # Sort by source lines | |
node sort-sloc.js -p ./src -o asc -l 10 # Show top 10, ascending order | |
node sort-sloc.js --path ./src --sort comment --limit 5 | |
node sort-sloc.js --output results # Save to results.csv | |
node sort-sloc.js --pretty # Pretty colorized output | |
node sort-sloc.js --pretty --output stats # Pretty output + save to CSV | |
`); | |
} | |
async function runSlocAndSort() { | |
const options = parseArgs(); | |
if (options.showHelp) { | |
showHelp(); | |
return; | |
} | |
try { | |
console.log(`Running sloc command on: ${options.srcPath}\n`); | |
// Execute the sloc command with the specified path | |
const { stdout, stderr } = await execAsync(`sloc "${options.srcPath}" --details`); | |
if (stderr) { | |
console.error('Error:', stderr); | |
return; | |
} | |
// Parse the output to extract file information | |
const files = parseSloc(stdout); | |
// Sort files by the specified metric and order | |
files.sort((a, b) => { | |
const aValue = a[options.sortBy]; | |
const bValue = b[options.sortBy]; | |
if (options.order === 'asc') { | |
return aValue - bValue; | |
} else { | |
return bValue - aValue; | |
} | |
}); | |
// Apply limit if specified | |
const displayFiles = options.limit ? files.slice(0, options.limit) : files; | |
// Handle CSV output | |
if (options.output) { | |
await saveToCsv(displayFiles, options.output); | |
console.log(`${colors.green}✓ Results saved to ${options.output}${colors.reset}`); | |
} | |
// Display results (unless only CSV output is requested) | |
if (!options.output || options.pretty) { | |
if (options.pretty) { | |
displayPrettyResults(displayFiles, options); | |
} else { | |
displayDetailedResults(displayFiles, options); | |
} | |
} | |
} catch (error) { | |
console.error('Failed to execute sloc command:', error.message); | |
console.log('\nMake sure:'); | |
console.log('1. sloc is installed globally (npm install -g sloc)'); | |
console.log(`2. The ${options.srcPath} directory exists`); | |
console.log('3. You have the necessary permissions'); | |
} | |
} | |
async function saveToCsv(files, filename) { | |
const header = 'File Path,Physical,Source,Comment,Single-line Comment,Block Comment,Mixed,Empty Block Comment,Empty,To Do\n'; | |
const rows = files.map(file => | |
`"${file.path}",${file.physical},${file.source},${file.comment},${file.singleLineComment},${file.blockComment},${file.mixed},${file.emptyBlockComment},${file.empty},${file.todo}` | |
).join('\n'); | |
const csvContent = header + rows; | |
await fs.writeFile(filename, csvContent, 'utf8'); | |
} | |
function displayPrettyResults(files, options) { | |
const sortMetricDisplay = options.sortBy.charAt(0).toUpperCase() + options.sortBy.slice(1); | |
const orderDisplay = options.order === 'asc' ? 'lowest to highest' : 'highest to lowest'; | |
console.log(`${colors.bright}${colors.blue}📊 Files sorted by ${sortMetricDisplay} lines (${orderDisplay})${colors.reset}`); | |
if (options.limit) { | |
console.log(`${colors.dim}Showing top ${Math.min(options.limit, files.length)} of ${files.length} files${colors.reset}`); | |
} | |
console.log(); | |
files.forEach((file, index) => { | |
const rank = `${colors.dim}${(index + 1).toString().padStart(3, ' ')}.${colors.reset}`; | |
const filename = `${colors.cyan}${path.basename(file.path)}${colors.reset}`; | |
const filepath = `${colors.gray}(${file.path})${colors.reset}`; | |
// Color code the main metric value | |
let mainValue; | |
const value = file[options.sortBy]; | |
if (value > 1000) { | |
mainValue = `${colors.red}${colors.bright}${value}${colors.reset}`; | |
} else if (value > 500) { | |
mainValue = `${colors.yellow}${value}${colors.reset}`; | |
} else if (value > 100) { | |
mainValue = `${colors.green}${value}${colors.reset}`; | |
} else { | |
mainValue = `${colors.dim}${value}${colors.reset}`; | |
} | |
const stats = [ | |
`${colors.blue}Physical:${colors.reset} ${file.physical}`, | |
`${colors.green}Source:${colors.reset} ${file.source}`, | |
`${colors.yellow}Comment:${colors.reset} ${file.comment}`, | |
`${colors.gray}Empty:${colors.reset} ${file.empty}` | |
].join(' | '); | |
console.log(`${rank} ${filename} ${filepath}`); | |
console.log(` ${stats}`); | |
console.log(); | |
}); | |
console.log(`${colors.dim}Total files processed: ${files.length}${colors.reset}`); | |
} | |
function displayDetailedResults(files, options) { | |
const sortMetricDisplay = options.sortBy.charAt(0).toUpperCase() + options.sortBy.slice(1); | |
const orderDisplay = options.order === 'asc' ? 'lowest to highest' : 'highest to lowest'; | |
console.log(`Files sorted by ${sortMetricDisplay} lines (${orderDisplay}):`); | |
if (options.limit) { | |
console.log(`Showing top ${Math.min(options.limit, files.length)} of ${files.length} files`); | |
} | |
console.log(); | |
files.forEach(file => { | |
console.log(`--- ${file.path}`); | |
console.log(''); | |
console.log(` Physical : ${file.physical}`); | |
console.log(` Source : ${file.source}`); | |
console.log(` Comment : ${file.comment}`); | |
console.log(` Single-line comment : ${file.singleLineComment}`); | |
console.log(` Block comment : ${file.blockComment}`); | |
console.log(` Mixed : ${file.mixed}`); | |
console.log(` Empty block comment : ${file.emptyBlockComment}`); | |
console.log(` Empty : ${file.empty}`); | |
console.log(` To Do : ${file.todo}`); | |
console.log(''); | |
}); | |
console.log(`\nTotal files processed: ${files.length}`); | |
if (options.limit && files.length > options.limit) { | |
console.log(`Showing: ${files.length}`); | |
} | |
} | |
function parseSloc(output) { | |
const files = []; | |
const lines = output.split('\n'); | |
let currentFile = null; | |
for (let i = 0; i < lines.length; i++) { | |
const line = lines[i].trim(); | |
// Check if this line starts a new file entry | |
if (line.startsWith('--- ')) { | |
// If we have a previous file, save it | |
if (currentFile) { | |
files.push(currentFile); | |
} | |
// Start a new file entry | |
currentFile = { | |
path: line.substring(4), // Remove "--- " prefix | |
physical: 0, | |
source: 0, | |
comment: 0, | |
singleLineComment: 0, | |
blockComment: 0, | |
mixed: 0, | |
emptyBlockComment: 0, | |
empty: 0, | |
todo: 0 | |
}; | |
} | |
// Parse the statistics lines | |
else if (currentFile && line.includes(':')) { | |
const parts = line.split(':'); | |
if (parts.length === 2) { | |
const key = parts[0].trim(); | |
const value = parseInt(parts[1].trim()) || 0; | |
switch (key) { | |
case 'Physical': | |
currentFile.physical = value; | |
break; | |
case 'Source': | |
currentFile.source = value; | |
break; | |
case 'Comment': | |
currentFile.comment = value; | |
break; | |
case 'Single-line comment': | |
currentFile.singleLineComment = value; | |
break; | |
case 'Block comment': | |
currentFile.blockComment = value; | |
break; | |
case 'Mixed': | |
currentFile.mixed = value; | |
break; | |
case 'Empty block comment': | |
currentFile.emptyBlockComment = value; | |
break; | |
case 'Empty': | |
currentFile.empty = value; | |
break; | |
case 'To Do': | |
currentFile.todo = value; | |
break; | |
} | |
} | |
} | |
} | |
// Don't forget the last file | |
if (currentFile) { | |
files.push(currentFile); | |
} | |
return files; | |
} | |
// Run the script | |
runSlocAndSort(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
need to npm i sloc -g