UNPKG

12.6 kBJavaScriptView Raw
1"use strict";
2/**
3 * @fileOverview Gruntfile tasks. These tasks are intended to help you when modifying the template. If you are
4 * just using the template, don't sweat this stuff. To use these tasks, you must install grunt, if you haven't already,
5 * and install the dependencies. All of this requires node.js, of course.
6 *
7 * Install grunt:
8 *
9 * npm install -g grunt-cli
10 *
11 * Then in the directory where you found this file:
12 *
13 * npm install
14 *
15 * And you are all set. See the individual tasks for details.
16 *
17 * @module Gruntfile
18 * @requires path
19 * @requires lodash
20 * @requires http
21 * @requires async
22 * @requires fs
23 */
24var path = require( "path" );
25var _ = require( "lodash" );
26var request = require('request');
27var async = require( "async" );
28var fs = require( "fs" );
29
30// this rather odd arrangement of composing tasks like this to make sure this works on both
31// windows and linux correctly. We can't depend on Grunt or Node to normalize
32// paths for us because we shell out to make this work. So we gather up
33// our relative paths here, normalize them later and then pass them into
34// the shell to be run by JSDoc3.
35
36/**
37 * The definition to run the development test files. This runs the files in `fixtures` with the
38 * project's `conf.json` file.
39 * @private
40 */
41var jsdocTestPages = {
42 dest : "./testdocs",
43 tutorials : "./fixtures/tutorials",
44 template : "./template",
45 config : "./fixtures/testdocs.conf.json",
46 options : " --lenient --verbose --recurse"
47};
48/**
49 * The definition to run the sample files. This runs the files in `fixtures` with the
50 * sample's `conf.json` file. No task directly exposes this configuration. The `fixtures` task
51 * modifies this for each swatch it finds and then run the docs command against it.
52 * @private
53 */
54var jsdocExamplePages = {
55 src : ["./fixtures/", "./README.md"],
56 dest : "./themes",
57 tutorials : "./fixtures/tutorials",
58 template : "./template",
59 config : "./fixtures/example.conf.json",
60 options : " --lenient --verbose --recurse"
61};
62
63/**
64 * This definition provides the project's main, published documentation.
65 * @private
66 */
67var projectDocs = {
68 src : ["./Gruntfile.js", "./README.md", "./template/publish.js"],
69 dest : "./dox",
70 tutorials : "",
71 template : "./template",
72 config : "./template/jsdoc.conf.json",
73 options : " --lenient --verbose --recurse --private"
74};
75
76/**
77 * Normalizes all paths from a JSDoc task definition and and returns an executable string that can be passed to the shell.
78 * @param {object} jsdoc A JSDoc definition
79 * @returns {string}
80 */
81function jsdocCommand( jsdoc ) {
82 var cmd = [];
83 cmd.unshift( jsdoc.options );
84 if ( jsdoc.tutorials.length > 0 ) {
85 cmd.push( "-u " + path.resolve( jsdoc.tutorials ) );
86 }
87 cmd.push( "-d " + path.resolve( jsdoc.dest ) );
88 cmd.push( "-t " + path.resolve( jsdoc.template ) );
89 cmd.push( "-c " + path.resolve( jsdoc.config ) );
90 _.each( jsdoc.src, function ( src ) {
91 cmd.push( path.resolve( src ) );
92 } );
93 cmd.unshift( path.resolve( "./node_modules/jsdoc/jsdoc" ) );
94 cmd.unshift( "node" );
95
96 return cmd.join( " " );
97}
98
99var tasks = {
100 shell : {
101 options : {
102 stdout : true,
103 stderr : true
104 },
105 /**
106 * TASK: Create the a documentation set for testing changes to the template
107 * @name shell:testdocs
108 * @memberOf module:Gruntfile
109 */
110 testdocs : {
111 command : jsdocCommand( jsdocTestPages )
112 },
113 /**
114 * TASK: Create project documentation
115 * @name shell:dox
116 * @memberOf module:Gruntfile
117 */
118 dox : {
119 command : jsdocCommand( projectDocs )
120 },
121 release1 : {
122 command : [
123 "touch Gruntfile.js",
124 "git add .",
125 'git commit -m "ready for release"',
126 ].join( ";" )
127
128 },
129 release2 : {
130 command : ["npm version patch",
131 "git push",
132 "git push --tags",
133 "npm publish"
134 ].join( "&&" )
135 }
136 },
137 jsdoc : {
138 testdocs : {
139 src : ['fixtures/**.js', "./README.md"],
140 jsdoc : "./node_modules/jsdoc/jsdoc.js",
141 options : {
142 destination : './testdocs',
143 rescurse : true,
144 "private" : true,
145 "template" : "./template",
146 "configure" : "./template/jsdoc.conf.json"
147 }
148 }
149 },
150 /**
151 * TASK: The less task creates the themed css file from main.less. The file is written to the template styles
152 * directory as site.[name of theme].css. Later the .conf file will look for the theme to apply based
153 * on this naming convention.
154 * @name less
155 * @memberOf module:Gruntfile
156 */
157 less : {
158 dev : {
159 files : {
160 "template/static/styles/site.<%= jsdocConf.templates.theme %>.css" : "styles/main.less"
161 }
162 }
163 },
164 copy : {
165 docs : {
166 files : [
167 {expand : true, cwd : "dox/", src : ['**'], dest : '../docstrap-dox/'},
168 {expand : true, cwd : "themes/", src : ['**'], dest : '../docstrap-dox/themes'}
169 ]
170 }
171 },
172 uglify : {
173 template : {
174 files : {
175 'template/static/scripts/docstrap.lib.js' : [
176 'bower_components/jquery/dist/jquery.min.js',
177 'bower_components/sunlight/src/sunlight.js',
178 'bower_components/sunlight/src/lang/sunlight.xml.js',
179 'bower_components/sunlight/src/**/*.js',
180
181 'bower_components/jquery.scrollTo/jquery.scrollTo.min.js',
182 'bower_components/jquery.localScroll/jquery.localScroll.min.js',
183 'bower_components/bootstrap/dist/js/bootstrap.min.js'
184 ]
185 }
186 }
187 }
188};
189
190module.exports = function ( grunt ) {
191 tasks.jsdocConf = grunt.file.readJSON( 'template/jsdoc.conf.json' );
192
193 grunt.initConfig( tasks );
194
195 grunt.loadNpmTasks( 'grunt-contrib-less' );
196 grunt.loadNpmTasks( 'grunt-shell' );
197 grunt.loadNpmTasks( 'grunt-contrib-copy' );
198 grunt.loadNpmTasks( 'grunt-contrib-uglify' );
199
200 grunt.registerTask( "default", ["docs"] );
201
202 /**
203 * Builds the project's documentation
204 * @name docs
205 * @memberof module:Gruntfile
206 */
207 grunt.registerTask( "docs", "Create the project documentation", ["shell:dox"] );
208 /**
209 * Compile the CSS and create the project documentation
210 * @name dev
211 * @memberof module:Gruntfile
212 */
213 grunt.registerTask( "dev", "Compile the CSS and create the project documentation", ["less", "shell:dox"] );
214 /**
215 * TASK: Builds the main less file and then generates the test documents
216 * @name testdocs
217 * @memberof module:Gruntfile
218 */
219 grunt.registerTask( "testdocs", "Builds the main less file and then generates the test documents", ["less:dev", "shell:testdocs"] );
220 /**
221 * TASK: Builds the whole shebang. Which means creating testdocs, the bootswatch fixtures and then resetting the
222 * styles directory.
223 * @name build
224 * @memberof module:Gruntfile
225 */
226 grunt.registerTask( "build", "Builds the whole shebang. Which means creating testdocs, the bootswatch samples and then resetting the styles directory", ["uglify:template", "testdocs", "shell:dox", "bootswatch", "examples", "apply", "copy"] );
227 /**
228 * TASK: Applies the theme in the conf file and applies it to the styles directory.
229 * @name apply
230 * @memberof module:Gruntfile
231 */
232 grunt.registerTask( "apply", "Applies the theme in the conf file and applies it to the styles directory", function () {
233 var def = {
234 less : "http://bootswatch.com/" + tasks.jsdocConf.templates.theme + "/bootswatch.less",
235 lessVariables : "http://bootswatch.com/" + tasks.jsdocConf.templates.theme + "/variables.less"
236 };
237 grunt.registerTask( "swatch-apply", _.partial( applyTheme, grunt, def ) );
238 grunt.task.run( ["swatch-apply"] );
239 } );
240 /**
241 * TASK: Grab all Bootswatch themes and create css from each one based on the main.less in the styles directory. NOTE that this will
242 * leave the last swatch downloaded in the styles directory, you will want to call "apply" afterwards
243 * @name bootswatch
244 * @memberof module:Gruntfile
245 */
246 grunt.registerTask( "bootswatch", "Grab all Bootswatch themes and create css from each one based on the main.less in the styles directory", function () {
247 var toRun = [];
248
249 var done = this.async();
250 getBootSwatchList( function ( err, list ) {
251 if ( err ) {return done( err );}
252
253 _.each( list.themes, function ( entry ) {
254
255 toRun.push( "swatch" + entry.name );
256 grunt.registerTask( "swatch" + entry.name, _.partial( applyTheme, grunt, entry ) );
257
258 var key = "template/static/styles/site." + entry.name.toLowerCase() + ".css";
259 var def = {};
260 def[key] = "styles/main.less";
261 tasks.less["swatch" + entry.name] = {
262 files : def
263 };
264 toRun.push( "less:swatch" + entry.name );
265 } );
266 grunt.task.run( toRun );
267 done();
268 } );
269
270 } );
271 /**
272 * TASK:Create fixtures from the themes. The files must have been built first from the bootswatch task.
273 * @name examples
274 * @memberof module:Gruntfile
275 */
276 grunt.registerTask( "examples", "Create samples from the themes", function () {
277 var toRun = [];
278 var done = this.async();
279 getBootSwatchList( function ( err, list ) {
280 if ( err ) {return done( err );}
281
282 _.each( list.themes, function ( entry ) {
283 var conf = grunt.file.readJSON( './fixtures/example.conf.json' );
284 conf.templates.theme = entry.name.toLowerCase();
285 grunt.file.write( "tmp/example.conf." + conf.templates.theme + ".json", JSON.stringify( conf, null, 4 ) );
286
287 var jsdenv = _.cloneDeep( jsdocExamplePages );
288 jsdenv.config = "./tmp/example.conf." + conf.templates.theme + ".json";
289 jsdenv.dest = "./themes/" + conf.templates.theme;
290 tasks.shell["example" + conf.templates.theme] = {
291 command : jsdocCommand( jsdenv )
292 };
293 toRun.push( "shell:example" + conf.templates.theme );
294 } );
295
296 grunt.registerTask( "cleanup", "", function () {
297 grunt.file["delete"]( "tmp/" );
298 } );
299 toRun.push( "cleanup" );
300 grunt.task.run( toRun );
301 done();
302 } );
303
304 } );
305
306 grunt.registerTask( "release", "Create the project documentation", ["shell:release1", "shell:release2"] );
307};
308
309/**
310 * Applies one of the Bootswatch themes to the working `styles` directory. When you want to modify a particular theme, this where you
311 * get the basis for it. The files are written to `./styles/variables.less` and `./styles/bootswatch.less`. The `./styles/main.less`
312 * file includes them directly, so after you apply the theme, modify `main.less` to your heart's content and then run the `less` task
313 * as in
314 *
315 * grunt less
316 *
317 * @param {object} grunt The grunt object reference
318 * @param {object} definition The swatch definition files
319 * @param {string} definition.less The url to the `bootswatch.less` file
320 * @param {string} definition.lessVariables The url to the `variables.less` file
321 * @private
322 */
323function applyTheme( grunt, definition ) {
324 //noinspection JSHint
325
326 var webProtocol = tasks.jsdocConf.templates.protocol || "//";
327 var done = this.async();
328 async.waterfall( [
329 function ( cb ) {
330 getBootSwatchComponent( definition.less, function ( err, swatch ) {
331 if ( err ) {return cb( err );}
332 var fullPath = path.join( __dirname, "styles/bootswatch.less" );
333 fs.writeFile( fullPath, swatch.replace( "http://", webProtocol ), cb );
334 } );
335 },
336 function ( cb ) {
337 getBootSwatchComponent( definition.lessVariables, function ( err, swatch ) {
338 if ( err ) {return cb( err );}
339 var fullPath = path.join( __dirname, "styles/variables.less" );
340 fs.writeFile( fullPath, swatch.replace( "http://", webProtocol ), cb );
341 } );
342 }
343 ], done );
344}
345
346/**
347 * Gets the list of available Bootswatches from, well, Bootswatch.
348 *
349 * @see http://news.bootswatch.com/post/22193315172/bootswatch-api
350 * @param {function(err, responseBody)} done The callback when complete
351 * @param {?object} done.err If an error occurred, you will find it here.
352 * @param {object} done.responseBody This is a parsed edition of the bootswatch server's response. It's format it defined
353 * by the return message from [here](http://api.bootswatch.com/)
354 * @private
355 */
356function getBootSwatchList( done ) {
357 request('http://api.bootswatch.com/3/', function(error, response, body) {
358 if (error) {
359 return done(error);
360 }
361 done(null, JSON.parse(body));
362 });
363}
364
365/**
366 * This method will get one of the components from Bootswatch, which is generally a `less` file or a `lessVariables` file.
367 *
368 * @see http://news.bootswatch.com/post/22193315172/bootswatch-api
369 * @param {string} url The url to retreive from
370 * @param {function(err, responseText)} done The callback when complete
371 * @param {?object} done.err If an error occurred, you will find it here.
372 * @param {string} done.responseText The body of whatever was returned
373 * @private
374 */
375function getBootSwatchComponent( url, done ) {
376 var body = "";
377 var req = request(url, function ( error, response, body ) {
378 if (error) {
379 return done(error);
380 }
381 done(null, body);
382 });
383}