Compare commits

...

71 Commits

Author SHA1 Message Date
Samy Pesse d1e226dba5 Bump version to 3.0.0 2016-05-20 22:18:37 +02:00
Samy Pesse 1923d28d71 Update dependencies 2016-05-20 22:17:44 +02:00
Samy Pessé 484a306adb Fix error with isInRoot when empty root 2016-05-20 21:41:02 +02:00
Samy Pessé 5bfc9f2f56 Fix PathUtils.isInRoot on windows 2016-05-20 21:37:22 +02:00
Samy Pesse 86a65e5287 Improve appveyor configuration 2016-05-20 19:23:51 +02:00
Samy Pesse 956d301dd7 Add node v6 to appveyor matrix 2016-05-20 18:22:44 +02:00
Samy Pesse 00d1a7b5a6 Update theme-default@1.0.1 2016-05-20 18:17:34 +02:00
Samy Pessé 2718c9c017 Fix test command for windows 2016-05-19 10:45:25 +02:00
Samy Pesse 3234c1e03f Fix #1310: enforce language config in language books 2016-05-19 00:15:09 +02:00
Aaron O'Mullan c8f8eece92 Merge pull request #1308 from arebee/patch-1
Fix english phrasing for Node version.
2016-05-19 00:10:08 +02:00
Richard Burte 7394efa9dc Fix english phrasing for Node version. 2016-05-18 09:54:04 -07:00
Samy Pesse 3e4241fc88 Implement renderInline method and missing output methods for API 2016-05-17 19:45:48 +02:00
Samy Pesse a5ae79604c Implement renderBlock method for plugin API 2016-05-17 19:30:28 +02:00
Soreine fc4bdd68a1 Can only use file names 2016-05-17 16:47:52 +02:00
Soreine f83975b022 Mention the structure configuration variable 2016-05-17 16:37:13 +02:00
Soreine 167d3957bf Expose lookup method for parsable files 2016-05-17 12:48:03 +02:00
Samy Pessé a9571fd67d Bump version to 3.0.0-pre.15 2016-05-12 16:53:14 +02:00
Samy Pessé ac87f48ba5 Update theme-default@1.0.0 2016-05-12 16:52:19 +02:00
Samy Pessé 05403f527d Fix bug that can happen when configuration file doesn't exist 2016-05-12 16:45:09 +02:00
Samy Pessé 53c72f0806 Add api method getPageByPath for plugins 2016-05-12 16:29:36 +02:00
Samy Pessé c8c2a61f96 Fix #1289: add option to filter files when listing using fs.listAllFiles 2016-05-12 16:10:11 +02:00
Samy Pessé 08d780d1f6 Fix #1300: parse page frontmatter and content when listing pages 2016-05-12 15:48:02 +02:00
Samy Pessé 2e74fafa36 Add template method "getPageByPath" 2016-05-12 15:12:08 +02:00
Samy Pessé 28ac9ab88a Fix #1299: pass fileExists as global function for template, accept null/undefined as param 2016-05-12 15:04:30 +02:00
Samy Pessé 26acab4dfb Fix generation of ebook for multilingual book (gitbook pdf) 2016-05-12 14:29:25 +02:00
Samy Pessé d4b562e001 Fix get of cover for multilingual books 2016-05-12 14:12:43 +02:00
Soreine 61956e0646 Revert "Extend Book.getLanguageBook"
This reverts commit 7892259fe6.
2016-05-12 14:08:29 +02:00
Soreine 7892259fe6 Extend Book.getLanguageBook 2016-05-12 13:35:29 +02:00
Samy Pessé b97d1c7796 Fix languages not being added to json output 2016-05-12 13:25:09 +02:00
Samy Pessé a091c5b331 Fix path resolution when generating page with conrefs 2016-05-12 12:19:00 +02:00
Samy Pessé 784b3379b5 Improve logging when generating multilingual book 2016-05-12 11:20:27 +02:00
Johan Preynat 62446e75d2 Update book's root folder definition in Changelog 2016-05-12 11:18:00 +02:00
Samy Pessé 8aa2f3075c Fix #1294: multilingual book should extend the book's config 2016-05-12 11:15:13 +02:00
Samy Pessé 839ee0385a Add method "getLanguageBook" to book model 2016-05-12 11:02:37 +02:00
Samy Pesse d6b60c6116 Adapt eslint configuration for mocha 2016-05-11 16:56:18 +02:00
Samy Pesse 9013f02850 Add test for transform option of ConrefLoader 2016-05-11 15:08:10 +02:00
Samy Pesse 961d43f085 Fix #1293: replace block shortcuts when including files 2016-05-11 14:45:54 +02:00
Samy Pessé 7bd49606e3 Merge pull request #1291 from GitbookIO/summary-modifiers
Summary modifiers
2016-05-11 13:57:21 +02:00
Samy Pesse d7c8635350 Increase mocha timeout 2016-05-11 13:57:00 +02:00
Soreine 19e9ff81d2 Tweak insertPart signature 2016-05-11 13:11:32 +02:00
Soreine e137bf632a Adds editArticleRef 2016-05-11 13:11:32 +02:00
Soreine 2fef898a1e Modifier moveArticleAfter 2016-05-11 13:11:32 +02:00
Soreine 0945807184 Greatly simplify moveArticle's implementation 2016-05-11 13:11:32 +02:00
Soreine d85663b153 Tests remove/insert articles and update insert signature 2016-05-11 13:11:31 +02:00
Soreine 481a7d790c Insert/remove part 2016-05-11 13:11:31 +02:00
Samy Pesse ef589a6b13 Switch tests to mocha while keeping jest structure 2016-05-11 13:02:24 +02:00
Samy Pesse d5c4af3377 Add immutable ignore instance for book 2016-05-11 13:02:24 +02:00
Soreine 0b4df4cdf2 Precise h2 headings for Glossary 2016-05-11 10:55:34 +02:00
Samy Pessé d6814dc48e Don't copy config file as asset 2016-05-10 15:23:59 +02:00
Samy Pessé ce50a1d50f Bump version to 3.0.0-pre.14 2016-05-10 14:37:22 +02:00
Samy Pessé a76e018d36 Update theme-default@1.0.0-pre.10 2016-05-10 14:37:05 +02:00
Samy Pessé b06f1fd677 Fix article.depth starting at 2 2016-05-10 14:36:03 +02:00
Samy Pessé 3d8b1880dc Fix page's title in pdf header/footer 2016-05-10 14:25:50 +02:00
Samy Pessé 1a13c8c9bb Add option "--no-live" for command serve 2016-05-10 13:14:32 +02:00
Samy Pessé 852fae3a09 Fix #1232: add option "--no-watch" to disable watcher and live reloading 2016-05-10 13:08:19 +02:00
Samy Pessé d342d31c8e Fix error when summary contains entry without ref 2016-05-10 12:12:19 +02:00
Samy Pesse 5b3f54fbc8 Fix "gitbook init" when article has no ref 2016-05-10 09:55:22 +02:00
Samy Pessé 308ece784c Merge pull request #1288 from todvora/patch-1
Fixed summary encoding
2016-05-10 08:55:40 +02:00
Tomas Dvorak 0758ddf04e Fixed summary encoding 2016-05-10 08:17:52 +02:00
Samy Pesse f24d9aef73 Add test for block replaced with anchors 2016-05-09 22:17:46 +02:00
Samy Pesse f8bb55f258 Adapt unit tests for blocks 2016-05-09 22:01:25 +02:00
Samy Pesse 76daebeb84 Fix unit test for templateBlock using a removed method "getParse" 2016-05-09 18:41:21 +02:00
Samy Pesse b0d366b7e8 Fix linting warning in models/templateBlock.js 2016-05-08 22:45:26 +02:00
Samy Pesse 3d0054dd64 Fix #1286: by default blocks should not be parsable 2016-05-08 22:33:41 +02:00
Samy Pessé 13bc89ff44 Merge pull request #1285 from GitbookIO/api/summary
[WIP] Api for plugins to access summary
2016-05-08 22:28:15 +02:00
Samy Pesse bb910f984e Improve comment for API summary.walk 2016-05-08 22:26:46 +02:00
Samy Pessé e576fdea14 Merge pull request #1278 from dylang/patch-1
bump bash-color to version that has a license
2016-05-08 18:44:53 +02:00
Samy Pesse c7864927ec Add method summary.getArticleByPath to summary api 2016-05-08 11:08:27 +02:00
Samy Pesse 54956de59f Start API for plugins to access the summary 2016-05-08 10:33:45 +02:00
Samy Pesse 2cbd30d1f2 Fix command "gitbook init" 2016-05-07 00:24:12 +02:00
Dylan Greene 55614f1675 bump bash-color to version that has a license
Version currently being used does not have a license making it difficult for companies to use.
2016-05-06 09:56:43 -04:00
105 changed files with 1444 additions and 461 deletions
+4 -2
View File
@@ -13,8 +13,10 @@
"env": { "env": {
"node": true, "node": true,
"browser": true, "browser": true,
"jest": true, "mocha": true
"jasmine": true },
"globals": {
"expect": true
}, },
"extends": "eslint:recommended" "extends": "eslint:recommended"
} }
+1 -1
View File
@@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- `links.sidebar` configuration is no longer supported, use summary sections instead - `links.sidebar` configuration is no longer supported, use summary sections instead
- `pdf.headerTemplate` and `pdf.footerTemplate` have been replaced by a template in theme/book: `_layout/ebook/pdf_header.html` and `_layout/ebook/pdf_footer.html` - `pdf.headerTemplate` and `pdf.footerTemplate` have been replaced by a template in theme/book: `_layout/ebook/pdf_header.html` and `_layout/ebook/pdf_footer.html`
- Markdown parser is now using CommonMark - Markdown parser is now using CommonMark
- Root folder for the book can be specified in a `.gitbook` file - Root folder for the book can be specified in the `"root"` property of the `book.json` file
- Multi-lingual books share assets folder - Multi-lingual books share assets folder
- YAML front matter is parsed and can extend page's properties - YAML front matter is parsed and can extend page's properties
- Fix `uk` translation - Fix `uk` translation
+9
View File
@@ -5,13 +5,18 @@ init:
# Test against these versions of Node.js. # Test against these versions of Node.js.
environment: environment:
matrix: matrix:
- nodejs_version: "6"
- nodejs_version: "5" - nodejs_version: "5"
- nodejs_version: "4" - nodejs_version: "4"
matrix:
fast_finish: true
# Install scripts. (runs after repo cloning) # Install scripts. (runs after repo cloning)
install: install:
# Get the latest stable version of Node.js or io.js # Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version - ps: Install-Product node $env:nodejs_version
- set CI=true
# install svgexport # install svgexport
- npm install svgexport -g - npm install svgexport -g
# install modules # install modules
@@ -27,3 +32,7 @@ test_script:
# Don't actually build. # Don't actually build.
build: off build: off
# Clone only last commit
shallow_clone: true
clone_depth: 1
+13
View File
@@ -7,6 +7,7 @@ GitBook allows you to customize your book using a flexible configuration. These
| Variable | Description | | Variable | Description |
| -------- | ----------- | | -------- | ----------- |
| `root` | Path to the root folder containing all the book's files, except `book.json`| | `root` | Path to the root folder containing all the book's files, except `book.json`|
| `structure` | To specify paths for Readme, Summary, Glossary etc. See [Structure paragraph](#structure). |
| `title` | Title of your book, default value is extracted from the README. On GitBook.com this field is pre-filled. | | `title` | Title of your book, default value is extracted from the README. On GitBook.com this field is pre-filled. |
| `description` | Description of your book, default value is extracted from the README. On GitBook.com this field is pre-filled. | | `description` | Description of your book, default value is extracted from the README. On GitBook.com this field is pre-filled. |
| `author` | Name of the author. On GitBook.com this field is pre-filled. | | `author` | Name of the author. On GitBook.com this field is pre-filled. |
@@ -32,6 +33,18 @@ Since version 3.0.0, GitBook can use themes. See [the theming section](themes/RE
| -------- | ----------- | | -------- | ----------- |
| `theme` | The theme to use for the book | | `theme` | The theme to use for the book |
### Structure
In addition to the `root` variable, you can tell Gitbook the name of the files for Readme, Summary, Glossary, Languages (instead of using the default names such as `README.md`).
These files must be at the root of your book (or the root of every language book). Paths such as `dir/MY_README.md` are not accepted.
| Variable | Description |
| -------- | ----------- |
| `structure.readme` | Readme file name (defaults to `README.md`) |
| `structure.summary` | Summary file name (defaults to `SUMMARY.md`) |
| `structure.glossary` | Glossary file name (defaults to `GLOSSARY.md`) |
| `structure.languages` | Languages file name (defaults to `LANGS.md`) |
### PDF Options ### PDF Options
PDF Output can be customized using a set of options in the `book.json`: PDF Output can be customized using a set of options in the `book.json`:
+3 -3
View File
@@ -2,13 +2,13 @@
Allows you to specify terms and their respective definitions to be displayed as annotations. Based on those terms, GitBook will automatically build an index and highlight those terms in pages. Allows you to specify terms and their respective definitions to be displayed as annotations. Based on those terms, GitBook will automatically build an index and highlight those terms in pages.
The `GLOSSARY.md` format is very simple : The `GLOSSARY.md` format is a list of `h2` headings, along with a description paragraph:
```markdown ```markdown
# Term ## Term
Definition for this term Definition for this term
# Another term ## Another term
With it's definition, this can contain bold text With it's definition, this can contain bold text
and all other kinds of inline markup ... and all other kinds of inline markup ...
``` ```
+1 -1
View File
@@ -37,7 +37,7 @@ var filepath = output.resolve('myimage.png');
var fileurl = output.toURL('mychapter/README.md'); var fileurl = output.toURL('mychapter/README.md');
// Write a file in the output folder // Write a file in the output folder
output.write('hello.txt', 'Hello World') output.writeFile('hello.txt', 'Hello World')
.then(function() { ... }); .then(function() { ... });
// Copy a file to the output folder // Copy a file to the output folder
+1 -1
View File
@@ -14,7 +14,7 @@ It integrates well with the [GitBook Editor](https://www.gitbook.com/editor).
Installing GitBook is easy and straightforward. Your system just needs to meet these two requirements: Installing GitBook is easy and straightforward. Your system just needs to meet these two requirements:
* NodeJS (v4.0.0 and above are adviced) * NodeJS (v4.0.0 and above is recommended)
* Windows, Linux, Unix, or Mac OS X * Windows, Linux, Unix, or Mac OS X
##### Install with NPM ##### Install with NPM
-25
View File
@@ -1,25 +0,0 @@
var path = require('path');
var fs = require('fs');
var matchers = {
/**
Verify that a file exists in a directory
*/
toHaveFile: function () {
return {
compare: function (actual, expected) {
var filePath = path.join(actual, expected);
var exists = fs.existsSync(filePath);
return {
pass: exists
};
}
};
}
};
jasmine.getEnv().beforeEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
jasmine.addMatchers(matchers);
});
+1 -1
View File
@@ -3,7 +3,7 @@ var initBook = require('../init');
describe('initBook', function() { describe('initBook', function() {
pit('should create a README and SUMMARY for empty book', function() { it('should create a README and SUMMARY for empty book', function() {
var dir = tmp.dirSync(); var dir = tmp.dirSync();
return initBook(dir.name) return initBook(dir.name)
+1 -3
View File
@@ -1,5 +1,3 @@
var Config = require('../models/config');
/** /**
Decode changes from a JS API to a config object Decode changes from a JS API to a config object
@@ -13,7 +11,7 @@ function decodeGlobal(config, result) {
delete values.generator; delete values.generator;
delete values.output; delete values.output;
return Config.updateValues(config, values); return config.updateValues(values);
} }
module.exports = decodeGlobal; module.exports = decodeGlobal;
+84 -2
View File
@@ -5,11 +5,15 @@ var fs = require('../utils/fs');
var Plugins = require('../plugins'); var Plugins = require('../plugins');
var deprecate = require('./deprecate'); var deprecate = require('./deprecate');
var encodeConfig = require('./encodeConfig');
var encodeNavigation = require('./encodeNavigation');
var fileToURL = require('../output/helper/fileToURL'); var fileToURL = require('../output/helper/fileToURL');
var defaultBlocks = require('../constants/defaultBlocks'); var defaultBlocks = require('../constants/defaultBlocks');
var gitbook = require('../gitbook'); var gitbook = require('../gitbook');
var parsers = require('../parsers');
var encodeConfig = require('./encodeConfig');
var encodeSummary = require('./encodeSummary');
var encodeNavigation = require('./encodeNavigation');
var encodePage = require('./encodePage');
/** /**
Encode a global context into a JS object Encode a global context into a JS object
@@ -29,6 +33,7 @@ function encodeGlobal(output) {
var result = { var result = {
log: logger, log: logger,
config: encodeConfig(output, book.getConfig()), config: encodeConfig(output, book.getConfig()),
summary: encodeSummary(output, book.getSummary()),
/** /**
Check if the book is a multilingual book Check if the book is a multilingual book
@@ -78,6 +83,47 @@ function encodeGlobal(output) {
return path.resolve(book.getContentRoot(), fileName); return path.resolve(book.getContentRoot(), fileName);
}, },
/**
Resolve a page by it path
@param {String} filePath
@return {String}
*/
getPageByPath: function(filePath) {
var page = output.getPage(filePath);
if (!page) return undefined;
return encodePage(output, page);
},
/**
Render a block of text (markdown/asciidoc)
@param {String} type
@param {String} text
@return {Promise<String>}
*/
renderBlock: function(type, text) {
var parser = parsers.get(type);
return parser.parsePage(text)
.get('content');
},
/**
Render an inline text (markdown/asciidoc)
@param {String} type
@param {String} text
@return {Promise<String>}
*/
renderInline: function(type, text) {
var parser = parsers.get(type);
return parser.parseInline(text)
.get('content');
},
template: { template: {
/** /**
Apply a templating block and returns its result Apply a templating block and returns its result
@@ -125,6 +171,21 @@ function encodeGlobal(output) {
return fileToURL(output, filePath); return fileToURL(output, filePath);
}, },
/**
Check that a file exists.
@param {String} fileName
@return {Promise}
*/
hasFile: function(fileName, content) {
return Promise()
.then(function() {
var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
return fs.exists(filePath);
});
},
/** /**
Write a file to the output folder, Write a file to the output folder,
It creates the required folder It creates the required folder
@@ -143,6 +204,27 @@ function encodeGlobal(output) {
return fs.writeFile(filePath, content); return fs.writeFile(filePath, content);
}); });
}); });
},
/**
Copy a file to the output folder
It creates the required folder.
@param {String} inputFile
@param {String} outputFile
@param {Buffer} content
@return {Promise}
*/
copyFile: function(inputFile, outputFile, content) {
return Promise()
.then(function() {
var outputFilePath = PathUtils.resolveInRoot(outputFolder, outputFile);
return fs.ensureFile(outputFilePath)
.then(function() {
return fs.copy(inputFile, outputFilePath);
});
});
} }
}, },
+51
View File
@@ -0,0 +1,51 @@
var encodeSummaryArticle = require('../json/encodeSummaryArticle');
/**
Encode summary to provide an API to plugin
@param {Output} output
@param {Config} config
@return {Object}
*/
function encodeSummary(output, summary) {
var result = {
/**
Iterate over the summary, it stops when the "iter" returns false
@param {Function} iter
*/
walk: function (iter) {
summary.getArticle(function(article) {
var jsonArticle = encodeSummaryArticle(article, false);
return iter(jsonArticle);
});
},
/**
Get an article by its level
@param {String} level
@return {Object}
*/
getArticleByLevel: function(level) {
var article = summary.getByLevel(level);
return (article? encodeSummaryArticle(article) : undefined);
},
/**
Get an article by its path
@param {String} level
@return {Object}
*/
getArticleByPath: function(level) {
var article = summary.getByPath(level);
return (article? encodeSummaryArticle(article) : undefined);
}
};
return result;
}
module.exports = encodeSummary;
+5 -1
View File
@@ -11,5 +11,9 @@ module.exports = {
// Modifiers // Modifiers
SummaryModifier: Modifiers.Summary, SummaryModifier: Modifiers.Summary,
ConfigModifier: Modifiers.Config ConfigModifier: Modifiers.Config,
// Constants
CONFIG_FILES: require('./constants/configFiles.js'),
IGNORE_FILES: require('./constants/ignoreFiles.js')
}; };
+7 -5
View File
@@ -18,8 +18,10 @@ module.exports = function(format) {
options.log options.log
], ],
exec: function(args, kwargs) { exec: function(args, kwargs) {
var extension = '.' + format;
// Output file will be stored in // Output file will be stored in
var outputFile = args[1] || ('book.' + format); var outputFile = args[1] || ('book' + extension);
// Create temporary directory // Create temporary directory
var outputFolder = tmp.dirSync().name; var outputFolder = tmp.dirSync().name;
@@ -42,23 +44,23 @@ module.exports = function(format) {
var languages = book.getLanguages(); var languages = book.getLanguages();
if (book.isMultilingual()) { if (book.isMultilingual()) {
return Promise.ForEach(languages, function(lang) { return Promise.forEach(languages.getList(), function(lang) {
var langID = lang.getID(); var langID = lang.getID();
var langOutputFile = path.join( var langOutputFile = path.join(
path.dirname(outputFile), path.dirname(outputFile),
path.basename(outputFile, format) + '_' + langID + '.' + format path.basename(outputFile, extension) + '_' + langID + extension
); );
return fs.copy( return fs.copy(
path.resolve(outputFolder, langID, 'index.' + format), path.resolve(outputFolder, langID, 'index' + extension),
langOutputFile langOutputFile
); );
}) })
.thenResolve(languages.getCount()); .thenResolve(languages.getCount());
} else { } else {
return fs.copy( return fs.copy(
path.resolve(outputFolder, 'index.' + format), path.resolve(outputFolder, 'index' + extension),
outputFile outputFile
).thenResolve(1); ).thenResolve(1);
} }
+1 -1
View File
@@ -4,7 +4,7 @@ var options = require('./options');
var initBook = require('../init'); var initBook = require('../init');
module.exports = { module.exports = {
name: 'install [book]', name: 'init [book]',
description: 'setup and create files for chapters', description: 'setup and create files for chapters',
options: [ options: [
options.log options.log
+45 -10
View File
@@ -16,12 +16,26 @@ var watch = require('./watch');
var server, lrServer, lrPath; var server, lrServer, lrPath;
function waitForCtrlC() {
var d = Promise.defer();
process.on('SIGINT', function() {
d.resolve();
});
return d.promise;
}
function generateBook(args, kwargs) { function generateBook(args, kwargs) {
var port = kwargs.port; var port = kwargs.port;
var outputFolder = getOutputFolder(args); var outputFolder = getOutputFolder(args);
var book = getBook(args, kwargs); var book = getBook(args, kwargs);
var Generator = Output.getGenerator(kwargs.format); var Generator = Output.getGenerator(kwargs.format);
var hasWatch = kwargs['watch'];
var hasLiveReloading = kwargs['live'];
// Stop server if running // Stop server if running
if (server.isRunning()) console.log('Stopping server'); if (server.isRunning()) console.log('Stopping server');
@@ -29,8 +43,10 @@ function generateBook(args, kwargs) {
.then(function() { .then(function() {
return Parse.parseBook(book) return Parse.parseBook(book)
.then(function(resultBook) { .then(function(resultBook) {
// Enable livereload plugin if (hasLiveReloading) {
resultBook = ConfigModifier.addPlugin(resultBook, 'livereload'); // Enable livereload plugin
resultBook = ConfigModifier.addPlugin(resultBook, 'livereload');
}
return Output.generate(Generator, resultBook, { return Output.generate(Generator, resultBook, {
root: outputFolder root: outputFolder
@@ -45,7 +61,7 @@ function generateBook(args, kwargs) {
.then(function() { .then(function() {
console.log('Serving book on http://localhost:'+port); console.log('Serving book on http://localhost:'+port);
if (lrPath) { if (lrPath && hasLiveReloading) {
// trigger livereload // trigger livereload
lrServer.changed({ lrServer.changed({
body: { body: {
@@ -55,7 +71,9 @@ function generateBook(args, kwargs) {
} }
}) })
.then(function() { .then(function() {
if (!kwargs.watch) return; if (!hasWatch) {
return waitForCtrlC();
}
return watch(book.getRoot()) return watch(book.getRoot())
.then(function(filepath) { .then(function(filepath) {
@@ -84,7 +102,12 @@ module.exports = {
}, },
{ {
name: 'watch', name: 'watch',
description: 'Enable/disable file watcher', description: 'Enable file watcher and live reloading',
defaults: true
},
{
name: 'live',
description: 'Enable live reloading',
defaults: true defaults: true
}, },
options.log, options.log,
@@ -92,13 +115,25 @@ module.exports = {
], ],
exec: function(args, kwargs) { exec: function(args, kwargs) {
server = new Server(); server = new Server();
lrServer = tinylr({}); var hasWatch = kwargs['watch'];
var hasLiveReloading = kwargs['live'];
return Promise.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport) return Promise()
.then(function() {
if (!hasWatch || !hasLiveReloading) {
return;
}
lrServer = tinylr({});
return Promise.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport)
.then(function() {
console.log('Live reload server started on port:', kwargs.lrport);
console.log('Press CTRL+C to quit ...');
console.log('');
});
})
.then(function() { .then(function() {
console.log('Live reload server started on port:', kwargs.lrport);
console.log('Press CTRL+C to quit ...');
console.log('');
return generateBook(args, kwargs); return generateBook(args, kwargs);
}); });
} }
+9 -10
View File
@@ -1,7 +1,6 @@
jest.autoMockOff(); var createMockFS = require('../mock');
describe('MockFS', function() { describe('MockFS', function() {
var createMockFS = require('../mock');
var fs = createMockFS({ var fs = createMockFS({
'README.md': 'Hello World', 'README.md': 'Hello World',
'SUMMARY.md': '# Summary', 'SUMMARY.md': '# Summary',
@@ -15,35 +14,35 @@ describe('MockFS', function() {
}); });
describe('exists', function() { describe('exists', function() {
pit('must return true for a file', function() { it('must return true for a file', function() {
return fs.exists('README.md') return fs.exists('README.md')
.then(function(result) { .then(function(result) {
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
}); });
pit('must return false for a non existing file', function() { it('must return false for a non existing file', function() {
return fs.exists('README_NOTEXISTS.md') return fs.exists('README_NOTEXISTS.md')
.then(function(result) { .then(function(result) {
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
}); });
pit('must return true for a directory', function() { it('must return true for a directory', function() {
return fs.exists('folder') return fs.exists('folder')
.then(function(result) { .then(function(result) {
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
}); });
pit('must return true for a deep file', function() { it('must return true for a deep file', function() {
return fs.exists('folder/test.md') return fs.exists('folder/test.md')
.then(function(result) { .then(function(result) {
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
}); });
pit('must return true for a deep file (2)', function() { it('must return true for a deep file (2)', function() {
return fs.exists('folder/folder2/hello.md') return fs.exists('folder/folder2/hello.md')
.then(function(result) { .then(function(result) {
expect(result).toBeTruthy(); expect(result).toBeTruthy();
@@ -52,14 +51,14 @@ describe('MockFS', function() {
}); });
describe('readAsString', function() { describe('readAsString', function() {
pit('must return content for a file', function() { it('must return content for a file', function() {
return fs.readAsString('README.md') return fs.readAsString('README.md')
.then(function(result) { .then(function(result) {
expect(result).toBe('Hello World'); expect(result).toBe('Hello World');
}); });
}); });
pit('must return content for a deep file', function() { it('must return content for a deep file', function() {
return fs.readAsString('folder/test.md') return fs.readAsString('folder/test.md')
.then(function(result) { .then(function(result) {
expect(result).toBe('Cool'); expect(result).toBe('Cool');
@@ -68,7 +67,7 @@ describe('MockFS', function() {
}); });
describe('readDir', function() { describe('readDir', function() {
pit('must return content for a directory', function() { it('must return content for a directory', function() {
return fs.readDir('./') return fs.readDir('./')
.then(function(files) { .then(function(files) {
expect(files.size).toBe(3); expect(files.size).toBe(3);
+5 -2
View File
@@ -47,8 +47,11 @@ function initBook(rootFolder) {
// Write pages // Write pages
return Promise.forEach(articles, function(article) { return Promise.forEach(articles, function(article) {
var filePath = path.join(rootFolder, article.getPath()); var articlePath = article.getPath();
if (!filePath) return; var filePath = articlePath? path.join(rootFolder, articlePath) : null;
if (!filePath) {
return;
}
return fs.assertFile(filePath, function() { return fs.assertFile(filePath, function() {
return fs.ensureFile(filePath) return fs.ensureFile(filePath)
+4
View File
@@ -4,6 +4,7 @@ var gitbook = require('../gitbook');
var encodeSummary = require('./encodeSummary'); var encodeSummary = require('./encodeSummary');
var encodeGlossary = require('./encodeGlossary'); var encodeGlossary = require('./encodeGlossary');
var encodeReadme = require('./encodeReadme'); var encodeReadme = require('./encodeReadme');
var encodeLanguages = require('./encodeLanguages');
/** /**
Encode a book to JSON Encode a book to JSON
@@ -22,6 +23,9 @@ function encodeBookToJson(book) {
glossary: encodeGlossary(book.getGlossary()), glossary: encodeGlossary(book.getGlossary()),
readme: encodeReadme(book.getReadme()), readme: encodeReadme(book.getReadme()),
config: book.getConfig().getValues().toJS(), config: book.getConfig().getValues().toJS(),
languages: book.isMultilingual()? encodeLanguages(book.getLanguages()) : undefined,
gitbook: { gitbook: {
version: gitbook.version, version: gitbook.version,
time: gitbook.START_TIME time: gitbook.START_TIME
+26
View File
@@ -0,0 +1,26 @@
var encodeFile = require('./encodeFile');
/**
Encode a languages listing to JSON
@param {Languages}
@return {Object}
*/
function encodeLanguages(languages) {
var file = languages.getFile();
var list = languages.getList();
return {
file: encodeFile(file),
list: list
.valueSeq()
.map(function(lang) {
return {
id: lang.getID(),
title: lang.getTitle()
};
}).toJS()
};
}
module.exports = encodeLanguages;
+1
View File
@@ -20,6 +20,7 @@ function encodeSummaryArticle(article, recursive) {
anchor: article.getAnchor(), anchor: article.getAnchor(),
url: article.getUrl(), url: article.getUrl(),
path: article.getPath(), path: article.getPath(),
ref: article.getRef(),
articles: articles articles: articles
}; };
} }
+2 -1
View File
@@ -7,5 +7,6 @@ module.exports = {
encodePage: require('./encodePage'), encodePage: require('./encodePage'),
encodeSummary: require('./encodeSummary'), encodeSummary: require('./encodeSummary'),
encodeSummaryArticle: require('./encodeSummaryArticle'), encodeSummaryArticle: require('./encodeSummaryArticle'),
encodeReadme: require('./encodeReadme') encodeReadme: require('./encodeReadme'),
encodeLanguages: require('./encodeLanguages')
}; };
+1 -4
View File
@@ -1,10 +1,7 @@
jest.autoMockOff();
var Immutable = require('immutable'); var Immutable = require('immutable');
var Config = require('../config');
describe('Config', function() { describe('Config', function() {
var Config = require('../config');
var config = Config.createWithValues({ var config = Config.createWithValues({
hello: { hello: {
world: 1, world: 1,
+4 -6
View File
@@ -1,10 +1,8 @@
jest.autoMockOff(); var File = require('../file');
var Glossary = require('../glossary');
var GlossaryEntry = require('../glossaryEntry');
describe('Glossary', function() { describe('Glossary', function() {
var File = require('../file');
var Glossary = require('../glossary');
var GlossaryEntry = require('../glossaryEntry');
var glossary = Glossary.createFromEntries(File(), [ var glossary = Glossary.createFromEntries(File(), [
{ {
name: 'Hello World', name: 'Hello World',
@@ -30,7 +28,7 @@ describe('Glossary', function() {
}); });
describe('toText', function() { describe('toText', function() {
pit('return as markdown', function() { it('return as markdown', function() {
return glossary.toText('.md') return glossary.toText('.md')
.then(function(text) { .then(function(text) {
expect(text).toContain('# Glossary'); expect(text).toContain('# Glossary');
+1 -3
View File
@@ -1,8 +1,6 @@
jest.autoMockOff(); var GlossaryEntry = require('../glossaryEntry');
describe('GlossaryEntry', function() { describe('GlossaryEntry', function() {
var GlossaryEntry = require('../glossaryEntry');
describe('getID', function() { describe('getID', function() {
it('must return a normalized ID', function() { it('must return a normalized ID', function() {
var entry = new GlossaryEntry({ var entry = new GlossaryEntry({
+17 -2
View File
@@ -1,6 +1,7 @@
describe('PluginDependency', function() { var Immutable = require('immutable');
var PluginDependency = require('../pluginDependency'); var PluginDependency = require('../pluginDependency');
describe('PluginDependency', function() {
describe('createFromString', function() { describe('createFromString', function() {
it('must parse name', function() { it('must parse name', function() {
var plugin = PluginDependency.createFromString('hello'); var plugin = PluginDependency.createFromString('hello');
@@ -42,6 +43,20 @@ describe('PluginDependency', function() {
}); });
}); });
}); });
describe('listToArray', function() {
var list = PluginDependency.listToArray(Immutable.List([
PluginDependency.createFromString('hello@1.0.0'),
PluginDependency.createFromString('noversion'),
PluginDependency.createFromString('-disabled')
]));
expect(list).toEqual([
'hello@1.0.0',
'noversion',
'-disabled'
]);
});
}); });
+17 -4
View File
@@ -8,11 +8,18 @@ describe('Summary', function() {
articles: [ articles: [
{ {
title: 'My First Article', title: 'My First Article',
path: 'README.md' ref: 'README.md'
}, },
{ {
title: 'My Second Article', title: 'My Second Article',
path: 'article.md' ref: 'article.md'
},
{
title: 'Article without ref'
},
{
title: 'Article with absolute ref',
ref: 'https://google.fr'
} }
] ]
}, },
@@ -33,7 +40,7 @@ describe('Summary', function() {
var part = summary.getByLevel('1'); var part = summary.getByLevel('1');
expect(part).toBeDefined(); expect(part).toBeDefined();
expect(part.getArticles().size).toBe(2); expect(part.getArticles().size).toBe(4);
}); });
it('can return a Part (2)', function() { it('can return a Part (2)', function() {
@@ -66,10 +73,16 @@ describe('Summary', function() {
expect(article).toBeDefined(); expect(article).toBeDefined();
expect(article.getTitle()).toBe('My Second Article'); expect(article.getTitle()).toBe('My Second Article');
}); });
it('return undefined if not found', function() {
var article = summary.getByPath('NOT_EXISTING.md');
expect(article).toBeFalsy();
});
}); });
describe('toText', function() { describe('toText', function() {
pit('return as markdown', function() { it('return as markdown', function() {
return summary.toText('.md') return summary.toText('.md')
.then(function(text) { .then(function(text) {
expect(text).toContain('# Summary'); expect(text).toContain('# Summary');
+60 -13
View File
@@ -6,14 +6,16 @@ describe('TemplateBlock', function() {
var TemplateBlock = require('../templateBlock'); var TemplateBlock = require('../templateBlock');
describe('create', function() { describe('create', function() {
pit('must initialize a simple TemplateBlock from a function', function() { it('must initialize a simple TemplateBlock from a function', function() {
var templateBlock = TemplateBlock.create('sayhello', function(block) { var templateBlock = TemplateBlock.create('sayhello', function(block) {
return '<p>Hello, World!</p>'; return {
body: '<p>Hello, World!</p>',
parse: true
};
}); });
// Check basic templateBlock properties // Check basic templateBlock properties
expect(templateBlock.getName()).toBe('sayhello'); expect(templateBlock.getName()).toBe('sayhello');
expect(templateBlock.getParse()).toBeTruthy();
expect(templateBlock.getEndTag()).toBe('endsayhello'); expect(templateBlock.getEndTag()).toBe('endsayhello');
expect(templateBlock.getBlocks().size).toBe(0); expect(templateBlock.getBlocks().size).toBe(0);
expect(templateBlock.getExtensionName()).toBe('BlocksayhelloExtension'); expect(templateBlock.getExtensionName()).toBe('BlocksayhelloExtension');
@@ -33,10 +35,13 @@ describe('TemplateBlock', function() {
describe('getShortcuts', function() { describe('getShortcuts', function() {
it('must return undefined if no shortcuts', function() { it('must return undefined if no shortcuts', function() {
var templateBlock = TemplateBlock.create('sayhello', function(block) { var templateBlock = TemplateBlock.create('sayhello', function(block) {
return '<p>Hello, World!</p>'; return {
body: '<p>Hello, World!</p>',
parse: true
};
}); });
expect(templateBlock.getShortcuts()).not.toBeDefined(); expect(templateBlock.getShortcuts()).toNotExist();
}); });
it('must return complete shortcut', function() { it('must return complete shortcut', function() {
@@ -62,9 +67,42 @@ describe('TemplateBlock', function() {
}); });
describe('toNunjucksExt()', function() { describe('toNunjucksExt()', function() {
pit('must create a valid nunjucks extension', function() { it('should replace by block anchor', function() {
var templateBlock = TemplateBlock.create('sayhello', function(block) { var templateBlock = TemplateBlock.create('sayhello', function(block) {
return '<p>Hello, World!</p>'; return 'Hello';
});
var blocks = {};
// Create a fresh Nunjucks environment
var env = new nunjucks.Environment(null, { autoescape: false });
// Add template block to environement
var Ext = templateBlock.toNunjucksExt({}, blocks);
env.addExtension(templateBlock.getExtensionName(), new Ext());
// Render a template using the block
var src = '{% sayhello %}{% endsayhello %}';
return Promise.nfcall(env.renderString.bind(env), src)
.then(function(res) {
blocks = Immutable.fromJS(blocks);
expect(blocks.size).toBe(1);
var blockId = blocks.keySeq().get(0);
var block = blocks.get(blockId);
expect(res).toBe('{{-%' + blockId + '%-}}');
expect(block.get('body')).toBe('Hello');
expect(block.get('name')).toBe('sayhello');
});
});
it('must create a valid nunjucks extension', function() {
var templateBlock = TemplateBlock.create('sayhello', function(block) {
return {
body: '<p>Hello, World!</p>',
parse: true
};
}); });
// Create a fresh Nunjucks environment // Create a fresh Nunjucks environment
@@ -82,9 +120,12 @@ describe('TemplateBlock', function() {
}); });
}); });
pit('must apply block arguments correctly', function() { it('must apply block arguments correctly', function() {
var templateBlock = TemplateBlock.create('sayhello', function(block) { var templateBlock = TemplateBlock.create('sayhello', function(block) {
return '<'+block.kwargs.tag+'>Hello, '+block.kwargs.name+'!</'+block.kwargs.tag+'>'; return {
body: '<'+block.kwargs.tag+'>Hello, '+block.kwargs.name+'!</'+block.kwargs.tag+'>',
parse: true
};
}); });
// Create a fresh Nunjucks environment // Create a fresh Nunjucks environment
@@ -102,11 +143,14 @@ describe('TemplateBlock', function() {
}); });
}); });
pit('must accept an async function', function() { it('must accept an async function', function() {
var templateBlock = TemplateBlock.create('sayhello', function(block) { var templateBlock = TemplateBlock.create('sayhello', function(block) {
return Promise() return Promise()
.then(function() { .then(function() {
return 'Hello ' + block.body; return {
body: 'Hello ' + block.body,
parse: true
};
}); });
}); });
@@ -125,7 +169,7 @@ describe('TemplateBlock', function() {
}); });
}); });
pit('must handle nested blocks', function() { it('must handle nested blocks', function() {
var templateBlock = new TemplateBlock({ var templateBlock = new TemplateBlock({
name: 'yoda', name: 'yoda',
blocks: Immutable.List(['start', 'end']), blocks: Immutable.List(['start', 'end']),
@@ -136,7 +180,10 @@ describe('TemplateBlock', function() {
nested[blk.name] = blk.body.trim(); nested[blk.name] = blk.body.trim();
}); });
return '<p class="yoda">'+nested.end+' '+nested.start+'</p>'; return {
body: '<p class="yoda">'+nested.end+' '+nested.start+'</p>',
parse: true
};
} }
}); });
+29 -6
View File
@@ -1,6 +1,5 @@
var path = require('path'); var path = require('path');
var Immutable = require('immutable'); var Immutable = require('immutable');
var Ignore = require('ignore');
var Logger = require('../utils/logger'); var Logger = require('../utils/logger');
@@ -10,7 +9,7 @@ var Readme = require('./readme');
var Summary = require('./summary'); var Summary = require('./summary');
var Glossary = require('./glossary'); var Glossary = require('./glossary');
var Languages = require('./languages'); var Languages = require('./languages');
var Ignore = require('./ignore');
var Book = Immutable.Record({ var Book = Immutable.Record({
// Logger for outptu message // Logger for outptu message
@@ -128,8 +127,7 @@ Book.prototype.isFileIgnored = function(filename) {
filename = path.join(language, filename); filename = path.join(language, filename);
} }
return ignore.isFileIgnored(filename);
return ignore.filter([filename]).length == 0;
}; };
/** /**
@@ -177,6 +175,17 @@ Book.prototype.isLanguageBook = function() {
return Boolean(this.getLanguage()); return Boolean(this.getLanguage());
}; };
/**
Return a languages book
@param {String} language
@return {Book}
*/
Book.prototype.getLanguageBook = function(language) {
var books = this.getBooks();
return books.get(language);
};
/** /**
Add a new language book Add a new language book
@@ -221,6 +230,16 @@ Book.prototype.setConfig = function(config) {
return this.set('config', config); return this.set('config', config);
}; };
/**
Set the ignore instance for this book
@param {Ignore}
@return {Book}
*/
Book.prototype.setIgnore = function(ignore) {
return this.set('ignore', ignore);
};
/** /**
Change log level Change log level
@@ -305,12 +324,16 @@ Book.prototype.getDefaultGlossaryPath = function() {
*/ */
Book.createFromParent = function createFromParent(parent, language) { Book.createFromParent = function createFromParent(parent, language) {
var ignore = parent.getIgnore(); var ignore = parent.getIgnore();
var config = parent.getConfig();
// Set language in configuration
config = config.setValue('language', language);
return new Book({ return new Book({
// Inherits config. logegr and list of ignored files // Inherits config. logegr and list of ignored files
logger: parent.getLogger(), logger: parent.getLogger(),
config: parent.getConfig(), config: config,
ignore: Ignore().add(ignore), ignore: ignore,
language: language, language: language,
fs: FS.reduceScope(parent.getContentFS(), language) fs: FS.reduceScope(parent.getContentFS(), language)
+55 -15
View File
@@ -18,6 +18,16 @@ Config.prototype.getValues = function() {
return this.get('values'); return this.get('values');
}; };
/**
Change the file for the configuration
@param {File} file
@return {Config}
*/
Config.prototype.setFile = function(file) {
return this.set('file', file);
};
/** /**
Return a configuration value by its key path Return a configuration value by its key path
@@ -40,7 +50,7 @@ Config.prototype.getValue = function(keyPath, def) {
@param {String} key @param {String} key
@param {Mixed} value @param {Mixed} value
@return {Mixed} @return {Config}
*/ */
Config.prototype.setValue = function(keyPath, value) { Config.prototype.setValue = function(keyPath, value) {
keyPath = Config.keyToKeyPath(keyPath); keyPath = Config.keyToKeyPath(keyPath);
@@ -68,6 +78,20 @@ Config.prototype.getPluginDependencies = function() {
} }
}; };
/**
Return a plugin dependency by its name
@param {String} name
@return {PluginDependency}
*/
Config.prototype.getPluginDependency = function(name) {
var plugins = this.getPluginDependencies();
return plugins.find(function(dep) {
return dep.getName() === name;
});
};
/** /**
Update the list of plugins dependencies Update the list of plugins dependencies
@@ -75,11 +99,40 @@ Config.prototype.getPluginDependencies = function() {
@return {Config} @return {Config}
*/ */
Config.prototype.setPluginDependencies = function(deps) { Config.prototype.setPluginDependencies = function(deps) {
var plugins = PluginDependency.listFromArray(deps); var plugins = PluginDependency.listToArray(deps);
return this.setValue('plugins', plugins); return this.setValue('plugins', plugins);
}; };
/**
Update values for an existing configuration
@param {Object} values
@returns {Config}
*/
Config.prototype.updateValues = function(values) {
values = Immutable.fromJS(values);
return this.set('values', values);
};
/**
Update values for an existing configuration
@param {Config} config
@param {Object} values
@returns {Config}
*/
Config.prototype.mergeValues = function(values) {
var currentValues = this.getValues();
values = Immutable.fromJS(values);
currentValues = currentValues.mergeDeep(values);
return this.set('values', currentValues);
};
/** /**
Create a new config for a file Create a new config for a file
@@ -106,19 +159,6 @@ Config.createWithValues = function(values) {
}); });
}; };
/**
Update values for an existing configuration
@param {Config} config
@param {Object} values
@returns {Config}
*/
Config.updateValues = function(config, values) {
values = Immutable.fromJS(values);
return config.set('values', values);
};
/** /**
Convert a keyPath to an array of keys Convert a keyPath to an array of keys
+14 -8
View File
@@ -198,23 +198,29 @@ FS.prototype.listFiles = function(dirname) {
/** /**
List all files in a directory List all files in a directory
@param {String} dirname @param {String} dirName
@param {Function(dirName)} filterFn: call it for each file/directory to test if it should stop iterating
@return {Promise<List<String>>} @return {Promise<List<String>>}
*/ */
FS.prototype.listAllFiles = function(folder) { FS.prototype.listAllFiles = function(dirName, filterFn) {
var that = this; var that = this;
folder = folder || '.'; dirName = dirName || '.';
return this.readDir(folder) return this.readDir(dirName)
.then(function(files) { .then(function(files) {
return Promise.reduce(files, function(out, file) { return Promise.reduce(files, function(out, file) {
var isDirectory = pathIsFolder(file); var isDirectory = pathIsFolder(file);
var newDirName = path.join(dirName, file);
if (!isDirectory) { if (filterFn && filterFn(newDirName) === false) {
return out.push(path.join(folder, file)); return out;
} }
return that.listAllFiles(path.join(folder, file)) if (!isDirectory) {
return out.push(newDirName);
}
return that.listAllFiles(newDirName, filterFn)
.then(function(inner) { .then(function(inner) {
return out.concat(inner); return out.concat(inner);
}); });
@@ -223,7 +229,7 @@ FS.prototype.listAllFiles = function(folder) {
}; };
/** /**
Find a file in a folder (case incensitive) Find a file in a folder (case insensitive)
Return the found filename Return the found filename
@param {String} dirname @param {String} dirname
+42
View File
@@ -0,0 +1,42 @@
var Immutable = require('immutable');
var IgnoreMutable = require('ignore');
/*
Immutable version of node-ignore
*/
var Ignore = Immutable.Record({
ignore: new IgnoreMutable()
}, 'Ignore');
Ignore.prototype.getIgnore = function() {
return this.get('ignore');
};
/**
Test if a file is ignored by these rules
@param {String} filePath
@return {Boolean}
*/
Ignore.prototype.isFileIgnored = function(filename) {
var ignore = this.getIgnore();
return ignore.filter([filename]).length == 0;
};
/**
Add rules
@param {String}
@return {Ignore}
*/
Ignore.prototype.add = function(rule) {
var ignore = this.getIgnore();
var newIgnore = new IgnoreMutable();
newIgnore.add(ignore);
newIgnore.add(rule);
return this.set('ignore', newIgnore);
};
module.exports = Ignore;
+14
View File
@@ -1,6 +1,7 @@
var Immutable = require('immutable'); var Immutable = require('immutable');
var Book = require('./book'); var Book = require('./book');
var LocationUtils = require('../utils/location');
var Output = Immutable.Record({ var Output = Immutable.Record({
book: Book(), book: Book(),
@@ -52,6 +53,19 @@ Output.prototype.getState = function() {
return this.get('state'); return this.get('state');
}; };
/**
Return a page byt its file path
@param {String} filePath
@return {Page|undefined}
*/
Output.prototype.getPage = function(filePath) {
filePath = LocationUtils.normalize(filePath);
var pages = this.getPages();
return pages.get(filePath);
};
/** /**
Get root folder for output Get root folder for output
+5
View File
@@ -55,6 +55,11 @@ Parser.prototype.parsePage = function(content) {
return Promise(page(content)); return Promise(page(content));
}; };
Parser.prototype.parseInline = function(content) {
var inline = this.get('inline');
return Promise(inline(content));
};
Parser.prototype.parseLanguages = function(content) { Parser.prototype.parseLanguages = function(content) {
var langs = this.get('langs'); var langs = this.get('langs');
return Promise(langs(content)); return Promise(langs(content));
+23 -5
View File
@@ -29,6 +29,24 @@ PluginDependency.prototype.isEnabled = function() {
return this.get('enabled'); return this.get('enabled');
}; };
/**
Create a plugin with a name and a plugin
@param {String}
@return {Plugin|undefined}
*/
PluginDependency.create = function(name, version, enabled) {
if (is.undefined(enabled)) {
enabled = true;
}
return new PluginDependency({
name: name,
version: version || DEFAULT_VERSION,
enabled: Boolean(enabled)
});
};
/** /**
Create a plugin from a string Create a plugin from a string
@@ -90,15 +108,15 @@ PluginDependency.listFromArray = function(arr) {
/** /**
Export plugin dependencies as an array Export plugin dependencies as an array
@param {List<PluginDependency>} @param {List<PluginDependency>} list
@return {Array<String>} @return {Array<String>}
*/ */
PluginDependency.listToArray = function(arr) { PluginDependency.listToArray = function(list) {
return arr return list
.map(function(dep) { .map(function(dep) {
var result; var result = '';
if (dep.isEnabled()) { if (!dep.isEnabled()) {
result += '-'; result += '-';
} }
+6 -1
View File
@@ -74,7 +74,12 @@ Summary.prototype.getByLevel = function(level) {
*/ */
Summary.prototype.getByPath = function(filePath) { Summary.prototype.getByPath = function(filePath) {
return this.getArticle(function(article) { return this.getArticle(function(article) {
return (LocationUtils.areIdenticalPaths(article.getPath(), filePath)); var articlePath = article.getPath();
return (
articlePath &&
LocationUtils.areIdenticalPaths(articlePath, filePath)
);
}); });
}; };
+3 -3
View File
@@ -30,12 +30,13 @@ SummaryArticle.prototype.getArticles = function() {
}; };
/** /**
Return how deep the article is Return how deep the article is.
The README has a depth of 1
@return {Number} @return {Number}
*/ */
SummaryArticle.prototype.getDepth = function() { SummaryArticle.prototype.getDepth = function() {
return this.getLevel().split('.').length; return (this.getLevel().split('.').length - 1);
}; };
/** /**
@@ -53,7 +54,6 @@ SummaryArticle.prototype.getPath = function() {
return undefined; return undefined;
} }
var parts = ref.split('#'); var parts = ref.split('#');
var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref); var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref);
+5 -10
View File
@@ -22,19 +22,13 @@ var TemplateBlock = Immutable.Record({
blocks: Immutable.List(), blocks: Immutable.List(),
// List of shortcuts to replace with this block // List of shortcuts to replace with this block
shortcuts: Immutable.Map(), shortcuts: Immutable.Map()
parse: true
}, 'TemplateBlock'); }, 'TemplateBlock');
TemplateBlock.prototype.getName = function() { TemplateBlock.prototype.getName = function() {
return this.get('name'); return this.get('name');
}; };
TemplateBlock.prototype.getParse = function() {
return this.get('parse');
};
TemplateBlock.prototype.getEndTag = function() { TemplateBlock.prototype.getEndTag = function() {
return this.get('end') || ('end' + this.getName()); return this.get('end') || ('end' + this.getName());
}; };
@@ -77,6 +71,8 @@ TemplateBlock.prototype.getExtensionName = function() {
@return {Nunjucks.Extension} @return {Nunjucks.Extension}
*/ */
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) { TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
blocksOutput = blocksOutput || {};
var that = this; var that = this;
var name = this.getName(); var name = this.getName();
var endTag = this.getEndTag(); var endTag = this.getEndTag();
@@ -241,9 +237,8 @@ TemplateBlock.prototype.normalizeBlockResult = function(result) {
@return {String} @return {String}
*/ */
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) { TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
var parse = this.getParse();
var indexedKey; var indexedKey;
var toIndex = (!parse) || (result.post !== undefined); var toIndex = (!result.parse) || (result.post !== undefined);
if (toIndex) { if (toIndex) {
indexedKey = genKey(); indexedKey = genKey();
@@ -251,7 +246,7 @@ TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
} }
// Parsable block, just return it // Parsable block, just return it
if (parse) { if (result.parse) {
return result.body; return result.body;
} }
@@ -0,0 +1,35 @@
var addPlugin = require('../addPlugin');
var Config = require('../../../models/config');
var Book = require('../../../models/book');
describe('addPlugin', function() {
var config = Config.createWithValues({
plugins: ['hello', 'world', '-disabled']
});
var book = Book().setConfig(config);
it('should have correct state of dependencies', function() {
var disabledDep = config.getPluginDependency('disabled');
expect(disabledDep).toBeDefined();
expect(disabledDep.getVersion()).toEqual('*');
expect(disabledDep.isEnabled()).toBeFalsy();
});
it('should add the plugin to the list', function() {
var newBook = addPlugin(book, 'test');
var newConfig = newBook.getConfig();
var testDep = newConfig.getPluginDependency('test');
expect(testDep).toBeDefined();
expect(testDep.getVersion()).toEqual('*');
expect(testDep.isEnabled()).toBeTruthy();
var disabledDep = newConfig.getPluginDependency('disabled');
expect(disabledDep).toBeDefined();
expect(disabledDep.getVersion()).toEqual('*');
expect(disabledDep.isEnabled()).toBeFalsy();
});
});
+1 -4
View File
@@ -11,10 +11,7 @@ function addPlugin(book, plugin, version) {
var config = book.getConfig(); var config = book.getConfig();
var deps = config.getPluginDependencies(); var deps = config.getPluginDependencies();
var dep = PluginDependency({ var dep = PluginDependency.create(plugin, version);
name: plugin,
version: version
});
deps = deps.push(dep); deps = deps.push(dep);
config = config.setPluginDependencies(deps); config = config.setPluginDependencies(deps);
@@ -0,0 +1,78 @@
var Summary = require('../../../models/summary');
var SummaryArticle = require('../../../models/summaryArticle');
var File = require('../../../models/file');
describe('insertArticle', function() {
var insertArticle = require('../insertArticle');
var summary = Summary.createFromParts(File(), [
{
articles: [
{
title: '1.1',
path: '1.1'
},
{
title: '1.2',
path: '1.2'
}
]
},
{
title: 'Part I',
articles: [
{
title: '2.1',
path: '2.1',
articles: [
{
title: '2.1.1',
path: '2.1.1'
},
{
title: '2.1.2',
path: '2.1.2'
}
]
},
{
title: '2.2',
path: '2.2'
}
]
}
]);
it('should insert an article at a given level', function() {
var article = SummaryArticle.create({
title: 'Inserted'
}, 'fake.level');
var newSummary = insertArticle(summary, article, '2.1.1');
var inserted = newSummary.getByLevel('2.1.1');
var nextOne = newSummary.getByLevel('2.1.2');
expect(inserted.getTitle()).toBe('Inserted');
expect(inserted.getLevel()).toBe('2.1.1');
expect(nextOne.getTitle()).toBe('2.1.1');
expect(nextOne.getLevel()).toBe('2.1.2');
});
it('should insert an article in last position', function() {
var article = SummaryArticle.create({
title: 'Inserted'
}, 'fake.level');
var newSummary = insertArticle(summary, article, '2.2');
var inserted = newSummary.getByLevel('2.2');
var previousOne = newSummary.getByLevel('2.1');
expect(inserted.getTitle()).toBe('Inserted');
expect(inserted.getLevel()).toBe('2.2');
expect(previousOne.getTitle()).toBe('2.1'); // Unchanged
expect(previousOne.getLevel()).toBe('2.1');
});
});
@@ -42,7 +42,7 @@ describe('moveArticle', function() {
} }
]); ]);
it('should move an article at in place', function() { it('should move an article to the same place', function() {
var newSummary = moveArticle(summary, '2.1', '2.1'); var newSummary = moveArticle(summary, '2.1', '2.1');
expect(Immutable.is(summary, newSummary)).toBe(true); expect(Immutable.is(summary, newSummary)).toBe(true);
@@ -0,0 +1,82 @@
var Immutable = require('immutable');
var Summary = require('../../../models/summary');
var File = require('../../../models/file');
describe('moveArticleAfter', function() {
var moveArticleAfter = require('../moveArticleAfter');
var summary = Summary.createFromParts(File(), [
{
articles: [
{
title: '1.1',
path: '1.1'
},
{
title: '1.2',
path: '1.2'
}
]
},
{
title: 'Part I',
articles: [
{
title: '2.1',
path: '2.1',
articles: [
{
title: '2.1.1',
path: '2.1.1'
},
{
title: '2.1.2',
path: '2.1.2'
}
]
},
{
title: '2.2',
path: '2.2'
}
]
}
]);
it('moving right after itself should be invariant', function() {
var newSummary = moveArticleAfter(summary, '2.1', '2.1');
expect(Immutable.is(summary, newSummary)).toBe(true);
});
it('moving after previous one should be invariant too', function() {
var newSummary = moveArticleAfter(summary, '2.1', '2.0');
expect(Immutable.is(summary, newSummary)).toBe(true);
});
it('should move an article after a previous level', function() {
var newSummary = moveArticleAfter(summary, '2.2', '2.0');
var moved = newSummary.getByLevel('2.1');
expect(moved.getTitle()).toBe('2.2');
expect(newSummary.getByLevel('2.2').getTitle()).toBe('2.1');
});
it('should move an article after a previous and less deep level', function() {
var newSummary = moveArticleAfter(summary, '2.1.1', '2.0');
var moved = newSummary.getByLevel('2.1');
expect(moved.getTitle()).toBe('2.1.1');
expect(newSummary.getByLevel('2.2.1').getTitle()).toBe('2.1.2');
expect(newSummary.getByLevel('2.2').getTitle()).toBe('2.1');
});
it('should move an article after a next level', function() {
var newSummary = moveArticleAfter(summary, '2.1', '2.2');
var moved = newSummary.getByLevel('2.2');
expect(moved.getTitle()).toBe('2.1');
expect(newSummary.getByLevel('2.1').getTitle()).toBe('2.2');
});
});
@@ -0,0 +1,53 @@
var Summary = require('../../../models/summary');
var File = require('../../../models/file');
describe('removeArticle', function() {
var removeArticle = require('../removeArticle');
var summary = Summary.createFromParts(File(), [
{
articles: [
{
title: '1.1',
path: '1.1'
},
{
title: '1.2',
path: '1.2'
}
]
},
{
title: 'Part I',
articles: [
{
title: '2.1',
path: '2.1',
articles: [
{
title: '2.1.1',
path: '2.1.1'
},
{
title: '2.1.2',
path: '2.1.2'
}
]
},
{
title: '2.2',
path: '2.2'
}
]
}
]);
it('should remove an article at a given level', function() {
var newSummary = removeArticle(summary, '2.1.1');
var removed = newSummary.getByLevel('2.1.1');
var nextOne = newSummary.getByLevel('2.1.2');
expect(removed.getTitle()).toBe('2.1.2');
expect(nextOne).toBe(null);
});
});
+17
View File
@@ -0,0 +1,17 @@
var mergeAtLevel = require('./mergeAtLevel');
/**
Edit the ref of an article
@param {Summary} summary
@param {String} level
@param {String} newRef
@return {Summary}
*/
function editArticleRef(summary, level, newRef) {
return mergeAtLevel(summary, level, {
ref: newRef
});
}
module.exports = editArticleRef;
+6 -2
View File
@@ -1,9 +1,13 @@
module.exports = { module.exports = {
insertArticle: require('./insertArticle'), insertArticle: require('./insertArticle'),
moveArticle: require('./moveArticle'), moveArticle: require('./moveArticle'),
moveArticleAfter: require('./moveArticleAfter'),
removeArticle: require('./removeArticle'), removeArticle: require('./removeArticle'),
unshiftArticle: require('./unshiftArticle'), unshiftArticle: require('./unshiftArticle'),
editArticleTitle: require('./editArticleTitle'),
editArticleRef: require('./editArticleRef'),
editPartTitle: require('./editPartTitle'), insertPart: require('./insertPart'),
editArticleTitle: require('./editArticleTitle') removePart: require('./removePart'),
editPartTitle: require('./editPartTitle')
}; };
+2 -2
View File
@@ -8,11 +8,11 @@ var indexArticleLevels = require('./indexArticleLevels');
subsequent article shifted. subsequent article shifted.
@param {Summary} summary @param {Summary} summary
@param {String|Article} level: level to insert at
@param {Article} article @param {Article} article
@param {String|Article} level: level to insert at
@return {Summary} @return {Summary}
*/ */
function insertArticle(summary, level, article) { function insertArticle(summary, article, level) {
article = SummaryArticle(article); article = SummaryArticle(article);
level = is.string(level)? level : level.getLevel(); level = is.string(level)? level : level.getLevel();
+19
View File
@@ -0,0 +1,19 @@
var SummaryPart = require('../../models/summaryPart');
var indexLevels = require('./indexLevels');
/**
Returns a new Summary with a part inserted at given index
@param {Summary} summary
@param {Part} part
@param {Number} index
@return {Summary}
*/
function insertPart(summary, part, index) {
part = SummaryPart(part);
var parts = summary.getParts().insert(index, part);
return indexLevels(summary.set('parts', parts));
}
module.exports = insertPart;
+3 -60
View File
@@ -15,68 +15,11 @@ function moveArticle(summary, origin, target) {
// Coerce to level // Coerce to level
var originLevel = is.string(origin)? origin : origin.getLevel(); var originLevel = is.string(origin)? origin : origin.getLevel();
var targetLevel = is.string(target)? target : target.getLevel(); var targetLevel = is.string(target)? target : target.getLevel();
var article = summary.getByLevel(originLevel); var article = summary.getByLevel(originLevel);
// Remove // Remove first
var removed = removeArticle(summary, origin); var removed = removeArticle(summary, originLevel);
return insertArticle(removed, article, targetLevel);
// Adjust targetLevel if removing impacted it
targetLevel = arrayToLevel(
shiftLevel(levelToArray(originLevel),
levelToArray(targetLevel)));
// Re-insert
return insertArticle(removed, target, article);
}
/**
@param {Array<Number>} removedLevel
@param {Array<Number>} level The level to udpate
@return {Array<Number>}
*/
function shiftLevel(removedLevel, level) {
if (level.length === 0) {
// `removedLevel` is under level, so no effect
return level;
} else if (removedLevel.length === 0) {
// Either `level` is a child of `removedLevel`... or they are equal
// This is undefined behavior.
return level;
}
var removedRoot = removedLevel[0];
var root = level[0];
var removedRest = removedLevel.slice(1);
var rest = level.slice(1);
if (removedRoot < root) {
// It will shift levels at this point. The rest is unchanged.
return Array.prototype.concat(root - 1, rest);
} else if (removedRoot === root) {
// Look deeper
return Array.prototype.concat(root, shiftLevel(removedRest, rest));
} else {
// No impact
return level;
}
}
/**
@param {String}
@return {Array<Number>}
*/
function levelToArray(l) {
return l.split('.').map(function (char) {
return parseInt(char, 10);
});
}
/**
@param {Array<Number>}
@return {String}
*/
function arrayToLevel(a) {
return a.join('.');
} }
module.exports = moveArticle; module.exports = moveArticle;
+60
View File
@@ -0,0 +1,60 @@
var is = require('is');
var removeArticle = require('./removeArticle');
var insertArticle = require('./insertArticle');
/**
Returns a new summary, with the an article moved after another
article. Unlike `moveArticle`, does not ensure that the article
will be found at the target's level plus one.
@param {Summary} summary
@param {String|SummaryArticle} origin
@param {String|SummaryArticle} afterTarget
@return {Summary}
*/
function moveArticleAfter(summary, origin, afterTarget) {
// Coerce to level
var originLevel = is.string(origin)? origin : origin.getLevel();
var afterTargetLevel = is.string(afterTarget)? afterTarget : afterTarget.getLevel();
var article = summary.getByLevel(originLevel);
var targetLevel = increment(afterTargetLevel);
if (targetLevel < origin) {
// Remove first
var removed = removeArticle(summary, originLevel);
// Insert then
return insertArticle(removed, article, targetLevel);
} else {
// Insert right after first
var inserted = insertArticle(summary, article, targetLevel);
// Remove old one
return removeArticle(inserted, originLevel);
}
}
/**
@param {String}
@return {Array<Number>}
*/
function levelToArray(l) {
return l.split('.').map(function (char) {
return parseInt(char, 10);
});
}
/**
@param {Array<Number>}
@return {String}
*/
function arrayToLevel(a) {
return a.join('.');
}
function increment(level) {
level = levelToArray(level);
level[level.length - 1]++;
return arrayToLevel(level);
}
module.exports = moveArticleAfter;
+15
View File
@@ -0,0 +1,15 @@
var indexLevels = require('./indexLevels');
/**
Remove a part at given index
@param {Summary} summary
@param {Number|} index
@return {Summary}
*/
function removePart(summary, index) {
var parts = summary.getParts().remove(index);
return indexLevels(summary.set('parts', parts));
}
module.exports = removePart;
+1 -1
View File
@@ -3,7 +3,7 @@ var EbookGenerator = require('../ebook');
describe('EbookGenerator', function() { describe('EbookGenerator', function() {
pit('should generate a SUMMARY.html', function() { it('should generate a SUMMARY.html', function() {
return generateMock(EbookGenerator, { return generateMock(EbookGenerator, {
'README.md': 'Hello World' 'README.md': 'Hello World'
}) })
+19 -2
View File
@@ -3,7 +3,7 @@ var JSONGenerator = require('../json');
describe('JSONGenerator', function() { describe('JSONGenerator', function() {
pit('should generate a README.json', function() { it('should generate a README.json', function() {
return generateMock(JSONGenerator, { return generateMock(JSONGenerator, {
'README.md': 'Hello World' 'README.md': 'Hello World'
}) })
@@ -12,7 +12,7 @@ describe('JSONGenerator', function() {
}); });
}); });
pit('should generate a json file for each articles', function() { it('should generate a json file for each articles', function() {
return generateMock(JSONGenerator, { return generateMock(JSONGenerator, {
'README.md': 'Hello World', 'README.md': 'Hello World',
'SUMMARY.md': '# Summary\n\n* [Page](test/page.md)', 'SUMMARY.md': '# Summary\n\n* [Page](test/page.md)',
@@ -25,5 +25,22 @@ describe('JSONGenerator', function() {
expect(folder).toHaveFile('test/page.json'); expect(folder).toHaveFile('test/page.json');
}); });
}); });
it('should generate a multilingual book', function() {
return generateMock(JSONGenerator, {
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
'en': {
'README.md': 'Hello'
},
'fr': {
'README.md': 'Bonjour'
}
})
.then(function(folder) {
expect(folder).toHaveFile('en/README.json');
expect(folder).toHaveFile('fr/README.json');
expect(folder).toHaveFile('README.json');
});
});
}); });
+10 -10
View File
@@ -3,7 +3,7 @@ var WebsiteGenerator = require('../website');
describe('WebsiteGenerator', function() { describe('WebsiteGenerator', function() {
pit('should generate an index.html', function() { it('should generate an index.html', function() {
return generateMock(WebsiteGenerator, { return generateMock(WebsiteGenerator, {
'README.md': 'Hello World' 'README.md': 'Hello World'
}) })
@@ -12,7 +12,7 @@ describe('WebsiteGenerator', function() {
}); });
}); });
pit('should copy asset files', function() { it('should copy asset files', function() {
return generateMock(WebsiteGenerator, { return generateMock(WebsiteGenerator, {
'README.md': 'Hello World', 'README.md': 'Hello World',
'myJsFile.js': 'var a = "test";', 'myJsFile.js': 'var a = "test";',
@@ -27,7 +27,7 @@ describe('WebsiteGenerator', function() {
}); });
}); });
pit('should generate an index.html for AsciiDoc', function() { it('should generate an index.html for AsciiDoc', function() {
return generateMock(WebsiteGenerator, { return generateMock(WebsiteGenerator, {
'README.adoc': 'Hello World' 'README.adoc': 'Hello World'
}) })
@@ -36,7 +36,7 @@ describe('WebsiteGenerator', function() {
}); });
}); });
pit('should generate an HTML file for each articles', function() { it('should generate an HTML file for each articles', function() {
return generateMock(WebsiteGenerator, { return generateMock(WebsiteGenerator, {
'README.md': 'Hello World', 'README.md': 'Hello World',
'SUMMARY.md': '# Summary\n\n* [Page](test/page.md)', 'SUMMARY.md': '# Summary\n\n* [Page](test/page.md)',
@@ -50,7 +50,7 @@ describe('WebsiteGenerator', function() {
}); });
}); });
pit('should not generate file if entry file doesn\'t exist', function() { it('should not generate file if entry file doesn\'t exist', function() {
return generateMock(WebsiteGenerator, { return generateMock(WebsiteGenerator, {
'README.md': 'Hello World', 'README.md': 'Hello World',
'SUMMARY.md': '# Summary\n\n* [Page 1](page.md)\n* [Page 2](test/page.md)', 'SUMMARY.md': '# Summary\n\n* [Page 1](page.md)\n* [Page 2](test/page.md)',
@@ -60,12 +60,12 @@ describe('WebsiteGenerator', function() {
}) })
.then(function(folder) { .then(function(folder) {
expect(folder).toHaveFile('index.html'); expect(folder).toHaveFile('index.html');
expect(folder).not.toHaveFile('page.html'); expect(folder).toNotHaveFile('page.html');
expect(folder).toHaveFile('test/page.html'); expect(folder).toHaveFile('test/page.html');
}); });
}); });
pit('should generate a multilingual book', function() { it('should generate a multilingual book', function() {
return generateMock(WebsiteGenerator, { return generateMock(WebsiteGenerator, {
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)', 'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
'en': { 'en': {
@@ -81,12 +81,12 @@ describe('WebsiteGenerator', function() {
expect(folder).toHaveFile('fr/index.html'); expect(folder).toHaveFile('fr/index.html');
// Should not copy languages as assets // Should not copy languages as assets
expect(folder).not.toHaveFile('en/README.md'); expect(folder).toNotHaveFile('en/README.md');
expect(folder).not.toHaveFile('fr/README.md'); expect(folder).toNotHaveFile('fr/README.md');
// Should copy assets only once // Should copy assets only once
expect(folder).toHaveFile('gitbook/style.css'); expect(folder).toHaveFile('gitbook/style.css');
expect(folder).not.toHaveFile('en/gitbook/style.css'); expect(folder).toNotHaveFile('en/gitbook/style.css');
expect(folder).toHaveFile('index.html'); expect(folder).toHaveFile('index.html');
}); });
+2 -1
View File
@@ -28,7 +28,8 @@ function createTemplateEngine(output) {
filters = defaultFilters.merge(filters); filters = defaultFilters.merge(filters);
// Create loader // Create loader
var loader = new Templating.ConrefsLoader(rootFolder, logger); var transformFn = Templating.replaceShortcuts.bind(null, blocks);
var loader = new Templating.ConrefsLoader(rootFolder, transformFn, logger);
// Create API context // Create API context
var context = Api.encodeGlobal(output); var context = Api.encodeGlobal(output);
+3 -3
View File
@@ -11,17 +11,17 @@ function getCoverPath(output) {
var outputRoot = output.getRoot(); var outputRoot = output.getRoot();
var book = output.getBook(); var book = output.getBook();
var config = book.getConfig(); var config = book.getConfig();
var cover = config.getValue('cover', 'cover.jpg'); var coverName = config.getValue('cover', 'cover.jpg');
// Resolve to absolute // Resolve to absolute
cover = fs.pickFile(outputRoot, cover); var cover = fs.pickFile(outputRoot, coverName);
if (cover) { if (cover) {
return cover; return cover;
} }
// Multilingual? try parent folder // Multilingual? try parent folder
if (book.isLanguageBook()) { if (book.isLanguageBook()) {
cover = fs.pickFile(path.join(outputRoot, '..'), cover); cover = fs.pickFile(path.join(outputRoot, '..'), coverName);
} }
return cover; return cover;
+1 -2
View File
@@ -22,8 +22,7 @@ function getPDFTemplate(output, type) {
var context = JSONUtils.encodeOutput(output); var context = JSONUtils.encodeOutput(output);
context.page = { context.page = {
num: '_PAGENUM_', num: '_PAGENUM_',
title: '_TITLE_', title: '_SECTION_'
section: '_SECTION_'
}; };
// Render the theme // Render the theme
+4 -2
View File
@@ -2,7 +2,6 @@ var path = require('path');
var Immutable = require('immutable'); var Immutable = require('immutable');
var Output = require('../models/output'); var Output = require('../models/output');
var Config = require('../models/config');
var Promise = require('../utils/promise'); var Promise = require('../utils/promise');
var fs = require('../utils/fs'); var fs = require('../utils/fs');
@@ -41,7 +40,7 @@ function processOutput(generator, startOutput) {
var book = output.getBook(); var book = output.getBook();
var config = book.getConfig(); var config = book.getConfig();
config = Config.updateValues(config, result); config = config.updateValues(result);
book = book.set('config', config); book = book.set('config', config);
return output.set('book', book); return output.set('book', book);
} }
@@ -78,6 +77,7 @@ function processOutput(generator, startOutput) {
return; return;
} }
var logger = book.getLogger();
var books = book.getBooks(); var books = book.getBooks();
var outputRoot = output.getRoot(); var outputRoot = output.getRoot();
var plugins = output.getPlugins(); var plugins = output.getPlugins();
@@ -95,6 +95,8 @@ function processOutput(generator, startOutput) {
plugins: plugins plugins: plugins
}); });
logger.info.ln('');
logger.info.ln('generating language "' + langBook.getLanguage() + '"');
return processOutput(generator, langOutput); return processOutput(generator, langOutput);
}); });
}) })
+5 -2
View File
@@ -1,3 +1,5 @@
var path = require('path');
var Promise = require('../utils/promise'); var Promise = require('../utils/promise');
var error = require('../utils/error'); var error = require('../utils/error');
var timing = require('../utils/timing'); var timing = require('../utils/timing');
@@ -21,7 +23,7 @@ function generatePage(output, page) {
return timing.measure( return timing.measure(
'page.generate', 'page.generate',
Parse.parsePage(book, page) Promise(page)
.then(function(resultPage) { .then(function(resultPage) {
var file = resultPage.getFile(); var file = resultPage.getFile();
var filePath = file.getPath(); var filePath = file.getPath();
@@ -44,7 +46,8 @@ function generatePage(output, page) {
// Render templating syntax // Render templating syntax
.then(function(content) { .then(function(content) {
return Templating.render(engine, filePath, content, context); var absoluteFilePath = path.join(book.getContentRoot(), filePath);
return Templating.render(engine, absoluteFilePath, content, context);
}) })
.then(function(output) { .then(function(output) {
+1 -2
View File
@@ -13,8 +13,7 @@ function resolveFileToURL(output, filePath) {
// Convert /test.png -> test.png // Convert /test.png -> test.png
filePath = LocationUtils.toAbsolute(filePath, '', ''); filePath = LocationUtils.toAbsolute(filePath, '', '');
var pages = output.getPages(); var page = output.getPage(filePath);
var page = pages.get(filePath);
// if file is a page, return correct .html url // if file is a page, return correct .html url
if (page) { if (page) {
+2 -1
View File
@@ -2,5 +2,6 @@
module.exports = { module.exports = {
name: 'json', name: 'json',
Options: require('./options'), Options: require('./options'),
onPage: require('./onPage') onPage: require('./onPage'),
onFinish: require('./onFinish')
}; };
+20 -5
View File
@@ -2,6 +2,7 @@ var path = require('path');
var Promise = require('../../utils/promise'); var Promise = require('../../utils/promise');
var fs = require('../../utils/fs'); var fs = require('../../utils/fs');
var JSONUtils = require('../../json');
/** /**
Finish the generation Finish the generation
@@ -21,11 +22,25 @@ function onFinish(output) {
var languages = book.getLanguages(); var languages = book.getLanguages();
var mainLanguage = languages.getDefaultLanguage(); var mainLanguage = languages.getDefaultLanguage();
// Copy README.json from it // Read the main JSON
return fs.copy( return fs.readFile(path.resolve(outputRoot, mainLanguage.getID(), 'README.json'), 'utf8')
path.resolve(outputRoot, mainLanguage.getID(), 'README.json'),
path.resolve(outputRoot, 'README.json') // Extend the JSON
) .then(function(content) {
var json = JSON.parse(content);
json.languages = JSONUtils.encodeLanguages(languages);
return json;
})
.then(function(json) {
return fs.writeFile(
path.resolve(outputRoot, 'README.json'),
JSON.stringify(json, null, 4)
);
})
.thenResolve(output); .thenResolve(output);
} }
@@ -1,11 +1,8 @@
jest.autoMockOff();
var cheerio = require('cheerio'); var cheerio = require('cheerio');
var addHeadingId = require('../addHeadingId');
describe('addHeadingId', function() { describe('addHeadingId', function() {
var addHeadingId = require('../addHeadingId'); it('should add an ID if none', function() {
pit('should add an ID if none', function() {
var $ = cheerio.load('<h1>Hello World</h1><h2>Cool !!</h2>'); var $ = cheerio.load('<h1>Hello World</h1><h2>Cool !!</h2>');
return addHeadingId($) return addHeadingId($)
@@ -15,7 +12,7 @@ describe('addHeadingId', function() {
}); });
}); });
pit('should not change existing IDs', function() { it('should not change existing IDs', function() {
var $ = cheerio.load('<h1 id="awesome">Hello World</h1>'); var $ = cheerio.load('<h1 id="awesome">Hello World</h1>');
return addHeadingId($) return addHeadingId($)
@@ -1,12 +1,9 @@
jest.autoMockOff();
var Immutable = require('immutable'); var Immutable = require('immutable');
var cheerio = require('cheerio'); var cheerio = require('cheerio');
var GlossaryEntry = require('../../../models/glossaryEntry'); var GlossaryEntry = require('../../../models/glossaryEntry');
var annotateText = require('../annotateText');
describe('annotateText', function() { describe('annotateText', function() {
var annotateText = require('../annotateText');
var entries = Immutable.List([ var entries = Immutable.List([
GlossaryEntry({ name: 'Word' }), GlossaryEntry({ name: 'Word' }),
GlossaryEntry({ name: 'Multiple Words' }) GlossaryEntry({ name: 'Multiple Words' })
@@ -12,7 +12,7 @@ describe('fetchRemoteImages', function() {
dir = tmp.dirSync(); dir = tmp.dirSync();
}); });
pit('should download image file', function() { it('should download image file', function() {
var $ = cheerio.load('<img src="' + URL + '" />'); var $ = cheerio.load('<img src="' + URL + '" />');
return fetchRemoteImages(dir.name, 'index.html', $) return fetchRemoteImages(dir.name, 'index.html', $)
@@ -24,7 +24,7 @@ describe('fetchRemoteImages', function() {
}); });
}); });
pit('should download image file and replace with relative path', function() { it('should download image file and replace with relative path', function() {
var $ = cheerio.load('<img src="' + URL + '" />'); var $ = cheerio.load('<img src="' + URL + '" />');
return fetchRemoteImages(dir.name, 'test/index.html', $) return fetchRemoteImages(dir.name, 'test/index.html', $)
@@ -1,11 +1,8 @@
jest.autoMockOff();
var cheerio = require('cheerio'); var cheerio = require('cheerio');
var Promise = require('../../../utils/promise'); var Promise = require('../../../utils/promise');
var highlightCode = require('../highlightCode');
describe('highlightCode', function() { describe('highlightCode', function() {
var highlightCode = require('../highlightCode');
function doHighlight(lang, code) { function doHighlight(lang, code) {
return { return {
text: '' + (lang || '') + '$' + code text: '' + (lang || '') + '$' + code
@@ -19,7 +16,7 @@ describe('highlightCode', function() {
}); });
} }
pit('should call it for normal code element', function() { it('should call it for normal code element', function() {
var $ = cheerio.load('<p>This is a <code>test</code></p>'); var $ = cheerio.load('<p>This is a <code>test</code></p>');
return highlightCode(doHighlight, $) return highlightCode(doHighlight, $)
@@ -29,7 +26,7 @@ describe('highlightCode', function() {
}); });
}); });
pit('should call it for markdown code block', function() { it('should call it for markdown code block', function() {
var $ = cheerio.load('<pre><code class="lang-js">test</code></pre>'); var $ = cheerio.load('<pre><code class="lang-js">test</code></pre>');
return highlightCode(doHighlight, $) return highlightCode(doHighlight, $)
@@ -39,7 +36,7 @@ describe('highlightCode', function() {
}); });
}); });
pit('should call it for asciidoc code block', function() { it('should call it for asciidoc code block', function() {
var $ = cheerio.load('<pre><code class="language-python">test</code></pre>'); var $ = cheerio.load('<pre><code class="language-python">test</code></pre>');
return highlightCode(doHighlight, $) return highlightCode(doHighlight, $)
@@ -49,7 +46,7 @@ describe('highlightCode', function() {
}); });
}); });
pit('should accept async highlighter', function() { it('should accept async highlighter', function() {
var $ = cheerio.load('<pre><code class="language-python">test</code></pre>'); var $ = cheerio.load('<pre><code class="language-python">test</code></pre>');
return highlightCode(doHighlightAsync, $) return highlightCode(doHighlightAsync, $)
+1 -1
View File
@@ -9,7 +9,7 @@ describe('inlinePng', function() {
dir = tmp.dirSync(); dir = tmp.dirSync();
}); });
pit('should write an inline PNG using data URI as a file', function() { it('should write an inline PNG using data URI as a file', function() {
var $ = cheerio.load('<img alt="GitBook Logo 20x20" src="data:image/png;base64,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"/>'); var $ = cheerio.load('<img alt="GitBook Logo 20x20" src="data:image/png;base64,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"/>');
return inlinePng(dir.name, 'index.html', $) return inlinePng(dir.name, 'index.html', $)
@@ -1,11 +1,8 @@
jest.autoMockOff();
var path = require('path'); var path = require('path');
var cheerio = require('cheerio'); var cheerio = require('cheerio');
var resolveLinks = require('../resolveLinks');
describe('resolveLinks', function() { describe('resolveLinks', function() {
var resolveLinks = require('../resolveLinks');
function resolveFileBasic(href) { function resolveFileBasic(href) {
return href; return href;
} }
@@ -21,7 +18,7 @@ describe('resolveLinks', function() {
describe('Absolute path', function() { describe('Absolute path', function() {
var TEST = '<p>This is a <a href="/test/cool.md"></a></p>'; var TEST = '<p>This is a <a href="/test/cool.md"></a></p>';
pit('should resolve path starting by "/" in root directory', function() { it('should resolve path starting by "/" in root directory', function() {
var $ = cheerio.load(TEST); var $ = cheerio.load(TEST);
return resolveLinks('hello.md', resolveFileBasic, $) return resolveLinks('hello.md', resolveFileBasic, $)
@@ -31,7 +28,7 @@ describe('resolveLinks', function() {
}); });
}); });
pit('should resolve path starting by "/" in child directory', function() { it('should resolve path starting by "/" in child directory', function() {
var $ = cheerio.load(TEST); var $ = cheerio.load(TEST);
return resolveLinks('afolder/hello.md', resolveFileBasic, $) return resolveLinks('afolder/hello.md', resolveFileBasic, $)
@@ -45,7 +42,7 @@ describe('resolveLinks', function() {
describe('Custom Resolver', function() { describe('Custom Resolver', function() {
var TEST = '<p>This is a <a href="/test/cool.md"></a> <a href="afile.png"></a></p>'; var TEST = '<p>This is a <a href="/test/cool.md"></a> <a href="afile.png"></a></p>';
pit('should resolve path correctly for absolute path', function() { it('should resolve path correctly for absolute path', function() {
var $ = cheerio.load(TEST); var $ = cheerio.load(TEST);
return resolveLinks('hello.md', resolveFileCustom, $) return resolveLinks('hello.md', resolveFileCustom, $)
@@ -55,7 +52,7 @@ describe('resolveLinks', function() {
}); });
}); });
pit('should resolve path correctly for absolute path (2)', function() { it('should resolve path correctly for absolute path (2)', function() {
var $ = cheerio.load(TEST); var $ = cheerio.load(TEST);
return resolveLinks('afodler/hello.md', resolveFileCustom, $) return resolveLinks('afodler/hello.md', resolveFileCustom, $)
+1 -1
View File
@@ -9,7 +9,7 @@ describe('svgToImg', function() {
dir = tmp.dirSync(); dir = tmp.dirSync();
}); });
pit('should write svg as a file', function() { it('should write svg as a file', function() {
var $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>'); var $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>');
return svgToImg(dir.name, 'index.html', $) return svgToImg(dir.name, 'index.html', $)
+4 -3
View File
@@ -2,16 +2,17 @@ var cheerio = require('cheerio');
var tmp = require('tmp'); var tmp = require('tmp');
var path = require('path'); var path = require('path');
var svgToImg = require('../svgToImg');
var svgToPng = require('../svgToPng');
describe('svgToPng', function() { describe('svgToPng', function() {
var dir; var dir;
var svgToImg = require('../svgToImg');
var svgToPng = require('../svgToPng');
beforeEach(function() { beforeEach(function() {
dir = tmp.dirSync(); dir = tmp.dirSync();
}); });
pit('should write svg as png file', function() { it('should write svg as png file', function() {
var $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>'); var $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>');
var fileName = 'index.html'; var fileName = 'index.html';
+5
View File
@@ -1,4 +1,5 @@
var Parse = require('../parse'); var Parse = require('../parse');
var Promise = require('../utils/promise');
/** /**
List and prepare all pages List and prepare all pages
@@ -10,6 +11,10 @@ function preparePages(output) {
var book = output.getBook(); var book = output.getBook();
var logger = book.getLogger(); var logger = book.getLogger();
if (book.isMultilingual()) {
return Promise(output);
}
return Parse.parsePagesList(book) return Parse.parsePagesList(book)
.then(function(pages) { .then(function(pages) {
logger.info.ln('found', pages.size, 'pages'); logger.info.ln('found', pages.size, 'pages');
+50 -19
View File
@@ -3,6 +3,7 @@ var nunjucks = require('nunjucks');
var DoExtension = require('nunjucks-do')(nunjucks); var DoExtension = require('nunjucks-do')(nunjucks);
var Api = require('../../api'); var Api = require('../../api');
var deprecate = require('../../api/deprecate');
var JSONUtils = require('../../json'); var JSONUtils = require('../../json');
var LocationUtils = require('../../utils/location'); var LocationUtils = require('../../utils/location');
var fs = require('../../utils/fs'); var fs = require('../../utils/fs');
@@ -51,24 +52,58 @@ function createTemplateEngine(output, currentFile) {
// Create API context // Create API context
var context = Api.encodeGlobal(output); var context = Api.encodeGlobal(output);
/**
Check if a file exists
@param {String} fileName
@return {Boolean}
*/
function fileExists(fileName) {
if (!fileName) {
return false;
}
var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
return fs.existsSync(filePath);
}
/**
Return an article by its path
@param {String} filePath
@return {Object|undefined}
*/
function getArticleByPath(filePath) {
var article = summary.getByPath(filePath);
if (!article) return undefined;
return JSONUtils.encodeSummaryArticle(article);
}
/**
Return a page by its path
@param {String} filePath
@return {Object|undefined}
*/
function getPageByPath(filePath) {
var page = output.getPage(filePath);
if (!page) return undefined;
return JSONUtils.encodePage(page, summary);
}
return TemplateEngine.create({ return TemplateEngine.create({
loader: loader, loader: loader,
context: context, context: context,
globals: { globals: {
/** getArticleByPath: getArticleByPath,
Return an article by its path getPageByPath: getPageByPath,
fileExists: fileExists
@param {String} articlePath
@return {Object|undefined}
*/
getArticleByPath: function(articlePath) {
var article = summary.getByPath(articlePath);
if (!article) return undefined;
return JSONUtils.encodeSummaryArticle(article);
}
}, },
filters: defaultFilters.merge({ filters: defaultFilters.merge({
@@ -102,13 +137,9 @@ function createTemplateEngine(output, currentFile) {
return LocationUtils.normalize(filePath); return LocationUtils.normalize(filePath);
}, },
/**
Check if a file exists fileExists: deprecate.method(book, 'fileExists', fileExists, 'Filter "fileExists" is deprecated, use "fileExists(filename)" '),
*/ getArticleByPath: deprecate.method(book, 'getArticleByPath', fileExists, 'Filter "getArticleByPath" is deprecated, use "getArticleByPath(filename)" '),
fileExists: function(fileName) {
var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
return fs.existsSync(filePath);
},
contentURL: function(filePath) { contentURL: function(filePath) {
return fileToURL(output, filePath); return fileToURL(output, filePath);
+1 -1
View File
@@ -6,7 +6,7 @@ var listAssets = require('../listAssets');
var parseGlossary = require('../parseGlossary'); var parseGlossary = require('../parseGlossary');
describe('listAssets', function() { describe('listAssets', function() {
pit('should not list glossary as asset', function() { it('should not list glossary as asset', function() {
var fs = createMockFS({ var fs = createMockFS({
'GLOSSARY.md': '# Glossary\n\n## Hello\nDescription for hello', 'GLOSSARY.md': '# Glossary\n\n## Hello\nDescription for hello',
'assetFile.js': '', 'assetFile.js': '',
+37 -2
View File
@@ -4,7 +4,7 @@ var createMockFS = require('../../fs/mock');
describe('parseBook', function() { describe('parseBook', function() {
var parseBook = require('../parseBook'); var parseBook = require('../parseBook');
pit('should parse multilingual book', function() { it('should parse multilingual book', function() {
var fs = createMockFS({ var fs = createMockFS({
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)', 'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
'en': { 'en': {
@@ -27,7 +27,42 @@ describe('parseBook', function() {
}); });
}); });
pit('should parse book in a directory', function() { it('should extend configuration for multilingual book', function() {
var fs = createMockFS({
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
'book.json': '{ "title": "Test", "author": "GitBook" }',
'en': {
'README.md': 'Hello',
'book.json': '{ "title": "Test EN" }'
},
'fr': {
'README.md': 'Bonjour'
}
});
var book = Book.createForFS(fs);
return parseBook(book)
.then(function(resultBook) {
var books = resultBook.getBooks();
expect(resultBook.isMultilingual()).toBe(true);
expect(books.size).toBe(2);
var en = books.get('en');
var fr = books.get('fr');
var enConfig = en.getConfig();
var frConfig = fr.getConfig();
expect(enConfig.getValue('title')).toBe('Test EN');
expect(enConfig.getValue('author')).toBe('GitBook');
expect(frConfig.getValue('title')).toBe('Test');
expect(frConfig.getValue('author')).toBe('GitBook');
});
});
it('should parse book in a directory', function() {
var fs = createMockFS({ var fs = createMockFS({
'book.json': JSON.stringify({ 'book.json': JSON.stringify({
root: './test' root: './test'
+2 -2
View File
@@ -4,7 +4,7 @@ var createMockFS = require('../../fs/mock');
describe('parseGlossary', function() { describe('parseGlossary', function() {
var parseGlossary = require('../parseGlossary'); var parseGlossary = require('../parseGlossary');
pit('should parse glossary if exists', function() { it('should parse glossary if exists', function() {
var fs = createMockFS({ var fs = createMockFS({
'GLOSSARY.md': '# Glossary\n\n## Hello\nDescription for hello' 'GLOSSARY.md': '# Glossary\n\n## Hello\nDescription for hello'
}); });
@@ -21,7 +21,7 @@ describe('parseGlossary', function() {
}); });
}); });
pit('should not fail if doesn\'t exist', function() { it('should not fail if doesn\'t exist', function() {
var fs = createMockFS({}); var fs = createMockFS({});
var book = Book.createForFS(fs); var book = Book.createForFS(fs);
+3 -3
View File
@@ -17,21 +17,21 @@ describe('parseIgnore', function() {
return parseIgnore(book); return parseIgnore(book);
} }
pit('should load rules from .ignore', function() { it('should load rules from .ignore', function() {
return getBook() return getBook()
.then(function(book) { .then(function(book) {
expect(book.isFileIgnored('test-1.js')).toBeTruthy(); expect(book.isFileIgnored('test-1.js')).toBeTruthy();
}); });
}); });
pit('should load rules from .gitignore', function() { it('should load rules from .gitignore', function() {
return getBook() return getBook()
.then(function(book) { .then(function(book) {
expect(book.isFileIgnored('test-2.js')).toBeTruthy(); expect(book.isFileIgnored('test-2.js')).toBeTruthy();
}); });
}); });
pit('should load rules from .bookignore', function() { it('should load rules from .bookignore', function() {
return getBook() return getBook()
.then(function(book) { .then(function(book) {
expect(book.isFileIgnored('test-3.js')).toBeFalsy(); expect(book.isFileIgnored('test-3.js')).toBeFalsy();
+2 -2
View File
@@ -5,7 +5,7 @@ var createMockFS = require('../../fs/mock');
describe('parseReadme', function() { describe('parseReadme', function() {
var parseReadme = require('../parseReadme'); var parseReadme = require('../parseReadme');
pit('should parse summary if exists', function() { it('should parse summary if exists', function() {
var fs = createMockFS({ var fs = createMockFS({
'README.md': '# Hello\n\nAnd here is the description.' 'README.md': '# Hello\n\nAnd here is the description.'
}); });
@@ -22,7 +22,7 @@ describe('parseReadme', function() {
}); });
}); });
pit('should fail if doesn\'t exist', function() { it('should fail if doesn\'t exist', function() {
var fs = createMockFS({}); var fs = createMockFS({});
var book = Book.createForFS(fs); var book = Book.createForFS(fs);
+2 -2
View File
@@ -4,7 +4,7 @@ var createMockFS = require('../../fs/mock');
describe('parseSummary', function() { describe('parseSummary', function() {
var parseSummary = require('../parseSummary'); var parseSummary = require('../parseSummary');
pit('should parse summary if exists', function() { it('should parse summary if exists', function() {
var fs = createMockFS({ var fs = createMockFS({
'SUMMARY.md': '# Summary\n\n* [Hello](hello.md)' 'SUMMARY.md': '# Summary\n\n* [Hello](hello.md)'
}); });
@@ -19,7 +19,7 @@ describe('parseSummary', function() {
}); });
}); });
pit('should not fail if doesn\'t exist', function() { it('should not fail if doesn\'t exist', function() {
var fs = createMockFS({}); var fs = createMockFS({});
var book = Book.createForFS(fs); var book = Book.createForFS(fs);
+1 -1
View File
@@ -8,7 +8,7 @@ var parsers = require('../parsers');
@param {Book} book @param {Book} book
@param {String} filename @param {String} filename
@return {Promise<>} @return {Promise<File | Undefined>}
*/ */
function findParsableFile(book, filename) { function findParsableFile(book, filename) {
var fs = book.getContentFS(); var fs = book.getContentFS();
+11 -10
View File
@@ -1,13 +1,14 @@
module.exports = { module.exports = {
parseBook: require('./parseBook'), parseBook: require('./parseBook'),
parseSummary: require('./parseSummary'), parseSummary: require('./parseSummary'),
parseGlossary: require('./parseGlossary'), parseGlossary: require('./parseGlossary'),
parseReadme: require('./parseReadme'), parseReadme: require('./parseReadme'),
parseConfig: require('./parseConfig'), parseConfig: require('./parseConfig'),
parsePagesList: require('./parsePagesList'), parsePagesList: require('./parsePagesList'),
parseIgnore: require('./parseIgnore'), parseIgnore: require('./parseIgnore'),
listAssets: require('./listAssets'), listAssets: require('./listAssets'),
parseLanguages: require('./parseLanguages'), parseLanguages: require('./parseLanguages'),
parsePage: require('./parsePage') parsePage: require('./parsePage'),
lookupStructureFile: require('./lookupStructureFile')
}; };
+15 -12
View File
@@ -20,20 +20,23 @@ function listAssets(book, pages) {
var langs = book.getLanguages(); var langs = book.getLanguages();
var langsFile = langs.getFile().getPath(); var langsFile = langs.getFile().getPath();
var config = book.getConfig();
var configFile = config.getFile().getPath();
function filterFile(file) {
return !(
file === summaryFile ||
file === glossaryFile ||
file === langsFile ||
file === configFile ||
book.isContentFileIgnored(file) ||
pages.has(file)
);
}
return timing.measure( return timing.measure(
'parse.listAssets', 'parse.listAssets',
fs.listAllFiles() fs.listAllFiles('.', filterFile)
.then(function(files) {
return files.filterNot(function(file) {
return (
book.isContentFileIgnored(file) ||
pages.has(file) ||
file === summaryFile ||
file === glossaryFile ||
file === langsFile
);
});
})
); );
} }
+20
View File
@@ -0,0 +1,20 @@
var findParsableFile = require('./findParsableFile');
/**
Lookup a structure file (ex: SUMMARY.md, GLOSSARY.md) in a book. Uses
book's config to find it.
@param {Book} book
@param {String} type: one of ["glossary", "readme", "summary", "langs"]
@return {Promise<File>} The path of the file found, relative
to the book content root.
*/
function lookupStructureFile(book, type) {
var config = book.getConfig();
var fileToSearch = config.getValue(['structure', type]);
return findParsableFile(book, fileToSearch);
}
module.exports = lookupStructureFile;
+1 -1
View File
@@ -42,7 +42,7 @@ function parseMultilingualBook(book) {
.then(parseBookContent) .then(parseBookContent)
.then(function(result) { .then(function(result) {
// Ignore content of this book when generating parent book // Ignore content of this book when generating parent book
ignore.add(langID + '/**'); ignore = ignore.add(langID + '/**');
currentBook = currentBook.set('ignore', ignore); currentBook = currentBook.set('ignore', ignore);
return currentBook.addLanguageBook(langID, result); return currentBook.addLanguageBook(langID, result);
+19 -15
View File
@@ -1,7 +1,5 @@
var Promise = require('../utils/promise'); var Promise = require('../utils/promise');
var Config = require('../models/config');
var File = require('../models/file');
var validateConfig = require('./validateConfig'); var validateConfig = require('./validateConfig');
var CONFIG_FILES = require('../constants/configFiles'); var CONFIG_FILES = require('../constants/configFiles');
@@ -13,6 +11,7 @@ var CONFIG_FILES = require('../constants/configFiles');
*/ */
function parseConfig(book) { function parseConfig(book) {
var fs = book.getFS(); var fs = book.getFS();
var config = book.getConfig();
return Promise.some(CONFIG_FILES, function(filename) { return Promise.some(CONFIG_FILES, function(filename) {
// Is this file ignored? // Is this file ignored?
@@ -21,15 +20,15 @@ function parseConfig(book) {
} }
// Try loading it // Try loading it
return Promise.all([ return fs.loadAsObject(filename)
fs.loadAsObject(filename), .then(function(cfg) {
fs.statFile(filename) return fs.statFile(filename)
]) .then(function(file) {
.spread(function(cfg, file) { return {
return { file: file,
file: file, values: cfg
values: cfg };
}; });
}) })
.fail(function(err) { .fail(function(err) {
if (err.code != 'MODULE_NOT_FOUND') throw(err); if (err.code != 'MODULE_NOT_FOUND') throw(err);
@@ -38,13 +37,18 @@ function parseConfig(book) {
}) })
.then(function(result) { .then(function(result) {
var file = result? result.file : File();
var values = result? result.values : {}; var values = result? result.values : {};
values = validateConfig(values); values = validateConfig(values);
var config = Config.create(file, values); // Set the file
return book.set('config', config); if (result.file) {
config = config.setFile(result.file);
}
// Merge with old values
config = config.mergeValues(values);
return book.setConfig(config);
}); });
} }
+22 -18
View File
@@ -1,6 +1,23 @@
var Promise = require('../utils/promise'); var Promise = require('../utils/promise');
var IGNORE_FILES = require('../constants/ignoreFiles'); var IGNORE_FILES = require('../constants/ignoreFiles');
var DEFAULT_IGNORES = [
// Skip Git stuff
'.git/',
// Skip OS X meta data
'.DS_Store',
// Skip stuff installed by plugins
'node_modules',
// Skip book outputs
'_book',
// Ignore files in the templates folder
'_layouts'
];
/** /**
Parse ignore files Parse ignore files
@@ -15,33 +32,20 @@ function parseIgnore(book) {
var fs = book.getFS(); var fs = book.getFS();
var ignore = book.getIgnore(); var ignore = book.getIgnore();
ignore.addPattern([ ignore = ignore.add(DEFAULT_IGNORES);
// Skip Git stuff
'.git/',
// Skip OS X meta data
'.DS_Store',
// Skip stuff installed by plugins
'node_modules',
// Skip book outputs
'_book',
// Ignore files in the templates folder
'_layouts'
]);
return Promise.serie(IGNORE_FILES, function(filename) { return Promise.serie(IGNORE_FILES, function(filename) {
return fs.readAsString(filename) return fs.readAsString(filename)
.then(function(content) { .then(function(content) {
ignore.addPattern(content.toString().split(/\r?\n/)); ignore = ignore.add(content.toString().split(/\r?\n/));
}, function(err) { }, function(err) {
return Promise(); return Promise();
}); });
}) })
.thenResolve(book); .then(function() {
return book.setIgnore(ignore);
});
} }
module.exports = parseIgnore; module.exports = parseIgnore;
+6 -4
View File
@@ -3,6 +3,7 @@ var Immutable = require('immutable');
var timing = require('../utils/timing'); var timing = require('../utils/timing');
var Page = require('../models/page'); var Page = require('../models/page');
var walkSummary = require('./walkSummary'); var walkSummary = require('./walkSummary');
var parsePage = require('./parsePage');
/** /**
Parse all pages from a book as an OrderedMap Parse all pages from a book as an OrderedMap
@@ -27,10 +28,11 @@ function parsePagesList(book) {
return fs.statFile(filepath) return fs.statFile(filepath)
.then(function(file) { .then(function(file) {
map = map.set( var page = Page.createForFile(file);
filepath, return parsePage(book, page);
Page.createForFile(file) })
); .then(function(page) {
map = map.set(filepath, page);
}, function() { }, function() {
// file doesn't exist // file doesn't exist
}); });
+2 -5
View File
@@ -1,6 +1,6 @@
var findParsableFile = require('./findParsableFile');
var Promise = require('../utils/promise'); var Promise = require('../utils/promise');
var error = require('../utils/error'); var error = require('../utils/error');
var lookupStructureFile = require('./lookupStructureFile');
/** /**
Parse a ParsableFile using a specific method Parse a ParsableFile using a specific method
@@ -55,11 +55,8 @@ function parseFile(fs, file, type) {
*/ */
function parseStructureFile(book, type) { function parseStructureFile(book, type) {
var fs = book.getContentFS(); var fs = book.getContentFS();
var config = book.getConfig();
var fileToSearch = config.getValue(['structure', type]); return lookupStructureFile(book, type)
return findParsableFile(book, fileToSearch)
.then(function(file) { .then(function(file) {
if (!file) return [undefined, undefined]; if (!file) return [undefined, undefined];
+1 -1
View File
@@ -4,7 +4,7 @@ var Immutable = require('immutable');
describe('findInstalled', function() { describe('findInstalled', function() {
var findInstalled = require('../findInstalled'); var findInstalled = require('../findInstalled');
pit('must list default plugins for gitbook directory', function() { it('must list default plugins for gitbook directory', function() {
// Read gitbook-plugins from package.json // Read gitbook-plugins from package.json
var pkg = require(path.resolve(__dirname, '../../../package.json')); var pkg = require(path.resolve(__dirname, '../../../package.json'));
var gitbookPlugins = Immutable.Seq(pkg.dependencies) var gitbookPlugins = Immutable.Seq(pkg.dependencies)
+2 -7
View File
@@ -1,13 +1,9 @@
jest.autoMockOff();
var Promise = require('../../utils/promise'); var Promise = require('../../utils/promise');
var Plugin = require('../../models/plugin'); var Plugin = require('../../models/plugin');
var validatePlugin = require('../validatePlugin');
describe('validatePlugin', function() { describe('validatePlugin', function() {
var validatePlugin = require('../validatePlugin'); it('must not validate a not loaded plugin', function() {
pit('must not validate a not loaded plugin', function() {
var plugin = Plugin.createFromString('test'); var plugin = Plugin.createFromString('test');
return validatePlugin(plugin) return validatePlugin(plugin)
@@ -17,5 +13,4 @@ describe('validatePlugin', function() {
return Promise(); return Promise();
}); });
}); });
}); });
+69 -11
View File
@@ -1,34 +1,92 @@
var path = require('path');
var TemplateEngine = require('../../models/templateEngine'); var TemplateEngine = require('../../models/templateEngine');
var renderTemplate = require('../render'); var renderTemplate = require('../render');
var ConrefsLoader = require('../conrefsLoader');
describe('ConrefsLoader', function() { describe('ConrefsLoader', function() {
var ConrefsLoader = require('../conrefsLoader'); var dirName = __dirname + '/';
var fileName = path.join(dirName, 'test.md');
var engine = TemplateEngine({
loader: new ConrefsLoader(__dirname)
});
describe('Git', function() { describe('Git', function() {
pit('should include content from git', function() { var engine = TemplateEngine({
return renderTemplate(engine, 'test.md', '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}') loader: new ConrefsLoader(dirName)
});
it('should include content from git', function() {
return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}')
.then(function(out) { .then(function(out) {
expect(out.getContent()).toBe('Hello from git'); expect(out.getContent()).toBe('Hello from git');
}); });
}); });
pit('should handle deep inclusion (1)', function() { it('should handle deep inclusion (1)', function() {
return renderTemplate(engine, 'test.md', '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}') return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}')
.then(function(out) { .then(function(out) {
expect(out.getContent()).toBe('First Hello. Hello from git'); expect(out.getContent()).toBe('First Hello. Hello from git');
}); });
}); });
pit('should handle deep inclusion (2)', function() { it('should handle deep inclusion (2)', function() {
return renderTemplate(engine, 'test.md', '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test3.md" %}') return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test3.md" %}')
.then(function(out) { .then(function(out) {
expect(out.getContent()).toBe('First Hello. Hello from git'); expect(out.getContent()).toBe('First Hello. Hello from git');
}); });
}); });
}); });
describe('Local', function() {
var engine = TemplateEngine({
loader: new ConrefsLoader(dirName)
});
describe('Relative', function() {
it('should resolve basic relative filepath', function() {
return renderTemplate(engine, fileName, '{% include "include.md" %}')
.then(function(out) {
expect(out.getContent()).toBe('Hello World');
});
});
it('should resolve basic parent filepath', function() {
return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "../include.md" %}')
.then(function(out) {
expect(out.getContent()).toBe('Hello World');
});
});
});
describe('Absolute', function() {
it('should resolve absolute filepath', function() {
return renderTemplate(engine, fileName, '{% include "/include.md" %}')
.then(function(out) {
expect(out.getContent()).toBe('Hello World');
});
});
it('should resolve absolute filepath when in a directory', function() {
return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "/include.md" %}')
.then(function(out) {
expect(out.getContent()).toBe('Hello World');
});
});
});
});
describe('transform', function() {
function transform(filePath, source) {
return 'test-' + source + '-endtest';
}
var engine = TemplateEngine({
loader: new ConrefsLoader(dirName, transform)
});
it('should transform included content', function() {
return renderTemplate(engine, fileName, '{% include "include.md" %}')
.then(function(out) {
expect(out.getContent()).toBe('test-Hello World-endtest');
});
});
});
}); });
+1
View File
@@ -0,0 +1 @@
Hello World
+13 -14
View File
@@ -1,27 +1,26 @@
var TemplateEngine = require('../../models/templateEngine'); var Immutable = require('immutable');
var TemplateBlock = require('../../models/templateBlock'); var TemplateBlock = require('../../models/templateBlock');
var replaceShortcuts = require('../replaceShortcuts'); var replaceShortcuts = require('../replaceShortcuts');
describe('replaceShortcuts', function() { describe('replaceShortcuts', function() {
var engine = TemplateEngine.create({ var blocks = Immutable.List([
blocks:[ TemplateBlock.create('math', {
TemplateBlock.create('math', { shortcuts: {
shortcuts: { start: '$$',
start: '$$', end: '$$',
end: '$$', parsers: ['markdown']
parsers: ['markdown'] }
} })
}) ]);
]
});
it('should correctly replace inline matches by block', function() { it('should correctly replace inline matches by block', function() {
var content = replaceShortcuts(engine, 'test.md', 'Hello $$a = b$$'); var content = replaceShortcuts(blocks, 'test.md', 'Hello $$a = b$$');
expect(content).toBe('Hello {% math %}a = b{% endmath %}'); expect(content).toBe('Hello {% math %}a = b{% endmath %}');
}); });
it('should correctly replace block matches', function() { it('should correctly replace block matches', function() {
var content = replaceShortcuts(engine, 'test.md', 'Hello\n$$\na = b\n$$\n'); var content = replaceShortcuts(blocks, 'test.md', 'Hello\n$$\na = b\n$$\n');
expect(content).toBe('Hello\n{% math %}\na = b\n{% endmath %}\n'); expect(content).toBe('Hello\n{% math %}\na = b\n{% endmath %}\n');
}); });
}); });
+23 -3
View File
@@ -12,12 +12,18 @@ var PathUtils = require('../utils/path');
- relative url ("./test.md") - relative url ("./test.md")
- absolute url ("/test.md") - absolute url ("/test.md")
- git url ("") - git url ("")
@param {String} rootFolder
@param {Function(filePath, source)} transformFn (optional)
@param {Logger} logger (optional)
*/ */
var ConrefsLoader = nunjucks.Loader.extend({ var ConrefsLoader = nunjucks.Loader.extend({
async: true, async: true,
init: function(rootFolder, logger) { init: function(rootFolder, transformFn, logger) {
this.rootFolder = rootFolder; this.rootFolder = rootFolder;
this.transformFn = transformFn;
this.logger = logger; this.logger = logger;
this.git = new Git(); this.git = new Git();
}, },
@@ -34,8 +40,15 @@ var ConrefsLoader = nunjucks.Loader.extend({
if (that.logger) that.logger.debug.ln('resolve from git', sourceURL, 'to', filepath); if (that.logger) that.logger.debug.ln('resolve from git', sourceURL, 'to', filepath);
} }
// Read file from absolute path // Read file from absolute path
return fs.readFile(filepath) return fs.readFile(filepath)
.then(function(source) {
if (that.transformFn) {
return that.transformFn(filepath, source);
}
return source;
})
.then(function(source) { .then(function(source) {
return { return {
src: source.toString('utf8'), src: source.toString('utf8'),
@@ -49,7 +62,14 @@ var ConrefsLoader = nunjucks.Loader.extend({
resolve: function(from, to) { resolve: function(from, to) {
// If origin is in the book, we enforce result file to be in the book // If origin is in the book, we enforce result file to be in the book
if (PathUtils.isInRoot(this.rootFolder, from)) { if (PathUtils.isInRoot(this.rootFolder, from)) {
var href = LocationUtils.toAbsolute(to, path.dirname(from), '');
// Path of current template in the rootFolder (not absolute to fs)
var fromRelative = path.relative(this.rootFolder, from);
// Resolve "to" to a filepath relative to rootFolder
var href = LocationUtils.toAbsolute(to, path.dirname(fromRelative), '');
// Return absolute path
return PathUtils.resolveInRoot(this.rootFolder, href); return PathUtils.resolveInRoot(this.rootFolder, href);
} }
+6 -5
View File
@@ -1,9 +1,10 @@
module.exports = { module.exports = {
render: require('./render'), render: require('./render'),
renderFile: require('./renderFile'), renderFile: require('./renderFile'),
postRender: require('./postRender'), postRender: require('./postRender'),
replaceShortcuts: require('./replaceShortcuts'),
ConrefsLoader: require('./conrefsLoader'), ConrefsLoader: require('./conrefsLoader'),
ThemesLoader: require('./themesLoader') ThemesLoader: require('./themesLoader')
}; };
+2 -3
View File
@@ -5,12 +5,11 @@ var parsers = require('../parsers');
Return a list of all shortcuts that can apply Return a list of all shortcuts that can apply
to a file for a TemplatEngine to a file for a TemplatEngine
@param {TemplateEngine} engine @param {List<TemplateBlock>} engine
@param {String} filePath @param {String} filePath
@return {List<TemplateShortcut>} @return {List<TemplateShortcut>}
*/ */
function listShortcuts(engine, filePath) { function listShortcuts(blocks, filePath) {
var blocks = engine.getBlocks();
var parser = parsers.getForFile(filePath); var parser = parsers.getForFile(filePath);
if (!parser) { if (!parser) {
+1 -1
View File
@@ -36,7 +36,7 @@ function postRender(engine, output) {
var content = output.getContent(); var content = output.getContent();
var blocks = output.getBlocks(); var blocks = output.getBlocks();
var result = replaceBlocks(content); var result = replaceBlocks(content, blocks);
return Promise.forEach(blocks, function(block) { return Promise.forEach(blocks, function(block) {
var post = block.get('post'); var post = block.get('post');
+2 -2
View File
@@ -7,7 +7,7 @@ var replaceShortcuts = require('./replaceShortcuts');
Render a template Render a template
@param {TemplateEngine} engine @param {TemplateEngine} engine
@param {String} filePath @param {String} filePath: absolute path for the loader
@param {String} content @param {String} content
@param {Object} context @param {Object} context
@return {Promise<TemplateOutput>} @return {Promise<TemplateOutput>}
@@ -22,7 +22,7 @@ function renderTemplate(engine, filePath, content, context) {
var env = engine.toNunjucks(blocks); var env = engine.toNunjucks(blocks);
// Replace shortcuts from plugin's blocks // Replace shortcuts from plugin's blocks
content = replaceShortcuts(engine, filePath, content); content = replaceShortcuts(engine.getBlocks(), filePath, content);
return timing.measure( return timing.measure(
'template.render', 'template.render',
+5 -2
View File
@@ -13,14 +13,17 @@ var render = require('./render');
function renderTemplateFile(engine, filePath, context) { function renderTemplateFile(engine, filePath, context) {
var loader = engine.getLoader(); var loader = engine.getLoader();
// Resolve the filePath
var resolvedFilePath = loader.resolve(null, filePath);
return Promise() return Promise()
.then(function() { .then(function() {
if (!loader.async) { if (!loader.async) {
return loader.getSource(filePath); return loader.getSource(resolvedFilePath);
} }
var deferred = Promise.defer(); var deferred = Promise.defer();
loader.getSource(filePath, deferred.makeNodeResolver()); loader.getSource(resolvedFilePath, deferred.makeNodeResolver());
return deferred.promise; return deferred.promise;
}) })
.then(function(result) { .then(function(result) {
+3 -3
View File
@@ -26,13 +26,13 @@ function applyShortcut(content, shortcut) {
/** /**
Replace shortcuts from blocks in a string Replace shortcuts from blocks in a string
@param {TemplateEngine} engine @param {List<TemplateBlock>} engine
@param {String} filePath @param {String} filePath
@param {String} content @param {String} content
@return {String} @return {String}
*/ */
function replaceShortcuts(engine, filePath, content) { function replaceShortcuts(blocks, filePath, content) {
var shortcuts = listShortcuts(engine, filePath); var shortcuts = listShortcuts(blocks, filePath);
return shortcuts.reduce(applyShortcut, content); return shortcuts.reduce(applyShortcut, content);
} }
+3 -3
View File
@@ -15,8 +15,8 @@ describe('Git', function() {
expect(Git.isUrl('git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1')).toBeTruthy(); expect(Git.isUrl('git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1')).toBeTruthy();
// Non valid // Non valid
expect(Git.isUrl('https://github.com/Hello/world.git')).not.toBeTruthy(); expect(Git.isUrl('https://github.com/Hello/world.git')).toBeFalsy();
expect(Git.isUrl('README.md')).not.toBeTruthy(); expect(Git.isUrl('README.md')).toBeFalsy();
}); });
it('should parse HTTPS urls', function() { it('should parse HTTPS urls', function() {
@@ -45,7 +45,7 @@ describe('Git', function() {
}); });
describe('Cloning and resolving', function() { describe('Cloning and resolving', function() {
pit('should clone an HTTPS url', function() { it('should clone an HTTPS url', function() {
var git = new Git(path.join(os.tmpdir(), 'test-git-'+Date.now())); var git = new Git(path.join(os.tmpdir(), 'test-git-'+Date.now()));
return git.resolve('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md') return git.resolve('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md')
.then(function(filename) { .then(function(filename) {

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