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 | ;
|
10 |
|
11 | var fs = require('fs'),
|
12 | path = require('path'),
|
13 | _ = require('lodash'),
|
14 | Q = require('q'),
|
15 | jsdox = require('jsdox');
|
16 |
|
17 | module.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 | };
|