mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-26 12:18:01 +00:00
Compare commits
274 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce50a1d50f | |||
| a76e018d36 | |||
| b06f1fd677 | |||
| 3d8b1880dc | |||
| 1a13c8c9bb | |||
| 852fae3a09 | |||
| d342d31c8e | |||
| 5b3f54fbc8 | |||
| 308ece784c | |||
| 0758ddf04e | |||
| f24d9aef73 | |||
| f8bb55f258 | |||
| 76daebeb84 | |||
| b0d366b7e8 | |||
| 3d0054dd64 | |||
| 13bc89ff44 | |||
| bb910f984e | |||
| e576fdea14 | |||
| c7864927ec | |||
| 54956de59f | |||
| 2cbd30d1f2 | |||
| 55614f1675 | |||
| 4f7421993f | |||
| 32ad7b530e | |||
| ee5d5010f6 | |||
| cf7e5884cd | |||
| 11a0a64d9e | |||
| b6051babac | |||
| 34947b5e20 | |||
| c621380b66 | |||
| c4b54033ce | |||
| 4d85d6eb6e | |||
| cc9274c9af | |||
| bbdf7efc34 | |||
| 38a986995a | |||
| ee8d35df7a | |||
| be411cd5ff | |||
| 2104019171 | |||
| 4e564707df | |||
| da8611e0c3 | |||
| 5d11641a5f | |||
| 24d38e4fcf | |||
| efb65a8413 | |||
| 3fc90554f5 | |||
| 4ebe28faae | |||
| 693171cb05 | |||
| 42a2d090aa | |||
| c84eaa83dd | |||
| 74c3ff80cb | |||
| 5893d3ee64 | |||
| 7f435c56a0 | |||
| 59b7100cf6 | |||
| 0923d1aa24 | |||
| ffe6c36f2f | |||
| 8c4752c5d8 | |||
| d6202e522b | |||
| 50a24c62c5 | |||
| 6b17d08892 | |||
| 79c91fd8c5 | |||
| 95764258ab | |||
| 41e6872557 | |||
| 30c96c37c2 | |||
| 20f1cf3dff | |||
| 8de0d6186e | |||
| edd7d8aefe | |||
| a8da4903f4 | |||
| c8df5033f7 | |||
| 412f418da3 | |||
| 3782d21069 | |||
| 82a29f4470 | |||
| 4a65326b24 | |||
| d75d8729be | |||
| c87060454e | |||
| bbba52c7ff | |||
| 33418bdb33 | |||
| de47db2b66 | |||
| fa6fcffbe1 | |||
| 93d3f11891 | |||
| 59479f10fc | |||
| 11e72253d4 | |||
| 37fca9c88a | |||
| 6d67048e98 | |||
| c516a5c1bd | |||
| 429431c13c | |||
| b80dfd7b9a | |||
| 4c144b6ceb | |||
| 718e32773a | |||
| fb75d5d3ee | |||
| 99bbcbebfc | |||
| 289e4b5dc1 | |||
| a974dcdab6 | |||
| 9a48620152 | |||
| e444b3a402 | |||
| fabf4f0c0a | |||
| d467888d6a | |||
| eb46311561 | |||
| 63839bb0a7 | |||
| 793fada0fb | |||
| 494ba78156 | |||
| 9816b27ebf | |||
| 1c43b7891b | |||
| 9d7739213e | |||
| 2ab5f6a305 | |||
| c73103969f | |||
| 6cbbdf5d5b | |||
| 3a48435497 | |||
| 084925de8b | |||
| 5bac2fe695 | |||
| 2c29fb241f | |||
| 77dc11bcc2 | |||
| 5154622566 | |||
| c681cd286a | |||
| c1d53ec11f | |||
| 36b49c66c6 | |||
| 80b8e340da | |||
| b51470db39 | |||
| a5f4596f8d | |||
| 5f3c3760c0 | |||
| 8b05885d57 | |||
| d9d7ab4ee5 | |||
| 30bce5f9bb | |||
| fbe0282738 | |||
| 5bad08b9a0 | |||
| 310691bca2 | |||
| 8f7967acd0 | |||
| faf18476b1 | |||
| 9acccad23a | |||
| db1a0aedf8 | |||
| 01a26fda25 | |||
| 0229272b0a | |||
| c817acff09 | |||
| a5eec81819 | |||
| 98f70879bd | |||
| ceb4627f4e | |||
| 8d9d4fe23f | |||
| 730298110c | |||
| 8578f7d0d1 | |||
| 07a5daefb0 | |||
| 0ed524bce5 | |||
| 94199af7b2 | |||
| b8d7a00f58 | |||
| aca963befc | |||
| 3ea4e334bf | |||
| 9955fa8c29 | |||
| fb37a6bcab | |||
| 4f8bdbb518 | |||
| 73c190891d | |||
| 93e701f471 | |||
| 68b9bf7e38 | |||
| 826f90505c | |||
| 7ae36f16c8 | |||
| 281f6d5535 | |||
| b4072a06c4 | |||
| cb3ee423c9 | |||
| 1360a744a7 | |||
| f3058598dc | |||
| 94db0f6ec9 | |||
| f08732095d | |||
| f946650ddb | |||
| 14fd6bbdce | |||
| 5059e8df34 | |||
| 3d405a1ac5 | |||
| 78e00db018 | |||
| e62bc31e63 | |||
| 4763f1342c | |||
| 9413e53b00 | |||
| 35d352bbeb | |||
| 8dd5e0c250 | |||
| 139e487998 | |||
| 9bb0a3fbd3 | |||
| 9a1a6f2625 | |||
| e781d6d2b1 | |||
| 5b342b50ce | |||
| 869822d180 | |||
| e50422bce5 | |||
| 999882e723 | |||
| d68dc82159 | |||
| cc9fdd0032 | |||
| 503cc4a729 | |||
| d585269228 | |||
| 50f535b910 | |||
| 978bdd149e | |||
| a9d558e891 | |||
| 8291eb58df | |||
| 7298dec353 | |||
| 7574b01863 | |||
| 98e739a12b | |||
| 262ce03e46 | |||
| 1906691a35 | |||
| 4ca23b84a4 | |||
| b05eb8440b | |||
| f4de7748f5 | |||
| 93c26a839b | |||
| 878e7441f2 | |||
| 27d387e3e1 | |||
| 7b7e4f60bc | |||
| 8bac111acf | |||
| 7a46d3b497 | |||
| 357aa92e31 | |||
| fa17f16716 | |||
| ce99c8c706 | |||
| be828cf004 | |||
| 1057bb15d3 | |||
| ea5e8891a8 | |||
| 57d83f082a | |||
| 8a3f80ba82 | |||
| 374ebd6f7a | |||
| ab2ebefe2b | |||
| c34a9f6213 | |||
| f5df41c75f | |||
| 0c2dc06cb7 | |||
| 4552ccd7d7 | |||
| 525bcd8295 | |||
| ac2ad8d8b5 | |||
| 14ce495e9e | |||
| 4aed2cf652 | |||
| 244fb0ca28 | |||
| f6e123f1ed | |||
| e1e4e7f011 | |||
| 814b6ff489 | |||
| 3aa17ab1f4 | |||
| 4646ed8f4a | |||
| 3f5c8acb3c | |||
| 93b257314a | |||
| 19a1db8cb4 | |||
| c3275a4aa9 | |||
| 927185d95c | |||
| a3df6c6f0c | |||
| e1fa977b5b | |||
| ce95f316b9 | |||
| a162af5a75 | |||
| fb0ea4610d | |||
| 4f86a9978b | |||
| cc78b880ce | |||
| 4336fdb241 | |||
| 87db7cf1d4 | |||
| 09ff802c7e | |||
| 71b59b1950 | |||
| a3300ad9c6 | |||
| 8f40d4affa | |||
| 560f3fcccb | |||
| ecaadf63c5 | |||
| d490a6e0b4 | |||
| 53ced205b8 | |||
| 61c4983949 | |||
| fb862f466f | |||
| 8d51300386 | |||
| c4b413563d | |||
| 8f852971f9 | |||
| 3c54c89464 | |||
| d07aa13822 | |||
| c218f7d0e3 | |||
| 72ad872e90 | |||
| 854f244d22 | |||
| 9acbb223db | |||
| c59fc687db | |||
| 0791b917f1 | |||
| ba182ce8e4 | |||
| 663291c515 | |||
| 99253eecea | |||
| 916a02cb36 | |||
| 114c4abcd1 | |||
| e1e3d11932 | |||
| df46217c78 | |||
| d18cc92396 | |||
| 8f63b4b790 | |||
| ce7de93936 | |||
| ef2a24fe6f | |||
| 038cd0155b | |||
| fb430ac1ed | |||
| 53cfa3703b | |||
| 7ed9e8d145 | |||
| 93e0b0cbea | |||
| 0118bd3168 |
+3
-1
@@ -1,2 +1,4 @@
|
||||
docs/**/*
|
||||
test/node_modules/**/*
|
||||
_book/**/*
|
||||
node_modules/**/*
|
||||
test/**/*
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"mocha": true,
|
||||
"browser": true
|
||||
"browser": true,
|
||||
"jest": true,
|
||||
"jasmine": true
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
@@ -3,7 +3,6 @@ language: node_js
|
||||
node_js:
|
||||
- "stable"
|
||||
- "4.1"
|
||||
- "0.12"
|
||||
before_install:
|
||||
- npm install svgexport -g
|
||||
after_success:
|
||||
|
||||
+4
-3
@@ -7,9 +7,10 @@ Names below are ordered by first contribution.
|
||||
Author
|
||||
------
|
||||
|
||||
- Samy Pessé <samypesse@gmail.com> (@SamyPesse)
|
||||
- Aaron O'Mullan <aaron.omullan@gmail.com> (@AaronO)
|
||||
|
||||
- Samy Pessé <samy@gitbook.com> (@SamyPesse)
|
||||
- Aaron O'Mullan <aaron@gitbook.com> (@AaronO)
|
||||
- Johan Preynat <johan@gitbook.com> (@jpreynat)
|
||||
- Nicolas Gaborit <nicolas@gitbook.com> (@soreine)
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
@@ -17,8 +17,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Fix `uk` translation
|
||||
- Fix heading ID including dashes
|
||||
- Fix error in code highlighting for unknown languages
|
||||
- Fix data-uri images being handled as external images
|
||||
- Accept SSH url as plugin version
|
||||
- Add templating blocks `markdown`, `asciidoc` and `markup`
|
||||
- Better search experience
|
||||
- Better default theme, more responsive and cleaner
|
||||
|
||||
## 2.6.7
|
||||
- Fix bug with filenames including spaces
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ init:
|
||||
# Test against these versions of Node.js.
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "0.12"
|
||||
- nodejs_version: "4.1"
|
||||
- nodejs_version: "5"
|
||||
- nodejs_version: "4"
|
||||
|
||||
# Install scripts. (runs after repo cloning)
|
||||
install:
|
||||
|
||||
@@ -6,11 +6,10 @@ module.exports = {
|
||||
title: 'GitBook Toolchain Documentation',
|
||||
|
||||
// Enforce use of GitBook v3
|
||||
gitbook: pkg.version,
|
||||
gitbook: '>=3.0.0-pre.0',
|
||||
|
||||
// Use the "official" theme
|
||||
plugins: ['theme-official', 'sitemap'],
|
||||
theme: 'official',
|
||||
|
||||
variables: {
|
||||
version: pkg.version
|
||||
@@ -18,7 +17,7 @@ module.exports = {
|
||||
|
||||
pluginsConfig: {
|
||||
sitemap: {
|
||||
hostname: 'https://docs.gitbook.com'
|
||||
hostname: 'https://toolchain.gitbook.com'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
GitBook can generates a website, but can also output content as ebook (ePub, Mobi, PDF).
|
||||
|
||||
```
|
||||
# Generate a PDF file
|
||||
$ gitbook pdf ./ ./mybook.pdf
|
||||
|
||||
# Generate an ePub file
|
||||
$ gitbook epub ./ ./mybook.epub
|
||||
|
||||
# Generate a Mobi file
|
||||
$ gitbook mobi ./ ./mybook.mobi
|
||||
```
|
||||
|
||||
### Installing ebook-convert
|
||||
|
||||
`ebook-convert` is required to generate ebooks (epub, mobi, pdf).
|
||||
|
||||
@@ -10,6 +10,8 @@ The following is a reference of the available data during book's parsing and the
|
||||
| `gitbook` | GitBook specific information |
|
||||
| `page` | Current page specific information |
|
||||
| `file` | File associated with the current page specific information |
|
||||
| `readme` | Information about the Readme |
|
||||
| `glossary` | Information about the Glossary |
|
||||
| `summary` | Information about the table of contents |
|
||||
| `languages` | List of languages for multi-lingual books |
|
||||
| `output` | Information about the output generator |
|
||||
@@ -71,3 +73,14 @@ Languages are defined by `{ id: 'en', title: 'English' }`.
|
||||
| `output.name` | Name of the output generator, possible values are `website`, `json`, `ebook` |
|
||||
| `output.format` | When `output.name == "ebook"`, `format` defines the ebook format that will be generated, possible values are `pdf`, `epub` or `mobi` |
|
||||
|
||||
### Readme Variables
|
||||
|
||||
| Variable | Description |
|
||||
| -------- | ----------- |
|
||||
| `readme.path` | Path to the Readme in the book |
|
||||
|
||||
### Glossary Variables
|
||||
|
||||
| Variable | Description |
|
||||
| -------- | ----------- |
|
||||
| `glossary.path` | Path to the Glossary in the book |
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
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);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
var gitbook = require('../gitbook');
|
||||
|
||||
describe('satisfies', function() {
|
||||
|
||||
it('should return true for *', function() {
|
||||
expect(gitbook.satisfies('*')).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
var tmp = require('tmp');
|
||||
var initBook = require('../init');
|
||||
|
||||
describe('initBook', function() {
|
||||
|
||||
pit('should create a README and SUMMARY for empty book', function() {
|
||||
var dir = tmp.dirSync();
|
||||
|
||||
return initBook(dir.name)
|
||||
.then(function() {
|
||||
expect(dir.name).toHaveFile('README.md');
|
||||
expect(dir.name).toHaveFile('SUMMARY.md');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
describe('GitBook', function() {
|
||||
it('should correctly export', function() {
|
||||
require('../');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
var Config = require('../models/config');
|
||||
|
||||
/**
|
||||
Decode changes from a JS API to a config object
|
||||
|
||||
@param {Config} config
|
||||
@param {Object} result: result from API
|
||||
@return {Config}
|
||||
*/
|
||||
function decodeGlobal(config, result) {
|
||||
var values = result.values;
|
||||
|
||||
delete values.generator;
|
||||
delete values.output;
|
||||
|
||||
return Config.updateValues(config, values);
|
||||
}
|
||||
|
||||
module.exports = decodeGlobal;
|
||||
@@ -0,0 +1,22 @@
|
||||
var decodeConfig = require('./decodeConfig');
|
||||
|
||||
/**
|
||||
Decode changes from a JS API to a output object.
|
||||
Only the configuration can be edited by plugin's hooks
|
||||
|
||||
@param {Output} output
|
||||
@param {Object} result: result from API
|
||||
@return {Output}
|
||||
*/
|
||||
function decodeGlobal(output, result) {
|
||||
var book = output.getBook();
|
||||
var config = book.getConfig();
|
||||
|
||||
// Update config
|
||||
config = decodeConfig(config, result.config);
|
||||
book = book.set('config', config);
|
||||
|
||||
return output.set('book', book);
|
||||
}
|
||||
|
||||
module.exports = decodeGlobal;
|
||||
@@ -0,0 +1,44 @@
|
||||
var deprecate = require('./deprecate');
|
||||
|
||||
/**
|
||||
Decode changes from a JS API to a page object.
|
||||
Only the content can be edited by plugin's hooks.
|
||||
|
||||
@param {Output} output
|
||||
@param {Page} page: page instance to edit
|
||||
@param {Object} result: result from API
|
||||
@return {Page}
|
||||
*/
|
||||
function decodePage(output, page, result) {
|
||||
var originalContent = page.getContent();
|
||||
|
||||
// No returned value
|
||||
// Existing content will be used
|
||||
if (!result) {
|
||||
return page;
|
||||
}
|
||||
|
||||
deprecate.disable('page.sections');
|
||||
|
||||
// GitBook 3
|
||||
// Use returned page.content if different from original content
|
||||
if (result.content != originalContent) {
|
||||
page = page.set('content', result.content);
|
||||
}
|
||||
|
||||
// GitBook 2 compatibility
|
||||
// Finally, use page.sections
|
||||
else if (result.sections) {
|
||||
page = page.set('content',
|
||||
result.sections.map(function(section) {
|
||||
return section.content;
|
||||
}).join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
deprecate.enable('page.sections');
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
module.exports = decodePage;
|
||||
@@ -0,0 +1,122 @@
|
||||
var is = require('is');
|
||||
var objectPath = require('object-path');
|
||||
|
||||
var logged = {};
|
||||
var disabled = {};
|
||||
|
||||
/**
|
||||
Log a deprecated notice
|
||||
|
||||
@param {Book|Output} book
|
||||
@param {String} key
|
||||
@param {String} message
|
||||
*/
|
||||
function logNotice(book, key, message) {
|
||||
if (logged[key] || disabled[key]) return;
|
||||
|
||||
logged[key] = true;
|
||||
|
||||
var logger = book.getLogger();
|
||||
logger.warn.ln(message);
|
||||
}
|
||||
|
||||
/**
|
||||
Deprecate a function
|
||||
|
||||
@param {Book|Output} book
|
||||
@param {String} key: unique identitifer for the deprecated
|
||||
@param {Function} fn
|
||||
@param {String} msg: message to print when called
|
||||
@return {Function}
|
||||
*/
|
||||
function deprecateMethod(book, key, fn, msg) {
|
||||
return function() {
|
||||
logNotice(book, key, msg);
|
||||
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Deprecate a property of an object
|
||||
|
||||
@param {Book|Output} book
|
||||
@param {String} key: unique identitifer for the deprecated
|
||||
@param {Object} instance
|
||||
@param {String|Function} property
|
||||
@param {String} msg: message to print when called
|
||||
@return {Function}
|
||||
*/
|
||||
function deprecateField(book, key, instance, property, value, msg) {
|
||||
var store = undefined;
|
||||
|
||||
var prepare = function() {
|
||||
if (!is.undefined(store)) return;
|
||||
|
||||
if (is.fn(value)) store = value();
|
||||
else store = value;
|
||||
};
|
||||
|
||||
var getter = function(){
|
||||
prepare();
|
||||
|
||||
logNotice(book, key, msg);
|
||||
return store;
|
||||
};
|
||||
var setter = function(v) {
|
||||
prepare();
|
||||
|
||||
logNotice(book, key, msg);
|
||||
store = v;
|
||||
return store;
|
||||
};
|
||||
|
||||
Object.defineProperty(instance, property, {
|
||||
get: getter,
|
||||
set: setter,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Enable a deprecation
|
||||
|
||||
@param {String} key: unique identitifer
|
||||
*/
|
||||
function enableDeprecation(key) {
|
||||
disabled[key] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
Disable a deprecation
|
||||
|
||||
@param {String} key: unique identitifer
|
||||
*/
|
||||
function disableDeprecation(key) {
|
||||
disabled[key] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Deprecate a method in favor of another one
|
||||
|
||||
@param {Book} book
|
||||
@param {String} key
|
||||
@param {Object} instance
|
||||
@param {String} oldName
|
||||
@param {String} newName
|
||||
*/
|
||||
function deprecateRenamedMethod(book, key, instance, oldName, newName, msg) {
|
||||
msg = msg || ('"' + oldName + '" is deprecated, use "' + newName + '()" instead');
|
||||
var fn = objectPath.get(instance, newName);
|
||||
|
||||
instance[oldName] = deprecateMethod(book, key, fn, msg);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
method: deprecateMethod,
|
||||
renamedMethod: deprecateRenamedMethod,
|
||||
field: deprecateField,
|
||||
enable: enableDeprecation,
|
||||
disable: disableDeprecation
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
var objectPath = require('object-path');
|
||||
var deprecate = require('./deprecate');
|
||||
|
||||
/**
|
||||
Encode a config object into a JS config api
|
||||
|
||||
@param {Output} output
|
||||
@param {Config} config
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeConfig(output, config) {
|
||||
var result = {
|
||||
values: config.getValues().toJS(),
|
||||
|
||||
get: function(key, defaultValue) {
|
||||
return objectPath.get(result.values, key, defaultValue);
|
||||
},
|
||||
|
||||
set: function(key, value) {
|
||||
return objectPath.set(result.values, key, value);
|
||||
}
|
||||
};
|
||||
|
||||
deprecate.field(output, 'config.options', result, 'options',
|
||||
result.values, '"config.options" property is deprecated, use "config.get(key)" instead');
|
||||
|
||||
deprecate.field(output, 'config.options.generator', result.values, 'generator',
|
||||
output.getGenerator(), '"options.generator" property is deprecated, use "output.name" instead');
|
||||
|
||||
deprecate.field(output, 'config.options.generator', result.values, 'output',
|
||||
output.getRoot(), '"options.output" property is deprecated, use "output.root()" instead');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeConfig;
|
||||
@@ -0,0 +1,178 @@
|
||||
var path = require('path');
|
||||
var Promise = require('../utils/promise');
|
||||
var PathUtils = require('../utils/path');
|
||||
var fs = require('../utils/fs');
|
||||
|
||||
var Plugins = require('../plugins');
|
||||
var deprecate = require('./deprecate');
|
||||
var fileToURL = require('../output/helper/fileToURL');
|
||||
var defaultBlocks = require('../constants/defaultBlocks');
|
||||
var gitbook = require('../gitbook');
|
||||
|
||||
var encodeConfig = require('./encodeConfig');
|
||||
var encodeSummary = require('./encodeSummary');
|
||||
var encodeNavigation = require('./encodeNavigation');
|
||||
|
||||
/**
|
||||
Encode a global context into a JS object
|
||||
It's the context for page's hook, etc
|
||||
|
||||
@param {Output} output
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeGlobal(output) {
|
||||
var book = output.getBook();
|
||||
var bookFS = book.getContentFS();
|
||||
var logger = output.getLogger();
|
||||
var outputFolder = output.getRoot();
|
||||
var plugins = output.getPlugins();
|
||||
var blocks = Plugins.listBlocks(plugins);
|
||||
|
||||
var result = {
|
||||
log: logger,
|
||||
config: encodeConfig(output, book.getConfig()),
|
||||
summary: encodeSummary(output, book.getSummary()),
|
||||
|
||||
/**
|
||||
Check if the book is a multilingual book
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
isMultilingual: function() {
|
||||
return book.isMultilingual();
|
||||
},
|
||||
|
||||
/**
|
||||
Check if the book is a language book for a multilingual book
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
isLanguageBook: function() {
|
||||
return book.isLanguageBook();
|
||||
},
|
||||
|
||||
/**
|
||||
Read a file from the book
|
||||
|
||||
@param {String} fileName
|
||||
@return {Promise<Buffer>}
|
||||
*/
|
||||
readFile: function(fileName) {
|
||||
return bookFS.read(fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
Read a file from the book as a string
|
||||
|
||||
@param {String} fileName
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
readFileAsString: function(fileName) {
|
||||
return bookFS.readAsString(fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
Resolve a file from the book root
|
||||
|
||||
@param {String} fileName
|
||||
@return {String}
|
||||
*/
|
||||
resolve: function(fileName) {
|
||||
return path.resolve(book.getContentRoot(), fileName);
|
||||
},
|
||||
|
||||
template: {
|
||||
/**
|
||||
Apply a templating block and returns its result
|
||||
|
||||
@param {String} name
|
||||
@param {Object} blockData
|
||||
@return {Promise|Object}
|
||||
*/
|
||||
applyBlock: function(name, blockData) {
|
||||
var block = blocks.get(name) || defaultBlocks.get(name);
|
||||
return Promise(block.applyBlock(blockData, result));
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
/**
|
||||
Name of the generator being used
|
||||
{String}
|
||||
*/
|
||||
name: output.getGenerator(),
|
||||
|
||||
/**
|
||||
Return absolute path to the root folder of output
|
||||
@return {String}
|
||||
*/
|
||||
root: function() {
|
||||
return outputFolder;
|
||||
},
|
||||
|
||||
/**
|
||||
Resolve a file from the output root
|
||||
|
||||
@param {String} fileName
|
||||
@return {String}
|
||||
*/
|
||||
resolve: function(fileName) {
|
||||
return path.resolve(outputFolder, fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
Convert a filepath into an url
|
||||
@return {String}
|
||||
*/
|
||||
toURL: function(filePath) {
|
||||
return fileToURL(output, filePath);
|
||||
},
|
||||
|
||||
/**
|
||||
Write a file to the output folder,
|
||||
It creates the required folder
|
||||
|
||||
@param {String} fileName
|
||||
@param {Buffer} content
|
||||
@return {Promise}
|
||||
*/
|
||||
writeFile: function(fileName, content) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
|
||||
|
||||
return fs.ensureFile(filePath)
|
||||
.then(function() {
|
||||
return fs.writeFile(filePath, content);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
gitbook: {
|
||||
version: gitbook.version
|
||||
}
|
||||
};
|
||||
|
||||
// Deprecated properties
|
||||
|
||||
deprecate.renamedMethod(output, 'this.isSubBook', result, 'isSubBook', 'isLanguageBook');
|
||||
deprecate.renamedMethod(output, 'this.contentLink', result, 'contentLink', 'output.toURL');
|
||||
|
||||
deprecate.field(output, 'this.generator', result, 'generator',
|
||||
output.getGenerator(), '"this.generator" property is deprecated, use "this.output.name" instead');
|
||||
|
||||
deprecate.field(output, 'this.navigation', result, 'navigation', function() {
|
||||
return encodeNavigation(output);
|
||||
}, '"navigation" property is deprecated');
|
||||
|
||||
deprecate.field(output, 'this.book', result, 'book',
|
||||
result, '"book" property is deprecated, use "this" directly instead');
|
||||
|
||||
deprecate.field(output, 'this.options', result, 'options',
|
||||
result.config.values, '"options" property is deprecated, use config.get(key) instead');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeGlobal;
|
||||
@@ -0,0 +1,64 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
/**
|
||||
Encode an article for next/prev
|
||||
|
||||
@param {Map<String:Page>}
|
||||
@param {Article}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeArticle(pages, article) {
|
||||
var articlePath = article.getPath();
|
||||
|
||||
return {
|
||||
path: articlePath,
|
||||
title: article.getTitle(),
|
||||
level: article.getLevel(),
|
||||
exists: (articlePath && pages.has(articlePath)),
|
||||
external: article.isExternal()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
this.navigation is a deprecated property from GitBook v2
|
||||
|
||||
@param {Output}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeNavigation(output) {
|
||||
var book = output.getBook();
|
||||
var pages = output.getPages();
|
||||
var summary = book.getSummary();
|
||||
var articles = summary.getArticlesAsList();
|
||||
|
||||
|
||||
var navigation = articles
|
||||
.map(function(article, i) {
|
||||
var ref = article.getRef();
|
||||
if (!ref) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var prev = articles.get(i - 1);
|
||||
var next = articles.get(i + 1);
|
||||
|
||||
return [
|
||||
ref,
|
||||
{
|
||||
index: i,
|
||||
title: article.getTitle(),
|
||||
introduction: (i === 0),
|
||||
prev: prev? encodeArticle(pages, prev) : undefined,
|
||||
next: next? encodeArticle(pages, next) : undefined,
|
||||
level: article.getLevel()
|
||||
}
|
||||
];
|
||||
})
|
||||
.filter(function(e) {
|
||||
return Boolean(e);
|
||||
});
|
||||
|
||||
return Immutable.Map(navigation).toJS();
|
||||
}
|
||||
|
||||
module.exports = encodeNavigation;
|
||||
@@ -0,0 +1,39 @@
|
||||
var JSONUtils = require('../json');
|
||||
var deprecate = require('./deprecate');
|
||||
var encodeProgress = require('./encodeProgress');
|
||||
|
||||
/**
|
||||
Encode a page in a context to a JS API
|
||||
|
||||
@param {Output} output
|
||||
@param {Page} page
|
||||
@return {Object}
|
||||
*/
|
||||
function encodePage(output, page) {
|
||||
var book = output.getBook();
|
||||
var summary = book.getSummary();
|
||||
var fs = book.getContentFS();
|
||||
var file = page.getFile();
|
||||
|
||||
// JS Page is based on the JSON output
|
||||
var result = JSONUtils.encodePage(page, summary);
|
||||
|
||||
result.type = file.getType();
|
||||
result.path = file.getPath();
|
||||
result.rawPath = fs.resolve(result.path);
|
||||
|
||||
deprecate.field(output, 'page.progress', result, 'progress', function() {
|
||||
return encodeProgress(output, page);
|
||||
}, '"page.progress" property is deprecated');
|
||||
|
||||
deprecate.field(output, 'page.sections', result, 'sections', [
|
||||
{
|
||||
content: result.content,
|
||||
type: 'normal'
|
||||
}
|
||||
], '"sections" property is deprecated, use page.content instead');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodePage;
|
||||
@@ -0,0 +1,63 @@
|
||||
var Immutable = require('immutable');
|
||||
var encodeNavigation = require('./encodeNavigation');
|
||||
|
||||
/**
|
||||
page.progress is a deprecated property from GitBook v2
|
||||
|
||||
@param {Output}
|
||||
@param {Page}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeProgress(output, page) {
|
||||
var current = page.getPath();
|
||||
var navigation = encodeNavigation(output);
|
||||
navigation = Immutable.Map(navigation);
|
||||
|
||||
var n = navigation.size;
|
||||
var percent = 0, prevPercent = 0, currentChapter = null;
|
||||
var done = true;
|
||||
|
||||
var chapters = navigation
|
||||
.map(function(nav, chapterPath) {
|
||||
nav.path = chapterPath;
|
||||
return nav;
|
||||
})
|
||||
.valueSeq()
|
||||
.sortBy(function(nav) {
|
||||
return nav.index;
|
||||
})
|
||||
.map(function(nav, i) {
|
||||
// Calcul percent
|
||||
nav.percent = (i * 100) / Math.max((n - 1), 1);
|
||||
|
||||
// Is it done
|
||||
nav.done = done;
|
||||
if (nav.path == current) {
|
||||
currentChapter = nav;
|
||||
percent = nav.percent;
|
||||
done = false;
|
||||
} else if (done) {
|
||||
prevPercent = nav.percent;
|
||||
}
|
||||
|
||||
return nav;
|
||||
})
|
||||
.toJS();
|
||||
|
||||
return {
|
||||
// Previous percent
|
||||
prevPercent: prevPercent,
|
||||
|
||||
// Current percent
|
||||
percent: percent,
|
||||
|
||||
// List of chapter with progress
|
||||
chapters: chapters,
|
||||
|
||||
// Current chapter
|
||||
current: currentChapter
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeProgress;
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
module.exports = {
|
||||
encodePage: require('./encodePage'),
|
||||
decodePage: require('./decodePage'),
|
||||
|
||||
encodeGlobal: require('./encodeGlobal'),
|
||||
decodeGlobal: require('./decodeGlobal')
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
function BackboneFile(book) {
|
||||
if (!(this instanceof BackboneFile)) return new BackboneFile(book);
|
||||
|
||||
this.book = book;
|
||||
this.log = this.book.log;
|
||||
|
||||
// Filename in the book
|
||||
this.path = '';
|
||||
this.parser;
|
||||
|
||||
_.bindAll(this);
|
||||
}
|
||||
|
||||
// Type of the backbone file
|
||||
BackboneFile.prototype.type = '';
|
||||
|
||||
// Parse a backbone file
|
||||
BackboneFile.prototype.parse = function() {
|
||||
// To be implemented by each child
|
||||
};
|
||||
|
||||
// Handle case where file doesn't exists
|
||||
BackboneFile.prototype.parseNotFound = function() {
|
||||
|
||||
};
|
||||
|
||||
// Return true if backbone file exists
|
||||
BackboneFile.prototype.exists = function() {
|
||||
return Boolean(this.path);
|
||||
};
|
||||
|
||||
// Locate a backbone file, could be .md, .asciidoc, etc
|
||||
BackboneFile.prototype.locate = function() {
|
||||
var that = this;
|
||||
var filename = this.book.config.getStructure(this.type, true);
|
||||
this.log.debug.ln('locating', this.type, ':', filename);
|
||||
|
||||
return this.book.findParsableFile(filename)
|
||||
.then(function(result) {
|
||||
if (!result) return;
|
||||
|
||||
that.path = result.path;
|
||||
that.parser = result.parser;
|
||||
});
|
||||
};
|
||||
|
||||
// Read and parse the file
|
||||
BackboneFile.prototype.load = function() {
|
||||
var that = this;
|
||||
this.log.debug.ln('loading', this.type, ':', that.path);
|
||||
|
||||
return this.locate()
|
||||
.then(function() {
|
||||
if (!that.path) return that.parseNotFound();
|
||||
|
||||
that.log.debug.ln(that.type, 'located at', that.path);
|
||||
|
||||
return that.book.readFile(that.path)
|
||||
|
||||
// Parse it
|
||||
.then(function(content) {
|
||||
return that.parse(content);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = BackboneFile;
|
||||
@@ -1,99 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
// Normalize a glossary entry name into a unique id
|
||||
function nameToId(name) {
|
||||
return name.toLowerCase()
|
||||
.replace(/[\/\\\?\%\*\:\;\|\"\'\\<\\>\#\$\(\)\!\.\@]/g, '')
|
||||
.replace(/ /g, '_')
|
||||
.trim();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
A glossary entry is represented by a name and a short description
|
||||
An unique id for the entry is generated using its name
|
||||
*/
|
||||
function GlossaryEntry(name, description) {
|
||||
if (!(this instanceof GlossaryEntry)) return new GlossaryEntry(name, description);
|
||||
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
|
||||
Object.defineProperty(this, 'id', {
|
||||
get: _.bind(this.getId, this)
|
||||
});
|
||||
}
|
||||
|
||||
// Normalizes a glossary entry's name to create an ID
|
||||
GlossaryEntry.prototype.getId = function() {
|
||||
return nameToId(this.name);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
A glossary is a list of entries stored in a GLOSSARY.md file
|
||||
*/
|
||||
function Glossary() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.entries = [];
|
||||
}
|
||||
util.inherits(Glossary, BackboneFile);
|
||||
|
||||
Glossary.prototype.type = 'glossary';
|
||||
|
||||
// Get templating context
|
||||
Glossary.prototype.getContext = function() {
|
||||
if (!this.path) return {};
|
||||
|
||||
return {
|
||||
glossary: {
|
||||
path: this.path
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Parse the readme content
|
||||
Glossary.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.glossary(content)
|
||||
.then(function(entries) {
|
||||
that.entries = _.map(entries, function(entry) {
|
||||
return new GlossaryEntry(entry.name, entry.description);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Return an entry by its id
|
||||
Glossary.prototype.get = function(id) {
|
||||
return _.find(this.entries, {
|
||||
id: id
|
||||
});
|
||||
};
|
||||
|
||||
// Find an entry by its name
|
||||
Glossary.prototype.find = function(name) {
|
||||
return this.get(nameToId(name));
|
||||
};
|
||||
|
||||
// Return false if glossary has entries (and exists)
|
||||
Glossary.prototype.isEmpty = function(id) {
|
||||
return _.size(this.entries) === 0;
|
||||
};
|
||||
|
||||
// Convert the glossary to a list of annotations
|
||||
Glossary.prototype.annotations = function() {
|
||||
return _.map(this.entries, function(entry) {
|
||||
return {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
description: entry.description,
|
||||
href: '/' + this.path + '#' + entry.id
|
||||
};
|
||||
}, this);
|
||||
};
|
||||
|
||||
module.exports = Glossary;
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
Readme: require('./readme'),
|
||||
Summary: require('./summary'),
|
||||
Glossary: require('./glossary'),
|
||||
Langs: require('./langs')
|
||||
};
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
function Language(title, folder) {
|
||||
var that = this;
|
||||
|
||||
this.title = title;
|
||||
this.folder = folder;
|
||||
|
||||
Object.defineProperty(this, 'id', {
|
||||
get: function() {
|
||||
return path.basename(that.folder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
A Langs is a list of languages stored in a LANGS.md file
|
||||
*/
|
||||
function Langs() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.languages = [];
|
||||
}
|
||||
util.inherits(Langs, BackboneFile);
|
||||
|
||||
Langs.prototype.type = 'langs';
|
||||
|
||||
// Parse the readme content
|
||||
Langs.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.langs(content)
|
||||
.then(function(langs) {
|
||||
that.languages = _.map(langs, function(entry) {
|
||||
return new Language(entry.title, entry.path);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Return the list of languages
|
||||
Langs.prototype.list = function() {
|
||||
return this.languages;
|
||||
};
|
||||
|
||||
// Return default/main language for the book
|
||||
Langs.prototype.getDefault = function() {
|
||||
return _.first(this.languages);
|
||||
};
|
||||
|
||||
// Return true if a language is the default one
|
||||
// "lang" cam be a string (id) or a Language entry
|
||||
Langs.prototype.isDefault = function(lang) {
|
||||
lang = lang.id || lang;
|
||||
return (this.cound() > 0 && this.getDefault().id == lang);
|
||||
};
|
||||
|
||||
// Return the count of languages
|
||||
Langs.prototype.count = function() {
|
||||
return _.size(this.languages);
|
||||
};
|
||||
|
||||
// Return templating context for the languages list
|
||||
Langs.prototype.getContext = function() {
|
||||
if (this.count() == 0) return {};
|
||||
|
||||
return {
|
||||
languages: {
|
||||
list: _.map(this.languages, function(lang) {
|
||||
return {
|
||||
id: lang.id,
|
||||
title: lang.title
|
||||
};
|
||||
})
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Langs;
|
||||
@@ -1,26 +0,0 @@
|
||||
var util = require('util');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
function Readme() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.title;
|
||||
this.description;
|
||||
}
|
||||
util.inherits(Readme, BackboneFile);
|
||||
|
||||
Readme.prototype.type = 'readme';
|
||||
|
||||
// Parse the readme content
|
||||
Readme.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.readme(content)
|
||||
.then(function(out) {
|
||||
that.title = out.title;
|
||||
that.description = out.description;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = Readme;
|
||||
@@ -1,348 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
|
||||
var location = require('../utils/location');
|
||||
var error = require('../utils/error');
|
||||
var BackboneFile = require('./file');
|
||||
|
||||
/*
|
||||
An article represent an entry in the Summary.
|
||||
It's defined by a title, a reference, and children articles,
|
||||
the reference (ref) can be a filename + anchor or an external file (optional)
|
||||
*/
|
||||
function TOCArticle(def, parent) {
|
||||
// Title
|
||||
this.title = def.title;
|
||||
|
||||
// Parent TOCPart or TOCArticle
|
||||
this.parent = parent;
|
||||
|
||||
// As string indicating the overall position
|
||||
// ex: '1.0.0'
|
||||
this.level;
|
||||
this._next;
|
||||
this._prev;
|
||||
|
||||
// When README has been automatically added
|
||||
this.isAutoIntro = def.isAutoIntro;
|
||||
this.isIntroduction = def.isIntroduction;
|
||||
|
||||
this.validate();
|
||||
|
||||
// Path can be a relative path or an url, or nothing
|
||||
this.ref = def.path;
|
||||
if (this.ref && !this.isExternal()) {
|
||||
var parts = this.ref.split('#');
|
||||
this.path = (parts.length > 1? parts.slice(0, -1).join('#') : this.ref);
|
||||
this.anchor = (parts.length > 1? '#' + _.last(parts) : null);
|
||||
|
||||
// Normalize path to remove ('./', etc)
|
||||
this.path = location.normalize(this.path);
|
||||
}
|
||||
|
||||
this.articles = _.map(def.articles || [], function(article) {
|
||||
if (article instanceof TOCArticle) return article;
|
||||
return new TOCArticle(article, this);
|
||||
}, this);
|
||||
}
|
||||
|
||||
// Validate the article
|
||||
TOCArticle.prototype.validate = function() {
|
||||
if (!this.title) {
|
||||
throw error.ParsingError(new Error('SUMMARY entries should have an non-empty title'));
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over all articles in this articles
|
||||
TOCArticle.prototype.walk = function(iter, base) {
|
||||
base = base || this.level;
|
||||
|
||||
_.each(this.articles, function(article, i) {
|
||||
var level = levelId(base, i);
|
||||
|
||||
if (iter(article, level) === false) {
|
||||
return false;
|
||||
}
|
||||
article.walk(iter, level);
|
||||
});
|
||||
};
|
||||
|
||||
// Return templating context for an article
|
||||
TOCArticle.prototype.getContext = function() {
|
||||
return {
|
||||
level: this.level,
|
||||
title: this.title,
|
||||
depth: this.depth(),
|
||||
path: this.isExternal()? undefined : this.path,
|
||||
anchor: this.isExternal()? undefined : this.anchor,
|
||||
url: this.isExternal()? this.ref : undefined
|
||||
};
|
||||
};
|
||||
|
||||
// Return true if is pointing to a file
|
||||
TOCArticle.prototype.hasLocation = function() {
|
||||
return Boolean(this.path);
|
||||
};
|
||||
|
||||
// Return true if is pointing to an external location
|
||||
TOCArticle.prototype.isExternal = function() {
|
||||
return location.isExternal(this.ref);
|
||||
};
|
||||
|
||||
// Return true if this article is the introduction
|
||||
TOCArticle.prototype.isIntro = function() {
|
||||
return Boolean(this.isIntroduction);
|
||||
};
|
||||
|
||||
// Return true if has children
|
||||
TOCArticle.prototype.hasChildren = function() {
|
||||
return this.articles.length > 0;
|
||||
};
|
||||
|
||||
// Return true if has an article as parent
|
||||
TOCArticle.prototype.hasParent = function() {
|
||||
return !(this.parent instanceof TOCPart);
|
||||
};
|
||||
|
||||
// Return depth of this article
|
||||
TOCArticle.prototype.depth = function() {
|
||||
return this.level.split('.').length;
|
||||
};
|
||||
|
||||
// Return next article in the TOC
|
||||
TOCArticle.prototype.next = function() {
|
||||
return this._next;
|
||||
};
|
||||
|
||||
// Return previous article in the TOC
|
||||
TOCArticle.prototype.prev = function() {
|
||||
return this._prev;
|
||||
};
|
||||
|
||||
// Map over all articles
|
||||
TOCArticle.prototype.map = function(iter) {
|
||||
return _.map(this.articles, iter);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
A part of a ToC is a composed of a tree of articles and an optiona title
|
||||
*/
|
||||
function TOCPart(part, parent) {
|
||||
if (!(this instanceof TOCPart)) return new TOCPart(part, parent);
|
||||
|
||||
TOCArticle.apply(this, arguments);
|
||||
}
|
||||
util.inherits(TOCPart, TOCArticle);
|
||||
|
||||
// Validate the part
|
||||
TOCPart.prototype.validate = function() { };
|
||||
|
||||
// Return a sibling (next or prev) of this part
|
||||
TOCPart.prototype.sibling = function(direction) {
|
||||
var parts = this.parent.parts;
|
||||
var pos = _.findIndex(parts, this);
|
||||
|
||||
if (parts[pos + direction]) {
|
||||
return parts[pos + direction];
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Iterate over all entries of the part
|
||||
TOCPart.prototype.walk = function(iter, base) {
|
||||
var articles = this.articles;
|
||||
|
||||
if (articles.length == 0) return;
|
||||
|
||||
// Has introduction?
|
||||
if (articles[0].isIntro()) {
|
||||
if (iter(articles[0], '0') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
articles = articles.slice(1);
|
||||
}
|
||||
|
||||
|
||||
_.each(articles, function(article, i) {
|
||||
var level = levelId(base, i);
|
||||
|
||||
if (iter(article, level) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
article.walk(iter, level);
|
||||
});
|
||||
};
|
||||
|
||||
// Return templating context for a part
|
||||
TOCPart.prototype.getContext = function(onArticle) {
|
||||
onArticle = onArticle || function(article) {
|
||||
return article.getContext();
|
||||
};
|
||||
|
||||
return {
|
||||
title: this.title,
|
||||
articles: this.map(onArticle)
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
A summary is composed of a list of parts, each composed wit a tree of articles.
|
||||
*/
|
||||
function Summary() {
|
||||
BackboneFile.apply(this, arguments);
|
||||
|
||||
this.parts = [];
|
||||
this._length = 0;
|
||||
}
|
||||
util.inherits(Summary, BackboneFile);
|
||||
|
||||
Summary.prototype.type = 'summary';
|
||||
|
||||
// Prepare summary when non existant
|
||||
Summary.prototype.parseNotFound = function() {
|
||||
this.update([]);
|
||||
};
|
||||
|
||||
// Parse the summary content
|
||||
Summary.prototype.parse = function(content) {
|
||||
var that = this;
|
||||
|
||||
return this.parser.summary(content)
|
||||
|
||||
.then(function(summary) {
|
||||
that.update(summary.parts);
|
||||
});
|
||||
};
|
||||
|
||||
// Return templating context for the summary
|
||||
Summary.prototype.getContext = function() {
|
||||
function onArticle(article) {
|
||||
var result = article.getContext();
|
||||
if (article.hasChildren()) {
|
||||
result.articles = article.map(onArticle);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
summary: {
|
||||
parts: _.map(this.parts, function(part) {
|
||||
return part.getContext(onArticle);
|
||||
})
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Iterate over all entries of the summary
|
||||
// iter is called with an TOCArticle
|
||||
Summary.prototype.walk = function(iter) {
|
||||
var hasMultipleParts = this.parts.length > 1;
|
||||
|
||||
_.each(this.parts, function(part, i) {
|
||||
part.walk(iter, hasMultipleParts? levelId('', i) : null);
|
||||
});
|
||||
};
|
||||
|
||||
// Find a specific article using a filter
|
||||
Summary.prototype.find = function(filter) {
|
||||
var result;
|
||||
|
||||
this.walk(function(article) {
|
||||
if (filter(article)) {
|
||||
result = article;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Flatten the list of articles
|
||||
Summary.prototype.flatten = function() {
|
||||
var result = [];
|
||||
|
||||
this.walk(function(article) {
|
||||
result.push(article);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Return the first TOCArticle for a specific page (or path)
|
||||
Summary.prototype.getArticle = function(page) {
|
||||
if (!_.isString(page)) page = page.path;
|
||||
|
||||
return this.find(function(article) {
|
||||
return article.path == page;
|
||||
});
|
||||
};
|
||||
|
||||
// Return the first TOCArticle for a specific level
|
||||
Summary.prototype.getArticleByLevel = function(lvl) {
|
||||
return this.find(function(article) {
|
||||
return article.level == lvl;
|
||||
});
|
||||
};
|
||||
|
||||
// Return the count of articles in the summary
|
||||
Summary.prototype.count = function() {
|
||||
return this._length;
|
||||
};
|
||||
|
||||
// Prepare the summary
|
||||
Summary.prototype.update = function(parts) {
|
||||
var that = this;
|
||||
|
||||
|
||||
that.parts = _.map(parts, function(part) {
|
||||
return new TOCPart(part, that);
|
||||
});
|
||||
|
||||
// Create first part if none
|
||||
if (that.parts.length == 0) {
|
||||
that.parts.push(new TOCPart({}, that));
|
||||
}
|
||||
|
||||
// Add README as first entry
|
||||
var firstArticle = that.parts[0].articles[0];
|
||||
if (!firstArticle || firstArticle.path != that.book.readme.path) {
|
||||
that.parts[0].articles.unshift(new TOCArticle({
|
||||
title: 'Introduction',
|
||||
path: that.book.readme.path,
|
||||
isAutoIntro: true
|
||||
}, that.parts[0]));
|
||||
}
|
||||
that.parts[0].articles[0].isIntroduction = true;
|
||||
|
||||
|
||||
// Update the count and indexing of "level"
|
||||
var prev = undefined;
|
||||
|
||||
that._length = 0;
|
||||
that.walk(function(article, level) {
|
||||
// Index level
|
||||
article.level = level;
|
||||
|
||||
// Chain articles
|
||||
article._prev = prev;
|
||||
if (prev) prev._next = article;
|
||||
|
||||
prev = article;
|
||||
|
||||
that._length += 1;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Return a level string from a base level and an index
|
||||
function levelId(base, i) {
|
||||
i = i + 1;
|
||||
return (base? [base || '', i] : [i]).join('.');
|
||||
}
|
||||
|
||||
module.exports = Summary;
|
||||
-405
@@ -1,405 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
var Ignore = require('ignore');
|
||||
|
||||
var Config = require('./config');
|
||||
var Readme = require('./backbone/readme');
|
||||
var Glossary = require('./backbone/glossary');
|
||||
var Summary = require('./backbone/summary');
|
||||
var Langs = require('./backbone/langs');
|
||||
var Page = require('./page');
|
||||
var pathUtil = require('./utils/path');
|
||||
var error = require('./utils/error');
|
||||
var Promise = require('./utils/promise');
|
||||
var Logger = require('./utils/logger');
|
||||
var parsers = require('./parsers');
|
||||
var initBook = require('./init');
|
||||
|
||||
|
||||
/*
|
||||
The Book class is an interface for parsing books content.
|
||||
It does not require to run on Node.js, isnce it only depends on the fs implementation
|
||||
*/
|
||||
|
||||
function Book(opts) {
|
||||
if (!(this instanceof Book)) return new Book(opts);
|
||||
|
||||
this.opts = _.defaults(opts || {}, {
|
||||
fs: null,
|
||||
|
||||
// Root path for the book
|
||||
root: '',
|
||||
|
||||
// Extend book configuration
|
||||
config: {},
|
||||
|
||||
// Log function
|
||||
log: function(msg) {
|
||||
process.stdout.write(msg);
|
||||
},
|
||||
|
||||
// Log level
|
||||
logLevel: 'info'
|
||||
});
|
||||
|
||||
if (!opts.fs) throw error.ParsingError(new Error('Book requires a fs instance'));
|
||||
|
||||
// Root path for the book
|
||||
this.root = opts.root;
|
||||
|
||||
// If multi-lingual, book can have a parent
|
||||
this.parent = opts.parent;
|
||||
if (this.parent) {
|
||||
this.language = path.relative(this.parent.root, this.root);
|
||||
}
|
||||
|
||||
// A book is linked to an fs, to access its content
|
||||
this.fs = opts.fs;
|
||||
|
||||
// Rules to ignore some files
|
||||
this.ignore = Ignore();
|
||||
this.ignore.addPattern([
|
||||
// Skip Git stuff
|
||||
'.git/',
|
||||
|
||||
// Skip OS X meta data
|
||||
'.DS_Store',
|
||||
|
||||
// Skip stuff installed by plugins
|
||||
'node_modules',
|
||||
|
||||
// Skip book outputs
|
||||
'_book',
|
||||
'*.pdf',
|
||||
'*.epub',
|
||||
'*.mobi'
|
||||
]);
|
||||
|
||||
// Create a logger for the book
|
||||
this.log = new Logger(opts.log, opts.logLevel);
|
||||
|
||||
// Create an interface to access the configuration
|
||||
this.config = new Config(this, opts.config);
|
||||
|
||||
// Interfaces for the book structure
|
||||
this.readme = new Readme(this);
|
||||
this.summary = new Summary(this);
|
||||
this.glossary = new Glossary(this);
|
||||
|
||||
// Multilinguals book
|
||||
this.langs = new Langs(this);
|
||||
this.books = [];
|
||||
|
||||
// List of page in the book
|
||||
this.pages = {};
|
||||
|
||||
// Deprecation for templates
|
||||
Object.defineProperty(this, 'options', {
|
||||
get: function () {
|
||||
this.log.warn.ln('"options" property is deprecated, use config.get(key) instead');
|
||||
var cfg = this.config.dump();
|
||||
error.deprecateField(cfg, 'book', (this.output? this.output.name : null), '"options.generator" property is deprecated, use "output.name" instead');
|
||||
|
||||
// options.generator
|
||||
cfg.generator = this.output? this.output.name : null;
|
||||
|
||||
// options.output
|
||||
cfg.output = this.output? this.output.root() : null;
|
||||
|
||||
return cfg;
|
||||
}
|
||||
});
|
||||
|
||||
_.bindAll(this);
|
||||
|
||||
// Loop for template filters/blocks
|
||||
error.deprecateField(this, 'book', this, '"book" property is deprecated, use "this" directly instead');
|
||||
}
|
||||
|
||||
// Return templating context for the book
|
||||
Book.prototype.getContext = function() {
|
||||
var variables = this.config.get('variables', {});
|
||||
|
||||
return {
|
||||
book: _.extend({
|
||||
language: this.language
|
||||
}, variables)
|
||||
};
|
||||
};
|
||||
|
||||
// Parse and prepare the configuration, fail if invalid
|
||||
Book.prototype.prepareConfig = function() {
|
||||
var that = this;
|
||||
|
||||
return this.config.load()
|
||||
.then(function() {
|
||||
var rootFolder = that.config.get('root');
|
||||
if (!rootFolder) return;
|
||||
|
||||
that.originalRoot = that.root;
|
||||
that.root = path.resolve(that.root, rootFolder);
|
||||
});
|
||||
};
|
||||
|
||||
// Resolve a path in the book source
|
||||
// Enforce that the output path is in the scope
|
||||
Book.prototype.resolve = function() {
|
||||
var filename = path.resolve.apply(path, [this.root].concat(_.toArray(arguments)));
|
||||
if (!this.isFileInScope(filename)) {
|
||||
throw error.FileOutOfScopeError({
|
||||
filename: filename,
|
||||
root: this.root
|
||||
});
|
||||
}
|
||||
|
||||
return filename;
|
||||
};
|
||||
|
||||
// Return false if a file is outside the book' scope
|
||||
Book.prototype.isFileInScope = function(filename) {
|
||||
filename = path.resolve(this.root, filename);
|
||||
|
||||
// Is the file in the scope of the parent?
|
||||
if (this.parent && this.parent.isFileInScope(filename)) return true;
|
||||
|
||||
// Is file in the root folder?
|
||||
return pathUtil.isInRoot(this.root, filename);
|
||||
};
|
||||
|
||||
// Parse .gitignore, etc to extract rules
|
||||
Book.prototype.parseIgnoreRules = function() {
|
||||
var that = this;
|
||||
|
||||
return Promise.serie([
|
||||
'.ignore',
|
||||
'.gitignore',
|
||||
'.bookignore'
|
||||
], function(filename) {
|
||||
return that.readFile(filename)
|
||||
.then(function(content) {
|
||||
that.ignore.addPattern(content.toString().split(/\r?\n/));
|
||||
}, function() {
|
||||
return Promise();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Parse the whole book
|
||||
Book.prototype.parse = function() {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(this.prepareConfig)
|
||||
.then(this.parseIgnoreRules)
|
||||
|
||||
// Parse languages
|
||||
.then(function() {
|
||||
return that.langs.load();
|
||||
})
|
||||
|
||||
.then(function() {
|
||||
if (that.isMultilingual()) {
|
||||
if (that.isLanguageBook()) {
|
||||
throw error.ParsingError(new Error('A multilingual book as a language book is forbidden'));
|
||||
}
|
||||
|
||||
that.log.info.ln('Parsing multilingual book, with', that.langs.count(), 'languages');
|
||||
|
||||
// Create a new book for each language and parse it
|
||||
return Promise.serie(that.langs.list(), function(lang) {
|
||||
that.log.debug.ln('Preparing book for language', lang.id);
|
||||
var langBook = new Book(_.extend({}, that.opts, {
|
||||
parent: that,
|
||||
config: that.config.dump(),
|
||||
root: that.resolve(lang.id)
|
||||
}));
|
||||
|
||||
that.books.push(langBook);
|
||||
|
||||
return langBook.parse();
|
||||
});
|
||||
}
|
||||
|
||||
return Promise()
|
||||
|
||||
// Parse the readme
|
||||
.then(that.readme.load)
|
||||
.then(function() {
|
||||
if (!that.readme.exists()) {
|
||||
throw new error.FileNotFoundError({ filename: 'README' });
|
||||
}
|
||||
|
||||
// Default configuration to infos extracted from readme
|
||||
if (!that.config.get('title')) that.config.set('title', that.readme.title);
|
||||
if (!that.config.get('description')) that.config.set('description', that.readme.description);
|
||||
})
|
||||
|
||||
// Parse the summary
|
||||
.then(that.summary.load)
|
||||
.then(function() {
|
||||
if (!that.summary.exists()) {
|
||||
that.log.warn.ln('no summary file in this book');
|
||||
}
|
||||
|
||||
// Index summary's articles
|
||||
that.summary.walk(function(article) {
|
||||
if (!article.hasLocation() || article.isExternal()) return;
|
||||
that.addPage(article.path);
|
||||
});
|
||||
})
|
||||
|
||||
// Parse the glossary
|
||||
.then(that.glossary.load)
|
||||
|
||||
// Add the glossary as a page
|
||||
.then(function() {
|
||||
if (!that.glossary.exists()) return;
|
||||
that.addPage(that.glossary.path);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Mark a filename as being parsable
|
||||
Book.prototype.addPage = function(filename) {
|
||||
if (this.hasPage(filename)) return this.getPage(filename);
|
||||
|
||||
filename = pathUtil.normalize(filename);
|
||||
this.pages[filename] = new Page(this, filename);
|
||||
return this.pages[filename];
|
||||
};
|
||||
|
||||
// Return a page by its filename (or undefined)
|
||||
Book.prototype.getPage = function(filename) {
|
||||
filename = pathUtil.normalize(filename);
|
||||
return this.pages[filename];
|
||||
};
|
||||
|
||||
|
||||
// Return true, if has a specific page
|
||||
Book.prototype.hasPage = function(filename) {
|
||||
return Boolean(this.getPage(filename));
|
||||
};
|
||||
|
||||
// Test if a file is ignored, return true if it is
|
||||
Book.prototype.isFileIgnored = function(filename) {
|
||||
return this.ignore.filter([filename]).length == 0;
|
||||
};
|
||||
|
||||
// Read a file in the book, throw error if ignored
|
||||
Book.prototype.readFile = function(filename) {
|
||||
if (this.isFileIgnored(filename)) return Promise.reject(new error.FileNotFoundError({ filename: filename }));
|
||||
return this.fs.readAsString(this.resolve(filename));
|
||||
};
|
||||
|
||||
// Get stat infos about a file
|
||||
Book.prototype.statFile = function(filename) {
|
||||
if (this.isFileIgnored(filename)) return Promise.reject(new error.FileNotFoundError({ filename: filename }));
|
||||
return this.fs.stat(this.resolve(filename));
|
||||
};
|
||||
|
||||
// Find a parsable file using a filename
|
||||
Book.prototype.findParsableFile = function(filename) {
|
||||
var that = this;
|
||||
|
||||
var ext = path.extname(filename);
|
||||
var basename = path.basename(filename, ext);
|
||||
|
||||
// Ordered list of extensions to test
|
||||
var exts = parsers.extensions;
|
||||
if (ext) exts = _.uniq([ext].concat(exts));
|
||||
|
||||
return _.reduce(exts, function(prev, ext) {
|
||||
return prev.then(function(output) {
|
||||
// Stop if already find a parser
|
||||
if (output) return output;
|
||||
|
||||
var filepath = basename+ext;
|
||||
|
||||
return that.fs.findFile(that.root, filepath)
|
||||
.then(function(realFilepath) {
|
||||
if (!realFilepath) return null;
|
||||
|
||||
return {
|
||||
parser: parsers.getByExt(ext),
|
||||
path: realFilepath
|
||||
};
|
||||
});
|
||||
});
|
||||
}, Promise(null));
|
||||
};
|
||||
|
||||
// Return true if book is associated to a language
|
||||
Book.prototype.isLanguageBook = function() {
|
||||
return Boolean(this.parent);
|
||||
};
|
||||
Book.prototype.isSubBook = Book.prototype.isLanguageBook;
|
||||
|
||||
// Return true if the book is main instance of a multilingual book
|
||||
Book.prototype.isMultilingual = function() {
|
||||
return this.langs.count() > 0;
|
||||
};
|
||||
|
||||
// Return true if file is in the scope of this book
|
||||
Book.prototype.isInBook = function(filename) {
|
||||
return pathUtil.isInRoot(
|
||||
this.root,
|
||||
filename
|
||||
);
|
||||
};
|
||||
|
||||
// Return true if file is in the scope of a child book
|
||||
Book.prototype.isInLanguageBook = function(filename) {
|
||||
var that = this;
|
||||
|
||||
return _.some(this.langs.list(), function(lang) {
|
||||
return pathUtil.isInRoot(
|
||||
that.resolve(lang.id),
|
||||
that.resolve(filename)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// ----- Parser Methods
|
||||
|
||||
// Render a markup string in inline mode
|
||||
Book.prototype.renderInline = function(type, src) {
|
||||
var parser = parsers.get(type);
|
||||
return parser.inline(src)
|
||||
.get('content');
|
||||
};
|
||||
|
||||
// Render a markup string in block mode
|
||||
Book.prototype.renderBlock = function(type, src) {
|
||||
var parser = parsers.get(type);
|
||||
return parser.page(src)
|
||||
.get('content');
|
||||
};
|
||||
|
||||
|
||||
// ----- DEPRECATED METHODS
|
||||
|
||||
Book.prototype.contentLink = error.deprecateMethod(function(s) {
|
||||
return this.output.toURL(s);
|
||||
}, '.contentLink() is deprecated, use ".output.toURL()" instead');
|
||||
|
||||
Book.prototype.contentPath = error.deprecateMethod(function(s) {
|
||||
return this.output.toURL(s);
|
||||
}, '.contentPath() is deprecated, use ".output.toURL()" instead');
|
||||
|
||||
Book.prototype.isSubBook = error.deprecateMethod(function() {
|
||||
return this.isLanguageBook();
|
||||
}, '.isSubBook() is deprecated, use ".isLanguageBook()" instead');
|
||||
|
||||
|
||||
// Initialize a book
|
||||
Book.init = function(fs, root, opts) {
|
||||
var book = new Book(_.extend(opts || {}, {
|
||||
root: root,
|
||||
fs: fs
|
||||
}));
|
||||
|
||||
return initBook(book);
|
||||
};
|
||||
|
||||
|
||||
module.exports = Book;
|
||||
@@ -0,0 +1,15 @@
|
||||
var Modifiers = require('./modifiers');
|
||||
|
||||
module.exports = {
|
||||
Parse: require('./parse'),
|
||||
|
||||
// Models
|
||||
Book: require('./models/book'),
|
||||
FS: require('./models/fs'),
|
||||
Summary: require('./models/summary'),
|
||||
Glossary: require('./models/glossary'),
|
||||
|
||||
// Modifiers
|
||||
SummaryModifier: Modifiers.Summary,
|
||||
ConfigModifier: Modifiers.Config
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
var Parse = require('../parse');
|
||||
var Output = require('../output');
|
||||
var timing = require('../utils/timing');
|
||||
|
||||
var options = require('./options');
|
||||
var getBook = require('./getBook');
|
||||
var getOutputFolder = require('./getOutputFolder');
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: 'build [book] [output]',
|
||||
description: 'build a book',
|
||||
options: [
|
||||
options.log,
|
||||
options.format,
|
||||
options.timing
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
var book = getBook(args, kwargs);
|
||||
var outputFolder = getOutputFolder(args);
|
||||
|
||||
var Generator = Output.getGenerator(kwargs.format);
|
||||
|
||||
return Parse.parseBook(book)
|
||||
.then(function(resultBook) {
|
||||
return Output.generate(Generator, resultBook, {
|
||||
root: outputFolder
|
||||
});
|
||||
})
|
||||
.fin(function() {
|
||||
if (kwargs.timing) timing.dump(book.getLogger());
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
var path = require('path');
|
||||
var tmp = require('tmp');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var fs = require('../utils/fs');
|
||||
var Parse = require('../parse');
|
||||
var Output = require('../output');
|
||||
|
||||
var options = require('./options');
|
||||
var getBook = require('./getBook');
|
||||
|
||||
|
||||
module.exports = function(format) {
|
||||
return {
|
||||
name: (format + ' [book] [output]'),
|
||||
description: 'build a book into an ebook file',
|
||||
options: [
|
||||
options.log
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
// Output file will be stored in
|
||||
var outputFile = args[1] || ('book.' + format);
|
||||
|
||||
// Create temporary directory
|
||||
var outputFolder = tmp.dirSync().name;
|
||||
|
||||
var book = getBook(args, kwargs);
|
||||
var logger = book.getLogger();
|
||||
var Generator = Output.getGenerator('ebook');
|
||||
|
||||
return Parse.parseBook(book)
|
||||
.then(function(resultBook) {
|
||||
return Output.generate(Generator, resultBook, {
|
||||
root: outputFolder,
|
||||
format: format
|
||||
});
|
||||
})
|
||||
|
||||
// Extract ebook file
|
||||
.then(function(output) {
|
||||
var book = output.getBook();
|
||||
var languages = book.getLanguages();
|
||||
|
||||
if (book.isMultilingual()) {
|
||||
return Promise.ForEach(languages, function(lang) {
|
||||
var langID = lang.getID();
|
||||
|
||||
var langOutputFile = path.join(
|
||||
path.dirname(outputFile),
|
||||
path.basename(outputFile, format) + '_' + langID + '.' + format
|
||||
);
|
||||
|
||||
return fs.copy(
|
||||
path.resolve(outputFolder, langID, 'index.' + format),
|
||||
langOutputFile
|
||||
);
|
||||
})
|
||||
.thenResolve(languages.getCount());
|
||||
} else {
|
||||
return fs.copy(
|
||||
path.resolve(outputFolder, 'index.' + format),
|
||||
outputFile
|
||||
).thenResolve(1);
|
||||
}
|
||||
})
|
||||
|
||||
// Log end
|
||||
.then(function(count) {
|
||||
logger.info.ok(count + ' file(s) generated');
|
||||
|
||||
logger.debug('cleaning up... ');
|
||||
return logger.debug.promise(fs.rmDir(outputFolder));
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
var path = require('path');
|
||||
var Book = require('../models/book');
|
||||
var createNodeFS = require('../fs/node');
|
||||
|
||||
/**
|
||||
Return a book instance to work on from
|
||||
command line args/kwargs
|
||||
|
||||
@param {Array} args
|
||||
@param {Object} kwargs
|
||||
@return {Book}
|
||||
*/
|
||||
function getBook(args, kwargs) {
|
||||
var input = path.resolve(args[0] || process.cwd());
|
||||
var logLevel = kwargs.log;
|
||||
|
||||
var fs = createNodeFS(input);
|
||||
var book = Book.createForFS(fs);
|
||||
|
||||
return book.setLogLevel(logLevel);
|
||||
}
|
||||
|
||||
module.exports = getBook;
|
||||
@@ -0,0 +1,17 @@
|
||||
var path = require('path');
|
||||
|
||||
/**
|
||||
Return path to output folder
|
||||
|
||||
@param {Array} args
|
||||
@return {String}
|
||||
*/
|
||||
function getOutputFolder(args) {
|
||||
var bookRoot = path.resolve(args[0] || process.cwd());
|
||||
var defaultOutputRoot = path.join(bookRoot, '_book');
|
||||
var outputFolder = args[1]? path.resolve(process.cwd(), args[1]) : defaultOutputRoot;
|
||||
|
||||
return outputFolder;
|
||||
}
|
||||
|
||||
module.exports = getOutputFolder;
|
||||
@@ -1,140 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
|
||||
var Book = require('../book');
|
||||
var NodeFS = require('../fs/node');
|
||||
var Logger = require('../utils/logger');
|
||||
var Promise = require('../utils/promise');
|
||||
var fs = require('../utils/fs');
|
||||
var JSONOutput = require('../output/json');
|
||||
var WebsiteOutput = require('../output/website');
|
||||
var EBookOutput = require('../output/ebook');
|
||||
|
||||
var nodeFS = new NodeFS();
|
||||
|
||||
var LOG_OPTION = {
|
||||
name: 'log',
|
||||
description: 'Minimum log level to display',
|
||||
values: _.chain(Logger.LEVELS)
|
||||
.keys()
|
||||
.map(function(s) {
|
||||
return s.toLowerCase();
|
||||
})
|
||||
.value(),
|
||||
defaults: 'info'
|
||||
};
|
||||
|
||||
var FORMAT_OPTION = {
|
||||
name: 'format',
|
||||
description: 'Format to build to',
|
||||
values: ['website', 'json', 'ebook'],
|
||||
defaults: 'website'
|
||||
};
|
||||
|
||||
var FORMATS = {
|
||||
json: JSONOutput,
|
||||
website: WebsiteOutput,
|
||||
ebook: EBookOutput
|
||||
};
|
||||
|
||||
// Commands which is processing a book
|
||||
// the root of the book is the first argument (or current directory)
|
||||
function bookCmd(fn) {
|
||||
return function(args, kwargs) {
|
||||
var input = path.resolve(args[0] || process.cwd());
|
||||
var book = new Book({
|
||||
fs: nodeFS,
|
||||
root: input,
|
||||
logLevel: kwargs.log
|
||||
});
|
||||
|
||||
return fn(book, args.slice(1), kwargs);
|
||||
};
|
||||
}
|
||||
|
||||
// Commands which is working on a Output instance
|
||||
function outputCmd(fn) {
|
||||
return bookCmd(function(book, args, kwargs) {
|
||||
var Out = FORMATS[kwargs.format];
|
||||
var outputFolder = undefined;
|
||||
|
||||
// Set output folder
|
||||
if (args[0]) {
|
||||
outputFolder = path.resolve(process.cwd(), args[0]);
|
||||
}
|
||||
|
||||
return fn(new Out(book, {
|
||||
root: outputFolder
|
||||
}), args);
|
||||
});
|
||||
}
|
||||
|
||||
// Command to generate an ebook
|
||||
function ebookCmd(format) {
|
||||
return {
|
||||
name: format + ' [book] [output] [file]',
|
||||
description: 'generates ebook '+format,
|
||||
options: [
|
||||
LOG_OPTION
|
||||
],
|
||||
exec: bookCmd(function(book, args, kwargs) {
|
||||
return fs.tmpDir()
|
||||
.then(function(dir) {
|
||||
var ext = '.'+format;
|
||||
var outputFile = path.resolve(process.cwd(), args[0] || ('book' + ext));
|
||||
var output = new EBookOutput(book, {
|
||||
root: dir,
|
||||
format: format
|
||||
});
|
||||
|
||||
return output.book.parse()
|
||||
.then(function() {
|
||||
return output.generate();
|
||||
})
|
||||
|
||||
// Copy the ebook files
|
||||
.then(function() {
|
||||
if (output.book.isMultilingual()) {
|
||||
return Promise.serie(output.book.langs.list(), function(lang) {
|
||||
var _outputFile = path.join(
|
||||
path.dirname(outputFile),
|
||||
path.basename(outputFile, ext) + '_' + lang.id + ext
|
||||
);
|
||||
|
||||
return fs.copy(
|
||||
path.resolve(dir, lang.id, 'index' + ext),
|
||||
_outputFile
|
||||
);
|
||||
})
|
||||
.thenResolve(output.book.langs.count());
|
||||
} else {
|
||||
return fs.copy(
|
||||
path.resolve(dir, 'index' + ext),
|
||||
outputFile
|
||||
).thenResolve(1);
|
||||
}
|
||||
})
|
||||
.then(function(n) {
|
||||
output.book.log.info.ok(n+' file(s) generated');
|
||||
|
||||
output.book.log.info('cleaning up... ');
|
||||
return output.book.log.info.promise(fs.rmDir(dir));
|
||||
});
|
||||
});
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
nodeFS: nodeFS,
|
||||
bookCmd: bookCmd,
|
||||
outputCmd: outputCmd,
|
||||
ebookCmd: ebookCmd,
|
||||
|
||||
options: {
|
||||
log: LOG_OPTION,
|
||||
format: FORMAT_OPTION
|
||||
},
|
||||
|
||||
FORMATS: FORMATS
|
||||
};
|
||||
+11
-198
@@ -1,199 +1,12 @@
|
||||
/* eslint-disable no-console */
|
||||
var buildEbook = require('./buildEbook');
|
||||
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
var tinylr = require('tiny-lr');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var PluginsManager = require('../plugins');
|
||||
var Book = require('../book');
|
||||
|
||||
var helper = require('./helper');
|
||||
var Server = require('./server');
|
||||
var watch = require('./watch');
|
||||
|
||||
module.exports = {
|
||||
commands: [
|
||||
{
|
||||
name: 'init [book]',
|
||||
description: 'setup and create files for chapters',
|
||||
options: [
|
||||
helper.options.log
|
||||
],
|
||||
exec: function(args) {
|
||||
var input = path.resolve(args[0] || process.cwd());
|
||||
return Book.init(helper.nodeFS, input);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'parse [book]',
|
||||
description: 'parse and returns debug information for a book',
|
||||
options: [
|
||||
helper.options.log
|
||||
],
|
||||
exec: helper.bookCmd(function(book) {
|
||||
return book.parse()
|
||||
.then(function() {
|
||||
book.log.info.ln('Book located in:', book.root);
|
||||
book.log.info.ln('');
|
||||
|
||||
if (book.config.exists()) book.log.info.ln('Configuration:', book.config.path);
|
||||
|
||||
if (book.isMultilingual()) {
|
||||
book.log.info.ln('Multilingual book detected:', book.langs.path);
|
||||
} else {
|
||||
book.log.info.ln('Readme:', book.readme.path);
|
||||
book.log.info.ln('Summary:', book.summary.path);
|
||||
if (book.glossary.exists()) book.log.info.ln('Glossary:', book.glossary.path);
|
||||
|
||||
book.log.info.ln('Pages:');
|
||||
_.each(book.pages, function(page) {
|
||||
book.log.info.ln('\t-', page.path);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
{
|
||||
name: 'install [book]',
|
||||
description: 'install all plugins dependencies',
|
||||
options: [
|
||||
helper.options.log
|
||||
],
|
||||
exec: helper.bookCmd(function(book, args) {
|
||||
var plugins = new PluginsManager(book);
|
||||
|
||||
return book.config.load()
|
||||
.then(function() {
|
||||
return plugins.install();
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
{
|
||||
name: 'build [book] [output]',
|
||||
description: 'build a book',
|
||||
options: [
|
||||
helper.options.log,
|
||||
helper.options.format
|
||||
],
|
||||
exec: helper.outputCmd(function(output, args, kwargs) {
|
||||
return output.book.parse()
|
||||
.then(function() {
|
||||
return output.generate();
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
helper.ebookCmd('pdf'),
|
||||
helper.ebookCmd('epub'),
|
||||
helper.ebookCmd('mobi'),
|
||||
|
||||
{
|
||||
name: 'serve [book]',
|
||||
description: 'Build then serve a book from a directory',
|
||||
options: [
|
||||
{
|
||||
name: 'port',
|
||||
description: 'Port for server to listen on',
|
||||
defaults: 4000
|
||||
},
|
||||
{
|
||||
name: 'lrport',
|
||||
description: 'Port for livereload server to listen on',
|
||||
defaults: 35729
|
||||
},
|
||||
{
|
||||
name: 'watch',
|
||||
description: 'Enable/disable file watcher',
|
||||
defaults: true
|
||||
},
|
||||
helper.options.format,
|
||||
helper.options.log
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
var input = path.resolve(args[0] || process.cwd());
|
||||
var server = new Server();
|
||||
|
||||
// Init livereload server
|
||||
var lrServer = tinylr({});
|
||||
var port = kwargs.port;
|
||||
var lrPath;
|
||||
|
||||
var generate = function() {
|
||||
|
||||
// Stop server if running
|
||||
if (server.isRunning()) console.log('Stopping server');
|
||||
return server.stop()
|
||||
|
||||
// Generate the book
|
||||
.then(function() {
|
||||
var book = new Book({
|
||||
fs: helper.nodeFS,
|
||||
root: input,
|
||||
logLevel: kwargs.log
|
||||
});
|
||||
|
||||
return book.parse()
|
||||
.then(function() {
|
||||
// Add livereload plugin
|
||||
book.config.set('plugins',
|
||||
book.config.get('plugins')
|
||||
.concat([
|
||||
{ name: 'livereload' }
|
||||
])
|
||||
);
|
||||
|
||||
var Out = helper.FORMATS[kwargs.format];
|
||||
var output = new Out(book);
|
||||
|
||||
return output.generate()
|
||||
.thenResolve(output);
|
||||
});
|
||||
})
|
||||
|
||||
// Start server and watch changes
|
||||
.then(function(output) {
|
||||
console.log();
|
||||
console.log('Starting server ...');
|
||||
return server.start(output.root(), port)
|
||||
.then(function() {
|
||||
console.log('Serving book on http://localhost:'+port);
|
||||
|
||||
if (lrPath) {
|
||||
// trigger livereload
|
||||
lrServer.changed({
|
||||
body: {
|
||||
files: [lrPath]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!kwargs.watch) return;
|
||||
|
||||
return watch(output.book.root)
|
||||
.then(function(filepath) {
|
||||
// set livereload path
|
||||
lrPath = filepath;
|
||||
console.log('Restart after change in file', filepath);
|
||||
console.log('');
|
||||
return generate();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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('');
|
||||
return generate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
};
|
||||
module.exports = [
|
||||
require('./build'),
|
||||
require('./serve'),
|
||||
require('./install'),
|
||||
require('./parse'),
|
||||
require('./init'),
|
||||
buildEbook('pdf'),
|
||||
buildEbook('epub'),
|
||||
buildEbook('mobi')
|
||||
];
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
var path = require('path');
|
||||
|
||||
var options = require('./options');
|
||||
var initBook = require('../init');
|
||||
|
||||
module.exports = {
|
||||
name: 'init [book]',
|
||||
description: 'setup and create files for chapters',
|
||||
options: [
|
||||
options.log
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
var bookRoot = path.resolve(process.cwd(), args[0] || './');
|
||||
|
||||
return initBook(bookRoot);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
var options = require('./options');
|
||||
var getBook = require('./getBook');
|
||||
|
||||
var Parse = require('../parse');
|
||||
var Plugins = require('../plugins');
|
||||
|
||||
module.exports = {
|
||||
name: 'install [book]',
|
||||
description: 'install all plugins dependencies',
|
||||
options: [
|
||||
options.log
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
var book = getBook(args, kwargs);
|
||||
|
||||
return Parse.parseConfig(book)
|
||||
.then(function(resultBook) {
|
||||
return Plugins.installPlugins(resultBook);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
var Logger = require('../utils/logger');
|
||||
|
||||
var logOptions = {
|
||||
name: 'log',
|
||||
description: 'Minimum log level to display',
|
||||
values: Logger.LEVELS
|
||||
.keySeq()
|
||||
.map(function(s) {
|
||||
return s.toLowerCase();
|
||||
}).toJS(),
|
||||
defaults: 'info'
|
||||
};
|
||||
|
||||
var formatOption = {
|
||||
name: 'format',
|
||||
description: 'Format to build to',
|
||||
values: ['website', 'json', 'ebook'],
|
||||
defaults: 'website'
|
||||
};
|
||||
|
||||
var timingOption = {
|
||||
name: 'timing',
|
||||
description: 'Print timing debug information',
|
||||
defaults: false
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
log: logOptions,
|
||||
format: formatOption,
|
||||
timing: timingOption
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
var options = require('./options');
|
||||
var getBook = require('./getBook');
|
||||
|
||||
var Parse = require('../parse');
|
||||
|
||||
function printBook(book) {
|
||||
var logger = book.getLogger();
|
||||
|
||||
var config = book.getConfig();
|
||||
var configFile = config.getFile();
|
||||
|
||||
var summary = book.getSummary();
|
||||
var summaryFile = summary.getFile();
|
||||
|
||||
var readme = book.getReadme();
|
||||
var readmeFile = readme.getFile();
|
||||
|
||||
var glossary = book.getGlossary();
|
||||
var glossaryFile = glossary.getFile();
|
||||
|
||||
if (configFile.exists()) {
|
||||
logger.info.ln('Configuration file is', configFile.getPath());
|
||||
}
|
||||
|
||||
if (readmeFile.exists()) {
|
||||
logger.info.ln('Introduction file is', readmeFile.getPath());
|
||||
}
|
||||
|
||||
if (glossaryFile.exists()) {
|
||||
logger.info.ln('Glossary file is', glossaryFile.getPath());
|
||||
}
|
||||
|
||||
if (summaryFile.exists()) {
|
||||
logger.info.ln('Table of Contents file is', summaryFile.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
function printMultingualBook(book) {
|
||||
var logger = book.getLogger();
|
||||
var languages = book.getLanguages();
|
||||
var books = book.getBooks();
|
||||
|
||||
logger.info.ln(languages.size + ' languages');
|
||||
|
||||
languages.forEach(function(lang) {
|
||||
logger.info.ln('Language:', lang.getTitle());
|
||||
printBook(books.get(lang.getID()));
|
||||
logger.info.ln('');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'parse [book]',
|
||||
description: 'parse and print debug information about a book',
|
||||
options: [
|
||||
options.log
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
var book = getBook(args, kwargs);
|
||||
var logger = book.getLogger();
|
||||
|
||||
return Parse.parseBook(book)
|
||||
.then(function(resultBook) {
|
||||
var rootFolder = book.getRoot();
|
||||
var contentFolder = book.getContentRoot();
|
||||
|
||||
logger.info.ln('Book located in:', rootFolder);
|
||||
if (contentFolder != rootFolder) {
|
||||
logger.info.ln('Content located in:', contentFolder);
|
||||
}
|
||||
|
||||
if (resultBook.isMultilingual()) {
|
||||
printMultingualBook(resultBook);
|
||||
} else {
|
||||
printBook(resultBook);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
var tinylr = require('tiny-lr');
|
||||
|
||||
var Parse = require('../parse');
|
||||
var Output = require('../output');
|
||||
var ConfigModifier = require('../modifiers').Config;
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
var options = require('./options');
|
||||
var getBook = require('./getBook');
|
||||
var getOutputFolder = require('./getOutputFolder');
|
||||
var Server = require('./server');
|
||||
var watch = require('./watch');
|
||||
|
||||
var server, lrServer, lrPath;
|
||||
|
||||
function waitForCtrlC() {
|
||||
var d = Promise.defer();
|
||||
|
||||
process.on('SIGINT', function() {
|
||||
d.resolve();
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
|
||||
function generateBook(args, kwargs) {
|
||||
var port = kwargs.port;
|
||||
var outputFolder = getOutputFolder(args);
|
||||
var book = getBook(args, kwargs);
|
||||
var Generator = Output.getGenerator(kwargs.format);
|
||||
|
||||
var hasWatch = kwargs['watch'];
|
||||
var hasLiveReloading = kwargs['live'];
|
||||
|
||||
// Stop server if running
|
||||
if (server.isRunning()) console.log('Stopping server');
|
||||
|
||||
return server.stop()
|
||||
.then(function() {
|
||||
return Parse.parseBook(book)
|
||||
.then(function(resultBook) {
|
||||
if (hasLiveReloading) {
|
||||
// Enable livereload plugin
|
||||
resultBook = ConfigModifier.addPlugin(resultBook, 'livereload');
|
||||
}
|
||||
|
||||
return Output.generate(Generator, resultBook, {
|
||||
root: outputFolder
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
console.log();
|
||||
console.log('Starting server ...');
|
||||
return server.start(outputFolder, port);
|
||||
})
|
||||
.then(function() {
|
||||
console.log('Serving book on http://localhost:'+port);
|
||||
|
||||
if (lrPath && hasLiveReloading) {
|
||||
// trigger livereload
|
||||
lrServer.changed({
|
||||
body: {
|
||||
files: [lrPath]
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
if (!hasWatch) {
|
||||
return waitForCtrlC();
|
||||
}
|
||||
|
||||
return watch(book.getRoot())
|
||||
.then(function(filepath) {
|
||||
// set livereload path
|
||||
lrPath = filepath;
|
||||
console.log('Restart after change in file', filepath);
|
||||
console.log('');
|
||||
return generateBook(args, kwargs);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'serve [book] [output]',
|
||||
description: 'serve the book as a website for testing',
|
||||
options: [
|
||||
{
|
||||
name: 'port',
|
||||
description: 'Port for server to listen on',
|
||||
defaults: 4000
|
||||
},
|
||||
{
|
||||
name: 'lrport',
|
||||
description: 'Port for livereload server to listen on',
|
||||
defaults: 35729
|
||||
},
|
||||
{
|
||||
name: 'watch',
|
||||
description: 'Enable file watcher and live reloading',
|
||||
defaults: true
|
||||
},
|
||||
{
|
||||
name: 'live',
|
||||
description: 'Enable live reloading',
|
||||
defaults: true
|
||||
},
|
||||
options.log,
|
||||
options.format
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
server = new Server();
|
||||
var hasWatch = kwargs['watch'];
|
||||
var hasLiveReloading = kwargs['live'];
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
if (!hasWatch || !hasLiveReloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
lrServer = tinylr({});
|
||||
return Promise.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport)
|
||||
.then(function() {
|
||||
console.log('Live reload server started on port:', kwargs.lrport);
|
||||
console.log('Press CTRL+C to quit ...');
|
||||
console.log('');
|
||||
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
return generateBook(args, kwargs);
|
||||
});
|
||||
}
|
||||
};
|
||||
+17
-4
@@ -6,20 +6,28 @@ var url = require('url');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
var Server = function() {
|
||||
function Server() {
|
||||
this.running = null;
|
||||
this.dir = null;
|
||||
this.port = 0;
|
||||
this.sockets = [];
|
||||
};
|
||||
}
|
||||
util.inherits(Server, events.EventEmitter);
|
||||
|
||||
// Return true if the server is running
|
||||
/**
|
||||
Return true if the server is running
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Server.prototype.isRunning = function() {
|
||||
return !!this.running;
|
||||
};
|
||||
|
||||
// Stop the server
|
||||
/**
|
||||
Stop the server
|
||||
|
||||
@return {Promise}
|
||||
*/
|
||||
Server.prototype.stop = function() {
|
||||
var that = this;
|
||||
if (!this.isRunning()) return Promise();
|
||||
@@ -40,6 +48,11 @@ Server.prototype.stop = function() {
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
Start the server
|
||||
|
||||
@return {Promise}
|
||||
*/
|
||||
Server.prototype.start = function(dir, port) {
|
||||
var that = this, pre = Promise();
|
||||
port = port || 8004;
|
||||
|
||||
+8
-4
@@ -1,21 +1,25 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
var chokidar = require('chokidar');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var parsers = require('../parsers');
|
||||
|
||||
// Watch a folder and resolve promise once a file is modified
|
||||
/**
|
||||
Watch a folder and resolve promise once a file is modified
|
||||
|
||||
@param {String} dir
|
||||
@return {Promise}
|
||||
*/
|
||||
function watch(dir) {
|
||||
var d = Promise.defer();
|
||||
dir = path.resolve(dir);
|
||||
|
||||
var toWatch = [
|
||||
'book.json', 'book.js'
|
||||
'book.json', 'book.js', '_layouts/**'
|
||||
];
|
||||
|
||||
// Watch all parsable files
|
||||
_.each(parsers.extensions, function(ext) {
|
||||
parsers.extensions.forEach(function(ext) {
|
||||
toWatch.push('**/*'+ext);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var semver = require('semver');
|
||||
|
||||
var gitbook = require('../gitbook');
|
||||
var Promise = require('../utils/promise');
|
||||
var validator = require('./validator');
|
||||
var plugins = require('./plugins');
|
||||
|
||||
// Config files to tested (sorted)
|
||||
var CONFIG_FILES = [
|
||||
'book.js',
|
||||
'book.json'
|
||||
];
|
||||
|
||||
/*
|
||||
Config is an interface for the book's configuration stored in "book.json" (or "book.js")
|
||||
*/
|
||||
|
||||
function Config(book, baseConfig) {
|
||||
this.book = book;
|
||||
this.fs = book.fs;
|
||||
this.log = book.log;
|
||||
this.path = '';
|
||||
|
||||
this.baseConfig = baseConfig || {};
|
||||
this.replace({});
|
||||
}
|
||||
|
||||
// Load configuration of the book
|
||||
// and verify that the configuration is satisfying
|
||||
Config.prototype.load = function() {
|
||||
var that = this;
|
||||
var isLanguageBook = this.book.isLanguageBook();
|
||||
|
||||
// Try all potential configuration file
|
||||
return Promise.some(CONFIG_FILES, function(filename) {
|
||||
that.log.debug.ln('try loading configuration from', filename);
|
||||
|
||||
return that.fs.loadAsObject(that.book.resolve(filename))
|
||||
.then(function(_config) {
|
||||
that.log.debug.ln('configuration loaded from', filename);
|
||||
|
||||
that.path = filename;
|
||||
return that.replace(_config);
|
||||
})
|
||||
.fail(function(err) {
|
||||
if (err.code != 'MODULE_NOT_FOUND') throw(err);
|
||||
else return Promise(false);
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
if (!isLanguageBook) {
|
||||
if (!gitbook.satisfies(that.options.gitbook)) {
|
||||
throw new Error('GitBook version doesn\'t satisfy version required by the book: '+that.options.gitbook);
|
||||
}
|
||||
if (that.options.gitbook != '*' && !semver.satisfies(semver.inc(gitbook.version, 'patch'), that.options.gitbook)) {
|
||||
that.log.warn.ln('gitbook version specified in your book.json might be too strict for future patches, \'>='+(_.first(gitbook.version.split('.'))+'.x.x')+'\' is more adequate');
|
||||
}
|
||||
|
||||
that.options.plugins = plugins.toList(that.options.plugins);
|
||||
} else {
|
||||
// Multilingual book should inherits the plugins list from parent
|
||||
that.options.plugins = that.book.parent.config.get('plugins');
|
||||
}
|
||||
|
||||
that.options.gitbook = gitbook.version;
|
||||
});
|
||||
};
|
||||
|
||||
// Replace the whole configuration
|
||||
Config.prototype.replace = function(options) {
|
||||
var that = this;
|
||||
|
||||
// Extend base config
|
||||
options = _.defaults(_.cloneDeep(options), this.baseConfig);
|
||||
|
||||
// Validate the config
|
||||
this.options = validator.validate(options);
|
||||
|
||||
// options.input == book.root
|
||||
Object.defineProperty(this.options, 'input', {
|
||||
get: function () {
|
||||
return that.book.root;
|
||||
}
|
||||
});
|
||||
|
||||
// options.originalInput == book.parent.root
|
||||
Object.defineProperty(this.options, 'originalInput', {
|
||||
get: function () {
|
||||
return that.book.parent? that.book.parent.root : undefined;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Return true if book has a configuration file
|
||||
Config.prototype.exists = function() {
|
||||
return Boolean(this.path);
|
||||
};
|
||||
|
||||
// Return path to a structure file
|
||||
// Strip the extension by default
|
||||
Config.prototype.getStructure = function(name, dontStripExt) {
|
||||
var filename = this.options.structure[name];
|
||||
if (dontStripExt) return filename;
|
||||
|
||||
filename = filename.split('.').slice(0, -1).join('.');
|
||||
return filename;
|
||||
};
|
||||
|
||||
// Return a configuration using a key and a default value
|
||||
Config.prototype.get = function(key, def) {
|
||||
return _.get(this.options, key, def);
|
||||
};
|
||||
|
||||
// Update a configuration
|
||||
Config.prototype.set = function(key, value) {
|
||||
return _.set(this.options, key, value);
|
||||
};
|
||||
|
||||
// Return a dump of the configuration
|
||||
Config.prototype.dump = function() {
|
||||
return _.cloneDeep(this.options);
|
||||
};
|
||||
|
||||
// Return templating context
|
||||
Config.prototype.getContext = function() {
|
||||
return {
|
||||
config: this.book.config.dump()
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Config;
|
||||
@@ -1,67 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
// Default plugins added to each books
|
||||
var DEFAULT_PLUGINS = ['highlight', 'search', 'sharing', 'fontsettings', 'theme-default'];
|
||||
|
||||
// Return true if a plugin is a default plugin
|
||||
function isDefaultPlugin(name, version) {
|
||||
return _.contains(DEFAULT_PLUGINS, name);
|
||||
}
|
||||
|
||||
// Normalize a list of plugins to use
|
||||
function normalizePluginsList(plugins) {
|
||||
// Normalize list to an array
|
||||
plugins = _.isString(plugins) ? plugins.split(',') : (plugins || []);
|
||||
|
||||
// Remove empty parts
|
||||
plugins = _.compact(plugins);
|
||||
|
||||
// Divide as {name, version} to handle format like 'myplugin@1.0.0'
|
||||
plugins = _.map(plugins, function(plugin) {
|
||||
if (plugin.name) return plugin;
|
||||
|
||||
var parts = plugin.split('@');
|
||||
var name = parts[0];
|
||||
var version = parts.slice(1).join('@');
|
||||
return {
|
||||
'name': name,
|
||||
'version': version // optional
|
||||
};
|
||||
});
|
||||
|
||||
// List plugins to remove
|
||||
var toremove = _.chain(plugins)
|
||||
.filter(function(plugin) {
|
||||
return plugin.name.length > 0 && plugin.name[0] == '-';
|
||||
})
|
||||
.map(function(plugin) {
|
||||
return plugin.name.slice(1);
|
||||
})
|
||||
.value();
|
||||
|
||||
// Merge with defaults
|
||||
_.each(DEFAULT_PLUGINS, function(plugin) {
|
||||
if (_.find(plugins, { name: plugin })) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugins.push({
|
||||
'name': plugin
|
||||
});
|
||||
});
|
||||
// Remove plugin that start with '-'
|
||||
plugins = _.filter(plugins, function(plugin) {
|
||||
return !_.contains(toremove, plugin.name) && !(plugin.name.length > 0 && plugin.name[0] == '-');
|
||||
});
|
||||
|
||||
// Remove duplicates
|
||||
plugins = _.uniq(plugins, 'name');
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isDefaultPlugin: isDefaultPlugin,
|
||||
toList: normalizePluginsList
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
var Immutable = require('immutable');
|
||||
var jsonSchemaDefaults = require('json-schema-defaults');
|
||||
|
||||
var schema = require('./configSchema');
|
||||
|
||||
module.exports = Immutable.fromJS(jsonSchemaDefaults(schema));
|
||||
@@ -0,0 +1,5 @@
|
||||
// Configuration files to test (sorted)
|
||||
module.exports = [
|
||||
'book.js',
|
||||
'book.json'
|
||||
];
|
||||
@@ -0,0 +1,51 @@
|
||||
var Immutable = require('immutable');
|
||||
var TemplateBlock = require('../models/templateBlock');
|
||||
|
||||
module.exports = Immutable.Map({
|
||||
html: TemplateBlock({
|
||||
name: 'html',
|
||||
process: function(blk) {
|
||||
return blk;
|
||||
}
|
||||
}),
|
||||
|
||||
code: TemplateBlock({
|
||||
name: 'code',
|
||||
process: function(blk) {
|
||||
return {
|
||||
html: false,
|
||||
body: blk.body
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
markdown: TemplateBlock({
|
||||
name: 'markdown',
|
||||
process: function(blk) {
|
||||
return this.book.renderInline('markdown', blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
asciidoc: TemplateBlock({
|
||||
name: 'asciidoc',
|
||||
process: function(blk) {
|
||||
return this.book.renderInline('asciidoc', blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
markup: TemplateBlock({
|
||||
name: 'markup',
|
||||
process: function(blk) {
|
||||
return this.book.renderInline(this.ctx.file.type, blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
var Immutable = require('immutable');
|
||||
var moment = require('moment');
|
||||
|
||||
|
||||
module.exports = {
|
||||
module.exports = Immutable.Map({
|
||||
// Format a date
|
||||
// ex: 'MMMM Do YYYY, h:mm:ss a
|
||||
date: function(time, format) {
|
||||
@@ -12,4 +12,4 @@ module.exports = {
|
||||
dateFromNow: function(time) {
|
||||
return moment(time).fromNow();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
/*
|
||||
List of default plugins for all books,
|
||||
default plugins should be installed in node dependencies of GitBook
|
||||
*/
|
||||
module.exports = Immutable.List([
|
||||
'highlight',
|
||||
'search',
|
||||
'lunr',
|
||||
'sharing',
|
||||
'fontsettings',
|
||||
'theme-default'
|
||||
]);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Files containing ignore pattner (sorted by priority)
|
||||
module.exports = [
|
||||
'.ignore',
|
||||
'.gitignore',
|
||||
'.bookignore'
|
||||
];
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
module.exports = '_assets';
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = [
|
||||
'init',
|
||||
'finish',
|
||||
'finish:before',
|
||||
'config',
|
||||
'page',
|
||||
'page:before'
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
/*
|
||||
All GitBook plugins are NPM packages starting with this prefix.
|
||||
*/
|
||||
module.exports = 'gitbook-plugin-';
|
||||
@@ -0,0 +1,6 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
module.exports = Immutable.List([
|
||||
'js',
|
||||
'css'
|
||||
]);
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
module.exports = '_layouts';
|
||||
@@ -0,0 +1,83 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('MockFS', function() {
|
||||
var createMockFS = require('../mock');
|
||||
var fs = createMockFS({
|
||||
'README.md': 'Hello World',
|
||||
'SUMMARY.md': '# Summary',
|
||||
'folder': {
|
||||
'test.md': 'Cool',
|
||||
'folder2': {
|
||||
'hello.md': 'Hello',
|
||||
'world.md': 'World'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('exists', function() {
|
||||
pit('must return true for a file', function() {
|
||||
return fs.exists('README.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return false for a non existing file', function() {
|
||||
return fs.exists('README_NOTEXISTS.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return true for a directory', function() {
|
||||
return fs.exists('folder')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return true for a deep file', function() {
|
||||
return fs.exists('folder/test.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return true for a deep file (2)', function() {
|
||||
return fs.exists('folder/folder2/hello.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readAsString', function() {
|
||||
pit('must return content for a file', function() {
|
||||
return fs.readAsString('README.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBe('Hello World');
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return content for a deep file', function() {
|
||||
return fs.readAsString('folder/test.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBe('Cool');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readDir', function() {
|
||||
pit('must return content for a directory', function() {
|
||||
return fs.readDir('./')
|
||||
.then(function(files) {
|
||||
expect(files.size).toBe(3);
|
||||
expect(files.includes('README.md')).toBeTruthy();
|
||||
expect(files.includes('SUMMARY.md')).toBeTruthy();
|
||||
expect(files.includes('folder/')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
/*
|
||||
A filesystem is an interface to read files
|
||||
GitBook can works with a virtual filesystem, for example in the browser.
|
||||
*/
|
||||
|
||||
// .readdir return files/folder as a list of string, folder ending with '/'
|
||||
function pathIsFolder(filename) {
|
||||
return _.last(filename) == '/' || _.last(filename) == '\\';
|
||||
}
|
||||
|
||||
|
||||
function FS() {
|
||||
|
||||
}
|
||||
|
||||
// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
|
||||
FS.prototype.exists = function(filename) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// Read a file and returns a promise with the content as a buffer
|
||||
FS.prototype.read = function(filename) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// Read stat infos about a file
|
||||
FS.prototype.stat = function(filename) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// List files/directories in a directory
|
||||
FS.prototype.readdir = function(folder) {
|
||||
// To implement for each fs
|
||||
};
|
||||
|
||||
// These methods don't require to be redefined, by default it uses .exists, .read, .write, .list
|
||||
// For optmization, it can be redefined:
|
||||
|
||||
// List files in a directory
|
||||
FS.prototype.listFiles = function(folder) {
|
||||
return this.readdir(folder)
|
||||
.then(function(files) {
|
||||
return _.reject(files, pathIsFolder);
|
||||
});
|
||||
};
|
||||
|
||||
// List all files in the fs
|
||||
FS.prototype.listAllFiles = function(folder) {
|
||||
var that = this;
|
||||
|
||||
return this.readdir(folder)
|
||||
.then(function(files) {
|
||||
return _.reduce(files, function(prev, file) {
|
||||
return prev.then(function(output) {
|
||||
var isDirectory = pathIsFolder(file);
|
||||
|
||||
if (!isDirectory) {
|
||||
output.push(file);
|
||||
return output;
|
||||
} else {
|
||||
return that.listAllFiles(path.join(folder, file))
|
||||
.then(function(files) {
|
||||
return output.concat(_.map(files, function(_file) {
|
||||
return path.join(file, _file);
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}, Promise([]));
|
||||
});
|
||||
};
|
||||
|
||||
// Read a file as a string (utf-8)
|
||||
FS.prototype.readAsString = function(filename) {
|
||||
return this.read(filename)
|
||||
.then(function(buf) {
|
||||
return buf.toString('utf-8');
|
||||
});
|
||||
};
|
||||
|
||||
// Find a file in a folder (case incensitive)
|
||||
// Return the real filename
|
||||
FS.prototype.findFile = function findFile(root, filename) {
|
||||
return this.listFiles(root)
|
||||
.then(function(files) {
|
||||
return _.find(files, function(file) {
|
||||
return (file.toLowerCase() == filename.toLowerCase());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Load a JSON file
|
||||
// By default, fs only supports JSON
|
||||
FS.prototype.loadAsObject = function(filename) {
|
||||
return this.readAsString(filename)
|
||||
.then(function(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = FS;
|
||||
@@ -0,0 +1,95 @@
|
||||
var path = require('path');
|
||||
var is = require('is');
|
||||
var Buffer = require('buffer').Buffer;
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var FS = require('../models/fs');
|
||||
var error = require('../utils/error');
|
||||
|
||||
/**
|
||||
Create a fake filesystem for unit testing GitBook.
|
||||
|
||||
@param {Map<String:String|Map>}
|
||||
*/
|
||||
function createMockFS(files) {
|
||||
files = Immutable.fromJS(files);
|
||||
var mtime = new Date();
|
||||
|
||||
function getFile(filePath) {
|
||||
var parts = path.normalize(filePath).split(path.sep);
|
||||
return parts.reduce(function(list, part, i) {
|
||||
if (!list) return null;
|
||||
|
||||
var file;
|
||||
|
||||
if (!part || part === '.') file = list;
|
||||
else file = list.get(part);
|
||||
|
||||
if (!file) return null;
|
||||
|
||||
if (is.string(file)) {
|
||||
if (i === (parts.length - 1)) return file;
|
||||
else return null;
|
||||
}
|
||||
|
||||
return file;
|
||||
}, files);
|
||||
}
|
||||
|
||||
function fsExists(filePath) {
|
||||
return Boolean(getFile(filePath) !== null);
|
||||
}
|
||||
|
||||
function fsReadFile(filePath) {
|
||||
var file = getFile(filePath);
|
||||
if (!is.string(file)) {
|
||||
throw error.FileNotFoundError({
|
||||
filename: filePath
|
||||
});
|
||||
}
|
||||
|
||||
return new Buffer(file, 'utf8');
|
||||
}
|
||||
|
||||
function fsStatFile(filePath) {
|
||||
var file = getFile(filePath);
|
||||
if (!file) {
|
||||
throw error.FileNotFoundError({
|
||||
filename: filePath
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
mtime: mtime
|
||||
};
|
||||
}
|
||||
|
||||
function fsReadDir(filePath) {
|
||||
var dir = getFile(filePath);
|
||||
if (!dir || is.string(dir)) {
|
||||
throw error.FileNotFoundError({
|
||||
filename: filePath
|
||||
});
|
||||
}
|
||||
|
||||
return dir
|
||||
.map(function(content, name) {
|
||||
if (!is.string(content)) {
|
||||
name = name + '/';
|
||||
}
|
||||
|
||||
return name;
|
||||
})
|
||||
.valueSeq();
|
||||
}
|
||||
|
||||
return FS.create({
|
||||
root: '',
|
||||
fsExists: fsExists,
|
||||
fsReadFile: fsReadFile,
|
||||
fsStatFile: fsStatFile,
|
||||
fsReadDir: fsReadDir
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createMockFS;
|
||||
+27
-51
@@ -1,36 +1,16 @@
|
||||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
var fresh = require('fresh-require');
|
||||
|
||||
var fs = require('../utils/fs');
|
||||
var Promise = require('../utils/promise');
|
||||
var BaseFS = require('./');
|
||||
var FS = require('../models/fs');
|
||||
|
||||
function NodeFS() {
|
||||
BaseFS.call(this);
|
||||
}
|
||||
util.inherits(NodeFS, BaseFS);
|
||||
|
||||
// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
|
||||
NodeFS.prototype.exists = function(filename) {
|
||||
return fs.exists(filename);
|
||||
};
|
||||
|
||||
// Read a file and returns a promise with the content as a buffer
|
||||
NodeFS.prototype.read = function(filename) {
|
||||
return fs.readFile(filename);
|
||||
};
|
||||
|
||||
// Read stat infos about a file
|
||||
NodeFS.prototype.stat = function(filename) {
|
||||
return fs.stat(filename);
|
||||
};
|
||||
|
||||
// List files in a directory
|
||||
NodeFS.prototype.readdir = function(folder) {
|
||||
function fsReadDir(folder) {
|
||||
return fs.readdir(folder)
|
||||
.then(function(files) {
|
||||
return _.chain(files)
|
||||
files = Immutable.List(files);
|
||||
|
||||
return files
|
||||
.map(function(file) {
|
||||
if (file == '.' || file == '..') return;
|
||||
|
||||
@@ -38,29 +18,25 @@ NodeFS.prototype.readdir = function(folder) {
|
||||
if (stat.isDirectory()) file = file + path.sep;
|
||||
return file;
|
||||
})
|
||||
.compact()
|
||||
.value();
|
||||
.filter(function(file) {
|
||||
return Boolean(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function fsLoadObject(filename) {
|
||||
return fresh(filename, require);
|
||||
}
|
||||
|
||||
module.exports = function createNodeFS(root) {
|
||||
return FS.create({
|
||||
root: root,
|
||||
|
||||
fsExists: fs.exists,
|
||||
fsReadFile: fs.readFile,
|
||||
fsStatFile: fs.stat,
|
||||
fsReadDir: fsReadDir,
|
||||
fsLoadObject: fsLoadObject,
|
||||
fsReadAsStream: fs.readStream
|
||||
});
|
||||
};
|
||||
|
||||
// Load a JSON/JS file
|
||||
NodeFS.prototype.loadAsObject = function(filename) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var jsFile;
|
||||
|
||||
try {
|
||||
jsFile = require.resolve(filename);
|
||||
|
||||
// Invalidate node.js cache for livreloading
|
||||
delete require.cache[jsFile];
|
||||
|
||||
return require(jsFile);
|
||||
}
|
||||
catch(err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = NodeFS;
|
||||
|
||||
+8
-13
@@ -6,8 +6,13 @@ var VERSION_STABLE = VERSION.replace(/\-(\S+)/g, '');
|
||||
|
||||
var START_TIME = new Date();
|
||||
|
||||
// Verify that this gitbook version satisfies a requirement
|
||||
// We can't directly use samver.satisfies since it will break all plugins when gitbook version is a prerelease (beta, alpha)
|
||||
/**
|
||||
Verify that this gitbook version satisfies a requirement
|
||||
We can't directly use samver.satisfies since it will break all plugins when gitbook version is a prerelease (beta, alpha)
|
||||
|
||||
@param {String} condition
|
||||
@return {Boolean}
|
||||
*/
|
||||
function satisfies(condition) {
|
||||
// Test with real version
|
||||
if (semver.satisfies(VERSION, condition)) return true;
|
||||
@@ -16,18 +21,8 @@ function satisfies(condition) {
|
||||
return semver.satisfies(VERSION_STABLE, condition);
|
||||
}
|
||||
|
||||
// Return templating/json context for gitbook itself
|
||||
function getContext() {
|
||||
return {
|
||||
gitbook: {
|
||||
version: pkg.version,
|
||||
time: START_TIME
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
version: pkg.version,
|
||||
satisfies: satisfies,
|
||||
getContext: getContext
|
||||
START_TIME: START_TIME
|
||||
};
|
||||
|
||||
+9
-6
@@ -1,7 +1,10 @@
|
||||
var Book = require('./book');
|
||||
var cli = require('./cli');
|
||||
var extend = require('extend');
|
||||
|
||||
module.exports = {
|
||||
Book: Book,
|
||||
commands: cli.commands
|
||||
};
|
||||
var common = require('./browser');
|
||||
|
||||
module.exports = extend({
|
||||
initBook: require('./init'),
|
||||
createNodeFS: require('./fs/node'),
|
||||
Output: require('./output'),
|
||||
commands: require('./cli')
|
||||
}, common);
|
||||
|
||||
+68
-52
@@ -1,66 +1,82 @@
|
||||
var path = require('path');
|
||||
|
||||
var createNodeFS = require('./fs/node');
|
||||
var fs = require('./utils/fs');
|
||||
var Promise = require('./utils/promise');
|
||||
var File = require('./models/file');
|
||||
var Readme = require('./models/readme');
|
||||
var Book = require('./models/book');
|
||||
var Parse = require('./parse');
|
||||
|
||||
// Initialize folder structure for a book
|
||||
// Read SUMMARY to created the right chapter
|
||||
function initBook(book) {
|
||||
var extensionToUse = '.md';
|
||||
/**
|
||||
Initialize folder structure for a book
|
||||
Read SUMMARY to created the right chapter
|
||||
|
||||
book.log.info.ln('init book at', book.root);
|
||||
return fs.mkdirp(book.root)
|
||||
@param {Book}
|
||||
@param {String}
|
||||
@return {Promise}
|
||||
*/
|
||||
function initBook(rootFolder) {
|
||||
var extension = '.md';
|
||||
|
||||
return fs.mkdirp(rootFolder)
|
||||
|
||||
// Parse the summary and readme
|
||||
.then(function() {
|
||||
return book.config.load();
|
||||
})
|
||||
.then(function() {
|
||||
book.log.info.ln('detect structure from SUMMARY (if it exists)');
|
||||
return book.summary.load();
|
||||
})
|
||||
.then(function() {
|
||||
var summary = book.summary.path || 'SUMMARY.md';
|
||||
var articles = book.summary.flatten();
|
||||
var fs = createNodeFS(rootFolder);
|
||||
var book = Book.createForFS(fs);
|
||||
|
||||
// Use extension of summary
|
||||
extensionToUse = path.extname(summary);
|
||||
return Parse.parseReadme(book)
|
||||
|
||||
// Readme doesn't have a path
|
||||
if (!articles[0].path) {
|
||||
articles[0].path = 'README' + extensionToUse;
|
||||
}
|
||||
|
||||
// Summary doesn't exists? create one
|
||||
if (!book.summary.path) {
|
||||
articles.push({
|
||||
title: 'Summary',
|
||||
path: 'SUMMARY'+extensionToUse
|
||||
});
|
||||
}
|
||||
|
||||
// Create files that don't exist
|
||||
return Promise.serie(articles, function(article) {
|
||||
if (!article.path) return;
|
||||
|
||||
var absolutePath = book.resolve(article.path);
|
||||
|
||||
return fs.exists(absolutePath)
|
||||
.then(function(exists) {
|
||||
if(exists) {
|
||||
book.log.info.ln('found', article.path);
|
||||
return;
|
||||
} else {
|
||||
book.log.info.ln('create', article.path);
|
||||
}
|
||||
|
||||
return fs.mkdirp(path.dirname(absolutePath))
|
||||
.then(function() {
|
||||
return fs.writeFile(absolutePath, '# '+article.title+'\n\n');
|
||||
});
|
||||
});
|
||||
// Setup default readme if doesn't found one
|
||||
.fail(function() {
|
||||
var readmeFile = File.createWithFilepath('README' + extension);
|
||||
var readme = Readme.create(readmeFile);
|
||||
return book.setReadme(readme);
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
book.log.info.ln('initialization is finished');
|
||||
.then(Parse.parseSummary)
|
||||
|
||||
.then(function(book) {
|
||||
var logger = book.getLogger();
|
||||
var summary = book.getSummary();
|
||||
var summaryFile = summary.getFile();
|
||||
var summaryFilename = summaryFile.getPath() || ('SUMMARY' + extension);
|
||||
|
||||
var articles = summary.getArticlesAsList();
|
||||
|
||||
// Write pages
|
||||
return Promise.forEach(articles, function(article) {
|
||||
var articlePath = article.getPath();
|
||||
var filePath = articlePath? path.join(rootFolder, articlePath) : null;
|
||||
if (!filePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fs.assertFile(filePath, function() {
|
||||
return fs.ensureFile(filePath)
|
||||
.then(function() {
|
||||
logger.info.ln('create', article.getPath());
|
||||
return fs.writeFile(filePath, '# ' + article.getTitle() + '\n\n');
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
// Write summary
|
||||
.then(function() {
|
||||
var filePath = path.join(rootFolder, summaryFilename);
|
||||
|
||||
return fs.ensureFile(filePath)
|
||||
.then(function() {
|
||||
logger.info.ln('create ' + path.basename(filePath));
|
||||
return fs.writeFile(filePath, summary.toText(extension));
|
||||
});
|
||||
})
|
||||
|
||||
// Log end
|
||||
.then(function() {
|
||||
logger.info.ln('initialization is finished');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
var extend = require('extend');
|
||||
|
||||
var gitbook = require('../gitbook');
|
||||
var encodeSummary = require('./encodeSummary');
|
||||
var encodeGlossary = require('./encodeGlossary');
|
||||
var encodeReadme = require('./encodeReadme');
|
||||
|
||||
/**
|
||||
Encode a book to JSON
|
||||
|
||||
@param {Book}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeBookToJson(book) {
|
||||
var config = book.getConfig();
|
||||
var language = book.getLanguage();
|
||||
|
||||
var variables = config.getValue('variables', {});
|
||||
|
||||
return {
|
||||
summary: encodeSummary(book.getSummary()),
|
||||
glossary: encodeGlossary(book.getGlossary()),
|
||||
readme: encodeReadme(book.getReadme()),
|
||||
config: book.getConfig().getValues().toJS(),
|
||||
gitbook: {
|
||||
version: gitbook.version,
|
||||
time: gitbook.START_TIME
|
||||
},
|
||||
book: extend({
|
||||
language: language? language : undefined
|
||||
}, variables.toJS())
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeBookToJson;
|
||||
@@ -0,0 +1,22 @@
|
||||
var encodeBook = require('./encodeBook');
|
||||
var encodePage = require('./encodePage');
|
||||
var encodeFile = require('./encodeFile');
|
||||
|
||||
/**
|
||||
Return a JSON representation of a book with a specific file
|
||||
|
||||
@param {Book} output
|
||||
@param {Page} page
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeBookWithPage(book, page) {
|
||||
var file = page.getFile();
|
||||
|
||||
var result = encodeBook(book);
|
||||
result.page = encodePage(page, book.getSummary());
|
||||
result.file = encodeFile(file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeBookWithPage;
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
/**
|
||||
Return a JSON representation of a file
|
||||
|
||||
@param {File} file
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeFileToJson(file) {
|
||||
var filePath = file.getPath();
|
||||
if (!filePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
path: filePath,
|
||||
mtime: file.getMTime(),
|
||||
type: file.getType()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeFileToJson;
|
||||
@@ -0,0 +1,21 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
var encodeGlossaryEntry = require('./encodeGlossaryEntry');
|
||||
|
||||
/**
|
||||
Encode a glossary to JSON
|
||||
|
||||
@param {Glossary}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeGlossary(glossary) {
|
||||
var file = glossary.getFile();
|
||||
var entries = glossary.getEntries();
|
||||
|
||||
return {
|
||||
file: encodeFile(file),
|
||||
entries: entries
|
||||
.map(encodeGlossaryEntry).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeGlossary;
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
/**
|
||||
Encode a SummaryArticle to JSON
|
||||
|
||||
@param {GlossaryEntry}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeGlossaryEntry(entry) {
|
||||
return {
|
||||
id: entry.getID(),
|
||||
name: entry.getName(),
|
||||
description: entry.getDescription()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeGlossaryEntry;
|
||||
@@ -0,0 +1,25 @@
|
||||
var encodeBook = require('./encodeBook');
|
||||
|
||||
/**
|
||||
Encode an output to JSON
|
||||
|
||||
@param {Output}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeOutputToJson(output) {
|
||||
var book = output.getBook();
|
||||
var generator = output.getGenerator();
|
||||
var options = output.getOptions();
|
||||
|
||||
var result = encodeBook(book);
|
||||
|
||||
result.output = {
|
||||
name: generator
|
||||
};
|
||||
|
||||
result.options = options.toJS();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeOutputToJson;
|
||||
@@ -0,0 +1,39 @@
|
||||
var encodeSummaryArticle = require('./encodeSummaryArticle');
|
||||
|
||||
/**
|
||||
Return a JSON representation of a page
|
||||
|
||||
@param {Page} page
|
||||
@param {Summary} summary
|
||||
@return {Object}
|
||||
*/
|
||||
function encodePage(page, summary) {
|
||||
var file = page.getFile();
|
||||
var attributes = page.getAttributes();
|
||||
var article = summary.getByPath(file.getPath());
|
||||
|
||||
var result = attributes.toJS();
|
||||
|
||||
if (article) {
|
||||
result.title = article.getTitle();
|
||||
result.level = article.getLevel();
|
||||
result.depth = article.getDepth();
|
||||
|
||||
var nextArticle = summary.getNextArticle(article);
|
||||
if (nextArticle) {
|
||||
result.next = encodeSummaryArticle(nextArticle);
|
||||
}
|
||||
|
||||
var prevArticle = summary.getPrevArticle(article);
|
||||
if (prevArticle) {
|
||||
result.previous = encodeSummaryArticle(prevArticle);
|
||||
}
|
||||
}
|
||||
|
||||
result.content = page.getContent();
|
||||
result.dir = page.getDir();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodePage;
|
||||
@@ -0,0 +1,17 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
|
||||
/**
|
||||
Encode a readme to JSON
|
||||
|
||||
@param {Readme}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeReadme(readme) {
|
||||
var file = readme.getFile();
|
||||
|
||||
return {
|
||||
file: encodeFile(file)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeReadme;
|
||||
@@ -0,0 +1,20 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
var encodeSummaryPart = require('./encodeSummaryPart');
|
||||
|
||||
/**
|
||||
Encode a summary to JSON
|
||||
|
||||
@param {Summary}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeSummary(summary) {
|
||||
var file = summary.getFile();
|
||||
var parts = summary.getParts();
|
||||
|
||||
return {
|
||||
file: encodeFile(file),
|
||||
parts: parts.map(encodeSummaryPart).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeSummary;
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
/**
|
||||
Encode a SummaryArticle to JSON
|
||||
|
||||
@param {SummaryArticle}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeSummaryArticle(article, recursive) {
|
||||
var articles = undefined;
|
||||
if (recursive !== false) {
|
||||
articles = article.getArticles()
|
||||
.map(encodeSummaryArticle)
|
||||
.toJS();
|
||||
}
|
||||
|
||||
return {
|
||||
title: article.getTitle(),
|
||||
level: article.getLevel(),
|
||||
depth: article.getDepth(),
|
||||
anchor: article.getAnchor(),
|
||||
url: article.getUrl(),
|
||||
path: article.getPath(),
|
||||
ref: article.getRef(),
|
||||
articles: articles
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeSummaryArticle;
|
||||
@@ -0,0 +1,17 @@
|
||||
var encodeSummaryArticle = require('./encodeSummaryArticle');
|
||||
|
||||
/**
|
||||
Encode a SummaryPart to JSON
|
||||
|
||||
@param {SummaryPart}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeSummaryPart(part) {
|
||||
return {
|
||||
title: part.getTitle(),
|
||||
articles: part.getArticles()
|
||||
.map(encodeSummaryArticle).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeSummaryPart;
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
module.exports = {
|
||||
encodeOutput: require('./encodeOutput'),
|
||||
encodeBookWithPage: require('./encodeBookWithPage'),
|
||||
encodeBook: require('./encodeBook'),
|
||||
encodeFile: require('./encodeFile'),
|
||||
encodePage: require('./encodePage'),
|
||||
encodeSummary: require('./encodeSummary'),
|
||||
encodeSummaryArticle: require('./encodeSummaryArticle'),
|
||||
encodeReadme: require('./encodeReadme')
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
var Immutable = require('immutable');
|
||||
|
||||
describe('Config', function() {
|
||||
var Config = require('../config');
|
||||
|
||||
var config = Config.createWithValues({
|
||||
hello: {
|
||||
world: 1,
|
||||
test: 'Hello',
|
||||
isFalse: false
|
||||
}
|
||||
});
|
||||
|
||||
describe('getValue', function() {
|
||||
it('must return value as immutable', function() {
|
||||
var value = config.getValue('hello');
|
||||
expect(Immutable.Map.isMap(value)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('must return deep value', function() {
|
||||
var value = config.getValue('hello.world');
|
||||
expect(value).toBe(1);
|
||||
});
|
||||
|
||||
it('must return default value if non existant', function() {
|
||||
var value = config.getValue('hello.nonExistant', 'defaultValue');
|
||||
expect(value).toBe('defaultValue');
|
||||
});
|
||||
|
||||
it('must not return default value for falsy values', function() {
|
||||
var value = config.getValue('hello.isFalse', 'defaultValue');
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setValue', function() {
|
||||
it('must set value as immutable', function() {
|
||||
var testConfig = config.setValue('hello', {
|
||||
'cool': 1
|
||||
});
|
||||
var value = testConfig.getValue('hello');
|
||||
|
||||
expect(Immutable.Map.isMap(value)).toBeTruthy();
|
||||
expect(value.size).toBe(1);
|
||||
expect(value.has('cool')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('must set deep value', function() {
|
||||
var testConfig = config.setValue('hello.world', 2);
|
||||
var hello = testConfig.getValue('hello');
|
||||
var world = testConfig.getValue('hello.world');
|
||||
|
||||
expect(Immutable.Map.isMap(hello)).toBeTruthy();
|
||||
expect(hello.size).toBe(3);
|
||||
|
||||
expect(world).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('Glossary', function() {
|
||||
var File = require('../file');
|
||||
var Glossary = require('../glossary');
|
||||
var GlossaryEntry = require('../glossaryEntry');
|
||||
|
||||
var glossary = Glossary.createFromEntries(File(), [
|
||||
{
|
||||
name: 'Hello World',
|
||||
description: 'Awesome!'
|
||||
},
|
||||
{
|
||||
name: 'JavaScript',
|
||||
description: 'This is a cool language'
|
||||
}
|
||||
]);
|
||||
|
||||
describe('createFromEntries', function() {
|
||||
it('must add all entries', function() {
|
||||
var entries = glossary.getEntries();
|
||||
expect(entries.size).toBe(2);
|
||||
});
|
||||
|
||||
it('must add entries as GlossaryEntries', function() {
|
||||
var entries = glossary.getEntries();
|
||||
var entry = entries.get('hello-world');
|
||||
expect(entry instanceof GlossaryEntry).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toText', function() {
|
||||
pit('return as markdown', function() {
|
||||
return glossary.toText('.md')
|
||||
.then(function(text) {
|
||||
expect(text).toContain('# Glossary');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('GlossaryEntry', function() {
|
||||
var GlossaryEntry = require('../glossaryEntry');
|
||||
|
||||
describe('getID', function() {
|
||||
it('must return a normalized ID', function() {
|
||||
var entry = new GlossaryEntry({
|
||||
name: 'Hello World'
|
||||
});
|
||||
|
||||
expect(entry.getID()).toBe('hello-world');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
describe('Plugin', function() {
|
||||
var Plugin = require('../plugin');
|
||||
|
||||
describe('createFromString', function() {
|
||||
it('must parse name', function() {
|
||||
var plugin = Plugin.createFromString('hello');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('*');
|
||||
});
|
||||
|
||||
it('must parse version', function() {
|
||||
var plugin = Plugin.createFromString('hello@1.0.0');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLoaded', function() {
|
||||
it('must return false for empty plugin', function() {
|
||||
var plugin = Plugin.createFromString('hello');
|
||||
expect(plugin.isLoaded()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
describe('PluginDependency', function() {
|
||||
var PluginDependency = require('../pluginDependency');
|
||||
|
||||
describe('createFromString', function() {
|
||||
it('must parse name', function() {
|
||||
var plugin = PluginDependency.createFromString('hello');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('*');
|
||||
});
|
||||
|
||||
it('must parse state', function() {
|
||||
var plugin = PluginDependency.createFromString('-hello');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.isEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
describe('Version', function() {
|
||||
it('must parse version', function() {
|
||||
var plugin = PluginDependency.createFromString('hello@1.0.0');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('must parse semver', function() {
|
||||
var plugin = PluginDependency.createFromString('hello@>=4.0.0');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('>=4.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIT Version', function() {
|
||||
it('must handle HTTPS urls', function() {
|
||||
var plugin = PluginDependency.createFromString('hello@git+https://github.com/GitbookIO/plugin-ga.git');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('git+https://github.com/GitbookIO/plugin-ga.git');
|
||||
});
|
||||
|
||||
it('must handle SSH urls', function() {
|
||||
var plugin = PluginDependency.createFromString('hello@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||
expect(plugin.getName()).toBe('hello');
|
||||
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
describe('Summary', function() {
|
||||
var File = require('../file');
|
||||
var Summary = require('../summary');
|
||||
|
||||
var summary = Summary.createFromParts(File(), [
|
||||
{
|
||||
articles: [
|
||||
{
|
||||
title: 'My First Article',
|
||||
ref: 'README.md'
|
||||
},
|
||||
{
|
||||
title: 'My Second Article',
|
||||
ref: 'article.md'
|
||||
},
|
||||
{
|
||||
title: 'Article without ref'
|
||||
},
|
||||
{
|
||||
title: 'Article with absolute ref',
|
||||
ref: 'https://google.fr'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Test'
|
||||
}
|
||||
]);
|
||||
|
||||
describe('createFromEntries', function() {
|
||||
it('must add all parts', function() {
|
||||
var parts = summary.getParts();
|
||||
expect(parts.size).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getByLevel', function() {
|
||||
it('can return a Part', function() {
|
||||
var part = summary.getByLevel('1');
|
||||
|
||||
expect(part).toBeDefined();
|
||||
expect(part.getArticles().size).toBe(4);
|
||||
});
|
||||
|
||||
it('can return a Part (2)', function() {
|
||||
var part = summary.getByLevel('2');
|
||||
|
||||
expect(part).toBeDefined();
|
||||
expect(part.getTitle()).toBe('Test');
|
||||
expect(part.getArticles().size).toBe(0);
|
||||
});
|
||||
|
||||
it('can return an Article', function() {
|
||||
var article = summary.getByLevel('1.1');
|
||||
|
||||
expect(article).toBeDefined();
|
||||
expect(article.getTitle()).toBe('My First Article');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getByPath', function() {
|
||||
it('return correct article', function() {
|
||||
var article = summary.getByPath('README.md');
|
||||
|
||||
expect(article).toBeDefined();
|
||||
expect(article.getTitle()).toBe('My First Article');
|
||||
});
|
||||
|
||||
it('return correct article', function() {
|
||||
var article = summary.getByPath('article.md');
|
||||
|
||||
expect(article).toBeDefined();
|
||||
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() {
|
||||
pit('return as markdown', function() {
|
||||
return summary.toText('.md')
|
||||
.then(function(text) {
|
||||
expect(text).toContain('# Summary');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
var nunjucks = require('nunjucks');
|
||||
var Immutable = require('immutable');
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
describe('TemplateBlock', function() {
|
||||
var TemplateBlock = require('../templateBlock');
|
||||
|
||||
describe('create', function() {
|
||||
pit('must initialize a simple TemplateBlock from a function', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Check basic templateBlock properties
|
||||
expect(templateBlock.getName()).toBe('sayhello');
|
||||
expect(templateBlock.getEndTag()).toBe('endsayhello');
|
||||
expect(templateBlock.getBlocks().size).toBe(0);
|
||||
expect(templateBlock.getExtensionName()).toBe('BlocksayhelloExtension');
|
||||
|
||||
// Check result of applying block
|
||||
return Promise()
|
||||
.then(function() {
|
||||
return templateBlock.applyBlock();
|
||||
})
|
||||
.then(function(result) {
|
||||
expect(result.name).toBe('sayhello');
|
||||
expect(result.body).toBe('<p>Hello, World!</p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getShortcuts', function() {
|
||||
it('must return undefined if no shortcuts', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
expect(templateBlock.getShortcuts()).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('must return complete shortcut', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', {
|
||||
process: function(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
},
|
||||
shortcuts: {
|
||||
parsers: ['markdown'],
|
||||
start: '$',
|
||||
end: '-'
|
||||
}
|
||||
});
|
||||
|
||||
var shortcut = templateBlock.getShortcuts();
|
||||
|
||||
expect(shortcut).toBeDefined();
|
||||
expect(shortcut.getStart()).toEqual('$');
|
||||
expect(shortcut.getEnd()).toEqual('-');
|
||||
expect(shortcut.getStartTag()).toEqual('sayhello');
|
||||
expect(shortcut.getEndTag()).toEqual('endsayhello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNunjucksExt()', function() {
|
||||
pit('should replace by block anchor', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
pit('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
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
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) {
|
||||
expect(res).toBe('<p>Hello, World!</p>');
|
||||
});
|
||||
});
|
||||
|
||||
pit('must apply block arguments correctly', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<'+block.kwargs.tag+'>Hello, '+block.kwargs.name+'!</'+block.kwargs.tag+'>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% sayhello name="Samy", tag="p" %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('<p>Hello, Samy!</p>');
|
||||
});
|
||||
});
|
||||
|
||||
pit('must accept an async function', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
return {
|
||||
body: 'Hello ' + block.body,
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% sayhello %}Samy{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('Hello Samy');
|
||||
});
|
||||
});
|
||||
|
||||
pit('must handle nested blocks', function() {
|
||||
var templateBlock = new TemplateBlock({
|
||||
name: 'yoda',
|
||||
blocks: Immutable.List(['start', 'end']),
|
||||
process: function(block) {
|
||||
var nested = {};
|
||||
|
||||
block.blocks.forEach(function(blk) {
|
||||
nested[blk.name] = blk.body.trim();
|
||||
});
|
||||
|
||||
return {
|
||||
body: '<p class="yoda">'+nested.end+' '+nested.start+'</p>',
|
||||
parse: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% yoda %}{% start %}this sentence should be{% end %}inverted{% endyoda %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('<p class="yoda">inverted this sentence should be</p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
describe('TemplateBlock', function() {
|
||||
var TemplateEngine = require('../templateEngine');
|
||||
|
||||
describe('create', function() {
|
||||
it('must initialize with a list of filters', function() {
|
||||
var engine = TemplateEngine.create({
|
||||
filters: {
|
||||
hello: function(name) {
|
||||
return 'Hello ' + name + '!';
|
||||
}
|
||||
}
|
||||
});
|
||||
var env = engine.toNunjucks();
|
||||
var res = env.renderString('{{ "Luke"|hello }}');
|
||||
|
||||
expect(res).toBe('Hello Luke!');
|
||||
});
|
||||
|
||||
it('must initialize with a list of globals', function() {
|
||||
var engine = TemplateEngine.create({
|
||||
globals: {
|
||||
hello: function(name) {
|
||||
return 'Hello ' + name + '!';
|
||||
}
|
||||
}
|
||||
});
|
||||
var env = engine.toNunjucks();
|
||||
var res = env.renderString('{{ hello("Luke") }}');
|
||||
|
||||
expect(res).toBe('Hello Luke!');
|
||||
});
|
||||
|
||||
it('must pass context to filters and blocks', function() {
|
||||
var engine = TemplateEngine.create({
|
||||
filters: {
|
||||
hello: function(name) {
|
||||
return 'Hello ' + name + ' ' + this.lastName + '!';
|
||||
}
|
||||
},
|
||||
context: {
|
||||
lastName: 'Skywalker'
|
||||
}
|
||||
});
|
||||
var env = engine.toNunjucks();
|
||||
var res = env.renderString('{{ "Luke"|hello }}');
|
||||
|
||||
expect(res).toBe('Hello Luke Skywalker!');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,320 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
var Ignore = require('ignore');
|
||||
|
||||
var Logger = require('../utils/logger');
|
||||
|
||||
var FS = require('./fs');
|
||||
var Config = require('./config');
|
||||
var Readme = require('./readme');
|
||||
var Summary = require('./summary');
|
||||
var Glossary = require('./glossary');
|
||||
var Languages = require('./languages');
|
||||
|
||||
|
||||
var Book = Immutable.Record({
|
||||
// Logger for outptu message
|
||||
logger: Logger(),
|
||||
|
||||
// Filesystem binded to the book scope to read files/directories
|
||||
fs: FS(),
|
||||
|
||||
// Ignore files parser
|
||||
ignore: Ignore(),
|
||||
|
||||
// Structure files
|
||||
config: Config(),
|
||||
readme: Readme(),
|
||||
summary: Summary(),
|
||||
glossary: Glossary(),
|
||||
languages: Languages(),
|
||||
|
||||
// ID of the language for language books
|
||||
language: String(),
|
||||
|
||||
// List of children, if multilingual (String -> Book)
|
||||
books: Immutable.OrderedMap()
|
||||
});
|
||||
|
||||
Book.prototype.getLogger = function() {
|
||||
return this.get('logger');
|
||||
};
|
||||
|
||||
Book.prototype.getFS = function() {
|
||||
return this.get('fs');
|
||||
};
|
||||
|
||||
Book.prototype.getIgnore = function() {
|
||||
return this.get('ignore');
|
||||
};
|
||||
|
||||
Book.prototype.getConfig = function() {
|
||||
return this.get('config');
|
||||
};
|
||||
|
||||
Book.prototype.getReadme = function() {
|
||||
return this.get('readme');
|
||||
};
|
||||
|
||||
Book.prototype.getSummary = function() {
|
||||
return this.get('summary');
|
||||
};
|
||||
|
||||
Book.prototype.getGlossary = function() {
|
||||
return this.get('glossary');
|
||||
};
|
||||
|
||||
Book.prototype.getLanguages = function() {
|
||||
return this.get('languages');
|
||||
};
|
||||
|
||||
Book.prototype.getBooks = function() {
|
||||
return this.get('books');
|
||||
};
|
||||
|
||||
Book.prototype.getLanguage = function() {
|
||||
return this.get('language');
|
||||
};
|
||||
|
||||
/**
|
||||
Return FS instance to access the content
|
||||
|
||||
@return {FS}
|
||||
*/
|
||||
Book.prototype.getContentFS = function() {
|
||||
var fs = this.getFS();
|
||||
var config = this.getConfig();
|
||||
var rootFolder = config.getValue('root');
|
||||
|
||||
if (rootFolder) {
|
||||
return FS.reduceScope(fs, rootFolder);
|
||||
}
|
||||
|
||||
return fs;
|
||||
};
|
||||
|
||||
/**
|
||||
Return root of the book
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getRoot = function() {
|
||||
var fs = this.getFS();
|
||||
return fs.getRoot();
|
||||
};
|
||||
|
||||
/**
|
||||
Return root for content of the book
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getContentRoot = function() {
|
||||
var fs = this.getContentFS();
|
||||
return fs.getRoot();
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a file is ignore (should not being parsed, etc)
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.isFileIgnored = function(filename) {
|
||||
var ignore = this.getIgnore();
|
||||
var language = this.getLanguage();
|
||||
|
||||
// Ignore is always relative to the root of the main book
|
||||
if (language) {
|
||||
filename = path.join(language, filename);
|
||||
}
|
||||
|
||||
|
||||
return ignore.filter([filename]).length == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a content file is ignore (should not being parsed, etc)
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.isContentFileIgnored = function(filename) {
|
||||
var config = this.getConfig();
|
||||
var rootFolder = config.getValue('root');
|
||||
|
||||
if (rootFolder) {
|
||||
filename = path.join(rootFolder, filename);
|
||||
}
|
||||
|
||||
return this.isFileIgnored(filename);
|
||||
};
|
||||
|
||||
/**
|
||||
Return a page from a book by its path
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.getPage = function(ref) {
|
||||
return this.getPages().get(ref);
|
||||
};
|
||||
|
||||
/**
|
||||
Is this book the parent of language's books
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Book.prototype.isMultilingual = function() {
|
||||
return (this.getLanguages().getCount() > 0);
|
||||
};
|
||||
|
||||
/**
|
||||
Return true if book is associated to a language
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Book.prototype.isLanguageBook = function() {
|
||||
return Boolean(this.getLanguage());
|
||||
};
|
||||
|
||||
/**
|
||||
Add a new language book
|
||||
|
||||
@param {String} language
|
||||
@param {Book} book
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.addLanguageBook = function(language, book) {
|
||||
var books = this.getBooks();
|
||||
books = books.set(language, book);
|
||||
|
||||
return this.set('books', books);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the summary for this book
|
||||
|
||||
@param {Summary}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setSummary = function(summary) {
|
||||
return this.set('summary', summary);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the readme for this book
|
||||
|
||||
@param {Readme}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setReadme = function(readme) {
|
||||
return this.set('readme', readme);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the configuration for this book
|
||||
|
||||
@param {Config}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setConfig = function(config) {
|
||||
return this.set('config', config);
|
||||
};
|
||||
|
||||
/**
|
||||
Change log level
|
||||
|
||||
@param {String} level
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setLogLevel = function(level) {
|
||||
this.getLogger().setLevel(level);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
Create a book using a filesystem
|
||||
|
||||
@param {FS} fs
|
||||
@return {Book}
|
||||
*/
|
||||
Book.createForFS = function createForFS(fs) {
|
||||
return new Book({
|
||||
fs: fs
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Infers the default extension for files
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultExt = function() {
|
||||
// Inferring sources
|
||||
var clues = [
|
||||
this.getReadme(),
|
||||
this.getSummary(),
|
||||
this.getGlossary()
|
||||
];
|
||||
|
||||
// List their extensions
|
||||
var exts = clues.map(function (clue) {
|
||||
var file = clue.getFile();
|
||||
if (file.exists()) {
|
||||
return file.getParser().getExtensions().first();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
// Adds the general default extension
|
||||
exts.push('.md');
|
||||
|
||||
// Choose the first non null
|
||||
return exts.find(function (e) { return e !== null; });
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Readme.
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultReadmePath = function() {
|
||||
return this.getContentRoot()+'README'+this.getDefaultExt();
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Summary.
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultSummaryPath = function() {
|
||||
return this.getContentRoot()+'SUMMARY'+this.getDefaultExt();
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Glossary.
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultGlossaryPath = function() {
|
||||
return this.getContentRoot()+'GLOSSARY'+this.getDefaultExt();
|
||||
};
|
||||
|
||||
/**
|
||||
Create a language book from a parent
|
||||
|
||||
@param {Book} parent
|
||||
@param {String} language
|
||||
@return {Book}
|
||||
*/
|
||||
Book.createFromParent = function createFromParent(parent, language) {
|
||||
var ignore = parent.getIgnore();
|
||||
|
||||
return new Book({
|
||||
// Inherits config. logegr and list of ignored files
|
||||
logger: parent.getLogger(),
|
||||
config: parent.getConfig(),
|
||||
ignore: Ignore().add(ignore),
|
||||
|
||||
language: language,
|
||||
fs: FS.reduceScope(parent.getContentFS(), language)
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Book;
|
||||
@@ -0,0 +1,134 @@
|
||||
var is = require('is');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
var PluginDependency = require('./pluginDependency');
|
||||
var configDefault = require('../constants/configDefault');
|
||||
|
||||
var Config = Immutable.Record({
|
||||
file: File(),
|
||||
values: configDefault
|
||||
}, 'Config');
|
||||
|
||||
Config.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Config.prototype.getValues = function() {
|
||||
return this.get('values');
|
||||
};
|
||||
|
||||
/**
|
||||
Return a configuration value by its key path
|
||||
|
||||
@param {String} key
|
||||
@return {Mixed}
|
||||
*/
|
||||
Config.prototype.getValue = function(keyPath, def) {
|
||||
var values = this.getValues();
|
||||
keyPath = Config.keyToKeyPath(keyPath);
|
||||
|
||||
if (!values.hasIn(keyPath)) {
|
||||
return Immutable.fromJS(def);
|
||||
}
|
||||
|
||||
return values.getIn(keyPath);
|
||||
};
|
||||
|
||||
/**
|
||||
Update a configuration value
|
||||
|
||||
@param {String} key
|
||||
@param {Mixed} value
|
||||
@return {Mixed}
|
||||
*/
|
||||
Config.prototype.setValue = function(keyPath, value) {
|
||||
keyPath = Config.keyToKeyPath(keyPath);
|
||||
|
||||
value = Immutable.fromJS(value);
|
||||
|
||||
var values = this.getValues();
|
||||
values = values.setIn(keyPath, value);
|
||||
|
||||
return this.set('values', values);
|
||||
};
|
||||
|
||||
/**
|
||||
Return a list of plugin dependencies
|
||||
|
||||
@return {List<PluginDependency>}
|
||||
*/
|
||||
Config.prototype.getPluginDependencies = function() {
|
||||
var plugins = this.getValue('plugins');
|
||||
|
||||
if (is.string(plugins)) {
|
||||
return PluginDependency.listFromString(plugins);
|
||||
} else {
|
||||
return PluginDependency.listFromArray(plugins);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Update the list of plugins dependencies
|
||||
|
||||
@param {List<PluginDependency>}
|
||||
@return {Config}
|
||||
*/
|
||||
Config.prototype.setPluginDependencies = function(deps) {
|
||||
var plugins = PluginDependency.listFromArray(deps);
|
||||
|
||||
return this.setValue('plugins', plugins);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new config for a file
|
||||
|
||||
@param {File} file
|
||||
@param {Object} values
|
||||
@returns {Config}
|
||||
*/
|
||||
Config.create = function(file, values) {
|
||||
return new Config({
|
||||
file: file,
|
||||
values: Immutable.fromJS(values)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new config
|
||||
|
||||
@param {Object} values
|
||||
@returns {Config}
|
||||
*/
|
||||
Config.createWithValues = function(values) {
|
||||
return new Config({
|
||||
values: Immutable.fromJS(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
|
||||
|
||||
@param {String|Array}
|
||||
@return {Array}
|
||||
*/
|
||||
Config.keyToKeyPath = function(keyPath) {
|
||||
if (is.string(keyPath)) keyPath = keyPath.split('.');
|
||||
return keyPath;
|
||||
};
|
||||
|
||||
module.exports = Config;
|
||||
@@ -0,0 +1,89 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var parsers = require('../parsers');
|
||||
|
||||
var File = Immutable.Record({
|
||||
// Path of the file, relative to the FS
|
||||
path: String(),
|
||||
|
||||
// Time when file data last modified
|
||||
mtime: Date()
|
||||
});
|
||||
|
||||
File.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
File.prototype.getMTime = function() {
|
||||
return this.get('mtime');
|
||||
};
|
||||
|
||||
/**
|
||||
Does the file exists / is set
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
File.prototype.exists = function() {
|
||||
return Boolean(this.getPath());
|
||||
};
|
||||
|
||||
/**
|
||||
Return type of file ('markdown' or 'asciidoc')
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
File.prototype.getType = function() {
|
||||
var parser = this.getParser();
|
||||
if (parser) {
|
||||
return parser.getName();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Return extension of this file (lowercased)
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
File.prototype.getExtension = function() {
|
||||
return path.extname(this.getPath()).toLowerCase();
|
||||
};
|
||||
|
||||
/**
|
||||
Return parser for this file
|
||||
|
||||
@return {Parser}
|
||||
*/
|
||||
File.prototype.getParser = function() {
|
||||
return parsers.getByExt(this.getExtension());
|
||||
};
|
||||
|
||||
/**
|
||||
Create a file from stats informations
|
||||
|
||||
@param {String} filepath
|
||||
@param {Object|fs.Stats} stat
|
||||
@return {File}
|
||||
*/
|
||||
File.createFromStat = function createFromStat(filepath, stat) {
|
||||
return new File({
|
||||
path: filepath,
|
||||
mtime: stat.mtime
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Create a file with only a path
|
||||
|
||||
@param {String} filepath
|
||||
@return {File}
|
||||
*/
|
||||
File.createWithFilepath = function createWithFilepath(filepath) {
|
||||
return new File({
|
||||
path: filepath
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = File;
|
||||
@@ -0,0 +1,301 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
var stream = require('stream');
|
||||
|
||||
var File = require('./file');
|
||||
var Promise = require('../utils/promise');
|
||||
var error = require('../utils/error');
|
||||
var PathUtil = require('../utils/path');
|
||||
|
||||
var FS = Immutable.Record({
|
||||
root: String(),
|
||||
|
||||
fsExists: Function(),
|
||||
fsReadFile: Function(),
|
||||
fsStatFile: Function(),
|
||||
fsReadDir: Function(),
|
||||
|
||||
fsLoadObject: null,
|
||||
fsReadAsStream: null
|
||||
});
|
||||
|
||||
/**
|
||||
Return path to the root
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
FS.prototype.getRoot = function() {
|
||||
return this.get('root');
|
||||
};
|
||||
|
||||
/**
|
||||
Verify that a file is in the fs scope
|
||||
|
||||
@param {String} filename
|
||||
@return {Boolean}
|
||||
*/
|
||||
FS.prototype.isInScope = function(filename) {
|
||||
var rootPath = this.getRoot();
|
||||
filename = path.join(rootPath, filename);
|
||||
|
||||
return PathUtil.isInRoot(rootPath, filename);
|
||||
};
|
||||
|
||||
/**
|
||||
Resolve a file in this FS
|
||||
|
||||
@param {String}
|
||||
@return {String}
|
||||
*/
|
||||
FS.prototype.resolve = function() {
|
||||
var rootPath = this.getRoot();
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var filename = path.join.apply(path, [rootPath].concat(args));
|
||||
filename = path.normalize(filename);
|
||||
|
||||
if (!this.isInScope(filename)) {
|
||||
throw error.FileOutOfScopeError({
|
||||
filename: filename,
|
||||
root: this.root
|
||||
});
|
||||
}
|
||||
|
||||
return filename;
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Boolean>}
|
||||
*/
|
||||
FS.prototype.exists = function(filename) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
filename = that.resolve(filename);
|
||||
var exists = that.get('fsExists');
|
||||
|
||||
return exists(filename);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read a file and returns a promise with the content as a buffer
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Buffer>}
|
||||
*/
|
||||
FS.prototype.read = function(filename) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
filename = that.resolve(filename);
|
||||
var read = that.get('fsReadFile');
|
||||
|
||||
return read(filename);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read a file as a string (utf-8)
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
FS.prototype.readAsString = function(filename, encoding) {
|
||||
encoding = encoding || 'utf8';
|
||||
|
||||
return this.read(filename)
|
||||
.then(function(buf) {
|
||||
return buf.toString(encoding);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read file as a stream
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Stream>}
|
||||
*/
|
||||
FS.prototype.readAsStream = function(filename) {
|
||||
var that = this;
|
||||
var filepath = that.resolve(filename);
|
||||
var fsReadAsStream = this.get('fsReadAsStream');
|
||||
|
||||
if (fsReadAsStream) {
|
||||
return Promise(fsReadAsStream(filepath));
|
||||
}
|
||||
|
||||
return this.read(filename)
|
||||
.then(function(buf) {
|
||||
var bufferStream = new stream.PassThrough();
|
||||
bufferStream.end(buf);
|
||||
|
||||
return bufferStream;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read stat infos about a file
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<File>}
|
||||
*/
|
||||
FS.prototype.statFile = function(filename) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var filepath = that.resolve(filename);
|
||||
var stat = that.get('fsStatFile');
|
||||
|
||||
return stat(filepath);
|
||||
})
|
||||
.then(function(stat) {
|
||||
return File.createFromStat(filename, stat);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
List files/directories in a directory.
|
||||
Directories ends with '/'
|
||||
|
||||
@param {String} dirname
|
||||
@return {Promise<List<String>>}
|
||||
*/
|
||||
FS.prototype.readDir = function(dirname) {
|
||||
var that = this;
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var dirpath = that.resolve(dirname);
|
||||
var readDir = that.get('fsReadDir');
|
||||
|
||||
return readDir(dirpath);
|
||||
})
|
||||
.then(function(files) {
|
||||
return Immutable.List(files);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
List only files in a diretcory
|
||||
Directories ends with '/'
|
||||
|
||||
@param {String} dirname
|
||||
@return {Promise<List<String>>}
|
||||
*/
|
||||
FS.prototype.listFiles = function(dirname) {
|
||||
return this.readDir(dirname)
|
||||
.then(function(files) {
|
||||
return files.filterNot(pathIsFolder);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
List all files in a directory
|
||||
|
||||
@param {String} dirname
|
||||
@return {Promise<List<String>>}
|
||||
*/
|
||||
FS.prototype.listAllFiles = function(folder) {
|
||||
var that = this;
|
||||
folder = folder || '.';
|
||||
|
||||
return this.readDir(folder)
|
||||
.then(function(files) {
|
||||
return Promise.reduce(files, function(out, file) {
|
||||
var isDirectory = pathIsFolder(file);
|
||||
|
||||
if (!isDirectory) {
|
||||
return out.push(path.join(folder, file));
|
||||
}
|
||||
|
||||
return that.listAllFiles(path.join(folder, file))
|
||||
.then(function(inner) {
|
||||
return out.concat(inner);
|
||||
});
|
||||
}, Immutable.List());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Find a file in a folder (case incensitive)
|
||||
Return the found filename
|
||||
|
||||
@param {String} dirname
|
||||
@param {String} filename
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
FS.prototype.findFile = function(dirname, filename) {
|
||||
return this.listFiles(dirname)
|
||||
.then(function(files) {
|
||||
return files.find(function(file) {
|
||||
return (file.toLowerCase() == filename.toLowerCase());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Load a JSON file
|
||||
By default, fs only supports JSON
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Object>}
|
||||
*/
|
||||
FS.prototype.loadAsObject = function(filename) {
|
||||
var that = this;
|
||||
var fsLoadObject = this.get('fsLoadObject');
|
||||
|
||||
return this.exists(filename)
|
||||
.then(function(exists) {
|
||||
if (!exists) {
|
||||
var err = new Error('Module doesn\'t exist');
|
||||
err.code = 'MODULE_NOT_FOUND';
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (fsLoadObject) {
|
||||
return fsLoadObject(that.resolve(filename));
|
||||
} else {
|
||||
return that.readAsString(filename)
|
||||
.then(function(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Create a FS instance
|
||||
|
||||
@param {Object} def
|
||||
@return {FS}
|
||||
*/
|
||||
FS.create = function create(def) {
|
||||
return new FS(def);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new FS instance with a reduced scope
|
||||
|
||||
@param {FS} fs
|
||||
@param {String} scope
|
||||
@return {FS}
|
||||
*/
|
||||
FS.reduceScope = function reduceScope(fs, scope) {
|
||||
return fs.set('root', path.join(fs.getRoot(), scope));
|
||||
};
|
||||
|
||||
|
||||
// .readdir return files/folder as a list of string, folder ending with '/'
|
||||
function pathIsFolder(filename) {
|
||||
var lastChar = filename[filename.length - 1];
|
||||
return lastChar == '/' || lastChar == '\\';
|
||||
}
|
||||
|
||||
module.exports = FS;
|
||||
@@ -0,0 +1,109 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var error = require('../utils/error');
|
||||
var File = require('./file');
|
||||
var GlossaryEntry = require('./glossaryEntry');
|
||||
var parsers = require('../parsers');
|
||||
|
||||
var Glossary = Immutable.Record({
|
||||
file: File(),
|
||||
entries: Immutable.OrderedMap()
|
||||
});
|
||||
|
||||
Glossary.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Glossary.prototype.getEntries = function() {
|
||||
return this.get('entries');
|
||||
};
|
||||
|
||||
/**
|
||||
Return an entry by its name
|
||||
|
||||
@param {String} name
|
||||
@return {GlossaryEntry}
|
||||
*/
|
||||
Glossary.prototype.getEntry = function(name) {
|
||||
var entries = this.getEntries();
|
||||
var id = GlossaryEntry.nameToID(name);
|
||||
|
||||
return entries.get(id);
|
||||
};
|
||||
|
||||
/**
|
||||
Render glossary as text
|
||||
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
Glossary.prototype.toText = function(parser) {
|
||||
var file = this.getFile();
|
||||
var entries = this.getEntries();
|
||||
|
||||
parser = parser? parsers.getByExt(parser) : file.getParser();
|
||||
|
||||
if (!parser) {
|
||||
throw error.FileNotParsableError({
|
||||
filename: file.getPath()
|
||||
});
|
||||
}
|
||||
|
||||
return parser.renderGlossary(entries.toJS());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Add/Replace an entry to a glossary
|
||||
|
||||
@param {Glossary} glossary
|
||||
@param {GlossaryEntry} entry
|
||||
@return {Glossary}
|
||||
*/
|
||||
Glossary.addEntry = function addEntry(glossary, entry) {
|
||||
var id = entry.getID();
|
||||
var entries = glossary.getEntries();
|
||||
|
||||
entries = entries.set(id, entry);
|
||||
return glossary.set('entries', entries);
|
||||
};
|
||||
|
||||
/**
|
||||
Add/Replace an entry to a glossary by name/description
|
||||
|
||||
@param {Glossary} glossary
|
||||
@param {GlossaryEntry} entry
|
||||
@return {Glossary}
|
||||
*/
|
||||
Glossary.addEntryByName = function addEntryByName(glossary, name, description) {
|
||||
var entry = new GlossaryEntry({
|
||||
name: name,
|
||||
description: description
|
||||
});
|
||||
|
||||
return Glossary.addEntry(glossary, entry);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a glossary from a list of entries
|
||||
|
||||
@param {String} filename
|
||||
@param {Array|List} entries
|
||||
@return {Glossary}
|
||||
*/
|
||||
Glossary.createFromEntries = function createFromEntries(file, entries) {
|
||||
entries = entries.map(function(entry) {
|
||||
if (!(entry instanceof GlossaryEntry)) {
|
||||
entry = new GlossaryEntry(entry);
|
||||
}
|
||||
|
||||
return [entry.getID(), entry];
|
||||
});
|
||||
|
||||
return new Glossary({
|
||||
file: file,
|
||||
entries: Immutable.OrderedMap(entries)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = Glossary;
|
||||
@@ -0,0 +1,43 @@
|
||||
var Immutable = require('immutable');
|
||||
var slug = require('github-slugid');
|
||||
|
||||
/*
|
||||
A definition represents an entry in the glossary
|
||||
*/
|
||||
|
||||
var GlossaryEntry = Immutable.Record({
|
||||
name: String(),
|
||||
description: String()
|
||||
});
|
||||
|
||||
GlossaryEntry.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
GlossaryEntry.prototype.getDescription = function() {
|
||||
return this.get('description');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Get identifier for this entry
|
||||
|
||||
@retrun {Boolean}
|
||||
*/
|
||||
GlossaryEntry.prototype.getID = function() {
|
||||
return GlossaryEntry.nameToID(this.getName());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Normalize a glossary entry name into a unique id
|
||||
|
||||
@param {String}
|
||||
@return {String}
|
||||
*/
|
||||
GlossaryEntry.nameToID = function nameToID(name) {
|
||||
return slug(name);
|
||||
};
|
||||
|
||||
|
||||
module.exports = GlossaryEntry;
|
||||
@@ -0,0 +1,21 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Language = Immutable.Record({
|
||||
title: String(),
|
||||
path: String()
|
||||
});
|
||||
|
||||
Language.prototype.getTitle = function() {
|
||||
return this.get('title');
|
||||
};
|
||||
|
||||
Language.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
Language.prototype.getID = function() {
|
||||
return path.basename(this.getPath());
|
||||
};
|
||||
|
||||
module.exports = Language;
|
||||
@@ -0,0 +1,71 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
var Language = require('./language');
|
||||
|
||||
var Languages = Immutable.Record({
|
||||
file: File(),
|
||||
list: Immutable.OrderedMap()
|
||||
});
|
||||
|
||||
Languages.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Languages.prototype.getList = function() {
|
||||
return this.get('list');
|
||||
};
|
||||
|
||||
/**
|
||||
Get default languages
|
||||
|
||||
@return {Language}
|
||||
*/
|
||||
Languages.prototype.getDefaultLanguage = function() {
|
||||
return this.getList().first();
|
||||
};
|
||||
|
||||
/**
|
||||
Get a language by its ID
|
||||
|
||||
@param {String} lang
|
||||
@return {Language}
|
||||
*/
|
||||
Languages.prototype.getLanguage = function(lang) {
|
||||
return this.getList().get(lang);
|
||||
};
|
||||
|
||||
/**
|
||||
Return count of langs
|
||||
|
||||
@return {Number}
|
||||
*/
|
||||
Languages.prototype.getCount = function() {
|
||||
return this.getList().size;
|
||||
};
|
||||
|
||||
/**
|
||||
Create a languages list from a JS object
|
||||
|
||||
@param {File}
|
||||
@param {Array}
|
||||
@return {Language}
|
||||
*/
|
||||
Languages.createFromList = function(file, langs) {
|
||||
var list = Immutable.OrderedMap();
|
||||
|
||||
langs.forEach(function(lang) {
|
||||
lang = Language({
|
||||
title: lang.title,
|
||||
path: lang.ref
|
||||
});
|
||||
list = list.set(lang.getID(), lang);
|
||||
});
|
||||
|
||||
return Languages({
|
||||
file: file,
|
||||
list: list
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Languages;
|
||||
@@ -0,0 +1,93 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Book = require('./book');
|
||||
|
||||
var Output = Immutable.Record({
|
||||
book: Book(),
|
||||
|
||||
// Name of the generator being used
|
||||
generator: String(),
|
||||
|
||||
// Map of plugins to use (String -> Plugin)
|
||||
plugins: Immutable.OrderedMap(),
|
||||
|
||||
// Map pages to generation (String -> Page)
|
||||
pages: Immutable.OrderedMap(),
|
||||
|
||||
// List assets (String)
|
||||
assets: Immutable.List(),
|
||||
|
||||
// Option for the generation
|
||||
options: Immutable.Map(),
|
||||
|
||||
// Internal state for the generation
|
||||
state: Immutable.Map()
|
||||
});
|
||||
|
||||
Output.prototype.getBook = function() {
|
||||
return this.get('book');
|
||||
};
|
||||
|
||||
Output.prototype.getGenerator = function() {
|
||||
return this.get('generator');
|
||||
};
|
||||
|
||||
Output.prototype.getPlugins = function() {
|
||||
return this.get('plugins');
|
||||
};
|
||||
|
||||
Output.prototype.getPages = function() {
|
||||
return this.get('pages');
|
||||
};
|
||||
|
||||
Output.prototype.getOptions = function() {
|
||||
return this.get('options');
|
||||
};
|
||||
|
||||
Output.prototype.getAssets = function() {
|
||||
return this.get('assets');
|
||||
};
|
||||
|
||||
Output.prototype.getState = function() {
|
||||
return this.get('state');
|
||||
};
|
||||
|
||||
/**
|
||||
Get root folder for output
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Output.prototype.getRoot = function() {
|
||||
return this.getOptions().get('root');
|
||||
};
|
||||
|
||||
/**
|
||||
Update state of output
|
||||
|
||||
@param {Map} newState
|
||||
@return {Output}
|
||||
*/
|
||||
Output.prototype.setState = function(newState) {
|
||||
return this.set('state', newState);
|
||||
};
|
||||
|
||||
/**
|
||||
Update options
|
||||
|
||||
@param {Map} newOptions
|
||||
@return {Output}
|
||||
*/
|
||||
Output.prototype.setOptions = function(newOptions) {
|
||||
return this.set('options', newOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
Return logegr for this output (same as book)
|
||||
|
||||
@return {Logger}
|
||||
*/
|
||||
Output.prototype.getLogger = function() {
|
||||
return this.getBook().getLogger();
|
||||
};
|
||||
|
||||
module.exports = Output;
|
||||
@@ -0,0 +1,55 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
var Page = Immutable.Record({
|
||||
file: File(),
|
||||
|
||||
// Attributes extracted from the YAML header
|
||||
attributes: Immutable.Map(),
|
||||
|
||||
// Content of the page
|
||||
content: String(),
|
||||
|
||||
// Direction of the text
|
||||
dir: String('ltr')
|
||||
});
|
||||
|
||||
Page.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Page.prototype.getAttributes = function() {
|
||||
return this.get('attributes');
|
||||
};
|
||||
|
||||
Page.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
Page.prototype.getDir = function() {
|
||||
return this.get('dir');
|
||||
};
|
||||
|
||||
/**
|
||||
Return path of the page
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Page.prototype.getPath = function() {
|
||||
return this.getFile().getPath();
|
||||
};
|
||||
|
||||
/**
|
||||
Create a page for a file
|
||||
|
||||
@param {File} file
|
||||
@return {Page}
|
||||
*/
|
||||
Page.createForFile = function(file) {
|
||||
return new Page({
|
||||
file: file
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Page;
|
||||
@@ -0,0 +1,117 @@
|
||||
var Immutable = require('immutable');
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
var Parser = Immutable.Record({
|
||||
name: String(),
|
||||
|
||||
// List of extensions that can be processed using this parser
|
||||
extensions: Immutable.List(),
|
||||
|
||||
// Parsing functions
|
||||
readme: Function(),
|
||||
langs: Function(),
|
||||
summary: Function(),
|
||||
glossary: Function(),
|
||||
page: Function(),
|
||||
inline: Function()
|
||||
});
|
||||
|
||||
Parser.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
Parser.prototype.getExtensions = function() {
|
||||
return this.get('extensions');
|
||||
};
|
||||
|
||||
// PARSE
|
||||
|
||||
Parser.prototype.parseReadme = function(content) {
|
||||
var readme = this.get('readme');
|
||||
return Promise(readme(content));
|
||||
};
|
||||
|
||||
Parser.prototype.parseSummary = function(content) {
|
||||
var summary = this.get('summary');
|
||||
return Promise(summary(content));
|
||||
};
|
||||
|
||||
Parser.prototype.parseGlossary = function(content) {
|
||||
var glossary = this.get('glossary');
|
||||
return Promise(glossary(content));
|
||||
};
|
||||
|
||||
Parser.prototype.preparePage = function(content) {
|
||||
var page = this.get('page');
|
||||
if (!page.prepare) {
|
||||
return Promise(content);
|
||||
}
|
||||
|
||||
return Promise(page.prepare(content));
|
||||
};
|
||||
|
||||
Parser.prototype.parsePage = function(content) {
|
||||
var page = this.get('page');
|
||||
return Promise(page(content));
|
||||
};
|
||||
|
||||
Parser.prototype.parseLanguages = function(content) {
|
||||
var langs = this.get('langs');
|
||||
return Promise(langs(content));
|
||||
};
|
||||
|
||||
Parser.prototype.parseInline = function(content) {
|
||||
var inline = this.get('inline');
|
||||
return Promise(inline(content));
|
||||
};
|
||||
|
||||
// TO TEXT
|
||||
|
||||
Parser.prototype.renderLanguages = function(content) {
|
||||
var langs = this.get('langs');
|
||||
return Promise(langs.toText(content));
|
||||
};
|
||||
|
||||
Parser.prototype.renderSummary = function(content) {
|
||||
var summary = this.get('summary');
|
||||
return Promise(summary.toText(content));
|
||||
};
|
||||
|
||||
Parser.prototype.renderGlossary = function(content) {
|
||||
var glossary = this.get('glossary');
|
||||
return Promise(glossary.toText(content));
|
||||
};
|
||||
|
||||
/**
|
||||
Test if this parser matches an extension
|
||||
|
||||
@param {String} ext
|
||||
@return {Boolean}
|
||||
*/
|
||||
Parser.prototype.matchExtension = function(ext) {
|
||||
var exts = this.getExtensions();
|
||||
return exts.includes(ext.toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new parser using a module (gitbook-markdown, etc)
|
||||
|
||||
@param {String} name
|
||||
@param {Array<String>} extensions
|
||||
@param {Object} module
|
||||
@return {Parser}
|
||||
*/
|
||||
Parser.create = function(name, extensions, module) {
|
||||
return new Parser({
|
||||
name: name,
|
||||
extensions: Immutable.List(extensions),
|
||||
readme: module.readme,
|
||||
langs: module.langs,
|
||||
summary: module.summary,
|
||||
glossary: module.glossary,
|
||||
page: module.page,
|
||||
inline: module.inline
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Parser;
|
||||
@@ -0,0 +1,165 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var TemplateBlock = require('./templateBlock');
|
||||
var PREFIX = require('../constants/pluginPrefix');
|
||||
var DEFAULT_VERSION = '*';
|
||||
|
||||
var Plugin = Immutable.Record({
|
||||
name: String(),
|
||||
|
||||
// Requirement version (ex: ">1.0.0")
|
||||
version: String(DEFAULT_VERSION),
|
||||
|
||||
// Path to load this plugin
|
||||
path: String(),
|
||||
|
||||
// Depth of this plugin in the dependency tree
|
||||
depth: Number(0),
|
||||
|
||||
// Content of the "package.json"
|
||||
package: Immutable.Map(),
|
||||
|
||||
// Content of the package itself
|
||||
content: Immutable.Map()
|
||||
}, 'Plugin');
|
||||
|
||||
Plugin.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
Plugin.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
Plugin.prototype.getVersion = function() {
|
||||
return this.get('version');
|
||||
};
|
||||
|
||||
Plugin.prototype.getPackage = function() {
|
||||
return this.get('package');
|
||||
};
|
||||
|
||||
Plugin.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
Plugin.prototype.getDepth = function() {
|
||||
return this.get('depth');
|
||||
};
|
||||
|
||||
/**
|
||||
Return the ID on NPM for this plugin
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Plugin.prototype.getNpmID = function() {
|
||||
return Plugin.nameToNpmID(this.getName());
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a plugin is loaded
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Plugin.prototype.isLoaded = function() {
|
||||
return Boolean(this.getPackage().size > 0);
|
||||
};
|
||||
|
||||
/**
|
||||
Return map of hooks
|
||||
@return {Map<String:Function>}
|
||||
*/
|
||||
Plugin.prototype.getHooks = function() {
|
||||
return this.getContent().get('hooks') || Immutable.Map();
|
||||
};
|
||||
|
||||
/**
|
||||
Return infos about resources for a specific type
|
||||
|
||||
@param {String} type
|
||||
@return {Map<String:Mixed>}
|
||||
*/
|
||||
Plugin.prototype.getResources = function(type) {
|
||||
if (type != 'website' && type != 'ebook') {
|
||||
throw new Error('Invalid assets type ' + type);
|
||||
}
|
||||
|
||||
var content = this.getContent();
|
||||
return (content.get(type)
|
||||
|| (type == 'website'? content.get('book') : null)
|
||||
|| Immutable.Map());
|
||||
};
|
||||
|
||||
/**
|
||||
Return map of filters
|
||||
@return {Map<String:Function>}
|
||||
*/
|
||||
Plugin.prototype.getFilters = function() {
|
||||
return this.getContent().get('filters');
|
||||
};
|
||||
|
||||
/**
|
||||
Return map of blocks
|
||||
@return {Map<String:TemplateBlock>}
|
||||
*/
|
||||
Plugin.prototype.getBlocks = function() {
|
||||
var blocks = this.getContent().get('blocks');
|
||||
blocks = blocks || Immutable.Map();
|
||||
|
||||
return blocks
|
||||
.map(function(block, blockName) {
|
||||
return TemplateBlock.create(blockName, block);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Return a specific hook
|
||||
|
||||
@param {String} name
|
||||
@return {Function|undefined}
|
||||
*/
|
||||
Plugin.prototype.getHook = function(name) {
|
||||
return this.getHooks().get(name);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a plugin from a string
|
||||
|
||||
@param {String}
|
||||
@return {Plugin}
|
||||
*/
|
||||
Plugin.createFromString = function(s) {
|
||||
var parts = s.split('@');
|
||||
var name = parts[0];
|
||||
var version = parts.slice(1).join('@');
|
||||
|
||||
return new Plugin({
|
||||
name: name,
|
||||
version: version || DEFAULT_VERSION
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Create a plugin from a dependency
|
||||
|
||||
@param {PluginDependency}
|
||||
@return {Plugin}
|
||||
*/
|
||||
Plugin.createFromDep = function(dep) {
|
||||
return new Plugin({
|
||||
name: dep.getName(),
|
||||
version: dep.getVersion()
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Return NPM id for a plugin name
|
||||
|
||||
@param {String}
|
||||
@return {String}
|
||||
*/
|
||||
Plugin.nameToNpmID = function(s) {
|
||||
return PREFIX + s;
|
||||
};
|
||||
|
||||
module.exports = Plugin;
|
||||
@@ -0,0 +1,115 @@
|
||||
var is = require('is');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var DEFAULT_VERSION = '*';
|
||||
|
||||
/*
|
||||
PluginDependency represents the informations about a plugin
|
||||
stored in config.plugins
|
||||
*/
|
||||
var PluginDependency = Immutable.Record({
|
||||
name: String(),
|
||||
|
||||
// Requirement version (ex: ">1.0.0")
|
||||
version: String(DEFAULT_VERSION),
|
||||
|
||||
// Is this plugin enabled or disabled?
|
||||
enabled: Boolean(true)
|
||||
}, 'PluginDependency');
|
||||
|
||||
PluginDependency.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
PluginDependency.prototype.getVersion = function() {
|
||||
return this.get('version');
|
||||
};
|
||||
|
||||
PluginDependency.prototype.isEnabled = function() {
|
||||
return this.get('enabled');
|
||||
};
|
||||
|
||||
/**
|
||||
Create a plugin from a string
|
||||
|
||||
@param {String}
|
||||
@return {Plugin|undefined}
|
||||
*/
|
||||
PluginDependency.createFromString = function(s) {
|
||||
var parts = s.split('@');
|
||||
var name = parts[0];
|
||||
var version = parts.slice(1).join('@');
|
||||
var enabled = true;
|
||||
|
||||
if (name[0] === '-') {
|
||||
enabled = false;
|
||||
name = name.slice(1);
|
||||
}
|
||||
|
||||
return new PluginDependency({
|
||||
name: name,
|
||||
version: version || DEFAULT_VERSION,
|
||||
enabled: enabled
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Create a PluginDependency from a string
|
||||
|
||||
@param {String}
|
||||
@return {List<PluginDependency>}
|
||||
*/
|
||||
PluginDependency.listFromString = function(s) {
|
||||
var parts = s.split(',');
|
||||
return PluginDependency.listFromArray(parts);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a PluginDependency from an array
|
||||
|
||||
@param {Array}
|
||||
@return {List<PluginDependency>}
|
||||
*/
|
||||
PluginDependency.listFromArray = function(arr) {
|
||||
return Immutable.List(arr)
|
||||
.map(function(entry) {
|
||||
if (is.string(entry)) {
|
||||
return PluginDependency.createFromString(entry);
|
||||
} else {
|
||||
return PluginDependency({
|
||||
name: entry.name,
|
||||
version: entry.version
|
||||
});
|
||||
}
|
||||
})
|
||||
.filter(function(dep) {
|
||||
return Boolean(dep.getName());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Export plugin dependencies as an array
|
||||
|
||||
@param {List<PluginDependency>}
|
||||
@return {Array<String>}
|
||||
*/
|
||||
PluginDependency.listToArray = function(arr) {
|
||||
return arr
|
||||
.map(function(dep) {
|
||||
var result;
|
||||
|
||||
if (dep.isEnabled()) {
|
||||
result += '-';
|
||||
}
|
||||
|
||||
result += dep.getName();
|
||||
if (dep.getVersion() !== DEFAULT_VERSION) {
|
||||
result += '@' + dep.getVersion();
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.toJS();
|
||||
};
|
||||
|
||||
module.exports = PluginDependency;
|
||||
@@ -0,0 +1,40 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
var Readme = Immutable.Record({
|
||||
file: File(),
|
||||
title: String(),
|
||||
description: String()
|
||||
});
|
||||
|
||||
Readme.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Readme.prototype.getTitle = function() {
|
||||
return this.get('title');
|
||||
};
|
||||
|
||||
Readme.prototype.getDescription = function() {
|
||||
return this.get('description');
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new readme
|
||||
|
||||
@param {File} file
|
||||
@param {Object} def
|
||||
@return {Readme}
|
||||
*/
|
||||
Readme.create = function(file, def) {
|
||||
def = def || {};
|
||||
|
||||
return new Readme({
|
||||
file: file,
|
||||
title: def.title || '',
|
||||
description: def.description || ''
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Readme;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user