diff --git a/gulpfile.js b/gulpfile.js index 25d64440d9..c53289b1a6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,6 +9,10 @@ var Git = require("nodegit"); var argv = require('yargs').argv; var Q = require("q"); var Minimatch = require("minimatch").Minimatch; +var Dgeni = require('dgeni'); +var fsExtra = require('fs-extra'); +var fs = fsExtra; + var docShredder = require('./public/doc-shredder/doc-shredder'); @@ -116,6 +120,24 @@ gulp.task('git-changed-examples', ['shred-full'], function(){ }); }); + +gulp.task('build-api-docs', function() { + var fs = require('fs-extra'); + if (!fs.existsSync('../angular')) { + throw new Error('build-api-docs task requires the angular2 repo to be at ' + path.resolve('../angular')); + } + try { + var dgeni = new Dgeni([require('./public/api-builder/angular.io-package')]); + return dgeni.generate(); + } catch(x) { + console.log(x); + console.log(x.stack); + throw x; + } +}); + + + function filterOutExcludedPatterns(fileNames, excludeMatchers) { return fileNames.filter(function(fileName) { return !excludeMatchers.some(function(excludeMatcher) { diff --git a/package.json b/package.json index 7e277e3d86..b5b29af132 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { - "type": "git" + "type": "git", + "url": "https://github.com/angular/angular.io.git" }, "licenses": [ { @@ -25,19 +26,26 @@ "del": "^1.2.0", "dgeni": "^0.4.0", "dgeni-packages": "^0.10.0", + "fs-extra": "^0.24.0", + "glob": "^5.0.14", "gulp": "^3.5.6", "gulp-task-listing": "^1.0.1", "gulp-util": "^3.0.6", "gulp-watch": "^4.3.4", + "html2jade": "^0.8.4", + "indent-string": "^2.1.0", "jasmine-core": "^2.3.4", "karma": "^0.13.8", "karma-chrome-launcher": "^0.2.0", "karma-jasmine": "^0.3.6", "lodash": "^3.10.1", + "marked": "^0.3.5", "minimatch": "^2.0.10", + "node-html-encoder": "0.0.2", "nodegit": "^0.4.1", "path": "^0.11.14", "q": "^1.4.1", + "typescript": "^1.5.3", "yargs": "^3.23.0" }, "contributors": [ diff --git a/public/api-builder/angular.io-package/index.js b/public/api-builder/angular.io-package/index.js new file mode 100644 index 0000000000..02664bd764 --- /dev/null +++ b/public/api-builder/angular.io-package/index.js @@ -0,0 +1,70 @@ +var path = require('canonical-path'); +var Package = require('dgeni').Package; +var basePackage = require('../public-docs-package'); + +var PARTIAL_PATH = 'partials'; +var MODULES_DOCS_PATH = PARTIAL_PATH + '/api'; + +// OLD paths +// var DOCS_DIST = 'dist/angular.io/partials/api/angular2/'; +// var DOCS_IO_DIST = '../angular.io/public/docs/js/latest/api/'; + +module.exports = new Package('angular.io', [basePackage]) + +.factory(require('./services/renderMarkdown')) +.processor(require('./processors/addJadeDataDocsProcessor')) + // overrides base packageInfo and returns the one for the 'angular/angular' repo. +.factory(require('./services/packageInfo')) + +// Configure rendering +.config(function(templateFinder, templateEngine) { + + templateFinder.templateFolders + .unshift(path.resolve(__dirname, 'templates')); +}) + +.config(function(writeFilesProcessor) { + writeFilesProcessor.outputFolder = 'dist/angular.io'; +}) + +.config(function(readFilesProcessor, generateNavigationDoc, createOverviewDump) { + // Clear out unwanted processors + readFilesProcessor.$enabled = false; + generateNavigationDoc.$enabled = false; + createOverviewDump.$enabled = false; +}) + + + + +.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) { + + computePathsProcessor.pathTemplates.push({ + docTypes: ['module'], + pathTemplate: '${id}/', + outputPathTemplate: MODULES_DOCS_PATH + '/${id}/index.jade' + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: EXPORT_DOC_TYPES, + pathTemplate: '${moduleDoc.id}/${name}-${docType}.html', + outputPathTemplate: MODULES_DOCS_PATH + '/${moduleDoc.id}/${name}-${docType}.jade', + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: ['jade-data'], + pathTemplate: '${originalDoc.id}/_data', + outputPathTemplate: MODULES_DOCS_PATH + '/${path}.json' + }); +}) + +.config(function(getLinkInfo) { + getLinkInfo.relativeLinks = true; +}) + + +.config(function(templateEngine, getInjectables) { + templateEngine.filters = templateEngine.filters.concat(getInjectables([require('./rendering/trimBlankLines')])); +}); + + diff --git a/public/api-builder/angular.io-package/mocks/mockPackage.js b/public/api-builder/angular.io-package/mocks/mockPackage.js new file mode 100644 index 0000000000..473bc36adb --- /dev/null +++ b/public/api-builder/angular.io-package/mocks/mockPackage.js @@ -0,0 +1,8 @@ +var Package = require('dgeni').Package; + +module.exports = function mockPackage() { + + return new Package('mockPackage', [require('../')]) + + .factory('log', function() { return require('dgeni/lib/mocks/log')(false); }) +}; diff --git a/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.js b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.js new file mode 100644 index 0000000000..9ae336c469 --- /dev/null +++ b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.js @@ -0,0 +1,77 @@ +var _ = require('lodash'); +var path = require('canonical-path'); + +var titleCase = function(text) { + return text.replace(/(.)(.*)/, function(_, first, rest) { + return first.toUpperCase() + rest; + }); +}; + +/* +* Create _data.json file for Harp pages +* +* http://harpjs.com/docs/development/metadata +* +* This method creates the meta data required for each page +* such as the title, description, etc. This meta data is used +* in the harp static site generator to create the title for headers +* and the navigation used in the API docs +* +*/ + +module.exports = function addJadeDataDocsProcessor() { + return { + $runAfter: ['adding-extra-docs'], + $runBefore: ['extra-docs-added'], + $process: function(docs) { + var extraDocs = []; + var modules = []; + + + /* + * Create Data for Modules + * + * Modules must be public and have content + */ + + _.forEach(docs, function(doc) { + if (doc.docType === 'module' && !doc.private && doc.exports.length) { + modules.push(doc); + + // GET DATA FOR INDEX PAGE OF MODULE SECTION + var indexPageInfo = [{ + name: 'index', + title: _.map(path.basename(doc.fileInfo.baseName).split('_'), function(part) { + return titleCase(part); + }).join(' '), + intro: doc.description.replace('"', '\"').replace(/\s*(\r?\n|\r)\s*/g," ") + }]; + + // GET DATA FOR EACH PAGE (CLASS, VARS, FUNCTIONS) + var modulePageInfo = _.map(doc.exports, function(exportDoc) { + return { + name: exportDoc.name + '-' + exportDoc.docType, + title: exportDoc.name, + varType: exportDoc.symbolTypeName && titleCase(exportDoc.symbolTypeName) + }; + }); + + //COMBINE PAGE DATA + var allPageData = indexPageInfo.concat(modulePageInfo); + + // PUSH DATA TO EXTRA DOCS ARRAY + extraDocs.push({ + id: doc.id + "-data", + aliases: [doc.id + "-data"], + docType: 'jade-data', + originalDoc: doc, + data: allPageData + }); + } + }); + + + return docs.concat(extraDocs); + } + }; +}; diff --git a/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.spec.js b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.spec.js new file mode 100644 index 0000000000..e0e4c5965d --- /dev/null +++ b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.spec.js @@ -0,0 +1,35 @@ +var mockPackage = require('../mocks/mockPackage'); +var Dgeni = require('dgeni'); + +describe('addJadeDataDocsProcessor', function() { + var dgeni, injector, processor; + + beforeEach(function() { + dgeni = new Dgeni([mockPackage()]); + injector = dgeni.configureInjector(); + processor = injector.get('addJadeDataDocsProcessor'); + }); + + it('should add a doc for each module', function() { + var docs = [ + { + docType: 'module', + id: 'someModule', + exports: [{ name: 'MyClass', docType: 'class'}], + fileInfo: { baseName: 'x_y' }, + description: 'some description\nsecond line' + } + ]; + docs = processor.$process(docs); + + expect(docs[1]).toEqual({ + id : 'someModule-data', + aliases : [ 'someModule-data' ], + docType : 'jade-data', + originalDoc : docs[0], + data : [ + { name : 'index', title : 'X Y', intro : 'some description second line' }, + { name : 'MyClass-class', title : 'MyClass', varType : undefined } + ] }); + }); +}); diff --git a/public/api-builder/angular.io-package/rendering/trimBlankLines.js b/public/api-builder/angular.io-package/rendering/trimBlankLines.js new file mode 100644 index 0000000000..82728d7635 --- /dev/null +++ b/public/api-builder/angular.io-package/rendering/trimBlankLines.js @@ -0,0 +1,12 @@ +module.exports = function(encodeCodeBlock) { + return { + name: 'trimBlankLines', + process: function(str) { + var lines = str.split(/\r?\n/); + while(lines[0] === '') { + lines.shift(); + } + return lines.join('\n'); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/angular.io-package/services/packageInfo.js b/public/api-builder/angular.io-package/services/packageInfo.js new file mode 100644 index 0000000000..1cd06db3c0 --- /dev/null +++ b/public/api-builder/angular.io-package/services/packageInfo.js @@ -0,0 +1,14 @@ +'use strict'; + +var fs = require('fs'); +var path = require('canonical-path'); + +/** + * Load information about this project from the package.json + * @return {Object} The package information + */ +module.exports = function packageInfo() { + + var topLevelPackageJson= path.join('../angular','package.json'); + return JSON.parse(fs.readFileSync(topLevelPackageJson), 'UTF-8'); +} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/services/renderMarkdown.js b/public/api-builder/angular.io-package/services/renderMarkdown.js new file mode 100644 index 0000000000..74d4ee1ac8 --- /dev/null +++ b/public/api-builder/angular.io-package/services/renderMarkdown.js @@ -0,0 +1,54 @@ +var marked = require('marked'); +var Encoder = require('node-html-encoder').Encoder; +var html2jade = require('html2jade'); +var indentString = require('indent-string'); + + +function stripTags(s) { //from sugar.js + return s.replace(RegExp('<\/?[^<>]*>', 'gi'), ''); +} + +// entity type encoder +var encoder = new Encoder('entity'); + + + +/** + * @dgService renderMarkdown + * @description + * Render the markdown in the given string as HTML. + */ +module.exports = function renderMarkdown(trimIndentation) { + + var renderer = new marked.Renderer(); + + renderer.code = function(code, lang, escaped) { + + var cssClasses = ['prettyprint', 'linenums']; + var trimmedCode = trimIndentation(code); + + if(lang) { + if(lang=='html') { + trimmedCode = encoder.htmlEncode(trimmedCode); + } + cssClasses.push(this.options.langPrefix + escape(lang, true)); + } + + return 'pre(class="' + cssClasses.join(' ') + '")\n' + indentString('code.\n', ' ', 2) + trimmedCode; + }; + + renderer.heading = function (text, level, raw) { + var headingText = marked.Renderer.prototype.heading.call(renderer, text, level, raw); + var title = 'h2 ' + stripTags(headingText); + + if (level==2) { + title = '.l-main-section\n' + indentString(title, ' ', 2) ; + } + + return title; + }; + + return function(content) { + return marked(content, { renderer: renderer }); + }; +}; \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/class.template.html b/public/api-builder/angular.io-package/templates/class.template.html new file mode 100644 index 0000000000..a51fc5fb08 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/class.template.html @@ -0,0 +1,62 @@ +{% include "lib/githubLinks.html" -%} +{% include "lib/paramList.html" -%} +{% extends 'layout/base.template.html' -%} + +{% block body %} +p.location-badge. + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ githubViewLink(doc) $} + +:markdown +{$ doc.moduleDoc.description | indent(2, true) | trimBlankLines $} + +{%- if doc.decorators %} +.l-main-section + h2 Annotations +{%- for decorator in doc.decorators %} + .l-sub-section + h3.annotation {$ decorator.name $} + pre.prettyprint + code. + @{$ decorator.name $}{$ paramList(decorator.arguments) | indent(8, false) $} +{% endfor %} +{% endif -%} + + +{%- if doc.constructorDoc or doc.members.length -%} +.l-main-section + h2 Members + +{%- if doc.constructorDoc %} + .l-sub-section + h3 {$ doc.constructorDoc.name $} + + {% if doc.constructorDoc.parameters %} + pre.prettyprint + code. + {$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $} + {% endif %} + :markdown +{$ doc.constructorDoc.description | indent(6, true) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $} + + +{% endif -%} + +{%- for member in doc.members %}{% if not member.private %} + .l-sub-section + h3 {$ member.name $}{% if member.optional %}?{% endif %} + + {% if member.parameters %} + pre.prettyprint + code. + {$ member.name $}{$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(doc.returnType) $} + {% endif %} + :markdown +{$ member.description | indent(6, true) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $} + + + +{% endif %}{% endfor %} +{%- endif -%} + +{% endblock %} diff --git a/public/api-builder/angular.io-package/templates/enum.template.html b/public/api-builder/angular.io-package/templates/enum.template.html new file mode 100644 index 0000000000..9c59159b29 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/enum.template.html @@ -0,0 +1 @@ +{% extends 'class.template.html' -%} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/function.template.html b/public/api-builder/angular.io-package/templates/function.template.html new file mode 100644 index 0000000000..5136a35021 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/function.template.html @@ -0,0 +1,22 @@ +{% include "lib/githubLinks.html" -%} +{% include "lib/paramList.html" -%} +{% extends 'layout/base.template.html' -%} + +{% block body %} +.l-main-section + h2(class="function export") {$ doc.name $} + + {% if doc.parameters %} + pre.prettyprint + code. + {$ doc.name $}{$ paramList(doc.parameters) | indent(4, true) | trim $}{$ returnType(doc.returnType) $} + {% endif %} + + p.location-badge. + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ githubViewLink(doc) $} + + :markdown +{$ doc.description | indent(4, true) | trimBlankLines $} + +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/jade-data.template.html b/public/api-builder/angular.io-package/templates/jade-data.template.html new file mode 100644 index 0000000000..3cefa7980e --- /dev/null +++ b/public/api-builder/angular.io-package/templates/jade-data.template.html @@ -0,0 +1,9 @@ +{ +{%- for item in doc.data %} + "{$ item.name $}" : { + "title" : "{$ item.title $}"{% if item.intro %}, + "intro" : "{$ item.intro $}"{% endif %}{% if item.varType %}, + "varType" : "{$ item.varType $}"{% endif %} + }{% if not loop.last %},{% endif %} +{% endfor -%} +} diff --git a/public/api-builder/angular.io-package/templates/layout/base.template.html b/public/api-builder/angular.io-package/templates/layout/base.template.html new file mode 100644 index 0000000000..16a0d9dc96 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/layout/base.template.html @@ -0,0 +1 @@ +{% block body %}{% endblock %} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/lib/paramList.html b/public/api-builder/angular.io-package/templates/lib/paramList.html new file mode 100644 index 0000000000..24ba12c080 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/lib/paramList.html @@ -0,0 +1,12 @@ +{% macro paramList(params) -%} + {%- if params -%} + ({%- for param in params -%} + {$ param | escape $}{% if not loop.last %}, {% endif %} + {%- endfor %}) + {%- endif %} +{%- endmacro -%} + + +{% macro returnType(returnType) -%} + {%- if returnType %} : {$ returnType | escape $}{% endif -%} +{%- endmacro -%} diff --git a/public/api-builder/angular.io-package/templates/module.template.html b/public/api-builder/angular.io-package/templates/module.template.html new file mode 100644 index 0000000000..3fbe568783 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/module.template.html @@ -0,0 +1,15 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' -%} +{% block body -%} +p.location-badge. + defined in {$ githubViewLink(doc) $} + +ul + for page, slug in public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]]._data + if slug != 'index' + - var url = "/docs/" + current.path[1] + "/" + current.path[2] + "/" + current.path[3] + "/" + current.path[4] + "/" + slug + ".html" + + li.c8 + != partial("../../../../../_includes/_hover-card", {name: page.title, url: url }) + +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/var.template.html b/public/api-builder/angular.io-package/templates/var.template.html new file mode 100644 index 0000000000..72aa66ecfd --- /dev/null +++ b/public/api-builder/angular.io-package/templates/var.template.html @@ -0,0 +1,12 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' %} + +{% block body %} +.l-main-section + p.location-badge. + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ githubViewLink(doc) $} + + :markdown +{$ doc.description | indent(4, true) | trimBlankLines $} +{% endblock %} diff --git a/public/api-builder/docs-package/index.js b/public/api-builder/docs-package/index.js new file mode 100644 index 0000000000..b456ea6b3d --- /dev/null +++ b/public/api-builder/docs-package/index.js @@ -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/generateNavigationDoc')) +.processor(require('./processors/extractTitleFromGuides')) +.processor(require('./processors/createOverviewDump')) +.processor(require('./processors/checkUnbalancedBackTicks')) + +// Configure the log service +.config(function(log) { + log.level = 'info'; +}) + + +.config(function(renderDocsProcessor, versionInfo) { + renderDocsProcessor.extraData.versionInfo = versionInfo; +}) + +// Configure file reading +.config(function(readFilesProcessor, ngdocFileReader, readTypeScriptModules) { + readFilesProcessor.fileReaders = [ngdocFileReader]; + var angular_repo_path = path.resolve(__dirname, '../../../../angular'); + // TODO: 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); + } + readFilesProcessor.basePath = angular_repo_path; + readFilesProcessor.sourceFiles = [ + { include: 'modules/*/docs/**/*.md', basePath: 'modules' }, + { include: 'docs/content/**/*.md', basePath: 'docs/content' } + ]; + + readTypeScriptModules.sourceFiles = [ + '*/*.@(js|es6|ts)', + '*/src/**/*.@(js|es6|ts)' + ]; + readTypeScriptModules.basePath = path.resolve(readFilesProcessor.basePath, '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' + }); +}); diff --git a/public/api-builder/docs-package/mocks/importedSrc.ts b/public/api-builder/docs-package/mocks/importedSrc.ts new file mode 100644 index 0000000000..74a115e5fe --- /dev/null +++ b/public/api-builder/docs-package/mocks/importedSrc.ts @@ -0,0 +1 @@ +export var x = 100; \ No newline at end of file diff --git a/public/api-builder/docs-package/mocks/mockPackage.js b/public/api-builder/docs-package/mocks/mockPackage.js new file mode 100644 index 0000000000..61d0df228a --- /dev/null +++ b/public/api-builder/docs-package/mocks/mockPackage.js @@ -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); }); + +}; diff --git a/public/api-builder/docs-package/mocks/testSrc.ts b/public/api-builder/docs-package/mocks/testSrc.ts new file mode 100644 index 0000000000..7b6d3f18dd --- /dev/null +++ b/public/api-builder/docs-package/mocks/testSrc.ts @@ -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; \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/checkUnbalancedBackTicks.js b/public/api-builder/docs-package/processors/checkUnbalancedBackTicks.js new file mode 100644 index 0000000000..2d359c7785 --- /dev/null +++ b/public/api-builder/docs-package/processors/checkUnbalancedBackTicks.js @@ -0,0 +1,28 @@ +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'], + $process: function(docs) { + _.forEach(docs, function(doc) { + if ( doc.renderedContent ) { + var matches = doc.renderedContent.match(BACKTICK_REGEX); + if (matches && matches.length % 2 !== 0) { + log.warn(createDocMessage('checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content', doc)); + console.log(doc.renderedContent); + } + } + }); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/convertPrivateClassesToInterfaces.js b/public/api-builder/docs-package/processors/convertPrivateClassesToInterfaces.js new file mode 100644 index 0000000000..6e6e758daa --- /dev/null +++ b/public/api-builder/docs-package/processors/convertPrivateClassesToInterfaces.js @@ -0,0 +1,10 @@ +module.exports = function convertPrivateClassesToInterfacesProcessor(convertPrivateClassesToInterfaces) { + return { + $runAfter: ['processing-docs'], + $runBefore: ['docs-processed'], + $process: function(docs) { + convertPrivateClassesToInterfaces(docs, false); + return docs; + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/createOverviewDump.js b/public/api-builder/docs-package/processors/createOverviewDump.js new file mode 100644 index 0000000000..eadd24e0af --- /dev/null +++ b/public/api-builder/docs-package/processors/createOverviewDump.js @@ -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); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/extractTitleFromGuides.js b/public/api-builder/docs-package/processors/extractTitleFromGuides.js new file mode 100644 index 0000000000..1a4e6c3806 --- /dev/null +++ b/public/api-builder/docs-package/processors/extractTitleFromGuides.js @@ -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]; + } +} \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/generateNavigationDoc.js b/public/api-builder/docs-package/processors/generateNavigationDoc.js new file mode 100644 index 0000000000..5e8ecf7149 --- /dev/null +++ b/public/api-builder/docs-package/processors/generateNavigationDoc.js @@ -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.private) { + 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); + } + }; +}; diff --git a/public/api-builder/docs-package/readers/ngdoc.js b/public/api-builder/docs-package/readers/ngdoc.js new file mode 100644 index 0000000000..a1e6a7e4d5 --- /dev/null +++ b/public/api-builder/docs-package/readers/ngdoc.js @@ -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; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/readers/ngdoc.spec.js b/public/api-builder/docs-package/readers/ngdoc.spec.js new file mode 100644 index 0000000000..658663c9fb --- /dev/null +++ b/public/api-builder/docs-package/readers/ngdoc.spec.js @@ -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 + }]); + }); + }); +}); + diff --git a/public/api-builder/docs-package/templates/class.template.html b/public/api-builder/docs-package/templates/class.template.html new file mode 100644 index 0000000000..a015cf5e7e --- /dev/null +++ b/public/api-builder/docs-package/templates/class.template.html @@ -0,0 +1,44 @@ +{% include "lib/paramList.html" -%} +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' -%} + +{% block body %} +
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $}
+
{$ doc.description | marked $}
+ +{%- if doc.decorators %} +exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $}
{$ doc.description | marked $}
+ +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/guide.template.html b/public/api-builder/docs-package/templates/guide.template.html new file mode 100644 index 0000000000..3ee289dbc8 --- /dev/null +++ b/public/api-builder/docs-package/templates/guide.template.html @@ -0,0 +1,5 @@ +{% extends 'layout/base.template.html' %} + +{% block body %} +{$ doc.description | marked $} +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/interface.template.html b/public/api-builder/docs-package/templates/interface.template.html new file mode 100644 index 0000000000..3bde456ceb --- /dev/null +++ b/public/api-builder/docs-package/templates/interface.template.html @@ -0,0 +1 @@ +{% extends 'class.template.html' -%} diff --git a/public/api-builder/docs-package/templates/layout/base.template.html b/public/api-builder/docs-package/templates/layout/base.template.html new file mode 100644 index 0000000000..16a0d9dc96 --- /dev/null +++ b/public/api-builder/docs-package/templates/layout/base.template.html @@ -0,0 +1 @@ +{% block body %}{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/lib/githubLinks.html b/public/api-builder/docs-package/templates/lib/githubLinks.html new file mode 100644 index 0000000000..574656542f --- /dev/null +++ b/public/api-builder/docs-package/templates/lib/githubLinks.html @@ -0,0 +1,3 @@ +{% macro githubViewLink(doc) -%} + {$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $}) +{%- endmacro -%} diff --git a/public/api-builder/docs-package/templates/lib/paramList.html b/public/api-builder/docs-package/templates/lib/paramList.html new file mode 100644 index 0000000000..85dccb212b --- /dev/null +++ b/public/api-builder/docs-package/templates/lib/paramList.html @@ -0,0 +1,7 @@ +{% macro paramList(params) -%} + {%- if params -%}( + {%- for param in params -%} + {$ param | escape $}{% if not loop.last %}, {% endif %} + {%- endfor %}) + {%- endif %} +{%- endmacro -%} diff --git a/public/api-builder/docs-package/templates/module.template.html b/public/api-builder/docs-package/templates/module.template.html new file mode 100644 index 0000000000..c016075c48 --- /dev/null +++ b/public/api-builder/docs-package/templates/module.template.html @@ -0,0 +1,19 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' %} + +{% block body %} +defined in {$ githubViewLink(doc) $}
+{$ doc.description | marked $}
+ +{% if doc.exports.length %} +exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $}
{$ doc.description | marked $}
+ +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/var.template.html b/public/api-builder/docs-package/templates/var.template.html new file mode 100644 index 0000000000..2d43873595 --- /dev/null +++ b/public/api-builder/docs-package/templates/var.template.html @@ -0,0 +1,10 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' %} + +{% block body %} +exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $}
{$ doc.description | marked $}
+ +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/links-package/index.js b/public/api-builder/links-package/index.js new file mode 100644 index 0000000000..a3ffc71991 --- /dev/null +++ b/public/api-builder/links-package/index.js @@ -0,0 +1,12 @@ +var Package = require('dgeni').Package; + +module.exports = new Package('links', []) + +.factory(require('./inline-tag-defs/link')) +.factory(require('dgeni-packages/ngdoc/services/getAliases')) +.factory(require('dgeni-packages/ngdoc/services/getDocFromAlias')) +.factory(require('./services/getLinkInfo')) + +.config(function(inlineTagProcessor, linkInlineTagDef) { + inlineTagProcessor.inlineTagDefinitions.push(linkInlineTagDef); +}); diff --git a/public/api-builder/links-package/inline-tag-defs/link.js b/public/api-builder/links-package/inline-tag-defs/link.js new file mode 100644 index 0000000000..89272c63ca --- /dev/null +++ b/public/api-builder/links-package/inline-tag-defs/link.js @@ -0,0 +1,33 @@ +var INLINE_LINK = /(\S+)(?:\s+([\s\S]+))?/; + +/** + * @dgService linkInlineTagDef + * @description + * Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors + * @kind function + * @param {Object} url The url to match + * @param {Function} docs error message + * @return {String} The html link information + * + * @property {boolean} relativeLinks Whether we expect the links to be relative to the originating doc + */ +module.exports = function linkInlineTagDef(getLinkInfo, createDocMessage, log) { + return { + name: 'link', + description: 'Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors', + handler: function(doc, tagName, tagDescription) { + + // Parse out the uri and title + return tagDescription.replace(INLINE_LINK, function(match, uri, title) { + + var linkInfo = getLinkInfo(uri, title, doc); + + if ( !linkInfo.valid ) { + log.warn(createDocMessage(linkInfo.error, doc)); + } + + return "" + linkInfo.title + ""; + }); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/links-package/services/getLinkInfo.js b/public/api-builder/links-package/services/getLinkInfo.js new file mode 100644 index 0000000000..787ff04338 --- /dev/null +++ b/public/api-builder/links-package/services/getLinkInfo.js @@ -0,0 +1,72 @@ +var _ = require('lodash'); +var path = require('canonical-path'); + +/** + * @dgService getLinkInfo + * @description + * Get link information to a document that matches the given url + * @kind function + * @param {String} url The url to match + * @param {String} title An optional title to return in the link information + * @return {Object} The link information + * + * @property {boolean} relativeLinks Whether we expect the links to be relative to the originating doc + */ +module.exports = function getLinkInfo(getDocFromAlias, encodeCodeBlock, log) { + + return function getLinkInfoImpl(url, title, currentDoc) { + var linkInfo = { + url: url, + type: 'url', + valid: true, + title: title || url + }; + + if ( !url ) { + throw new Error('Invalid url'); + } + + var docs = getDocFromAlias(url, currentDoc); + + if ( !getLinkInfoImpl.useFirstAmbiguousLink && docs.length > 1 ) { + + linkInfo.valid = false; + linkInfo.errorType = 'ambiguous'; + linkInfo.error = 'Ambiguous link: "' + url + '".\n' + + docs.reduce(function(msg, doc) { return msg + '\n "' + doc.id + '" ('+ doc.docType + ') : (' + doc.path + ' / ' + doc.fileInfo.relativePath + ')'; }, 'Matching docs: '); + + } else if ( docs.length >= 1 ) { + + linkInfo.url = docs[0].path; + linkInfo.title = title || encodeCodeBlock(docs[0].name, true); + linkInfo.type = 'doc'; + + if ( getLinkInfoImpl.relativeLinks && currentDoc && currentDoc.path ) { + var currentFolder = path.dirname(currentDoc.path); + var docFolder = path.dirname(linkInfo.url); + var relativeFolder = path.relative(path.join('/', currentFolder), path.join('/', docFolder)); + linkInfo.url = path.join(relativeFolder, path.basename(linkInfo.url)); + log.debug(currentDoc.path, docs[0].path, linkInfo.url); + } + + } else if ( url.indexOf('#') > 0 ) { + var pathAndHash = url.split('#'); + linkInfo = getLinkInfoImpl(pathAndHash[0], title, currentDoc); + linkInfo.url = linkInfo.url + '#' + pathAndHash[1]; + return linkInfo; + + } else if ( url.indexOf('/') === -1 && url.indexOf('#') !== 0 ) { + + linkInfo.valid = false; + linkInfo.errorType = 'missing'; + linkInfo.error = 'Invalid link (does not match any doc): "' + url + '"'; + + } else { + + linkInfo.title = title || (( url.indexOf('#') === 0 ) ? url.substring(1) : path.basename(url, '.html')); + + } + + return linkInfo; + }; +}; \ No newline at end of file diff --git a/public/api-builder/public-docs-package/index.js b/public/api-builder/public-docs-package/index.js new file mode 100644 index 0000000000..ee8823d845 --- /dev/null +++ b/public/api-builder/public-docs-package/index.js @@ -0,0 +1,24 @@ +var Package = require('dgeni').Package; +var basePackage = require('../docs-package'); + +module.exports = new Package('angular-v2-public-docs', [basePackage]) + +.config(function(readTypeScriptModules) { + readTypeScriptModules.sourceFiles = [ + 'angular2/lifecycle_hooks.ts', + 'angular2/core.ts', + 'angular2/http.ts', + 'angular2/router.ts', + 'angular2/test.ts' + ]; + readTypeScriptModules.hidePrivateMembers = true; +}) + +.config(function(getLinkInfo) { + getLinkInfo.useFirstAmbiguousLink = false; +}) + +// Configure file writing +.config(function(writeFilesProcessor) { + writeFilesProcessor.outputFolder = 'dist/public_docs'; +}); diff --git a/public/api-builder/typescript-package/index.js b/public/api-builder/typescript-package/index.js new file mode 100644 index 0000000000..def48537ae --- /dev/null +++ b/public/api-builder/typescript-package/index.js @@ -0,0 +1,70 @@ +var basePackage = require('dgeni-packages/base'); +var Package = require('dgeni').Package; +var path = require('canonical-path'); + +// Define the dgeni package for generating the docs +module.exports = new Package('typescript-parsing', [basePackage]) + +// Register the services and file readers +.factory(require('./services/modules')) +.factory(require('./services/tsParser')) +.factory(require('./services/tsParser/createCompilerHost')) +.factory(require('./services/tsParser/getFileInfo')) +.factory(require('./services/tsParser/getExportDocType')) +.factory(require('./services/tsParser/getContent')) + +.factory(require('./services/convertPrivateClassesToInterfaces')) + +.factory('EXPORT_DOC_TYPES', function() { + return [ + 'class', + 'interface', + 'function', + 'var', + 'const', + 'enum', + 'type-alias' + ]; +}) + + +// Register the processors +.processor(require('./processors/readTypeScriptModules')) + + +// Configure the log service +.config(function(log) { + log.level = 'warn'; +}) + + +// Configure ids and paths +.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) { + + computeIdsProcessor.idTemplates.push({ + docTypes: ['member'], + idTemplate: '${classDoc.id}.${name}', + getAliases: function(doc) { return [doc.id]; } + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: ['member'], + pathTemplate: '${classDoc.path}/${name}', + getOutputPath: function() {} // These docs are not written to their own file, instead they are part of their class doc + }); + + var MODULES_DOCS_PATH = 'partials/modules'; + + computePathsProcessor.pathTemplates.push({ + docTypes: ['module'], + pathTemplate: '/${id}', + outputPathTemplate: MODULES_DOCS_PATH + '/${id}/index.html' + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: EXPORT_DOC_TYPES, + pathTemplate: '${moduleDoc.path}/${name}', + outputPathTemplate: MODULES_DOCS_PATH + '/${path}/index.html' + }); + +}); diff --git a/public/api-builder/typescript-package/mocks/mockPackage.js b/public/api-builder/typescript-package/mocks/mockPackage.js new file mode 100644 index 0000000000..834d53c959 --- /dev/null +++ b/public/api-builder/typescript-package/mocks/mockPackage.js @@ -0,0 +1,11 @@ +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); }) + .factory('templateEngine', function() { return {}; }); + +}; diff --git a/public/api-builder/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts b/public/api-builder/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts new file mode 100644 index 0000000000..476d4cc44b --- /dev/null +++ b/public/api-builder/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts @@ -0,0 +1,4 @@ +export var __esModule = true; +export class OKToExport {} +export function _thisIsPrivate() {} +export var thisIsOK = '!'; \ No newline at end of file diff --git a/public/api-builder/typescript-package/mocks/readTypeScriptModules/interfaces.ts b/public/api-builder/typescript-package/mocks/readTypeScriptModules/interfaces.ts new file mode 100644 index 0000000000..a2d4be4fec --- /dev/null +++ b/public/api-builder/typescript-package/mocks/readTypeScriptModules/interfaces.ts @@ -0,0 +1,5 @@ +export interface MyInterface { + optionalProperty? : string +