Compare commits

...

111 Commits

Author SHA1 Message Date
Samy Pessé a61f56662a Bump version to 2.0.0 2015-03-25 10:43:58 +01:00
Samy Pessé f42e48be03 Fix scroll reset on small resolutions (mobile & tablet) 2015-03-24 23:59:03 +01:00
Samy Pessé f640e101ba Fix scroling position when preparing page 2015-03-24 23:52:41 +01:00
Samy Pessé b7da276ae0 Fix title for introduction (with i18n) 2015-03-24 23:49:12 +01:00
Samy Pessé bf7c0989f5 Update gitbook-parsers@0.7.2 2015-03-24 23:35:17 +01:00
Samy Pessé 65fdbe7f55 Simplify examples of commands 2015-03-24 23:19:00 +01:00
Samy Pessé c9af209a93 Merge pull request #667 from GitbookIO/better-testing
Better Unit Tests
2015-03-24 23:14:17 +01:00
Samy Pessé 48ab44a776 Add tests for conrefs 2015-03-24 18:22:44 +01:00
Samy Pessé b2e684af8f Add test for templating context 2015-03-24 17:34:47 +01:00
Samy Pessé b38a2c11a4 Add tests for plugins 2015-03-24 17:31:15 +01:00
Samy Pessé c31acef47a Add tests for images in ebook 2015-03-24 16:26:19 +01:00
Samy Pessé e9a573e18a Add test for default description from readme 2015-03-24 14:57:29 +01:00
Samy Pessé c07ddc77f7 Add tests for links 2015-03-24 14:52:24 +01:00
Samy Pessé 303fc992d4 Add test for git url parsing 2015-03-24 14:44:07 +01:00
Samy Pessé 4b7797f61b Add basic tests for multilingual books 2015-03-24 14:16:03 +01:00
Samy Pessé f775d41933 Add base tests for ebook generation 2015-03-24 13:52:29 +01:00
Samy Pessé 7722e7bdfa Update gitbook-parsers@0.7.1 2015-03-24 13:33:56 +01:00
Samy Pessé 346c05f0de Add tests for configuration parsing 2015-03-24 13:24:20 +01:00
Samy Pessé 73c69923b3 Add test for summary parsing
Update gitbook-parsers
2015-03-24 13:11:29 +01:00
Samy Pessé afbca4bdf5 Improve tests for glossary 2015-03-24 11:54:03 +01:00
Samy Pessé b61371942b Add test for glossary replacements 2015-03-24 11:27:37 +01:00
Samy Pessé 61c07e8876 Add test for glossary parsing 2015-03-24 10:24:49 +01:00
Samy Pessé d317741144 Add basic test for multilingual book 2015-03-24 09:36:51 +01:00
Samy Pessé 275739a778 Add basic test for website generation 2015-03-24 08:49:39 +01:00
Samy Pessé cb3a43ed8a Add string representation for books 2015-03-24 08:49:22 +01:00
Samy Pessé ec8d1d8fc9 Improve test for json format 2015-03-23 23:48:46 +01:00
Samy Pessé 868a3186dd Add assertion to test that a file exists 2015-03-23 23:32:07 +01:00
Samy Pessé 17b546181c Fix cleanup after tests 2015-03-23 23:23:25 +01:00
Samy Pessé 2d1aa15af8 New cleaner helper for testing 2015-03-23 23:18:15 +01:00
Samy Pessé 63ee94ff89 Ignore codeblocks when replacing glossary terms 2015-03-23 11:00:50 +01:00
Samy Pessé 7d9dc08a38 Highlight codeblocks in gitbook instead than the parsers 2015-03-23 10:31:48 +01:00
Samy Pessé a836bdedb4 Bump version to 2.0.0-beta.5 2015-03-17 11:23:35 +01:00
Samy Pessé 15ebae8cb1 Fix #641: fix progress order in json format 2015-03-17 11:22:56 +01:00
Samy Pessé 315556310f Bump version to 2.0.0-beta.4 2015-03-17 10:48:25 +01:00
Samy Pessé 9c0b26304b Merge branch master 2015-03-17 10:46:17 +01:00
Samy Pessé acdaea9f6c Fix #649: add option "author" in book.json to set meta author in html 2015-03-17 10:38:56 +01:00
Samy Pessé e481deb5bb Merge pull request #650 from wcandillon/patch-2
Update default generator
2015-03-14 16:00:09 -07:00
William Candillon 6d3ac6074c 💄 Update default generator 2015-03-14 15:45:15 -07:00
Samy Pessé 5e611bde48 Merge pull request #648 from wcandillon/patch-1
Bump to lodash 3.5.0
2015-03-14 15:25:39 -07:00
Samy Pessé 9448562156 Add editorconfig and normalize indent 2015-03-14 23:23:28 +01:00
William Candillon e7e8fe4d08 ⬆️ Bump to lodash 3.5.0 2015-03-14 11:35:28 -07:00
Samy Pessé 8320484b88 Fix #647: Fix links normalization (content and hash) 2015-03-13 23:59:37 +01:00
Samy Pessé 0e243e6f23 Bump version to 2.0.0-beta.3 2015-03-12 21:23:25 +01:00
Samy Pessé 5cd25b0d21 Update gitbook-parsers@0.5.4 2015-03-12 21:20:51 +01:00
Samy Pessé 3218ee7455 Fix tests to adapt to changes in parsers 2015-03-11 22:05:58 +01:00
Samy Pessé 0552007b8a Bump version to 2.0.0-beta.2 2015-03-11 22:03:54 +01:00
Samy Pessé 50bc89d9d9 Fix define of introduction from summary 2015-03-11 22:02:46 +01:00
Samy Pessé 743a88f53a Fix typo in bin message 2015-03-11 11:40:56 +01:00
Samy Pessé ee92c6dd99 Add changelog for 2.0.0-beta.1 2015-03-11 11:23:48 +01:00
Samy Pessé b6c54c17d1 Bump version to 2.0.0-beta.1 2015-03-11 11:17:56 +01:00
Samy Pessé 992ef1ab74 Use latest commit from nunjucks windows fix branch 2015-03-11 11:17:43 +01:00
Samy Pessé 960848ba34 Ensure inline svgs have xml header 2015-03-11 09:38:18 +01:00
Samy Pessé d9aca7a9df Fix #639: update nunjucks to support windows 2015-03-11 00:57:07 +01:00
Samy Pessé 05a0de2d86 Remove require of old dependency "shell-escape" 2015-03-11 00:50:48 +01:00
Samy Pessé 19e4f498cf Fix inline svg extraction on windows 2015-03-10 16:45:22 -07:00
Samy Pessé 3055139297 Fix run of svgexport on windows 2015-03-10 16:31:43 -07:00
Samy Pessé 3d6b099dea Fix links normalization on windows 2015-03-10 22:44:54 +01:00
Samy Pessé dd970e33b8 Remove --no-exit option from mocha tests runner 2015-03-10 21:31:43 +01:00
Samy Pessé 59ba479465 Test option --no-exit for mocha tests 2015-03-10 16:14:04 +01:00
Samy Pessé 54c9ecf3c2 Use mocha from node_modules/.bin 2015-03-10 15:44:39 +01:00
Samy Pessé 8308ae69a2 Remove command export from "npm test" 2015-03-10 15:01:51 +01:00
Samy Pessé bd6c3287ed Add badge for windows testing 2015-03-10 14:58:23 +01:00
Samy Pessé 24fc36deb1 Add appveyor.yml for windows testing
Related to #639
2015-03-10 14:52:09 +01:00
Samy Pessé 2f44b61cec Merge pull request #638 from wapmorgan/patch-1
Update README.md
2015-03-09 21:13:29 +01:00
wapmorgan 45ee503db1 Update README.md
Fix link
2015-03-10 00:12:53 +04:00
Samy Pessé dc826da99b Update README.md 2015-03-09 10:58:11 +01:00
Samy Pessé 3ea659d582 Improve readme presentation 2015-03-09 10:57:23 +01:00
Samy Pessé 34fc2831e0 Merge branch 'version/2.0' 2015-03-09 10:43:12 +01:00
Samy Pessé 1bf68a5aa0 Merge branch 'master' into version/2.0
Conflicts:
	README.md
