Skip to content

Instantly share code, notes, and snippets.

@malobre
Last active February 28, 2023 22:56
Show Gist options
  • Save malobre/dd30c30d3c0b7fb21fb78ea01404a8e5 to your computer and use it in GitHub Desktop.
Save malobre/dd30c30d3c0b7fb21fb78ea01404a8e5 to your computer and use it in GitHub Desktop.
A tag function that removes leading spaces until a line reaches the left margin.
// MIT License
//
// Copyright (c) 2023 Maël Obréjan
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// A tag function that removes leading spaces until a line reaches the left margin.
function dedent(strings, ...values) => {
let lines = String.raw({ raw: strings }, ...values).split("\n");
const minIdent = Math.min(
...lines
.map((line) => [line, line.replace(/^ +/, "")])
.filter(([_, trimmedLine]) => trimmedLine.length > 0)
.map(([line, trimmedLine]) => line.length - trimmedLine.length),
);
return lines.map((line) => line.slice(minIdent)).join("\n");
}
// A cached version for better performance when repeatedly formatting the same template with different parameters.
function dedentCached(strings, ...values) {
const self = dedentCached;
self.cache ??= new Map();
if (!self.cache.has(strings)) {
const lines = strings
.reduce((buf, slice) => {
let [line, ...lines] = buf;
for (const end of slice.split("\n")) {
lines = [[...(line ?? []), end], ...lines];
line = undefined;
}
return lines;
}, [])
.reverse();
const minIndent = Math.min(
...lines
.map(([line]) => [line, line.replace(/^ +/, "")])
.filter(([_, trimmedLine]) => trimmedLine.length > 0)
.map(([line, trimmedLine]) => line.length - trimmedLine.length),
);
self.cache.set(
strings,
lines
.map(([start, ...rest]) => [start.slice(minIndent), ...rest])
.reduce(([end, ...buf], [start, ...rest]) => [
...rest.reverse(),
`${end}\n${start}`,
...buf,
])
.reverse(),
);
}
return String.raw({ raw: self.cache.get(strings) }, ...values);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment