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="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:

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.
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);
}
}

View File

@ -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",

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 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")

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
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]

View File

@ -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
<b><a href="/docs/js/latest/api/">Angular 2 API Preview (JavaScript)</a></b>
[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")

View File

@ -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 = [];

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;
}