// 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 `
${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();