/* * SPDX-License-Identifier: MIT * Copyright © 2015-2025 Desmond Brand * Copyright © 2026 Paul Anthony Webb (modifications) * * Dap is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. */ /*** UTILITY ------------------------------------------ ***/ const dedent: Dedent = createDedent({}); interface Dedent { (literals: string): string; (strings: TemplateStringsArray, ...values: unknown[]): string; withOptions: CreateDedent; } interface DedentOptions { alignValues?: boolean; escapeSpecialCharacters?: boolean; trimWhitespace?: boolean; } type CreateDedent = (options: DedentOptions) => Dedent; /*** EXPORT ------------------------------------------- ***/ export default dedent; /*** PROGRAM ------------------------------------------ ***/ function createDedent(options: DedentOptions): Dedent { function dedent(literals: string): string; function dedent(strings: TemplateStringsArray, ...values: unknown[]): string; function dedent(strings: TemplateStringsArray | string, ...values: unknown[]) { const raw = typeof strings === "string" ? [strings] : strings.raw; const { alignValues = false, escapeSpecialCharacters = Array.isArray(strings), trimWhitespace = true } = options; let result = ""; for (let i = 0; i < raw.length; i++) { let next = raw[i]; if (escapeSpecialCharacters) { next = next .replace(/\\\n[ \t]*/g, "") .replace(/\\`/g, "`") .replace(/\\\$/g, "$") .replace(/\\\{/g, "{"); } result += next; if (i < values.length) { result += alignValues ? alignValue(values[i], result) : values[i]; } } const lines = result.split("\n"); let minIndent: number | null = null; for (const line of lines) { const match = line.match(/^(\s+)\S+/); if (match) { const indent = match[1].length; minIndent = minIndent === null ? indent : Math.min(minIndent, indent); } } if (minIndent !== null) { result = lines .map((line) => (line[0] === " " || line[0] === "\t" ? line.slice(minIndent) : line)) .join("\n"); } if (trimWhitespace) result = result.trim(); if (escapeSpecialCharacters) result = result.replace(/\\n/g, "\n"); return result; } dedent.withOptions = (newOptions: DedentOptions): Dedent => createDedent({ ...options, ...newOptions }); return dedent; } /*** HELPER ------------------------------------------- ***/ function alignValue(value: unknown, precedingText: string) { if (typeof value !== "string" || !value.includes("\n")) return value; const currentLine = precedingText.slice(precedingText.lastIndexOf("\n") + 1); const indentMatch = currentLine.match(/^(\s+)/); if (indentMatch) return value.replace(/\n/g, `\n${indentMatch[1]}`); return value; } /*** adapted from https://github.com/dmnd/dedent ***/