diff --git a/.travis.yml b/.travis.yml
index c6cadbafa5..ef7145f388 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,13 +15,13 @@ env:
- TASK=lint
- TASK="run-e2e-tests --fast" SCRIPT=examples-install.sh
- TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh
- - TASK=harp-compile SCRIPT=deploy-install.sh
- - TASK=harp-compile SCRIPT=deploy-install-preview.sh
+ - TASK=build-compile SCRIPT=deploy-install.sh
+ - TASK=build-compile SCRIPT=deploy-install-preview.sh
matrix:
fast_finish: true
allow_failures:
- 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:
- npm install -g gulp --no-optional
install:
diff --git a/gulpfile.js b/gulpfile.js
index 6f784956fb..a7bc1101b2 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -30,6 +30,7 @@ var tslint = require('gulp-tslint');
// 2. Think about using spawn instead of exec in case of long error messages.
var TOOLS_PATH = './tools';
+var ANGULAR_IO_PROJECT_PATH = path.resolve('.');
var ANGULAR_PROJECT_PATH = '../angular';
var PUBLIC_PATH = './public';
var TEMP_PATH = './_temp';
@@ -63,12 +64,21 @@ var _devguideShredJadeOptions = {
};
var _apiShredOptions = {
+ lang: 'ts',
examplesDir: path.join(ANGULAR_PROJECT_PATH, 'modules/@angular/examples'),
fragmentsDir: path.join(DOCS_PATH, '_fragments/_api'),
zipDir: path.join(RESOURCES_PATH, 'zips/api'),
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 _excludeMatchers = _excludePatterns.map(function(excludePattern){
@@ -96,6 +106,14 @@ var _exampleProtractorBoilerplateFiles = [
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) {
// Testing via indexOf() for now. If we need to match only paths with folders
// 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)
*/
function runE2e() {
+ if (!argv.lang) configLangs('ts|js'); // Exclude dart by default
var promise;
if (argv.fast) {
// fast; skip all setup
@@ -183,8 +202,6 @@ function runE2e() {
// each app/spec collection sequentially.
function findAndRunE2eTests(filter, outputFile) {
// 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 header = `Doc Sample Protractor Results for ${lang} on ${new Date().toLocaleString()}\n`;
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
//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() {
return buildShredMaps(true);
@@ -542,12 +561,40 @@ gulp.task('build-js-api-docs', ['_shred-api-examples'], function() {
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() {
return plunkerBuilder.buildPlunkers(EXAMPLES_PATH, LIVE_EXAMPLES_PATH, { errFn: gutil.log });
});
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(){
@@ -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();
});
+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() {
return harpCompile().then(function() {
gutil.log('compile ok');
@@ -693,8 +765,15 @@ gulp.task('_shred-clean-devguide', function(cb) {
});
gulp.task('_shred-api-examples', ['_shred-clean-api'], function() {
- checkAngularProjectPath();
- return docShredder.shred(_apiShredOptions);
+ const promises = [];
+ 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) {
@@ -1087,8 +1166,8 @@ function buildApiDocs(targetLanguage) {
var dgeni = new Dgeni([package]);
return dgeni.generate();
} catch(err) {
- gutil.log(err);
- gutil.log(err.stack);
+ console.error(err);
+ console.error(err.stack);
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) {
var options = {
devguideExamplesDir: _devguideShredOptions.examplesDir,
@@ -1270,8 +1391,13 @@ function execCommands(cmds, options, cb) {
});
}
-function checkAngularProjectPath() {
- if (!fs.existsSync(ANGULAR_PROJECT_PATH)) {
- throw new Error('API related tasks require the angular2 repo to be at ' + path.resolve(ANGULAR_PROJECT_PATH));
+function ngPathFor(lang) {
+ return ANGULAR_PROJECT_PATH + (lang === 'dart' ? '-dart' : '');
+}
+
+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);
}
}
diff --git a/package.json b/package.json
index 44d1d2da1a..4a582d893a 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"broken-link-checker": "0.7.1",
"browser-sync": "^2.9.3",
"canonical-path": "0.0.2",
+ "cheerio": "^0.20.0",
"cross-spawn": "^4.0.0",
"codelyzer": "0.0.22",
"del": "^2.2.0",
diff --git a/public/_includes/_hero.jade b/public/_includes/_hero.jade
index 6e301a0b71..3a27ed9d2b 100644
--- a/public/_includes/_hero.jade
+++ b/public/_includes/_hero.jade
@@ -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 headerTitle = title + (typeof varType !== 'undefined' ? (': ' + varType) : '');
- var capitalize = function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); }
- var useBadges = docType || stability;
-
-// renamer :: String -> String
-// Renames `Let` and `Var` into `Const`
+//- renamer :: String -> String
+//- Renames `Let` and `Var` into `Const`
- var renamer = function renamer(docType) {
- return (docType === 'Let' || docType === 'Var') ? 'Const' : docType
- }
@@ -13,7 +13,7 @@
if current.path[4] && current.path[3] == 'api'
- var textFormat = 'is-standard-case'
-header(class="hero background-sky")
+header(class="hero background-sky", style=fixHeroCss ? "height:auto" : "")
div(class="inner-header")
h1(class="hero-title text-display-1 #{textFormat}") #{headerTitle}
if useBadges
@@ -33,5 +33,7 @@ header(class="hero background-sky")
if 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"
!= partial("_version-dropdown")
diff --git a/public/docs/_layout-dart-api.jade b/public/docs/_layout-dart-api.jade
new file mode 100644
index 0000000000..d8e77353b6
--- /dev/null
+++ b/public/docs/_layout-dart-api.jade
@@ -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
diff --git a/public/docs/_layout.jade b/public/docs/_layout.jade
index e277180fa0..936c478fee 100644
--- a/public/docs/_layout.jade
+++ b/public/docs/_layout.jade
@@ -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
html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework")
+ // template: public/docs/_layout
head
!= partial("../_includes/_head-include")
+ block head-extra
+ //-
body(class="l-offset-nav l-offset-side-nav" ng-controller="AppCtrl as appCtrl")
!= partial("../_includes/_main-nav")
if current.path[2]
diff --git a/public/docs/dart/latest/api/index.jade b/public/docs/dart/latest/api/index.jade
index c0b081114d..6954ca79c5 100644
--- a/public/docs/dart/latest/api/index.jade
+++ b/public/docs/dart/latest/api/index.jade
@@ -1,10 +1,13 @@
-.l-main-section
- h2 Beta
+:marked
+ > **WARNING:** API documentation is preliminary and subject to change.
- p.
- The proposed Angular 2 API does not yet have Dart-specific documentation.
- However, because the Dart and JavaScript APIs are generated from the same source,
- you might find the JavaScript API docs helpful:
+ > **Known issues:** Although this generated API reference displays Dart
+ APIs, individual pages sometimes describe TypeScript APIs accompanied with
+ TypeScript code. The angular.io issue tracker contains [all known
+ issues][api-issues]; if you notice others, please [report
+ them][new-issue]. Thanks!
- p.text-center
- Angular 2 API Preview (JavaScript)
+ [new-issue]: https://github.com/angular/angular.io/issues/new?labels=dart,api&title=%5BDart%5D%5BAPI%5D%20
+ [api-issues]: https://github.com/angular/angular.io/issues?q=label%3Aapi+label%3Adart
+
+api-list(src="api-list.json" lang="dart")
diff --git a/public/resources/js/directives/api-list.js b/public/resources/js/directives/api-list.js
index 7be1c88d6d..97c4afa980 100644
--- a/public/resources/js/directives/api-list.js
+++ b/public/resources/js/directives/api-list.js
@@ -26,6 +26,8 @@ angularIO.directive('apiList', function () {
controller: function($scope, $attrs, $http, $location) {
var $ctrl = this;
+ var isForDart = $attrs.lang === 'dart';
+
$ctrl.apiTypes = [
{ cssClass: 'stable', title: 'Stable', matches: ['stable']},
{ cssClass: 'directive', title: 'Directive', matches: ['directive'] },
@@ -37,6 +39,9 @@ angularIO.directive('apiList', function () {
{ 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.apiType = getApiTypeFromLocation();
$ctrl.groupedSections = [];
diff --git a/scripts/patch.sh b/scripts/patch.sh
new file mode 100755
index 0000000000..87876b20b5
--- /dev/null
+++ b/scripts/patch.sh
@@ -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
diff --git a/tools/api-builder/dart-package/index.js b/tools/api-builder/dart-package/index.js
new file mode 100644
index 0000000000..6b9d825d22
--- /dev/null
+++ b/tools/api-builder/dart-package/index.js
@@ -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'))
+ ;
diff --git a/tools/api-builder/dart-package/processors/loadDartDocData.js b/tools/api-builder/dart-package/processors/loadDartDocData.js
new file mode 100644
index 0000000000..af42f68eec
--- /dev/null
+++ b/tools/api-builder/dart-package/processors/loadDartDocData.js
@@ -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);
+ }
+ };
+};
diff --git a/tools/api-builder/dart-package/processors/loadDartDocHtml.js b/tools/api-builder/dart-package/processors/loadDartDocHtml.js
new file mode 100644
index 0000000000..94fbc203d3
--- /dev/null
+++ b/tools/api-builder/dart-package/processors/loadDartDocHtml.js
@@ -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();
+ });
+
+ });
+
+ }));
+ }
+ }
+};
diff --git a/tools/api-builder/dart-package/services/apiListDataFileService.js b/tools/api-builder/dart-package/services/apiListDataFileService.js
new file mode 100644
index 0000000000..afc6be6e36
--- /dev/null
+++ b/tools/api-builder/dart-package/services/apiListDataFileService.js
@@ -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);
+}
\ No newline at end of file
diff --git a/tools/api-builder/dart-package/services/arrayFromIterable.js b/tools/api-builder/dart-package/services/arrayFromIterable.js
new file mode 100644
index 0000000000..e38890737f
--- /dev/null
+++ b/tools/api-builder/dart-package/services/arrayFromIterable.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = function arrayFromIterable(iterable) {
+ const arr = [];
+ for (let e of iterable) arr.push(e);
+ return arr;
+};
diff --git a/tools/api-builder/dart-package/services/dartPkgConfigInfo.js b/tools/api-builder/dart-package/services/dartPkgConfigInfo.js
new file mode 100644
index 0000000000..05117df847
--- /dev/null
+++ b/tools/api-builder/dart-package/services/dartPkgConfigInfo.js
@@ -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;
+};
\ No newline at end of file
diff --git a/tools/api-builder/dart-package/services/logFactory.js b/tools/api-builder/dart-package/services/logFactory.js
new file mode 100644
index 0000000000..84b7fa4b93
--- /dev/null
+++ b/tools/api-builder/dart-package/services/logFactory.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = function logFactory() {
+ var winston = require('winston');
+ winston.cli();
+ winston.level = 'info';
+ return winston;
+};
\ No newline at end of file
diff --git a/tools/api-builder/dart-package/services/preprocessDartDocData.js b/tools/api-builder/dart-package/services/preprocessDartDocData.js
new file mode 100644
index 0000000000..9ecc0dcfc0
--- /dev/null
+++ b/tools/api-builder/dart-package/services/preprocessDartDocData.js
@@ -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;
+};
\ No newline at end of file
diff --git a/tools/api-builder/dart-package/test.js b/tools/api-builder/dart-package/test.js
new file mode 100644
index 0000000000..22ffb7f73d
--- /dev/null
+++ b/tools/api-builder/dart-package/test.js
@@ -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;
+});
diff --git a/tools/dart-api-builder/dab.js b/tools/dart-api-builder/dab.js
new file mode 100644
index 0000000000..b314719f02
--- /dev/null
+++ b/tools/dart-api-builder/dab.js
@@ -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 `