UNPKG

7.19 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 templateDir: null
90 });
91
92 //map the list of files we'll be generating documentation for
93 files.forEach(function(group) {
94 dest = group.dest;
95 group.src.filter(function(file) {
96 var obj = {
97 filename: path.basename(file),
98 folder: path.dirname(file),
99 path: file
100 };
101
102 if(options.pathFilter)
103 obj.folder = obj.folder.replace(options.pathFilter, '');
104
105 folders.push(obj);
106 });
107 });
108
109 //map the set of folders, sort, and generate documentation
110 folders = _(folders)
111 .groupBy(function(p) { return p.folder; })
112 .sortBy(function(p) { return p.length && p[0].folder; })
113 .map(function(group) {
114 var file = group.length && group[0] || {};
115 return {
116 name: file.folder,
117 path: file.path.replace(file.filename, ''),
118 files: group,
119 fileData: {}
120 };
121 })
122 .each(function(folder) {
123 var def = Q.defer();
124
125 //queue the promise for the docs, and ensure the target exists (can safely run on an existing folder)
126 promises.push(def.promise);
127 grunt.file.mkdir(dest +'/'+ folder.name);
128
129 //generate the docs for the folder, resolving the promise when complete
130 jsdox.generateForDir(folder.path, dest +'/'+ folder.name, options.templateDir, def.resolve, function(file, fileData) {
131 folder.fileData[file] = fileData;
132 });
133 });
134
135 //when all docs have been generated, build the table of contents, if needed (this stuff should be moved into a template)
136 Q.all(promises).then(function() {
137 if(!options.contentsEnabled) return done();
138
139 var buf = [],
140 w = function() { buf.push.apply(buf, arguments); };
141
142 w(options.contentsTitle || 'Documentation', '===');
143
144 folders.forEach(function(folder) {
145 var docs = [];
146
147 //derive the set of documents we *actually* generated docs for; that way we only add documented
148 //code to the table of contents
149 folder.files.forEach(function(file) {
150 //reset the path, pointing to the markdown file, and set the file data parsed by jsdox
151 file.path = path.join(file.folder, file.filename.replace('.js', '')).replace(/^\//, '');
152 file.data = folder.fileData[file.filename]||{};
153 if(grunt.file.exists(dest, file.path + '.md')) docs.push(file);
154 });
155
156 if(docs.length > 0) {
157 w(folder.name || 'Root', '---');
158 w('name | overview', ':-- | :--');
159 docs.forEach(function(file) {
160 w('['+ file.path +'.js]('+ file.path + '.md' + ') | '+ (file.data.overview?'_'+ file.data.overview +'_':''));
161 });
162 w('- - -');
163 }
164
165 w('\n');
166 });
167
168 //write out the table of contents, and complete the async generate task
169 fs.writeFileSync(path.join(dest, (options.contentsFile || 'readme.md')), buf.join('\n'));
170 done();
171 });
172 }
173
174 grunt.registerMultiTask('jsdox', 'Recursively generates markdown files for your project.', function() {
175 var target = this.target,
176 data = this.data,
177 done = this.async(),
178 handler = this.target === 'publish' ? publish : generate;
179
180 //we're either generating documents based on the passed in config; or we're publishing the docs repo...
181 handler.call(this, target, data, done);
182 });
183
184};