UNPKG

gulp-ngdocs

Version:

gulp plugin for angularjs documentation

338 lines (306 loc) 9.76 kB
'use strict'; var dom = require('./src/dom'); var ngdoc = require('./src/ngdoc'); var reader = require('./src/reader'); var fs = require('fs'); var vfs = require('vinyl-fs'); var through2 = require('through2'); var extend = require('extend'); var _ = require('lodash'); var gutil = require('gulp-util'); var File = gutil.File; var PluginError = gutil.PluginError; var path = require('canonical-path'); var StringDecoder = require('string_decoder').StringDecoder; var decoder = new StringDecoder('utf8'); var merge = require('merge-stream'); var setup = {sections: {}, pages: [], apis: {}}; var fakeDest = '_FAKE_DEST_'; var templates = path.resolve(__dirname, 'src/templates'); var nodeModules = path.resolve(__dirname, 'node_modules'); var flattenedNodeModules = path.resolve(__dirname, '../'); function copyTemplates() { return function () { return vfs.src(['**/*', '!**/*.tmpl'], {cwd: templates}); }; } function streamFile(src, dir, dest, name) { return function () { return vfs.src(src).pipe(through2.obj(function (file, enc, callback) { name = name === undefined ? path.normalize(file.path).split('/').pop() : name; this.push(new File({ base: dest, cwd: dest, path: path.join(dest, dir, name), contents: file.contents })); callback(null); })); }; } function sections(sects) { return merge(_.map(sects, function (data, key) { if (data instanceof String) { data = {glob: data}; } if (!data.hasOwnProperty('glob')) { throw new PluginError('gulp-ngdocs', 'Invalid sections, please refer to the documentation.'); } if (!data.hasOwnProperty('title')) { data.title = key; } if (!data.hasOwnProperty('api')) { data.api = true; } var glob = data.glob; var opts = data.globOpts; setup.sections[key] = data.title; setup.apis[key] = data.api; return vfs.src(glob, opts) .pipe(through2.obj( function (file, enc, cb) { file.section = key; this.push(file); cb(null); } )); })); } function processDoc(opts) { opts = opts || {}; var options = extend({ startPage: '/api', scripts: [], styles: [], title: 'API Documentation', html5Mode: true, editExample: true, navTemplate: false, navContent: '', navTemplateData: {} }, opts); setup.pages = []; //Extend loadDefaults options.loadDefaults = extend({ angular: true, angularAnimate: true, marked: true }, opts.loadDefaults); if (options.scripts && !(options.scripts instanceof Array)) { options.scripts = [options.scripts]; } if (options.styles && !(options.styles instanceof Array)) { options.styles = [options.styles]; } var defaultSection = 'api'; var defaultScripts = []; //Default root paths for scripts var scriptPaths = { angular : [ 'angular/angular.min.js', 'angular/angular.min.js.map' ], angularAnimate: [ 'angular-animate/angular-animate.min.js', 'angular-animate/angular-animate.min.js.map' ], marked: [ 'marked/lib/marked.js' ] }; //Sets default script paths function joinNodeModules(jsPaths){ _.each(jsPaths, function(jsPath){ var libPath = path.join(nodeModules, jsPath), flattenedLibPath = path.join(flattenedNodeModules, jsPath); if (fs.existsSync(libPath)) { defaultScripts.push(libPath); } else if (fs.existsSync(flattenedLibPath)) { defaultScripts.push(flattenedLibPath); } else { console.error('Could not find ' + jsPath); } }); } //Iterate and checks to join paths _.each(scriptPaths, function(paths, key){ if(options.loadDefaults[key]) joinNodeModules(paths); }); function writeSetup() { var options = setup.__options, content, data = { scripts: options.scripts, styles: options.styles, sections: _.keys(setup.sections).join('|'), discussions: options.discussions, analytics: options.analytics, navContent: options.navContent, title: options.title, image: options.image, titleLink: options.titleLink, imageLink: options.imageLink, bestMatch: options.bestMatch, deferLoad: !!options.deferLoad }; // create index.html var index = path.resolve(templates, 'index.tmpl'); if (options.template && path.resolve(options.template)) { index = options.template; } content = fs.readFileSync(index, 'utf8'); content = _.template(content, data); docsStream.push(new File({ base: fakeDest, cwd: fakeDest, path: path.join(fakeDest, 'index.html'), contents: new Buffer(content, 'utf8') })); // create setup file setup.html5Mode = options.html5Mode; setup.editExample = options.editExample; setup.startPage = options.startPage; setup.discussions = options.discussions; setup.scripts = options.scripts; docsStream.push(new File({ base: fakeDest, cwd: fakeDest, path: setup.__file, contents: new Buffer('NG_DOCS=' + JSON.stringify(setup, null, 2) + ';', 'utf8') })); } function transformFunction (file, enc, callback) { if (file.isNull()) { callback(null); return; // ignore } if (file.isStream()) { callback(new gutil.PluginError('gulp-ngdocs', 'Streaming not supported')); return; } if (file.contents) { if (!merged) { merged = merge(fstreams.map(function (f) { var s = f(); s.on('data', function (file) { docsStream.push(file); }); return s; })); merged.on('end', function () { mergedEnded = true; if (docsStreamEndCb) { docsStreamEndCb(null); } }); } var content = decoder.write(file.contents); if (!file.section) { file.section = defaultSection; } reader.process(content, file.path, file.section, options); } callback(null); } function flushFunction (cb) { if (merged) { docsStreamEndCb = cb; // IMPORTANT: If you do not want an error here to destroy a gulp watch, // see: http://stackoverflow.com/questions/23971388/prevent-errors-from-breaking-crashing-gulp-watch try{ ngdoc.merge(reader.docs); reader.docs.forEach(function(doc){ try { // this hack is here because on OSX angular.module and angular.Module map to the same file. var id = doc.id.replace('angular.Module', 'angular.IModule').replace(':', '.'), file = path.join(fakeDest, 'partials', doc.section, id + '.html'), dir = path.join(fakeDest, 'partials', doc.section); docsStream.push(new File({ base: fakeDest, cwd: fakeDest, path: file, contents: new Buffer(doc.html(), 'utf8') })); } catch (docError) { var cause = docError.name + ': ' + docError.message, placement = doc.file + ':' + doc.line, message = cause + ' at ' + placement; throw new Error(message); } }); ngdoc.checkBrokenLinks(reader.docs, setup.apis, options); setup.pages = _.union(setup.pages, ngdoc.metadata(reader.docs)); } catch (flushError) { console.log(flushError); cb(flushError); } writeSetup(this); if (mergedEnded) { docsStreamEndCb(null); docsStreamEndCb = false; } } else { cb(null); } } var fstreams = []; var docsStream = through2.obj(transformFunction, flushFunction); var merged = false; var mergedEnded = false; var docsStreamEndCb = false; if (options.navTemplate) { options.navContent = _.template( fs.readFileSync(options.navTemplate, 'utf8'), options.navTemplateData); } if (options.image) { if (!/^((https?:)?\/\/|\.\.\/)/.test(options.image)) { fstreams.push(streamFile(options.image, 'img', fakeDest)); options.image = 'img/' + path.basename(options.image); } } var scriptNames = []; options.scripts = _.map(options.scripts, function (file) { var fileName = path.normalize(file); scriptNames.push(fileName); if (/^((https?:)?\/\/)/.test(file)) { return file; } else { fstreams.push(streamFile(file, path.join('js', path.dirname(fileName)), fakeDest)); return path.join('js', fileName); } }); _.forEach(defaultScripts, function (script, i) { var fileName = path.normalize(script).split('/').pop(); if (scriptNames.indexOf(fileName) === -1) { fstreams.push(streamFile(script, 'js', fakeDest)); options.scripts.splice(i, 0, path.join('js', fileName)); } }); //Filter the maps options.scripts = _.filter(options.scripts, function (file) { return !(/^.*\.map$/.test(file)); }); options.styles = _.map(options.styles, function(file) { var fileName = path.normalize(file); if (/^((https?:)?\/\/)/.test(file)) { return file; } else { fstreams.push(streamFile(file, path.join('css', path.dirname(fileName)), fakeDest)); return path.join('css', fileName); } }); fstreams.push(copyTemplates(templates)); setup.__file = path.join(fakeDest, 'js/docs-setup.js'); setup.__options = options; reader.docs = []; if (Object.keys(setup.sections).length === 0) { setup.sections[defaultSection] = options.title; setup.apis[defaultSection] = true; } return docsStream; } module.exports = { process: processDoc, sections: sections };