-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdedent.ts
111 lines (102 loc) · 2.89 KB
/
dedent.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* @file dedent.ts
* @author Brandon Kalinowski
* @copyright 2020-2024 Brandon Kalinowski (@brandonkal). All rights reserved.
* @description Dedent is a tagged template literal function.
* It is used by yaml-tag.ts and has tests to ensure consistency with a whitespace-sensitive content.
* @license MIT
*/
import unraw from "./unraw.ts";
/** calculates indent for a given string. */
export function calcIndent(text: string, ignoreEmptyFirstLine?: boolean) {
let lines = text.split("\n");
let skippedFirst = false;
if (ignoreEmptyFirstLine) {
lines = lines.slice(1);
skippedFirst = true;
}
const indents: number[] = [];
for (const l of lines) {
const m = l.match(/^(\s+)\S+/);
if (m) {
indents.push(m[1].length);
} else if (l.match(/^\S/)) {
indents.push(0);
break;
}
}
return { indent: indents.length ? Math.min(...indents) : 0, skippedFirst };
}
const COLLAPSE = "♜";
const VALUE_MARK = "♘";
interface DedentResult {
strings: string[];
values: unknown[];
}
/*
* Lines that contain only whitespace are not used for measuring.
*/
export function execDedent(
literals: string | string[] | TemplateStringsArray,
values: unknown[],
): DedentResult {
const strings: readonly string[] = typeof literals === "string"
? [literals]
: literals;
const raws = (literals as TemplateStringsArray).raw;
// first, perform interpolation
let text = "";
for (let i = 0; i < strings.length; i++) {
const next = raws
? raws[i].replace(/\\\n/g, `${COLLAPSE}\n`)
: strings[i];
text += next;
if (i < values.length) {
text += VALUE_MARK;
}
}
const { indent } = calcIndent(text, true);
let result = text
.split("\n")
.map((line, j) => (j > 0 ? line.substr(indent) : line))
.join("\n");
if (raws) {
const pattern = new RegExp(`${COLLAPSE}\n`, "g");
result = result.replace(pattern, "");
}
const parts = unraw(result).split(VALUE_MARK);
return { strings: parts, values };
}
/*
* Lines that contain only whitespace are not used for measuring.
*/
export function dedent(literals: string): string;
export function dedent(
literals: TemplateStringsArray | string[],
...values: unknown[]
): string;
export function dedent(
literals: string | string[] | TemplateStringsArray,
...values: unknown[]
): string {
const out = execDedent(literals, values);
return mergeAndReduceToString(out.strings, out.values);
}
/**
* Zipper-merge two arrays together into string. Elements will be coerced to
* string values.
* @param a The array whose first element will be the first itme in the output
* string. Can be sparse.
* @param b The array to merge into `a`. Can be sparse.
* @example
* merge([1, 2, 3], ["A", "B", "C", "D", "E"]);
* // => "1A2B3CDE"
*/
function mergeAndReduceToString(a: unknown[], b: unknown[]): string {
let result = "";
for (let i = 0; i < Math.max(a.length, b.length); i++) {
if (i in a) result += a[i];
if (i in b) result += b[i];
}
return result;
}