// Copyright (C) 2020 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. const ejs = require('ejs'); const marked = require('marked'); const argv = require('yargs').argv const fs = require('fs-extra'); const path = require('path'); const hljs = require('highlight.js'); const CS_BASE_URL = 'https://cs.android.com/android/platform/superproject/+/master:external/perfetto'; const ROOT_DIR = path.dirname(path.dirname(path.dirname(__dirname))); const DOCS_DIR = path.join(ROOT_DIR, 'docs'); let outDir = ''; let curMdFile = ''; let title = ''; function hrefInDocs(href) { if (href.match(/^(https?:)|^(mailto:)|^#/)) { return undefined; } let pathFromRoot; if (href.startsWith('/')) { pathFromRoot = href; } else { curDocDir = '/' + path.relative(ROOT_DIR, path.dirname(curMdFile)); pathFromRoot = path.join(curDocDir, href); } if (pathFromRoot.startsWith('/docs/')) { return pathFromRoot; } return undefined; } function assertNoDeadLink(relPathFromRoot) { relPathFromRoot = relPathFromRoot.replace(/\#.*$/g, ''); // Remove #line. // Skip check for build-time generated reference pages. if (relPathFromRoot.endsWith('.autogen')) return; const fullPath = path.join(ROOT_DIR, relPathFromRoot); if (!fs.existsSync(fullPath) && !fs.existsSync(fullPath + '.md')) { const msg = `Dead link: ${relPathFromRoot} in ${curMdFile}`; console.error(msg); throw new Error(msg); } } function renderHeading(text, level) { // If the heading has an explicit ${#anchor}, use that. Otherwise infer the // anchor from the text but only for h2 and h3. Note the right-hand-side TOC // is dynamically generated from anchors (explicit or implicit). if (level === 1 && !title) { title = text; } let anchorId = ''; const explicitAnchor = /{#([\w-_.]+)}/.exec(text); if (explicitAnchor) { text = text.replace(explicitAnchor[0], ''); anchorId = explicitAnchor[1]; } else if (level >= 2 && level <= 3) { anchorId = text.toLowerCase().replace(/[^\w]+/g, '-'); anchorId = anchorId.replace(/[-]+/g, '-'); // Drop consecutive '-'s. } let anchor = ''; if (anchorId) { anchor = ``; } return `${anchor}${text}`; } function renderLink(originalLinkFn, href, title, text) { if (href.startsWith('../')) { throw new Error( `Don\'t use relative paths in docs, always use /docs/xxx ` + `or /src/xxx for both links to docs and code (${href})`) } const docsHref = hrefInDocs(href); let sourceCodeLink = undefined; if (docsHref !== undefined) { // Check that the target doc exists. Skip the check on /reference/ files // that are typically generated at build time. assertNoDeadLink(docsHref); href = docsHref.replace(/[.](md|autogen)\b/, ''); href = href.replace(/\/README$/, '/'); } else if (href.startsWith('/') && !href.startsWith('//')) { // /tools/xxx -> github/tools/xxx. sourceCodeLink = href; } if (sourceCodeLink !== undefined) { // Fix up line anchors for GitHub link: #42 -> #L42. sourceCodeLink = sourceCodeLink.replace(/#(\d+)$/g, '#L$1') assertNoDeadLink(sourceCodeLink); href = CS_BASE_URL + sourceCodeLink; } return originalLinkFn(href, title, text); } function renderCode(text, lang) { if (lang === 'mermaid') { return `
${text}
`; } let hlHtml = ''; if (lang) { hlHtml = hljs.highlight(lang, text).value } else { hlHtml = hljs.highlightAuto(text).value } return `${hlHtml}` } function renderImage(originalImgFn, href, title, text) { const docsHref = hrefInDocs(href); if (docsHref !== undefined) { const outFile = outDir + docsHref; const outParDir = path.dirname(outFile); fs.ensureDirSync(outParDir); fs.copyFileSync(ROOT_DIR + docsHref, outFile); } if (href.endsWith('.svg')) { return `` } return originalImgFn(href, title, text); } function renderParagraph(text) { let cssClass = ''; if (text.startsWith('NOTE:')) { cssClass = 'note'; }  else if (text.startsWith('TIP:')) { cssClass = 'tip'; }  else if (text.startsWith('TODO:') || text.startsWith('FIXME:')) { cssClass = 'todo'; }  else if (text.startsWith('WARNING:')) { cssClass = 'warning'; }  else if (text.startsWith('Summary:')) { cssClass = 'summary'; } if (cssClass != '') { cssClass = ` class="callout ${cssClass}"`; } return `${text}

\n`; } function render(rawMarkdown) { const renderer = new marked.Renderer(); const originalLinkFn = renderer.link.bind(renderer); const originalImgFn = renderer.image.bind(renderer); renderer.link = (hr, ti, te) => renderLink(originalLinkFn, hr, ti, te); renderer.image = (hr, ti, te) => renderImage(originalImgFn, hr, ti, te); renderer.code = renderCode; renderer.heading = renderHeading; renderer.paragraph = renderParagraph; return marked(rawMarkdown, {renderer: renderer}); } function main() { const inFile = argv['i']; const outFile = argv['o']; outDir = argv['odir']; const templateFile = argv['t']; if (!outFile || !outDir) { console.error( 'Usage: --odir site -o out.html [-i input.md] [-t templ.html]'); process.exit(1); } curMdFile = inFile; let markdownHtml = ''; if (inFile) { markdownHtml = render(fs.readFileSync(inFile, 'utf8')); } if (templateFile) { // TODO rename nav.html to sitemap or something more mainstream. const navFilePath = path.join(outDir, 'docs', '_nav.html'); const fallbackTitle = 'Perfetto - System profiling, app tracing and trace analysis'; const templateData = { markdown: markdownHtml, title: title ? `${title} - Perfetto Tracing Docs` : fallbackTitle, fileName: '/' + outFile.split('/').slice(1).join('/'), }; if (fs.existsSync(navFilePath)) { templateData['nav'] = fs.readFileSync(navFilePath, 'utf8'); } ejs.renderFile(templateFile, templateData, (err, html) => { if (err) throw err; fs.writeFileSync(outFile, html); process.exit(0); }); } else { fs.writeFileSync(outFile, markdownHtml); process.exit(0); } } main();