Last active
December 15, 2024 23:25
-
-
Save rodrigocfd/59d6e09ae50f7f005a6eb43b1a63a0d6 to your computer and use it in GitHub Desktop.
Convert spaces to tabs
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
| /** | |
| * Converts spaces to tab on all files within a folder, recursively. | |
| * Rodrigo Dias <rcesar@gmail.com> | |
| * Friday, December 13, 2024. | |
| */ | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const skipDir = ['.git', 'node_modules']; | |
| const NC = '\x1b[0m'; | |
| const RED = '\x1b[31m'; | |
| const GRE = '\x1b[32m'; | |
| const BLU = '\x1b[34m'; | |
| const target = process.argv[2]; | |
| if (!target) { | |
| console.log('Usage: node tabify.js FOLDER [--crlf]\n'); | |
| console.log('Options:'); | |
| console.log(' --crlf Uses CR/LF for linebreaks; default is LF.'); | |
| process.exit(1); | |
| } else if (!fs.lstatSync(target).isDirectory()) { | |
| console.log(`${RED}Not a folder: ${target}${NC}`); | |
| process.exit(1); | |
| } | |
| const useCrLf = process.argv[3] === '--crlf'; | |
| let numSubFolders = 0; | |
| let numFiles = 0; | |
| let numFilesDone = 0; | |
| recursivePass(target); | |
| console.log(`${BLU}Summary:${NC}`) | |
| console.log(`${GRE} Found ${numFiles} file(s) and ${numSubFolders} subfolder(s).${NC}`); | |
| console.log(`${GRE} Tabs replaced in ${numFilesDone} file(s).${NC}`); | |
| /** | |
| * @param {string} target | |
| */ | |
| function recursivePass(target) { | |
| if (target.endsWith('/') || target.endsWith('\\')) | |
| target = target.substring(0, target.length - 1); // remove trailing slash | |
| fs.readdirSync(target, {}).forEach(name => { | |
| const thisPath = target + path.sep + name; | |
| if (fs.lstatSync(thisPath).isDirectory()) { | |
| if (skipDir.indexOf(name) === -1) { | |
| ++numSubFolders; | |
| recursivePass(thisPath); | |
| } | |
| } else { | |
| ++numFiles; | |
| if (processFile(thisPath)) ++numFilesDone; | |
| } | |
| }); | |
| } | |
| /** | |
| * @param {string} filePath | |
| * @returns {boolean} | |
| */ | |
| function processFile(filePath) { | |
| const lines = fs.readFileSync(filePath, {encoding: 'utf8'}).split(/\r?\n/g); | |
| let wasTreated = false; | |
| if (!fileHasTabs(lines)) { | |
| const indents = getIndents(lines); | |
| if (indents.length) { | |
| const lowestIndent = findLowestIndent(indents); | |
| swapSpacesWithTabs(lines, indents, lowestIndent); | |
| wasTreated = true; | |
| } | |
| } | |
| const newText = lines.join(useCrLf ? '\r\n' : '\n'); | |
| fs.writeFileSync(filePath, newText); | |
| return wasTreated; | |
| } | |
| /** | |
| * @param {string[]} lines | |
| * @returns {boolean} | |
| */ | |
| function fileHasTabs(lines) { | |
| for (const line of lines) { | |
| if (line.startsWith('\t')) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * @param {string[]} lines | |
| * @returns {{line: number; spaces: number}[]} | |
| */ | |
| function getIndents(lines) { | |
| const indents = []; | |
| for (let i = 0; i < lines.length; ++i) { | |
| if (lines[i].startsWith(' ')) { | |
| indents.push({ | |
| line: i, | |
| spaces: (line => { | |
| let c = 0; | |
| while (line.charAt(c) === ' ') ++c; | |
| return c; | |
| })(lines[i]), | |
| }); | |
| } | |
| } | |
| return indents; | |
| } | |
| /** | |
| * @param {{line: number; spaces: number}[]} indents | |
| * @returns {number} | |
| */ | |
| function findLowestIndent(indents) { | |
| let lowest = Number.MAX_VALUE; | |
| for (const indent of indents) { | |
| if (indent.spaces < lowest) | |
| lowest = indent.spaces; | |
| } | |
| return lowest; | |
| } | |
| /** | |
| * @param {string[]} lines | |
| * @param {{line: number; spaces: number}[]} indents | |
| * @param {number} numSpaces | |
| */ | |
| function swapSpacesWithTabs(lines, indents, numSpaces) { | |
| for (const indent of indents) { | |
| const numTabs = (indent.spaces - (indent.spaces % numSpaces)) / numSpaces; | |
| lines[indent.line] = '\t'.repeat(numTabs) | |
| + lines[indent.line].substring(numTabs * numSpaces); // in-place modification | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment