UNPKG

6.65 kBJavaScriptView Raw
1/**
2 * grunt-jsdox
3 * https://github.com/mmacmillan/grunt-jsdox
4 *
5 * Copyright (c) 2014 Mike MacMillan
6 * Licensed under the MIT license.
7 */
8
9'use strict';
10
11var fs = require('fs'),
12 path = require('path'),
13 _ = require('lodash'),
14 Q = require('q'),
15 jsdox = require('jsdox');
16
17module.exports = function(grunt) {
18
19 /**
20 * publishes the 'dest' folder to the git repo it is currently configured for. this code only
21 * issues git commands; it doesn't target any specific repo...setting up the markdown repo ahead of time is
22 * necessary.
23 *
24 * @param target the target action, which is always 'publish'; ignore
25 * @param data the data for the publish action
26 * @param done the handler to end the async operation
27 * @returns {*}
28 */
29 function publish(target, data, done) {
30 if(data.enabled !== true)
31 return grunt.fail.fatal('publishing is disabled; set "publish.enabled" to true to enable');
32
33 if(!data.path)
34 return grunt.fail.fatal('the "publish.path" attribute must be provided for the publish task');
35
36 if(!data.message)
37 return grunt.fail.fatal('the "publish.message" attribute must be provided for the publish task');
38
39 /**
40 * provide the defaults for the git commands. by default, we are assuming the markdown repo is stored
41 * in a separate directory, so our git commands need to support that...provide the git-dir and work-tree
42 * paths. the standard publish process is:
43 *
44 * git add .
45 * git commit -m <configured commit message>
46 * git push <remoteName> <remoteBranch> (defaults to upstream master)
47 *
48 */
49 _.defaults(data, {
50 addCmd: ['--git-dir='+ data.path +'/.git', '--work-tree='+ data.path, 'add', '.'],
51 commitCmd: ['--git-dir='+ data.path +'/.git', '--work-tree='+ data.path, 'commit', '-m', data.message],
52 pushCmd: ['--git-dir='+ data.path +'/.git', '--work-tree='+ data.path, 'push', data.remoteName || 'upstream', data.remoteBranch || 'master']
53 });
54
55 //run the git commands, using promises to handle when complete
56 function cmd(args) {
57 var def = Q.defer();
58 grunt.util.spawn({ cmd: 'git', args: args }, def.resolve);
59 return def.promise;
60 };
61
62 //add, commit, and publish the doc repository
63 cmd(data.addCmd)
64 .then(cmd.bind(this, data.commitCmd))
65 .then(cmd.bind(this, data.pushCmd))
66 .then(done);
67 }
68
69
70 /**
71 * generates the markdown documentation using the jsDox module, for the target files
72 *
73 * @param target the generation action that we are running
74 * @param data the data for the target action
75 * @param done the handler to end the async operation
76 */
77 function generate(target, data, done) {
78 var files = this.files,
79 dest = null,
80 paths = [],
81 promises = [],
82 folders = [];
83
84 //by default, we generate a table of contents called readme.md
85 var options = this.options({
86 contentsEnabled: true,
87 contentsFile: 'readme.md',
88 contentsTitle: 'Documentation'
89 });
90
91 //map the list of files we'll be generating documentation for
92 files.forEach(function(group) {
93 dest = group.dest;
94 group.src.filter(function(file) {
95 var obj = {
96 filename: path.basename(file),
97 folder: path.dirname(file),
98 path: file
99 };
100
101 if(options.pathFilter)
102 obj.folder = obj.folder.replace(options.pathFilter, '');
103
104 folders.push(obj);
105 })
106 });
107
108 //map the set of folders, sort, and generate documentation
109 folders = _(folders)
110 .groupBy(function(p) { return p.folder })
111 .sortBy(function(p) { return p.length && p[0].folder })
112 .map(function(group) {
113 var file = group.length && group[0] || {};
114 return {
115 name: file.folder,
116 path: file.path.replace(file.filename, ''),
117 files: group
118 }
119 })
120 .each(function(folder) {
121 var def = Q.defer();
122
123 //queue the promise for the docs, and ensure the target exists (can safely run on an existing folder)
124 promises.push(def.promise);
125 grunt.file.mkdir(dest +'/'+ folder.name);
126
127 //generate the docs for the folder, resolving the promise when complete
128 jsdox.generateForDir(folder.path, dest +'/'+ folder.name, def.resolve);
129 });
130
131 //when all docs have been generated, build the table of contents, if needed (this stuff should be moved into a template)
132 Q.all(promises).then(function() {
133 if(!options.contentsEnabled) return done();
134
135 var buf = [],
136 w = function() { buf.push.apply(buf, arguments) };
137
138 w(options.contentsTitle || 'Documentation', '===\n');
139
140 folders.forEach(function(folder) {
141 var docs = [];
142
143 //derive the set of documents we *actually* generated docs for; that way we only add documented
144 //code to the table of contents
145 folder.files.forEach(function(file) {
146 var p = path.join(file.folder, file.filename.replace('.js', '')).replace(/^\//, '');
147
148 if(grunt.file.exists(dest, p +'.md'))
149 docs.push(p);
150 })
151
152 if(docs.length > 0) {
153 w(folder.name || 'Root', '---');
154 w(' |', '-|');
155
156 docs.forEach(function(p) { w('['+ p +'.js]('+ p +') |') });
157 }
158
159 w('\n');
160 });
161
162 //write out the table of contents, and complete the async generate task
163 fs.writeFileSync(path.join(dest, (options.contentsFile || 'readme.md')), buf.join('\n'));
164 done();
165 });
166 }
167
168 grunt.registerMultiTask('jsdox', 'Recursively generates markdown files for your project.', function() {
169 var target = this.target,
170 data = this.data,
171 done = this.async(),
172 handler = this.target == 'publish' ? publish : generate;
173
174 //we're either generating documents based on the passed in config; or we're publishing the docs repo...
175 handler.call(this, target, data, done);
176 });
177
178};