• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Build all.html by combining the generated toc and apicontent from each
2// of the generated html files.
3
4import fs from 'fs';
5import buildCSSForFlavoredJS from './buildCSSForFlavoredJS.mjs';
6
7const source = new URL('../../out/doc/api/', import.meta.url);
8
9// Get a list of generated API documents.
10const htmlFiles = fs.readdirSync(source, 'utf8')
11  .filter((name) => name.includes('.html') && name !== 'all.html');
12
13// Read the table of contents.
14const toc = fs.readFileSync(new URL('./index.html', source), 'utf8');
15
16// Extract (and concatenate) the toc and apicontent from each document.
17let contents = '';
18let apicontent = '';
19
20// Identify files that should be skipped. As files are processed, they
21// are added to this list to prevent dupes.
22const seen = new Set(['all.html', 'index.html']);
23
24for (const link of toc.match(/<a.*?>/g)) {
25  const href = /href="(.*?)"/.exec(link)[1];
26  if (!htmlFiles.includes(href) || seen.has(href)) continue;
27  const data = fs.readFileSync(new URL(`./${href}`, source), 'utf8');
28
29  // Split the doc.
30  const match = /(<\/ul>\s*)?<\/\w+>\s*<\w+ id="apicontent">/.exec(data);
31
32  // Get module name
33  const moduleName = href.replace(/\.html$/, '');
34
35  contents += data.slice(0, match.index)
36    .replace(/[\s\S]*?id="toc"[^>]*>\s*<\w+>.*?<\/\w+>\s*(<ul>\s*)?/, '')
37    // Prefix TOC links with current module name
38    .replace(/<a href="#(?!DEP[0-9]{4})([^"]+)"/g, (match, anchor) => {
39      return `<a href="#all_${moduleName}_${anchor}"`;
40    });
41
42  apicontent += '<section>' + data.slice(match.index + match[0].length)
43    .replace(/<!-- API END -->[\s\S]*/, '</section>')
44    // Prefix all in-page anchor marks with module name
45    .replace(/<a class="mark" href="#([^"]+)" id="([^"]+)"/g, (match, anchor, id) => {
46      if (anchor !== id) throw new Error(`Mark does not match: ${anchor} should match ${id}`);
47      return `<a class="mark" href="#all_${moduleName}_${anchor}" id="all_${moduleName}_${anchor}"`;
48    })
49    // Prefix all in-page links with current module name
50    .replace(/<a href="#(?!DEP[0-9]{4})([^"]+)"/g, (match, anchor) => {
51      return `<a href="#all_${moduleName}_${anchor}"`;
52    })
53    // Update footnote id attributes on anchors
54    .replace(/<a href="([^"]+)" id="(user-content-fn[^"]+)"/g, (match, href, id) => {
55      return `<a href="${href}" id="all_${moduleName}_${id}"`;
56    })
57    // Update footnote id attributes on list items
58    .replace(/<(\S+) id="(user-content-fn[^"]+)"/g, (match, tagName, id) => {
59      return `<${tagName} id="all_${moduleName}_${id}"`;
60    })
61    // Prefix all links to other docs modules with those module names
62    .replace(/<a href="((\w[^#"]*)\.html)#/g, (match, href, linkModule) => {
63      if (!htmlFiles.includes(href)) return match;
64      return `<a href="#all_${linkModule}_`;
65    })
66    .trim() + '\n';
67
68  // Mark source as seen.
69  seen.add(href);
70}
71
72// Replace various mentions of index with all.
73let all = toc.replace(/index\.html/g, 'all.html')
74  .replace('<a href="all.html">', '<a href="index.html">')
75  .replace('index.json', 'all.json')
76  .replace('api-section-index', 'api-section-all')
77  .replace('data-id="index"', 'data-id="all"')
78  .replace(/<li class="edit_on_github">.*?<\/li>/, '');
79
80// Clean up the title.
81all = all.replace(/<title>.*?\| /, '<title>');
82
83// Insert the combined table of contents.
84const tocStart = /<!-- TOC -->/.exec(all);
85all = all.slice(0, tocStart.index + tocStart[0].length) +
86  '<details id="toc" open><summary>Table of contents</summary>\n' +
87  '<ul>\n' + contents + '</ul>\n' +
88  '</details>\n' +
89  all.slice(tocStart.index + tocStart[0].length);
90
91// Replace apicontent with the concatenated set of apicontents from each source.
92const apiStart = /<\w+ id="apicontent">\s*/.exec(all);
93const apiEnd = all.lastIndexOf('<!-- API END -->');
94all = all.slice(0, apiStart.index + apiStart[0].length)
95    .replace(
96      '\n</head>',
97      buildCSSForFlavoredJS(new Set(Array.from(
98        apicontent.matchAll(/(?<=<pre class="with-)\d+(?=-chars">)/g),
99        (x) => Number(x[0]),
100      ))) + '\n</head>',
101    ) +
102  apicontent +
103  all.slice(apiEnd);
104
105// Write results.
106fs.writeFileSync(new URL('./all.html', source), all, 'utf8');
107
108// Validate all hrefs have a target.
109const idRe = / id="([^"]+)"/g;
110const ids = new Set([...all.matchAll(idRe)].map((match) => match[1]));
111
112const hrefRe = / href="#([^"]+)"/g;
113const hrefMatches = all.matchAll(hrefRe);
114for (const match of hrefMatches) {
115  if (!ids.has(match[1])) throw new Error(`link not found: ${match[1]}`);
116}
117