android13/external/perfetto/infra/perfetto.dev/src/markdown_render.js

229 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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)));
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 = `<a name="${anchorId}" class="anchor" href="#${anchorId}"></a>`;
}
return `<h${level}>${anchor}${text}</h${level}>`;
}
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 `<div class="mermaid">${text}</div>`;
}
let hlHtml = '';
if (lang) {
hlHtml = hljs.highlight(lang, text).value
} else {
hlHtml = hljs.highlightAuto(text).value
}
return `<code class="hljs code-block">${hlHtml}</code>`
}
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 `<object type="image/svg+xml" data="${href}"></object>`
}
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}"`;
}
// Rudimentary support of definition lists.
var colonStart = text.search("\n:")
if (colonStart != -1) {
var key = text.substring(0, colonStart);
var value = text.substring(colonStart + 2);
return `<dl><dt><p>${key}</p></dt><dd><p>${value}</p></dd></dl>`
}
return `<p${cssClass}>${text}</p>\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: '/' + path.relative(outDir, outFile),
};
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();