diff options
Diffstat (limited to 'packages/gitbook/src/cli')
-rw-r--r-- | packages/gitbook/src/cli/build.js | 34 | ||||
-rw-r--r-- | packages/gitbook/src/cli/buildEbook.js | 78 | ||||
-rw-r--r-- | packages/gitbook/src/cli/getBook.js | 23 | ||||
-rw-r--r-- | packages/gitbook/src/cli/getOutputFolder.js | 17 | ||||
-rw-r--r-- | packages/gitbook/src/cli/index.js | 12 | ||||
-rw-r--r-- | packages/gitbook/src/cli/init.js | 17 | ||||
-rw-r--r-- | packages/gitbook/src/cli/install.js | 21 | ||||
-rw-r--r-- | packages/gitbook/src/cli/options.js | 31 | ||||
-rw-r--r-- | packages/gitbook/src/cli/parse.js | 79 | ||||
-rw-r--r-- | packages/gitbook/src/cli/serve.js | 159 | ||||
-rw-r--r-- | packages/gitbook/src/cli/server.js | 127 | ||||
-rw-r--r-- | packages/gitbook/src/cli/watch.js | 46 |
12 files changed, 644 insertions, 0 deletions
diff --git a/packages/gitbook/src/cli/build.js b/packages/gitbook/src/cli/build.js new file mode 100644 index 0000000..3f5c937 --- /dev/null +++ b/packages/gitbook/src/cli/build.js @@ -0,0 +1,34 @@ +const Parse = require('../parse'); +const Output = require('../output'); +const timing = require('../utils/timing'); + +const options = require('./options'); +const getBook = require('./getBook'); +const getOutputFolder = require('./getOutputFolder'); + + +module.exports = { + name: 'build [book] [output]', + description: 'build a book', + options: [ + options.log, + options.format, + options.timing + ], + exec(args, kwargs) { + const book = getBook(args, kwargs); + const outputFolder = getOutputFolder(args); + + const Generator = Output.getGenerator(kwargs.format); + + return Parse.parseBook(book) + .then(function(resultBook) { + return Output.generate(Generator, resultBook, { + root: outputFolder + }); + }) + .fin(function() { + if (kwargs.timing) timing.dump(book.getLogger()); + }); + } +}; diff --git a/packages/gitbook/src/cli/buildEbook.js b/packages/gitbook/src/cli/buildEbook.js new file mode 100644 index 0000000..56e63f8 --- /dev/null +++ b/packages/gitbook/src/cli/buildEbook.js @@ -0,0 +1,78 @@ +const path = require('path'); +const tmp = require('tmp'); + +const Promise = require('../utils/promise'); +const fs = require('../utils/fs'); +const Parse = require('../parse'); +const Output = require('../output'); + +const options = require('./options'); +const getBook = require('./getBook'); + + +module.exports = function(format) { + return { + name: (format + ' [book] [output]'), + description: 'build a book into an ebook file', + options: [ + options.log + ], + exec(args, kwargs) { + const extension = '.' + format; + + // Output file will be stored in + const outputFile = args[1] || ('book' + extension); + + // Create temporary directory + const outputFolder = tmp.dirSync().name; + + const book = getBook(args, kwargs); + const logger = book.getLogger(); + const Generator = Output.getGenerator('ebook'); + + return Parse.parseBook(book) + .then(function(resultBook) { + return Output.generate(Generator, resultBook, { + root: outputFolder, + format + }); + }) + + // Extract ebook file + .then(function(output) { + const book = output.getBook(); + const languages = book.getLanguages(); + + if (book.isMultilingual()) { + return Promise.forEach(languages.getList(), function(lang) { + const langID = lang.getID(); + + const langOutputFile = path.join( + path.dirname(outputFile), + path.basename(outputFile, extension) + '_' + langID + extension + ); + + return fs.copy( + path.resolve(outputFolder, langID, 'index' + extension), + langOutputFile + ); + }) + .thenResolve(languages.getCount()); + } else { + return fs.copy( + path.resolve(outputFolder, 'index' + extension), + outputFile + ).thenResolve(1); + } + }) + + // Log end + .then(function(count) { + logger.info.ok(count + ' file(s) generated'); + + logger.debug('cleaning up... '); + return logger.debug.promise(fs.rmDir(outputFolder)); + }); + } + }; +}; diff --git a/packages/gitbook/src/cli/getBook.js b/packages/gitbook/src/cli/getBook.js new file mode 100644 index 0000000..b37e49c --- /dev/null +++ b/packages/gitbook/src/cli/getBook.js @@ -0,0 +1,23 @@ +const path = require('path'); +const Book = require('../models/book'); +const createNodeFS = require('../fs/node'); + +/** + Return a book instance to work on from + command line args/kwargs + + @param {Array} args + @param {Object} kwargs + @return {Book} +*/ +function getBook(args, kwargs) { + const input = path.resolve(args[0] || process.cwd()); + const logLevel = kwargs.log; + + const fs = createNodeFS(input); + const book = Book.createForFS(fs); + + return book.setLogLevel(logLevel); +} + +module.exports = getBook; diff --git a/packages/gitbook/src/cli/getOutputFolder.js b/packages/gitbook/src/cli/getOutputFolder.js new file mode 100644 index 0000000..94f22da --- /dev/null +++ b/packages/gitbook/src/cli/getOutputFolder.js @@ -0,0 +1,17 @@ +const path = require('path'); + +/** + Return path to output folder + + @param {Array} args + @return {String} +*/ +function getOutputFolder(args) { + const bookRoot = path.resolve(args[0] || process.cwd()); + const defaultOutputRoot = path.join(bookRoot, '_book'); + const outputFolder = args[1] ? path.resolve(process.cwd(), args[1]) : defaultOutputRoot; + + return outputFolder; +} + +module.exports = getOutputFolder; diff --git a/packages/gitbook/src/cli/index.js b/packages/gitbook/src/cli/index.js new file mode 100644 index 0000000..48ad117 --- /dev/null +++ b/packages/gitbook/src/cli/index.js @@ -0,0 +1,12 @@ +const buildEbook = require('./buildEbook'); + +module.exports = [ + require('./build'), + require('./serve'), + require('./install'), + require('./parse'), + require('./init'), + buildEbook('pdf'), + buildEbook('epub'), + buildEbook('mobi') +]; diff --git a/packages/gitbook/src/cli/init.js b/packages/gitbook/src/cli/init.js new file mode 100644 index 0000000..51d6869 --- /dev/null +++ b/packages/gitbook/src/cli/init.js @@ -0,0 +1,17 @@ +const path = require('path'); + +const options = require('./options'); +const initBook = require('../init'); + +module.exports = { + name: 'init [book]', + description: 'setup and create files for chapters', + options: [ + options.log + ], + exec(args, kwargs) { + const bookRoot = path.resolve(process.cwd(), args[0] || './'); + + return initBook(bookRoot); + } +}; diff --git a/packages/gitbook/src/cli/install.js b/packages/gitbook/src/cli/install.js new file mode 100644 index 0000000..6af4013 --- /dev/null +++ b/packages/gitbook/src/cli/install.js @@ -0,0 +1,21 @@ +const options = require('./options'); +const getBook = require('./getBook'); + +const Parse = require('../parse'); +const Plugins = require('../plugins'); + +module.exports = { + name: 'install [book]', + description: 'install all plugins dependencies', + options: [ + options.log + ], + exec(args, kwargs) { + const book = getBook(args, kwargs); + + return Parse.parseConfig(book) + .then(function(resultBook) { + return Plugins.installPlugins(resultBook); + }); + } +}; diff --git a/packages/gitbook/src/cli/options.js b/packages/gitbook/src/cli/options.js new file mode 100644 index 0000000..d643f91 --- /dev/null +++ b/packages/gitbook/src/cli/options.js @@ -0,0 +1,31 @@ +const Logger = require('../utils/logger'); + +const logOptions = { + name: 'log', + description: 'Minimum log level to display', + values: Logger.LEVELS + .keySeq() + .map(function(s) { + return s.toLowerCase(); + }).toJS(), + defaults: 'info' +}; + +const formatOption = { + name: 'format', + description: 'Format to build to', + values: ['website', 'json', 'ebook'], + defaults: 'website' +}; + +const timingOption = { + name: 'timing', + description: 'Print timing debug information', + defaults: false +}; + +module.exports = { + log: logOptions, + format: formatOption, + timing: timingOption +}; diff --git a/packages/gitbook/src/cli/parse.js b/packages/gitbook/src/cli/parse.js new file mode 100644 index 0000000..3d38fe7 --- /dev/null +++ b/packages/gitbook/src/cli/parse.js @@ -0,0 +1,79 @@ +const options = require('./options'); +const getBook = require('./getBook'); + +const Parse = require('../parse'); + +function printBook(book) { + const logger = book.getLogger(); + + const config = book.getConfig(); + const configFile = config.getFile(); + + const summary = book.getSummary(); + const summaryFile = summary.getFile(); + + const readme = book.getReadme(); + const readmeFile = readme.getFile(); + + const glossary = book.getGlossary(); + const glossaryFile = glossary.getFile(); + + if (configFile.exists()) { + logger.info.ln('Configuration file is', configFile.getPath()); + } + + if (readmeFile.exists()) { + logger.info.ln('Introduction file is', readmeFile.getPath()); + } + + if (glossaryFile.exists()) { + logger.info.ln('Glossary file is', glossaryFile.getPath()); + } + + if (summaryFile.exists()) { + logger.info.ln('Table of Contents file is', summaryFile.getPath()); + } +} + +function printMultingualBook(book) { + const logger = book.getLogger(); + const languages = book.getLanguages(); + const books = book.getBooks(); + + logger.info.ln(languages.size + ' languages'); + + languages.forEach(function(lang) { + logger.info.ln('Language:', lang.getTitle()); + printBook(books.get(lang.getID())); + logger.info.ln(''); + }); +} + +module.exports = { + name: 'parse [book]', + description: 'parse and print debug information about a book', + options: [ + options.log + ], + exec(args, kwargs) { + const book = getBook(args, kwargs); + const logger = book.getLogger(); + + return Parse.parseBook(book) + .then(function(resultBook) { + const rootFolder = book.getRoot(); + const contentFolder = book.getContentRoot(); + + logger.info.ln('Book located in:', rootFolder); + if (contentFolder != rootFolder) { + logger.info.ln('Content located in:', contentFolder); + } + + if (resultBook.isMultilingual()) { + printMultingualBook(resultBook); + } else { + printBook(resultBook); + } + }); + } +}; diff --git a/packages/gitbook/src/cli/serve.js b/packages/gitbook/src/cli/serve.js new file mode 100644 index 0000000..6397c2e --- /dev/null +++ b/packages/gitbook/src/cli/serve.js @@ -0,0 +1,159 @@ +/* eslint-disable no-console */ + +const tinylr = require('tiny-lr'); +const open = require('open'); + +const Parse = require('../parse'); +const Output = require('../output'); +const ConfigModifier = require('../modifiers').Config; + +const Promise = require('../utils/promise'); + +const options = require('./options'); +const getBook = require('./getBook'); +const getOutputFolder = require('./getOutputFolder'); +const Server = require('./server'); +const watch = require('./watch'); + +let server, lrServer, lrPath; + +function waitForCtrlC() { + const d = Promise.defer(); + + process.on('SIGINT', function() { + d.resolve(); + }); + + return d.promise; +} + + +function generateBook(args, kwargs) { + const port = kwargs.port; + const outputFolder = getOutputFolder(args); + const book = getBook(args, kwargs); + const Generator = Output.getGenerator(kwargs.format); + const browser = kwargs['browser']; + + const hasWatch = kwargs['watch']; + const hasLiveReloading = kwargs['live']; + const hasOpen = kwargs['open']; + + // Stop server if running + if (server.isRunning()) console.log('Stopping server'); + + return server.stop() + .then(function() { + return Parse.parseBook(book) + .then(function(resultBook) { + if (hasLiveReloading) { + // Enable livereload plugin + let config = resultBook.getConfig(); + config = ConfigModifier.addPlugin(config, 'livereload'); + resultBook = resultBook.set('config', config); + } + + return Output.generate(Generator, resultBook, { + root: outputFolder + }); + }); + }) + .then(function() { + console.log(); + console.log('Starting server ...'); + return server.start(outputFolder, port); + }) + .then(function() { + console.log('Serving book on http://localhost:' + port); + + if (lrPath && hasLiveReloading) { + // trigger livereload + lrServer.changed({ + body: { + files: [lrPath] + } + }); + } + + if (hasOpen) { + open('http://localhost:' + port, browser); + } + }) + .then(function() { + if (!hasWatch) { + return waitForCtrlC(); + } + + return watch(book.getRoot()) + .then(function(filepath) { + // set livereload path + lrPath = filepath; + console.log('Restart after change in file', filepath); + console.log(''); + return generateBook(args, kwargs); + }); + }); +} + +module.exports = { + name: 'serve [book] [output]', + description: 'serve the book as a website for testing', + 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 file watcher and live reloading', + defaults: true + }, + { + name: 'live', + description: 'Enable live reloading', + defaults: true + }, + { + name: 'open', + description: 'Enable opening book in browser', + defaults: false + }, + { + name: 'browser', + description: 'Specify browser for opening book', + defaults: '' + }, + options.log, + options.format + ], + exec(args, kwargs) { + server = new Server(); + const hasWatch = kwargs['watch']; + const hasLiveReloading = kwargs['live']; + + return Promise() + .then(function() { + if (!hasWatch || !hasLiveReloading) { + return; + } + + lrServer = tinylr({}); + return Promise.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(''); + + }); + }) + .then(function() { + return generateBook(args, kwargs); + }); + } +}; diff --git a/packages/gitbook/src/cli/server.js b/packages/gitbook/src/cli/server.js new file mode 100644 index 0000000..c494efc --- /dev/null +++ b/packages/gitbook/src/cli/server.js @@ -0,0 +1,127 @@ +const events = require('events'); +const http = require('http'); +const send = require('send'); +const url = require('url'); + +const Promise = require('../utils/promise'); + +class Server extends events.EventEmitter { + constructor() { + super(); + this.running = null; + this.dir = null; + this.port = 0; + this.sockets = []; + } + + /** + * Return true if the server is running + * @return {Boolean} + */ + isRunning() { + return !!this.running; + } + + /** + * Stop the server + * @return {Promise} + */ + stop() { + const that = this; + if (!this.isRunning()) return Promise(); + + const d = Promise.defer(); + this.running.close(function(err) { + that.running = null; + that.emit('state', false); + + if (err) d.reject(err); + else d.resolve(); + }); + + for (let i = 0; i < this.sockets.length; i++) { + this.sockets[i].destroy(); + } + + return d.promise; + } + + /** + * Start the server + * @return {Promise} + */ + start(dir, port) { + const that = this; + let pre = Promise(); + port = port || 8004; + + if (that.isRunning()) pre = this.stop(); + return pre + .then(function() { + const d = Promise.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() { + const resultURL = urlTransform(req.url, function(parsed) { + parsed.pathname += '/'; + return parsed; + }); + + res.statusCode = 301; + res.setHeader('Location', resultURL); + res.end('Redirecting to ' + resultURL); + } + + res.setHeader('X-Current-Location', 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; + }); + } +} + +/** + * urlTransform is a helper function that allows a function to transform + * a url string in it's parsed form and returns the new url as a string + * + * @param {String} uri + * @param {Function} fn + * @return {String} + */ +function urlTransform(uri, fn) { + return url.format(fn(url.parse(uri))); +} + +module.exports = Server; diff --git a/packages/gitbook/src/cli/watch.js b/packages/gitbook/src/cli/watch.js new file mode 100644 index 0000000..e1d453c --- /dev/null +++ b/packages/gitbook/src/cli/watch.js @@ -0,0 +1,46 @@ +const path = require('path'); +const chokidar = require('chokidar'); + +const Promise = require('../utils/promise'); +const parsers = require('../parsers'); + +/** + Watch a folder and resolve promise once a file is modified + + @param {String} dir + @return {Promise} +*/ +function watch(dir) { + const d = Promise.defer(); + dir = path.resolve(dir); + + const toWatch = [ + 'book.json', 'book.js', '_layouts/**' + ]; + + // Watch all parsable files + parsers.extensions.forEach(function(ext) { + toWatch.push('**/*' + ext); + }); + + const watcher = chokidar.watch(toWatch, { + cwd: dir, + ignored: '_book/**', + ignoreInitial: true + }); + + watcher.once('all', function(e, filepath) { + watcher.close(); + + d.resolve(filepath); + }); + watcher.once('error', function(err) { + watcher.close(); + + d.reject(err); + }); + + return d.promise; +} + +module.exports = watch; |