summaryrefslogtreecommitdiffstats
path: root/packages/gitbook/src/cli
diff options
context:
space:
mode:
Diffstat (limited to 'packages/gitbook/src/cli')
-rw-r--r--packages/gitbook/src/cli/build.js34
-rw-r--r--packages/gitbook/src/cli/buildEbook.js78
-rw-r--r--packages/gitbook/src/cli/getBook.js23
-rw-r--r--packages/gitbook/src/cli/getOutputFolder.js17
-rw-r--r--packages/gitbook/src/cli/index.js12
-rw-r--r--packages/gitbook/src/cli/init.js17
-rw-r--r--packages/gitbook/src/cli/install.js21
-rw-r--r--packages/gitbook/src/cli/options.js31
-rw-r--r--packages/gitbook/src/cli/parse.js79
-rw-r--r--packages/gitbook/src/cli/serve.js159
-rw-r--r--packages/gitbook/src/cli/server.js127
-rw-r--r--packages/gitbook/src/cli/watch.js46
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;