diff options
-rw-r--r-- | lib/index.js | 120 | ||||
-rw-r--r-- | lib/utils/server.js | 96 | ||||
-rw-r--r-- | lib/utils/string.js | 3 | ||||
-rw-r--r-- | lib/utils/watch.js | 27 | ||||
-rw-r--r-- | package.json | 5 |
5 files changed, 243 insertions, 8 deletions
diff --git a/lib/index.js b/lib/index.js index a2c8edf..a03f39d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,9 +1,27 @@ var Q = require("q"); var _ = require("lodash"); var path = require("path"); +var tinylr = require('tiny-lr-fork'); var Book = require("./book"); var Plugin = require("./plugin"); +var Server = require("./utils/server"); +var stringUtils = require("./utils/string"); +var watch = require("./utils/watch"); + +var LOG_OPTION = { + name: "log", + description: "Minimum log level to display", + values: _.chain(Book.LOG_LEVELS).keys().map(stringUtils.toLowerCase).value(), + defaults: "info" +}; + +var FORMAT_OPTION = { + name: "format", + description: "Format to build to", + values: ["website", "json"], + defaults: "website" +}; module.exports = { Book: Book, @@ -11,20 +29,21 @@ module.exports = { commands: [ // Build command that simply build a book into an output folder { - name: "build", + name: "build [book] [output]", description: "build a book", + options: [ + FORMAT_OPTION, + LOG_OPTION + ], exec: function(args, kwargs) { var input = args[0] || process.cwd(); var output = args[1] || path.join(input, "_book"); - kwargs = _.defaults(kwargs || {}, { - format: "website" - }); var book = new Book(input, _.extend({}, { 'config': { 'output': output }, - 'logLevel': Book.LOG_LEVELS[(kwargs.log || "info").toUpperCase()] + 'logLevel': Book.LOG_LEVELS[(kwargs.log).toUpperCase()] })); return book.parse() @@ -34,9 +53,98 @@ module.exports = { } }, + // Build and serve a book + { + name: "serve [book]", + description: "Build then serve a gitbook from a directory", + options: [ + { + name: "port", + description: "Port for server to listen on", + defaults: 4000 + }, + { + name: "lrport", + description: "Port for livereload server to listen on", + defaults: 35729 + }, + { + name: "watch", + description: "Enable/disable file watcher", + defaults: true + }, + FORMAT_OPTION, + LOG_OPTION + ], + exec: function(args, kwargs) { + var input = args[0] || process.cwd(); + var server = new Server(); + + // Init livereload server + var lrServer = tinylr({}); + var lrPath = undefined; + + var generate = function() { + if (server.isRunning()) console.log("Stopping server"); + + return server.stop() + .then(function() { + var book = new Book(input, _.extend({}, { + 'config': { + 'defaultsPlugins': ["livereload"] + }, + 'logLevel': Book.LOG_LEVELS[(kwargs.log).toUpperCase()] + })); + + return book.parse() + .then(function() { + return book.generate(kwargs.format); + }) + .thenResolve(book); + }) + .then(function(book) { + console.log(); + console.log('Starting server ...'); + return server.start(book.options.output, kwargs.port) + .then(function() { + console.log('Serving book on http://localhost:'+kwargs.port); + + if (lrPath) { + // trigger livereload + lrServer.changed({ + body: { + files: [lrPath] + } + }); + } + + if (!kwargs.watch) return; + + return watch(book.root) + .then(function(filepath) { + // set livereload path + lrPath = filepath; + console.log("Restart after change in files"); + console.log(''); + return generate(); + }) + }) + }); + }; + + return Q.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport) + .then(function() { + console.log('Live reload server started on port:', kwargs.lrport); + console.log('Press CTRL+C to quit ...'); + console.log('') + return generate(); + }); + } + }, + // Install command that install plugins needed by a book { - name: "install", + name: "install [book]", description: "install plugins dependencies", exec: function(args, kwargs) { var input = args[0] || process.cwd(); diff --git a/lib/utils/server.js b/lib/utils/server.js new file mode 100644 index 0000000..2b97fe8 --- /dev/null +++ b/lib/utils/server.js @@ -0,0 +1,96 @@ +var Q = require('q'); +var _ = require('lodash'); + +var events = require('events'); +var http = require('http'); +var send = require('send'); +var util = require('util'); +var url = require('url'); + +var Server = function() { + this.running = null; + this.dir = null; + this.port = 0; + this.sockets = []; +}; +util.inherits(Server, events.EventEmitter); + +// Return true if the server is running +Server.prototype.isRunning = function() { + return this.running != null; +}; + +// Stop the server +Server.prototype.stop = function() { + var that = this; + if (!this.isRunning()) return Q(); + + var d = Q.defer(); + this.running.close(function(err) { + that.running = null; + that.emit("state", false); + + if (err) d.reject(err); + else d.resolve(); + }); + + for (var i = 0; i < this.sockets.length; i++) { + this.sockets[i].destroy(); + } + + return d.promise; +}; + +Server.prototype.start = function(dir, port) { + var that = this, pre = Q(); + port = port || 8004; + + if (that.isRunning()) pre = this.stop(); + return pre + .then(function() { + var d = Q.defer(); + + that.running = http.createServer(function(req, res){ + // Render error + function error(err) { + res.statusCode = err.status || 500; + res.end(err.message); + } + + // Redirect to directory's index.html + function redirect() { + res.statusCode = 301; + res.setHeader('Location', req.url + '/'); + res.end('Redirecting to ' + req.url + '/'); + } + + // Send file + send(req, url.parse(req.url).pathname) + .root(dir) + .on('error', error) + .on('directory', redirect) + .pipe(res); + }); + + that.running.on('connection', function (socket) { + that.sockets.push(socket); + socket.setTimeout(4000); + socket.on('close', function () { + that.sockets.splice(that.sockets.indexOf(socket), 1); + }); + }); + + that.running.listen(port, function(err) { + if (err) return d.reject(err); + + that.port = port; + that.dir = dir; + that.emit("state", true); + d.resolve(); + }); + + return d.promise; + }); +} + +module.exports = Server; diff --git a/lib/utils/string.js b/lib/utils/string.js index 54c4c66..72a9ca0 100644 --- a/lib/utils/string.js +++ b/lib/utils/string.js @@ -22,5 +22,6 @@ function optionsToShellArgs(options) { module.exports = { escapeShellArg: escapeShellArg, - optionsToShellArgs: optionsToShellArgs + optionsToShellArgs: optionsToShellArgs, + toLowerCase: String.prototype.toLowerCase.call.bind(String.prototype.toLowerCase) }; diff --git a/lib/utils/watch.js b/lib/utils/watch.js new file mode 100644 index 0000000..715179d --- /dev/null +++ b/lib/utils/watch.js @@ -0,0 +1,27 @@ +var Q = require('q'); +var path = require('path'); +var Gaze = require('gaze').Gaze; + +function watch(dir) { + var d = Q.defer(); + dir = path.resolve(dir); + + var gaze = new Gaze("**/*.md", { + cwd: dir + }); + + gaze.once("all", function(e, filepath) { + gaze.close(); + + d.resolve(filepath); + }); + gaze.once("error", function(err) { + gaze.close(); + + d.reject(err); + }); + + return d.promise; +} + +module.exports = watch; diff --git a/package.json b/package.json index 191c731..27f69ff 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,10 @@ "npmi": "0.1.1", "cheerio": "0.18.0", "gitbook-plugin-mathjax": "0.0.6", - "gitbook-plugin-livereload": "0.0.1" + "gitbook-plugin-livereload": "0.0.1", + "gaze": "~0.5.1", + "send": "0.2.0", + "tiny-lr-fork": "0.0.5" }, "devDependencies": { "mocha": "1.18.2", |