diff --git a/package.json b/package.json index ff89ab49da..f1889acfa4 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ "devDependencies": { "clean-css": "2.2.16", "coveralls": "2.11.2", + "debounce": "^1.0.0", "expect.js": "0.3.1", + "gaze": "^0.5.1", "istanbul": "0.3.13", "jquery": "2.1.1", "jshint": "2.5.6", @@ -54,9 +56,9 @@ "mocha-phantomjs": "3.5.1", "phantomjs": "1.9.10", "proj4": "2.3.3", + "resemblejs": "1.2.0", "sinon": "1.10.3", - "slimerjs-edge": "0.10.0-pre-2", - "resemblejs": "1.2.0" + "slimerjs-edge": "0.10.0-pre-2" }, "ext": [ "rbush" diff --git a/tasks/install.js b/tasks/install.js index 52cce80f8f..d3850dc968 100644 --- a/tasks/install.js +++ b/tasks/install.js @@ -1,17 +1,12 @@ var async = require('async'); var buildExt = require('./build-ext'); -var parseExamples = require('./parse-examples'); -/** - * Parse examples and build external modules. - */ -async.waterfall([ - parseExamples, - buildExt -], function(err) { +buildExt(function(err) { if (err) { process.stderr.write(err + '\n'); process.exit(1); + } else { + process.exit(0); } }); diff --git a/tasks/parse-examples.js b/tasks/parse-examples.js deleted file mode 100644 index a99eea4d83..0000000000 --- a/tasks/parse-examples.js +++ /dev/null @@ -1,163 +0,0 @@ -var fs = require('fs'); -var path = require('path'); - -var async = require('async'); -var Parser = require('htmlparser2').Parser; - -var buildExamples = require('./build-examples'); - -var exampleDir = path.join(__dirname, '..', 'examples'); - - -/** - * List all .html files in the example directory (excluding index.html). - * @param {function(Error, Array.)} callback Called with any error or - * the list of paths to examples. - */ -function listExamples(callback) { - fs.readdir(exampleDir, function(err, items) { - if (err) { - return callback(err); - } - var examplePaths = items.filter(function(item) { - return /\.html$/i.test(item) && item !== 'index.html'; - }).map(function(item) { - return path.join(exampleDir, item); - }); - callback(null, examplePaths); - }); -} - - -/** - * Parse info from examples. - * @param {Array.} examplePaths Paths to examples. - * @param {function(Error, Array.)} callback Called with any error or - * the list of example info objects. - */ -function parseExamples(examplePaths, callback) { - async.map(examplePaths, function(examplePath, next) { - fs.readFile(examplePath, function(err, data) { - if (err) { - return next(err); - } - var name = path.basename(examplePath); - var info = { - link: name, - example: name, - title: '', - shortdesc: '', - tags: '' - }; - var key; - var openTag; - var parser = new Parser({ - onopentag: function(tag, attrs) { - if (attrs.id in info) { - key = attrs.id; - openTag = tag; - } - }, - ontext: function(text) { - if (key) { - info[key] += text.replace(/\n/g, '').trim() + ' '; - } - }, - onclosetag: function(tag) { - if (tag === openTag) { - info[key] = info[key].trim(); - key = undefined; - openTag = undefined; - } - }, - onerror: function(err2) { - var message = 'Trouble parsing ' + examplePath + '\n' + err2.message; - next(new Error(message)); - } - }); - parser.write(data.toString('utf8')); - parser.end(); - next(null, info); - }); - }, callback); -} - - -/** - * Create an inverted index of keywords from examples. Property names are - * lowercased words. Property values are objects mapping example index to word - * count. - * @param {Array.} exampleInfos Array of example info objects. - * @return {Object} Word index. - */ -function createWordIndex(exampleInfos) { - var index = {}; - var keys = ['shortdesc', 'title', 'tags']; - exampleInfos.forEach(function(info, i) { - keys.forEach(function(key) { - var text = info[key]; - var words = text ? text.split(/\W+/) : []; - words.forEach(function(word) { - if (word) { - word = word.toLowerCase(); - var counts = index[word]; - if (counts) { - if (index in counts) { - counts[i] += 1; - } else { - counts[i] = 1; - } - } else { - counts = {}; - counts[i] = 1; - index[word] = counts; - } - } - }); - }); - }); - return index; -} - - -/** - * Write the example-list.js file with example info and word index. - * @param {Array.} exampleInfos Array of example info objects. - * @param {function(Error)} callback Called with any error. - */ -function writeExampleList(exampleInfos, callback) { - var info = { - examples: exampleInfos, - index: createWordIndex(exampleInfos) - }; - var indexPath = path.join(exampleDir, 'example-list.js'); - var str = 'var info = ' + JSON.stringify(info); - fs.writeFile(indexPath, str, callback); -} - - -/** - * List examples, parse them, and write example list. - * @param {function(Error)} callback Called with any error. - */ -function main(callback) { - async.waterfall([ - buildExamples, - listExamples, - parseExamples, - writeExampleList - ], callback); -} - -if (require.main === module) { - main(function(err) { - if (err) { - process.stderr.write(err.message + '\n'); - process.exit(1); - } else { - process.exit(0); - } - }); -} - -module.exports = main; diff --git a/tasks/readme.md b/tasks/readme.md index 6bfc5507d8..aefbaedd63 100644 --- a/tasks/readme.md +++ b/tasks/readme.md @@ -107,13 +107,10 @@ This is useful for projects that use the Closure Compiler to build, but want to Called internally to parse the library for annotations and write out a `build/info.json` file. -## `parse-examples.js` - -Called after install to generate an example index. After new examples are added, run `node tasks/parse-examples.js` to regenerate the example index. - ## `build-examples.js` -Called internally by `parse-examples.js` to build the examples from templates. +Builds examples and the example index. + ## `serve.js` diff --git a/tasks/serve.js b/tasks/serve.js index dfed1e5e8d..c301c9a1b3 100644 --- a/tasks/serve.js +++ b/tasks/serve.js @@ -7,9 +7,12 @@ var path = require('path'); var url = require('url'); +var Gaze = require('gaze').Gaze; var closure = require('closure-util'); +var debounce = require('debounce'); var nomnom = require('nomnom'); +var buildExamples = require('./build-examples'); var log = closure.log; @@ -68,6 +71,26 @@ var createServer = exports.createServer = function(callback) { }); }; +/** + * Build the examples and exit on any error. + * @param {Function=} opt_callback Called when done building examples. + */ +function buildExamplesOrFatal(opt_callback) { + log.info('serve', 'Building examples.'); + buildExamples(function(err) { + if (err) { + log.error('serve', 'Building examples failed.'); + log.error('serve', err.message); + log.error('serve', 'Use "verbose" logging to see the full stack trace.'); + log.verbose('serve', err.stack); + process.exit(1); + } + log.verbose('serve', 'Done building examples.'); + if (opt_callback) { + opt_callback(); + } + }); +} /** * If running this module directly start the server. @@ -92,21 +115,30 @@ if (require.main === module) { /** @type {string} */ log.level = options.loglevel; - log.info('serve', 'Parsing dependencies ...'); - createServer(function(err, server) { - if (err) { - log.error('serve', 'Parsing failed'); - log.error('serve', err.message); - process.exit(1); - } - server.listen(options.port, function() { - log.info('serve', 'Listening on http://localhost:' + - options.port + '/ (Ctrl+C to stop)'); - }); - server.on('error', function(err) { - log.error('serve', 'Server failed to start: ' + err.message); - process.exit(1); + buildExamplesOrFatal(function() { + log.info('serve', 'Parsing dependencies.'); + createServer(function(err, server) { + if (err) { + log.error('serve', 'Parsing failed'); + log.error('serve', err.message); + process.exit(1); + } + server.listen(options.port, function() { + log.info('serve', 'Listening on http://localhost:' + + options.port + '/ (Ctrl+C to stop)'); + }); + server.on('error', function(err) { + log.error('serve', 'Server failed to start: ' + err.message); + process.exit(1); + }); }); + var gaze = new Gaze('examples_src/**/*'); + var debouncedBuild = debounce(buildExamplesOrFatal, 250); + gaze.on('all', function(event, filepath) { + log.verbose('serve', 'Watch event: ' + event + ' ' + filepath); + debouncedBuild(); + }); }); + }