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