refact(api-builder): move into tools folder

This commit is contained in:
Peter Bacon Darwin
2015-11-04 11:20:07 +00:00
parent ce298009e6
commit 83fbe75980
88 changed files with 63 additions and 2 deletions
+123
View File
@@ -0,0 +1,123 @@
var Package = require('dgeni').Package;
var jsdocPackage = require('dgeni-packages/jsdoc');
var nunjucksPackage = require('dgeni-packages/nunjucks');
var typescriptPackage = require('../typescript-package');
var linksPackage = require('../links-package');
var gitPackage = require('dgeni-packages/git');
var path = require('canonical-path');
var fs = require('fs');
// Define the dgeni package for generating the docs
module.exports = new Package('angular-v2-docs', [jsdocPackage, nunjucksPackage, typescriptPackage, linksPackage, gitPackage])
// Register the services and file readers
.factory(require('./readers/ngdoc'))
// Register the processors
.processor(require('./processors/convertPrivateClassesToInterfaces'))
.processor(require('./processors/extractDirectiveClasses'))
.processor(require('./processors/generateNavigationDoc'))
.processor(require('./processors/extractTitleFromGuides'))
.processor(require('./processors/createOverviewDump'))
.processor(require('./processors/checkUnbalancedBackTicks'))
.processor(require('./processors/convertBackticksToCodeBlocks'))
// Configure the log service
.config(function(log) {
log.level = 'info';
})
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push({ name: 'internal', transforms: function() { return true; } });
})
.config(function(renderDocsProcessor, versionInfo) {
renderDocsProcessor.extraData.versionInfo = versionInfo;
})
// Configure file reading
.config(function(readFilesProcessor, ngdocFileReader, readTypeScriptModules) {
readFilesProcessor.fileReaders = [ngdocFileReader];
// set the readFilesProcessor base path to point to angular repo.
var angular_repo_path = path.resolve(__dirname, '../../../../angular');
// confirm that the angular repo is actually there.
if (!fs.existsSync(angular_repo_path)) {
throw new Error('build-api-docs task requires the angular2 repo to be at ' + angular_repo_path);
}
readTypeScriptModules.sourceFiles = [
'*/*.@(js|es6|ts)',
'*/src/**/*.@(js|es6|ts)'
];
readTypeScriptModules.basePath = path.resolve(angular_repo_path, 'modules');
})
.config(function(parseTagsProcessor, getInjectables) {
// We actually don't want to parse param docs in this package as we are getting the data out using TS
parseTagsProcessor.tagDefinitions.forEach(function(tagDef) {
if (tagDef.name === 'param') {
tagDef.docProperty = 'paramData';
tagDef.transforms = [];
}
});
})
// Configure links
.config(function(getLinkInfo) {
getLinkInfo.useFirstAmbiguousLink = true;
})
// Configure file writing
.config(function(writeFilesProcessor) {
writeFilesProcessor.outputFolder = 'dist/docs';
})
// Configure rendering
.config(function(templateFinder, templateEngine) {
// Nunjucks and Angular conflict in their template bindings so change Nunjucks
templateEngine.config.tags = {
variableStart: '{$',
variableEnd: '$}'
};
templateFinder.templateFolders
.unshift(path.resolve(__dirname, 'templates'));
templateFinder.templatePatterns = [
'${ doc.template }',
'${ doc.id }.${ doc.docType }.template.html',
'${ doc.id }.template.html',
'${ doc.docType }.template.html',
'common.template.html'
];
})
// Configure ids and paths
.config(function(computeIdsProcessor, computePathsProcessor) {
computeIdsProcessor.idTemplates.push({
docTypes: ['guide'],
getId: function(doc) {
return doc.fileInfo.relativePath
// path should be relative to `modules` folder
.replace(/.*\/?modules\//, '')
// path should not include `/docs/`
.replace(/\/docs\//, '/')
// path should not have a suffix
.replace(/\.\w*$/, '');
},
getAliases: function(doc) { return [doc.id]; }
});
computePathsProcessor.pathTemplates.push({
docTypes: ['guide'],
pathTemplate: '/${id}',
outputPathTemplate: 'partials/guides/${id}.html'
});
});
@@ -0,0 +1 @@
export var x = 100;
@@ -0,0 +1,10 @@
var Package = require('dgeni').Package;
module.exports = function mockPackage() {
return new Package('mockPackage', [require('../')])
// provide a mock log service
.factory('log', function() { return require('dgeni/lib/mocks/log')(false); });
};
@@ -0,0 +1,34 @@
/**
* @module
* @description
* This is the module description
*/
export * from 'importedSrc';
/**
* This is some random other comment
*/
/**
* This is MyClass
*/
export class MyClass {
message: String;
/**
* Create a new MyClass
* @param {String} name The name to say hello to
*/
constructor(name) { this.message = 'hello ' + name; }
/**
* Return a greeting message
*/
greet() { return this.message; }
}
/**
* An exported function
*/
export var myFn = (val: number) => return val * 2;
@@ -0,0 +1,31 @@
var _ = require('lodash');
/**
* @dgProcessor checkUnbalancedBackTicks
* @description
* Searches the rendered content for an odd number of (```) backticks,
* which would indicate an unbalanced pair and potentially a typo in the
* source content.
*/
module.exports = function checkUnbalancedBackTicks(log, createDocMessage) {
var BACKTICK_REGEX = /^ *```/gm;
return {
// $runAfter: ['checkAnchorLinksProcessor'],
$runAfter: ['inlineTagProcessor'],
$runBefore: ['writeFilesProcessor'],
$process: function(docs) {
_.forEach(docs, function(doc) {
if ( doc.renderedContent ) {
var matches = doc.renderedContent.match(BACKTICK_REGEX);
if (matches && matches.length % 2 !== 0) {
doc.unbalancedBackTicks = true;
log.warn(createDocMessage('checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content', doc));
log.warn(doc.renderedContent);
}
}
});
}
};
};
@@ -0,0 +1,32 @@
var mockPackage = require('../mocks/mockPackage');
var Dgeni = require('dgeni');
var path = require('canonical-path');
var _ = require('lodash');
describe('checkUnbalancedBackTicks', function() {
var dgeni, injector, processor, log;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
processor = injector.get('checkUnbalancedBackTicks');
log = injector.get('log');
});
it('should warn if there are an odd number of back ticks in the rendered content', function() {
var docs = [
{ renderedContent:
'```\n' +
'code block\n' +
'```\n' +
'```\n' +
'code block with missing closing back ticks\n'
}
];
processor.$process(docs);
expect(log.warn).toHaveBeenCalledWith('checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content - doc');
expect(docs[0].unbalancedBackTicks).toBe(true);
});
});
@@ -0,0 +1,61 @@
var _ = require('lodash');
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// captures below
// 1st is entire ``` including leading whitespace
// 2nd is the leading whitespace on the line before the ``` fence
// 3rd is the name of the language (if any) specified after the ```
// 4th is the contents of the ``` block up until but not including the first non whitespace char after the end ```
// 5th is the padding of the first nonblank line following the backtick block
// 6th is the first char on the next line.
var BACKTICK_CAPTURE = /(( *)```(.*$)([^]*?)```\s*)^(\s*)(\S)/m;
var CODE_EXAMPLE = 'code-example(format="linenums" language="js").';
module.exports = function convertBackticksToCodeBlocks() {
return {
$runAfter: ['checkUnbalancedBackTicks'],
$runBefore: ['writeFilesProcessor'],
$process: function(docs) {
_.forEach(docs, function(doc) {
if (!doc.unbalancedBackTicks) {
// Idea here is to translate backtick ``` regions into code-example blocks.
var captures = BACKTICK_CAPTURE.exec(doc.renderedContent);
while (captures) {
var entireBlock = captures[1];
var prePad = captures[2];
var language = captures[3];
var blockContents = captures[4];
var postPad = captures[5];
var nextBlockStartChar = captures[6];
var codeExamplePrefix = language.length ? CODE_EXAMPLE.replace('js', language) : CODE_EXAMPLE;
// modulo op in next line insures that pad is always a multiple of 2 ( jade whitespace).
var newPrePad = prePad.substr(2 + (prePad.length % 2)); // exdent
var replaceVal = '\n' + newPrePad + codeExamplePrefix + escapeHtml(blockContents) + '\n';
// if nextBlock does NOT start with a '.' then we want to restart a markdown block.
// and that block needs to be exdented from the preceding code-example content.
if (nextBlockStartChar != '.') {
if (postPad.length >= 2) {
// modulo op in next line insures that pad is always a multiple of 2 ( jade whitespace).
postPad = postPad.substr(2 + (postPad.length % 2)); // exdent
}
replaceVal = replaceVal + postPad + ':markdown\n';
}
doc.renderedContent = doc.renderedContent.replace(entireBlock, replaceVal);
captures = BACKTICK_CAPTURE.exec(doc.renderedContent);
}
}
});
}
};
};
@@ -0,0 +1,59 @@
var mockPackage = require('../mocks/mockPackage');
var Dgeni = require('dgeni');
var path = require('canonical-path');
var _ = require('lodash');
describe('convertBackticksToCodeBlocks', function() {
var dgeni, injector, processor;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
processor = injector.get('convertBackticksToCodeBlocks');
});
it('should convert backtick code blocks to code-example blocks', function() {
var docs = [{
renderedContent:
'preamble\n' +
'```ts\n' +
'export class TypeScriptClass {\n' +
'}\n' +
'```\n' +
'postamble\n'
}];
processor.$process(docs);
expect(docs[0].renderedContent).toEqual(
'preamble\n' +
'\n' +
'code-example(format="linenums" language="ts").\n' +
'export class TypeScriptClass {\n' +
'}\n' +
'\n' +
':markdown\n' +
'postamble\n'
);
});
it('should ignore docs that have been marked as having unbalanced backticks', function() {
var docs = [{
renderedContent:
'preamble\n' +
'```ts\n' +
'export class TypeScriptClass {\n' +
'}\n' +
'postamble\n'
}];
processor.$process(docs);
expect(docs[0].renderedContent).toEqual(
'preamble\n' +
'```ts\n' +
'export class TypeScriptClass {\n' +
'}\n' +
'postamble\n'
);
})
});
@@ -0,0 +1,10 @@
module.exports = function convertPrivateClassesToInterfacesProcessor(convertPrivateClassesToInterfaces) {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
$process: function(docs) {
convertPrivateClassesToInterfaces(docs, false);
return docs;
}
};
};
@@ -0,0 +1,24 @@
var _ = require('lodash');
module.exports = function createOverviewDump() {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
$process: function(docs) {
var overviewDoc = {
id: 'overview-dump',
aliases: ['overview-dump'],
path: 'overview-dump',
outputPath: 'overview-dump.html',
modules: []
};
_.forEach(docs, function(doc) {
if ( doc.docType === 'module' ) {
overviewDoc.modules.push(doc);
}
});
docs.push(overviewDoc);
}
};
};
@@ -0,0 +1,32 @@
var _ = require('lodash');
var vm = require('vm');
module.exports = function extractDirectiveClassesProcessor() {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
decoratorTypes: ['Directive', 'Component', 'View'],
$process: function(docs) {
var decoratorTypes = this.decoratorTypes;
_.forEach(docs, function(doc) {
_.forEach(doc.decorators, function(decorator) {
if (decoratorTypes.indexOf(decorator.name) !== -1) {
// We use this sneaky vm trick to extract the object literal
// argument from the decorator's constructor call
var args = decorator.arguments ?
vm.runInNewContext('dummy = ' + decorator.arguments[0]) : {};
doc[decorator.name.toLowerCase() + 'Options'] = args;
doc.docType = 'directive';
}
});
});
return docs;
}
};
};
@@ -0,0 +1,45 @@
var mockPackage = require('../mocks/mockPackage');
var Dgeni = require('dgeni');
describe('extractDirectiveClasses processor', function() {
var dgeni, injector, processor;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
processor = injector.get('extractDirectiveClassesProcessor');
});
it('should extract specified decorator arguments', function() {
var doc = {
id: 'angular2/angular2.ngFor',
name: 'ngFor',
docType: 'class',
decorators: [
{
name: 'Directive',
arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}']
}
]
};
var docs = processor.$process([doc]);
expect(doc).toEqual(jasmine.objectContaining({
id: 'angular2/angular2.ngFor',
name: 'ngFor',
docType: 'directive',
decorators: [
{
name: 'Directive',
arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}']
}
]
}));
expect(doc.directiveOptions).toEqual({
selector: '[ng-for][ng-for-of]',
properties: ['ngForOf']
});
});
});
@@ -0,0 +1,24 @@
var _ = require('lodash');
module.exports = function extractTitleFromGuides() {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
$process: function(docs) {
_(docs).forEach(function(doc) {
if (doc.docType === 'guide') {
doc.name = doc.name || getNameFromHeading(doc.description);
}
});
}
};
};
function getNameFromHeading(text) {
var match = /^\s*#\s*(.*)/.exec(text);
if (match) {
return match[1];
}
}
@@ -0,0 +1,68 @@
var _ = require('lodash');
module.exports = function generateNavigationDoc() {
return {
$runAfter: ['docs-processed'],
$runBefore: ['rendering-docs'],
$process: function(docs) {
var modulesDoc = {
value: { sections: [] },
moduleName: 'navigation-modules',
serviceName: 'MODULES',
template: 'data-module.template.js',
outputPath: 'js/navigation-modules.js'
};
_.forEach(docs, function(doc) {
if ( doc.docType === 'module' ) {
var moduleNavItem = {
path: doc.path,
partial: doc.outputPath,
name: doc.id,
type: 'module',
pages: []
};
modulesDoc.value.sections.push(moduleNavItem);
_.forEach(doc.exports, function(exportDoc) {
if (!exportDoc.internal) {
var exportNavItem = {
path: exportDoc.path,
partial: exportDoc.outputPath,
name: exportDoc.name,
type: exportDoc.docType
};
moduleNavItem.pages.push(exportNavItem);
}
});
}
});
docs.push(modulesDoc);
var guidesDoc = {
value: { pages: [] },
moduleName: 'navigation-guides',
serviceName: 'GUIDES',
template: 'data-module.template.js',
outputPath: 'js/navigation-guides.js'
};
_.forEach(docs, function(doc) {
if ( doc.docType === 'guide' ) {
var guideDoc = {
path: doc.path,
partial: doc.outputPath,
name: doc.name,
type: 'guide'
};
guidesDoc.value.pages.push(guideDoc);
}
});
docs.push(guidesDoc);
}
};
};
@@ -0,0 +1,32 @@
var path = require('canonical-path');
/**
* @dgService ngdocFileReader
* @description
* This file reader will pull the contents from a text file (by default .ngdoc)
*
* The doc will initially have the form:
* ```
* {
* content: 'the content of the file',
* startingLine: 1
* }
* ```
*/
module.exports = function ngdocFileReader() {
var reader = {
name: 'ngdocFileReader',
defaultPattern: /\.md$/,
getDocs: function(fileInfo) {
// We return a single element array because ngdoc files only contain one document
return [{
docType: 'guide',
content: fileInfo.content,
startingLine: 1
}];
}
};
return reader;
};
@@ -0,0 +1,45 @@
var ngdocFileReaderFactory = require('./ngdoc');
var path = require('canonical-path');
describe('ngdocFileReader', function() {
var fileReader;
var createFileInfo = function(file, content, basePath) {
return {
fileReader: fileReader.name,
filePath: file,
baseName: path.basename(file, path.extname(file)),
extension: path.extname(file).replace(/^\./, ''),
basePath: basePath,
relativePath: path.relative(basePath, file),
content: content
};
};
beforeEach(function() {
fileReader = ngdocFileReaderFactory();
});
describe('defaultPattern', function() {
it('should match .md files', function() {
expect(fileReader.defaultPattern.test('abc.md')).toBeTruthy();
expect(fileReader.defaultPattern.test('abc.js')).toBeFalsy();
});
});
describe('getDocs', function() {
it('should return an object containing info about the file and its contents', function() {
var fileInfo = createFileInfo('project/path/modules/someModule/foo/docs/subfolder/bar.ngdoc', 'A load of content', 'project/path');
expect(fileReader.getDocs(fileInfo)).toEqual([{
docType: 'guide',
content: 'A load of content',
startingLine: 1
}]);
});
});
});
@@ -0,0 +1,44 @@
{% include "lib/paramList.html" -%}
{% include "lib/githubLinks.html" -%}
{% extends 'layout/base.template.html' -%}
{% block body %}
<h1 class="class export">{$ doc.name $} <span class="type">{$ doc.docType $}</span></h1>
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
defined in {$ githubViewLink(doc) $}
</p>
<p>{$ doc.description | marked $}</p>
{%- if doc.decorators %}
<h2>Annotations</h2>
{%- for decorator in doc.decorators %}
<h3 class="annotation">@{$ decorator.name $}{$ paramList(decorator.arguments) $}</h3>
{% endfor %}
{% endif -%}
{%- if doc.constructorDoc or doc.members.length -%}
<h2>Members</h2>
{%- if doc.constructorDoc %}
<section class="member constructor">
<h1 id="constructor" class="name">{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.params) $}</h1>
{% marked %}
{$ doc.constructorDoc.description $}
{% endmarked %}
</section>
{% endif -%}
{%- for member in doc.members %}{% if not member.internal %}
<section class="member">
<h1 id="{$ member.name $}" class="name">
{$ member.name $}{% if member.optional %}?{% endif %}{$ paramList(member.params) $}
</h1>
{% marked %}
{$ member.description $}
{% endmarked %}
</section>
{% endif %}{% endfor %}
{%- endif -%}
{% endblock %}
@@ -0,0 +1,9 @@
{% extends 'layout/base.template.html' %}
{% block body %}
<h1>{$ doc.id $}</h1>
<h2>({$ doc.docType $})</h2>
<div>
{$ doc.description | marked $}
</div>
{% endblock %}
@@ -0,0 +1 @@
{% extends 'var.template.html' -%}
@@ -0,0 +1,3 @@
angular.module('{$ doc.moduleName $}', [])
.value('{$ doc.serviceName $}', {$ doc.value | json $});
@@ -0,0 +1,11 @@
{% include "lib/paramList.html" -%}
{% include "lib/githubLinks.html" -%}
{% extends 'layout/base.template.html' -%}
{% block body %}
<h1 class="function export">{$ doc.name $}{$ paramList(doc.parameters) $}</h1>
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
defined in {$ githubViewLink(doc) $}</p>
<p>{$ doc.description | marked $}</p>
{% endblock %}
@@ -0,0 +1,5 @@
{% extends 'layout/base.template.html' %}
{% block body %}
{$ doc.description | marked $}
{% endblock %}
@@ -0,0 +1 @@
{% extends 'class.template.html' -%}
@@ -0,0 +1 @@
{% block body %}{% endblock %}
@@ -0,0 +1,3 @@
{% macro githubViewLink(doc) -%}
<a href="https://github.com/{$ versionInfo.gitRepoInfo.owner $}/{$ versionInfo.gitRepoInfo.repo $}/tree/{$ versionInfo.currentVersion.isSnapshot and versionInfo.currentVersion.SHA or versionInfo.currentVersion.raw $}/modules/{$ doc.fileInfo.relativePath $}#L{$ doc.location.start.line+1 $}-L{$ doc.location.end.line+1 $}">{$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $})</a>
{%- endmacro -%}
@@ -0,0 +1,7 @@
{% macro paramList(params) -%}
{%- if params -%}<span class="params">(
{%- for param in params -%}
<span class="param">{$ param | escape $}{% if not loop.last %}, {% endif %}</span>
{%- endfor %})</span>
{%- endif %}
{%- endmacro -%}
@@ -0,0 +1,19 @@
{% include "lib/githubLinks.html" -%}
{% extends 'layout/base.template.html' %}
{% block body %}
<h1 class="id">{$ doc.id $} <span class="type">module</span></h1>
<p>defined in {$ githubViewLink(doc) $}</p>
<p>{$ doc.description | marked $}</p>
{% if doc.exports.length %}
<h2>Exports</h2>
<ul>
{%- for exportDoc in doc.exports %}
{% if not exportDoc.internal -%}
<li><a href="{$ exportDoc.path $}"><strong>{$ exportDoc.name $}</strong> {$ exportDoc.docType $}</a></li>
{%- endif %}
{%- endfor %}
</ul>
{% endif %}
{% endblock %}
@@ -0,0 +1,43 @@
{% include "lib/paramList.html" -%}
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
h2 {
padding-left: 20px;
}
h3 {
padding-left: 50px;
}
h4 {
padding-left: 60px;
}
</style>
</head>
<body>
<h1>Modules</h1>
{% for module in doc.modules %}
<h2>{$ module.id $}
{%- if module.public %} (public){% endif %}</h2>
{% for export in module.exports %}
<h3>{$ export.name $}</h3>
{%- if export.constructorDoc %}
<h4>{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.params) $}</h4>
{% endif -%}
{%- for member in export.members %}
<h4>{$ member.name $}{$ paramList(member.params) $}</h4>
{% endfor %}
{% endfor %}
{% endfor %}
</body>
</html>
@@ -0,0 +1,10 @@
{% include "lib/githubLinks.html" -%}
{% extends 'layout/base.template.html' %}
{% block body %}
<h1>{$ doc.name $} <span class="type">type alias</span></h1>
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
defined in {$ githubViewLink(doc) $}</p>
<p>{$ doc.description | marked $}</p>
{% endblock %}
@@ -0,0 +1,10 @@
{% include "lib/githubLinks.html" -%}
{% extends 'layout/base.template.html' %}
{% block body %}
<h1>{$ doc.name $} <span class="type">variable</span></h1>
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
defined in {$ githubViewLink(doc) $}</p>
<p>{$ doc.description | marked $}</p>
{% endblock %}