238 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
const assert = require('assert-plus');
 | 
						|
const cheerio = require('cheerio');
 | 
						|
const Encoder = require('node-html-encoder').Encoder;
 | 
						|
const fs = require('fs-extra');
 | 
						|
const path = require('canonical-path');
 | 
						|
 | 
						|
module.exports = function dabFactory(ngIoProjPath) {
 | 
						|
    const encoder = new Encoder('entity');
 | 
						|
 | 
						|
    // Get the functionality we need from the dgeni package by the same name.
 | 
						|
    const dartApiBuilderDgeniProjPath = 'tools/api-builder/dart-package';
 | 
						|
    const dab = require(path.resolve(ngIoProjPath, dartApiBuilderDgeniProjPath)).module;
 | 
						|
 | 
						|
    const log = dab.logFactory[1]();
 | 
						|
    const dartPkgConfigInfo = dab.dartPkgConfigInfo[1]();
 | 
						|
    const preprocessDartDocData = dab.preprocessDartDocData[1](log, dartPkgConfigInfo);
 | 
						|
    const loadDartDocDataProcessor = dab.loadDartDocDataProcessor[1](log, dartPkgConfigInfo, preprocessDartDocData);
 | 
						|
    const apiListDataFileService = dab.apiListDataFileService[1](log, dartPkgConfigInfo);
 | 
						|
    const Array_from = dab.arrayFromIterable[1];
 | 
						|
 | 
						|
    // Load API data, then create and save 'api-list.json'.
 | 
						|
    function loadApiDataAndSaveToApiListFile() {
 | 
						|
        const docs = [];
 | 
						|
        loadDartDocDataProcessor.$process(docs);
 | 
						|
        log.debug('Number of Dart API entries loaded:', docs.length);
 | 
						|
        var libMap = apiListDataFileService.createDataAndSaveToFile(docs);
 | 
						|
        for (let name in libMap) {
 | 
						|
            log.debug('  ', name, 'has', libMap[name].length, 'top-level entries');
 | 
						|
        }
 | 
						|
        return docs;
 | 
						|
    }
 | 
						|
 | 
						|
    // Create and save the container's '_data.json' file.
 | 
						|
    function _createDirData(containerName, destDirPath, entries) {
 | 
						|
        const entryNames = Array_from(entries.keys()).sort();
 | 
						|
        const dataMap = Object.create(null);
 | 
						|
        entryNames.map((n) => {
 | 
						|
            const e = entries.get(n);
 | 
						|
            assert.object(e, `entry named ${n}`);
 | 
						|
            dataMap[path.basename(e.path, '.html')] = e;
 | 
						|
        });
 | 
						|
        const dataFilePath = path.resolve(destDirPath, '_data.json');
 | 
						|
        fs.writeFile(dataFilePath, JSON.stringify(dataMap, null, 2));
 | 
						|
        log.info(containerName, 'wrote', Object.keys(dataMap).length, 'entries to', dataFilePath);
 | 
						|
    }
 | 
						|
 | 
						|
    function _adjustDocsRelativeLinks($, div) {
 | 
						|
        // Omit leading https://angular.io so links work for local test sites.
 | 
						|
        const urlToDocs = '/docs/dart/latest/';
 | 
						|
        const urlToExamples = 'http://angular-examples.github.io/';
 | 
						|
        const docsLinkList = div.find('a[href^="docs/"],a[href^="examples/"]');
 | 
						|
        docsLinkList.each((i, elt) => {
 | 
						|
            const href = $(elt).attr('href');
 | 
						|
            const matches = href.match(/(\w+)\/(.*)$/);
 | 
						|
            // TODO: support links to chapters of other languages, e.g., 'docs/ts/latest/...'.
 | 
						|
            const urlStart = matches[1] === 'docs' ? urlToDocs : urlToExamples;
 | 
						|
            const absHref = urlStart + matches[2];
 | 
						|
            log.info(`Found angular.io relative link: ${href} --> ${absHref}`);
 | 
						|
            $(elt).attr('href', absHref);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    function _insertExampleFragments(enclosedByName, eltId, $, div) {
 | 
						|
        const fragDirBase = path.join(dartPkgConfigInfo.ngIoDartApiDocPath, '../../../_fragments/');
 | 
						|
        const exList = div.find('p:contains("{@example")');
 | 
						|
        exList.each((i, elt) => {
 | 
						|
            const text = $(elt).text();
 | 
						|
            log.debug(`Found example: ${enclosedByName} ${eltId}`, text);
 | 
						|
            const matches = text.match(/^\s*{@example\s+([^\s]+)(\s+region=[\'\"]?([-\w]+)[\'\"]?)?\s*}([\s\S]*)$/);
 | 
						|
            if (!matches) {
 | 
						|
                log.warn(enclosedByName, eltId, 'has an invalidly formed @example tag:', text);
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
            // const [, exRelPath, /*regionTagAndValue*/, region, rest] = matches;
 | 
						|
            const rest = matches[4].trim();
 | 
						|
            if (rest) log.warn(enclosedByName, eltId, '@example must be the only element in a paragraph, but found:', text);
 | 
						|
            const exRelPath = matches[1];
 | 
						|
            const region = matches[3];
 | 
						|
 | 
						|
            let exRelPathParts = path.dirname(exRelPath).split(path.sep);
 | 
						|
            let fragDir;
 | 
						|
            if (exRelPathParts[0] === 'docs') {
 | 
						|
                // Path is to a docs example, not an API example.
 | 
						|
                const exampleName = exRelPathParts[1];
 | 
						|
                fragDir = path.join(fragDirBase, exampleName, 'dart');
 | 
						|
                exRelPathParts = exRelPathParts.slice(2);
 | 
						|
            } else {
 | 
						|
                fragDir = path.join(fragDirBase, '_api');
 | 
						|
            }
 | 
						|
            const extn = path.extname(exRelPath);
 | 
						|
            const baseName = path.basename(exRelPath, extn);
 | 
						|
            const fileNameNoExt = baseName + (region ? `-${region}` : '')
 | 
						|
            const exFragPath = path.resolve(fragDir, ...exRelPathParts, `${fileNameNoExt}${extn}.md`);
 | 
						|
            if (!fs.existsSync(exFragPath)) {
 | 
						|
                log.warn('Fragment not found:', exFragPath);
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
            $(elt).empty();
 | 
						|
            const md = fs.readFileSync(exFragPath, 'utf8');
 | 
						|
            const codeElt = _extractAndWrapInCodeTags(md);
 | 
						|
            $(elt).html(codeElt);
 | 
						|
            log.silly('Fragment code in html:', $(elt).html());
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    function _extractAndWrapInCodeTags(md) {
 | 
						|
        const lines = md.split('\n');
 | 
						|
        // Drop first and last lines that are the code markdown tripple ticks (and last \n):
 | 
						|
        lines.shift();
 | 
						|
        while (lines && lines.pop().trim() !== '```') {}
 | 
						|
        const code = lines.map((line) => encoder.htmlEncode(line)).join('\n');
 | 
						|
        // TS uses format="linenums"; removing that for now.
 | 
						|
        return `<code-example language="dart">${code}\n</code-example>`;
 | 
						|
    }
 | 
						|
 | 
						|
    function _createEntryJadeFile(e, destDirPath) {
 | 
						|
        const htmlPagePath = path.resolve(dartPkgConfigInfo.ngDartDocPath, e.path);
 | 
						|
        if (!fs.existsSync(htmlPagePath)) {
 | 
						|
            log.warn('Entry', e.name, ': expected to find file but didn\'t', htmlPagePath);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        const html = fs.readFileSync(htmlPagePath, 'utf8');
 | 
						|
        log.debug('Reading (and then deleting)', html.length, 'chars from', htmlPagePath);
 | 
						|
        const $ = cheerio.load(html);
 | 
						|
        const div = $('div.body.container');
 | 
						|
        $('div.sidebar-offcanvas-left').remove();
 | 
						|
        const baseNameNoExtn = path.basename(e.path, '.html');
 | 
						|
        _adjustDocsRelativeLinks($, div);
 | 
						|
        _insertExampleFragments(e.enclosedByQualifiedName, baseNameNoExtn, $, div);
 | 
						|
 | 
						|
        const outFileNoExtn = path.join(destDirPath, baseNameNoExtn);
 | 
						|
        const depth = path.dirname(e.path).split('/').length;
 | 
						|
        assert(depth === 1 || depth == 2, 'depth ' + depth);
 | 
						|
        const jadeFilePath = path.resolve(outFileNoExtn + '.jade');
 | 
						|
        const breadcrumbs = $('header > nav ol.breadcrumbs');
 | 
						|
        fs.writeFileSync(jadeFilePath, apiEntryJadeTemplate(depth, breadcrumbs, div));
 | 
						|
        // In case harp cached the .html version, remove it since it will be generated.
 | 
						|
        try {
 | 
						|
            fs.unlinkSync(path.resolve(outFileNoExtn + '.html'));
 | 
						|
        } catch (err) {
 | 
						|
            if (e.enclosedBy && e.enclosedBy.type === 'class' &&
 | 
						|
                e.enclosedBy.name.toLowerCase() === e.name.toLowerCase()) {
 | 
						|
                // Do nothing since this is a known bug with dartdoc:
 | 
						|
                // https://github.com/dart-lang/dartdoc/issues/1196
 | 
						|
            } else {
 | 
						|
                console.error(err);
 | 
						|
                console.error(`Output path: ${destDirPath}`);
 | 
						|
                console.error(`Entity: ${e}`);
 | 
						|
                console.error(err.stack);
 | 
						|
                throw err;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        log.debug('  ', e.enclosedByQualifiedName, 'entry', e.name, 'wrote to ', jadeFilePath);
 | 
						|
    }
 | 
						|
 | 
						|
    function _createJadeFiles(containerName, destDirPath, entries) {
 | 
						|
        let numApiPagesWritten = 0;
 | 
						|
        for (let name of entries.keys()) {
 | 
						|
            _createEntryJadeFile(entries.get(name), destDirPath);
 | 
						|
            numApiPagesWritten++
 | 
						|
        }
 | 
						|
        log.info(containerName, 'created', numApiPagesWritten, 'Jade entry files.');
 | 
						|
        return numApiPagesWritten;
 | 
						|
    }
 | 
						|
 | 
						|
    function createApiDataAndJadeFiles(docs) {
 | 
						|
        let numApiPagesWritten = 0;
 | 
						|
        let map = apiListDataFileService.containerToEntryMap;
 | 
						|
        for (let name of map.keys()) {
 | 
						|
            if (!name) continue; // skip package-level
 | 
						|
            let destDirPath = path.resolve(dartPkgConfigInfo.ngIoDartApiDocPath, name);
 | 
						|
            let entries;
 | 
						|
            if (!fs.existsSync(destDirPath)) {
 | 
						|
                log.error(`Dartdoc API folder not found:`, destDirPath);
 | 
						|
            } else if ((entries = map.get(name)).size > 0) {
 | 
						|
                _createDirData(name, destDirPath, entries);
 | 
						|
                numApiPagesWritten += _createJadeFiles(name, destDirPath, entries);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return numApiPagesWritten;
 | 
						|
    }
 | 
						|
 | 
						|
    const _self = {
 | 
						|
        Array_from: Array_from,
 | 
						|
        apiEntryJadeTemplate: apiEntryJadeTemplate,
 | 
						|
        apiListDataFileService: apiListDataFileService,
 | 
						|
        loadApiDataAndSaveToApiListFile: loadApiDataAndSaveToApiListFile,
 | 
						|
        createApiDataAndJadeFiles: createApiDataAndJadeFiles,
 | 
						|
        dartPkgConfigInfo: dartPkgConfigInfo,
 | 
						|
        loadDartDocDataProcessor: loadDartDocDataProcessor,
 | 
						|
        log: log,
 | 
						|
        preprocessDartDocData: preprocessDartDocData,
 | 
						|
    };
 | 
						|
    Object.freeze(_self);
 | 
						|
    return _self;
 | 
						|
};
 | 
						|
 | 
						|
function _indentedEltHtml($elt, i, filterFnOpt) {
 | 
						|
    let lines = $elt.html().split('\n');
 | 
						|
    if (filterFnOpt) lines = lines.filter(filterFnOpt);
 | 
						|
    const indent = '                    '.substring(0,i);
 | 
						|
    return lines.map((line) => `${indent}| ${line}`).join('\n');
 | 
						|
}
 | 
						|
 | 
						|
function apiEntryJadeTemplate(baseHrefDepth, $breadcrumbs, $mainDiv) {
 | 
						|
    const baseHref = path.join(...Array(baseHrefDepth).fill('..'));
 | 
						|
    // TODO/investigate: for some reason $breadcrumbs.html() is missing the <ol></ol>. We add it back in the template below.
 | 
						|
    const breadcrumbs = _indentedEltHtml($breadcrumbs, 6, (line) => !line.match(/^\s*$/));
 | 
						|
    const mainDivHtml = _indentedEltHtml($mainDiv, 4);
 | 
						|
    // WARNING: since the following is Jade, indentation is significant.
 | 
						|
    const result = `
 | 
						|
extends ${baseHref}/../../../_layout-dart-api
 | 
						|
 | 
						|
include ${baseHref}/../_util-fns
 | 
						|
 | 
						|
block head-extra
 | 
						|
  // generated Dart API page template: head-extra
 | 
						|
  //- <base> is required because all the links in dartdoc generated pages are "pseudo-absolute"
 | 
						|
  base(href="${baseHref}")
 | 
						|
  link(rel="stylesheet" href="static-assets/styles.css")
 | 
						|
 | 
						|
block breadcrumbs
 | 
						|
  // generated Dart API page template: breadcrumbs
 | 
						|
  .banner
 | 
						|
    ol.breadcrumbs.gt-separated.hidden-xs
 | 
						|
${breadcrumbs}
 | 
						|
 | 
						|
block main-content
 | 
						|
  // generated Dart API page template: main-content: start
 | 
						|
  div.dart-api-entry-main
 | 
						|
${mainDivHtml}
 | 
						|
  // generated Dart API page template: main-content: end
 | 
						|
`;
 | 
						|
        return result;
 | 
						|
    }
 |