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