Skip to content

Instantly share code, notes, and snippets.

@madidier
Created August 16, 2023 22:42
Show Gist options
  • Save madidier/d7d7ffce92ffd5cbcbfdefacaeedb1fe to your computer and use it in GitHub Desktop.
Save madidier/d7d7ffce92ffd5cbcbfdefacaeedb1fe to your computer and use it in GitHub Desktop.
JavaScript unindent tagged template literal
const unindent = (strings, ...values) => {
// Quick and not so dirty unindented template strings.
// Handles tabs very roughly if they are present.
// The very first line is always left as is if not entirely whitespace,
// and removed otherwise.
const matchLine = line => {
const {groups} =
/^(?<line>(?<indent>[\t ]*)(?<nonws>[^\n]*)\n?)(?<tail>.*)$/s.exec(line);
return {
line: groups.line,
indent: groups.indent.length,
hasNonWS: !!groups.nonws,
hasNewline: groups.line.endsWith('\n'),
tail: groups.tail,
};
};
const shortestIndent = (() => {
const [SKIP, LINE_START] = ['SKIP', 'LINE_START'];
let state = {tag: SKIP};
let indent = Infinity;
for (let str of strings) {
if (state.tag === LINE_START) {
indent = Math.min(indent, state.indent);
state = {tag: SKIP};
}
while (str) {
const match = matchLine(str);
switch (state.tag) {
case SKIP:
if (match.hasNewline) {
state = {tag: LINE_START, indent: 0};
}
break;
case LINE_START:
// Known invariant here: state.indent === 0
state = {tag: LINE_START, indent: match.indent};
if (match.hasNonWS) {
indent = Math.min(indent, match.indent);
state = {tag: SKIP};
}
if (match.hasNewline) {
state = {tag: LINE_START, indent: 0};
}
break;
}
str = match.tail;
}
}
return indent === Infinity ? 0 : indent;
})();
const fixedStrings = (() => {
const [START, SKIP, LINE_START] = ['START', 'SKIP', 'LINE_START'];
let state = START;
const result = [];
for (let str of strings) {
const builder = [];
while (str) {
const match = matchLine(str);
switch (state) {
case START:
if (!match.hasNewline || match.hasNonWS) {
builder.push(match.line);
}
state = match.hasNewline ? LINE_START : SKIP;
break;
case SKIP:
builder.push(match.line);
if (match.hasNewline) state = LINE_START;
break;
case LINE_START:
if (match.line.length <= shortestIndent && match.hasNewline) {
builder.push('\n');
} else {
builder.push(match.line.substring(shortestIndent));
}
if (!match.hasNewline) state = SKIP;
break;
}
str = match.tail;
}
result.push(builder.join(''));
}
return result;
})();
return String.raw({raw: fixedStrings}, ...values);
};
const example = vars => {
return unindent`
# ${vars.title}
This markdown was generated from a JS tagged template. Here is some
properly indented code:
\`\`\`html
<div id="${vars.divId}">
<p class="${vars.paragraphClass}">
${vars.paragraph}
</p>
</div>
\`\`\`
`;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment