Last active
February 13, 2026 03:00
-
-
Save JSuder-xx/b866fbee1b0cc5ecbf10e84388132060 to your computer and use it in GitHub Desktop.
ElmCondense Sketch
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
| // This script takes Elm code that has been formatted with Elm-Format on stdin and console logs it out condensed. | |
| // Modifications | |
| // - Linefeed removal | |
| // - THREE lines before comments collapsed to ONE. | |
| // - TWO lines between top-level declarations collapsed to ONE. | |
| // - ONE line after case variant expressions and one line after if/then/else REMOVED. | |
| // - Single line comments move the closing -} up so that the comment fits on one line. | |
| // - CONTENTIOUS: Top level declarations where the declaration and body fit in 60 characters or less represented as ONE line. | |
| // - CONTENTIOUS: Replaces 4 spaces with 2. | |
| // | |
| // One could wire this up in VS Code by | |
| // 1. Turning off Elm-Format | |
| // 2. Using a custom script runner that | |
| // a. First manually runs Elm-Format for a file on save. | |
| // b. And then pipes it through this utility and writes it back out. | |
| (async () => { | |
| let data = ""; | |
| for await (const chunk of process.stdin) { | |
| data += chunk; | |
| } | |
| console.log(condense(data)); | |
| })(); | |
| // @ts-check | |
| /** @type {(selection: string) => string} */ | |
| function condense(selection) { | |
| const input = selection.split("\n"); | |
| /** @type {string[]} */ | |
| const output = [] | |
| let inputLineIdx = 0; | |
| // seek past the module header and imports | |
| while (notAtEnd() | |
| && ( | |
| startsWith("module ") || | |
| startsWith("import ") || | |
| isEmpty(0) || | |
| startsComment() || | |
| startsIndented(0) | |
| )) { | |
| if (startsComment()) | |
| fitSingleLineCommentsOnOneLine(); | |
| else | |
| eatLine(); | |
| } | |
| // process module body | |
| while (notAtEnd()) { | |
| // Collapse the THREE lines before a standalone comment to ONE. | |
| if (isEmpty(0) && isEmpty(1) && isEmpty(2)) { | |
| emitEmptyLine(); | |
| skipLines(2); | |
| } | |
| // Collapse the TWO lines between top-level bindings to ONE. | |
| else if (isEmpty(0) && isEmpty(1)) { | |
| emitEmptyLine(); | |
| skipLines(1); | |
| } | |
| // Remove linefeed between case variants and let bindings | |
| else if (isEmpty(0)) { | |
| skipLines(1); | |
| } | |
| else if (startsComment()) { | |
| fitSingleLineCommentsOnOneLine(); | |
| } | |
| // Represent top level declaration that could fit in less than 60 characters as a single line rather than a declaration line and a body line. | |
| else if ( | |
| !startsIndented(0) // declaration | |
| && startsIndented(1) // body | |
| && isEmpty(2) // line feed after body means single line body | |
| && (relative(0).length + relative(1).length) <= 60 // declaration + body is less than 60 characters | |
| ) { | |
| output.push(relative(0) + " " + relative(1).trim()); | |
| skipLines(2); | |
| } | |
| else | |
| eatLine(); | |
| } | |
| return output.join("\n"); | |
| // WHERE-CLAUSE | |
| /** @type {(skipCount: number) => void} */ | |
| function skipLines(n) { | |
| inputLineIdx += n; | |
| } | |
| function notAtEnd() { | |
| return inputLineIdx < input.length; | |
| } | |
| function eatLine() { | |
| output.push(relative(0).replace(/ /g, ' ')); | |
| inputLineIdx++ | |
| } | |
| /** @type {(relativeIdx: number) => boolean} */ | |
| function startsIndented(relativeLineIndex) { | |
| const lineText = relative(relativeLineIndex); | |
| return lineText.length > 0 && lineText[0] === " "; | |
| } | |
| function emitEmptyLine() { | |
| output.push("") | |
| inputLineIdx++; | |
| } | |
| function fitSingleLineCommentsOnOneLine() { | |
| if (!startsComment()) throw new Error("Expecting start of comment"); | |
| // Single line comment is two lines in Elm format. | |
| if (relative(1).startsWith("-}")) { | |
| output.push(`${relative(0)} -}`); | |
| inputLineIdx += 2; | |
| } else { | |
| // multiline comment - leave this alone | |
| eatLine(); | |
| while (relative(0).trim() !== "-}") eatLine(); | |
| } | |
| } | |
| /** @type {(relativeIdx: number) => string} */ | |
| function relative(relativeIdx) { | |
| return input[inputLineIdx + relativeIdx] || "" | |
| } | |
| /** @type {(relativeIdx: number) => boolean} */ | |
| function isEmpty(relativeIdx) { | |
| return relative(relativeIdx) === "" | |
| } | |
| /** @type {(prefix: string) => boolean} */ | |
| function startsWith(s) { | |
| return relative(0).startsWith(s) | |
| } | |
| function startsComment() { | |
| return startsWith("{-|") | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment