Compare commits

...

26 Commits

Author SHA1 Message Date
Aaron O'Mullan 7fd7732345 Bump version to 1.4.2 2014-12-10 23:27:01 +01:00
Aaron O'Mullan 960ef4d945 Fix descriptor and me leak with bad write queues
Node.js for some reason queues up writes from fs.writeFile causing
memory and descriptor leaks, preventing building large books. This is
now fixed

Fixes #336, fixes #454
2014-12-10 16:32:49 +01:00
Aaron O'Mullan 92523fb4b5 Force process.exit at end of build 2014-12-09 17:31:23 +01:00
Samy Pessé 66bb46df22 Merge pull request #516 from jr0cket/patch-1
Add gitbook init command to "how to use" section
2014-12-08 00:46:04 +01:00
John Stevenson 0454710d3b Add gitbook init command to "how to use" section
Highlight that Gitbook can generate the files and directories for a book from its SUMMARY.md file.
2014-12-07 23:44:41 +00:00
Samy Pessé c9a31cc987 Add option isbn for books 2014-12-03 23:19:47 +01:00
Samy Pessé f03c316274 Bump version to 1.4.1 2014-11-30 17:01:47 +01:00
Samy Pessé 001a4f91be Fix command 'install' without arguments 2014-11-30 17:00:12 +01:00
Samy Pessé 44a3bfe296 Bump version to 1.4.0 2014-11-30 14:59:08 +01:00
Samy Pessé a9ce6f808d Add doc about gitbook install 2014-11-30 14:54:34 +01:00
Samy Pessé e5c667e0fa Add test for parsing of plugins list with version 2014-11-30 14:50:47 +01:00
Samy Pessé e36f274dbe Handle specific version for plugins 2014-11-30 14:46:21 +01:00
Samy Pessé 2a88427e74 Improve error message when plugins not found 2014-11-30 13:13:00 +01:00
Samy Pessé ed48f5f3ee Add command "install" to install plugins from book.json 2014-11-30 13:04:38 +01:00
Samy Pessé 7b4accb117 Bump version to 1.3.4 2014-11-25 18:55:33 +01:00
Samy Pessé d6437d31d4 Omit "generator" when building file with config file 2014-11-25 18:53:02 +01:00
Samy Pessé 3dd33f1f3c Merge pull request #502 from tonka3000/X-UA-Compatible-tag
add X-UA-Compatible meta tag for IE
2014-11-25 15:20:14 +01:00
Samy Pessé 3a0f00411b Fix #505: add glossary to ebook file 2014-11-24 22:08:19 +01:00
Samy Pessé 5b655bb29a Fx typo 2014-11-24 21:56:33 +01:00
Samy Pessé faf10605e8 Add hook finish:before 2014-11-24 21:41:50 +01:00
Samy Pessé 5c98b3350b Merge pull request #503 from jr0cket/ebook-command-fix
updated eBook generation commands - resolves #432
2014-11-22 16:41:19 +01:00
John Stevenson 6f4e1d10c1 made commands clearer by seperating output filename option 2014-11-22 14:47:12 +00:00
John Stevenson 870886599a updated eBook generation commands 2014-11-22 11:26:08 +00:00
Michael Aigner 1b9801b8a4 add X-UA-Compatible meta tag for IE (https://github.com/GitbookIO/gitbook/issues/501) 2014-11-20 10:16:40 +01:00
Samy Pessé 897463b22e Bump version to 1.3.3 2014-11-14 22:10:19 +01:00
Samy Pessé aa4894e94c Fix parsing of already lexed content 2014-11-14 22:09:30 +01:00
18 changed files with 221 additions and 50 deletions
+15
View File
@@ -1,5 +1,20 @@
# Release notes
## 1.4.1
- Fix command 'install' without arguments
## 1.4.0
- Add command `gitbook install` to install plugins from book.json
- `package.json` is no longer necessary
## 1.3.4
- Add glossary to ebooks
- Fix autocover with new hook "finish:before"
- Add X-UA-Compatible meta tag for IE
## 1.3.3
- Fix parsing of lexed content using the client library
## 1.3.2
- ePub files are now passing validation from epubcheck
- Fix replacement of multiple glossary terms in a single sentence
+13 -2
View File
@@ -16,6 +16,11 @@ GitBook can be installed from **NPM** using:
$ npm install gitbook -g
```
Create the directories and files for a book from its [SUMMARY.md](https://github.com/GitbookIO/gitbook#book-format) file using
```
$ gitbook init
```
You can serve a repository as a book using:
```
@@ -52,9 +57,10 @@ Here are the options that can be stored in this file:
// It's not advised this option in the book.json
"generator": "site",
// Book title and description (defaults are extracted from the README)
// Book metadats (somes are extracted from the README by default)
"title": null,
"description": null,
"isbn": null,
// For ebook format, the extension to use for generation (default is detected from output extension)
// "epub", "pdf", "mobi"
@@ -132,7 +138,10 @@ You can publish your books to our index by visiting [GitBook.io](http://www.gitb
GitBook can generate your book in the following formats:
* **Static Website**: This is the default format. It generates a complete interactive static website that can be, for example, hosted on GitHub Pages.
* **eBook**: A complete eBook with exercise solutions at the end of the book. Generate this format using: ```gitbook ebook ./myrepo```. You need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed. The output format could be **PDF**, **ePub** or **MOBI**.
* **eBook**: A complete eBook with exercise solutions at the end of the book. You need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed. You can specify the eBook filename with the `-o` option, otherwise `book` will be used.
* Generate a **PDF** using: `gitbook pdf ./myrepo`
* Generate a **ePub** using: `gitbook epub ./myrepo`
* Generate a **MOBI** using: `gitbook mobi ./myrepo`
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate this format using: ```gitbook build ./myrepo -f json```.
## Book Format
@@ -211,6 +220,8 @@ The platform [GitBook.io](https://www.gitbook.io/) is like an "Heroku for books"
Plugins can be used to extend your book's functionality. Read [GitbookIO/plugin](https://github.com/GitbookIO/plugin) for more information about how to build a plugin for GitBook.
Plugins needed to build a book can be installed using: `gitbook install ./`.
##### Official plugins:
| Name | Description |
+5
View File
@@ -51,6 +51,11 @@ var makeBuildFunc = function(converter) {
// Exit process with failure code
process.exit(-1);
})
.then(function() {
// Prevent badly behaving plugins
// from making the process hang
process.exit(0);
});
};
};
+25 -1
View File
@@ -7,7 +7,7 @@ var prog = require('commander');
var tinylr = require('tiny-lr-fork');
var pkg = require('../package.json');
var generators = require("../lib/generate").generators;
var genbook = require("../lib/generate");
var initDir = require("../lib/generate/init");
var fs = require('../lib/generate/fs');
@@ -80,6 +80,30 @@ build.command(prog.command('serve [source_dir]'))
generate();
});
build.commandEbook(prog.command('install [source_dir]'))
.description('Install plugins for a book')
.action(function(dir, options) {
dir = dir || process.cwd();
console.log("Install plugins in", dir);
genbook.config.read({
input: dir
})
.then(function(options) {
return genbook.Plugin.install(options);
})
.then(function() {
console.log("Successfully installed plugins!");
})
.fail(function(err) {
// Log error
utils.logError(err);
// Exit process with failure code
process.exit(-1);
});
});
build.commandEbook(prog.command('pdf [source_dir]'))
.description('Build a gitbook as a PDF')
.action(function(dir, options) {
+1 -1
View File
@@ -428,7 +428,7 @@ function parsePage(src, options) {
// Lex if not already lexed
var parsed = {
lexed: (_.isArray(src) ? page.content : lex(include(src, options.includer || function() { return undefined; })))
lexed: (_.isArray(src) ? src : lex(include(src, options.includer || function() { return undefined; })))
};
parsed.sections = parsed.lexed.map(function(section) {
// Transform given type
+1 -1
View File
File diff suppressed because one or more lines are too long
+28 -4
View File
@@ -1,3 +1,4 @@
var Q = require('q');
var _ = require('lodash');
var path = require('path');
@@ -16,9 +17,10 @@ var CONFIG = {
// Configuration file to use
"configFile": "book",
// Book title and description (defaults are extracted from the README)
// Book metadats (somes are extracted from the README by default)
"title": null,
"description": null,
"isbn": null,
// For ebook format, the extension to use for generation (default is detected from output extension)
// "epub", "pdf", "mobi"
@@ -96,10 +98,32 @@ var CONFIG = {
}
};
// Return complete configuration
var defaultsConfig = function(options) {
return _.merge(options || {}, CONFIG, _.defaults);
};
// Read configuration from book.json
var readConfig = function(options) {
options = defaultsConfig(options);
return Q()
.then(function() {
try {
var _config = require(path.resolve(options.input, options.configFile));
options = _.merge(options, _.omit(_config, 'input', 'configFile', 'defaultsPlugins', 'generator'));
}
catch(err) {
// No config file: not a big deal
return Q();
}
})
.thenResolve(options);
};
module.exports = {
CONFIG: CONFIG,
defaults: function(options) {
return _.merge(options || {}, CONFIG, _.defaults);
}
defaults: defaultsConfig,
read: readConfig
}
+1
View File
@@ -30,6 +30,7 @@ Generator.prototype.finish = function() {
"--cover": that.options.cover,
"--title": that.options.title,
"--comments": that.options.description,
"--isbn": that.options.isbn,
"--authors": that.options.author,
"--publisher": "GitBook",
"--chapter": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter ')]",
+14 -1
View File
@@ -64,7 +64,20 @@ var getFiles = function(path) {
module.exports = {
list: getFiles,
readFile: Q.denodeify(fs.readFile),
writeFile: Q.denodeify(fs.writeFile),
//writeFile: Q.denodeify(fs.writeFile),
writeFile: function(filename, data, options) {
var d = Q.defer();
try {
fs.writeFileSync(filename, data, options)
} catch(err) {
d.reject(err);
}
d.resolve();
return d.promise;
},
mkdirp: Q.denodeify(fsExtra.mkdirp),
copy: Q.denodeify(fsExtra.copy),
remove: Q.denodeify(fsExtra.remove),
+16 -20
View File
@@ -48,28 +48,18 @@ var loadGenerator = function(options) {
var generate = function(options) {
// Set defaults to options
options = defaultConfig.defaults(options);
// Validate options
if (!options.input) {
return Q.reject(new Error("Need option input (book input directory)"));
}
// Check files to get folder type (book, multilanguage book or neither)
return checkGenerator(options)
// Read config file
.then(function() {
try {
var _config = require(path.resolve(options.input, options.configFile));
return defaultConfig.read(options)
.then(function(_options) {
options = _options;
options = _.merge(options, _.omit(_config, 'input', 'configFile', 'defaultsPlugins'));
}
catch(err) {
// No config file: not a big deal
return Q();
// Validate options
if (!options.input) {
return Q.reject(new Error("Need option input (book input directory)"));
}
// Check files to get folder type (book, multilanguage book or neither)
return checkGenerator(options);
})
// Read readme
@@ -99,7 +89,9 @@ var generate = function(options) {
});
};
/*
* Generate a multilanguage book by generating a book for each folder.
*/
var generateMultiLang = function(options) {
var langsSummary;
options.output = options.output || path.join(options.input, "_book");
@@ -297,6 +289,9 @@ var generateBook = function(options) {
})
// Finish generation
.then(function() {
return generator.callHook("finish:before");
})
.then(function() {
return generator.finish();
})
@@ -375,4 +370,5 @@ module.exports = {
file: generateFile,
book: generateBook,
Plugin: Plugin,
config: defaultConfig
};
+9 -1
View File
@@ -26,6 +26,9 @@ Generator.prototype.loadTemplates = function() {
this.summaryTemplate = swig.compileFile(
this.plugins.template("ebook:sumary") || path.resolve(this.options.theme, 'templates/ebook/summary.html')
);
this.glossaryTemplate = swig.compileFile(
this.plugins.template("ebook:glossary") || path.resolve(this.options.theme, 'templates/ebook/glossary.html')
);
};
// Generate table of contents
@@ -46,7 +49,7 @@ Generator.prototype.finish = function() {
var output = path.join(this.options.output, "index.html");
var progress = parse.progress(this.options.navigation, "README.md");
return Q()
// Write table of contents
@@ -54,6 +57,11 @@ Generator.prototype.finish = function() {
return that.writeToc();
})
// Write glossary
.then(function() {
return that.writeGlossary();
})
// Copy cover
.then(function() {
return that.copyCover();
+57 -16
View File
@@ -4,6 +4,7 @@ var semver = require("semver");
var path = require("path");
var url = require("url");
var fs = require("./fs");
var npmi = require('npmi');
var resolve = require('resolve');
var pkg = require("../../package.json");
@@ -152,33 +153,73 @@ Plugin.prototype.copyAssets = function(out, options) {
};
// Normalize a list of plugin name to use
Plugin.normalizeNames = function(names) {
// Install a list of plugin
Plugin.install = function(options) {
// Normalize list of plugins
var plugins = Plugin.normalizeList(options.plugins);
// Install plugins one by one
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function() {
var fullname = "gitbook-plugin-"+plugin.name;
console.log("Install plugin", plugin.name, "from npm ("+fullname+") with version", (plugin.version || "*"));
return Q.nfcall(npmi, {
'name': fullname,
'version': plugin.version,
'path': options.input,
'npmLoad': {
'loglevel': 'silent',
'loaded': false,
'prefix': options.input
}
});
});
}, Q());
};
// Normalize a list of plugins to use
Plugin.normalizeList = function(plugins) {
// Normalize list to an array
names = _.isString(names) ? names.split(",") : (names || []);
plugins = _.isString(plugins) ? plugins.split(",") : (plugins || []);
// Divide as {name, version} to handle format like "myplugin@1.0.0"
plugins = _.map(plugins, function(plugin) {
var parts = plugin.split("@");
return {
'name': parts[0],
'version': parts[1] // optional
}
});
// List plugins to remove
var toremove = _.chain(names)
.filter(function(name) {
return name.length > 0 && name[0] == "-";
var toremove = _.chain(plugins)
.filter(function(plugin) {
return plugin.name.length > 0 && plugin.name[0] == "-";
})
.map(function(name) {
return name.slice(1);
.map(function(plugin) {
return plugin.name.slice(1);
})
.value();
// Merge with defaults
names = _.chain(names)
.concat(Plugin.defaults)
plugins = _.chain(plugins)
.concat(_.map(Plugin.defaults, function(plugin) {
return { 'name': plugin }
}))
.uniq()
.value();
// Remove plugins starting with
names = _.filter(names, function(name) {
return !_.contains(toremove, name) && !(name.length > 0 && name[0] == "-");
// Build final list
plugins = _.filter(plugins, function(plugin) {
return !_.contains(toremove, plugin.name) && !(plugin.name.length > 0 && plugin.name[0] == "-");
});
return names;
return plugins;
};
// Normalize a list of plugin name to use
Plugin.normalizeNames = function(plugins) {
return _.pluck(Plugin.normalizeList(plugins), "name");
};
// Extract data from a list of plugin
@@ -196,7 +237,7 @@ Plugin.fromList = function(names, root, generator, options) {
return plugin;
});
if (_.size(failed) > 0) return Q.reject(new Error("Error loading plugins: "+failed.join(",")));
if (_.size(failed) > 0) return Q.reject(new Error("Error loading plugins: "+failed.join(",")+". Run 'gitbook install' to install plugins from NPM."));
// The raw resources extracted from each plugin
var pluginResources;
@@ -276,7 +317,7 @@ Plugin.fromList = function(names, root, generator, options) {
});
};
// Default plugins
// Default plugins added to each books
Plugin.defaults = [
"mathjax"
];
+1 -1
View File
@@ -52,7 +52,7 @@ function parsePage(src, options) {
// Lex if not already lexed
var parsed = {
lexed: (_.isArray(src) ? page.content : lex(include(src, options.includer || function() { return undefined; })))
lexed: (_.isArray(src) ? src : lex(include(src, options.includer || function() { return undefined; })))
};
parsed.sections = parsed.lexed.map(function(section) {
// Transform given type
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "1.3.2",
"version": "1.4.2",
"homepage": "http://www.gitbook.io/",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -19,6 +19,7 @@
"highlight.js": "8.3.0",
"tmp": "0.0.23",
"semver": "2.2.1",
"npmi": "0.1.1",
"gaze": "~0.5.1",
"resolve": "0.6.3",
"tiny-lr-fork": "0.0.5",
+10 -1
View File
@@ -12,7 +12,7 @@ describe('Plugin validation', function () {
});
});
describe('Plugin list of names', function () {
describe('Plugins list', function () {
var firstDefault = _.first(Plugin.defaults);
it('should convert string to array', function() {
@@ -27,6 +27,15 @@ describe('Plugin list of names', function () {
it('should remove name starting with -', function() {
assert(!_.contains(Plugin.normalizeNames(["-"+firstDefault]), firstDefault));
});
it('should accept version', function() {
var _name = "test@0.3.0,exercises@1.2.0,test2";
var plugins = Plugin.normalizeList(_name);
assert(_.find(plugins, {'name': "test"}).version = "0.3.0");
assert(_.find(plugins, {'name': "exercises"}).version = "1.2.0");
assert(!_.find(plugins, {'name': "test2"}).version);
});
});
describe('Plugin defaults loading', function () {
+15
View File
@@ -0,0 +1,15 @@
{% extends "./layout.html" %}
{% block title %}Glossary | {{ title }}{% endblock %}
{% block content %}
<div class="page page-toc">
<h1>Glossary</h1>
{% for item in glossaryIndex %}
<section class="normal glossary" id="{{ item.id }}">
<h2><a href="#{{ item.id }}">{{ item.name }}</a></h2>
<p>{{ item.description }}</p>
</section>
{% endfor %}
</div>
{% endblock %}
+4
View File
@@ -40,6 +40,10 @@
<h1>Table of Contents</h1>
<ol>
{{ articles(summary.chapters) }}
{% if glossary.length > 0 %}
<li><a href="{{ basePath }}/GLOSSARY.html">Glossary</a></li>
{% endif %}
</ol>
</div>
{% endblock %}
+4
View File
@@ -4,6 +4,7 @@
<head>
{{ htmlSnippet("head:start")|default("") }}
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=11; IE=10; IE=9; IE=8; IE=7; IE=EDGE" />
<title>{% block title %}{% endblock %}</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="{% block description %}{% endblock %}">
@@ -14,6 +15,9 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{ staticBase }}/images/apple-touch-icon-precomposed-152.png">
<link rel="shortcut icon" href="{{ staticBase }}/images/favicon.ico" type="image/x-icon">
{% if options.isbn %}
<meta name="identifier" content="{{ options.isbn }}" scheme="ISBN">
{% endif %}
{% block head %}{% endblock %}
{{ htmlSnippet("head:end")|default("") }}
</head>