2015-03-09 10:42:42 +01:00
Samy Pessé d72c397f23 Update gitbook-parsers@0.5.2 2015-03-08 23:09:50 +01:00
Samy Pessé 2c69525148 Add GitBook as book producer in ebook-convert 2015-03-08 22:53:18 +01:00
Samy Pessé ee3a0ba801 Fix #633: update markdown parser to fix codeblocks
Sort glossary and fix #634
2015-03-08 22:53:18 +01:00
Samy Pessé 7a4179a2a2 Merge pull request #631 from MHM5000/version/2.0
[i18n] - fa: Persian/Farsi
2015-03-07 18:48:05 +01:00
Mohammad Hossein Mojtahedi 1d0083c5f4 added @mhm5000 in translators 2015-03-07 18:22:40 +03:30
Mohammad Hossein Mojtahedi 33a6111a30 [i18n] - fa: Persian/Farsi 2015-03-07 16:48:44 +03:30
Samy Pessé a5d7a87e31 Enable rtl in ebooks 2015-03-06 22:45:53 +01:00
Samy Pessé c7f7c5b5ab Base translation for Arabic (with rtl support) 2015-03-06 22:32:45 +01:00
Samy Pessé 55a7b99b4d Fix #227: Add base rtl support for website 2015-03-06 22:32:25 +01:00
Samy Pessé 8bfba604b3 Update gitbook-parsers@0.5.0 and include page.prepare
Fix #625: escape code blocks in markdown
2015-03-06 16:54:46 +01:00
Samy Pessé 2a5fa1c5fb Fix #629: add generator name in template context 2015-03-06 14:34:55 +01:00
Samy Pessé a3e95feb26 Add instructions to use version from git 2015-03-06 13:58:23 +01:00
Samy Pessé 145b00f590 Fix #628: preserve pure anchor links and hash in relative links 2015-03-06 10:25:06 +01:00
Samy Pessé 5f2785f0a8 Escape svgexport args
Fixes shakiba/svgexport#5 and #624
2015-03-05 10:06:45 +01:00
Samy Pessé 412529c548 Merge pull request #621 from nasirkhan/version/2.0
Bengali (bn) added in theme/i18n
2015-02-27 17:48:07 +01:00
nasirkhan 547ec6ade8 bn language added 2015-02-27 22:29:10 +06:00
nasirkhan eac513cfa3 Merge remote-tracking branch 'upstream/version/2.0' into version/2.0 2015-02-27 22:16:11 +06:00
Aaron O'Mullan 5ec8a1f18c Add en.json
Fixes #620
2015-02-27 16:02:31 +01:00
Samy Pessé c923a7e01a Fix transformation of README.md links in index.html 2015-02-26 01:14:27 +01:00
Samy Pessé 366e6ed105 Bump version to 2.0.0-alpha.9 2015-02-24 13:57:20 +01:00
Samy Pessé 52078eb666 Use latest nunjucks from mozilla/nunjucks 2015-02-24 13:54:08 +01:00
Samy Pessé d9ddbff443 Fix links in sidebar 2015-02-24 13:51:10 +01:00
Samy Pessé 01b160f9a1 Fix normalization of html link (README to index) 2015-02-21 13:40:38 +01:00
Samy Pessé 23fcb817a4 Fix html snippets escaping 2015-02-20 20:52:07 +01:00
Samy Pessé f531a3f984 Update nunjucks-autoescape 2015-02-20 12:24:33 +01:00
Samy Pessé c777023e9f Add introduction to changelog 2015-02-17 14:18:49 +01:00
Samy Pessé 62c99759c1 Bump version to 2.0.0-alpha.8 2015-02-14 16:43:19 +01:00
Samy Pessé 6951c21d19 Update gitbook-parsers@0.4.1 2015-02-14 16:26:16 +01:00
Samy Pessé 3a8289483e Improve language detection from book.json 2015-02-14 15:34:19 +01:00
Samy Pessé 4079de9587 Fix #608: fix chapter name for glossary 2015-02-14 15:29:41 +01:00
Samy Pessé b86d171082 Escape description in title attr of glossary link
Related to #609
2015-02-14 11:53:30 +01:00
Samy Pessé 959b272e65 Fix #609: don't escape html in glossary 2015-02-14 11:52:33 +01:00
Samy Pessé 684440e2d1 Fix generation of multilingual book as ebook 2015-02-14 11:49:42 +01:00
Samy Pessé 4740cd3b06 Merge pull request #605 from GitbookIO/update/lodash
Update lodash to 3.x.x
2015-02-13 08:18:45 +01:00
Samy Pessé b3131d99d2 Update lodash@3.2.0 2015-02-13 08:16:28 +01:00
Samy Pessé b0fd2a67ea Fix test with post in blocks 2015-02-13 08:09:28 +01:00
Samy Pessé 7c83869869 Limit concurrent blocks processed 2015-02-13 00:04:27 +01:00
Samy Pessé 57d5c23eaf Accept post function from block to post process content 2015-02-12 23:36:22 +01:00
Samy Pessé 8d92b74e61 Fix page normalization with empty links or images 2015-02-12 18:43:06 +01:00
Samy Pessé 4e2cda99cc Complete changelog for 2.0.0-alpha.7 2015-02-12 16:40:50 +01:00
Samy Pessé 12a7a4c4cf Fix test for lodash 3.1.0 (from git) 2015-02-11 17:53:46 +01:00
Samy Pessé e1594cde2c Remove doc about custom styles 2015-01-19 13:14:52 +01:00
118 changed files with 2310 additions and 1654 deletions
+10
View File
@@ -0,0 +1,10 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
+2
View File
@@ -28,6 +28,8 @@ Translators
- Andrey Akinshin (@AndreyAkinshin)
- Norwegian
- Knut Melvær (@kmelve)
- Persian (Farsi)
- Mohammad Hossein Mojtahedi (@mhm5000)
- Portuguese
- Ryan O'Mullan (@RyanOM)
- Spanish
+49
View File
@@ -1,4 +1,53 @@
# Release notes
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 2.0.0
- Fix page title of introduction
- Ignore codeblocks when replacing glossary terms
- Improve Unit Tests
- Fix scrolling position in website when preparing page
## 2.0.0-beta.5
- Fix progress order in json format
## 2.0.0-beta.4
- Fix default generator for use programmatically
- Add option "author" for html meta tags
- Fix links normalization (content and hash)
## 2.0.0-beta.3
- Fix odd cases with code blocks escaping
## 2.0.0-beta.2
- Fix definition of entry point title using SUMMARY.md
## 2.0.0-beta.1
- Fix windows incompatibility
- Add support for rtl (enabled by default for `ar` and `fa`)
- Escape code blocks in markdown parser
- Add Persian/Farsi translation (`fa`)
- Add Arabic translation (`ar`)
- Add Bengali translation (`bn`)
- Provide generator name in template context
## 2.0.0-alpha.9
- Fix links in sidebar
- Fix normalization of html link (README to index)
- Fix html snippets escaping
## 2.0.0-alpha.8
- Improve locale detection for i18n
- Fix chapter name for Glossary in pdf
- Don't escape html in glossary items
- Fix generation of multilingual book as ebook
- Add "post" block attribute to post-process
## 2.0.0-alpha.7
- Fix display of glossary in ebook formats
- Add default footer and header to pdf
- Fix generation of json format compatible with 1.x.x
- Add Simplifiled Chinese and Traditional Chinese translations
## 2.0.0-alpha.6
- Add es and pt translations
+33 -15
View File
@@ -1,12 +1,17 @@
GitBook
=======
:warning: This branch contains the version 2.0.
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
[![NPM version](https://badge.fury.io/js/gitbook.svg)](http://badge.fury.io/js/gitbook)
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/GitbookIO/gitbook?branch=master&svg=true)](https://ci.appveyor.com/project/SamyPesse/gitbook)
GitBook is a command line tool (and Node.js library) for building beautiful books using GitHub/Git and Markdown (or AsciiDoc). Here is an example: [Learn Javascript](https://www.gitbook.com/book/GitBookIO/javascript). You can publish and host book easily online using [gitbook.com](https://www.gitbook.com), a web-editor is [also available](https://www.gitbook.com/editor). You can follow [@GitBookIO](https://twitter.com/GitBookIO) on Twitter. Complete documentation is available at [help.gitbook.io](http://help.gitbook.io/).
GitBook is a command line tool (and Node.js library) for building beautiful books using GitHub/Git and Markdown (or AsciiDoc). Here is an example: [Learn Javascript](https://www.gitbook.com/book/GitBookIO/javascript).
You can publish and host book easily online using [gitbook.com](https://www.gitbook.com), a web-editor is [also available](https://www.gitbook.com/editor).
Stay updated by following [@GitBookIO](https://twitter.com/GitBookIO) on Twitter or [GitBook](https://www.facebook.com/gitbookcom) on Facebook.
Complete documentation is available at [help.gitbook.com](http://help.gitbook.com/).
![Image](https://raw.github.com/GitbookIO/gitbook/master/preview.png)
@@ -27,25 +32,25 @@ $ gitbook init
You can serve a repository as a book using:
```
$ gitbook serve ./repository
$ gitbook serve
```
Or simply build the static website using:
```
$ gitbook build ./repository ./outputFolder
$ gitbook build
```
## Features
* [Output as a website or ebook (pdf, epub, mobi)](#output-formats)
* [Multi-Languages](#multi-languages)
* [Glossary](#glossary)
* [Cover](#cover)
* [AsciiDoc Support](#asciidoc)
* [Variables and Templating](#variables-and-templating)
* [Content References](#content-references)
* [Plugins](#plugins)
* [Output as a website or ebook (pdf, epub, mobi)](http://help.gitbook.com/format/output.html)
* [Multi-Languages](http://help.gitbook.com/format/languages.html)
* [Glossary](http://help.gitbook.com/format/glossary.html)
* [Cover](http://help.gitbook.com/format/cover.html)
* [AsciiDoc Support](http://help.gitbook.com/format/asciidoc.html)
* [Variables and Templating](http://help.gitbook.com/format/templating.html)
* [Content References](http://help.gitbook.com/format/conrefs.html)
* [Plugins](http://help.gitbook.com/plugins/index.html)
* Interractive reader website:
* Search
* Font Settings (Serif, Sans Serif)
@@ -199,7 +204,7 @@ The platform [GitBook.com](https://www.gitbook.com/) is like an "Heroku for book
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 ./`. You can find plugins at [plugins.gitbook.com](plugins.gitbook.com).
Plugins needed to build a book can be installed using: `gitbook install ./`. You can find plugins at [plugins.gitbook.com](http://plugins.gitbook.com).
## Debugging
@@ -209,3 +214,16 @@ You can use the options `--log=debug` and `--debug` to get better error messages
```
$ gitbook build ./ -- log=debug --debug
```
#### How to use the latest commit from GitBook in gitbook-cli
To use the latest commit from `GitBook/gitbook` with `gitbook-cli`:
```
$ git clone https://github.com/GitbookIO/gitbook.git ./gitbook
$ gitbook versions:link 2.0.0 ./gitbook
```
Now the version tag `2.0.0` wil be associated with the `./gitbook` folder.
You can uninstall it using: `gitbook versions:uninstall 2.0.0`.
+29
View File
@@ -0,0 +1,29 @@
# Fix line endings in Windows. (runs before repo cloning)
init:
- git config --global core.autocrlf input
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.10"
- nodejs_version: "0.11"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# install svgexport
- npm install svgexport -g
# install modules
- npm install
# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- npm --version
# run tests
- npm test
# Don't actually build.
build: off
+1 -1
View File
@@ -3,5 +3,5 @@
var color = require('bash-color');
console.log(color.red("You need to install 'gitbook-cli' to have access to the gitbook command anywhere on your system."));
console.log(color.red("If you've installe this package globally, you need to uninstall it."));
console.log(color.red("If you've installed this package globally, you need to uninstall it."));
console.log(color.red(">> Run 'npm uninstall -g gitbook' then 'npm install -g gitbook-cli'"));
+336 -319
View File
@@ -10,6 +10,7 @@ var fs = require("./utils/fs");
var parseNavigation = require("./utils/navigation");
var parseProgress = require("./utils/progress");
var pageUtil = require("./utils/page");
var batch = require("./utils/batch");
var links = require("./utils/links");
var logger = require("./utils/logger");
@@ -21,68 +22,68 @@ var PluginsList = require("./pluginslist");
var generators = require("./generators");
var Book = function(root, context, parent) {
var that = this;
var that = this;
this.context = _.defaults(context || {}, {
// Extend book configuration
config: {},
this.context = _.defaults(context || {}, {
// Extend book configuration
config: {},
// Log function
log: function(msg) {
process.stdout.write(msg);
},
// Log function
log: function(msg) {
process.stdout.write(msg);
},
// Log level
logLevel: "info"
});
// Log level
logLevel: "info"
});
// Log
this.log = logger(this.context.log, this.context.logLevel);
// Log
this.log = logger(this.context.log, this.context.logLevel);
// Root folder of the book
this.root = path.resolve(root);
// Root folder of the book
this.root = path.resolve(root);
// Parent book
this.parent = parent;
// Parent book
this.parent = parent;
// Configuration
this.config = new Configuration(this, this.context.config);
Object.defineProperty(this, "options", {
get: function () {
return this.config.options;
}
});
// Configuration
this.config = new Configuration(this, this.context.config);
Object.defineProperty(this, "options", {
get: function () {
return this.config.options;
}
});
// Template
this.template = new TemplateEngine(this);
// Template
this.template = new TemplateEngine(this);
// Summary
this.summary = {};
this.navigation = [];
// Summary
this.summary = {};
this.navigation = [];
// Glossary
this.glossary = [];
// Glossary
this.glossary = [];
// Langs
this.langs = [];
// Langs
this.langs = [];
// Sub-books
this.books = [];
// Sub-books
this.books = [];
// Files in the book
this.files = [];
// Files in the book
this.files = [];
// List of plugins
this.plugins = new PluginsList(this);
// List of plugins
this.plugins = new PluginsList(this);
// Structure files
// Structure files
this.summaryFile = null;
this.glossaryFile = null;
this.readmeFile = null;
this.readmeFile = null;
this.langsFile = null;
// Search Index
this.searchIndex = lunr(function () {
// Search Index
this.searchIndex = lunr(function () {
this.ref('url');
this.field('title', { boost: 10 });
@@ -90,151 +91,158 @@ var Book = function(root, context, parent) {
});
// Bind methods
_.bindAll(this);
_.bindAll(this);
};
// Return string representation
Book.prototype.toString = function() {
return "[Book "+this.root+"]";
};
// Initialize and parse the book: config, summary, glossary
Book.prototype.parse = function() {
var that = this;
var multilingual = false;
var that = this;
var multilingual = false;
return this.parseConfig()
return this.parseConfig()
.then(function() {
return that.parsePlugins();
})
.then(function() {
return that.parsePlugins();
})
.then(function() {
return that.parseLangs()
.then(function() {
multilingual = that.langs.length > 0;
if (multilingual) that.log.info.ln("Parsing multilingual book, with", that.langs.length, "lanuages");
.then(function() {
return that.parseLangs()
.then(function() {
multilingual = that.langs.length > 0;
if (multilingual) that.log.info.ln("Parsing multilingual book, with", that.langs.length, "lanuages");
// Sub-books that inherit from the current book configuration
that.books = _.map(that.langs, function(lang) {
that.log.info.ln("Preparing language book", lang.lang);
return new Book(
path.join(that.root, lang.path),
_.merge({}, that.context, {
config: _.extend({}, that.options, {
'output': path.join(that.options.output, lang.lang),
'language': lang.lang
})
}),
that
)
});
});
})
// Sub-books that inherit from the current book configuration
that.books = _.map(that.langs, function(lang) {
that.log.info.ln("Preparing language book", lang.lang);
return new Book(
path.join(that.root, lang.path),
_.merge({}, that.context, {
config: _.extend({}, that.options, {
'output': path.join(that.options.output, lang.lang),
'language': lang.lang
})
}),
that
)
});
});
})
.then(function() {
if (multilingual) return;
return that.listAllFiles();
})
.then(function() {
if (multilingual) return;
return that.parseReadme();
})
.then(function() {
if (multilingual) return;
return that.parseSummary();
})
.then(function() {
if (multilingual) return;
return that.parseGlossary();
})
.then(function() {
if (multilingual) return;
return that.listAllFiles();
})
.then(function() {
if (multilingual) return;
return that.parseReadme();
})
.then(function() {
if (multilingual) return;
return that.parseSummary();
})
.then(function() {
if (multilingual) return;
return that.parseGlossary();
})
.then(function() {
// Init sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.parse();
});
}, Q());
})
.then(function() {
// Init sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.parse();
});
}, Q());
})
.thenResolve(this);
.thenResolve(this);
};
// Generate the output
Book.prototype.generate = function(generator) {
var that = this;
that.options.generator = generator || that.options.generator;
var that = this;
that.options.generator = generator || that.options.generator;
that.log.info.ln("start generation with", that.options.generator, "generator");
return Q()
that.log.info.ln("start generation with", that.options.generator, "generator");
return Q()
// Clean output folder
.then(function() {
// Clean output folder
.then(function() {
that.log.info("clean", that.options.generator, "generator");
return fs.clean(that.options.output)
return fs.clean(that.options.output)
.progress(function(p) {
that.log.debug.ln("remove", p.file, "("+p.i+"/"+p.count+")");
})
.then(function() {
that.log.info.ok();
});
})
})
// Create generator
.then(function() {
var Generator = generators[generator];
if (!Generator) throw "Generator '"+that.options.generator+"' doesn't exist";
generator = new Generator(that);
// Create generator
.then(function() {
var Generator = generators[generator];
if (!Generator) throw "Generator '"+that.options.generator+"' doesn't exist";
generator = new Generator(that);
return generator.prepare();
})
return generator.prepare();
})
// Generate content
.then(function() {
if (that.isMultilingual()) {
return that.generateMultiLingual(generator);
} else {
// Separate list of files into the different operations needed
var ops = _.groupBy(that.files, function(file) {
if (file[file.length -1] == "/") {
return "directories";
// Generate content
.then(function() {
if (that.isMultilingual()) {
return that.generateMultiLingual(generator);
} else {
// Separate list of files into the different operations needed
var ops = _.groupBy(that.files, function(file) {
if (file[file.length -1] == "/") {
return "directories";
} else if (_.contains(parsers.extensions, path.extname(file)) && that.navigation[file]) {
return "content";
return "content";
} else {
return "files";
return "files";
}
});
});
return Q()
return Q()
// First, let's create folder
.then(function() {
return _.reduce(ops["directories"] || [], function(prev, folder) {
return prev.then(function() {
that.log.debug.ln("transferring folder", folder);
return Q(generator.transferFolder(folder));
});
}, Q());
})
// First, let's create folder
.then(function() {
return _.reduce(ops["directories"] || [], function(prev, folder) {
return prev.then(function() {
that.log.debug.ln("transferring folder", folder);
return Q(generator.transferFolder(folder));
});
}, Q());
})
// Then, let's copy other files
.then(function() {
return Q.all(_.map(ops["files"] || [], function(file) {
that.log.debug.ln("transferring file", file);
// Then, let's copy other files
.then(function() {
return Q.all(_.map(ops["files"] || [], function(file) {
that.log.debug.ln("transferring file", file);
return Q(generator.transferFile(file));
}));
})
}));
})
// Finally let's generate content
.then(function() {
return _.reduce(ops["content"] || [], function(prev, file) {
return prev.then(function() {
that.log.debug.ln("converting", file);
return Q(generator.convertFile(file));
});
}, Q());
});
}
})
// Finally let's generate content
.then(function() {
var nFiles = (ops["content"] || []).length;
return _.reduce(ops["content"] || [], function(prev, file, i) {
return prev.then(function() {
var p = ((i*100)/nFiles).toFixed(0)+"%";
that.log.info.ln("processing", file, p);
return Q(generator.convertFile(file));
});
}, Q());
});
}
})
// Finish generation
// Finish generation
.then(function() {
return generator.callHook("finish:before");
})
@@ -245,28 +253,28 @@ Book.prototype.generate = function(generator) {
return generator.callHook("finish");
})
.then(function() {
that.log.info.ln("generation is finished");
that.log.info.ln("generation is finished");
});
};
// Generate the output for a multilingual book
Book.prototype.generateMultiLingual = function(generator) {
var that = this;
var that = this;
return Q()
.then(function() {
// Generate sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.generate(that.options.generator);
});
}, Q());
});
return Q()
.then(function() {
// Generate sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.generate(that.options.generator);
});
}, Q());
});
};
// Extract files from ebook generated
Book.prototype.generateFile = function(output, options) {
var book = this;
var book = this;
options = _.defaults(options || {}, {
ebookFormat: path.extname(output).slice(1)
@@ -275,7 +283,7 @@ Book.prototype.generateFile = function(output, options) {
return fs.tmp.dir()
.then(function(tmpDir) {
book.setOutput(tmpDir);
book.setOutput(tmpDir);
return book.generate(options.ebookFormat)
.then(function(_options) {
@@ -310,7 +318,7 @@ Book.prototype.generateFile = function(output, options) {
}
})
.then(function(n) {
book.log.info.ok(n+" file(s) generated");
book.log.info.ok(n+" file(s) generated");
return fs.remove(tmpDir);
});
@@ -320,146 +328,147 @@ Book.prototype.generateFile = function(output, options) {
// Parse configuration
Book.prototype.parseConfig = function() {
var that = this;
var that = this;
that.log.info("loading book configuration....")
return that.config.load()
.then(function() {
that.log.info.ok();
});
that.log.info("loading book configuration....")
return that.config.load()
.then(function() {
that.log.info.ok();
});
};
// Parse list of plugins
Book.prototype.parsePlugins = function() {
var that = this;
var failed = [];
var that = this;
var failed = [];
// Load plugins
return that.plugins.load(that.options.plugins)
.then(function() {
if (_.size(that.plugins.failed) > 0) return Q.reject(new Error("Error loading plugins: "+that.plugins.failed.join(",")+". Run 'gitbook install' to install plugins from NPM."));
if (_.size(that.plugins.failed) > 0) return Q.reject(new Error("Error loading plugins: "+that.plugins.failed.join(",")+". Run 'gitbook install' to install plugins from NPM."));
that.log.info.ok(that.plugins.count()+" plugins loaded");
that.log.debug.ln("normalize plugins list");
that.log.info.ok(that.plugins.count()+" plugins loaded");
that.log.debug.ln("normalize plugins list");
});
};
// Parse readme to extract defaults title and description
Book.prototype.parseReadme = function() {
var that = this;
var structure = that.config.getStructure("readme");
that.log.debug.ln("start parsing readme:", structure);
var that = this;
var structure = that.config.getStructure("readme");
that.log.debug.ln("start parsing readme:", structure);
return that.findFile(structure)
.then(function(readme) {
if (!readme) throw "No README file";
if (!_.contains(that.files, readme.path)) throw "README file is ignored";
return that.findFile(structure)
.then(function(readme) {
if (!readme) throw "No README file";
if (!_.contains(that.files, readme.path)) throw "README file is ignored";
that.readmeFile = readme.path;
that.readmeFile = readme.path;
that._defaultsStructure(that.readmeFile);
that.log.debug.ln("readme located at", that.readmeFile);
return that.template.renderFile(that.readmeFile)
.then(function(content) {
return readme.parser.readme(content);
});
})
.then(function(readme) {
that.options.title = that.options.title || readme.title;
that.options.description = that.options.description || readme.description;
});
that.log.debug.ln("readme located at", that.readmeFile);
return that.template.renderFile(that.readmeFile)
.then(function(content) {
return readme.parser.readme(content);
});
})
.then(function(readme) {
that.options.title = that.options.title || readme.title;
that.options.description = that.options.description || readme.description;
});
};
// Parse langs to extract list of sub-books
Book.prototype.parseLangs = function() {
var that = this;
var that = this;
var structure = that.config.getStructure("langs");
that.log.debug.ln("start parsing languages index:", structure);
var structure = that.config.getStructure("langs");
that.log.debug.ln("start parsing languages index:", structure);
return that.findFile(structure)
.then(function(langs) {
if (!langs) return [];
return that.findFile(structure)
.then(function(langs) {
if (!langs) return [];
that.langsFile = langs.path;
that._defaultsStructure(that.langsFile);
that.log.debug.ln("languages index located at", that.langsFile);
return that.template.renderFile(that.langsFile)
.then(function(content) {
return langs.parser.langs(content);
});
})
.then(function(langs) {
that.langs = langs;
});
that.log.debug.ln("languages index located at", that.langsFile);
return that.template.renderFile(that.langsFile)
.then(function(content) {
return langs.parser.langs(content);
});
})
.then(function(langs) {
that.langs = langs;
});
};
// Parse summary to extract list of chapters
Book.prototype.parseSummary = function() {
var that = this;
var that = this;
var structure = that.config.getStructure("summary");
that.log.debug.ln("start parsing summary:", structure);
var structure = that.config.getStructure("summary");
that.log.debug.ln("start parsing summary:", structure);
return that.findFile(structure)
.then(function(summary) {
if (!summary) throw "No SUMMARY file";
return that.findFile(structure)
.then(function(summary) {
if (!summary) throw "No SUMMARY file";
// Remove the summary from the list of files to parse
// Remove the summary from the list of files to parse
that.summaryFile = summary.path;
that._defaultsStructure(that.summaryFile);
that.files = _.without(that.files, that.summaryFile);
that.files = _.without(that.files, that.summaryFile);
that.log.debug.ln("summary located at", that.summaryFile);
return that.template.renderFile(that.summaryFile)
.then(function(content) {
return summary.parser.summary(content, {
entryPoint: that.readmeFile,
files: that.files
});
});
})
.then(function(summary) {
that.summary = summary;
that.navigation = parseNavigation(that.summary, that.files);
});
that.log.debug.ln("summary located at", that.summaryFile);
return that.template.renderFile(that.summaryFile)
.then(function(content) {
return summary.parser.summary(content, {
entryPoint: that.readmeFile,
entryPointTitle: "",
files: that.files
});
});
})
.then(function(summary) {
that.summary = summary;
that.navigation = parseNavigation(that.summary, that.files);
});
};
// Parse glossary to extract terms
Book.prototype.parseGlossary = function() {
var that = this;
var that = this;
var structure = that.config.getStructure("glossary");
that.log.debug.ln("start parsing glossary: ", structure);
var structure = that.config.getStructure("glossary");
that.log.debug.ln("start parsing glossary: ", structure);
return that.findFile(structure)
.then(function(glossary) {
if (!glossary) return [];
return that.findFile(structure)
.then(function(glossary) {
if (!glossary) return [];
// Remove the glossary from the list of files to parse
// Remove the glossary from the list of files to parse
that.glossaryFile = glossary.path;
that._defaultsStructure(that.glossaryFile);
that.files = _.without(that.files, that.glossaryFile);
that.files = _.without(that.files, that.glossaryFile);
that.log.debug.ln("glossary located at", that.glossaryFile);
return that.template.renderFile(that.glossaryFile)
.then(function(content) {
return glossary.parser.glossary(content);
});
})
.then(function(glossary) {
that.glossary = glossary;
});
that.log.debug.ln("glossary located at", that.glossaryFile);
return that.template.renderFile(that.glossaryFile)
.then(function(content) {
return glossary.parser.glossary(content);
});
})
.then(function(glossary) {
that.glossary = glossary;
});
};
// Parse a page
Book.prototype.parsePage = function(filename, options) {
var that = this, page = {};
var that = this, page = {};
options = _.defaults(options || {}, {
// Transform svg images
convertImages: false,
// Transform svg images
convertImages: false,
// Interpolate before templating
interpolateTemplate: _.identity,
@@ -475,12 +484,12 @@ Book.prototype.parsePage = function(filename, options) {
});
};
that.log.debug.ln("start parsing file", filename);
that.log.debug.ln("start parsing file", filename);
var extension = path.extname(filename);
var filetype = parsers.get(extension);
var extension = path.extname(filename);
var filetype = parsers.get(extension);
if (!filetype) return Q.reject(new Error("Can't parse file: "+filename));
if (!filetype) return Q.reject(new Error("Can't parse file: "+filename));
// Type of parser used
page.type = filetype.name;
@@ -494,7 +503,7 @@ Book.prototype.parsePage = function(filename, options) {
// Progress in the book
page.progress = parseProgress(that.navigation, filename);
that.log.debug.ln("render template", filename);
that.log.debug.ln("render template", filename);
// Read file content
return that.readFile(page.path)
@@ -504,12 +513,20 @@ Book.prototype.parsePage = function(filename, options) {
return interpolate(options.interpolateTemplate);
})
// Prepare page markup
.then(function() {
return filetype.page.prepare(page.content)
.then(function(content) {
page.content = content;
});
})
// Generate template
.then(function() {
return that.template.renderPage(page);
})
// Parse markup
// Prepare and Parse markup
.then(function(content) {
page.content = content;
@@ -519,88 +536,88 @@ Book.prototype.parsePage = function(filename, options) {
// Post process sections
.then(function(_page) {
return _.reduce(_page.sections, function(prev, section) {
return prev.then(function(_sections) {
return that.template.postProcess(section.content || "")
.then(function(content) {
section.content = content;
return _sections.concat([section]);
})
});
}, Q([]));
return _.reduce(_page.sections, function(prev, section) {
return prev.then(function(_sections) {
return that.template.postProcess(section.content || "")
.then(function(content) {
section.content = content;
return _sections.concat([section]);
})
});
}, Q([]));
})
// Prepare html
.then(function(_sections) {
return pageUtil.normalize(_sections, {
book: that,
convertImages: options.convertImages,
input: filename,
.then(function(_sections) {
return pageUtil.normalize(_sections, {
book: that,
convertImages: options.convertImages,
input: filename,
navigation: that.navigation,
base: path.dirname(filename) || './',
output: path.dirname(filename) || './',
glossary: that.glossary
});
})
})
// Interpolate output
.then(function(_sections) {
page.sections = _sections;
return interpolate(options.interpolateContent);
})
// Interpolate output
.then(function(_sections) {
page.sections = _sections;
return interpolate(options.interpolateContent);
})
.then(function() {
that.indexPage(page);
return page;
});
.then(function() {
that.indexPage(page);
return page;
});
};
// Find file that can be parsed with a specific filename
Book.prototype.findFile = function(filename) {
var that = this;
var that = this;
return _.reduce(parsers.extensions, function(prev, ext) {
return prev.then(function(output) {
// Stop if already find a parser
if (output) return output;
return _.reduce(parsers.extensions, function(prev, ext) {
return prev.then(function(output) {
// Stop if already find a parser
if (output) return output;
var filepath = filename+ext;
var filepath = filename+ext;
return that.fileExists(filepath)
.then(function(exists) {
if (!exists) return null;
return {
parser: parsers.get(ext),
path: filepath
};
})
});
}, Q(null));
return that.fileExists(filepath)
.then(function(exists) {
if (!exists) return null;
return {
parser: parsers.get(ext),
path: filepath
};
})
});
}, Q(null));
};
// Check if a file exists in the book
Book.prototype.fileExists = function(filename) {
return fs.exists(
path.join(this.root, filename)
);
return fs.exists(
path.join(this.root, filename)
);
};
// Read a file
Book.prototype.readFile = function(filename) {
return fs.readFile(
path.join(this.root, filename),
{ encoding: "utf8" }
);
return fs.readFile(
path.join(this.root, filename),
{ encoding: "utf8" }
);
};
// Return stat for a file
Book.prototype.statFile = function(filename) {
return fs.stat(path.join(this.root, filename));
return fs.stat(path.join(this.root, filename));
};
// List all files in the book
Book.prototype.listAllFiles = function() {
var that = this;
var that = this;
return fs.list(this.root, {
ignoreFiles: ['.ignore', '.gitignore', '.bookignore'],
@@ -634,18 +651,18 @@ Book.prototype.listAllFiles = function() {
// Return true if the book is a multilingual book
Book.prototype.isMultilingual = function(filename) {
return this.books.length > 0;
return this.books.length > 0;
};
// Return root of the parent
Book.prototype.parentRoot = function() {
if (this.parent) return this.parent.parentRoot();
return this.root;
if (this.parent) return this.parent.parentRoot();
return this.root;
};
// Return true if it's a sub-book
Book.prototype.isSubBook = function() {
return !!this.parent;
return !!this.parent;
};
// Test if the file is the entry point
@@ -661,7 +678,7 @@ Book.prototype.resolve = function(p) {
// Normalize a link to .html and convert README -> index
Book.prototype.contentLink = function(link) {
if (
path.basename(link) == "README"
path.basename(link, path.extname(link)) == "README"
|| link == this.readmeFile
) {
link = path.join(path.dirname(link), "index"+path.extname(link));
@@ -673,11 +690,11 @@ Book.prototype.contentLink = function(link) {
// Index a page into the search index
Book.prototype.indexPage = function(page) {
var nav = this.navigation[page.path];
if (!nav) return;
var nav = this.navigation[page.path];
if (!nav) return;
this.log.debug.ln("index page", page.path);
this.searchIndex.add({
this.log.debug.ln("index page", page.path);
this.searchIndex.add({
url: this.contentLink(page.path),
title: nav.title,
body: pageUtil.extractText(page.sections),
@@ -697,12 +714,12 @@ Book.prototype._defaultsStructure = function(filename) {
// Change output path
Book.prototype.setOutput = function(p) {
var that = this;
this.options.output = path.resolve(p);
var that = this;
this.options.output = path.resolve(p);
_.each(this.books, function(book) {
book.setOutput(path.join(that.options.output, book.options.language));
});
_.each(this.books, function(book) {
book.setOutput(path.join(that.options.output, book.options.language));
});
};
+20 -12
View File
@@ -64,10 +64,10 @@ function normalizePluginsNames(plugins) {
var Configuration = function(book, options) {
var that = this;
this.book = book;
this.book = book;
this.options = _.cloneDeep(Configuration.DEFAULT);
this.options = _.merge(this.options, options || {});
this.options = _.merge(this.options, options || {});
// options.input == book.root
Object.defineProperty(this.options, "input", {
@@ -79,15 +79,15 @@ var Configuration = function(book, options) {
// Read and parse the configuration
Configuration.prototype.load = function() {
var that = this;
var that = this;
return Q()
return Q()
.then(function() {
try {
var _config = require(path.resolve(that.book.root, that.options.configFile));
that.options = _.merge(
that.options,
_.omit(_config, 'configFile', 'defaultsPlugins', 'generator', 'extension')
that.options,
_.omit(_config, 'configFile', 'defaultsPlugins', 'generator', 'extension')
);
}
catch(err) {
@@ -96,17 +96,23 @@ Configuration.prototype.load = function() {
}
})
.then(function() {
if (!semver.satisfies(pkg.version, that.options.gitbook)) {
throw "GitBook version doesn't satisfy version required by the book: "+that.options.gitbook;
}
if (that.options.gitbook == "*") {
that.book.log.warn.ln("you should specify a gitbook version to use in your book.json, for example: "+(_.first(pkg.version.split("."))+".x.x"));
}
that.options.output = path.resolve(that.options.output || path.join(that.book.root, "_book"));
that.options.plugins = normalizePluginsList(that.options.plugins);
that.options.defaultsPlugins = normalizePluginsList(that.options.defaultsPlugins || "");
that.options.plugins = _.union(that.options.plugins, that.options.defaultsPlugins);
that.options.plugins = _.uniq(that.options.plugins, "name");
if (!semver.satisfies(pkg.version, that.options.gitbook)) {
throw "GitBook version doesn't satisfy version required by the book: "+that.options.gitbook;
}
if (that.options.gitbook == "*") {
that.book.log.warn.ln("you should specify a gitbook version to use in your book.json, for example: "+(_.first(pkg.version.split("."))+".x.x"));
// Default value for text direction (from language)
if (!that.options.direction) {
var lang = i18n.getByLanguage(that.options.language);
if (lang) that.options.direction = lang.direction;
}
that.options.gitbook = pkg.version;
@@ -133,7 +139,7 @@ Configuration.prototype.normalizeLanguage = function() {
Configuration.DEFAULT = {
// Options that can't be extend
"configFile": "book",
"generator": "site",
"generator": "website",
"extension": null,
// Book metadats (somes are extracted from the README by default)
@@ -141,6 +147,8 @@ Configuration.DEFAULT = {
"description": null,
"isbn": null,
"language": "en",
"direction": null,
"author": null,
// version of gitbook to use
"gitbook": "*",
+12 -5
View File
@@ -30,7 +30,7 @@ Generator.prototype.prepareTemplates = function() {
this.templates["summary"] = this.book.plugins.template("ebook:summary") || path.resolve(this.options.theme, 'templates/ebook/summary.html');
this.templates["glossary"] = this.book.plugins.template("ebook:glossary") || path.resolve(this.options.theme, 'templates/ebook/glossary.html');
return Q();
return Q();
};
// Generate table of contents
@@ -44,10 +44,11 @@ Generator.prototype.writeSummary = function() {
Generator.prototype.finish = function() {
var that = this;
return BaseGenerator.prototype.finish.apply(this)
.then(function() {
return that.writeSummary();
})
return Q()
.then(this.copyAssets)
.then(this.copyCover)
.then(this.writeGlossary)
.then(this.writeSummary)
.then(function() {
if (!that.ebookFormat) return Q();
@@ -63,6 +64,8 @@ Generator.prototype.finish = function() {
"--comments": that.options.description,
"--isbn": that.options.isbn,
"--authors": that.options.author,
"--language": that.options.language,
"--book-producer": "GitBook",
"--publisher": "GitBook",
"--chapter": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter ')]",
"--chapter-mark": "pagebreak",
@@ -90,6 +93,10 @@ Generator.prototype.finish = function() {
"--pdf-header-template": String(pdfOptions.headerTemplate) || "<p class='header'><span>"+that.options.title+"</span></p>",
"--pdf-footer-template": String(pdfOptions.footerTemplate) || "<p class='footer'><span>_SECTION_</span> <span style='float:right;'>_PAGENUM_</span></p>"
});
} else if (that.ebookFormat == "epub") {
_.extend(_options, {
"--dont-split-on-page-breaks": true
});
}
var command = [
+6 -6
View File
@@ -2,10 +2,10 @@ var _ = require("lodash");
var EbookGenerator = require("./ebook");
module.exports = {
json: require("./json"),
website: require("./website"),
ebook: EbookGenerator,
pdf: _.partialRight(EbookGenerator, "pdf"),
mobi: _.partialRight(EbookGenerator, "mobi"),
epub: _.partialRight(EbookGenerator, "epub")
json: require("./json"),
website: require("./website"),
ebook: EbookGenerator,
pdf: _.partialRight(EbookGenerator, "pdf"),
mobi: _.partialRight(EbookGenerator, "mobi"),
epub: _.partialRight(EbookGenerator, "epub")
};
+19 -19
View File
@@ -64,11 +64,11 @@ Generator.prototype.prepareStyles = function() {
// Prepare templates
Generator.prototype.prepareTemplates = function() {
this.templates["page"] = this.book.plugins.template("site:page") || path.resolve(this.options.theme, 'templates/website/page.html');
this.templates["page"] = this.book.plugins.template("site:page") || path.resolve(this.options.theme, 'templates/website/page.html');
this.templates["langs"] = this.book.plugins.template("site:langs") || path.resolve(this.options.theme, 'templates/website/langs.html');
this.templates["glossary"] = this.book.plugins.template("site:glossary") || path.resolve(this.options.theme, 'templates/website/glossary.html');
return Q();
return Q();
};
// Prepare template engine
@@ -142,7 +142,7 @@ Generator.prototype.convertFile = function(input) {
var basePath = path.relative(path.dirname(output), that.options.output) || ".";
if (process.platform === 'win32') basePath = basePath.replace(/\\/g, '/');
that.book.log.info.ln("write parsed file", page.path, "to", relativeOutput);
that.book.log.debug.ln("write parsed file", page.path, "to", relativeOutput);
return that._writeTemplate(that.templates["page"], {
progress: page.progress,
@@ -213,38 +213,38 @@ Generator.prototype._writeTemplate = function(tpl, options, output, interpolate)
interpolate = interpolate || _.identity;
return Q()
.then(function(sections) {
return that.env.render(
tpl,
_.extend({
return that.env.render(
tpl,
_.extend({
gitbook: {
version: pkg.version
},
styles: that.styles,
styles: that.styles,
revision: that.revision,
revision: that.revision,
title: that.options.title,
description: that.options.description,
title: that.options.title,
description: that.options.description,
language: that.book.config.normalizeLanguage(),
glossary: that.book.glossary,
glossary: that.book.glossary,
summary: that.book.summary,
allNavigation: that.book.navigation,
summary: that.book.summary,
allNavigation: that.book.navigation,
plugins: {
plugins: {
resources: that.book.plugins.resources(that.namespace)
},
pluginsConfig: JSON.stringify(that.options.pluginsConfig),
htmlSnippet: _.partial(_.partialRight(that.book.plugins.html, that, options), that.namespace),
pluginsConfig: JSON.stringify(that.options.pluginsConfig),
htmlSnippet: _.partial(_.partialRight(that.book.plugins.html, that, options), that.namespace),
options: that.options,
options: that.options,
basePath: ".",
staticBase: path.join(".", "gitbook"),
}, options)
);
}, options)
);
})
.then(interpolate)
.then(function(html) {
+170 -170
View File
@@ -12,199 +12,199 @@ var watch = require("./utils/watch");
var logger = require("./utils/logger");
var LOG_OPTION = {
name: "log",
description: "Minimum log level to display",
values: _.chain(logger.LEVELS).keys().map(stringUtils.toLowerCase).value(),
defaults: "info"
name: "log",
description: "Minimum log level to display",
values: _.chain(logger.LEVELS).keys().map(stringUtils.toLowerCase).value(),
defaults: "info"
};
var FORMAT_OPTION = {
name: "format",
description: "Format to build to",
values: ["website", "json", "ebook"],
defaults: "website"
name: "format",
description: "Format to build to",
values: ["website", "json", "ebook"],
defaults: "website"
};
module.exports = {
Book: Book,
LOG_LEVELS: logger.LEVELS,
Book: Book,
LOG_LEVELS: logger.LEVELS,
commands: _.flatten([
{
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");
commands: _.flatten([
{
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");
var book = new Book(input, _.extend({}, {
'config': {
'output': output
},
'logLevel': kwargs.log
}));
var book = new Book(input, _.extend({}, {
'config': {
'output': output
},
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generate(kwargs.format);
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
return book.parse()
.then(function() {
return book.generate(kwargs.format);
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
_.map(["pdf", "epub", "mobi"], function(ebookType) {
return {
name: ebookType+" [book] [output]",
description: "build a book to "+ebookType,
options: [
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var output = args[1];
_.map(["pdf", "epub", "mobi"], function(ebookType) {
return {
name: ebookType+" [book] [output]",
description: "build a book to "+ebookType,
options: [
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var output = args[1];
var book = new Book(input, _.extend({}, {
'logLevel': kwargs.log
}));
var book = new Book(input, _.extend({}, {
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generateFile(output, {
ebookFormat: ebookType
});
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
};
}),
return book.parse()
.then(function() {
return book.generateFile(output, {
ebookFormat: ebookType
});
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
};
}),
{
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();
{
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;
// Init livereload server
var lrServer = tinylr({});
var lrPath = undefined;
var generate = function() {
if (server.isRunning()) console.log("Stopping server");
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': kwargs.log
}));
return server.stop()
.then(function() {
var book = new Book(input, _.extend({}, {
'config': {
'defaultsPlugins': ["livereload"]
},
'logLevel': kwargs.log
}));
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);
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 (lrPath) {
// trigger livereload
lrServer.changed({
body: {
files: [lrPath]
}
});
}
if (!kwargs.watch) return;
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 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();
});
}
},
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();
});
}
},
{
name: "install [book]",
description: "install plugins dependencies",
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
{
name: "install [book]",
description: "install plugins dependencies",
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var book = new Book(input);
var book = new Book(input);
return book.config.load()
.then(function() {
return book.plugins.install();
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
return book.config.load()
.then(function() {
return book.plugins.install();
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
{
name: "init [directory]",
description: "create files and folders based on contents of SUMMARY.md",
exec: function(args, kwargs) {
return Book.init(args[0] || process.cwd())
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
}
])
{
name: "init [directory]",
description: "create files and folders based on contents of SUMMARY.md",
exec: function(args, kwargs) {
return Book.init(args[0] || process.cwd())
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
}
])
};
+114 -114
View File
@@ -8,113 +8,113 @@ var Plugin = require("./plugin");
var pkg = require("../package.json");
var initNPM = _.memoize(function() {
return Q.nfcall(npm.load, { silent: true, loglevel: 'silent' });
return Q.nfcall(npm.load, { silent: true, loglevel: 'silent' });
});
var PluginsList = function(book, plugins) {
this.book = book;
this.log = this.book.log;
this.book = book;
this.log = this.book.log;
// List of Plugin objects
this.list = [];
// List of Plugin objects
this.list = [];
// List of names of failed plugins
this.failed = [];
// List of names of failed plugins
this.failed = [];
// Namespaces
this.namespaces = _.chain(["website", "ebook"])
.map(function(namespace) {
return [
namespace,
{
html: {},
resources: _.chain(Plugin.RESOURCES)
.map(function(type) {
return [type, []];
})
.object()
.value()
}
];
})
.object()
.value();
// Namespaces
this.namespaces = _.chain(["website", "ebook"])
.map(function(namespace) {
return [
namespace,
{
html: {},
resources: _.chain(Plugin.RESOURCES)
.map(function(type) {
return [type, []];
})
.object()
.value()
}
];
})
.object()
.value();
// Bind methods
_.bindAll(this);
// Bind methods
_.bindAll(this);
if (plugins) this.load(plugins);
if (plugins) this.load(plugins);
};
// return count of plugins
PluginsList.prototype.count = function() {
return this.list.length;
return this.list.length;
};
// Add and load a plugin
PluginsList.prototype.load = function(plugin, options) {
var that = this;
var that = this;
if (_.isArray(plugin)) {
return _.reduce(plugin, function(prev, p) {
return prev.then(function() {
return that.load(p);
});
}, Q());
}
if (_.isObject(plugin) && !(plugin instanceof Plugin)) plugin = plugin.name;
if (_.isString(plugin)) plugin = new Plugin(this.book, plugin);
if (_.isArray(plugin)) {
return _.reduce(plugin, function(prev, p) {
return prev.then(function() {
return that.load(p);
});
}, Q());
}
if (_.isObject(plugin) && !(plugin instanceof Plugin)) plugin = plugin.name;
if (_.isString(plugin)) plugin = new Plugin(this.book, plugin);
that.log.info("load plugin", plugin.name, "....");
if (!plugin.isValid()) {
that.log.info.fail();
that.failed.push(plugin.name);
return Q();
that.log.info("load plugin", plugin.name, "....");
if (!plugin.isValid()) {
that.log.info.fail();
that.failed.push(plugin.name);
return Q();
} else {
that.log.info.ok();
that.log.info.ok();
// Push in the list
that.list.push(plugin);
// Push in the list
that.list.push(plugin);
}
// Extract filters
_.each(plugin.getFilters(), function(filterFunc, filterName) {
// Extract filters
_.each(plugin.getFilters(), function(filterFunc, filterName) {
that.book.template.addFilter(filterName, filterFunc);
});
// Extract blocks
_.each(plugin.getBlocks(), function(block, blockName) {
_.each(plugin.getBlocks(), function(block, blockName) {
that.book.template.addBlock(blockName, block);
});
return _.reduce(_.keys(that.namespaces), function(prev, namespaceName) {
return prev.then(function() {
return plugin.getResources(namespaceName)
.then(function(plResources) {
var namespace = that.namespaces[namespaceName];
return _.reduce(_.keys(that.namespaces), function(prev, namespaceName) {
return prev.then(function() {
return plugin.getResources(namespaceName)
.then(function(plResources) {
var namespace = that.namespaces[namespaceName];
// Extract js and css
_.each(Plugin.RESOURCES, function(resourceType) {
namespace.resources[resourceType] = (namespace.resources[resourceType] || []).concat(plResources[resourceType] || []);
});
// Extract js and css
_.each(Plugin.RESOURCES, function(resourceType) {
namespace.resources[resourceType] = (namespace.resources[resourceType] || []).concat(plResources[resourceType] || []);
});
// Map of html resources by name added by each plugin
_.each(plResources.html || {}, function(value, tag) {
// Turn into function if not one already
if (!_.isFunction(value)) value = _.constant(value);
// Map of html resources by name added by each plugin
_.each(plResources.html || {}, function(value, tag) {
// Turn into function if not one already
if (!_.isFunction(value)) value = _.constant(value);
namespace.html[tag] = namespace.html[tag] || [];
namespace.html[tag].push(value);
});
})
});
}, Q());
namespace.html[tag] = namespace.html[tag] || [];
namespace.html[tag].push(value);
});
})
});
}, Q());
};
// Call a hook
PluginsList.prototype.hook = function(name, data) {
return _.reduce(this.list, function(prev, plugin) {
return _.reduce(this.list, function(prev, plugin) {
return prev.then(function(ret) {
return plugin.callHook(name, ret);
});
@@ -123,7 +123,7 @@ PluginsList.prototype.hook = function(name, data) {
// Return a template from a plugin
PluginsList.prototype.template = function(name) {
var withTpl = _.find(this.list, function(plugin) {
var withTpl = _.find(this.list, function(plugin) {
return (
plugin.infos.templates &&
plugin.infos.templates[name]
@@ -136,7 +136,7 @@ PluginsList.prototype.template = function(name) {
// Return an html snippet
PluginsList.prototype.html = function(namespace, tag, context, options) {
var htmlSnippets = this.namespaces[namespace].html[tag];
var htmlSnippets = this.namespaces[namespace].html[tag];
return _.map(htmlSnippets || [], function(code) {
return code.call(context, options);
}).join("\n");
@@ -144,13 +144,13 @@ PluginsList.prototype.html = function(namespace, tag, context, options) {
// Return a resources map for a namespace
PluginsList.prototype.resources = function(namespace) {
return this.namespaces[namespace].resources;
return this.namespaces[namespace].resources;
};
// Install plugins from a book
PluginsList.prototype.install = function() {
var that = this;
var defaultsPlugins = _.pluck(that.book.options.defaultsPlugins)
var that = this;
var defaultsPlugins = _.pluck(that.book.options.defaultsPlugins)
// Remove defaults (no need to install)
var plugins = _.filter(that.book.options.plugins, function(plugin) {
@@ -167,51 +167,51 @@ PluginsList.prototype.install = function() {
// Resolve version if needed
.then(function() {
if (plugin.version) return plugin.version;
if (plugin.version) return plugin.version;
that.book.log.info.ln("No version specified, resolve plugin", plugin.name);
return initNPM()
.then(function() {
return Q.nfcall(npm.commands.view, [fullname+"@*", "engines"], true);
})
.then(function(versions) {
return _.chain(versions)
.pairs()
.map(function(v) {
return {
version: v[0],
gitbook: (v[1].engines || {})["gitbook"]
}
})
.filter(function(v) {
return v.gitbook && semver.satisfies(pkg.version, v.gitbook);
})
.sort(function(v1, v2) {
return semver.lt(v1.version, v2.version)? 1 : -1;
})
.pluck("version")
.first()
.value();
});
that.book.log.info.ln("No version specified, resolve plugin", plugin.name);
return initNPM()
.then(function() {
return Q.nfcall(npm.commands.view, [fullname+"@*", "engines"], true);
})
.then(function(versions) {
return _.chain(versions)
.pairs()
.map(function(v) {
return {
version: v[0],
gitbook: (v[1].engines || {})["gitbook"]
}
})
.filter(function(v) {
return v.gitbook && semver.satisfies(pkg.version, v.gitbook);
})
.sort(function(v1, v2) {
return semver.lt(v1.version, v2.version)? 1 : -1;
})
.pluck("version")
.first()
.value();
});
})
// Install the plugin with the resolved version
.then(function(version) {
if (!version) {
throw "Found no satisfactory version for plugin "+plugin.name;
}
if (!version) {
throw "Found no satisfactory version for plugin "+plugin.name;
}
that.book.log.info.ln("install plugin", plugin.name, "from npm ("+fullname+") with version", version);
return Q.nfcall(npmi, {
'name': fullname,
'version': version,
'path': that.book.root,
'npmLoad': {
'loglevel': 'silent',
'loaded': true,
'prefix': that.book.root
}
});
that.book.log.info.ln("install plugin", plugin.name, "from npm ("+fullname+") with version", version);
return Q.nfcall(npmi, {
'name': fullname,
'version': version,
'path': that.book.root,
'npmLoad': {
'loglevel': 'silent',
'loaded': true,
'prefix': that.book.root
}
});
})
.then(function() {
that.book.log.info.ok("plugin", plugin.name, "installed with success");
+81 -57
View File
@@ -6,36 +6,37 @@ var nunjucks = require("nunjucks");
var git = require("./utils/git");
var stringUtils = require("./utils/string");
var fs = require("./utils/fs");
var batch = require("./utils/batch");
var pkg = require("../package.json");
// The loader should handle relative and git url
var BookLoader = nunjucks.Loader.extend({
async: true,
async: true,
init: function(book) {
this.book = book;
this.book = book;
},
getSource: function(fileurl, callback) {
var that = this;
var that = this;
git.resolveFile(fileurl)
.then(function(filepath) {
// Is local file
if (!filepath) filepath = path.resolve(that.book.root, fileurl);
else that.book.log.debug.ln("resolve from git", fileurl, "to", filepath)
git.resolveFile(fileurl)
.then(function(filepath) {
// Is local file
if (!filepath) filepath = path.resolve(that.book.root, fileurl);
else that.book.log.debug.ln("resolve from git", fileurl, "to", filepath)
// Read file from absolute path
return fs.readFile(filepath)
.then(function(source) {
return {
src: source.toString(),
path: filepath
}
});
})
.nodeify(callback);
// Read file from absolute path
return fs.readFile(filepath)
.then(function(source) {
return {
src: source.toString(),
path: filepath
}
});
})
.nodeify(callback);
},
resolve: function(from, to) {
@@ -45,27 +46,27 @@ var BookLoader = nunjucks.Loader.extend({
var TemplateEngine = function(book) {
this.book = book;
this.book = book;
this.log = this.book.log;
// Nunjucks env
this.env = new nunjucks.Environment(
new BookLoader(book),
{
// Escaping is done after by the markdown parser
autoescape: false,
// Nunjucks env
this.env = new nunjucks.Environment(
new BookLoader(book),
{
// Escaping is done after by the markdown parser
autoescape: false,
// Tags
tags: {
blockStart: '{%',
blockEnd: '%}',
variableStart: '{{',
variableEnd: '}}',
commentStart: '{###',
commentEnd: '###}'
}
}
);
// Tags
tags: {
blockStart: '{%',
blockEnd: '%}',
variableStart: '{{',
variableEnd: '}}',
commentStart: '{###',
commentEnd: '###}'
}
}
);
// List of tags shortcuts
this.shortcuts = [];
@@ -87,18 +88,21 @@ TemplateEngine.prototype.processBlock = function(blk) {
if (_.isString(blk)) blk = { body: blk };
blk = _.defaults(blk, {
parse: false
parse: false,
post: undefined
});
blk.id = _.uniqueId("blk");
var toAdd = (!blk.parse) || (blk.post != undefined);
// Add to global map
if (toAdd) this.blocks[blk.id] = blk;
//Parsable block, just return it
if (blk.parse) {
return blk.body;
}
// Add to global map
this.blocks[blk.id] = blk;
// Return it as a macro
return "%+%"+blk.id+"%+%";
};
@@ -114,9 +118,6 @@ TemplateEngine.prototype.replaceBlocks = function(content) {
var body = blk.body;
// Remove it from map
delete that.blocks[key];
return body;
});
};
@@ -239,21 +240,25 @@ TemplateEngine.prototype.addBlock = function(name, block) {
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
// Extract blocks body
var _blocks = _.chain(block.blocks)
.reverse()
.map(function(blockName){
return {
name: blockName,
body: args.pop()()
};
})
.reverse()
.value();
// Extract blocks
var blocks = args
.concat([])
.slice(-block.blocks.length);
// Eliminate blocks from list
if (block.blocks.length > 0) args = args.slice(0, -block.blocks.length);
// Extract main body and kwargs
var body = args.pop();
var kwargs = args.pop() || {};
// Extract blocks body
var _blocks = _.map(block.blocks, function(blockName, i){
return {
name: blockName,
body: blocks[i]()
};
});
var func = that.bindContext(block.process);
@@ -313,7 +318,8 @@ TemplateEngine.prototype.renderString = function(content, context, options) {
// infos about gitbook
gitbook: {
version: pkg.version
version: pkg.version,
generator: this.book.options.generator
}
});
options = _.defaults(options || {}, {
@@ -330,7 +336,7 @@ TemplateEngine.prototype.renderString = function(content, context, options) {
// Render a file from the book
TemplateEngine.prototype.renderFile = function(filename, options) {
var that = this, context;
var that = this, context;
return that.book.readFile(filename)
.then(function(content) {
@@ -363,8 +369,26 @@ TemplateEngine.prototype.renderPage = function(page) {
// Post process content
TemplateEngine.prototype.postProcess = function(content) {
var that = this;
return Q(content)
.then(this.replaceBlocks);
.then(that.replaceBlocks)
.then(function(_content) {
return batch.execEach(that.blocks, {
max: 20,
fn: function(blk, blkId) {
return Q()
.then(function() {
if (!blk.post) return Q();
return blk.post();
})
.then(function() {
delete that.blocks[blkId];
});
}
})
.thenResolve(_content);
});
};
module.exports = TemplateEngine;
+52
View File
@@ -0,0 +1,52 @@
var Q = require("q");
var _ = require("lodash");
// Execute a method for all element
function execEach(items, options) {
if (_.size(items) == 0) return Q();
var concurrents = 0, d = Q.defer(), pending = [];
options = _.defaults(options || {}, {
max: 100,
fn: function(item) {}
});
function startItem(item, i) {
if (concurrents >= options.max) {
pending.push([item, i]);
return;
}
concurrents++;
Q()
.then(function() {
return options.fn(item, i);
})
.then(function() {
concurrents--;
// Next pending
var next = pending.shift();
if (concurrents == 0 && !next) {
d.resolve();
} else if (next) {
startItem.apply(null, next);
}
})
.fail(function(err) {
pending = [];
d.reject(err);
})
}
_.each(items, startItem);
return d.promise;
}
module.exports = {
execEach: execEach
};
+36
View File
@@ -0,0 +1,36 @@
var hljs = require('highlight.js');
var MAP = {
'py': 'python',
'js': 'javascript',
'json': 'javascript',
'rb': 'ruby',
'csharp': 'cs',
};
function normalize(lang) {
if(!lang) { return null; }
var lower = lang.toLowerCase();
return MAP[lower] || lower;
}
function highlight(lang, code) {
if(!lang) return code;
// Normalize lang
lang = normalize(lang);
try {
return hljs.highlight(lang, code).value;
} catch(e) { }
return code;
}
// Exports
module.exports = {
highlight: highlight,
normalize: normalize,
MAP: MAP
};
+58 -58
View File
@@ -15,7 +15,7 @@ var GIT_TMP = null;
// Check if an url is a git dependency url
function checkGitUrl(giturl) {
return (giturl.indexOf(GIT_PREFIX) === 0);
return (giturl.indexOf(GIT_PREFIX) === 0);
}
// Validates a SHA in hexadecimal
@@ -25,88 +25,88 @@ function validateSha(str) {
// Parse and extract infos
function parseGitUrl(giturl) {
var ref, uri, fileParts, filepath;
var ref, uri, fileParts, filepath;
if (!checkGitUrl(giturl)) return null;
giturl = giturl.slice(GIT_PREFIX.length);
if (!checkGitUrl(giturl)) return null;
giturl = giturl.slice(GIT_PREFIX.length);
uri = new URI(giturl);
ref = uri.fragment() || "master";
uri.fragment(null);
uri = new URI(giturl);
ref = uri.fragment() || "master";
uri.fragment(null);
// Extract file inside the repo (after the .git)
fileParts =uri.path().split(".git");
filepath = fileParts.length > 1? fileParts.slice(1).join(".git") : "";
if (filepath[0] == "/") filepath = filepath.slice(1);
// Extract file inside the repo (after the .git)
fileParts =uri.path().split(".git");
filepath = fileParts.length > 1? fileParts.slice(1).join(".git") : "";
if (filepath[0] == "/") filepath = filepath.slice(1);
// Recreate pathname without the real filename
uri.path(_.first(fileParts)+".git");
// Recreate pathname without the real filename
uri.path(_.first(fileParts)+".git");
return {
host: uri.toString(),
ref: ref || "master",
filepath: filepath
};
return {
host: uri.toString(),
ref: ref || "master",
filepath: filepath
};
}
// Clone a git repo from a specific ref
function cloneGitRepo(host, ref) {
var isBranch = false;
var isBranch = false;
ref = ref || "master";
if (!validateSha(ref)) isBranch = true;
ref = ref || "master";
if (!validateSha(ref)) isBranch = true;
return Q()
return Q()
// Create temporary folder to store git repos
.then(function() {
if (GIT_TMP) return;
return fs.tmp.dir()
.then(function(_tmp) {
GIT_TMP = _tmp;
});
})
// Create temporary folder to store git repos
.then(function() {
if (GIT_TMP) return;
return fs.tmp.dir()
.then(function(_tmp) {
GIT_TMP = _tmp;
});
})
// Return or clone the git repo
.then(function() {
// Unique ID for repo/ref combinaison
var repoId = crc.crc32(host+"#"+ref).toString(16);
// Return or clone the git repo
.then(function() {
// Unique ID for repo/ref combinaison
var repoId = crc.crc32(host+"#"+ref).toString(16);
// Absolute path to the folder
var repoPath = path.resolve(GIT_TMP, repoId);
// Absolute path to the folder
var repoPath = path.resolve(GIT_TMP, repoId);
return fs.exists(repoPath)
.then(function(doExists) {
if (doExists) return;
return fs.exists(repoPath)
.then(function(doExists) {
if (doExists) return;
// Clone repo
return exec("git clone "+host+" "+repoPath)
.then(function() {
return exec("git checkout "+ref, { cwd: repoPath });
})
})
.thenResolve(repoPath);
});
// Clone repo
return exec("git clone "+host+" "+repoPath)
.then(function() {
return exec("git checkout "+ref, { cwd: repoPath });
})
})
.thenResolve(repoPath);
});
}
// Get file from a git repo
function resolveFileFromGit(giturl) {
if (_.isString(giturl)) giturl = parseGitUrl(giturl);
if (!giturl) return Q(null);
if (_.isString(giturl)) giturl = parseGitUrl(giturl);
if (!giturl) return Q(null);
// Clone or get from cache
return cloneGitRepo(giturl.host, giturl.ref)
.then(function(repo) {
// Clone or get from cache
return cloneGitRepo(giturl.host, giturl.ref)
.then(function(repo) {
// Resolve relative path
return path.resolve(repo, giturl.filepath);
});
// Resolve relative path
return path.resolve(repo, giturl.filepath);
});
};
module.exports = {
checkUrl: checkGitUrl,
parseUrl: parseGitUrl,
resolveFile: resolveFileFromGit
checkUrl: checkGitUrl,
parseUrl: parseGitUrl,
resolveFile: resolveFileFromGit
};
+41 -35
View File
@@ -5,12 +5,14 @@ var fs = require("fs");
var I18N_PATH = path.resolve(__dirname, "../../theme/i18n/")
var getLocales = _.memoize(function() {
var locales = fs.readdirSync(I18N_PATH);
return _.chain(locales)
var locales = fs.readdirSync(I18N_PATH);
return _.chain(locales)
.map(function(local) {
local = path.basename(local, ".json");
return [local, _.extend(require(path.join(I18N_PATH, local)), {
id: local
return [local, _.extend({
direction: "ltr"
}, require(path.join(I18N_PATH, local)), {
id: local
})];
})
.object()
@@ -18,49 +20,53 @@ var getLocales = _.memoize(function() {
});
var getLanguages = function() {
return _.keys(getLocales());
return _.keys(getLocales());
};
var getByLanguage = function(lang) {
lang = normalizeLanguage(lang);
var locales = getLocales();
return locales[lang];
lang = normalizeLanguage(lang);
var locales = getLocales();
return locales[lang];
};
var compareLocales = function(lang, locale) {
var langMain = _.first(lang.split("-"));
var localeMain = _.first(locale.split("-"));
var langMain = _.first(lang.split("-"));
var langSecond = _.last(lang.split("-"));
if (locale == lang) return 100;
if (localeMain == langMain) return 50;
return 0;
var localeMain = _.first(locale.split("-"));
var localeSecond = _.last(locale.split("-"));
if (locale == lang) return 100;
if (localeMain == langMain) return 50;
if (localeSecond == langSecond) return 20;
return 0;
};
var normalizeLanguage = _.memoize(function(lang) {
var locales = getLocales();
var language = _.chain(locales)
.values()
.map(function(locale) {
locale = locale.id;
var locales = getLocales();
var language = _.chain(locales)
.values()
.map(function(locale) {
locale = locale.id;
return {
locale: locale,
score: compareLocales(lang, locale)
}
})
.filter(function(lang) {
return lang.score > 0;
})
.sortBy("score")
.pluck("locale")
.last()
.value();
return language || lang;
return {
locale: locale,
score: compareLocales(lang, locale)
}
})
.filter(function(lang) {
return lang.score > 0;
})
.sortBy("score")
.pluck("locale")
.last()
.value();
return language || lang;
});
module.exports = {
getLocales: getLocales,
getLanguages: getLanguages,
getByLanguage: getByLanguage,
normalizeLanguage: normalizeLanguage
getLocales: getLocales,
getLanguages: getLanguages,
getByLanguage: getByLanguage,
normalizeLanguage: normalizeLanguage
};
+17 -16
View File
@@ -1,38 +1,39 @@
var _ = require("lodash");
var Q = require("q");
var fs = require("./fs");
var exec = require('child_process').exec;
var spawn = require('spawn-cmd').spawn;
var links = require("./links");
// Convert a svg file
var convertSVG = function(source, dest, options) {
if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source));
if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source));
var d = Q.defer();
var d = Q.defer();
options = _.defaults(options || {}, {
options = _.defaults(options || {}, {
});
});
//var command = shellescape(['svgexport', source, dest]);
var child = spawn('svgexport', [source, dest]);
var command = "svgexport "+source+" "+dest;
child.on("error", function(error) {
if (error.code == "ENOENT") error = new Error("Need to install 'svgexport' using 'npm install svgexport -g'");
return d.reject(error);
});
var child = exec(command, function (error, stdout, stderr) {
if (error) {
if (error.code == 127) error = new Error("Need to install 'svgexport' using 'npm install svgexport -g'");
return d.reject(error);
}
if (fs.existsSync(dest)) {
child.on("close", function(code) {
if (code == 0 && fs.existsSync(dest)) {
d.resolve();
} else {
d.reject(new Error("Error converting "+source));
d.reject(new Error("Error converting "+source+" into "+dest));
}
});
return d.promise;
return d.promise;
};
module.exports = {
convertSVG: convertSVG,
INVALID: [".svg"]
convertSVG: convertSVG,
INVALID: [".svg"]
};
+21 -8
View File
@@ -15,12 +15,27 @@ var isRelative = function(href) {
try {
var parsed = url.parse(href);
return !!(!parsed.protocol && parsed.path);// && parsed.path[0] != '/';
return !!(!parsed.protocol && parsed.path);
} catch(err) {}
return true;
};
// Return true if the link is an achor
var isAnchor = function(href) {
try {
var parsed = url.parse(href);
return !!(!parsed.protocol && !parsed.path && parsed.hash);
} catch(err) {}
return false;
};
// Normalize a path to be a link
var normalizeLink = function(s) {
return s.replace(/\\/g, '/');
};
// Relative to absolute path
// dir: directory parent of the file currently in rendering process
// outdir: directory parent from the html output
@@ -36,7 +51,7 @@ var toAbsolute = function(_href, dir, outdir) {
_href = path.relative(outdir, hrefInRoot);
// Normalize windows paths
_href = _href.replace(/\\/g, '/');
_href = normalizeLink(_href);
return _href;
};
@@ -45,11 +60,7 @@ var toAbsolute = function(_href, dir, outdir) {
var join = function() {
var _href = path.join.apply(path, arguments);
if (process.platform === 'win32') {
_href = _href.replace(/\\/g, '/');
}
return _href;
return normalizeLink(_href);
};
// Change extension
@@ -61,9 +72,11 @@ var changeExtension = function(filename, newext) {
};
module.exports = {
isAnchor: isAnchor,
isRelative: isRelative,
isExternal: isExternal,
toAbsolute: toAbsolute,
join: join,
changeExtension: changeExtension
changeExtension: changeExtension,
normalize: normalizeLink
};
+75 -75
View File
@@ -3,100 +3,100 @@ var util = require('util');
var color = require('bash-color');
var LEVELS = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
DISABLED: 10
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
DISABLED: 10
};
var COLORS = {
DEBUG: color.purple,
INFO: color.cyan,
WARN: color.yellow,
ERROR: color.red
DEBUG: color.purple,
INFO: color.cyan,
WARN: color.yellow,
ERROR: color.red
};
module.exports = function(_write, logLevel) {
var logger = {};
var lastChar = '\n';
if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()];
var logger = {};
var lastChar = '\n';
if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()];
// Write a simple message
logger.write = function(msg) {
msg = msg.toString();
lastChar = _.last(msg);
return _write(msg);
};
// Write a simple message
logger.write = function(msg) {
msg = msg.toString();
lastChar = _.last(msg);
return _write(msg);
};
// Format a message
logger.format = function() {
return util.format.apply(util, arguments);
};
// Format a message
logger.format = function() {
return util.format.apply(util, arguments);
};
// Write a line
logger.writeLn = function(msg) {
return this.write((msg || "")+"\n");
};
// Write a line
logger.writeLn = function(msg) {
return this.write((msg || "")+"\n");
};
// Write a message with a certain level
logger.log = function(level) {
if (level < logLevel) return;
// Write a message with a certain level
logger.log = function(level) {
if (level < logLevel) return;
var levelKey = _.findKey(LEVELS, function(v) { return v == level; });
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
var levelKey = _.findKey(LEVELS, function(v) { return v == level; });
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
if (lastChar == '\n') {
msg = COLORS[levelKey](levelKey.toLowerCase()+":")+" "+msg;
}
if (lastChar == '\n') {
msg = COLORS[levelKey](levelKey.toLowerCase()+":")+" "+msg;
}
return logger.write(msg);
};
logger.logLn = function() {
if (lastChar != '\n') logger.write("\n");
return logger.write(msg);
};
logger.logLn = function() {
if (lastChar != '\n') logger.write("\n");
var args = Array.prototype.slice.apply(arguments);
args.push("\n");
logger.log.apply(logger, args);
};
var args = Array.prototype.slice.apply(arguments);
args.push("\n");
logger.log.apply(logger, args);
};
// Write a OK
logger.ok = function(level) {
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
if (arguments.length > 1) {
logger.logLn(level, color.green('>> ') + msg.trim().replace(/\n/g, color.green('\n>> ')));
} else {
logger.log(level, color.green("OK"), "\n");
}
};
// Write a OK
logger.ok = function(level) {
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
if (arguments.length > 1) {
logger.logLn(level, color.green('>> ') + msg.trim().replace(/\n/g, color.green('\n>> ')));
} else {
logger.log(level, color.green("OK"), "\n");
}
};
// Write an "FAIL"
logger.fail = function(level) {
return logger.log(level, color.red("ERROR")+"\n");
};
// Write an "FAIL"
logger.fail = function(level) {
return logger.log(level, color.red("ERROR")+"\n");
};
_.each(_.omit(LEVELS, 'DISABLED'), function(level, levelKey) {
levelKey = levelKey.toLowerCase();
_.each(_.omit(LEVELS, 'DISABLED'), function(level, levelKey) {
levelKey = levelKey.toLowerCase();
logger[levelKey] = _.partial(logger.log, level);
logger[levelKey].ln = _.partial(logger.logLn, level);
logger[levelKey].ok = _.partial(logger.ok, level);
logger[levelKey].fail = _.partial(logger.fail, level);
logger[levelKey].promise = function(p) {
return p.
then(function(st) {
logger[levelKey].ok();
return st;
}, function(err) {
logger[levelKey].fail();
throw err;
});
}
});
logger[levelKey] = _.partial(logger.log, level);
logger[levelKey].ln = _.partial(logger.logLn, level);
logger[levelKey].ok = _.partial(logger.ok, level);
logger[levelKey].fail = _.partial(logger.fail, level);
logger[levelKey].promise = function(p) {
return p.
then(function(st) {
logger[levelKey].ok();
return st;
}, function(err) {
logger[levelKey].fail();
throw err;
});
}
});
return logger;
return logger;
};
module.exports.LEVELS = LEVELS;
module.exports.COLORS = COLORS;
+3 -1
View File
@@ -36,7 +36,7 @@ function navigation(summary, files) {
var prev = null, next = null;
// Skip if no path
if(!current.path) return null;
if(!current.exists) return null;
// Find prev
prev = _.chain(navNodes)
@@ -57,7 +57,9 @@ function navigation(summary, files) {
return [current.path, {
index: i,
title: current.title,
introduction: current.introduction,
prev: prev,
next: next,
level: current.level,
+77 -34
View File
@@ -1,5 +1,6 @@
var Q = require('q');
var _ = require('lodash');
var url = require('url');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
@@ -9,6 +10,8 @@ var crc = require("crc");
var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');
var batch = require('./batch');
var code = require('./code');
// Render a cheerio dom as html
var renderDom = function($, dom, options) {
@@ -103,7 +106,7 @@ function normalizeHtml(src, options) {
// Generate filename
dest = "/"+fs.getUniqueFilename(outputRoot, dest);
svgContent[dest] = content;
svgContent[dest] = '<?xml version="1.0" encoding="UTF-8"?>'+content;
$(this).replaceWith($("<img>").attr("src", dest));
});
}
@@ -112,6 +115,7 @@ function normalizeHtml(src, options) {
$("img").each(function() {
var origin = undefined;
var src = $(this).attr("src");
if (!src) return;
var isExternal = links.isExternal(src);
// Transform as relative to the bases
@@ -130,7 +134,7 @@ function normalizeHtml(src, options) {
}
var ext = path.extname(src);
var srcAbs = path.join("/", options.base, src);
var srcAbs = links.join("/", options.base, src);
// Test image extension
if (_.contains(imgUtils.INVALID, ext)) {
@@ -142,7 +146,7 @@ function normalizeHtml(src, options) {
var dest = "";
// Replace extension
dest = path.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
dest = links.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
dest = dest[0] == "/"? dest.slice(1) : dest;
// Get a name that doesn't exists
@@ -161,7 +165,7 @@ function normalizeHtml(src, options) {
dest: path.join("./", dest)
});
src = path.join("/", dest);
src = links.join("/", dest);
}
// Reset as relative to output
@@ -180,18 +184,27 @@ function normalizeHtml(src, options) {
$(this).attr("src", src);
});
// Normalize links
$("a").each(function() {
var href = $(this).attr("href");
if (!href) return;
if (links.isRelative(href)) {
var absolutePath = path.join(options.base, href);
if (links.isAnchor(href)) {
// Keep it as it is
} else if (links.isRelative(href)) {
var parts = url.parse(href);
var absolutePath = path.join(options.base, parts.pathname);
var anchor = parts.hash || "";
// If is in navigation relative: change extsnio nto be .html
if (options.navigation[absolutePath]) href = links.changeExtension(href, ".html");
// If is in navigation relative: transform as content
if (options.navigation[absolutePath]) {
absolutePath = options.book.contentLink(absolutePath);
}
// Transform as absolute
href = links.toAbsolute(href, options.base, options.output);
href = links.toAbsolute("/"+absolutePath, options.base, options.output)+anchor;
} else {
// External links
$(this).attr("target", "_blank");
}
@@ -199,12 +212,39 @@ function normalizeHtml(src, options) {
$(this).attr("href", href);
});
// Replace glossayr terms
// Highlight code blocks
$("code").each(function() {
// Extract language
var lang = _.chain(
($(this).attr("class") || "").split(" ")
)
.map(function(cl) {
if (cl.search("lang-") === 0) return cl.slice("lang-".length);
return null;
})
.compact()
.first()
.value();
if (lang) {
var html = code.highlight(
lang,
$(this).text()
);
$(this).html(html);
}
});
// Replace glossary terms
_.each(options.glossary, function(term) {
var r = new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
var includedInFiles = false;
$("*").each(function() {
// Ignore codeblocks
if (_.contains(["code", "pre"], this.name.toLowerCase())) return;
replaceText($, this, r, function(match) {
// Add to files index in glossary
if (!includedInFiles) {
@@ -212,7 +252,7 @@ function normalizeHtml(src, options) {
term.files = term.files || [];
term.files.push(options.navigation[options.input]);
}
return "<a href='"+links.toAbsolute("/GLOSSARY.html", options.base, options.output)+"#"+term.id+"' class='glossary-term' title='"+term.description+"'>"+match+"</a>";
return "<a href='"+links.toAbsolute("/GLOSSARY.html", options.base, options.output)+"#"+term.id+"' class='glossary-term' title='"+_.escape(term.description)+"'>"+match+"</a>";
});
});
});
@@ -230,33 +270,36 @@ function convertImages(images, options) {
var downloaded = [];
options.book.log.debug.ln("convert ", images.length, "images to png");
return _.reduce(images, function(prev, image) {
var imgin = path.resolve(options.book.options.output, image.source);
return batch.execEach(images, {
max: 100,
fn: function(image) {
var imgin = path.resolve(options.book.options.output, image.source);
return prev
return Q()
// Write image if need to be download
.then(function() {
if (!image.origin && !_.contains(downloaded, image.origin)) return;
options.book.log.debug("download image", image.origin, "...");
downloaded.push(image.origin);
return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)));
})
// Write image if need to be download
.then(function() {
if (!image.origin && !_.contains(downloaded, image.origin)) return;
options.book.log.debug("download image", image.origin, "...");
downloaded.push(image.origin);
return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)));
})
// Write svg if content
.then(function() {
if (!image.content) return;
return fs.writeFile(imgin, image.content);
})
// Write svg if content
.then(function() {
if (!image.content) return;
return fs.writeFile(imgin, image.content);
})
// Convert
.then(function() {
if (!image.dest) return;
var imgout = path.resolve(options.book.options.output, image.dest);
options.book.log.debug("convert image", image.source, "to", image.dest, "...");
return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
});
}, Q())
// Convert
.then(function() {
if (!image.dest) return;
var imgout = path.resolve(options.book.options.output, image.dest);
options.book.log.debug("convert image", image.source, "to", image.dest, "...");
return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
});
}
})
.then(function() {
options.book.log.debug.ok(images.length+" images converted with success");
});
+8
View File
@@ -7,10 +7,18 @@ var calculProgress = function(navigation, current) {
var done = true;
var chapters = _.chain(navigation)
// Transform as array
.map(function(nav, path) {
nav.path = path;
return nav;
})
// Sort entries
.sortBy(function(nav) {
return nav.index;
})
.map(function(nav, i) {
// Calcul percent
nav.percent = (i * 100) / Math.max((n - 1), 1);
+13 -10
View File
@@ -1,25 +1,25 @@
{
"name": "gitbook",
"version": "2.0.0-alpha.7",
"version": "2.0.0",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
"dependencies": {
"q": "1.0.1",
"lunr": "0.5.7",
"lodash": "2.4.1",
"lodash": "3.5.0",
"graceful-fs": "3.0.5",
"resolve": "0.6.3",
"fs-extra": "0.16.3",
"fs-extra": "0.16.5",
"fstream-ignore": "1.0.2",
"gitbook-parsers": "0.4.0",
"nunjucks": "git+https://github.com/SamyPesse/nunjucks.git#4019d1b7379372336b86ce1b0bf84352a2029747",
"nunjucks-autoescape": "0.1.0",
"gitbook-parsers": "0.7.2",
"nunjucks": "git+https://github.com/mozilla/nunjucks.git#0f8b21b8df7e8e852b2e1889388653b7075f0d09",
"nunjucks-autoescape": "0.1.1",
"nunjucks-filter": "0.1.0",
"nunjucks-i18n": "1.0.0",
"semver": "2.2.1",
"npmi": "0.1.1",
"cheerio": "0.18.0",
"cheerio": "0.19.0",
"gitbook-plugin-livereload": "0.0.1",
"gaze": "~0.5.1",
"send": "0.2.0",
@@ -30,10 +30,13 @@
"URIjs": "1.14.1",
"request": "2.51.0",
"npm": "2.4.1",
"dom-serializer": "0.1.0"
"dom-serializer": "0.1.0",
"spawn-cmd": "0.0.2",
"highlight.js": "8.4.0"
},
"devDependencies": {
"mocha": "1.18.2",
"mocha": "2.2.1",
"should": "5.2.0",
"grunt": "~0.4.2",
"grunt-cli": "0.1.11",
"grunt-contrib-copy": "0.5.0",
@@ -42,7 +45,7 @@
"grunt-bower-install-simple": "0.9.2"
},
"scripts": {
"test": "export TESTING=true; mocha --reporter list --timeout 15000"
"test": "node_modules/.bin/mocha --reporter spec --timeout 15000"
},
"repository": {
"type": "git",
+52
View File
@@ -0,0 +1,52 @@
var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var should = require('should');
var cheerio = require('cheerio');
should.Assertion.add('file', function(file, description) {
this.params = { actual: this.obj.toString(), operator: 'have file ' + file, message: description };
this.obj.should.have.property('options').which.is.an.Object;
this.obj.options.should.have.property('output').which.is.a.String;
this.assert(fs.existsSync(path.resolve(this.obj.options.output, file)));
});
should.Assertion.add('jsonfile', function(file, description) {
this.params = { actual: this.obj.toString(), operator: 'have valid jsonfile ' + file, message: description };
this.obj.should.have.property('options').which.is.an.Object;
this.obj.options.should.have.property('output').which.is.a.String;
this.assert(JSON.parse(fs.readFileSync(path.resolve(this.obj.options.output, file), { encoding: "utf-8" })));
});
should.Assertion.add('html', function(rules, description) {
this.params = { actual: "HTML string", operator: 'valid html', message: description };
var $ = cheerio.load(this.obj);
_.each(rules, function(validations, query) {
validations = _.defaults(validations || {}, {
count: 1,
attributes: {},
trim: false,
text: undefined
});
var $el = $(query);
// Test number of elements
$el.should.have.lengthOf(validations.count);
// Test text
if (validations.text !== undefined) {
var text = $el.text();
if (validations.trim) text = text.trim();
text.should.be.equal(validations.text);
}
// Test attributes
_.each(validations.attributes, function(value, name) {
$el.attr(name).should.be.equal(value);
});
});
});
+3
View File
@@ -0,0 +1,3 @@
# Readme
Default description for the book.
+1
View File
@@ -0,0 +1 @@
# Readme
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
"title": "js-config"
};
+1
View File
@@ -0,0 +1 @@
# Readme
+3
View File
@@ -0,0 +1,3 @@
{
"title": "json-config"
}
+10
View File
@@ -0,0 +1,10 @@
# Readme
### Relative
<p id="t1">{% include "./hello.md" %}</p>
### Git
<p id="t2">{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}</p>
<p id="t3">{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}</p>
+1
View File
@@ -0,0 +1 @@
# Summary
+1
View File
@@ -0,0 +1 @@
Hello World
+9
View File
@@ -0,0 +1,9 @@
# Glossary
## test
Just a simple and easy to understand test.
## hakunamatata
This word is not present in the content.
+8
View File
@@ -0,0 +1,8 @@
# Readme
The word test should be replaced by a glossary link.
```
But the word test should not be replaced in code blocks
```
+3
View File
@@ -0,0 +1,3 @@
# Summary
* [Page](folder/PAGE.md)
+3
View File
@@ -0,0 +1,3 @@
# Page
Just a page in a sub-directory to test relative link to glossary.
+18
View File
@@ -0,0 +1,18 @@
# Tests for Images
# SVG relative image
### Convert SVG to PNG
![test.svg](test.svg)
### Should only convert it once
![test.svg](test.svg)
### Download remote images
![remote image](http://upload.wikimedia.org/wikipedia/commons/b/b0/NewTux.svg)
### Remote images with same filename
![youtube1](http://img.youtube.com/vi/9bZkp7q19f0/0.jpg)
![youtube2](http://img.youtube.com/vi/IkV2HQLAKHY/0.jpg)
+3
View File
@@ -0,0 +1,3 @@
# Summary
* [Page](./folder/PAGE.md)
+3
View File
@@ -0,0 +1,3 @@
### Images from other folder
![test.svg](../test.svg)
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-root" width="100%" height="100%" viewBox="0 0 480 360">
<title id="test-title">basic SVG tiny doc</title>
<g id="test-body-content">
<text font-family="Arial" font-size="14" text-anchor="middle" x="225" y="25">hello world</text>
</g>
<text id="revision" x="10" y="340" font-size="40" stroke="none" fill="black">Revision: 1.1</text>
<rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 730 B

+4
View File
@@ -0,0 +1,4 @@
# Language
* [English](en)
* [French](fr)
+1
View File
@@ -0,0 +1 @@
# Readme
+1
View File
@@ -0,0 +1 @@
Hello
+1
View File
@@ -0,0 +1 @@
# Summary
+1
View File
@@ -0,0 +1 @@
Bonjour
+1
View File
@@ -0,0 +1 @@
# Summary
+1
View File
@@ -0,0 +1 @@
# Page 1
+1
View File
@@ -0,0 +1 @@
# Readme
+5
View File
@@ -0,0 +1,5 @@
# Summary
* [Page 1](./PAGE1.md)
* [Page 2](./folder/PAGE2.md)
* [Don't exists](./NOTFOUND.md)
+1
View File
@@ -0,0 +1 @@
# Page 2
+26 -8
View File
@@ -1,14 +1,32 @@
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var Book = require('../').Book;
describe('Configuration parsing', function () {
it('should correctly load from json', function() {
assert(books[0].options.title == "Test");
describe('Configuration', function () {
it('should extract default title from README', function() {
return books.parse("basic")
.then(function(book) {
book.options.title.should.be.equal("Readme");
});
});
it('should correctly load from javascript', function() {
assert(books[4].options.title == "Test 2");
it('should extract default description from README', function() {
return books.parse("basic")
.then(function(book) {
book.options.description.should.be.equal("Default description for the book.");
});
});
it('should correctly load from json (book.json)', function() {
return books.parse("config-json")
.then(function(book) {
book.options.title.should.be.equal("json-config");
});
});
it('should correctly load from JavaScript (book.js)', function() {
return books.parse("config-js")
.then(function(book) {
book.options.title.should.be.equal("js-config");
});
});
});
+43
View File
@@ -0,0 +1,43 @@
var fs = require('fs');
var path = require('path');
describe('ConRefs', function () {
var book, readme;
before(function() {
return books.generate("conrefs", "website")
.then(function(_book) {
book = _book;
readme = fs.readFileSync(
path.join(book.options.output, "index.html"),
{ encoding: "utf-8" }
);
});
});
it('should handle local references', function() {
readme.should.be.html({
".page-inner p#t1": {
count: 1,
text: "Hello World",
trim: true
}
});
});
it('should handle git references', function() {
readme.should.be.html({
".page-inner p#t2": {
count: 1,
text: "Hello from git",
trim: true
},
".page-inner p#t3": {
count: 1,
text: "First Hello. Hello from git",
trim: true
}
});
});
});
+17 -29
View File
@@ -1,36 +1,24 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var cheerio = require('cheerio');
var fs = require("fs");
var fsUtil = require("../lib/utils/fs");
describe('eBook generator', function () {
describe('Basic Book', function() {
var book;
before(function() {
return books.generate("basic", "ebook")
.then(function(_book) {
book = _book;
});
});
describe('eBook Generator', function () {
it('should correctly generate ebook pages', function(done) {
testGeneration(books[1], "ebook", function(output) {
assert(fs.existsSync(path.join(output, "SUMMARY.html")));
}, done);
});
it('should correctly output a SUMMARY.html', function() {
book.should.have.file("SUMMARY.html");
});
it('should correctly convert svg images to png', function(done) {
testGeneration(books[4], "ebook", function(output) {
// Check that all images exists
_.each([
"index.html",
"sub/PAGE.html"
], function(pageName) {
var pageFile = path.join(output, pageName);
var pageFolder = path.dirname(pageFile);
var pageContent = fs.readFileSync(pageFile, {encoding: "utf8"});
var $ = cheerio.load(pageContent);
$("img").each(function() {
var src = $(this).attr("src");
assert(fs.existsSync(path.resolve(pageFolder, src)), src+" not found for page "+pageName);
})
});
}, done);
it('should correctly copy assets', function() {
book.should.have.file("gitbook");
book.should.have.file("gitbook/style.css");
});
});
});
-6
View File
@@ -1,6 +0,0 @@
# Glossary
## Description
This is the dexcription of a description.
-3
View File
@@ -1,3 +0,0 @@
# Other Title
This is a description, it's simple, no ?
-1
View File
@@ -1 +0,0 @@
# Summary
-3
View File
@@ -1,3 +0,0 @@
{
"title": "Test"
}
-3
View File
@@ -1,3 +0,0 @@
body {
background: red;
}
-9
View File
@@ -1,9 +0,0 @@
# Test
a test text
# Test 2
a second test
-4
View File
@@ -1,4 +0,0 @@
# Test
This file is not parsed by gitbook because the structure is defined in book.json.
-6
View File
@@ -1,6 +0,0 @@
# Summary
* [Chapter 1](test.md)
* [Article 1](sub/test1.md)
* [Chapter 2](test2.md)
* [Google](https://www.google.com)
-5
View File
@@ -1,5 +0,0 @@
{
"structure": {
"readme": "intro.md"
}
}
-4
View File
@@ -1,4 +0,0 @@
# My Book
Test description
-8
View File
@@ -1,8 +0,0 @@
# This file is used to etst links transformations:
This is a relative link [test](../intro.md).
![image](../image.png)
This is a glossary link.
-4
View File
@@ -1,4 +0,0 @@
# Languages
* [English](en/)
* [French](fr/)
-2
View File
@@ -1,2 +0,0 @@
# Multi-Languages test
-1
View File
@@ -1 +0,0 @@
# English Book
-1
View File
@@ -1 +0,0 @@
# French Book
-4
View File
@@ -1,4 +0,0 @@
= My Book
Test description
-5
View File
@@ -1,5 +0,0 @@
= Summary
. link:test.adoc[Chapter 1]
.. link:test1.adoc[Article 1]
. link:test2.adoc[Chapter 2]
View File
View File
View File
-11
View File
@@ -1,11 +0,0 @@
# Other Title
A description
![test image to be converted](./test.svg)
![test url](http://upload.wikimedia.org/wikipedia/commons/b/b0/NewTux.svg)
![test image to be converted, second use](./test.svg)
# Test with youtube videos that have the same filename:
![youtube1](http://img.youtube.com/vi/9bZkp7q19f0/0.jpg)
![youtube2](http://img.youtube.com/vi/IkV2HQLAKHY/0.jpg)
-3
View File
@@ -1,3 +0,0 @@
# Summary
* [Page](sub/PAGE.md)
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
title: "Test 2"
};
-18
View File
@@ -1,18 +0,0 @@
##
## Image from root page
![test image to be converted](../test.svg)
![test url](http://upload.wikimedia.org/wikipedia/commons/b/b0/NewTux.svg)
## Inline svg
{% html %}
<svg xmlns="http://www.w3.org/2000/svg">
<path d="M97.008198003228,6.103238498249268A97.2,97.2 0 0,1 87.9491894996971,41.385747140125076L54.289623147961166,25.546757493904366A60,60 0 0,0 59.88160370569629,3.7674311717588074Z"></path>
</svg>
{% endhtml %}
## Remote image
![remote url](http://upload.wikimedia.org/wikipedia/commons/a/af/Tux.png)
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-root" width="100%" height="100%" viewBox="0 0 480 360">
<title id="test-title">basic SVG tiny doc</title>
<g id="test-body-content">
<text font-family="Arial" font-size="14" text-anchor="middle" x="225" y="25">hello world</text>
</g>
<text id="revision" x="10" y="340" font-size="40" stroke="none" fill="black">Revision: 1.1</text>
<rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
</svg>

Before

Width:  |  Height:  |  Size: 709 B

-5
View File
@@ -1,5 +0,0 @@
# Test Content inclusion
{% include "./test.md" %}
Git1:{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}
Git2:{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}
-1
View File
@@ -1 +0,0 @@
Hello World
+17 -18
View File
@@ -1,32 +1,31 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var fs = require("fs");
var git = require("../lib/utils/git");
var should = require('should');
var git = require('../lib/utils/git');
describe('GIT parser and getter', function () {
it('should correctly parse an https url', function() {
var parts = git.parseUrl("git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md");
assert(parts);
assert.equal(parts.host, "https://gist.github.com/69ea4542e4c8967d2fa7.git");
assert.equal(parts.ref, "master");
assert.equal(parts.filepath, "test.md");
should.exist(parts);
parts.host.should.be.equal("https://gist.github.com/69ea4542e4c8967d2fa7.git");
parts.ref.should.be.equal("master");
parts.filepath.should.be.equal("test.md");
});
it('should correctly parse an https url with a reference', function() {
var parts = git.parseUrl("git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md#0.1.2");
assert(parts);
assert.equal(parts.host, "https://gist.github.com/69ea4542e4c8967d2fa7.git");
assert.equal(parts.ref, "0.1.2");
assert.equal(parts.filepath, "test.md");
should.exist(parts);
parts.host.should.be.equal("https://gist.github.com/69ea4542e4c8967d2fa7.git");
parts.ref.should.be.equal("0.1.2");
parts.filepath.should.be.equal("test.md");
});
it('should correctly parse an ssh url', function() {
var parts = git.parseUrl("git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1");
assert(parts);
assert.equal(parts.host, "git@github.com:GitbookIO/gitbook.git");
assert.equal(parts.ref, "e1594cde2c32e4ff48f6c4eff3d3d461743d74e1");
assert.equal(parts.filepath, "directory/README.md");
should.exist(parts);
parts.host.should.be.equal("git@github.com:GitbookIO/gitbook.git");
parts.ref.should.be.equal("e1594cde2c32e4ff48f6c4eff3d3d461743d74e1");
parts.filepath.should.be.equal("directory/README.md");
});
});
+69 -26
View File
@@ -1,35 +1,78 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var cheerio = require('cheerio');
var fs = require("fs");
var fsUtil = require("../lib/utils/fs");
describe('Glossary', function () {
describe('Parsing', function() {
var book;
before(function() {
return books.parse("glossary")
.then(function(_book) {
book = _book;
});
});
describe('Glossary Generation', function () {
it('should correctly replace glossary terms', function(done) {
testGeneration(books[0], "website", function(output) {
var content = fs.readFileSync(path.join(output, "index.html"), { encoding: "utf8" });
var $ = cheerio.load(content);
var $body = $(".page-inner");
var $a = $("a[href='GLOSSARY.html#description']");
assert($a.length == 1);
assert($a.text() == "description");
}, done);
it('should correctly list items', function() {
book.should.have.property("glossary");
book.glossary.should.have.lengthOf(2);
});
});
it('should correctly replace glossary terms in sub pages', function(done) {
testGeneration(books[1], "website", function(output) {
var content = fs.readFileSync(path.join(output, "sub/test1.html"), { encoding: "utf8" });
var $ = cheerio.load(content);
describe('Generation', function() {
var book;
var $body = $(".page-inner");
var $a = $("a[href='../GLOSSARY.html#test']");
assert($a.length == 1);
assert($a.text() == "test");
assert($a.attr("title") == "a test text");
}, done);
before(function() {
return books.generate("glossary", "website")
.then(function(_book) {
book = _book;
});
});
it('should correctly generate a GLOSSARY.html', function() {
book.should.have.file("GLOSSARY.html");
});
describe('Page Integration', function() {
var readme, page;
before(function() {
readme = fs.readFileSync(
path.join(book.options.output, "index.html"),
{ encoding: "utf-8" }
);
page = fs.readFileSync(
path.join(book.options.output, "folder/PAGE.html"),
{ encoding: "utf-8" }
);
});
it('should correctly replaced terms by links', function() {
readme.should.be.html({
".page-inner a[href='GLOSSARY.html#test']": {
count: 1,
text: "test",
attributes: {
title: "Just a simple and easy to understand test."
}
}
});
});
it('should correctly replaced terms by links (relative)', function() {
page.should.be.html({
".page-inner a[href='../GLOSSARY.html#test']": {
count: 1
}
});
});
it('should not replace terms in codeblocks', function() {
readme.should.be.html({
".page-inner code a": {
count: 0
}
});
});
});
});
});
+55 -49
View File
@@ -1,62 +1,68 @@
var os = require('os');
var path = require('path');
var Q = require('q');
var fs = require('fs');
var _ = require('lodash');
var should = require('should');
var fsUtil = require("../lib/utils/fs");
var fsUtil = require('../lib/utils/fs');
var Book = require('../').Book;
var LOG_LEVELS = require('../').LOG_LEVELS;
// Nicety for mocha / Q
global.qdone = function qdone(promise, done) {
return promise.then(function() {
return done();
}, function(err) {
return done(err);
}).done();
require("./assertions");
var BOOKS = {};
var TMPDIR = os.tmpdir();
// Generate and return a book
function generateBook(bookId, test) {
return parseBook(bookId, test)
.then(function(book) {
return book.generate(test)
.thenResolve(book);
});
}
// Generate and return a book
function parseBook(bookId, test) {
test = test || "website";
BOOKS[bookId] = BOOKS[bookId] || {};
if (BOOKS[bookId][test]) return Q(BOOKS[bookId][test]);
BOOKS[bookId][test] = new Book(path.resolve(__dirname, "books", bookId), {
logLevel: LOG_LEVELS.DISABLED,
config: {
output: path.resolve(TMPDIR, bookId+"-"+test)
}
});
return BOOKS[bookId][test].parse()
.then(function() {
return BOOKS[bookId][test];
});
}
global.books = {
parse: parseBook,
generate: generateBook
};
// Test generation of a book
global.testGeneration = function(book, type, func, done) {
var OUTPUT_PATH = book.options.output;
qdone(
book.generate(type)
.then(function() {
func(OUTPUT_PATH);
})
.fin(function() {
return fsUtil.remove(OUTPUT_PATH);
}),
done);
};
// Books for testings
var books = fs.readdirSync(path.join(__dirname, './fixtures/'));
global.books = _.chain(books)
.sortBy()
.map(function(book) {
if (book.indexOf("test") !== 0) return null;
return new Book(path.join(__dirname, './fixtures/', book), {
logLevel: LOG_LEVELS.DISABLED
});
})
.compact()
.value();
// Init before doing tests
before(function(done) {
qdone(
_.reduce(global.books, function(prev, book) {
// Cleanup all tests
after(function() {
return _.chain(BOOKS)
.map(function(types, bookId) {
return _.values(types);
})
.flatten()
.reduce(function(prev, book) {
return prev.then(function() {
return fsUtil.remove(path.join(book.root, "_book"));
return fsUtil.remove(book.options.output);
})
.then(function() {
return book.parse();
});
}, Q()),
done
);
}, Q())
.value();
});
+56
View File
@@ -0,0 +1,56 @@
var fs = require('fs');
var _ = require('lodash');
var path = require('path');
var cheerio = require('cheerio');
describe('Images', function () {
var book, readme, $, $img, srcs;
before(function() {
return books.generate("images", "ebook")
.then(function(_book) {
book = _book;
readme = fs.readFileSync(
path.join(book.options.output, "index.html"),
{ encoding: "utf-8" }
);
$ = cheerio.load(readme);
$img = $("img");
srcs = $img.map(function() { return $(this).attr("src"); })
});
});
it('should detect all images', function() {
_.uniq(srcs).should.have.lengthOf(4);
});
it('should keep image tags', function() {
srcs.should.have.lengthOf(5);
});
it('should not have .svg files', function() {
_.each(srcs, function(src) {
path.extname(src).should.not.equal(".svg");
});
});
it('should correctly convert svg images to png', function() {
_.each(srcs, function(src) {
book.should.have.file(src);
});
});
it('should handle relative paths', function() {
var PAGE = fs.readFileSync(
path.join(book.options.output, "folder/PAGE.html"),
{ encoding: "utf-8" }
);
PAGE.should.be.html({
"img[src='../test.png']": {
count: 1
}
});
});
});
+79 -46
View File
@@ -1,59 +1,92 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var fs = require("fs");
describe('JSON generator', function () {
it('should correctly generate a book to json with glossary', function(done) {
testGeneration(books[0], "json", function(output) {
assert(fs.existsSync(path.join(output, "README.json")));
describe('Basic Book', function() {
var book;
var readme = JSON.parse(fs.readFileSync(path.join(output, "README.json")));
assert(readme.sections[0].content.indexOf('class="glossary-term"') > 0);
}, done);
before(function() {
return books.generate("basic", "json")
.then(function(_book) {
book = _book;
});
});
it('should correctly output a README.json', function() {
book.should.have.file("README.json");
});
it('should output a valid json', function() {
book.should.have.jsonfile("README.json");
});
describe('Page Format', function() {
var page;
before(function() {
page = JSON.parse(
fs.readFileSync(
path.join(book.options.output, "README.json"),
{ encoding: "utf-8" }
)
);
});
it('should contains valid section', function() {
page.should.have.property("sections").with.lengthOf(1);
page.sections[0].should.have.property("content").which.is.a.String;
page.sections[0].should.have.property("type").which.is.a.String.which.equal("normal");
});
it('should contains valid progress', function() {
page.should.have.property("progress");
page.progress.should.have.property("chapters").with.lengthOf(1);
page.progress.should.have.property("current");
});
it('should contains no languages', function() {
page.should.have.property("langs").with.lengthOf(0);
});
});
});
it('should correctly generate a book to json with sub folders', function(done) {
testGeneration(books[1], "json", function(output) {
assert(fs.existsSync(path.join(output, "README.json")));
assert(fs.existsSync(path.join(output, "intro.json")));
assert(fs.existsSync(path.join(output, "sub/test1.json")));
describe('Multilingual Book', function() {
var book;
var test1 = JSON.parse(fs.readFileSync(path.join(output, "sub/test1.json")));
assert(test1.sections[0].content.indexOf("intro.html") > 0);
}, done);
});
before(function() {
return books.generate("languages", "json")
.then(function(_book) {
book = _book;
});
});
it('should correctly generate a multilingual book to json', function(done) {
testGeneration(books[2], "json", function(output) {
assert(fs.existsSync(path.join(output, "README.json")));
assert(fs.existsSync(path.join(output, "en/README.json")));
assert(fs.existsSync(path.join(output, "fr/README.json")));
}, done);
});
it('should correctly output READMEs', function() {
book.should.have.file("README.json");
book.should.have.file("en/README.json");
book.should.have.file("fr/README.json");
});
it('should correctly generate an asciidoc book to json', function(done) {
testGeneration(books[3], "json", function(output) {
assert(fs.existsSync(path.join(output, "README.json")));
assert(fs.existsSync(path.join(output, "test.json")));
assert(fs.existsSync(path.join(output, "test1.json")));
assert(fs.existsSync(path.join(output, "test2.json")));
}, done);
});
it('should output valid json', function() {
book.should.have.jsonfile("README.json");
book.should.have.jsonfile("en/README.json");
book.should.have.jsonfile("fr/README.json");
});
it('should correctly generate a book with local inclusion', function(done) {
testGeneration(books[5], "json", function(output) {
var readme = JSON.parse(fs.readFileSync(path.join(output, "README.json")));
assert(readme.sections[0].content.indexOf('Hello World') > 0);
}, done);
});
describe('Page Format', function() {
var page;
it('should correctly generate a book with external inclusion', function(done) {
testGeneration(books[5], "json", function(output) {
var readme = JSON.parse(fs.readFileSync(path.join(output, "README.json")));
assert(readme.sections[0].content.indexOf('Git1:Hello from git') > 0);
assert(readme.sections[0].content.indexOf('Git2:First Hello. Hello from git') > 0);
}, done);
before(function() {
page = JSON.parse(
fs.readFileSync(
path.join(book.options.output, "README.json"),
{ encoding: "utf-8" }
)
);
});
it('should contains no languages', function() {
page.should.have.property("langs").with.lengthOf(2);
});
});
});
});
+40
View File
@@ -0,0 +1,40 @@
var fs = require('fs');
var path = require('path');
describe('Languages', function () {
describe('Parsing', function() {
var book;
before(function() {
return books.parse("languages")
.then(function(_book) {
book = _book;
});
});
it('should correctly list languages', function() {
book.should.have.property("books");
book.books.should.have.lengthOf(2);
book.books[0].options.language.should.be.equal("en");
book.books[1].options.language.should.be.equal("fr");
});
});
describe('Generation', function() {
var book;
before(function() {
return books.generate("languages", "website")
.then(function(_book) {
book = _book;
});
});
it('should correctly create books', function() {
book.should.have.file("index.html");
book.should.have.file("en/index.html");
book.should.have.file("fr/index.html");
});
});
});
+20 -16
View File
@@ -1,33 +1,37 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var should = require('should');
var links = require("../lib/utils/links");
describe('Links', function () {
it('should correctly test external links', function() {
assert(links.isExternal("http://google.fr"));
assert(links.isExternal("https://google.fr"));
assert(!links.isExternal("test.md"));
assert(!links.isExternal("folder/test.md"));
assert(!links.isExternal("/folder/test.md"));
links.isExternal("http://google.fr").should.be.exactly(true);
links.isExternal("https://google.fr").should.be.exactly(true);
links.isExternal("test.md").should.be.exactly(false);
links.isExternal("folder/test.md").should.be.exactly(false);
links.isExternal("/folder/test.md").should.be.exactly(false);
});
it('should correctly detect anchor links', function() {
links.isAnchor("#test").should.be.exactly(true);
links.isAnchor(" #test").should.be.exactly(true);
links.isAnchor("https://google.fr#test").should.be.exactly(false);
links.isAnchor("test.md#test").should.be.exactly(false);
});
describe('toAbsolute', function() {
it('should correctly transform as absolute', function() {
assert.equal(links.toAbsolute("http://google.fr"), "http://google.fr");
assert.equal(links.toAbsolute("test.md", "./", "./"), "test.md");
assert.equal(links.toAbsolute("folder/test.md", "./", "./"), "folder/test.md");
links.toAbsolute("http://google.fr").should.be.equal("http://google.fr");
links.toAbsolute("test.md", "./", "./").should.be.equal("test.md");
links.toAbsolute("folder/test.md", "./", "./").should.be.equal("folder/test.md");
});
it('should correctly handle windows path', function() {
assert.equal(links.toAbsolute("folder\\test.md", "./", "./"), "folder/test.md");
links.toAbsolute("folder\\test.md", "./", "./").should.be.equal("folder/test.md");
});
it('should correctly handle absolute path', function() {
assert.equal(links.toAbsolute("/test.md", "./", "./"), "test.md");
assert.equal(links.toAbsolute("/test.md", "test", "test"), "../test.md");
assert.equal(links.toAbsolute("/sub/test.md", "test", "test"), "../sub/test.md");
links.toAbsolute("/test.md", "./", "./").should.be.equal("test.md");
links.toAbsolute("/test.md", "test", "test").should.be.equal("../test.md");
links.toAbsolute("/sub/test.md", "test", "test").should.be.equal("../sub/test.md");
});
});
});
-82
View File
@@ -1,82 +0,0 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
describe('Book parsing', function () {
it('should correctly parse the readme', function() {
assert.equal(books[1].options.title, 'My Book');
assert.equal(books[1].options.description, 'Test description');
});
it('should correctly parse the readme with asciidoc', function() {
assert.equal(books[3].options.title, 'My Book');
assert.equal(books[3].options.description, 'Test description');
});
it('should correctly parse the summary', function() {
var LEXED = books[1].summary;
assert.equal(LEXED.chapters[0].path, 'intro.md');
assert.equal(LEXED.chapters[0].exists, true);
assert.equal(LEXED.chapters[0].introduction, true);
assert.equal(LEXED.chapters[0].external, false);
assert.equal(LEXED.chapters[1].path, 'test.md');
assert.equal(LEXED.chapters[1].exists, false);
assert.equal(LEXED.chapters[1].introduction, false);
assert.equal(LEXED.chapters[1].external, false);
assert.equal(LEXED.chapters[2].path, 'test2.md');
assert.equal(LEXED.chapters[2].exists, false);
assert.equal(LEXED.chapters[2].introduction, false);
assert.equal(LEXED.chapters[2].external, false);
assert.equal(LEXED.chapters[3].path, 'https://www.google.com');
assert.equal(LEXED.chapters[3].exists, true);
assert.equal(LEXED.chapters[3].introduction, false);
assert.equal(LEXED.chapters[3].external, true);
});
it('should correctly parse the glossary', function() {
var LEXED = books[1].glossary;
assert.equal(LEXED[0].id, "test");
assert.equal(LEXED[0].name, "Test");
assert.equal(LEXED[0].description, "a test text");
assert.equal(LEXED[1].id, "test_2");
assert.equal(LEXED[1].name, "Test 2");
assert.equal(LEXED[1].description, "a second test");
});
it('should correctly parse list of files', function() {
var FILES = books[1].files;
assert.deepEqual(FILES, [ 'README.md', 'intro.md', 'sub/', 'sub/test1.md' ]);
});
it('should correctly parse the languages', function() {
assert.equal(books[2].books.length, 2);
assert(books[2].isMultilingual());
assert.equal(books[2].books[0].options.language, "en");
assert.equal(books[2].books[0].options.title, "English Book");
assert.equal(books[2].books[1].options.language, "fr");
assert.equal(books[2].books[1].options.title, "French Book");
});
it('should correctly parse the navigation', function() {
var NAVIGATION = books[1].navigation;
assert.equal(_.size(NAVIGATION), 2);
assert(NAVIGATION["intro.md"])
assert.equal(NAVIGATION["intro.md"].title, "Introduction");
assert.equal(NAVIGATION["intro.md"].prev, null);
assert.equal(NAVIGATION["intro.md"].next.title, "Article 1");
assert.equal(NAVIGATION["sub/test1.md"].title, "Article 1");
assert.equal(NAVIGATION["sub/test1.md"].prev.title, "Introduction");
assert.equal(NAVIGATION["sub/test1.md"].next, null);
});
});
+133 -132
View File
@@ -1,189 +1,190 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var fs = require("fs");
var should = require('should');
var path = require('path');
var Plugin = require("../lib/plugin");
var PLUGINS_ROOT = path.resolve(__dirname, "plugins");
var Plugin = require('../lib/plugin');
var PLUGINS_ROOT = path.resolve(__dirname, 'plugins');
describe('Plugins', function () {
describe('invalid plugin', function() {
it('should signal as invalid', function() {
var plugin = new Plugin(books[0], "invalid");
var book;
before(function() {
return books.parse("basic")
.then(function(_book) {
book = _book;
});
});
describe('Invalid', function() {
var plugin;
before(function() {
plugin = new Plugin(book, "invalid");
plugin.load("./invalid", PLUGINS_ROOT);
assert(!plugin.isValid());
});
it('should be detected', function() {
should(plugin.isValid()).be.exactly(false);
});
});
describe('empty plugin', function() {
var plugin = new Plugin(books[0], "invalid");
plugin.load("./empty", PLUGINS_ROOT);
describe('Empty', function() {
var plugin;
it('should valid a plugin', function() {
assert(plugin.isValid());
before(function() {
plugin = new Plugin(book, "empty");
plugin.load("./empty", PLUGINS_ROOT);
});
it('should return an empty list of resources', function(done) {
qdone(
plugin.getResources()
.then(function(resources) {
_.each(Plugin.RESOURCES, function(resName) {
assert.equal(resources[resName].length, 0);
it('should valid a plugin', function() {
should(plugin.isValid()).be.exactly(true);
});
it('should return an empty list of resources', function() {
return plugin.getResources()
.then(function(resources) {
_.each(Plugin.RESOURCES, function(resName) {
resources[resName].should.have.lengthOf(0);
});
});
});
});
describe('Resources', function() {
var plugin;
before(function() {
plugin = new Plugin(book, "resources");
plugin.load("./resources", PLUGINS_ROOT);
return book.plugins.load(plugin);
});
it('should valid a plugin', function() {
should(plugin.isValid()).be.exactly(true);
});
describe('Website', function() {
it('should return a valid list of resources', function() {
return plugin.getResources("website")
.then(function(resources) {
resources["js"].should.have.lengthOf(1);
});
}),
done);
});
it('should extend books plugins', function() {
var resources = book.plugins.resources("website");
resources["js"].should.have.lengthOf(1);
});
});
describe('eBook', function() {
it('should return a valid list of resources', function() {
return plugin.getResources("ebook")
.then(function(resources) {
resources["css"].should.have.lengthOf(1);
});
});
it('should extend books plugins', function() {
var resources = book.plugins.resources("ebook");
resources["css"].should.have.lengthOf(1);
});
});
});
describe('resources plugin', function() {
var plugin = new Plugin(books[0], "resources");
plugin.load("./resources", PLUGINS_ROOT);
describe('Filters', function() {
var plugin;
before(function(done) {
qdone(books[0].plugins.load(plugin), done);
before(function() {
plugin = new Plugin(book, "filters");
plugin.load("./filters", PLUGINS_ROOT);
return book.plugins.load(plugin);
});
it('should valid a plugin', function() {
assert(plugin.isValid());
});
it('should return a valid list of resources (website)', function(done) {
qdone(
plugin.getResources("website")
.then(function(resources) {
assert.equal(resources["js"].length, 1);
}),
done);
});
it('should return a valid list of resources (ebook)', function(done) {
qdone(
plugin.getResources("ebook")
.then(function(resources) {
assert.equal(resources["css"].length, 1);
}),
done);
});
it('should extend books plugins (website)', function() {
var resources = books[0].plugins.resources("website");
assert.equal(resources["js"].length, 1);
});
it('should extend books plugins (ebook)', function() {
var resources = books[0].plugins.resources("ebook");
assert.equal(resources["css"].length, 1);
});
});
describe('filters', function() {
var plugin = new Plugin(books[0], "filters");
plugin.load("./filters", PLUGINS_ROOT);
before(function(done) {
qdone(books[0].plugins.load(plugin), done);
});
it('should valid a plugin', function() {
assert(plugin.isValid());
should(plugin.isValid()).be.exactly(true);
});
it('should return a map of filters', function() {
var filters = plugin.getFilters();
assert.equal(_.size(filters), 2);
assert(filters["hello"]);
assert(filters["helloCtx"]);
_.size(filters).should.equal(2);
filters.should.have.property("hello");
filters.should.have.property("helloCtx");
});
it('should correctly extend template filters', function(done) {
qdone(
books[0].template.renderString('{{ "World"|hello }}')
it('should correctly extend template filters', function() {
return book.template.renderString('{{ "World"|hello }}')
.then(function(content) {
assert.equal(content, "Hello World");
}),
done
);
content.should.equal("Hello World");
});
});
it('should correctly set book as context', function(done) {
qdone(
books[0].template.renderString('{{ "root"|helloCtx }}')
it('should correctly set book as context', function() {
return book.template.renderString('{{ "root"|helloCtx }}')
.then(function(content) {
assert.equal(content, "root:"+books[0].root);
}),
done
);
content.should.equal("root:"+book.root);
});
});
});
describe('blocks', function() {
var plugin = new Plugin(books[0], "blocks");
plugin.load("./blocks", PLUGINS_ROOT);
describe('Blocks', function() {
var plugin;
before(function() {
plugin = new Plugin(book, "blocks");
plugin.load("./blocks", PLUGINS_ROOT);
return book.plugins.load(plugin);
});
var testTpl = function(str, args, options) {
return books[0].template.renderString(str, args, options)
.then(books[0].template.postProcess)
return book.template.renderString(str, args, options)
.then(book.template.postProcess)
};
before(function(done) {
qdone(books[0].plugins.load(plugin), done);
});
it('should valid a plugin', function() {
assert(plugin.isValid());
should(plugin.isValid()).be.exactly(true);
});
it('should correctly extend template blocks', function(done) {
qdone(
testTpl('{% test %}hello{% endtest %}')
it('should correctly extend template blocks', function() {
return testTpl('{% test %}hello{% endtest %}')
.then(function(content) {
assert.equal(content, "testhellotest");
}),
done
);
content.should.equal("testhellotest");
});
});
it('should correctly accept shortcuts', function(done) {
qdone(
testTpl('$$hello$$', {}, {
it('should correctly accept shortcuts', function() {
return testTpl('$$hello$$', {}, {
type: "markdown"
})
.then(function(content) {
assert.equal(content, "testhellotest");
}),
done
);
content.should.equal("testhellotest");
});
});
it('should correctly extend template blocks with defined end', function(done) {
qdone(
testTpl('{% test2 %}hello{% endtest2end %}')
it('should correctly extend template blocks with defined end', function() {
return testTpl('{% test2 %}hello{% endtest2end %}')
.then(function(content) {
assert.equal(content, "test2hellotest2");
}),
done
);
content.should.equal("test2hellotest2");
});
});
it('should correctly extend template blocks with sub-blocks', function(done) {
qdone(
testTpl('{% test3join separator=";" %}hello{% also %}world{% endtest3join %}')
it('should correctly extend template blocks with sub-blocks', function() {
return testTpl('{% test3join separator=";" %}hello{% also %}world{% endtest3join %}')
.then(function(content) {
assert.equal(content, "hello;world");
}),
done
);
content.should.equal("hello;world");
});
});
it('should correctly extend template blocks with different sub-blocks', function(done) {
qdone(
testTpl('{% test4join separator=";" %}hello{% also %}the{% finally %}world{% endtest4join %}')
it('should correctly extend template blocks with different sub-blocks', function() {
return testTpl('{% test4join separator=";" %}hello{% also %}the{% finally %}world{% endtest4join %}')
.then(function(content) {
assert.equal(content, "hello;the;world");
}),
done
);
content.should.equal("hello;the;world");
});
});
});
});
+11 -11
View File
@@ -2,22 +2,22 @@ var assert = require("assert");
module.exports = {
blocks: {
"test": {
"test": {
shortcuts: {
parsers: ["markdown"],
start: "$$",
end: "$$"
},
process: function(blk) {
return "test"+blk.body+"test";
}
},
"test2": {
end: "endtest2end",
process: function(blk) {
return "test2"+blk.body+"test2";
}
},
process: function(blk) {
return "test"+blk.body+"test";
}
},
"test2": {
end: "endtest2end",
process: function(blk) {
return "test2"+blk.body+"test2";
}
},
"test3join": {
blocks: [
"also"
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
return "Hello "+text;
},
helloCtx: function(text) {
return text+":"+this.book.root;
return text+":"+this.book.root;
}
}
};
+4 -4
View File
@@ -1,12 +1,12 @@
module.exports = {
website: {
website: {
js: [
"https://cdn.mathjax.org/mathjax/2.4-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
]
},
ebook: {
css: [
"test"
]
css: [
"test"
]
}
};
+45
View File
@@ -0,0 +1,45 @@
var fs = require('fs');
var path = require('path');
describe('Summary', function () {
describe('Parsing', function() {
var book;
before(function() {
return books.parse("summary")
.then(function(_book) {
book = _book;
});
});
it('should correctly list items', function() {
book.should.have.property("summary");
book.summary.should.have.property("chapters");
book.summary.chapters.should.have.lengthOf(4);
});
it('should correctly mark non-existant entries', function() {
book.summary.chapters[0].exists.should.have.equal(true);
book.summary.chapters[1].exists.should.have.equal(true);
book.summary.chapters[2].exists.should.have.equal(true);
book.summary.chapters[3].exists.should.have.equal(false);
});
});
describe('Generation', function() {
var book;
before(function() {
return books.generate("summary", "website")
.then(function(_book) {
book = _book;
});
});
it('should create files according to summary', function() {
book.should.have.file("index.html");
book.should.have.file("PAGE1.html");
book.should.have.file("folder/PAGE2.html");
});
});
});
+33
View File
@@ -0,0 +1,33 @@
var pkg = require("../package.json");
describe('Templating', function () {
var book;
before(function() {
return books.parse("basic")
.then(function(_book) {
book = _book;
});
});
var testTpl = function(str, args, options) {
return book.template.renderString(str, args, options)
.then(book.template.postProcess)
};
describe('Context', function() {
it('should correctly have access to generator', function() {
return testTpl('{{ gitbook.generator }}')
.then(function(content) {
content.should.equal("website");
});
});
it('should correctly have access to gitbook version', function() {
return testTpl('{{ gitbook.version }}')
.then(function(content) {
content.should.equal(pkg.version.toString());
});
});
});
});
+18 -33
View File
@@ -1,40 +1,25 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var fs = require("fs");
var fsUtil = require("../lib/utils/fs");
describe('Website generator', function () {
describe('Basic Book', function() {
var book;
before(function() {
return books.generate("basic", "website")
.then(function(_book) {
book = _book;
});
});
describe('Website Generator', function () {
it('should correctly generate a book to website', function(done) {
testGeneration(books[1], "website", function(output) {
assert(fs.existsSync(path.join(output, "index.html")));
assert(fs.existsSync(path.join(output, "search_index.json")));
}, done);
});
it('should correctly output an index.html', function() {
book.should.have.file("index.html");
});
it('should correctly include styles in website', function(done) {
testGeneration(books[0], "website", function(output) {
assert(fs.existsSync(path.join(output, "styles/website.css")));
var INDEX = fs.readFileSync(path.join(output, "index.html")).toString();
assert(INDEX.indexOf("styles/website.css") > 0);
}, done);
});
it('should correctly include glossary in website', function(done) {
testGeneration(books[0], "website", function(output) {
assert(fs.existsSync(path.join(output, "GLOSSARY.html")));
}, done);
});
it('should correctly generate a multilingual book to website', function(done) {
testGeneration(books[2], "website", function(output) {
assert(fs.existsSync(path.join(output, "index.html")));
assert(fs.existsSync(path.join(output, "gitbook")));
assert(fs.existsSync(path.join(output, "fr/index.html")));
assert(fs.existsSync(path.join(output, "en/index.html")));
}, done);
it('should correctly copy assets', function() {
book.should.have.file("gitbook");
book.should.have.file("gitbook/app.js");
book.should.have.file("gitbook/style.css");
});
});
});

Some files were not shown because too many files have changed in this diff Show More