/
write-heading-ids.ts
144 lines (129 loc) · 4.75 KB
/
write-heading-ids.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* This script adds custom heading ids to all files in the docs directory.
* The headings in our API reference may have format such as
+ ### `opacity` (Number, optional)
+ ### `opacity?: number`
+ ### `getPosition` ([Function](../../developer-guide/using-layers.md#accessors), optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square")
+ ### `needsRedraw(options?: {clearRedrawFlags?: boolean}): string | false`
* The default generated hash id by Docusaurus will be something like:
+ #-opacity-number-optional
+ #-opacity-number-
+ #-getposition-functiondeveloper-guideusing-layersmdaccessors-optional-transition-enabledhttpsimgshieldsiobadgetransition-enabled-greensvgstyleflat-square
+ #-needsredraw-options-clearredrawflags-boolean-string-false-
* They are long, difficult to read, subject to how the documentation is written, and when the call signature changes.
* This script appends a custom id tag to the end of such headings, so that their hash ids will look like:
+ #opacity
+ #opacity
+ #getposition
+ #needsredraw
*/
const fs = require('fs/promises');
const path = require('path');
const docsDir: string = path.resolve(__dirname, '../docs');
/** Should match if the line is a header */
const headerTest = /^(#+)\s+(?<headerContent>.*?)\s*(?<customId>\{#[\w\-]+\})?$/;
/** Should match if the header describes an API */
const apiTest = /^`((?<code>\w+)[^`]*)`\s*(\(.*?\)|$)/;
test();
main();
/** Test that getCustomId handles different formats correctly */
function test() {
expect(
getCustomId(
`The line width of each object, in units specified by widthUnits (default pixels). `
)?.[2],
undefined,
'not header'
);
expect(getCustomId(`## Learning deck.gl`)?.[2], undefined, 'does not contain code');
expect(getCustomId(`## Changes to \`TileLayer\``)?.[2], undefined, 'is not api');
expect(getCustomId(`## \`pickObjects\``)?.[2], '{#pickobjects}', 'single word api');
expect(getCustomId(`## \`@deck.gl/extensions\``)?.[2], undefined, 'Package name');
expect(
getCustomId(`## \`strokeOpacity\` (Number)`)?.[2],
'{#strokeopacity}',
'with type annotation 1'
);
expect(
getCustomId(`## \`backgroundPadding:number[]\``)?.[2],
'{#backgroundpadding}',
'with type annotation 2'
);
expect(
getCustomId(`## \`onBeforeRender(gl: WebGLRenderingContext)\``)?.[2],
'{#onbeforerender}',
'with call signature'
);
expect(
getCustomId(
`##### \`getWidth\` ([Function](../../developer-guide/using-layers.md#accessors)|Number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square")`
)?.[2],
'{#getwidth}',
'with extra flag'
);
console.log('All tests pass!\n');
}
/** Traverse all files in the docs directory, append custom ids if necessary */
async function main() {
const files = await listFiles(docsDir, '.md');
for (const f of files) {
await processFile(f);
}
}
/** Parse a single line of text in a .md file.
* @returns new content in 3 parts if custom id is needed, null otherwise.
*/
function getCustomId(line: string): [hash: string, headerContent: string, customId: string] | null {
const m = line.trim().match(headerTest);
if (!m) {
return null;
}
const m1 = m.groups!.headerContent.match(apiTest);
if (!m1) {
return null;
}
const customId = m1.groups!.code.toLowerCase();
return [m[1], m[2], `{#${customId}}`];
}
/** Process a md file. Rewrites the file content if custom ids are needed. */
async function processFile(path: string): Promise<void> {
const context = await fs.readFile(path, {encoding: 'utf-8'});
let changed = false;
const lines = context.split('\n').map(line => {
const customId = getCustomId(line);
if (customId) {
changed = true;
return customId.join(' ');
}
return line;
});
if (changed) {
console.log(path);
await fs.writeFile(path, lines.join('\n'));
}
}
/** Recursively get all files within the given directory. */
async function listFiles(path: string, extension: string): Promise<string[]> {
const result: string[] = [];
const items = await fs.readdir(path);
for (const item of items) {
const itemPath = `${path}/${item}`;
const info = await fs.lstat(itemPath);
if (item.endsWith(extension) && info.isFile()) {
result.push(itemPath);
} else if (info.isDirectory()) {
const files = await listFiles(itemPath, extension);
result.push(...files);
}
}
return result;
}
/* Test utils */
function expect(value1: any, value2: any, message: string) {
if (value1 === value2) {
console.log('Ok', message, value2 || '');
} else {
console.log('Not ok', message);
throw new Error(`Expect ${value2}, got ${value1}`);
}
}