-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmacros-runner.ts
126 lines (116 loc) · 3.87 KB
/
macros-runner.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { kuma } from '@webdoky/yari-ports';
import Context from './context';
import serializeHtmlNode from './utils/serializeHtmlNode';
import { ExpungedMacroInsert } from '../components';
const { macros: Macros, parseMacroArgs, extractMacros } = kuma;
// List of macros that should be processed anyway, i.e for rendering navigation
const navigationalMacros = ['cssref', 'jssidebar', 'jsref', 'htmlref'];
const UNESCAPED_BACKTICK_MATCH = /([^\\])`/g;
const UNESCAPED_SINGLE_QUOTE_MATCH = /([^\\])'/g;
/**
* Some macros, or their output may contain symbols, which may be considered
* part of markdown syntax, so we have to additionally escape them.
*
* @param content string
* @returns string
*/
const escapeMarkdownCharacters = (content: string) => {
return content
.replaceAll(UNESCAPED_BACKTICK_MATCH, '$1\\`')
.replaceAll(UNESCAPED_SINGLE_QUOTE_MATCH, "$1\\'");
};
export const runMacros = (
content: string,
context: Context,
navigationOnly = false,
) => {
// const { path } = context;
let resultContent = content;
const recognizedMacros = extractMacros(content);
const data = {
macros: [],
};
const failedMacros = {};
const macrosRegistry = new Macros(context);
recognizedMacros.map((expression) => {
const { match, functionName, args } = expression;
if (
!navigationOnly ||
navigationalMacros.includes(functionName.toLowerCase())
) {
let result = match; // uninterpolated macros will be visible by default
const macroFunction = macrosRegistry.lookup(functionName);
if (macroFunction) {
try {
if (args) {
result = macroFunction(...parseMacroArgs(args));
} else {
result = macroFunction();
}
} catch (e) {
result = match; // Do nothing
if (failedMacros[functionName]) {
failedMacros[functionName].count += 1;
failedMacros[functionName].lastMessage = e?.message;
failedMacros[functionName].lastUsedExpression = match;
} else {
failedMacros[functionName] = {
count: 1,
lastMessage: e?.message,
lastUsedExpression: match,
};
}
// throw new Error(
// `Error while processing page ${path} with macro {{${functionName}${
// args ? `(${args})` : ''
// }}}. Original error: ${e.message}`
// );
}
} else {
if (failedMacros[functionName]) {
failedMacros[functionName].count += 1;
failedMacros[functionName].lastUsedExpression = match;
} else {
failedMacros[functionName] = {
count: 1,
lastMessage: 'Macro missing',
lastUsedExpression: match,
};
}
}
if (typeof result !== 'string') {
// if the output is not a string, then we have to additionaly process it in the app
// so put it into data layer instead
data.macros.push({
macro: functionName.toLowerCase(),
result: JSON.stringify(result),
});
result = '';
}
resultContent = resultContent.replace(
match,
result !== match
? escapeMarkdownCharacters(result)
: serializeHtmlNode(
ExpungedMacroInsert,
escapeMarkdownCharacters(result),
),
);
}
});
const numberOfFailedMacros = Object.keys(failedMacros).length;
if (numberOfFailedMacros) {
console.warn(
`${context.env.path}: got ${numberOfFailedMacros} failed macros`,
);
Object.entries(failedMacros).forEach(([functionName, entry]: any) => {
console.warn(
`\x1b[33m${entry.count} failed ${functionName} macros, the last expression was: ${entry.lastUsedExpression}, message: ${entry.lastMessage}\x1b[0m`,
);
});
}
return {
content: resultContent,
data,
};
};