From 2fd162425da9899ba961b5405ef585cf88b71cd6 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Wed, 10 Aug 2016 10:36:23 -0700 Subject: [PATCH] chore(dart): api doc builder enhancements (#2050) - #2049, support ng.io doc relative links and code-regions - Change dartdoc output folder to `docs/api` (from `doc/api`). --- gulpfile.js | 24 ++++++++++------ tools/api-builder/dart-package/test.js | 2 +- tools/dart-api-builder/dab.js | 40 ++++++++++++++++++++++---- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index c6e15ae13f..063669f2e9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -604,20 +604,19 @@ gulp.task('build-dart-cheatsheet', [], function() { gulp.task('dartdoc', ['pub upgrade'], function() { const ngRepoPath = ngPathFor('dart'); - if (argv.fast && fs.existsSync(path.resolve(ngRepoPath, 'doc'))) { - gutil.log('Skipping dartdoc: --fast flag enabled and "doc" dir exists'); + if (argv.fast && fs.existsSync(path.resolve(ngRepoPath, 'docs', 'api'))) { + gutil.log('Skipping dartdoc: --fast flag enabled and "docs/api" dir exists'); return true; } checkAngularProjectPath(ngRepoPath); const topLevelLibFilePath = path.resolve(ngRepoPath, 'lib', 'angular2.dart'); const tmpPath = topLevelLibFilePath + '.disabled'; - if (!fs.existsSync(topLevelLibFilePath)) throw new Error(`Missing file: ${topLevelLibFilePath}`); - fs.renameSync(topLevelLibFilePath, tmpPath); + renameIfExistsSync(topLevelLibFilePath, tmpPath); gutil.log(`Hiding top-level angular2 library: ${topLevelLibFilePath}`); - const dartdoc = spawnExt('dartdoc', ['--output', 'doc/api', '--add-crossdart'], { cwd: ngRepoPath}); + const dartdoc = spawnExt('dartdoc', ['--output', 'docs/api', '--add-crossdart'], { cwd: ngRepoPath}); return dartdoc.promise.finally(() => { gutil.log(`Restoring top-level angular2 library: ${topLevelLibFilePath}`); - fs.renameSync(tmpPath, topLevelLibFilePath); + renameIfExistsSync(tmpPath, topLevelLibFilePath); }) }); @@ -1235,15 +1234,14 @@ function buildDartCheatsheet() { function buildApiDocsForDart() { - const apiDir = 'api'; const vers = 'latest'; const dab = require('./tools/dart-api-builder/dab')(ANGULAR_IO_PROJECT_PATH); const log = dab.log; log.level = _dgeniLogLevel; const dabInfo = dab.dartPkgConfigInfo; - dabInfo.ngIoDartApiDocPath = path.join(DOCS_PATH, 'dart', vers, apiDir); - dabInfo.ngDartDocPath = path.join(ngPathFor('dart'), 'doc', apiDir); + dabInfo.ngIoDartApiDocPath = path.join(DOCS_PATH, 'dart', vers, 'api'); + dabInfo.ngDartDocPath = path.join(ngPathFor('dart'), 'docs', 'api'); // Exclude API entries for developer/internal libraries. Also exclude entries for // the top-level catch all "angular2" library (otherwise every entry appears twice). dabInfo.excludeLibRegExp = new RegExp(/^(?!angular2)|\.testing|_|codegen|^angular2$/); @@ -1455,3 +1453,11 @@ function checkAngularProjectPath(_ngPath) { if (fs.existsSync(ngPath)) return; throw new Error('API related tasks require the angular2 repo to be at ' + ngPath); } + +function renameIfExistsSync(oldPath, newPath) { + if (fs.existsSync(oldPath)) { + fs.renameSync(oldPath, newPath); + } else { + gutil.log(`renameIfExistsSync cannot find file to rename: ${oldPath}`); + } +} diff --git a/tools/api-builder/dart-package/test.js b/tools/api-builder/dart-package/test.js index 22ffb7f73d..bba07a62b9 100644 --- a/tools/api-builder/dart-package/test.js +++ b/tools/api-builder/dart-package/test.js @@ -14,7 +14,7 @@ const apiDocPath = path.join(DOCS_PATH, 'dart/latest/api'); dartPkg.config(function (dartPkgConfigInfo) { dartPkgConfigInfo.ngIoDartApiDocPath = apiDocPath; - dartPkgConfigInfo.ngDartDocPath = path.join(ANGULAR_IO_PROJECT_PATH, '../ngdart/doc/api'); + dartPkgConfigInfo.ngDartDocPath = path.join(ANGULAR_IO_PROJECT_PATH, '../angular-dart/docs/api'); }); const dgeni = new Dgeni([dartPkg]); diff --git a/tools/dart-api-builder/dab.js b/tools/dart-api-builder/dab.js index b314719f02..ea1c5b946d 100644 --- a/tools/dart-api-builder/dab.js +++ b/tools/dart-api-builder/dab.js @@ -46,25 +46,53 @@ module.exports = function dabFactory(ngIoProjPath) { 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 fragDir = path.join(dartPkgConfigInfo.ngIoDartApiDocPath, '../../../_fragments/_api'); + 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(/{@example\s+([^\s]+)(\s+region=[\'\"]?(\w+)[\'\"]?)?\s*}/); + 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]; - const dir = path.dirname(exRelPath) + 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, dir, `${fileNameNoExt}${extn}.md`); + const exFragPath = path.resolve(fragDir, ...exRelPathParts, `${fileNameNoExt}${extn}.md`); if (!fs.existsSync(exFragPath)) { log.warn('Fragment not found:', exFragPath); return true; @@ -80,7 +108,8 @@ module.exports = function dabFactory(ngIoProjPath) { 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(); lines.pop(); lines.pop(); + 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}\n`; @@ -98,6 +127,7 @@ module.exports = function dabFactory(ngIoProjPath) { 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);