docs(api/dart): add support for generation and display (#1888)

Fixes #1880.
Supersedes #1593.
This commit is contained in:
Patrice Chalin 2016-07-15 14:10:12 -07:00 committed by Kathy Walrath
parent 08d051d2f6
commit 1bef20abc4
19 changed files with 737 additions and 28 deletions

View File

@ -15,13 +15,13 @@ env:
- TASK=lint - TASK=lint
- TASK="run-e2e-tests --fast" SCRIPT=examples-install.sh - TASK="run-e2e-tests --fast" SCRIPT=examples-install.sh
- TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh - TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh
- TASK=harp-compile SCRIPT=deploy-install.sh - TASK=build-compile SCRIPT=deploy-install.sh
- TASK=harp-compile SCRIPT=deploy-install-preview.sh - TASK=build-compile SCRIPT=deploy-install-preview.sh
matrix: matrix:
fast_finish: true fast_finish: true
allow_failures: allow_failures:
- env: "TASK=\"run-e2e-tests --fast\" SCRIPT=examples-install-preview.sh" - env: "TASK=\"run-e2e-tests --fast\" SCRIPT=examples-install-preview.sh"
- env: "TASK=harp-compile SCRIPT=deploy-install-preview.sh" - env: "TASK=build-compile SCRIPT=deploy-install-preview.sh"
before_install: before_install:
- npm install -g gulp --no-optional - npm install -g gulp --no-optional
install: install:

View File

@ -30,6 +30,7 @@ var tslint = require('gulp-tslint');
// 2. Think about using spawn instead of exec in case of long error messages. // 2. Think about using spawn instead of exec in case of long error messages.
var TOOLS_PATH = './tools'; var TOOLS_PATH = './tools';
var ANGULAR_IO_PROJECT_PATH = path.resolve('.');
var ANGULAR_PROJECT_PATH = '../angular'; var ANGULAR_PROJECT_PATH = '../angular';
var PUBLIC_PATH = './public'; var PUBLIC_PATH = './public';
var TEMP_PATH = './_temp'; var TEMP_PATH = './_temp';
@ -63,12 +64,21 @@ var _devguideShredJadeOptions = {
}; };
var _apiShredOptions = { var _apiShredOptions = {
lang: 'ts',
examplesDir: path.join(ANGULAR_PROJECT_PATH, 'modules/@angular/examples'), examplesDir: path.join(ANGULAR_PROJECT_PATH, 'modules/@angular/examples'),
fragmentsDir: path.join(DOCS_PATH, '_fragments/_api'), fragmentsDir: path.join(DOCS_PATH, '_fragments/_api'),
zipDir: path.join(RESOURCES_PATH, 'zips/api'), zipDir: path.join(RESOURCES_PATH, 'zips/api'),
logLevel: _dgeniLogLevel logLevel: _dgeniLogLevel
}; };
var _apiShredOptionsForDart = {
lang: 'dart',
examplesDir: path.resolve(ngPathFor('dart'), 'examples'),
fragmentsDir: path.join(DOCS_PATH, '_fragments/_api'),
zipDir: path.join(RESOURCES_PATH, 'zips/api'),
logLevel: _dgeniLogLevel
};
var _excludePatterns = ['**/node_modules/**', '**/typings/**', '**/packages/**']; var _excludePatterns = ['**/node_modules/**', '**/typings/**', '**/packages/**'];
var _excludeMatchers = _excludePatterns.map(function(excludePattern){ var _excludeMatchers = _excludePatterns.map(function(excludePattern){
@ -96,6 +106,14 @@ var _exampleProtractorBoilerplateFiles = [
var _exampleConfigFilename = 'example-config.json'; var _exampleConfigFilename = 'example-config.json';
var lang, langs;
function configLangs(langOption) {
lang = (langOption || 'all').toLowerCase();
if (lang === 'all') { lang = '(ts|js|dart)'; }
langs = lang.match(/\w+/g); // the languages in `lang` as an array
}
configLangs(argv.lang);
function isDartPath(path) { function isDartPath(path) {
// Testing via indexOf() for now. If we need to match only paths with folders // Testing via indexOf() for now. If we need to match only paths with folders
// named 'dart' vs 'dart*' then try: path.match('/dart(/|$)') != null; // named 'dart' vs 'dart*' then try: path.match('/dart(/|$)') != null;
@ -131,6 +149,7 @@ gulp.task('run-e2e-tests', runE2e);
* all means (ts|js|dart) * all means (ts|js|dart)
*/ */
function runE2e() { function runE2e() {
if (!argv.lang) configLangs('ts|js'); // Exclude dart by default
var promise; var promise;
if (argv.fast) { if (argv.fast) {
// fast; skip all setup // fast; skip all setup
@ -183,8 +202,6 @@ function runE2e() {
// each app/spec collection sequentially. // each app/spec collection sequentially.
function findAndRunE2eTests(filter, outputFile) { function findAndRunE2eTests(filter, outputFile) {
// create an output file with header. // create an output file with header.
var lang = (argv.lang || '(ts|js)').toLowerCase();
if (lang === 'all') { lang = '(ts|js|dart)'; }
var startTime = new Date().getTime(); var startTime = new Date().getTime();
var header = `Doc Sample Protractor Results for ${lang} on ${new Date().toLocaleString()}\n`; var header = `Doc Sample Protractor Results for ${lang} on ${new Date().toLocaleString()}\n`;
header += argv.fast ? header += argv.fast ?
@ -528,7 +545,9 @@ gulp.task('build-docs', ['build-devguide-docs', 'build-api-docs', 'build-plunker
// Stop zipping examples Feb 28, 2016 // Stop zipping examples Feb 28, 2016
//gulp.task('build-docs', ['build-devguide-docs', 'build-api-docs', 'build-plunkers', '_zip-examples']); //gulp.task('build-docs', ['build-devguide-docs', 'build-api-docs', 'build-plunkers', '_zip-examples']);
gulp.task('build-api-docs', ['build-js-api-docs', 'build-ts-api-docs', 'build-dart-cheatsheet']); gulp.task('build-api-docs', ['build-js-api-docs', 'build-ts-api-docs',
// On TRAVIS? Skip building the Dart API docs for now.
...(process.env.TRAVIS ? [] : ['build-dart-api-docs'])]);
gulp.task('build-devguide-docs', ['_shred-devguide-examples', '_shred-devguide-shared-jade'], function() { gulp.task('build-devguide-docs', ['_shred-devguide-examples', '_shred-devguide-shared-jade'], function() {
return buildShredMaps(true); return buildShredMaps(true);
@ -542,12 +561,40 @@ gulp.task('build-js-api-docs', ['_shred-api-examples'], function() {
return buildApiDocs('js'); return buildApiDocs('js');
}); });
gulp.task('build-dart-api-docs', ['_shred-api-examples', 'dartdoc'], function() {
// TODO(chalin): also build build-dart-cheatsheet
return buildApiDocsForDart();
});
gulp.task('build-plunkers', ['_copy-example-boilerplate'], function() { gulp.task('build-plunkers', ['_copy-example-boilerplate'], function() {
return plunkerBuilder.buildPlunkers(EXAMPLES_PATH, LIVE_EXAMPLES_PATH, { errFn: gutil.log }); return plunkerBuilder.buildPlunkers(EXAMPLES_PATH, LIVE_EXAMPLES_PATH, { errFn: gutil.log });
}); });
gulp.task('build-dart-cheatsheet', [], function() { gulp.task('build-dart-cheatsheet', [], function() {
return buildApiDocs('dart'); gutil.log('build-dart-cheatsheet - NOT IMPLEMENTED YET');
// return buildApiDocsForDart();
});
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');
return true;
}
checkAngularProjectPath(ngRepoPath);
const dartdoc = spawnExt('dartdoc', ['--output', 'doc/api', '--add-crossdart'], { cwd: ngRepoPath});
return dartdoc.promise;
});
gulp.task('pub upgrade', [], function() {
const ngRepoPath = ngPathFor('dart');
if (argv.fast && fs.existsSync(path.resolve(ngRepoPath, 'packages'))) {
gutil.log('Skipping pub upgrade: --fast flag enabled and "packages" dir exists');
return true;
}
checkAngularProjectPath(ngRepoPath);
const pubUpgrade = spawnExt('pub', ['upgrade'], { cwd: ngRepoPath});
return pubUpgrade.promise;
}); });
gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){ gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){
@ -596,10 +643,35 @@ gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){
}); });
}); });
gulp.task('harp-compile', ['build-docs'], function() { gulp.task('harp-compile', [], function() {
return harpCompile()
});
gulp.task('serve', [], function() {
// Harp will serve files from workspace.
const cmd = 'npm run harp -- server .';
gutil.log('Launching harp server (over project files)');
gutil.log(` > ${cmd}`);
gutil.log('Note: issuing this command directly from the command line will show harp comiple warnings.');
return execPromise(cmd);
});
gulp.task('serve-www', [], function() {
// Serve generated site.
return execPromise('npm run live-server ./www');
});
gulp.task('build-compile', ['build-docs'], function() {
return harpCompile(); return harpCompile();
}); });
gulp.task('check-serve', ['build-docs'], function() {
return harpCompile().then(function() {
gutil.log('Launching live-server over ./www');
return execPromise('npm run live-server ./www');
});
});
gulp.task('check-deploy', ['build-docs'], function() { gulp.task('check-deploy', ['build-docs'], function() {
return harpCompile().then(function() { return harpCompile().then(function() {
gutil.log('compile ok'); gutil.log('compile ok');
@ -693,8 +765,15 @@ gulp.task('_shred-clean-devguide', function(cb) {
}); });
gulp.task('_shred-api-examples', ['_shred-clean-api'], function() { gulp.task('_shred-api-examples', ['_shred-clean-api'], function() {
checkAngularProjectPath(); const promises = [];
return docShredder.shred(_apiShredOptions); gutil.log('Shredding API examples for languages: ' + langs.join(', '));
langs.forEach((lang) => {
if (lang === 'js') return; // JS is handled via TS.
checkAngularProjectPath(ngPathFor(lang));
const options = lang == 'dart' ? _apiShredOptionsForDart : _apiShredOptions;
promises.push(docShredder.shred(options));
});
return Q.all(promises);
}); });
gulp.task('_shred-clean-api', function(cb) { gulp.task('_shred-clean-api', function(cb) {
@ -1087,8 +1166,8 @@ function buildApiDocs(targetLanguage) {
var dgeni = new Dgeni([package]); var dgeni = new Dgeni([package]);
return dgeni.generate(); return dgeni.generate();
} catch(err) { } catch(err) {
gutil.log(err); console.error(err);
gutil.log(err.stack); console.error(err.stack);
throw err; throw err;
} }
@ -1099,6 +1178,48 @@ function buildApiDocs(targetLanguage) {
} }
} }
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);
// 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$/);
try {
checkAngularProjectPath('dart');
var destPath = dabInfo.ngIoDartApiDocPath;
var sourceDirs = fs.readdirSync(dabInfo.ngDartDocPath)
.filter((name) => !name.match(/^index/))
.map((name) => path.join(dabInfo.ngDartDocPath, name));
log.info(`Building Dart API pages for ${sourceDirs.length} libraries`);
return copyFiles(sourceDirs, [destPath]).then(() => {
log.debug('Finished copying', sourceDirs.length, 'directories from', dabInfo.ngDartDocPath, 'to', destPath);
const apiEntries = dab.loadApiDataAndSaveToApiListFile();
const tmpDocsPath = path.resolve(path.join(process.env.HOME, 'tmp/docs.json'));
if (argv.dumpDocsJson) fs.writeFileSync(tmpDocsPath, JSON.stringify(apiEntries, null, 2));
dab.createApiDataAndJadeFiles(apiEntries);
}).catch((err) => {
console.log(err);
});
} catch(err) {
console.error(err);
console.error(err.stack);
throw err;
}
}
function buildShredMaps(shouldWrite) { function buildShredMaps(shouldWrite) {
var options = { var options = {
devguideExamplesDir: _devguideShredOptions.examplesDir, devguideExamplesDir: _devguideShredOptions.examplesDir,
@ -1270,8 +1391,13 @@ function execCommands(cmds, options, cb) {
}); });
} }
function checkAngularProjectPath() { function ngPathFor(lang) {
if (!fs.existsSync(ANGULAR_PROJECT_PATH)) { return ANGULAR_PROJECT_PATH + (lang === 'dart' ? '-dart' : '');
throw new Error('API related tasks require the angular2 repo to be at ' + path.resolve(ANGULAR_PROJECT_PATH)); }
function checkAngularProjectPath(lang) {
var ngPath = path.resolve(ngPathFor(lang || 'ts'));
if (!fs.existsSync(ngPath)) {
throw new Error('API related tasks require the angular2 repo to be at ' + ngPath);
} }
} }

View File

@ -30,6 +30,7 @@
"broken-link-checker": "0.7.1", "broken-link-checker": "0.7.1",
"browser-sync": "^2.9.3", "browser-sync": "^2.9.3",
"canonical-path": "0.0.2", "canonical-path": "0.0.2",
"cheerio": "^0.20.0",
"cross-spawn": "^4.0.0", "cross-spawn": "^4.0.0",
"codelyzer": "0.0.22", "codelyzer": "0.0.22",
"del": "^2.2.0", "del": "^2.2.0",

View File

@ -1,11 +1,11 @@
// Refer to jade.template.html and addJadeDataDocsProcessor to figure out where the context of this jade file originates // template: public/_includes/_hero
//- Refer to jade.template.html and addJadeDataDocsProcessor to figure out where the context of this jade file originates
- var textFormat = ''; - var textFormat = '';
- var headerTitle = title + (typeof varType !== 'undefined' ? (': ' + varType) : ''); - var headerTitle = title + (typeof varType !== 'undefined' ? (': ' + varType) : '');
- var capitalize = function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } - var capitalize = function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); }
- var useBadges = docType || stability; - var useBadges = docType || stability;
//- renamer :: String -> String
// renamer :: String -> String //- Renames `Let` and `Var` into `Const`
// Renames `Let` and `Var` into `Const`
- var renamer = function renamer(docType) { - var renamer = function renamer(docType) {
- return (docType === 'Let' || docType === 'Var') ? 'Const' : docType - return (docType === 'Let' || docType === 'Var') ? 'Const' : docType
- } - }
@ -13,7 +13,7 @@
if current.path[4] && current.path[3] == 'api' if current.path[4] && current.path[3] == 'api'
- var textFormat = 'is-standard-case' - var textFormat = 'is-standard-case'
header(class="hero background-sky") header(class="hero background-sky", style=fixHeroCss ? "height:auto" : "")
div(class="inner-header") div(class="inner-header")
h1(class="hero-title text-display-1 #{textFormat}") #{headerTitle} h1(class="hero-title text-display-1 #{textFormat}") #{headerTitle}
if useBadges if useBadges
@ -33,5 +33,7 @@ header(class="hero background-sky")
if subtitle if subtitle
h2.hero-subtitle.text-subhead #{subtitle} h2.hero-subtitle.text-subhead #{subtitle}
else if current.path[3] == 'api' && current.path[1] == 'dart'
block breadcrumbs
else if current.path[0] == "docs" else if current.path[0] == "docs"
!= partial("_version-dropdown") != partial("_version-dropdown")

View File

@ -0,0 +1,40 @@
//- WARNING: _layout.jade and _layout-dart-api.jade should match in terms of content
//- except that one uses Harp partial/yield and the other uses Jade extends/include.
doctype
html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework")
// template: public/docs/_layout-dart-api
head
include ../_includes/_head-include
block head-extra
block var-def
body(class="l-offset-nav l-offset-side-nav" ng-controller="AppCtrl as appCtrl")
include ../_includes/_main-nav
if current.path[2]
include _includes/_side-nav
include ../_includes/_hero
include ../_includes/_banner
if current.path[3] == 'api'
if current.path[4] == 'index'
block main-content
else
article(class="l-content-small grid-fluid docs-content")
block main-content
else if current.path.indexOf('cheatsheet') > 0
block main-content
else
if current.path[3] == 'index' || current.path[3] == 'styleguide'
article(class="l-content-small grid-fluid docs-content")
block main-content
else
article(class="l-content-small grid-fluid docs-content")
div(class="c10")
.showcase
.showcase-content
block main-content
if (current.path[3] == 'guide' || current.path[3] == 'tutorial') && current.path[4]
include ../_includes/_next-item
include ../_includes/_footer
include ../_includes/_scripts-include

View File

@ -1,8 +1,13 @@
//- WARNING: _layout.jade and _layout-dart-api.jade should match in terms of content
//- except that one uses Harp partial/yield and the other uses Jade extends/include.
doctype doctype
html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework") html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework")
// template: public/docs/_layout
head head
!= partial("../_includes/_head-include") != partial("../_includes/_head-include")
block head-extra
//-
body(class="l-offset-nav l-offset-side-nav" ng-controller="AppCtrl as appCtrl") body(class="l-offset-nav l-offset-side-nav" ng-controller="AppCtrl as appCtrl")
!= partial("../_includes/_main-nav") != partial("../_includes/_main-nav")
if current.path[2] if current.path[2]

View File

@ -1,10 +1,13 @@
.l-main-section :marked
h2 Beta > **WARNING:** API documentation is preliminary and subject to change.
p. > **Known issues:** Although this generated API reference displays Dart
The proposed Angular 2 API does not yet have Dart-specific documentation. APIs, individual pages sometimes describe TypeScript APIs accompanied with
However, because the Dart and JavaScript APIs are generated from the same source, TypeScript code. The angular.io issue tracker contains [all known
you might find the JavaScript API docs helpful: issues][api-issues]; if you notice others, please [report
them][new-issue]. Thanks!
p.text-center [new-issue]: https://github.com/angular/angular.io/issues/new?labels=dart,api&title=%5BDart%5D%5BAPI%5D%20
<b><a href="/docs/js/latest/api/">Angular 2 API Preview (JavaScript)</a></b> [api-issues]: https://github.com/angular/angular.io/issues?q=label%3Aapi+label%3Adart
api-list(src="api-list.json" lang="dart")

View File

@ -26,6 +26,8 @@ angularIO.directive('apiList', function () {
controller: function($scope, $attrs, $http, $location) { controller: function($scope, $attrs, $http, $location) {
var $ctrl = this; var $ctrl = this;
var isForDart = $attrs.lang === 'dart';
$ctrl.apiTypes = [ $ctrl.apiTypes = [
{ cssClass: 'stable', title: 'Stable', matches: ['stable']}, { cssClass: 'stable', title: 'Stable', matches: ['stable']},
{ cssClass: 'directive', title: 'Directive', matches: ['directive'] }, { cssClass: 'directive', title: 'Directive', matches: ['directive'] },
@ -37,6 +39,9 @@ angularIO.directive('apiList', function () {
{ cssClass: 'const', title: 'Const', matches: ['var', 'let', 'const'] } { cssClass: 'const', title: 'Const', matches: ['var', 'let', 'const'] }
]; ];
if (isForDart) $ctrl.apiTypes = $ctrl.apiTypes.filter((t) =>
!t.cssClass.match(/^(stable|directive|decorator|interface|enum)$/));
$ctrl.apiFilter = getApiFilterFromLocation(); $ctrl.apiFilter = getApiFilterFromLocation();
$ctrl.apiType = getApiTypeFromLocation(); $ctrl.apiType = getApiTypeFromLocation();
$ctrl.groupedSections = []; $ctrl.groupedSections = [];

17
scripts/patch.sh Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -e -o pipefail
TARGET=node_modules/terraform/lib/helpers/raw.js
# Around line 282 change from/to:
# var namespace = sourcePath.split(".")[0].split("/")
# var namespace = sourcePath.split('.').slice(0, -1).join('.').split('/')
if [ -e "$TARGET" ]; then
perl -i.bak -pe 's/^(\s+var namespace.*split\("."\))\[0\]/\1.slice(0, -1).join(".")/' "$TARGET"
echo "Patched '$TARGET'."
else
echo "Nothing to patch. Can't find file '$TARGET'."
exit 1;
fi

View File

@ -0,0 +1,18 @@
'use strict';
var Package = require('dgeni').Package;
var path = require('canonical-path');
module.exports = new Package('dart', [])
.factory(require('./services/apiListDataFileService'))
.factory(require('./services/arrayFromIterable'))
.factory(require('./services/dartPkgConfigInfo'))
.factory(require('./services/logFactory'))
.factory(require('./services/preprocessDartDocData'))
// Register the processors
.processor(require('./processors/loadDartDocData'))
// .processor(require('./processors/createApiListData'))
// .processor(require('./processors/loadDartDocHtml'))
;

View File

@ -0,0 +1,21 @@
'use strict';
const path = require('canonical-path');
module.exports = function loadDartDocDataProcessor(log, dartPkgConfigInfo, preprocessDartDocData) {
return {
// $runAfter: ['reading-docs'],
// $runBefore: ['docs-read'],
$process: function (docs) {
if (docs.length != 0) log.error('Expected docs array to be nonempty.');
const dataFilePath = path.resolve(dartPkgConfigInfo.ngDartDocPath, 'index.json');
const dartDocData = require(dataFilePath);
log.info('Loaded', dartDocData.length, 'dartdoc api entries from', dataFilePath);
preprocessDartDocData.preprocess(dartDocData);
docs.push(...dartDocData);
}
};
};

View File

@ -0,0 +1,45 @@
'use strict';
var path = require('canonical-path');
var fs = require("q-io/fs");
var q = require('q');
var cheerio = require('cheerio');
// Original sample file by @petebacondarwin
// Not currently used, but keeping it for now,
// until we completely rule out use of dgeni.
module.exports = function loadDartDocHtmlProcessor(log, dartPkgConfigInfo) {
return {
$runAfter: ['loadDartDocDataProcessor'],
// $runBefore: ['docs-read'],
$process: function (docs) {
var ngIoDartApiDocPath = dartPkgConfigInfo.ngIoDartApiDocPath;
// Return a promise sync we are async in here
return q.all(docs.map(function (doc) {
if (doc.kind.match(/-dart-api$/)) return;
// Load up the HTML and extract the contents of the body
var htmlPath = path.resolve(ngIoDartApiDocPath, doc.href);
return fs.exists(htmlPath).then(function (exists) {
if (!exists) {
log.debug('missing html ' + htmlPath);
return;
}
return fs.read().then(function (html) {
log.info('Reading ' + htmlPath)
var $ = cheerio.load(html);
doc.htmlContent = $('body').contents().html();
});
});
}));
}
}
};

View File

@ -0,0 +1,76 @@
'use strict';
const assert = require('assert-plus');
const fs = require('fs-extra');
const path = require('canonical-path');
const Array_from = require('./arrayFromIterable');
module.exports = function apiListDataFileService(log, dartPkgConfigInfo) {
const _self = {
mainDataFileName: 'api-list.json',
mainDataFilePath: null,
libToEntryMap: null,
containerToEntryMap: null,
numExcludedEntries: 0,
createDataAndSaveToFile: function (dartDocDataWithExtraProps) {
const libToEntryMap = _self.libToEntryMap = new Map();
const containerToEntryMap = _self.containerToEntryMap = new Map();
const re = dartPkgConfigInfo.excludeLibRegExp;
// Populate the two maps from dartDocDataWithExtraProps.
dartDocDataWithExtraProps.forEach((e) => {
// Skip non-preprocessed entries.
if (!e.kind) return true;
// Exclude non-public APIs.
if (e.libName.match(re)) { _self.numExcludedEntries++; return true; }
let key;
if (e.kind.startsWith('entry')) {
// Store library entry info in lib map.
key = e.libName;
assert.equal(key, e.enclosedByQualifiedName, e);
_set(libToEntryMap, key, e);
} else if (e.enclosedBy) {
assert.notEqual(e.type, 'library');
key = e.enclosedByQualifiedName;
} else {
assert.equal(e.type, 'library');
// Add library "index" page to the library's entries in the general container map,
// but not the lib map which is used to create the main API page index.
key = e.libName;
_set(containerToEntryMap, key, e);
// Add the library as an entry to the Angular2 package container:
key = '';
}
_set(containerToEntryMap, key, e);
});
log.info('Excluded', _self.numExcludedEntries, 'library entries (regexp match).');
// Write the library map out as the top-level data file.
_self.mainDataFilePath = path.resolve(path.join(dartPkgConfigInfo.ngIoDartApiDocPath, _self.mainDataFileName));
// The data file needs to be a map of lib names to an array of entries
const fileData = Object.create(null);
for (let name of Array_from(libToEntryMap.keys()).sort()) {
fileData[name] = Array_from(libToEntryMap.get(name).values());
}
fs.writeFileSync(_self.mainDataFilePath, JSON.stringify(fileData, null, 2));
log.info('Wrote', Object.keys(fileData).length, 'library entries to', _self.mainDataFilePath);
return fileData;
},
}
return _self;
};
// Adds e to the map of m[key].
function _set(m, key, e) {
if (!m.has(key)) m.set(key, new Map());
const entryMap = m.get(key);
entryMap.set(e.name, e);
}

View File

@ -0,0 +1,7 @@
'use strict';
module.exports = function arrayFromIterable(iterable) {
const arr = [];
for (let e of iterable) arr.push(e);
return arr;
};

View File

@ -0,0 +1,13 @@
'use strict';
/**
* @return {Object} The Dart package config information
*/
module.exports = function dartPkgConfigInfo() {
const _self = {
ngIoDartApiDocPath: 'ngIoDartApiDocPath is uninitialized',
ngDartDocPath: 'ngDartDocPath is uninitialized',
excludeLibRegExp: null,
};
return _self;
};

View File

@ -0,0 +1,8 @@
'use strict';
module.exports = function logFactory() {
var winston = require('winston');
winston.cli();
winston.level = 'info';
return winston;
};

View File

@ -0,0 +1,81 @@
'use strict';
const assert = require('assert-plus');
const path = require('canonical-path');
const fs = require('fs-extra');
module.exports = function preprocessDartDocData(log, dartPkgConfigInfo) {
const _self = {
entryMap: null,
preprocess: function (dartDocData) {
// List of API entities
let entryMap = _self.entryMap = new Map(); // used to remove duplicates
let numDuplicates = 0;
dartDocData
.forEach((e) => {
if (entryMap.has(e.href)) {
log.debug('Dartdoc preprocessor: duplicate entry for', e.href);
numDuplicates++;
return true;
}
// Sample entry (note that enclosedBy is optional):
// {
// "name": "Pipe",
// "qualifiedName": "angular2.core.Pipe",
// "href": "angular2.core/Pipe-class.html",
// "type": "class",
// "enclosedBy": {
// "name": "angular2.core",
// "type": "library"
// }
// }
// Save original type property since it will be overridden.
e.origDartDocType = e.type;
const name = e.name;
const qualifiedName = e.qualifiedName;
const matches = e.href.match(/-([a-z]+)\.html/);
let type = matches ? (e.typeFromHref = matches[1]) : e.type;
// Conform to TS type names for now.
if (type === 'constant') type = 'let';
let libName;
e.enclosedByQualifiedName = path.dirname(e.href);
if (e.enclosedBy && e.enclosedBy.type === 'library') {
e.kind = 'entry-dart-api';
libName = e.enclosedBy.name;
assert.equal(libName, e.enclosedByQualifiedName, e.kind);
} else if (e.origDartDocType === 'library') {
e.kind = 'library-dart-api';
libName = e.name;
e.enclosedByQualifiedName = ''; // Dart libraries can only be at the top level.
} else {
e.kind = 'subentry-dart-api';
libName = e.enclosedByQualifiedName.split('/')[0];
assert.equal(path.join(libName, e.enclosedBy.name), e.enclosedByQualifiedName, e);
}
e.docType = type;
e.libName = libName;
e.path = e.href;
e.title = name;
e.layout = false; // To prevent public/docs/_layout.jade from be applied to Dart API pages
// Also set above:
// e.kind: one of {library,entry,subentry}-dart-api
// e.enclosedByQualifiedName
// e.origDartDocType
// e.typeFromHref
Object.freeze(e);
entryMap.set(e.path, e);
log.silly('Preproc API entity =', JSON.stringify(e, null, 2));
});
// There shouldn't be duplicates (hence the warning), but there are:
// https://github.com/dart-lang/dartdoc/issues/1197
if (numDuplicates) log.warn('Number of duplicate dartdoc entries', numDuplicates);
},
};
return _self;
};

View File

@ -0,0 +1,26 @@
'use strict';
// This file is likely outdated.
// To run, cd to this dir and
// node test.js
const path = require('canonical-path');
const Dgeni = require('dgeni');
const dartPkg = require(path.resolve('.'));
const ANGULAR_IO_PROJECT_PATH = '../../..';
const DOCS_PATH = path.join(ANGULAR_IO_PROJECT_PATH, 'public/docs');
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');
});
const dgeni = new Dgeni([dartPkg]);
dgeni.generate().catch(function (err) {
console.log(err);
console.log(err.stack);
throw err;
});

View File

@ -0,0 +1,215 @@
'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 _insertExampleFragments(enclosedByName, eltId, $, div) {
const fragDir = path.join(dartPkgConfigInfo.ngIoDartApiDocPath, '../../../_fragments/_api');
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*}/);
if (!matches) {
log.warn(enclosedByName, eltId, 'has an invalidly formed @example tag:', text);
return true;
}
const exRelPath = matches[1];
const region = matches[3];
const dir = path.dirname(exRelPath)
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`);
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(); lines.pop(); lines.pop();
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');
_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 var-def
//- FIXME: a CSS expert needs to figure out why the header CSS needs to be patched for Dart.
//- This enables the patch:
- var fixHeroCss = 1;
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='https://fonts.googleapis.com/css?family=Source+Code+Pro|Roboto:500,400italic,300,400' type='text/css')
link(rel="stylesheet" href="static-assets/prettify.css")
link(rel="stylesheet" href="static-assets/css/bootstrap.min.css")
link(rel="stylesheet" href="static-assets/styles.css")
block breadcrumbs
// generated Dart API page template: breadcrumbs
nav.dropdown
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;
}