mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-03 12:10:34 +00:00
Merge pull request #1543 from GitbookIO/dream
React for rendering website with plugins
This commit is contained in:
@@ -1,15 +1,4 @@
|
||||
{
|
||||
"rules": {
|
||||
"indent": [ 2, 4 ],
|
||||
"quotes": [ 2, "single" ],
|
||||
"linebreak-style": [ 2, "unix" ],
|
||||
"semi": [ 2, "always" ],
|
||||
"no-unused-vars": [ 2, {
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
} ],
|
||||
"spaced-comment": [ 2, "always" ]
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
@@ -18,5 +7,5 @@
|
||||
"globals": {
|
||||
"expect": true
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
"extends": "gitbook"
|
||||
}
|
||||
|
||||
+8
-2
@@ -22,14 +22,20 @@ build/Release
|
||||
# Dependency directory
|
||||
# Deployed apps should consider commenting this line out:
|
||||
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||
/node_modules
|
||||
node_modules
|
||||
|
||||
# yarn
|
||||
yarn.lock
|
||||
|
||||
# vim swapfile
|
||||
*.swp
|
||||
|
||||
# GitBook with babel
|
||||
/packages/gitbook-core/lib
|
||||
/packages/gitbook-plugin/lib
|
||||
|
||||
# Output of documentation
|
||||
_book
|
||||
|
||||
book.pdf
|
||||
book.epub
|
||||
book.mobi
|
||||
|
||||
+11
@@ -2,6 +2,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 4.0.0
|
||||
- **Breaking Changes:** Most plugins and themes should be incompatible with this version
|
||||
- GitBook rendering is now done using React, which brings better APIs for plugins and a more interactive website.
|
||||
- *Performances*: Installation of plugins is much faster thanks to `ied`
|
||||
- *Performances*: Git conrefs are faster over large books
|
||||
- *Website Feature:* new default plugin `copy-code` to copy code blocks content in one click
|
||||
- *Website Feature:* new default plugin `heading-anchors` to have clickable headings
|
||||
- *Website Feature:* `fontsettings` is now only controlling the font size, and can work on multiple themes
|
||||
- *Plugin API*: hook `page` can no longer modify the content, only the page's attributes
|
||||
- *Plugin API*: plugins can no longer export resources, instead use the `_assets` folder
|
||||
|
||||
## 3.2.2
|
||||
- Fix catching parsing errors leading to possible missing pages in generated book
|
||||
- Rollback markdown parser to `kramed` until `markup-it` is stable enough
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
# Contributing
|
||||
|
||||
Want to contribute to GitBook? That would be awesome!
|
||||
|
||||
- [Send feedback](#send-feedback)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Asking Questions](#asking-questions)
|
||||
- [Submitting Pull Requests](#submitting-pull-requests)
|
||||
- [Running Tests](#running-tests)
|
||||
|
||||
## Send feedback
|
||||
|
||||
We’ve done our best to test but your feedback is really important. If you encounter problems or have feedback about GitBook products (GitBook.com, GitBook Editor, or the Toolchain), please log an issue in the [GitbookIO/feedback](https://github.com/GitbookIO/feedback/issues) repository.
|
||||
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
If you run into any weird behavior while using GitBook, feel free to open a new issue in this repository! To be most helpful, please include the steps to reproduce the bug as best you can, including the output of command `gitbook -V`.
|
||||
|
||||
## Asking Questions
|
||||
|
||||
Questions are very welcome :smile:! Previous questions that folks have asked are tagged with a [`question`](https://github.com/GitBookIO/gitbook/issues?q=is%3Aissue+is%3Aclosed+label%3Aquestion) label, so before opening a new issue double-check that someone hasn't asked it before. But if you don't see anything, or if you're not sure if it's the same, err on the side of asking!
|
||||
|
||||
We've also got a [GitBook Community Slack](https://slack.gitbook.com/) where you can ask questions and get answers from other people using GitBook.
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
All pull requests are super welcomed and greatly appreciated! Easy issues are marked with an [`easy-one`](https://github.com/GitBookIO/gitbook/issues?q=is%3Aopen+is%3Aissue+label%3Aeasy-one) label if you're looking for a simple place to get familiar with the code base.
|
||||
|
||||
Please include tests and docs with every pull request!
|
||||
|
||||
## Running Tests
|
||||
|
||||
To run the examples, you need to have the GitBook repository cloned to your computer. After that, you need to `cd` into the directory where you cloned it, and install the dependencies from `npm`.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
Then you'll need to bootstrap it:
|
||||
|
||||
```
|
||||
npm run bootstrap
|
||||
```
|
||||
|
||||
Which will also compile the source files. Then run the tests with:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
@@ -5,11 +5,8 @@ module.exports = {
|
||||
root: './docs',
|
||||
title: 'GitBook Toolchain Documentation',
|
||||
|
||||
// Enforce use of GitBook v3
|
||||
gitbook: '3.1.1',
|
||||
|
||||
// Use the "official" theme
|
||||
plugins: ['theme-official@2.1.1', '-sharing', '-fontsettings', 'sitemap'],
|
||||
plugins: ['sitemap'],
|
||||
|
||||
variables: {
|
||||
version: pkg.version
|
||||
|
||||
@@ -42,6 +42,13 @@
|
||||
* [Test your plugin](plugins/testing.md)
|
||||
* [Theming](themes/README.md)
|
||||
|
||||
### Plugin Development
|
||||
|
||||
* [Introduction](./api/README.md)
|
||||
* [Node](./api/node.md)
|
||||
* [Connect to the context](./api/connect.md)
|
||||
* [Components](./api/components.md)
|
||||
|
||||
--
|
||||
|
||||
* [FAQ](faq.md)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{% extends template.self %}
|
||||
|
||||
{% block header_nav %}
|
||||
<a href="https://github.com/GitbookIO/gitbook/blob/master/docs/{{ file.path }}" target="_blank" class="btn btn-link pull-right hidden-xs">
|
||||
<i class="octicon octicon-mark-github"></i> Edit on GitHub
|
||||
</a>
|
||||
<a href="{{ "faq.md"|resolveFile }}" class="btn btn-link pull-right hidden-xs">
|
||||
F.A.Q
|
||||
</a>
|
||||
<a href="https://github.com/GitbookIO/gitbook/blob/master/CHANGES.md" target="_blank" class="btn btn-link pull-right hidden-xs">
|
||||
{{ book.version }}
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
{{ super() }}
|
||||
<hr>
|
||||
<div class="btn-group btn-group-justified">
|
||||
{% if page.previous and page.previous.path %}
|
||||
<a class="btn" href="{{ page.previous.path|resolveFile }}"><b>Previous:</b> {{ page.previous.title }}</a>
|
||||
{% endif %}
|
||||
{% if page.next and page.next.path %}
|
||||
<a class="btn" href="{{ page.next.path|resolveFile }}"><b>Next:</b> {{ page.next.title }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Plugin Architecture
|
||||
|
||||
A GitBook plugin is a NPM package that follows a defined convention.
|
||||
|
||||
`gitbook-plugin` is a command line utility to help you create, test and release plugins.
|
||||
|
||||
### Bootstrap your first plugin
|
||||
|
||||
Install `gitbook-plugin` from NPM:
|
||||
|
||||
```
|
||||
$ npm install gitbook-plugin -g
|
||||
```
|
||||
|
||||
Then create your plugin:
|
||||
|
||||
```
|
||||
$ gitbook-plugin create
|
||||
```
|
||||
|
||||
You will be asked for a plugin name, and a few other things to complete the creation process.
|
||||
|
||||
|
||||
### Publish your plugin
|
||||
@@ -0,0 +1,86 @@
|
||||
# Components
|
||||
|
||||
|
||||
## Injection
|
||||
|
||||
Plugins can inject components by registering React components to some roles.
|
||||
|
||||
#### Register a components
|
||||
|
||||
During the initialization phase of your plugin, dispatch the `GitBook.registerComponent` action:
|
||||
|
||||
```js
|
||||
dispatch(GitBook.registerComponent(MyCustomButton, { role: 'toolbar:buttons:left' }));
|
||||
```
|
||||
|
||||
#### Roles
|
||||
|
||||
Custom roles can be use for interopability with other plugins, but GitBook and the default theme set a convention for common roles:
|
||||
|
||||
| Role | Description | Props |
|
||||
| ---- | ----------- | ----- |
|
||||
| `page:container` | DIV container for the page's content | `{ page: Page }` |
|
||||
| `summary:container` | DIV container for the whole summary | `{ summary: Summary }` |
|
||||
| `summary:parts` | DIV container for summary's parts | `{ parts: List<SummaryPart> }` |
|
||||
| `summary:part` | DIV for a specific part | `{ part: SummaryPart }` |
|
||||
| `summary:articles` | UL container for a part's articles | `{ articles: List<SummaryArticle> }` |
|
||||
| `summary:article` | LI for a specific article | `{ article: SummaryArticle }` |
|
||||
|
||||
## Default Components
|
||||
|
||||
#### `GitBook.Head`
|
||||
|
||||
Extends the meta tags of the page. This is an alias for [react-helmet](https://github.com/nfl/react-helmet).
|
||||
|
||||
```js
|
||||
<GitBook.Head
|
||||
title="My page"
|
||||
/>
|
||||
```
|
||||
|
||||
#### `GitBook.ImportCSS`
|
||||
|
||||
Import a CSS file by resolving the path correctly according to the current page:
|
||||
|
||||
```js
|
||||
<GitBook.ImportCSS href="myfile.css" />
|
||||
```
|
||||
|
||||
#### `GitBook.ImportJS`
|
||||
|
||||
Import a JS file by resolving the path correctly according to the current page:
|
||||
|
||||
```js
|
||||
<GitBook.ImportJS src="mylib.js" />
|
||||
```
|
||||
|
||||
#### `GitBook.InjectedComponent`
|
||||
|
||||
Inject a component matching a specific role:
|
||||
|
||||
```js
|
||||
<GitBook.InjectedComponent matching={{ role: 'mycustomrole' }} props={{ someProp: 1 }}>
|
||||
<b>Inner content</b>
|
||||
</GitBook.InjectedComponent>
|
||||
```
|
||||
|
||||
#### `GitBook.InjectedComponentSet`
|
||||
|
||||
Same API as `InjectedComponentSet` but render the matching components in chain instead of composed:
|
||||
|
||||
```js
|
||||
<GitBook.InjectedComponentSet matching={{ role: 'mytoolbar' }} />
|
||||
```
|
||||
|
||||
**Warning:** Children are discarded.
|
||||
|
||||
#### `GitBook.FlexLayout` and `GitBook.FlexBox`
|
||||
|
||||
A simple wrapper that provides a Flexbox layout with the given direction and style. Any additional props you set on the Flexbox are rendered.
|
||||
|
||||
```js
|
||||
<GitBook.FlexLayout column>
|
||||
<GitBook.FlexBox>First column</GitBook.FlexBox>
|
||||
<GitBook.FlexBox>Second column</GitBook.FlexBox>
|
||||
</GitBook.FlexLayout>
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
# Connect to the context
|
||||
|
||||
`GitBook.connect(Component, [mapStateToProps], [mapActionsToProps])` connects a react component to the GitBook context.
|
||||
|
||||
It does not modify the component class passed to it.
|
||||
Instead, it returns a new, connected component class, for you to use.
|
||||
|
||||
### `mapStateToProps(state, [ownProps]): stateProps`
|
||||
|
||||
If specified, the component will subscribe to GitBook store updates. Any time it updates, `mapStateToProps` will be called. Its result must be a plain object, and it will be merged into the component’s props.
|
||||
|
||||
If you omit it, the component will not be subscribed to the GitBook store. If `ownProps` is specified as a second argument, its value will be the props passed to your component, and `mapStateToProps` will be additionally re-invoked whenever the component receives new props (e.g. if props received from a parent component have shallowly changed, and you use the `ownProps` argument, `mapStateToProps` is re-evaluated).
|
||||
|
||||
For example to render the title of the current page:
|
||||
|
||||
```js
|
||||
const GitBook = require('gitbook-core');
|
||||
|
||||
let PageTitle = React.createClass({
|
||||
render() {
|
||||
const { page } = this.props;
|
||||
return <h1>{page.title}</h1>;
|
||||
}
|
||||
});
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { page: state.page };
|
||||
}
|
||||
|
||||
PageTitle = GitBook.connect(PageTitle, mapStateToProps);
|
||||
```
|
||||
|
||||
### `mapActionsToProps(actions, [dispatch])`
|
||||
@@ -0,0 +1,44 @@
|
||||
# Internationalize your plugin
|
||||
|
||||
GitBook has built-in support for internationalization. Plugins can register new languages and provide the right messages for different languages.
|
||||
|
||||
### Register locale and messages
|
||||
|
||||
The first step is to register messages for a language:
|
||||
|
||||
```js
|
||||
module.exports = GitBook.createPlugin({
|
||||
init: (dispatch, getState, { I18n }) => {
|
||||
dispatch(I18n.registerLocale('en-US', {
|
||||
MY_PLUGIN_MESSAGE: 'Hello World'
|
||||
}));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Render a message in a component
|
||||
|
||||
`GitBook.connect` adds a `i18n` prop to access the internationalization:
|
||||
|
||||
```js
|
||||
const GitBook = require('gitbook-core');
|
||||
const { React } = GitBook;
|
||||
|
||||
const MyButton = React.createClass({
|
||||
propTypes: {
|
||||
i18n: GitBook.Shapes.i18n
|
||||
},
|
||||
|
||||
render() {
|
||||
const { i18n } = this.props;
|
||||
|
||||
return (
|
||||
<GitBook.Button>
|
||||
{i18.t('MY_PLUGIN_MESSAGE')}
|
||||
</GitBook.Button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = GitBook.connect(MyButton);
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
# Navigation
|
||||
|
||||
### Listen to url change
|
||||
|
||||
Listen for changes to the current location:
|
||||
|
||||
```js
|
||||
const onLocationChanged = (location) => {
|
||||
console.log(location.pathname);
|
||||
console.log(location.query);
|
||||
console.log(location.hash);
|
||||
};
|
||||
|
||||
module.exports = GitBook.createPlugin({
|
||||
init: (dispatch, getState, { Navigation }) => {
|
||||
dispatch(Navigation.listen(onLocationChanged));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The `onLocationChanged` will be triggered for initial state.
|
||||
|
||||
### Changing the url
|
||||
@@ -0,0 +1,97 @@
|
||||
# Node APIs
|
||||
|
||||
GitBooks provides different Node APIs and contexts to plugins. These APIs can vary according to the GitBook version being used, your plugin should specify the `engines.gitbook` field in `package.json` accordingly.
|
||||
|
||||
#### Book instance
|
||||
|
||||
The `Book` interface is the central point of GitBook, it centralize all access read methods.
|
||||
|
||||
```js
|
||||
// Read configuration from book.json
|
||||
var value = book.config.get('title', 'Default Value');
|
||||
|
||||
// Resolve a filename to an absolute path
|
||||
var filepath = book.resolve('README.md');
|
||||
|
||||
// Render an inline markup string
|
||||
book.renderInline('markdown', 'This is **Markdown**')
|
||||
.then(function(str) { ... })
|
||||
|
||||
// Render a markup string (block mode)
|
||||
book.renderBlock('markdown', '* This is **Markdown**')
|
||||
.then(function(str) { ... })
|
||||
```
|
||||
|
||||
#### Output instance
|
||||
|
||||
The `Output` class represent the output/write process.
|
||||
|
||||
```js
|
||||
// Return root folder for the output
|
||||
var root = output.root();
|
||||
|
||||
// Resolve a file in the output folder
|
||||
var filepath = output.resolve('myimage.png');
|
||||
|
||||
// Convert a filename to an URL (returns a path to an html file)
|
||||
var fileurl = output.toURL('mychapter/README.md');
|
||||
|
||||
// Write a file in the output folder
|
||||
output.writeFile('hello.txt', 'Hello World')
|
||||
.then(function() { ... });
|
||||
|
||||
// Copy a file to the output folder
|
||||
output.copyFile('./myfile.jpg', 'cover.jpg')
|
||||
.then(function() { ... });
|
||||
|
||||
// Verify that a file exists
|
||||
output.hasFile('hello.txt')
|
||||
.then(function(exists) { ... });
|
||||
```
|
||||
|
||||
#### Page instance
|
||||
|
||||
A page instance represent the current parsed page.
|
||||
|
||||
```js
|
||||
// Title of the page (from SUMMARY)
|
||||
page.title
|
||||
|
||||
// Content of the page (Markdown/Asciidoc/HTML according to the stage)
|
||||
page.content
|
||||
|
||||
// Relative path in the book
|
||||
page.path
|
||||
|
||||
// Absolute path to the file
|
||||
page.rawPath
|
||||
|
||||
// Type of parser used for this file
|
||||
page.type ('markdown' or 'asciidoc')
|
||||
```
|
||||
|
||||
#### Context for Blocks and Filters
|
||||
|
||||
Blocks and filters have access to the same context, this context is bind to the template engine execution:
|
||||
|
||||
```js
|
||||
{
|
||||
// Current templating syntax
|
||||
"ctx": {
|
||||
// For example, after a {% set message = "hello" %}
|
||||
"message": "hello"
|
||||
},
|
||||
|
||||
// Book instance
|
||||
"book" <Book>,
|
||||
|
||||
// Output instance
|
||||
"output": <Output>
|
||||
}
|
||||
```
|
||||
|
||||
For example a filter or block function can access the current book using: `this.book`.
|
||||
|
||||
#### Context for Hooks
|
||||
|
||||
Hooks only have access to the `<Book>` instance using `this.book`.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"lerna": "2.0.0-beta.29",
|
||||
"version": "4.0.0"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
var decodeConfig = require('./decodeConfig');
|
||||
|
||||
/**
|
||||
Decode changes from a JS API to a output object.
|
||||
Only the configuration can be edited by plugin's hooks
|
||||
|
||||
@param {Output} output
|
||||
@param {Object} result: result from API
|
||||
@return {Output}
|
||||
*/
|
||||
function decodeGlobal(output, result) {
|
||||
var book = output.getBook();
|
||||
var config = book.getConfig();
|
||||
|
||||
// Update config
|
||||
config = decodeConfig(config, result.config);
|
||||
book = book.set('config', config);
|
||||
|
||||
return output.set('book', book);
|
||||
}
|
||||
|
||||
module.exports = decodeGlobal;
|
||||
@@ -1,44 +0,0 @@
|
||||
var deprecate = require('./deprecate');
|
||||
|
||||
/**
|
||||
Decode changes from a JS API to a page object.
|
||||
Only the content can be edited by plugin's hooks.
|
||||
|
||||
@param {Output} output
|
||||
@param {Page} page: page instance to edit
|
||||
@param {Object} result: result from API
|
||||
@return {Page}
|
||||
*/
|
||||
function decodePage(output, page, result) {
|
||||
var originalContent = page.getContent();
|
||||
|
||||
// No returned value
|
||||
// Existing content will be used
|
||||
if (!result) {
|
||||
return page;
|
||||
}
|
||||
|
||||
deprecate.disable('page.sections');
|
||||
|
||||
// GitBook 3
|
||||
// Use returned page.content if different from original content
|
||||
if (result.content != originalContent) {
|
||||
page = page.set('content', result.content);
|
||||
}
|
||||
|
||||
// GitBook 2 compatibility
|
||||
// Finally, use page.sections
|
||||
else if (result.sections) {
|
||||
page = page.set('content',
|
||||
result.sections.map(function(section) {
|
||||
return section.content;
|
||||
}).join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
deprecate.enable('page.sections');
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
module.exports = decodePage;
|
||||
@@ -1,122 +0,0 @@
|
||||
var is = require('is');
|
||||
var objectPath = require('object-path');
|
||||
|
||||
var logged = {};
|
||||
var disabled = {};
|
||||
|
||||
/**
|
||||
Log a deprecated notice
|
||||
|
||||
@param {Book|Output} book
|
||||
@param {String} key
|
||||
@param {String} message
|
||||
*/
|
||||
function logNotice(book, key, message) {
|
||||
if (logged[key] || disabled[key]) return;
|
||||
|
||||
logged[key] = true;
|
||||
|
||||
var logger = book.getLogger();
|
||||
logger.warn.ln(message);
|
||||
}
|
||||
|
||||
/**
|
||||
Deprecate a function
|
||||
|
||||
@param {Book|Output} book
|
||||
@param {String} key: unique identitifer for the deprecated
|
||||
@param {Function} fn
|
||||
@param {String} msg: message to print when called
|
||||
@return {Function}
|
||||
*/
|
||||
function deprecateMethod(book, key, fn, msg) {
|
||||
return function() {
|
||||
logNotice(book, key, msg);
|
||||
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Deprecate a property of an object
|
||||
|
||||
@param {Book|Output} book
|
||||
@param {String} key: unique identitifer for the deprecated
|
||||
@param {Object} instance
|
||||
@param {String|Function} property
|
||||
@param {String} msg: message to print when called
|
||||
@return {Function}
|
||||
*/
|
||||
function deprecateField(book, key, instance, property, value, msg) {
|
||||
var store = undefined;
|
||||
|
||||
var prepare = function() {
|
||||
if (!is.undefined(store)) return;
|
||||
|
||||
if (is.fn(value)) store = value();
|
||||
else store = value;
|
||||
};
|
||||
|
||||
var getter = function(){
|
||||
prepare();
|
||||
|
||||
logNotice(book, key, msg);
|
||||
return store;
|
||||
};
|
||||
var setter = function(v) {
|
||||
prepare();
|
||||
|
||||
logNotice(book, key, msg);
|
||||
store = v;
|
||||
return store;
|
||||
};
|
||||
|
||||
Object.defineProperty(instance, property, {
|
||||
get: getter,
|
||||
set: setter,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Enable a deprecation
|
||||
|
||||
@param {String} key: unique identitifer
|
||||
*/
|
||||
function enableDeprecation(key) {
|
||||
disabled[key] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
Disable a deprecation
|
||||
|
||||
@param {String} key: unique identitifer
|
||||
*/
|
||||
function disableDeprecation(key) {
|
||||
disabled[key] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Deprecate a method in favor of another one
|
||||
|
||||
@param {Book} book
|
||||
@param {String} key
|
||||
@param {Object} instance
|
||||
@param {String} oldName
|
||||
@param {String} newName
|
||||
*/
|
||||
function deprecateRenamedMethod(book, key, instance, oldName, newName, msg) {
|
||||
msg = msg || ('"' + oldName + '" is deprecated, use "' + newName + '()" instead');
|
||||
var fn = objectPath.get(instance, newName);
|
||||
|
||||
instance[oldName] = deprecateMethod(book, key, fn, msg);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
method: deprecateMethod,
|
||||
renamedMethod: deprecateRenamedMethod,
|
||||
field: deprecateField,
|
||||
enable: enableDeprecation,
|
||||
disable: disableDeprecation
|
||||
};
|
||||
@@ -1,257 +0,0 @@
|
||||
var path = require('path');
|
||||
var Promise = require('../utils/promise');
|
||||
var PathUtils = require('../utils/path');
|
||||
var fs = require('../utils/fs');
|
||||
|
||||
var Plugins = require('../plugins');
|
||||
var deprecate = require('./deprecate');
|
||||
var fileToURL = require('../output/helper/fileToURL');
|
||||
var defaultBlocks = require('../constants/defaultBlocks');
|
||||
var gitbook = require('../gitbook');
|
||||
var 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
|
||||
It's the context for page's hook, etc
|
||||
|
||||
@param {Output} output
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeGlobal(output) {
|
||||
var book = output.getBook();
|
||||
var bookFS = book.getContentFS();
|
||||
var logger = output.getLogger();
|
||||
var outputFolder = output.getRoot();
|
||||
var plugins = output.getPlugins();
|
||||
var blocks = Plugins.listBlocks(plugins);
|
||||
|
||||
var result = {
|
||||
log: logger,
|
||||
config: encodeConfig(output, book.getConfig()),
|
||||
summary: encodeSummary(output, book.getSummary()),
|
||||
|
||||
/**
|
||||
Check if the book is a multilingual book
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
isMultilingual: function() {
|
||||
return book.isMultilingual();
|
||||
},
|
||||
|
||||
/**
|
||||
Check if the book is a language book for a multilingual book
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
isLanguageBook: function() {
|
||||
return book.isLanguageBook();
|
||||
},
|
||||
|
||||
/**
|
||||
Read a file from the book
|
||||
|
||||
@param {String} fileName
|
||||
@return {Promise<Buffer>}
|
||||
*/
|
||||
readFile: function(fileName) {
|
||||
return bookFS.read(fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
Read a file from the book as a string
|
||||
|
||||
@param {String} fileName
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
readFileAsString: function(fileName) {
|
||||
return bookFS.readAsString(fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
Resolve a file from the book root
|
||||
|
||||
@param {String} fileName
|
||||
@return {String}
|
||||
*/
|
||||
resolve: function(fileName) {
|
||||
return path.resolve(book.getContentRoot(), fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
@param {String} name
|
||||
@param {Object} blockData
|
||||
@return {Promise|Object}
|
||||
*/
|
||||
applyBlock: function(name, blockData) {
|
||||
var block = blocks.get(name) || defaultBlocks.get(name);
|
||||
return Promise(block.applyBlock(blockData, result));
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
/**
|
||||
Name of the generator being used
|
||||
{String}
|
||||
*/
|
||||
name: output.getGenerator(),
|
||||
|
||||
/**
|
||||
Return absolute path to the root folder of output
|
||||
@return {String}
|
||||
*/
|
||||
root: function() {
|
||||
return outputFolder;
|
||||
},
|
||||
|
||||
/**
|
||||
Resolve a file from the output root
|
||||
|
||||
@param {String} fileName
|
||||
@return {String}
|
||||
*/
|
||||
resolve: function(fileName) {
|
||||
return path.resolve(outputFolder, fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
Convert a filepath into an url
|
||||
@return {String}
|
||||
*/
|
||||
toURL: function(filePath) {
|
||||
return fileToURL(output, filePath);
|
||||
},
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
@param {String} fileName
|
||||
@param {Buffer} content
|
||||
@return {Promise}
|
||||
*/
|
||||
writeFile: function(fileName, content) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
|
||||
|
||||
return fs.ensureFile(filePath)
|
||||
.then(function() {
|
||||
return fs.writeFile(filePath, content);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
gitbook: {
|
||||
version: gitbook.version
|
||||
}
|
||||
};
|
||||
|
||||
// Deprecated properties
|
||||
|
||||
deprecate.renamedMethod(output, 'this.isSubBook', result, 'isSubBook', 'isLanguageBook');
|
||||
deprecate.renamedMethod(output, 'this.contentLink', result, 'contentLink', 'output.toURL');
|
||||
|
||||
deprecate.field(output, 'this.generator', result, 'generator',
|
||||
output.getGenerator(), '"this.generator" property is deprecated, use "this.output.name" instead');
|
||||
|
||||
deprecate.field(output, 'this.navigation', result, 'navigation', function() {
|
||||
return encodeNavigation(output);
|
||||
}, '"navigation" property is deprecated');
|
||||
|
||||
deprecate.field(output, 'this.book', result, 'book',
|
||||
result, '"book" property is deprecated, use "this" directly instead');
|
||||
|
||||
deprecate.field(output, 'this.options', result, 'options',
|
||||
result.config.values, '"options" property is deprecated, use config.get(key) instead');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeGlobal;
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
encodePage: require('./encodePage'),
|
||||
decodePage: require('./decodePage'),
|
||||
|
||||
encodeGlobal: require('./encodeGlobal'),
|
||||
decodeGlobal: require('./decodeGlobal')
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
var Modifiers = require('./modifiers');
|
||||
|
||||
module.exports = {
|
||||
Parse: require('./parse'),
|
||||
|
||||
// Models
|
||||
Book: require('./models/book'),
|
||||
FS: require('./models/fs'),
|
||||
File: require('./models/file'),
|
||||
Summary: require('./models/summary'),
|
||||
Glossary: require('./models/glossary'),
|
||||
Config: require('./models/config'),
|
||||
Page: require('./models/page'),
|
||||
PluginDependency: require('./models/pluginDependency'),
|
||||
|
||||
// Modifiers
|
||||
SummaryModifier: Modifiers.Summary,
|
||||
ConfigModifier: Modifiers.Config,
|
||||
|
||||
// Constants
|
||||
CONFIG_FILES: require('./constants/configFiles.js'),
|
||||
IGNORE_FILES: require('./constants/ignoreFiles.js'),
|
||||
DEFAULT_PLUGINS: require('./constants/defaultPlugins'),
|
||||
EXTENSIONS_MARKDOWN: require('./constants/extsMarkdown'),
|
||||
EXTENSIONS_ASCIIDOC: require('./constants/extsAsciidoc')
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
var path = require('path');
|
||||
var Book = require('../models/book');
|
||||
var createNodeFS = require('../fs/node');
|
||||
|
||||
/**
|
||||
Return a book instance to work on from
|
||||
command line args/kwargs
|
||||
|
||||
@param {Array} args
|
||||
@param {Object} kwargs
|
||||
@return {Book}
|
||||
*/
|
||||
function getBook(args, kwargs) {
|
||||
var input = path.resolve(args[0] || process.cwd());
|
||||
var logLevel = kwargs.log;
|
||||
|
||||
var fs = createNodeFS(input);
|
||||
var book = Book.createForFS(fs);
|
||||
|
||||
return book.setLogLevel(logLevel);
|
||||
}
|
||||
|
||||
module.exports = getBook;
|
||||
@@ -1,17 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
/**
|
||||
Return path to output folder
|
||||
|
||||
@param {Array} args
|
||||
@return {String}
|
||||
*/
|
||||
function getOutputFolder(args) {
|
||||
var bookRoot = path.resolve(args[0] || process.cwd());
|
||||
var defaultOutputRoot = path.join(bookRoot, '_book');
|
||||
var outputFolder = args[1]? path.resolve(process.cwd(), args[1]) : defaultOutputRoot;
|
||||
|
||||
return outputFolder;
|
||||
}
|
||||
|
||||
module.exports = getOutputFolder;
|
||||
@@ -1,17 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var options = require('./options');
|
||||
var initBook = require('../init');
|
||||
|
||||
module.exports = {
|
||||
name: 'init [book]',
|
||||
description: 'setup and create files for chapters',
|
||||
options: [
|
||||
options.log
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
var bookRoot = path.resolve(process.cwd(), args[0] || './');
|
||||
|
||||
return initBook(bookRoot);
|
||||
}
|
||||
};
|
||||
@@ -1,128 +0,0 @@
|
||||
var events = require('events');
|
||||
var http = require('http');
|
||||
var send = require('send');
|
||||
var util = require('util');
|
||||
var url = require('url');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
function Server() {
|
||||
this.running = null;
|
||||
this.dir = null;
|
||||
this.port = 0;
|
||||
this.sockets = [];
|
||||
}
|
||||
util.inherits(Server, events.EventEmitter);
|
||||
|
||||
/**
|
||||
Return true if the server is running
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Server.prototype.isRunning = function() {
|
||||
return !!this.running;
|
||||
};
|
||||
|
||||
/**
|
||||
Stop the server
|
||||
|
||||
@return {Promise}
|
||||
*/
|
||||
Server.prototype.stop = function() {
|
||||
var that = this;
|
||||
if (!this.isRunning()) return Promise();
|
||||
|
||||
var d = Promise.defer();
|
||||
this.running.close(function(err) {
|
||||
that.running = null;
|
||||
that.emit('state', false);
|
||||
|
||||
if (err) d.reject(err);
|
||||
else d.resolve();
|
||||
});
|
||||
|
||||
for (var i = 0; i < this.sockets.length; i++) {
|
||||
this.sockets[i].destroy();
|
||||
}
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
Start the server
|
||||
|
||||
@return {Promise}
|
||||
*/
|
||||
Server.prototype.start = function(dir, port) {
|
||||
var that = this, pre = Promise();
|
||||
port = port || 8004;
|
||||
|
||||
if (that.isRunning()) pre = this.stop();
|
||||
return pre
|
||||
.then(function() {
|
||||
var d = Promise.defer();
|
||||
|
||||
that.running = http.createServer(function(req, res){
|
||||
// Render error
|
||||
function error(err) {
|
||||
res.statusCode = err.status || 500;
|
||||
res.end(err.message);
|
||||
}
|
||||
|
||||
// Redirect to directory's index.html
|
||||
function redirect() {
|
||||
var resultURL = urlTransform(req.url, function(parsed) {
|
||||
parsed.pathname += '/';
|
||||
return parsed;
|
||||
});
|
||||
|
||||
res.statusCode = 301;
|
||||
res.setHeader('Location', resultURL);
|
||||
res.end('Redirecting to ' + resultURL);
|
||||
}
|
||||
|
||||
res.setHeader('X-Current-Location', req.url);
|
||||
|
||||
// Send file
|
||||
send(req, url.parse(req.url).pathname, {
|
||||
root: dir
|
||||
})
|
||||
.on('error', error)
|
||||
.on('directory', redirect)
|
||||
.pipe(res);
|
||||
});
|
||||
|
||||
that.running.on('connection', function (socket) {
|
||||
that.sockets.push(socket);
|
||||
socket.setTimeout(4000);
|
||||
socket.on('close', function () {
|
||||
that.sockets.splice(that.sockets.indexOf(socket), 1);
|
||||
});
|
||||
});
|
||||
|
||||
that.running.listen(port, function(err) {
|
||||
if (err) return d.reject(err);
|
||||
|
||||
that.port = port;
|
||||
that.dir = dir;
|
||||
that.emit('state', true);
|
||||
d.resolve();
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
urlTransform is a helper function that allows a function to transform
|
||||
a url string in it's parsed form and returns the new url as a string
|
||||
|
||||
@param {String} uri
|
||||
@param {Function} fn
|
||||
@return {String}
|
||||
*/
|
||||
function urlTransform(uri, fn) {
|
||||
return url.format(fn(url.parse(uri)));
|
||||
}
|
||||
|
||||
module.exports = Server;
|
||||
@@ -1,6 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
var jsonSchemaDefaults = require('json-schema-defaults');
|
||||
|
||||
var schema = require('./configSchema');
|
||||
|
||||
module.exports = Immutable.fromJS(jsonSchemaDefaults(schema));
|
||||
@@ -1,51 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
var TemplateBlock = require('../models/templateBlock');
|
||||
|
||||
module.exports = Immutable.Map({
|
||||
html: TemplateBlock({
|
||||
name: 'html',
|
||||
process: function(blk) {
|
||||
return blk;
|
||||
}
|
||||
}),
|
||||
|
||||
code: TemplateBlock({
|
||||
name: 'code',
|
||||
process: function(blk) {
|
||||
return {
|
||||
html: false,
|
||||
body: blk.body
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
markdown: TemplateBlock({
|
||||
name: 'markdown',
|
||||
process: function(blk) {
|
||||
return this.book.renderInline('markdown', blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
asciidoc: TemplateBlock({
|
||||
name: 'asciidoc',
|
||||
process: function(blk) {
|
||||
return this.book.renderInline('asciidoc', blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
markup: TemplateBlock({
|
||||
name: 'markup',
|
||||
process: function(blk) {
|
||||
return this.book.renderInline(this.ctx.file.type, blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
var extend = require('extend');
|
||||
|
||||
var common = require('./browser');
|
||||
|
||||
module.exports = extend({
|
||||
initBook: require('./init'),
|
||||
createNodeFS: require('./fs/node'),
|
||||
Output: require('./output'),
|
||||
commands: require('./cli')
|
||||
}, common);
|
||||
@@ -1,39 +0,0 @@
|
||||
var extend = require('extend');
|
||||
|
||||
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
|
||||
|
||||
@param {Book}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeBookToJson(book) {
|
||||
var config = book.getConfig();
|
||||
var language = book.getLanguage();
|
||||
|
||||
var variables = config.getValue('variables', {});
|
||||
|
||||
return {
|
||||
summary: encodeSummary(book.getSummary()),
|
||||
glossary: encodeGlossary(book.getGlossary()),
|
||||
readme: encodeReadme(book.getReadme()),
|
||||
config: book.getConfig().getValues().toJS(),
|
||||
|
||||
languages: book.isMultilingual()? encodeLanguages(book.getLanguages()) : undefined,
|
||||
|
||||
gitbook: {
|
||||
version: gitbook.version,
|
||||
time: gitbook.START_TIME
|
||||
},
|
||||
book: extend({
|
||||
language: language? language : undefined
|
||||
}, variables.toJS())
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeBookToJson;
|
||||
@@ -1,22 +0,0 @@
|
||||
var encodeBook = require('./encodeBook');
|
||||
var encodePage = require('./encodePage');
|
||||
var encodeFile = require('./encodeFile');
|
||||
|
||||
/**
|
||||
* Return a JSON representation of a book with a specific file
|
||||
*
|
||||
* @param {Book} output
|
||||
* @param {Page} page
|
||||
* @return {Object}
|
||||
*/
|
||||
function encodeBookWithPage(book, page) {
|
||||
var file = page.getFile();
|
||||
|
||||
var result = encodeBook(book);
|
||||
result.page = encodePage(page, book.getSummary());
|
||||
result.file = encodeFile(file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeBookWithPage;
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
/**
|
||||
Return a JSON representation of a file
|
||||
|
||||
@param {File} file
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeFileToJson(file) {
|
||||
var filePath = file.getPath();
|
||||
if (!filePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
path: filePath,
|
||||
mtime: file.getMTime(),
|
||||
type: file.getType()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeFileToJson;
|
||||
@@ -1,21 +0,0 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
var encodeGlossaryEntry = require('./encodeGlossaryEntry');
|
||||
|
||||
/**
|
||||
Encode a glossary to JSON
|
||||
|
||||
@param {Glossary}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeGlossary(glossary) {
|
||||
var file = glossary.getFile();
|
||||
var entries = glossary.getEntries();
|
||||
|
||||
return {
|
||||
file: encodeFile(file),
|
||||
entries: entries
|
||||
.map(encodeGlossaryEntry).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeGlossary;
|
||||
@@ -1,26 +0,0 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
|
||||
/**
|
||||
Encode a languages listing to JSON
|
||||
|
||||
@param {Languages}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeLanguages(languages) {
|
||||
var file = languages.getFile();
|
||||
var list = languages.getList();
|
||||
|
||||
return {
|
||||
file: encodeFile(file),
|
||||
list: list
|
||||
.valueSeq()
|
||||
.map(function(lang) {
|
||||
return {
|
||||
id: lang.getID(),
|
||||
title: lang.getTitle()
|
||||
};
|
||||
}).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeLanguages;
|
||||
@@ -1,25 +0,0 @@
|
||||
var encodeBook = require('./encodeBook');
|
||||
|
||||
/**
|
||||
* Encode an output to JSON
|
||||
*
|
||||
* @param {Output}
|
||||
* @return {Object}
|
||||
*/
|
||||
function encodeOutputToJson(output) {
|
||||
var book = output.getBook();
|
||||
var generator = output.getGenerator();
|
||||
var options = output.getOptions();
|
||||
|
||||
var result = encodeBook(book);
|
||||
|
||||
result.output = {
|
||||
name: generator
|
||||
};
|
||||
|
||||
result.options = options.toJS();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeOutputToJson;
|
||||
@@ -1,23 +0,0 @@
|
||||
var encodeOutput = require('./encodeOutput');
|
||||
var encodePage = require('./encodePage');
|
||||
var encodeFile = require('./encodeFile');
|
||||
|
||||
/**
|
||||
* Return a JSON representation of a book with a specific file
|
||||
*
|
||||
* @param {Book} output
|
||||
* @param {Page} page
|
||||
* @return {Object}
|
||||
*/
|
||||
function encodeOutputWithPage(output, page) {
|
||||
var file = page.getFile();
|
||||
var book = output.getBook();
|
||||
|
||||
var result = encodeOutput(output);
|
||||
result.page = encodePage(page, book.getSummary());
|
||||
result.file = encodeFile(file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeOutputWithPage;
|
||||
@@ -1,39 +0,0 @@
|
||||
var encodeSummaryArticle = require('./encodeSummaryArticle');
|
||||
|
||||
/**
|
||||
Return a JSON representation of a page
|
||||
|
||||
@param {Page} page
|
||||
@param {Summary} summary
|
||||
@return {Object}
|
||||
*/
|
||||
function encodePage(page, summary) {
|
||||
var file = page.getFile();
|
||||
var attributes = page.getAttributes();
|
||||
var article = summary.getByPath(file.getPath());
|
||||
|
||||
var result = attributes.toJS();
|
||||
|
||||
if (article) {
|
||||
result.title = article.getTitle();
|
||||
result.level = article.getLevel();
|
||||
result.depth = article.getDepth();
|
||||
|
||||
var nextArticle = summary.getNextArticle(article);
|
||||
if (nextArticle) {
|
||||
result.next = encodeSummaryArticle(nextArticle);
|
||||
}
|
||||
|
||||
var prevArticle = summary.getPrevArticle(article);
|
||||
if (prevArticle) {
|
||||
result.previous = encodeSummaryArticle(prevArticle);
|
||||
}
|
||||
}
|
||||
|
||||
result.content = page.getContent();
|
||||
result.dir = page.getDir();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodePage;
|
||||
@@ -1,17 +0,0 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
|
||||
/**
|
||||
Encode a readme to JSON
|
||||
|
||||
@param {Readme}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeReadme(readme) {
|
||||
var file = readme.getFile();
|
||||
|
||||
return {
|
||||
file: encodeFile(file)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeReadme;
|
||||
@@ -1,20 +0,0 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
var encodeSummaryPart = require('./encodeSummaryPart');
|
||||
|
||||
/**
|
||||
Encode a summary to JSON
|
||||
|
||||
@param {Summary}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeSummary(summary) {
|
||||
var file = summary.getFile();
|
||||
var parts = summary.getParts();
|
||||
|
||||
return {
|
||||
file: encodeFile(file),
|
||||
parts: parts.map(encodeSummaryPart).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeSummary;
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
/**
|
||||
Encode a SummaryArticle to JSON
|
||||
|
||||
@param {SummaryArticle}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeSummaryArticle(article, recursive) {
|
||||
var articles = undefined;
|
||||
if (recursive !== false) {
|
||||
articles = article.getArticles()
|
||||
.map(encodeSummaryArticle)
|
||||
.toJS();
|
||||
}
|
||||
|
||||
return {
|
||||
title: article.getTitle(),
|
||||
level: article.getLevel(),
|
||||
depth: article.getDepth(),
|
||||
anchor: article.getAnchor(),
|
||||
url: article.getUrl(),
|
||||
path: article.getPath(),
|
||||
ref: article.getRef(),
|
||||
articles: articles
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeSummaryArticle;
|
||||
@@ -1,17 +0,0 @@
|
||||
var encodeSummaryArticle = require('./encodeSummaryArticle');
|
||||
|
||||
/**
|
||||
Encode a SummaryPart to JSON
|
||||
|
||||
@param {SummaryPart}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeSummaryPart(part) {
|
||||
return {
|
||||
title: part.getTitle(),
|
||||
articles: part.getArticles()
|
||||
.map(encodeSummaryArticle).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeSummaryPart;
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
encodeOutput: require('./encodeOutput'),
|
||||
encodeBookWithPage: require('./encodeBookWithPage'),
|
||||
encodeOutputWithPage: require('./encodeOutputWithPage'),
|
||||
encodeBook: require('./encodeBook'),
|
||||
encodeFile: require('./encodeFile'),
|
||||
encodePage: require('./encodePage'),
|
||||
encodeSummary: require('./encodeSummary'),
|
||||
encodeSummaryArticle: require('./encodeSummaryArticle'),
|
||||
encodeReadme: require('./encodeReadme'),
|
||||
encodeLanguages: require('./encodeLanguages')
|
||||
};
|
||||
@@ -1,205 +0,0 @@
|
||||
var nunjucks = require('nunjucks');
|
||||
var Immutable = require('immutable');
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
describe('TemplateBlock', function() {
|
||||
var TemplateBlock = require('../templateBlock');
|
||||
|
||||
describe('create', function() {
|
||||
it('must initialize a simple TemplateBlock from a function', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Check basic templateBlock properties
|
||||
expect(templateBlock.getName()).toBe('sayhello');
|
||||
expect(templateBlock.getEndTag()).toBe('endsayhello');
|
||||
expect(templateBlock.getBlocks().size).toBe(0);
|
||||
expect(templateBlock.getExtensionName()).toBe('BlocksayhelloExtension');
|
||||
|
||||
// Check result of applying block
|
||||
return Promise()
|
||||
.then(function() {
|
||||
return templateBlock.applyBlock();
|
||||
})
|
||||
.then(function(result) {
|
||||
expect(result.name).toBe('sayhello');
|
||||
expect(result.body).toBe('<p>Hello, World!</p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getShortcuts', function() {
|
||||
it('must return undefined if no shortcuts', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
expect(templateBlock.getShortcuts()).toNotExist();
|
||||
});
|
||||
|
||||
it('must return complete shortcut', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', {
|
||||
process: function(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
},
|
||||
shortcuts: {
|
||||
parsers: ['markdown'],
|
||||
start: '$',
|
||||
end: '-'
|
||||
}
|
||||
});
|
||||
|
||||
var shortcut = templateBlock.getShortcuts();
|
||||
|
||||
expect(shortcut).toBeDefined();
|
||||
expect(shortcut.getStart()).toEqual('$');
|
||||
expect(shortcut.getEnd()).toEqual('-');
|
||||
expect(shortcut.getStartTag()).toEqual('sayhello');
|
||||
expect(shortcut.getEndTag()).toEqual('endsayhello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNunjucksExt()', function() {
|
||||
it('should replace by block anchor', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return 'Hello';
|
||||
});
|
||||
|
||||
var blocks = {};
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt({}, blocks);
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% sayhello %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
blocks = Immutable.fromJS(blocks);
|
||||
expect(blocks.size).toBe(1);
|
||||
|
||||
var blockId = blocks.keySeq().get(0);
|
||||
var block = blocks.get(blockId);
|
||||
|
||||
expect(res).toBe('{{-%' + blockId + '%-}}');
|
||||
expect(block.get('body')).toBe('Hello');
|
||||
expect(block.get('name')).toBe('sayhello');
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% sayhello %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('<p>Hello, World!</p>');
|
||||
});
|
||||
});
|
||||
|
||||
it('must apply block arguments correctly', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<'+block.kwargs.tag+'>Hello, '+block.kwargs.name+'!</'+block.kwargs.tag+'>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% sayhello name="Samy", tag="p" %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('<p>Hello, Samy!</p>');
|
||||
});
|
||||
});
|
||||
|
||||
it('must accept an async function', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
return {
|
||||
body: 'Hello ' + block.body,
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% sayhello %}Samy{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('Hello Samy');
|
||||
});
|
||||
});
|
||||
|
||||
it('must handle nested blocks', function() {
|
||||
var templateBlock = new TemplateBlock({
|
||||
name: 'yoda',
|
||||
blocks: Immutable.List(['start', 'end']),
|
||||
process: function(block) {
|
||||
var nested = {};
|
||||
|
||||
block.blocks.forEach(function(blk) {
|
||||
nested[blk.name] = blk.body.trim();
|
||||
});
|
||||
|
||||
return {
|
||||
body: '<p class="yoda">'+nested.end+' '+nested.start+'</p>',
|
||||
parse: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% yoda %}{% start %}this sentence should be{% end %}inverted{% endyoda %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('<p class="yoda">inverted this sentence should be</p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,364 +0,0 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Logger = require('../utils/logger');
|
||||
|
||||
var FS = require('./fs');
|
||||
var Config = require('./config');
|
||||
var Readme = require('./readme');
|
||||
var Summary = require('./summary');
|
||||
var Glossary = require('./glossary');
|
||||
var Languages = require('./languages');
|
||||
var Ignore = require('./ignore');
|
||||
|
||||
var Book = Immutable.Record({
|
||||
// Logger for outptu message
|
||||
logger: Logger(),
|
||||
|
||||
// Filesystem binded to the book scope to read files/directories
|
||||
fs: FS(),
|
||||
|
||||
// Ignore files parser
|
||||
ignore: Ignore(),
|
||||
|
||||
// Structure files
|
||||
config: Config(),
|
||||
readme: Readme(),
|
||||
summary: Summary(),
|
||||
glossary: Glossary(),
|
||||
languages: Languages(),
|
||||
|
||||
// ID of the language for language books
|
||||
language: String(),
|
||||
|
||||
// List of children, if multilingual (String -> Book)
|
||||
books: Immutable.OrderedMap()
|
||||
});
|
||||
|
||||
Book.prototype.getLogger = function() {
|
||||
return this.get('logger');
|
||||
};
|
||||
|
||||
Book.prototype.getFS = function() {
|
||||
return this.get('fs');
|
||||
};
|
||||
|
||||
Book.prototype.getIgnore = function() {
|
||||
return this.get('ignore');
|
||||
};
|
||||
|
||||
Book.prototype.getConfig = function() {
|
||||
return this.get('config');
|
||||
};
|
||||
|
||||
Book.prototype.getReadme = function() {
|
||||
return this.get('readme');
|
||||
};
|
||||
|
||||
Book.prototype.getSummary = function() {
|
||||
return this.get('summary');
|
||||
};
|
||||
|
||||
Book.prototype.getGlossary = function() {
|
||||
return this.get('glossary');
|
||||
};
|
||||
|
||||
Book.prototype.getLanguages = function() {
|
||||
return this.get('languages');
|
||||
};
|
||||
|
||||
Book.prototype.getBooks = function() {
|
||||
return this.get('books');
|
||||
};
|
||||
|
||||
Book.prototype.getLanguage = function() {
|
||||
return this.get('language');
|
||||
};
|
||||
|
||||
/**
|
||||
Return FS instance to access the content
|
||||
|
||||
@return {FS}
|
||||
*/
|
||||
Book.prototype.getContentFS = function() {
|
||||
var fs = this.getFS();
|
||||
var config = this.getConfig();
|
||||
var rootFolder = config.getValue('root');
|
||||
|
||||
if (rootFolder) {
|
||||
return FS.reduceScope(fs, rootFolder);
|
||||
}
|
||||
|
||||
return fs;
|
||||
};
|
||||
|
||||
/**
|
||||
Return root of the book
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getRoot = function() {
|
||||
var fs = this.getFS();
|
||||
return fs.getRoot();
|
||||
};
|
||||
|
||||
/**
|
||||
Return root for content of the book
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getContentRoot = function() {
|
||||
var fs = this.getContentFS();
|
||||
return fs.getRoot();
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a file is ignore (should not being parsed, etc)
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.isFileIgnored = function(filename) {
|
||||
var ignore = this.getIgnore();
|
||||
var language = this.getLanguage();
|
||||
|
||||
// Ignore is always relative to the root of the main book
|
||||
if (language) {
|
||||
filename = path.join(language, filename);
|
||||
}
|
||||
|
||||
return ignore.isFileIgnored(filename);
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a content file is ignore (should not being parsed, etc)
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.isContentFileIgnored = function(filename) {
|
||||
var config = this.getConfig();
|
||||
var rootFolder = config.getValue('root');
|
||||
|
||||
if (rootFolder) {
|
||||
filename = path.join(rootFolder, filename);
|
||||
}
|
||||
|
||||
return this.isFileIgnored(filename);
|
||||
};
|
||||
|
||||
/**
|
||||
Return a page from a book by its path
|
||||
|
||||
@param {String} ref
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Book.prototype.getPage = function(ref) {
|
||||
return this.getPages().get(ref);
|
||||
};
|
||||
|
||||
/**
|
||||
Is this book the parent of language's books
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Book.prototype.isMultilingual = function() {
|
||||
return (this.getLanguages().getCount() > 0);
|
||||
};
|
||||
|
||||
/**
|
||||
Return true if book is associated to a language
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
Book.prototype.isLanguageBook = function() {
|
||||
return Boolean(this.getLanguage());
|
||||
};
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
@param {String} language
|
||||
@param {Book} book
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.addLanguageBook = function(language, book) {
|
||||
var books = this.getBooks();
|
||||
books = books.set(language, book);
|
||||
|
||||
return this.set('books', books);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the summary for this book
|
||||
|
||||
@param {Summary}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setSummary = function(summary) {
|
||||
return this.set('summary', summary);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the readme for this book
|
||||
|
||||
@param {Readme}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setReadme = function(readme) {
|
||||
return this.set('readme', readme);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the configuration for this book
|
||||
|
||||
@param {Config}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setConfig = function(config) {
|
||||
return this.set('config', config);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the ignore instance for this book
|
||||
|
||||
@param {Ignore}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setIgnore = function(ignore) {
|
||||
return this.set('ignore', ignore);
|
||||
};
|
||||
|
||||
/**
|
||||
Change log level
|
||||
|
||||
@param {String} level
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setLogLevel = function(level) {
|
||||
this.getLogger().setLevel(level);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
Create a book using a filesystem
|
||||
|
||||
@param {FS} fs
|
||||
@return {Book}
|
||||
*/
|
||||
Book.createForFS = function createForFS(fs) {
|
||||
return new Book({
|
||||
fs: fs
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Infers the default extension for files
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultExt = function() {
|
||||
// Inferring sources
|
||||
var clues = [
|
||||
this.getReadme(),
|
||||
this.getSummary(),
|
||||
this.getGlossary()
|
||||
];
|
||||
|
||||
// List their extensions
|
||||
var exts = clues.map(function (clue) {
|
||||
var file = clue.getFile();
|
||||
if (file.exists()) {
|
||||
return file.getParser().getExtensions().first();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
// Adds the general default extension
|
||||
exts.push('.md');
|
||||
|
||||
// Choose the first non null
|
||||
return exts.find(function (e) { return e !== null; });
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Readme.
|
||||
@param {Boolean} [absolute=false] False for a path relative to
|
||||
this book's content root
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultReadmePath = function(absolute) {
|
||||
var defaultPath = 'README'+this.getDefaultExt();
|
||||
if (absolute) {
|
||||
return path.join(this.getContentRoot(), defaultPath);
|
||||
} else {
|
||||
return defaultPath;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Summary.
|
||||
@param {Boolean} [absolute=false] False for a path relative to
|
||||
this book's content root
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultSummaryPath = function(absolute) {
|
||||
var defaultPath = 'SUMMARY'+this.getDefaultExt();
|
||||
if (absolute) {
|
||||
return path.join(this.getContentRoot(), defaultPath);
|
||||
} else {
|
||||
return defaultPath;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Glossary.
|
||||
@param {Boolean} [absolute=false] False for a path relative to
|
||||
this book's content root
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultGlossaryPath = function(absolute) {
|
||||
var defaultPath = 'GLOSSARY'+this.getDefaultExt();
|
||||
if (absolute) {
|
||||
return path.join(this.getContentRoot(), defaultPath);
|
||||
} else {
|
||||
return defaultPath;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Create a language book from a parent
|
||||
|
||||
@param {Book} parent
|
||||
@param {String} language
|
||||
@return {Book}
|
||||
*/
|
||||
Book.createFromParent = function createFromParent(parent, language) {
|
||||
var ignore = parent.getIgnore();
|
||||
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: config,
|
||||
ignore: ignore,
|
||||
|
||||
language: language,
|
||||
fs: FS.reduceScope(parent.getContentFS(), language)
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Book;
|
||||
@@ -1,42 +0,0 @@
|
||||
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,107 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Book = require('./book');
|
||||
var LocationUtils = require('../utils/location');
|
||||
|
||||
var Output = Immutable.Record({
|
||||
book: Book(),
|
||||
|
||||
// Name of the generator being used
|
||||
generator: String(),
|
||||
|
||||
// Map of plugins to use (String -> Plugin)
|
||||
plugins: Immutable.OrderedMap(),
|
||||
|
||||
// Map pages to generation (String -> Page)
|
||||
pages: Immutable.OrderedMap(),
|
||||
|
||||
// List assets (String)
|
||||
assets: Immutable.List(),
|
||||
|
||||
// Option for the generation
|
||||
options: Immutable.Map(),
|
||||
|
||||
// Internal state for the generation
|
||||
state: Immutable.Map()
|
||||
});
|
||||
|
||||
Output.prototype.getBook = function() {
|
||||
return this.get('book');
|
||||
};
|
||||
|
||||
Output.prototype.getGenerator = function() {
|
||||
return this.get('generator');
|
||||
};
|
||||
|
||||
Output.prototype.getPlugins = function() {
|
||||
return this.get('plugins');
|
||||
};
|
||||
|
||||
Output.prototype.getPages = function() {
|
||||
return this.get('pages');
|
||||
};
|
||||
|
||||
Output.prototype.getOptions = function() {
|
||||
return this.get('options');
|
||||
};
|
||||
|
||||
Output.prototype.getAssets = function() {
|
||||
return this.get('assets');
|
||||
};
|
||||
|
||||
Output.prototype.getState = function() {
|
||||
return this.get('state');
|
||||
};
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Output.prototype.getRoot = function() {
|
||||
return this.getOptions().get('root');
|
||||
};
|
||||
|
||||
/**
|
||||
Update state of output
|
||||
|
||||
@param {Map} newState
|
||||
@return {Output}
|
||||
*/
|
||||
Output.prototype.setState = function(newState) {
|
||||
return this.set('state', newState);
|
||||
};
|
||||
|
||||
/**
|
||||
Update options
|
||||
|
||||
@param {Map} newOptions
|
||||
@return {Output}
|
||||
*/
|
||||
Output.prototype.setOptions = function(newOptions) {
|
||||
return this.set('options', newOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
Return logegr for this output (same as book)
|
||||
|
||||
@return {Logger}
|
||||
*/
|
||||
Output.prototype.getLogger = function() {
|
||||
return this.getBook().getLogger();
|
||||
};
|
||||
|
||||
module.exports = Output;
|
||||
@@ -1,70 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
var yaml = require('js-yaml');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
var Page = Immutable.Record({
|
||||
file: File(),
|
||||
|
||||
// Attributes extracted from the YAML header
|
||||
attributes: Immutable.Map(),
|
||||
|
||||
// Content of the page
|
||||
content: String(),
|
||||
|
||||
// Direction of the text
|
||||
dir: String('ltr')
|
||||
});
|
||||
|
||||
Page.prototype.getFile = function() {
|
||||
return this.get('file');
|
||||
};
|
||||
|
||||
Page.prototype.getAttributes = function() {
|
||||
return this.get('attributes');
|
||||
};
|
||||
|
||||
Page.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
Page.prototype.getDir = function() {
|
||||
return this.get('dir');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return page as text
|
||||
* @return {String}
|
||||
*/
|
||||
Page.prototype.toText = function() {
|
||||
var attrs = this.getAttributes();
|
||||
var content = this.getContent();
|
||||
|
||||
if (attrs.size === 0) {
|
||||
return content;
|
||||
}
|
||||
|
||||
var frontMatter = '---\n' + yaml.safeDump(attrs.toJS(), { skipInvalid: true }) + '---\n\n';
|
||||
return (frontMatter + content);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return path of the page
|
||||
* @return {String}
|
||||
*/
|
||||
Page.prototype.getPath = function() {
|
||||
return this.getFile().getPath();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a page for a file
|
||||
* @param {File} file
|
||||
* @return {Page}
|
||||
*/
|
||||
Page.createForFile = function(file) {
|
||||
return new Page({
|
||||
file: file
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Page;
|
||||
@@ -1,169 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var TemplateBlock = require('./templateBlock');
|
||||
var PluginDependency = require('./pluginDependency');
|
||||
var THEME_PREFIX = require('../constants/themePrefix');
|
||||
|
||||
var DEFAULT_VERSION = '*';
|
||||
|
||||
var Plugin = Immutable.Record({
|
||||
name: String(),
|
||||
|
||||
// Requirement version (ex: ">1.0.0")
|
||||
version: String(DEFAULT_VERSION),
|
||||
|
||||
// Path to load this plugin
|
||||
path: String(),
|
||||
|
||||
// Depth of this plugin in the dependency tree
|
||||
depth: Number(0),
|
||||
|
||||
// Parent depending on this plugin
|
||||
parent: String(),
|
||||
|
||||
// Content of the "package.json"
|
||||
package: Immutable.Map(),
|
||||
|
||||
// Content of the package itself
|
||||
content: Immutable.Map()
|
||||
}, 'Plugin');
|
||||
|
||||
Plugin.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
Plugin.prototype.getPath = function() {
|
||||
return this.get('path');
|
||||
};
|
||||
|
||||
Plugin.prototype.getVersion = function() {
|
||||
return this.get('version');
|
||||
};
|
||||
|
||||
Plugin.prototype.getPackage = function() {
|
||||
return this.get('package');
|
||||
};
|
||||
|
||||
Plugin.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
Plugin.prototype.getDepth = function() {
|
||||
return this.get('depth');
|
||||
};
|
||||
|
||||
Plugin.prototype.getParent = function() {
|
||||
return this.get('parent');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the ID on NPM for this plugin
|
||||
* @return {String}
|
||||
*/
|
||||
Plugin.prototype.getNpmID = function() {
|
||||
return PluginDependency.nameToNpmID(this.getName());
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a plugin is loaded
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Plugin.prototype.isLoaded = function() {
|
||||
return Boolean(this.getPackage().size > 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a plugin is a theme given its name
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Plugin.prototype.isTheme = function() {
|
||||
var name = this.getName();
|
||||
return (name && name.indexOf(THEME_PREFIX) === 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return map of hooks
|
||||
* @return {Map<String:Function>}
|
||||
*/
|
||||
Plugin.prototype.getHooks = function() {
|
||||
return this.getContent().get('hooks') || Immutable.Map();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return infos about resources for a specific type
|
||||
* @param {String} type
|
||||
* @return {Map<String:Mixed>}
|
||||
*/
|
||||
Plugin.prototype.getResources = function(type) {
|
||||
if (type != 'website' && type != 'ebook') {
|
||||
throw new Error('Invalid assets type ' + type);
|
||||
}
|
||||
|
||||
var content = this.getContent();
|
||||
return (content.get(type)
|
||||
|| (type == 'website'? content.get('book') : null)
|
||||
|| Immutable.Map());
|
||||
};
|
||||
|
||||
/**
|
||||
* Return map of filters
|
||||
* @return {Map<String:Function>}
|
||||
*/
|
||||
Plugin.prototype.getFilters = function() {
|
||||
return this.getContent().get('filters');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return map of blocks
|
||||
* @return {Map<String:TemplateBlock>}
|
||||
*/
|
||||
Plugin.prototype.getBlocks = function() {
|
||||
var blocks = this.getContent().get('blocks');
|
||||
blocks = blocks || Immutable.Map();
|
||||
|
||||
return blocks
|
||||
.map(function(block, blockName) {
|
||||
return TemplateBlock.create(blockName, block);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a specific hook
|
||||
* @param {String} name
|
||||
* @return {Function|undefined}
|
||||
*/
|
||||
Plugin.prototype.getHook = function(name) {
|
||||
return this.getHooks().get(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a plugin from a string
|
||||
* @param {String}
|
||||
* @return {Plugin}
|
||||
*/
|
||||
Plugin.createFromString = function(s) {
|
||||
var parts = s.split('@');
|
||||
var name = parts[0];
|
||||
var version = parts.slice(1).join('@');
|
||||
|
||||
return new Plugin({
|
||||
name: name,
|
||||
version: version || DEFAULT_VERSION
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a plugin from a dependency
|
||||
* @param {PluginDependency}
|
||||
* @return {Plugin}
|
||||
*/
|
||||
Plugin.createFromDep = function(dep) {
|
||||
return new Plugin({
|
||||
name: dep.getName(),
|
||||
version: dep.getVersion()
|
||||
});
|
||||
};
|
||||
|
||||
Plugin.nameToNpmID = PluginDependency.nameToNpmID;
|
||||
|
||||
module.exports = Plugin;
|
||||
@@ -1,281 +0,0 @@
|
||||
var is = require('is');
|
||||
var extend = require('extend');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var genKey = require('../utils/genKey');
|
||||
var TemplateShortcut = require('./templateShortcut');
|
||||
|
||||
var NODE_ENDARGS = '%%endargs%%';
|
||||
|
||||
var TemplateBlock = Immutable.Record({
|
||||
// Name of block, also the start tag
|
||||
name: String(),
|
||||
|
||||
// End tag, default to "end<name>"
|
||||
end: String(),
|
||||
|
||||
// Function to process the block content
|
||||
process: Function(),
|
||||
|
||||
// List of String, for inner block tags
|
||||
blocks: Immutable.List(),
|
||||
|
||||
// List of shortcuts to replace with this block
|
||||
shortcuts: Immutable.Map()
|
||||
}, 'TemplateBlock');
|
||||
|
||||
TemplateBlock.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
TemplateBlock.prototype.getEndTag = function() {
|
||||
return this.get('end') || ('end' + this.getName());
|
||||
};
|
||||
|
||||
TemplateBlock.prototype.getProcess = function() {
|
||||
return this.get('process');
|
||||
};
|
||||
|
||||
TemplateBlock.prototype.getBlocks = function() {
|
||||
return this.get('blocks');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return shortcuts associated with this block or undefined
|
||||
* @return {TemplateShortcut|undefined}
|
||||
*/
|
||||
TemplateBlock.prototype.getShortcuts = function() {
|
||||
var shortcuts = this.get('shortcuts');
|
||||
if (shortcuts.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return TemplateShortcut.createForBlock(this, shortcuts);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return name for the nunjucks extension
|
||||
* @return {String}
|
||||
*/
|
||||
TemplateBlock.prototype.getExtensionName = function() {
|
||||
return 'Block' + this.getName() + 'Extension';
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a nunjucks extension to represents this block
|
||||
* @return {Nunjucks.Extension}
|
||||
*/
|
||||
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
||||
blocksOutput = blocksOutput || {};
|
||||
|
||||
var that = this;
|
||||
var name = this.getName();
|
||||
var endTag = this.getEndTag();
|
||||
var blocks = this.getBlocks().toJS();
|
||||
|
||||
function Ext() {
|
||||
this.tags = [name];
|
||||
|
||||
this.parse = function(parser, nodes) {
|
||||
var lastBlockName = null;
|
||||
var lastBlockArgs = null;
|
||||
var allBlocks = blocks.concat([endTag]);
|
||||
|
||||
// Parse first block
|
||||
var tok = parser.nextToken();
|
||||
lastBlockArgs = parser.parseSignature(null, true);
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
var args = new nodes.NodeList();
|
||||
var bodies = [];
|
||||
var blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
|
||||
var blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
|
||||
|
||||
// Parse while we found "end<block>"
|
||||
do {
|
||||
// Read body
|
||||
var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
|
||||
|
||||
// Handle body with previous block name and args
|
||||
blockNamesNode.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockName));
|
||||
blockArgCounts.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockArgs.children.length));
|
||||
bodies.push(currentBody);
|
||||
|
||||
// Append arguments of this block as arguments of the run function
|
||||
lastBlockArgs.children.forEach(function(child) {
|
||||
args.addChild(child);
|
||||
});
|
||||
|
||||
// Read new block
|
||||
lastBlockName = parser.nextToken().value;
|
||||
|
||||
// Parse signature and move to the end of the block
|
||||
if (lastBlockName != endTag) {
|
||||
lastBlockArgs = parser.parseSignature(null, true);
|
||||
}
|
||||
|
||||
parser.advanceAfterBlockEnd(lastBlockName);
|
||||
} while (lastBlockName != endTag);
|
||||
|
||||
args.addChild(blockNamesNode);
|
||||
args.addChild(blockArgCounts);
|
||||
args.addChild(new nodes.Literal(args.lineno, args.colno, NODE_ENDARGS));
|
||||
|
||||
return new nodes.CallExtensionAsync(this, 'run', args, bodies);
|
||||
};
|
||||
|
||||
this.run = function(context) {
|
||||
var fnArgs = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
var args;
|
||||
var blocks = [];
|
||||
var bodies = [];
|
||||
var blockNames;
|
||||
var blockArgCounts;
|
||||
var callback;
|
||||
|
||||
// Extract callback
|
||||
callback = fnArgs.pop();
|
||||
|
||||
// Detect end of arguments
|
||||
var endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
|
||||
|
||||
// Extract arguments and bodies
|
||||
args = fnArgs.slice(0, endArgIndex);
|
||||
bodies = fnArgs.slice(endArgIndex + 1);
|
||||
|
||||
// Extract block counts
|
||||
blockArgCounts = args.pop();
|
||||
blockNames = args.pop();
|
||||
|
||||
// Recreate list of blocks
|
||||
blockNames.forEach(function(name, i) {
|
||||
var countArgs = blockArgCounts[i];
|
||||
var blockBody = bodies.shift();
|
||||
|
||||
var blockArgs = countArgs > 0? args.slice(0, countArgs) : [];
|
||||
args = args.slice(countArgs);
|
||||
var blockKwargs = extractKwargs(blockArgs);
|
||||
|
||||
blocks.push({
|
||||
name: name,
|
||||
body: blockBody(),
|
||||
args: blockArgs,
|
||||
kwargs: blockKwargs
|
||||
});
|
||||
});
|
||||
|
||||
var mainBlock = blocks.shift();
|
||||
mainBlock.blocks = blocks;
|
||||
|
||||
Promise()
|
||||
.then(function() {
|
||||
var ctx = extend({
|
||||
ctx: context
|
||||
}, mainContext || {});
|
||||
|
||||
return that.applyBlock(mainBlock, ctx);
|
||||
})
|
||||
.then(function(result) {
|
||||
return that.blockResultToHtml(result, blocksOutput);
|
||||
})
|
||||
.nodeify(callback);
|
||||
};
|
||||
}
|
||||
|
||||
return Ext;
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply a block to a content
|
||||
* @param {Object} inner
|
||||
* @param {Object} context
|
||||
* @return {Promise<String>|String}
|
||||
*/
|
||||
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
||||
var processFn = this.getProcess();
|
||||
|
||||
inner = inner || {};
|
||||
inner.args = inner.args || [];
|
||||
inner.kwargs = inner.kwargs || {};
|
||||
inner.blocks = inner.blocks || [];
|
||||
|
||||
var r = processFn.call(context, inner);
|
||||
|
||||
if (Promise.isPromiseAlike(r)) {
|
||||
return r.then(this.normalizeBlockResult.bind(this));
|
||||
} else {
|
||||
return this.normalizeBlockResult(r);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize result from a block process function
|
||||
* @param {Object|String} result
|
||||
* @return {Object}
|
||||
*/
|
||||
TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
||||
if (is.string(result)) {
|
||||
result = { body: result };
|
||||
}
|
||||
result.name = this.getName();
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a block result to HTML
|
||||
* @param {Object} result
|
||||
* @param {Object} blocksOutput: stored post processing blocks in this object
|
||||
* @return {String}
|
||||
*/
|
||||
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
||||
var indexedKey;
|
||||
var toIndex = (!result.parse) || (result.post !== undefined);
|
||||
|
||||
if (toIndex) {
|
||||
indexedKey = genKey();
|
||||
blocksOutput[indexedKey] = result;
|
||||
}
|
||||
|
||||
// Parsable block, just return it
|
||||
if (result.parse) {
|
||||
return result.body;
|
||||
}
|
||||
|
||||
// Return it as a position marker
|
||||
return '{{-%' + indexedKey + '%-}}';
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a template block from a function or an object
|
||||
* @param {String} blockName
|
||||
* @param {Object} block
|
||||
* @return {TemplateBlock}
|
||||
*/
|
||||
TemplateBlock.create = function(blockName, block) {
|
||||
if (is.fn(block)) {
|
||||
block = new Immutable.Map({
|
||||
process: block
|
||||
});
|
||||
}
|
||||
|
||||
block = new TemplateBlock(block);
|
||||
block = block.set('name', blockName);
|
||||
return block;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract kwargs from an arguments array
|
||||
* @param {Array} args
|
||||
* @return {Object}
|
||||
*/
|
||||
function extractKwargs(args) {
|
||||
var last = args[args.length - 1];
|
||||
return (is.object(last) && last.__keywords)? args.pop() : {};
|
||||
}
|
||||
|
||||
module.exports = TemplateBlock;
|
||||
@@ -1,139 +0,0 @@
|
||||
var nunjucks = require('nunjucks');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var TemplateEngine = Immutable.Record({
|
||||
// Map of {TemplateBlock}
|
||||
blocks: Immutable.Map(),
|
||||
|
||||
// Map of Extension
|
||||
extensions: Immutable.Map(),
|
||||
|
||||
// Map of filters: {String} name -> {Function} fn
|
||||
filters: Immutable.Map(),
|
||||
|
||||
// Map of globals: {String} name -> {Mixed}
|
||||
globals: Immutable.Map(),
|
||||
|
||||
// Context for filters / blocks
|
||||
context: Object(),
|
||||
|
||||
// Nunjucks loader
|
||||
loader: nunjucks.FileSystemLoader('views')
|
||||
}, 'TemplateEngine');
|
||||
|
||||
TemplateEngine.prototype.getBlocks = function() {
|
||||
return this.get('blocks');
|
||||
};
|
||||
|
||||
TemplateEngine.prototype.getGlobals = function() {
|
||||
return this.get('globals');
|
||||
};
|
||||
|
||||
TemplateEngine.prototype.getFilters = function() {
|
||||
return this.get('filters');
|
||||
};
|
||||
|
||||
TemplateEngine.prototype.getShortcuts = function() {
|
||||
return this.get('shortcuts');
|
||||
};
|
||||
|
||||
TemplateEngine.prototype.getLoader = function() {
|
||||
return this.get('loader');
|
||||
};
|
||||
|
||||
TemplateEngine.prototype.getContext = function() {
|
||||
return this.get('context');
|
||||
};
|
||||
|
||||
TemplateEngine.prototype.getExtensions = function() {
|
||||
return this.get('extensions');
|
||||
};
|
||||
|
||||
/**
|
||||
Return a block by its name (or undefined)
|
||||
|
||||
@param {String} name
|
||||
@return {TemplateBlock}
|
||||
*/
|
||||
TemplateEngine.prototype.getBlock = function(name) {
|
||||
var blocks = this.getBlocks();
|
||||
return blocks.find(function(block) {
|
||||
return block.getName() === name;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Return a nunjucks environment from this configuration
|
||||
|
||||
@return {Nunjucks.Environment}
|
||||
*/
|
||||
TemplateEngine.prototype.toNunjucks = function(blocksOutput) {
|
||||
var loader = this.getLoader();
|
||||
var blocks = this.getBlocks();
|
||||
var filters = this.getFilters();
|
||||
var globals = this.getGlobals();
|
||||
var extensions = this.getExtensions();
|
||||
var context = this.getContext();
|
||||
|
||||
var env = new nunjucks.Environment(
|
||||
loader,
|
||||
{
|
||||
// Escaping is done after by the asciidoc/markdown parser
|
||||
autoescape: false,
|
||||
|
||||
// Syntax
|
||||
tags: {
|
||||
blockStart: '{%',
|
||||
blockEnd: '%}',
|
||||
variableStart: '{{',
|
||||
variableEnd: '}}',
|
||||
commentStart: '{###',
|
||||
commentEnd: '###}'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Add filters
|
||||
filters.forEach(function(filterFn, filterName) {
|
||||
env.addFilter(filterName, filterFn.bind(context));
|
||||
});
|
||||
|
||||
// Add blocks
|
||||
blocks.forEach(function(block) {
|
||||
var extName = block.getExtensionName();
|
||||
var Ext = block.toNunjucksExt(context, blocksOutput);
|
||||
|
||||
env.addExtension(extName, new Ext());
|
||||
});
|
||||
|
||||
// Add globals
|
||||
globals.forEach(function(globalValue, globalName) {
|
||||
env.addGlobal(globalName, globalValue);
|
||||
});
|
||||
|
||||
// Add other extensions
|
||||
extensions.forEach(function(ext, extName) {
|
||||
env.addExtension(extName, ext);
|
||||
});
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
/**
|
||||
Create a template engine
|
||||
|
||||
@param {Object} def
|
||||
@return {TemplateEngine}
|
||||
*/
|
||||
TemplateEngine.create = function(def) {
|
||||
return new TemplateEngine({
|
||||
blocks: Immutable.List(def.blocks || []),
|
||||
extensions: Immutable.Map(def.extensions || {}),
|
||||
filters: Immutable.Map(def.filters || {}),
|
||||
globals: Immutable.Map(def.globals || {}),
|
||||
context: def.context,
|
||||
loader: def.loader
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = TemplateEngine;
|
||||
@@ -1,42 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var TemplateOutput = Immutable.Record({
|
||||
// Text content of the template
|
||||
content: String(),
|
||||
|
||||
// Map of blocks to replace / post process
|
||||
blocks: Immutable.Map()
|
||||
}, 'TemplateOutput');
|
||||
|
||||
TemplateOutput.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
TemplateOutput.prototype.getBlocks = function() {
|
||||
return this.get('blocks');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update content of this output
|
||||
* @param {String} content
|
||||
* @return {TemplateContent}
|
||||
*/
|
||||
TemplateOutput.prototype.setContent = function(content) {
|
||||
return this.set('content', content);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a TemplateOutput from a text content
|
||||
* and an object containing block definition
|
||||
* @param {String} content
|
||||
* @param {Object} blocks
|
||||
* @return {TemplateOutput}
|
||||
*/
|
||||
TemplateOutput.create = function(content, blocks) {
|
||||
return new TemplateOutput({
|
||||
content: content,
|
||||
blocks: Immutable.fromJS(blocks)
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = TemplateOutput;
|
||||
@@ -1,45 +0,0 @@
|
||||
var Templating = require('../templating');
|
||||
var TemplateEngine = require('../models/templateEngine');
|
||||
|
||||
var Api = require('../api');
|
||||
var Plugins = require('../plugins');
|
||||
|
||||
var defaultBlocks = require('../constants/defaultBlocks');
|
||||
var defaultFilters = require('../constants/defaultFilters');
|
||||
|
||||
/**
|
||||
Create template engine for an output.
|
||||
It adds default filters/blocks, then add the ones from plugins
|
||||
|
||||
@param {Output} output
|
||||
@return {TemplateEngine}
|
||||
*/
|
||||
function createTemplateEngine(output) {
|
||||
var plugins = output.getPlugins();
|
||||
var book = output.getBook();
|
||||
var rootFolder = book.getContentRoot();
|
||||
var logger = book.getLogger();
|
||||
|
||||
var filters = Plugins.listFilters(plugins);
|
||||
var blocks = Plugins.listBlocks(plugins);
|
||||
|
||||
// Extend with default
|
||||
blocks = defaultBlocks.merge(blocks);
|
||||
filters = defaultFilters.merge(filters);
|
||||
|
||||
// Create loader
|
||||
var transformFn = Templating.replaceShortcuts.bind(null, blocks);
|
||||
var loader = new Templating.ConrefsLoader(rootFolder, transformFn, logger);
|
||||
|
||||
// Create API context
|
||||
var context = Api.encodeGlobal(output);
|
||||
|
||||
return new TemplateEngine({
|
||||
filters: filters,
|
||||
blocks: blocks,
|
||||
loader: loader,
|
||||
context: context
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createTemplateEngine;
|
||||
@@ -1,41 +0,0 @@
|
||||
var juice = require('juice');
|
||||
|
||||
var WebsiteGenerator = require('../website');
|
||||
var JSONUtils = require('../../json');
|
||||
var Templating = require('../../templating');
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
|
||||
/**
|
||||
Generate PDF header/footer templates
|
||||
|
||||
@param {Output} output
|
||||
@param {String} type
|
||||
@return {String}
|
||||
*/
|
||||
function getPDFTemplate(output, type) {
|
||||
var filePath = 'pdf_' + type + '.html';
|
||||
var outputRoot = output.getRoot();
|
||||
var engine = WebsiteGenerator.createTemplateEngine(output, filePath);
|
||||
|
||||
// Generate context
|
||||
var context = JSONUtils.encodeOutput(output);
|
||||
context.page = {
|
||||
num: '_PAGENUM_',
|
||||
title: '_SECTION_'
|
||||
};
|
||||
|
||||
// Render the theme
|
||||
return Templating.renderFile(engine, 'ebook/' + filePath, context)
|
||||
|
||||
// Inline css and assets
|
||||
.then(function(tplOut) {
|
||||
return Promise.nfcall(juice.juiceResources, tplOut.getContent(), {
|
||||
webResources: {
|
||||
relativeTo: outputRoot
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = getPDFTemplate;
|
||||
@@ -1,91 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var WebsiteGenerator = require('../website');
|
||||
var JSONUtils = require('../../json');
|
||||
var Templating = require('../../templating');
|
||||
var Promise = require('../../utils/promise');
|
||||
var error = require('../../utils/error');
|
||||
var command = require('../../utils/command');
|
||||
var writeFile = require('../helper/writeFile');
|
||||
|
||||
var getConvertOptions = require('./getConvertOptions');
|
||||
var SUMMARY_FILE = 'SUMMARY.html';
|
||||
|
||||
/**
|
||||
Write the SUMMARY.html
|
||||
|
||||
@param {Output}
|
||||
@return {Output}
|
||||
*/
|
||||
function writeSummary(output) {
|
||||
var options = output.getOptions();
|
||||
var prefix = options.get('prefix');
|
||||
|
||||
var filePath = SUMMARY_FILE;
|
||||
var engine = WebsiteGenerator.createTemplateEngine(output, filePath);
|
||||
var context = JSONUtils.encodeOutput(output);
|
||||
|
||||
// Render the theme
|
||||
return Templating.renderFile(engine, prefix + '/summary.html', context)
|
||||
|
||||
// Write it to the disk
|
||||
.then(function(tplOut) {
|
||||
return writeFile(output, filePath, tplOut.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Generate the ebook file as "index.pdf"
|
||||
|
||||
@param {Output}
|
||||
@return {Output}
|
||||
*/
|
||||
function runEbookConvert(output) {
|
||||
var logger = output.getLogger();
|
||||
var options = output.getOptions();
|
||||
var format = options.get('format');
|
||||
var outputFolder = output.getRoot();
|
||||
|
||||
if (!format) {
|
||||
return Promise(output);
|
||||
}
|
||||
|
||||
return getConvertOptions(output)
|
||||
.then(function(options) {
|
||||
var cmd = [
|
||||
'ebook-convert',
|
||||
path.resolve(outputFolder, SUMMARY_FILE),
|
||||
path.resolve(outputFolder, 'index.' + format),
|
||||
command.optionsToShellArgs(options)
|
||||
].join(' ');
|
||||
|
||||
return command.exec(cmd)
|
||||
.progress(function(data) {
|
||||
logger.debug(data);
|
||||
})
|
||||
.fail(function(err) {
|
||||
if (err.code == 127) {
|
||||
throw error.RequireInstallError({
|
||||
cmd: 'ebook-convert',
|
||||
install: 'Install it from Calibre: https://calibre-ebook.com'
|
||||
});
|
||||
}
|
||||
|
||||
throw error.EbookError(err);
|
||||
});
|
||||
})
|
||||
.thenResolve(output);
|
||||
}
|
||||
|
||||
/**
|
||||
Finish the generation, generates the SUMMARY.html
|
||||
|
||||
@param {Output}
|
||||
@return {Output}
|
||||
*/
|
||||
function onFinish(output) {
|
||||
return writeSummary(output)
|
||||
.then(runEbookConvert);
|
||||
}
|
||||
|
||||
module.exports = onFinish;
|
||||
@@ -1,17 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Options = Immutable.Record({
|
||||
// Root folder for the output
|
||||
root: String(),
|
||||
|
||||
// Prefix for generation
|
||||
prefix: String('ebook'),
|
||||
|
||||
// Format to generate using ebook-convert
|
||||
format: String(),
|
||||
|
||||
// Force use of absolute urls ("index.html" instead of "/")
|
||||
directoryIndex: Boolean(false)
|
||||
});
|
||||
|
||||
module.exports = Options;
|
||||
@@ -1,79 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var error = require('../utils/error');
|
||||
var timing = require('../utils/timing');
|
||||
|
||||
var Templating = require('../templating');
|
||||
var JSONUtils = require('../json');
|
||||
var createTemplateEngine = require('./createTemplateEngine');
|
||||
var callPageHook = require('./callPageHook');
|
||||
|
||||
/**
|
||||
* Prepare and generate HTML for a page
|
||||
*
|
||||
* @param {Output} output
|
||||
* @param {Page} page
|
||||
* @return {Promise<Page>}
|
||||
*/
|
||||
function generatePage(output, page) {
|
||||
var book = output.getBook();
|
||||
var engine = createTemplateEngine(output);
|
||||
|
||||
return timing.measure(
|
||||
'page.generate',
|
||||
Promise(page)
|
||||
.then(function(resultPage) {
|
||||
var file = resultPage.getFile();
|
||||
var filePath = file.getPath();
|
||||
var parser = file.getParser();
|
||||
var context = JSONUtils.encodeOutputWithPage(output, resultPage);
|
||||
|
||||
if (!parser) {
|
||||
return Promise.reject(error.FileNotParsableError({
|
||||
filename: filePath
|
||||
}));
|
||||
}
|
||||
|
||||
// Call hook "page:before"
|
||||
return callPageHook('page:before', output, resultPage)
|
||||
|
||||
// Escape code blocks with raw tags
|
||||
.then(function(currentPage) {
|
||||
return parser.preparePage(currentPage.getContent());
|
||||
})
|
||||
|
||||
// Render templating syntax
|
||||
.then(function(content) {
|
||||
var absoluteFilePath = path.join(book.getContentRoot(), filePath);
|
||||
return Templating.render(engine, absoluteFilePath, content, context);
|
||||
})
|
||||
|
||||
.then(function(output) {
|
||||
var content = output.getContent();
|
||||
|
||||
return parser.parsePage(content)
|
||||
.then(function(result) {
|
||||
return output.setContent(result.content);
|
||||
});
|
||||
})
|
||||
|
||||
// Post processing for templating syntax
|
||||
.then(function(output) {
|
||||
return Templating.postRender(engine, output);
|
||||
})
|
||||
|
||||
// Return new page
|
||||
.then(function(content) {
|
||||
return resultPage.set('content', content);
|
||||
})
|
||||
|
||||
// Call final hook
|
||||
.then(function(currentPage) {
|
||||
return callPageHook('page', output, currentPage);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = generatePage;
|
||||
@@ -1,73 +0,0 @@
|
||||
var Modifiers = require('./modifiers');
|
||||
var resolveFileToURL = require('./helper/resolveFileToURL');
|
||||
var Api = require('../api');
|
||||
var Plugins = require('../plugins');
|
||||
var Promise = require('../utils/promise');
|
||||
var defaultBlocks = require('../constants/defaultBlocks');
|
||||
var fileToOutput = require('./helper/fileToOutput');
|
||||
|
||||
var CODEBLOCK = 'code';
|
||||
|
||||
/**
|
||||
* Return default modifier to prepare a page for
|
||||
* rendering.
|
||||
*
|
||||
* @return {Array<Modifier>}
|
||||
*/
|
||||
function getModifiers(output, page) {
|
||||
var book = output.getBook();
|
||||
var plugins = output.getPlugins();
|
||||
var glossary = book.getGlossary();
|
||||
var file = page.getFile();
|
||||
|
||||
// Glossary entries
|
||||
var entries = glossary.getEntries();
|
||||
var glossaryFile = glossary.getFile();
|
||||
var glossaryFilename = fileToOutput(output, glossaryFile.getPath());
|
||||
|
||||
// Current file path
|
||||
var currentFilePath = file.getPath();
|
||||
|
||||
// Get TemplateBlock for highlighting
|
||||
var blocks = Plugins.listBlocks(plugins);
|
||||
var code = blocks.get(CODEBLOCK) || defaultBlocks.get(CODEBLOCK);
|
||||
|
||||
// Current context
|
||||
var context = Api.encodeGlobal(output);
|
||||
|
||||
return [
|
||||
// Normalize IDs on headings
|
||||
Modifiers.addHeadingId,
|
||||
|
||||
// Annotate text with glossary entries
|
||||
Modifiers.annotateText.bind(null, entries, glossaryFilename),
|
||||
|
||||
// Resolve images
|
||||
Modifiers.resolveImages.bind(null, currentFilePath),
|
||||
|
||||
// Resolve links (.md -> .html)
|
||||
Modifiers.resolveLinks.bind(null,
|
||||
currentFilePath,
|
||||
resolveFileToURL.bind(null, output)
|
||||
),
|
||||
|
||||
// Highlight code blocks using "code" block
|
||||
Modifiers.highlightCode.bind(null, function(lang, source) {
|
||||
return Promise(code.applyBlock({
|
||||
body: source,
|
||||
kwargs: {
|
||||
language: lang
|
||||
}
|
||||
}, context))
|
||||
.then(function(result) {
|
||||
if (result.html === false) {
|
||||
return { text: result.body };
|
||||
} else {
|
||||
return { html: result.body };
|
||||
}
|
||||
});
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = getModifiers;
|
||||
@@ -1,32 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var PathUtils = require('../../utils/path');
|
||||
var LocationUtils = require('../../utils/location');
|
||||
|
||||
var OUTPUT_EXTENSION = '.html';
|
||||
|
||||
/**
|
||||
* Convert a filePath (absolute) to a filename for output
|
||||
*
|
||||
* @param {Output} output
|
||||
* @param {String} filePath
|
||||
* @return {String}
|
||||
*/
|
||||
function fileToOutput(output, filePath) {
|
||||
var book = output.getBook();
|
||||
var readme = book.getReadme();
|
||||
var fileReadme = readme.getFile();
|
||||
|
||||
if (
|
||||
path.basename(filePath, path.extname(filePath)) == 'README' ||
|
||||
(fileReadme.exists() && filePath == fileReadme.getPath())
|
||||
) {
|
||||
filePath = path.join(path.dirname(filePath), 'index' + OUTPUT_EXTENSION);
|
||||
} else {
|
||||
filePath = PathUtils.setExtension(filePath, OUTPUT_EXTENSION);
|
||||
}
|
||||
|
||||
return LocationUtils.normalize(filePath);
|
||||
}
|
||||
|
||||
module.exports = fileToOutput;
|
||||
@@ -1,31 +0,0 @@
|
||||
var path = require('path');
|
||||
var LocationUtils = require('../../utils/location');
|
||||
|
||||
var fileToOutput = require('./fileToOutput');
|
||||
|
||||
/**
|
||||
Convert a filePath (absolute) to an url (without hostname).
|
||||
It returns an absolute path.
|
||||
|
||||
"README.md" -> "/"
|
||||
"test/hello.md" -> "test/hello.html"
|
||||
"test/README.md" -> "test/"
|
||||
|
||||
@param {Output} output
|
||||
@param {String} filePath
|
||||
@return {String}
|
||||
*/
|
||||
function fileToURL(output, filePath) {
|
||||
var options = output.getOptions();
|
||||
var directoryIndex = options.get('directoryIndex');
|
||||
|
||||
filePath = fileToOutput(output, filePath);
|
||||
|
||||
if (directoryIndex && path.basename(filePath) == 'index.html') {
|
||||
filePath = path.dirname(filePath) + '/';
|
||||
}
|
||||
|
||||
return LocationUtils.normalize(filePath);
|
||||
}
|
||||
|
||||
module.exports = fileToURL;
|
||||
@@ -1,26 +0,0 @@
|
||||
var LocationUtils = require('../../utils/location');
|
||||
|
||||
var fileToURL = require('./fileToURL');
|
||||
|
||||
/**
|
||||
* Resolve an absolute path (extracted from a link)
|
||||
*
|
||||
* @param {Output} output
|
||||
* @param {String} filePath
|
||||
* @return {String}
|
||||
*/
|
||||
function resolveFileToURL(output, filePath) {
|
||||
// Convert /test.png -> test.png
|
||||
filePath = LocationUtils.toAbsolute(filePath, '', '');
|
||||
|
||||
var page = output.getPage(filePath);
|
||||
|
||||
// if file is a page, return correct .html url
|
||||
if (page) {
|
||||
filePath = fileToURL(output, filePath);
|
||||
}
|
||||
|
||||
return LocationUtils.normalize(filePath);
|
||||
}
|
||||
|
||||
module.exports = resolveFileToURL;
|
||||
@@ -1,40 +0,0 @@
|
||||
var cheerio = require('cheerio');
|
||||
var tmp = require('tmp');
|
||||
var path = require('path');
|
||||
|
||||
var URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png';
|
||||
|
||||
describe('fetchRemoteImages', function() {
|
||||
var dir;
|
||||
var fetchRemoteImages = require('../fetchRemoteImages');
|
||||
|
||||
beforeEach(function() {
|
||||
dir = tmp.dirSync();
|
||||
});
|
||||
|
||||
it('should download image file', function() {
|
||||
var $ = cheerio.load('<img src="' + URL + '" />');
|
||||
|
||||
return fetchRemoteImages(dir.name, 'index.html', $)
|
||||
.then(function() {
|
||||
var $img = $('img');
|
||||
var src = $img.attr('src');
|
||||
|
||||
expect(dir.name).toHaveFile(src);
|
||||
});
|
||||
});
|
||||
|
||||
it('should download image file and replace with relative path', function() {
|
||||
var $ = cheerio.load('<img src="' + URL + '" />');
|
||||
|
||||
return fetchRemoteImages(dir.name, 'test/index.html', $)
|
||||
.then(function() {
|
||||
var $img = $('img');
|
||||
var src = $img.attr('src');
|
||||
|
||||
expect(dir.name).toHaveFile(path.join('test', src));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
var cheerio = require('cheerio');
|
||||
var Promise = require('../../../utils/promise');
|
||||
var highlightCode = require('../highlightCode');
|
||||
|
||||
describe('highlightCode', function() {
|
||||
function doHighlight(lang, code) {
|
||||
return {
|
||||
text: '' + (lang || '') + '$' + code
|
||||
};
|
||||
}
|
||||
|
||||
function doHighlightAsync(lang, code) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
return doHighlight(lang, code);
|
||||
});
|
||||
}
|
||||
|
||||
it('should call it for normal code element', function() {
|
||||
var $ = cheerio.load('<p>This is a <code>test</code></p>');
|
||||
|
||||
return highlightCode(doHighlight, $)
|
||||
.then(function() {
|
||||
var $code = $('code');
|
||||
expect($code.text()).toBe('$test');
|
||||
});
|
||||
});
|
||||
|
||||
it('should call it for markdown code block', function() {
|
||||
var $ = cheerio.load('<pre><code class="lang-js">test</code></pre>');
|
||||
|
||||
return highlightCode(doHighlight, $)
|
||||
.then(function() {
|
||||
var $code = $('code');
|
||||
expect($code.text()).toBe('js$test');
|
||||
});
|
||||
});
|
||||
|
||||
it('should call it for asciidoc code block', function() {
|
||||
var $ = cheerio.load('<pre><code class="language-python">test</code></pre>');
|
||||
|
||||
return highlightCode(doHighlight, $)
|
||||
.then(function() {
|
||||
var $code = $('code');
|
||||
expect($code.text()).toBe('python$test');
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept async highlighter', function() {
|
||||
var $ = cheerio.load('<pre><code class="language-python">test</code></pre>');
|
||||
|
||||
return highlightCode(doHighlightAsync, $)
|
||||
.then(function() {
|
||||
var $code = $('code');
|
||||
expect($code.text()).toBe('python$test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
var cheerio = require('cheerio');
|
||||
var tmp = require('tmp');
|
||||
var inlinePng = require('../inlinePng');
|
||||
|
||||
describe('inlinePng', function() {
|
||||
var dir;
|
||||
|
||||
beforeEach(function() {
|
||||
dir = tmp.dirSync();
|
||||
});
|
||||
|
||||
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', $)
|
||||
.then(function() {
|
||||
var $img = $('img');
|
||||
var src = $img.attr('src');
|
||||
|
||||
expect(dir.name).toHaveFile(src);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
var path = require('path');
|
||||
var cheerio = require('cheerio');
|
||||
var resolveLinks = require('../resolveLinks');
|
||||
|
||||
describe('resolveLinks', function() {
|
||||
function resolveFileBasic(href) {
|
||||
return 'fakeDir/' + href;
|
||||
}
|
||||
|
||||
function resolveFileCustom(href) {
|
||||
if (path.extname(href) == '.md') {
|
||||
return href.slice(0, -3) + '.html';
|
||||
}
|
||||
|
||||
return href;
|
||||
}
|
||||
|
||||
describe('Absolute path', function() {
|
||||
var TEST = '<p>This is a <a href="/test/cool.md"></a></p>';
|
||||
|
||||
it('should resolve path starting by "/" in root directory', function() {
|
||||
var $ = cheerio.load(TEST);
|
||||
|
||||
return resolveLinks('hello.md', resolveFileBasic, $)
|
||||
.then(function() {
|
||||
var link = $('a');
|
||||
expect(link.attr('href')).toBe('fakeDir/test/cool.md');
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve path starting by "/" in child directory', function() {
|
||||
var $ = cheerio.load(TEST);
|
||||
|
||||
return resolveLinks('afolder/hello.md', resolveFileBasic, $)
|
||||
.then(function() {
|
||||
var link = $('a');
|
||||
expect(link.attr('href')).toBe('../fakeDir/test/cool.md');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Anchor', function() {
|
||||
it('should prevent anchors in resolution', function() {
|
||||
var TEST = '<p>This is a <a href="test/cool.md#an-anchor"></a></p>';
|
||||
var $ = cheerio.load(TEST);
|
||||
|
||||
return resolveLinks('hello.md', resolveFileCustom, $)
|
||||
.then(function() {
|
||||
var link = $('a');
|
||||
expect(link.attr('href')).toBe('test/cool.html#an-anchor');
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore pure anchor links', function() {
|
||||
var TEST = '<p>This is a <a href="#an-anchor"></a></p>';
|
||||
var $ = cheerio.load(TEST);
|
||||
|
||||
return resolveLinks('hello.md', resolveFileCustom, $)
|
||||
.then(function() {
|
||||
var link = $('a');
|
||||
expect(link.attr('href')).toBe('#an-anchor');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom Resolver', function() {
|
||||
var TEST = '<p>This is a <a href="/test/cool.md"></a> <a href="afile.png"></a></p>';
|
||||
|
||||
it('should resolve path correctly for absolute path', function() {
|
||||
var $ = cheerio.load(TEST);
|
||||
|
||||
return resolveLinks('hello.md', resolveFileCustom, $)
|
||||
.then(function() {
|
||||
var link = $('a').first();
|
||||
expect(link.attr('href')).toBe('test/cool.html');
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve path correctly for absolute path (2)', function() {
|
||||
var $ = cheerio.load(TEST);
|
||||
|
||||
return resolveLinks('afodler/hello.md', resolveFileCustom, $)
|
||||
.then(function() {
|
||||
var link = $('a').first();
|
||||
expect(link.attr('href')).toBe('../test/cool.html');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('External link', function() {
|
||||
var TEST = '<p>This is a <a href="http://www.github.com">external link</a></p>';
|
||||
|
||||
it('should have target="_blank" attribute', function() {
|
||||
var $ = cheerio.load(TEST);
|
||||
|
||||
return resolveLinks('hello.md', resolveFileBasic, $)
|
||||
.then(function() {
|
||||
var link = $('a');
|
||||
expect(link.attr('target')).toBe('_blank');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
var cheerio = require('cheerio');
|
||||
var tmp = require('tmp');
|
||||
|
||||
describe('svgToImg', function() {
|
||||
var dir;
|
||||
var svgToImg = require('../svgToImg');
|
||||
|
||||
beforeEach(function() {
|
||||
dir = tmp.dirSync();
|
||||
});
|
||||
|
||||
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', $)
|
||||
.then(function() {
|
||||
var $img = $('img');
|
||||
var src = $img.attr('src');
|
||||
|
||||
expect(dir.name).toHaveFile(src);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
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;
|
||||
|
||||
beforeEach(function() {
|
||||
dir = tmp.dirSync();
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
return svgToImg(dir.name, fileName, $)
|
||||
.then(function() {
|
||||
return svgToPng(dir.name, fileName, $);
|
||||
})
|
||||
.then(function() {
|
||||
var $img = $('img');
|
||||
var src = $img.attr('src');
|
||||
|
||||
expect(dir.name).toHaveFile(src);
|
||||
expect(path.extname(src)).toBe('.png');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
var path = require('path');
|
||||
var crc = require('crc');
|
||||
|
||||
var editHTMLElement = require('./editHTMLElement');
|
||||
var fs = require('../../utils/fs');
|
||||
var LocationUtils = require('../../utils/location');
|
||||
|
||||
/**
|
||||
Fetch all remote images
|
||||
|
||||
@param {String} rootFolder
|
||||
@param {String} currentFile
|
||||
@param {HTMLDom} $
|
||||
@return {Promise}
|
||||
*/
|
||||
function fetchRemoteImages(rootFolder, currentFile, $) {
|
||||
var currentDirectory = path.dirname(currentFile);
|
||||
|
||||
return editHTMLElement($, 'img', function($img) {
|
||||
var src = $img.attr('src');
|
||||
var extension = path.extname(src);
|
||||
|
||||
if (!LocationUtils.isExternal(src)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We avoid generating twice the same PNG
|
||||
var hash = crc.crc32(src).toString(16);
|
||||
var fileName = hash + extension;
|
||||
var filePath = path.join(rootFolder, fileName);
|
||||
|
||||
return fs.assertFile(filePath, function() {
|
||||
return fs.download(src, filePath);
|
||||
})
|
||||
.then(function() {
|
||||
// Convert to relative
|
||||
src = LocationUtils.relative(currentDirectory, fileName);
|
||||
|
||||
$img.replaceWith('<img src="' + src + '" />');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = fetchRemoteImages;
|
||||
@@ -1,58 +0,0 @@
|
||||
var is = require('is');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Promise = require('../../utils/promise');
|
||||
var editHTMLElement = require('./editHTMLElement');
|
||||
|
||||
/**
|
||||
Return language for a code blocks from a list of class names
|
||||
|
||||
@param {Array<String>}
|
||||
@return {String}
|
||||
*/
|
||||
function getLanguageForClass(classNames) {
|
||||
return Immutable.List(classNames)
|
||||
.map(function(cl) {
|
||||
// Markdown
|
||||
if (cl.search('lang-') === 0) {
|
||||
return cl.slice('lang-'.length);
|
||||
}
|
||||
|
||||
// Asciidoc
|
||||
if (cl.search('language-') === 0) {
|
||||
return cl.slice('language-'.length);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.find(function(cl) {
|
||||
return Boolean(cl);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Highlight all code elements
|
||||
|
||||
@param {Function(lang, body) -> String} highlight
|
||||
@param {HTMLDom} $
|
||||
@return {Promise}
|
||||
*/
|
||||
function highlightCode(highlight, $) {
|
||||
return editHTMLElement($, 'code', function($code) {
|
||||
var classNames = ($code.attr('class') || '').split(' ');
|
||||
var lang = getLanguageForClass(classNames);
|
||||
var source = $code.text();
|
||||
|
||||
return Promise(highlight(lang, source))
|
||||
.then(function(r) {
|
||||
if (is.string(r.html)) {
|
||||
$code.html(r.html);
|
||||
} else {
|
||||
$code.text(r.text);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = highlightCode;
|
||||
@@ -1,25 +0,0 @@
|
||||
var cheerio = require('cheerio');
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
/**
|
||||
Apply a list of operations to a page and
|
||||
output the new page.
|
||||
|
||||
@param {Page}
|
||||
@param {List|Array<Transformation>}
|
||||
@return {Promise<Page>}
|
||||
*/
|
||||
function modifyHTML(page, operations) {
|
||||
var html = page.getContent();
|
||||
var $ = cheerio.load(html);
|
||||
|
||||
return Promise.forEach(operations, function(op) {
|
||||
return op($);
|
||||
})
|
||||
.then(function() {
|
||||
var resultHTML = $.html();
|
||||
return page.set('content', resultHTML);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = modifyHTML;
|
||||
@@ -1,53 +0,0 @@
|
||||
var path = require('path');
|
||||
var url = require('url');
|
||||
|
||||
var LocationUtils = require('../../utils/location');
|
||||
var editHTMLElement = require('./editHTMLElement');
|
||||
|
||||
/**
|
||||
Resolve all HTML links:
|
||||
- /test.md in hello -> ../test.html
|
||||
|
||||
@param {String} currentFile
|
||||
@param {Function(String) -> String} resolveFile
|
||||
@param {HTMLDom} $
|
||||
*/
|
||||
function resolveLinks(currentFile, resolveFile, $) {
|
||||
var currentDirectory = path.dirname(currentFile);
|
||||
|
||||
return editHTMLElement($, 'a', function($a) {
|
||||
var href = $a.attr('href');
|
||||
|
||||
// Don't change a tag without href
|
||||
if (!href) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (LocationUtils.isExternal(href)) {
|
||||
$a.attr('target', '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
// Split anchor
|
||||
var parsed = url.parse(href);
|
||||
href = parsed.pathname || '';
|
||||
|
||||
if (href) {
|
||||
// Calcul absolute path for this
|
||||
href = LocationUtils.toAbsolute(href, currentDirectory, '.');
|
||||
|
||||
// Resolve file
|
||||
href = resolveFile(href);
|
||||
|
||||
// Convert back to relative
|
||||
href = LocationUtils.relative(currentDirectory, href);
|
||||
}
|
||||
|
||||
// Add back anchor
|
||||
href = href + (parsed.hash || '');
|
||||
|
||||
$a.attr('href', href);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = resolveLinks;
|
||||
@@ -1,26 +0,0 @@
|
||||
var Parse = require('../parse');
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
/**
|
||||
List and prepare all pages
|
||||
|
||||
@param {Output}
|
||||
@return {Promise<Output>}
|
||||
*/
|
||||
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');
|
||||
|
||||
return output.set('pages', pages);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = preparePages;
|
||||
@@ -1,38 +0,0 @@
|
||||
var createMockOutput = require('../../__tests__/createMock');
|
||||
var prepareI18n = require('../prepareI18n');
|
||||
var createTemplateEngine = require('../createTemplateEngine');
|
||||
|
||||
var WebsiteGenerator = require('../');
|
||||
|
||||
describe('i18n', function() {
|
||||
it('should correctly use english as default language', function() {
|
||||
return createMockOutput(WebsiteGenerator, {
|
||||
'README.md': 'Hello World'
|
||||
})
|
||||
.then(function(output) {
|
||||
return prepareI18n(output);
|
||||
})
|
||||
.then(function(output) {
|
||||
var engine = createTemplateEngine(output, 'README.md');
|
||||
var t = engine.getFilters().get('t');
|
||||
|
||||
expect(t('SUMMARY_INTRODUCTION')).toEqual('Introduction');
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly use language from book.json', function() {
|
||||
return createMockOutput(WebsiteGenerator, {
|
||||
'README.md': 'Hello World',
|
||||
'book.json': JSON.stringify({ language: 'fr' })
|
||||
})
|
||||
.then(function(output) {
|
||||
return prepareI18n(output);
|
||||
})
|
||||
.then(function(output) {
|
||||
var engine = createTemplateEngine(output, 'README.md');
|
||||
var t = engine.getFilters().get('t');
|
||||
|
||||
expect(t('GITBOOK_LINK')).toEqual('Publié avec GitBook');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,117 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var ASSET_FOLDER = require('../../constants/pluginAssetsFolder');
|
||||
var Promise = require('../../utils/promise');
|
||||
var fs = require('../../utils/fs');
|
||||
|
||||
/**
|
||||
Copy all assets from plugins.
|
||||
Assets are files stored in "_assets"
|
||||
nd resources declared in the plugin itself.
|
||||
|
||||
@param {Output}
|
||||
@return {Promise}
|
||||
*/
|
||||
function copyPluginAssets(output) {
|
||||
var book = output.getBook();
|
||||
|
||||
// Don't copy plugins assets for language book
|
||||
// It'll be resolved to the parent folder
|
||||
if (book.isLanguageBook()) {
|
||||
return Promise(output);
|
||||
}
|
||||
|
||||
var plugins = output.getPlugins()
|
||||
|
||||
// We reverse the order of plugins to copy
|
||||
// so that first plugins can replace assets from other plugins.
|
||||
.reverse();
|
||||
|
||||
return Promise.forEach(plugins, function(plugin) {
|
||||
return copyAssets(output, plugin)
|
||||
.then(function() {
|
||||
return copyResources(output, plugin);
|
||||
});
|
||||
})
|
||||
.thenResolve(output);
|
||||
}
|
||||
|
||||
/**
|
||||
Copy assets from a plugin
|
||||
|
||||
@param {Plugin}
|
||||
@return {Promise}
|
||||
*/
|
||||
function copyAssets(output, plugin) {
|
||||
var logger = output.getLogger();
|
||||
var pluginRoot = plugin.getPath();
|
||||
var options = output.getOptions();
|
||||
|
||||
var outputRoot = options.get('root');
|
||||
var assetOutputFolder = path.join(outputRoot, 'gitbook');
|
||||
var prefix = options.get('prefix');
|
||||
|
||||
var assetFolder = path.join(pluginRoot, ASSET_FOLDER, prefix);
|
||||
|
||||
if (!fs.existsSync(assetFolder)) {
|
||||
return Promise();
|
||||
}
|
||||
|
||||
logger.debug.ln('copy assets from theme', assetFolder);
|
||||
return fs.copyDir(
|
||||
assetFolder,
|
||||
assetOutputFolder,
|
||||
{
|
||||
deleteFirst: false,
|
||||
overwrite: true,
|
||||
confirm: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Copy resources from a plugin
|
||||
|
||||
@param {Plugin}
|
||||
@return {Promise}
|
||||
*/
|
||||
function copyResources(output, plugin) {
|
||||
var logger = output.getLogger();
|
||||
|
||||
var options = output.getOptions();
|
||||
var outputRoot = options.get('root');
|
||||
|
||||
var state = output.getState();
|
||||
var resources = state.getResources();
|
||||
|
||||
var pluginRoot = plugin.getPath();
|
||||
var pluginResources = resources.get(plugin.getName());
|
||||
|
||||
var assetsFolder = pluginResources.get('assets');
|
||||
var assetOutputFolder = path.join(outputRoot, 'gitbook', plugin.getNpmID());
|
||||
|
||||
if (!assetsFolder) {
|
||||
return Promise();
|
||||
}
|
||||
|
||||
// Resolve assets folder
|
||||
assetsFolder = path.resolve(pluginRoot, assetsFolder);
|
||||
if (!fs.existsSync(assetsFolder)) {
|
||||
logger.warn.ln('assets folder for plugin "' + plugin.getName() + '" doesn\'t exist');
|
||||
return Promise();
|
||||
}
|
||||
|
||||
logger.debug.ln('copy resources from plugin', assetsFolder);
|
||||
|
||||
return fs.copyDir(
|
||||
assetsFolder,
|
||||
assetOutputFolder,
|
||||
{
|
||||
deleteFirst: false,
|
||||
overwrite: true,
|
||||
confirm: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = copyPluginAssets;
|
||||
@@ -1,151 +0,0 @@
|
||||
var path = require('path');
|
||||
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');
|
||||
var PathUtils = require('../../utils/path');
|
||||
var TemplateEngine = require('../../models/templateEngine');
|
||||
var templatesFolder = require('../../constants/templatesFolder');
|
||||
var defaultFilters = require('../../constants/defaultFilters');
|
||||
var Templating = require('../../templating');
|
||||
var listSearchPaths = require('./listSearchPaths');
|
||||
|
||||
var fileToURL = require('../helper/fileToURL');
|
||||
var resolveFileToURL = require('../helper/resolveFileToURL');
|
||||
|
||||
/**
|
||||
* Directory for a theme with the templates
|
||||
*/
|
||||
function templateFolder(dir) {
|
||||
return path.join(dir, templatesFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create templating engine to render themes
|
||||
*
|
||||
* @param {Output} output
|
||||
* @param {String} currentFile
|
||||
* @return {TemplateEngine}
|
||||
*/
|
||||
function createTemplateEngine(output, currentFile) {
|
||||
var book = output.getBook();
|
||||
var state = output.getState();
|
||||
var i18n = state.getI18n();
|
||||
var config = book.getConfig();
|
||||
var summary = book.getSummary();
|
||||
var outputFolder = output.getRoot();
|
||||
|
||||
// Search paths for templates
|
||||
var searchPaths = listSearchPaths(output);
|
||||
var tplSearchPaths = searchPaths.map(templateFolder);
|
||||
|
||||
// Create loader
|
||||
var loader = new Templating.ThemesLoader(tplSearchPaths);
|
||||
|
||||
// Get languages
|
||||
var language = config.getValue('language');
|
||||
|
||||
// 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: {
|
||||
getArticleByPath: getArticleByPath,
|
||||
getPageByPath: getPageByPath,
|
||||
fileExists: fileExists
|
||||
},
|
||||
|
||||
filters: defaultFilters.merge({
|
||||
/**
|
||||
* Translate a sentence
|
||||
*/
|
||||
t: function t(s) {
|
||||
return i18n.t(language, s);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve an absolute file path into a
|
||||
* relative path.
|
||||
* it also resolve pages
|
||||
*/
|
||||
resolveFile: function(filePath) {
|
||||
filePath = resolveFileToURL(output, filePath);
|
||||
return LocationUtils.relativeForFile(currentFile, filePath);
|
||||
},
|
||||
|
||||
resolveAsset: function(filePath) {
|
||||
filePath = LocationUtils.toAbsolute(filePath, '', '');
|
||||
filePath = path.join('gitbook', filePath);
|
||||
filePath = LocationUtils.relativeForFile(currentFile, filePath);
|
||||
|
||||
// Use assets from parent if language book
|
||||
if (book.isLanguageBook()) {
|
||||
filePath = path.join('../', filePath);
|
||||
}
|
||||
|
||||
return LocationUtils.normalize(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);
|
||||
}
|
||||
}),
|
||||
|
||||
extensions: {
|
||||
'DoExtension': new DoExtension()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createTemplateEngine;
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
name: 'website',
|
||||
State: require('./state'),
|
||||
Options: require('./options'),
|
||||
onInit: require('./onInit'),
|
||||
onFinish: require('./onFinish'),
|
||||
onPage: require('./onPage'),
|
||||
onAsset: require('./onAsset'),
|
||||
createTemplateEngine: require('./createTemplateEngine')
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
/**
|
||||
List search paths for templates / i18n, etc
|
||||
|
||||
@param {Output} output
|
||||
@return {List<String>}
|
||||
*/
|
||||
function listSearchPaths(output) {
|
||||
var book = output.getBook();
|
||||
var plugins = output.getPlugins();
|
||||
|
||||
var searchPaths = plugins
|
||||
.valueSeq()
|
||||
.map(function(plugin) {
|
||||
return plugin.getPath();
|
||||
})
|
||||
.toList();
|
||||
|
||||
return searchPaths.unshift(book.getContentRoot());
|
||||
}
|
||||
|
||||
|
||||
module.exports = listSearchPaths;
|
||||
@@ -1,28 +0,0 @@
|
||||
var path = require('path');
|
||||
var fs = require('../../utils/fs');
|
||||
|
||||
/**
|
||||
Copy an asset to the output folder
|
||||
|
||||
@param {Output} output
|
||||
@param {Page} page
|
||||
*/
|
||||
function onAsset(output, asset) {
|
||||
var book = output.getBook();
|
||||
var options = output.getOptions();
|
||||
var bookFS = book.getContentFS();
|
||||
|
||||
var outputFolder = options.get('root');
|
||||
var outputPath = path.resolve(outputFolder, asset);
|
||||
|
||||
return fs.ensureFile(outputPath)
|
||||
.then(function() {
|
||||
return bookFS.readAsStream(asset)
|
||||
.then(function(stream) {
|
||||
return fs.writeStream(outputPath, stream);
|
||||
});
|
||||
})
|
||||
.thenResolve(output);
|
||||
}
|
||||
|
||||
module.exports = onAsset;
|
||||
@@ -1,35 +0,0 @@
|
||||
var Promise = require('../../utils/promise');
|
||||
var JSONUtils = require('../../json');
|
||||
var Templating = require('../../templating');
|
||||
var writeFile = require('../helper/writeFile');
|
||||
var createTemplateEngine = require('./createTemplateEngine');
|
||||
|
||||
/**
|
||||
Finish the generation, write the languages index
|
||||
|
||||
@param {Output}
|
||||
@return {Output}
|
||||
*/
|
||||
function onFinish(output) {
|
||||
var book = output.getBook();
|
||||
var options = output.getOptions();
|
||||
var prefix = options.get('prefix');
|
||||
|
||||
if (!book.isMultilingual()) {
|
||||
return Promise(output);
|
||||
}
|
||||
|
||||
var filePath = 'index.html';
|
||||
var engine = createTemplateEngine(output, filePath);
|
||||
var context = JSONUtils.encodeOutput(output);
|
||||
|
||||
// Render the theme
|
||||
return Templating.renderFile(engine, prefix + '/languages.html', context)
|
||||
|
||||
// Write it to the disk
|
||||
.then(function(tplOut) {
|
||||
return writeFile(output, filePath, tplOut.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = onFinish;
|
||||
@@ -1,20 +0,0 @@
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
var copyPluginAssets = require('./copyPluginAssets');
|
||||
var prepareI18n = require('./prepareI18n');
|
||||
var prepareResources = require('./prepareResources');
|
||||
|
||||
/**
|
||||
Initialize the generator
|
||||
|
||||
@param {Output}
|
||||
@return {Output}
|
||||
*/
|
||||
function onInit(output) {
|
||||
return Promise(output)
|
||||
.then(prepareI18n)
|
||||
.then(prepareResources)
|
||||
.then(copyPluginAssets);
|
||||
}
|
||||
|
||||
module.exports = onInit;
|
||||
@@ -1,76 +0,0 @@
|
||||
var path = require('path');
|
||||
var omit = require('omit-keys');
|
||||
|
||||
var Templating = require('../../templating');
|
||||
var Plugins = require('../../plugins');
|
||||
var JSONUtils = require('../../json');
|
||||
var LocationUtils = require('../../utils/location');
|
||||
var Modifiers = require('../modifiers');
|
||||
var writeFile = require('../helper/writeFile');
|
||||
var getModifiers = require('../getModifiers');
|
||||
var createTemplateEngine = require('./createTemplateEngine');
|
||||
var fileToOutput = require('../helper/fileToOutput');
|
||||
|
||||
/**
|
||||
* Write a page as a json file
|
||||
*
|
||||
* @param {Output} output
|
||||
* @param {Page} page
|
||||
*/
|
||||
function onPage(output, page) {
|
||||
var options = output.getOptions();
|
||||
var prefix = options.get('prefix');
|
||||
|
||||
var file = page.getFile();
|
||||
|
||||
var book = output.getBook();
|
||||
var plugins = output.getPlugins();
|
||||
var state = output.getState();
|
||||
var resources = state.getResources();
|
||||
|
||||
var engine = createTemplateEngine(output, page.getPath());
|
||||
|
||||
// Output file path
|
||||
var filePath = fileToOutput(output, file.getPath());
|
||||
|
||||
// Calcul relative path to the root
|
||||
var outputDirName = path.dirname(filePath);
|
||||
var basePath = LocationUtils.normalize(path.relative(outputDirName, './'));
|
||||
|
||||
return Modifiers.modifyHTML(page, getModifiers(output, page))
|
||||
.then(function(resultPage) {
|
||||
// Generate the context
|
||||
var context = JSONUtils.encodeOutputWithPage(output, resultPage);
|
||||
context.plugins = {
|
||||
resources: Plugins.listResources(plugins, resources).toJS()
|
||||
};
|
||||
|
||||
context.template = {
|
||||
getJSContext: function() {
|
||||
return {
|
||||
page: omit(context.page, 'content'),
|
||||
config: context.config,
|
||||
file: context.file,
|
||||
gitbook: context.gitbook,
|
||||
basePath: basePath,
|
||||
book: {
|
||||
language: book.getLanguage()
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// We should probabbly move it to "template" or a "site" namespace
|
||||
context.basePath = basePath;
|
||||
|
||||
// Render the theme
|
||||
return Templating.renderFile(engine, prefix + '/page.html', context)
|
||||
|
||||
// Write it to the disk
|
||||
.then(function(tplOut) {
|
||||
return writeFile(output, filePath, tplOut.getContent());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = onPage;
|
||||
@@ -1,14 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Options = Immutable.Record({
|
||||
// Root folder for the output
|
||||
root: String(),
|
||||
|
||||
// Prefix for generation
|
||||
prefix: String('website'),
|
||||
|
||||
// Use directory index url instead of "index.html"
|
||||
directoryIndex: Boolean(true)
|
||||
});
|
||||
|
||||
module.exports = Options;
|
||||
@@ -1,30 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var fs = require('../../utils/fs');
|
||||
var Promise = require('../../utils/promise');
|
||||
var listSearchPaths = require('./listSearchPaths');
|
||||
|
||||
/**
|
||||
* Prepare i18n, load translations from plugins and book
|
||||
*
|
||||
* @param {Output}
|
||||
* @return {Promise<Output>}
|
||||
*/
|
||||
function prepareI18n(output) {
|
||||
var state = output.getState();
|
||||
var i18n = state.getI18n();
|
||||
var searchPaths = listSearchPaths(output);
|
||||
|
||||
searchPaths
|
||||
.reverse()
|
||||
.forEach(function(searchPath) {
|
||||
var i18nRoot = path.resolve(searchPath, '_i18n');
|
||||
|
||||
if (!fs.existsSync(i18nRoot)) return;
|
||||
i18n.load(i18nRoot);
|
||||
});
|
||||
|
||||
return Promise(output);
|
||||
}
|
||||
|
||||
module.exports = prepareI18n;
|
||||
@@ -1,54 +0,0 @@
|
||||
var is = require('is');
|
||||
var Immutable = require('immutable');
|
||||
var Promise = require('../../utils/promise');
|
||||
|
||||
var Api = require('../../api');
|
||||
|
||||
/**
|
||||
Prepare plugins resources, add all output corresponding type resources
|
||||
|
||||
@param {Output}
|
||||
@return {Promise<Output>}
|
||||
*/
|
||||
function prepareResources(output) {
|
||||
var plugins = output.getPlugins();
|
||||
var options = output.getOptions();
|
||||
var type = options.get('prefix');
|
||||
var state = output.getState();
|
||||
var context = Api.encodeGlobal(output);
|
||||
|
||||
var result = Immutable.Map();
|
||||
|
||||
return Promise.forEach(plugins, function(plugin) {
|
||||
var pluginResources = plugin.getResources(type);
|
||||
|
||||
return Promise()
|
||||
.then(function() {
|
||||
// Apply resources if is a function
|
||||
if (is.fn(pluginResources)) {
|
||||
return Promise()
|
||||
.then(pluginResources.bind(context));
|
||||
}
|
||||
else {
|
||||
return pluginResources;
|
||||
}
|
||||
})
|
||||
.then(function(resources) {
|
||||
result = result.set(plugin.getName(), Immutable.Map(resources));
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
// Set output resources
|
||||
state = state.merge({
|
||||
resources: result
|
||||
});
|
||||
|
||||
output = output.merge({
|
||||
state: state
|
||||
});
|
||||
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = prepareResources;
|
||||
@@ -1,43 +0,0 @@
|
||||
var timing = require('../utils/timing');
|
||||
|
||||
/**
|
||||
List all assets in a book
|
||||
Assets are file not ignored and not a page
|
||||
|
||||
@param {Book} book
|
||||
@param {List<String>} pages
|
||||
@param
|
||||
*/
|
||||
function listAssets(book, pages) {
|
||||
var fs = book.getContentFS();
|
||||
|
||||
var summary = book.getSummary();
|
||||
var summaryFile = summary.getFile().getPath();
|
||||
|
||||
var glossary = book.getGlossary();
|
||||
var glossaryFile = glossary.getFile().getPath();
|
||||
|
||||
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('.', filterFile)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = listAssets;
|
||||
@@ -1,19 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var Book = require('../../models/book');
|
||||
var createNodeFS = require('../../fs/node');
|
||||
var findForBook = require('../findForBook');
|
||||
|
||||
describe('findForBook', function() {
|
||||
var fs = createNodeFS(
|
||||
path.resolve(__dirname, '../../..')
|
||||
);
|
||||
var book = Book.createForFS(fs);
|
||||
|
||||
it('should list default plugins', function() {
|
||||
return findForBook(book)
|
||||
.then(function(plugins) {
|
||||
expect(plugins.has('fontsettings')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var PluginDependency = require('../../models/pluginDependency');
|
||||
var Book = require('../../models/book');
|
||||
var NodeFS = require('../../fs/node');
|
||||
var installPlugin = require('../installPlugin');
|
||||
|
||||
var Parse = require('../../parse');
|
||||
|
||||
describe('installPlugin', function() {
|
||||
var book;
|
||||
|
||||
this.timeout(30000);
|
||||
|
||||
before(function() {
|
||||
var fs = NodeFS(path.resolve(__dirname, '../../../'));
|
||||
var baseBook = Book.createForFS(fs);
|
||||
|
||||
return Parse.parseConfig(baseBook)
|
||||
.then(function(_book) {
|
||||
book = _book;
|
||||
});
|
||||
});
|
||||
|
||||
it('must install a plugin from NPM', function() {
|
||||
var dep = PluginDependency.createFromString('ga');
|
||||
return installPlugin(book, dep);
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var Book = require('../../models/book');
|
||||
var NodeFS = require('../../fs/node');
|
||||
var installPlugins = require('../installPlugins');
|
||||
|
||||
var Parse = require('../../parse');
|
||||
|
||||
describe('installPlugins', function() {
|
||||
var book;
|
||||
|
||||
this.timeout(30000);
|
||||
|
||||
before(function() {
|
||||
var fs = NodeFS(path.resolve(__dirname, '../../../'));
|
||||
var baseBook = Book.createForFS(fs);
|
||||
|
||||
return Parse.parseConfig(baseBook)
|
||||
.then(function(_book) {
|
||||
book = _book;
|
||||
});
|
||||
});
|
||||
|
||||
it('must install all plugins from NPM', function() {
|
||||
return installPlugins(book)
|
||||
.then(function(n) {
|
||||
expect(n).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
var PluginDependency = require('../../models/pluginDependency');
|
||||
var listDependencies = require('../listDependencies');
|
||||
var toNames = require('../toNames');
|
||||
|
||||
describe('listDependencies', function() {
|
||||
it('must list default', function() {
|
||||
var deps = PluginDependency.listFromString('ga,great');
|
||||
var plugins = listDependencies(deps);
|
||||
var names = toNames(plugins);
|
||||
|
||||
expect(names).toEqual([
|
||||
'ga', 'great',
|
||||
'highlight', 'search', 'lunr', 'sharing', 'fontsettings',
|
||||
'theme-default' ]);
|
||||
});
|
||||
|
||||
it('must list from array with -', function() {
|
||||
var deps = PluginDependency.listFromString('ga,-great');
|
||||
var plugins = listDependencies(deps);
|
||||
var names = toNames(plugins);
|
||||
|
||||
expect(names).toEqual([
|
||||
'ga',
|
||||
'highlight', 'search', 'lunr', 'sharing', 'fontsettings',
|
||||
'theme-default' ]);
|
||||
});
|
||||
|
||||
it('must remove default plugins using -', function() {
|
||||
var deps = PluginDependency.listFromString('ga,-search');
|
||||
var plugins = listDependencies(deps);
|
||||
var names = toNames(plugins);
|
||||
|
||||
expect(names).toEqual([
|
||||
'ga',
|
||||
'highlight', 'lunr', 'sharing', 'fontsettings',
|
||||
'theme-default' ]);
|
||||
});
|
||||
});
|
||||
@@ -1,91 +0,0 @@
|
||||
var readInstalled = require('read-installed');
|
||||
var Immutable = require('immutable');
|
||||
var path = require('path');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var fs = require('../utils/fs');
|
||||
var Plugin = require('../models/plugin');
|
||||
var PREFIX = require('../constants/pluginPrefix');
|
||||
|
||||
/**
|
||||
* Validate if a package name is a GitBook plugin
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function validateId(name) {
|
||||
return name && name.indexOf(PREFIX) === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List all packages installed inside a folder
|
||||
*
|
||||
* @param {String} folder
|
||||
* @return {OrderedMap<String:Plugin>}
|
||||
*/
|
||||
function findInstalled(folder) {
|
||||
var options = {
|
||||
dev: false,
|
||||
log: function() {},
|
||||
depth: 4
|
||||
};
|
||||
var results = Immutable.OrderedMap();
|
||||
|
||||
function onPackage(pkg, parent) {
|
||||
if (!pkg.name) return;
|
||||
|
||||
var name = pkg.name;
|
||||
var version = pkg.version;
|
||||
var pkgPath = pkg.realPath;
|
||||
var depth = pkg.depth;
|
||||
var dependencies = pkg.dependencies;
|
||||
|
||||
var pluginName = name.slice(PREFIX.length);
|
||||
|
||||
if (!validateId(name)){
|
||||
if (parent) return;
|
||||
} else {
|
||||
results = results.set(pluginName, Plugin({
|
||||
name: pluginName,
|
||||
version: version,
|
||||
path: pkgPath,
|
||||
depth: depth,
|
||||
parent: parent
|
||||
}));
|
||||
}
|
||||
|
||||
Immutable.Map(dependencies).forEach(function(dep) {
|
||||
onPackage(dep, pluginName);
|
||||
});
|
||||
}
|
||||
|
||||
// Search for gitbook-plugins in node_modules folder
|
||||
var node_modules = path.join(folder, 'node_modules');
|
||||
|
||||
// List all folders in node_modules
|
||||
return fs.readdir(node_modules)
|
||||
.fail(function() {
|
||||
return Promise([]);
|
||||
})
|
||||
.then(function(modules) {
|
||||
return Promise.serie(modules, function(module) {
|
||||
// Not a gitbook-plugin
|
||||
if (!validateId(module)) {
|
||||
return Promise();
|
||||
}
|
||||
|
||||
// Read gitbook-plugin package details
|
||||
var module_folder = path.join(node_modules, module);
|
||||
return Promise.nfcall(readInstalled, module_folder, options)
|
||||
.then(function(data) {
|
||||
onPackage(data);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
// Return installed plugins
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = findInstalled;
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
loadForBook: require('./loadForBook'),
|
||||
validateConfig: require('./validateConfig'),
|
||||
installPlugins: require('./installPlugins'),
|
||||
listResources: require('./listResources'),
|
||||
listBlocks: require('./listBlocks'),
|
||||
listFilters: require('./listFilters')
|
||||
};
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
var npmi = require('npmi');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var resolveVersion = require('./resolveVersion');
|
||||
|
||||
/**
|
||||
Install a plugin for a book
|
||||
|
||||
@param {Book}
|
||||
@param {PluginDependency}
|
||||
@return {Promise}
|
||||
*/
|
||||
function installPlugin(book, plugin) {
|
||||
var logger = book.getLogger();
|
||||
|
||||
var installFolder = book.getRoot();
|
||||
var name = plugin.getName();
|
||||
var requirement = plugin.getVersion();
|
||||
|
||||
logger.info.ln('');
|
||||
logger.info.ln('installing plugin "' + name + '"');
|
||||
|
||||
// Find a version to install
|
||||
return resolveVersion(plugin)
|
||||
.then(function(version) {
|
||||
if (!version) {
|
||||
throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
|
||||
}
|
||||
|
||||
logger.info.ln('install plugin "' + name +'" (' + requirement + ') from NPM with version', version);
|
||||
return Promise.nfcall(npmi, {
|
||||
'name': plugin.getNpmID(),
|
||||
'version': version,
|
||||
'path': installFolder,
|
||||
'npmLoad': {
|
||||
'loglevel': 'silent',
|
||||
'loaded': true,
|
||||
'prefix': installFolder
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
logger.info.ok('plugin "' + name + '" installed with success');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = installPlugin;
|
||||
@@ -1,18 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
/**
|
||||
List blocks from a list of plugins
|
||||
|
||||
@param {OrderedMap<String:Plugin>}
|
||||
@return {Map<String:TemplateBlock>}
|
||||
*/
|
||||
function listBlocks(plugins) {
|
||||
return plugins
|
||||
.reverse()
|
||||
.reduce(function(result, plugin) {
|
||||
var blocks = plugin.getBlocks();
|
||||
return result.merge(blocks);
|
||||
}, Immutable.Map());
|
||||
}
|
||||
|
||||
module.exports = listBlocks;
|
||||
@@ -1,17 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
/**
|
||||
List filters from a list of plugins
|
||||
|
||||
@param {OrderedMap<String:Plugin>}
|
||||
@return {Map<String:Function>}
|
||||
*/
|
||||
function listFilters(plugins) {
|
||||
return plugins
|
||||
.reverse()
|
||||
.reduce(function(result, plugin) {
|
||||
return result.merge(plugin.getFilters());
|
||||
}, Immutable.Map());
|
||||
}
|
||||
|
||||
module.exports = listFilters;
|
||||
@@ -1,45 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
var path = require('path');
|
||||
|
||||
var LocationUtils = require('../utils/location');
|
||||
var PLUGIN_RESOURCES = require('../constants/pluginResources');
|
||||
|
||||
/**
|
||||
List all resources from a list of plugins
|
||||
|
||||
@param {OrderedMap<String:Plugin>}
|
||||
@param {String} type
|
||||
@return {Map<String:List<{url, path}>}
|
||||
*/
|
||||
function listResources(plugins, resources) {
|
||||
return plugins.reduce(function(result, plugin) {
|
||||
var npmId = plugin.getNpmID();
|
||||
var pluginResources = resources.get(plugin.getName());
|
||||
|
||||
PLUGIN_RESOURCES.forEach(function(resourceType) {
|
||||
var assets = pluginResources.get(resourceType);
|
||||
if (!assets) return;
|
||||
|
||||
var list = result.get(resourceType) || Immutable.List();
|
||||
|
||||
assets = assets.map(function(assetFile) {
|
||||
if (LocationUtils.isExternal(assetFile)) {
|
||||
return {
|
||||
url: assetFile
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
path: LocationUtils.normalize(path.join(npmId, assetFile))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
list = list.concat(assets);
|
||||
result = result.set(resourceType, list);
|
||||
});
|
||||
|
||||
return result;
|
||||
}, Immutable.Map());
|
||||
}
|
||||
|
||||
module.exports = listResources;
|
||||
@@ -1,22 +0,0 @@
|
||||
var path = require('path');
|
||||
var resolve = require('resolve');
|
||||
|
||||
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
||||
|
||||
/**
|
||||
* Resolve the root folder containing for node_modules
|
||||
* since gitbook can be used as a library and dependency can be flattened.
|
||||
*
|
||||
* @return {String} folderPath
|
||||
*/
|
||||
function locateRootFolder() {
|
||||
var firstDefaultPlugin = DEFAULT_PLUGINS.first();
|
||||
var pluginPath = resolve.sync(firstDefaultPlugin.getNpmID() + '/package.json', {
|
||||
basedir: __dirname
|
||||
});
|
||||
var nodeModules = path.resolve(pluginPath, '../../..');
|
||||
|
||||
return nodeModules;
|
||||
}
|
||||
|
||||
module.exports = locateRootFolder;
|
||||
@@ -1,71 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
var jsonschema = require('jsonschema');
|
||||
var jsonSchemaDefaults = require('json-schema-defaults');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var error = require('../utils/error');
|
||||
var mergeDefaults = require('../utils/mergeDefaults');
|
||||
|
||||
/**
|
||||
Validate one plugin for a book and update book's confiration
|
||||
|
||||
@param {Book}
|
||||
@param {Plugin}
|
||||
@return {Book}
|
||||
*/
|
||||
function validatePluginConfig(book, plugin) {
|
||||
var config = book.getConfig();
|
||||
var packageInfos = plugin.getPackage();
|
||||
|
||||
var configKey = [
|
||||
'pluginsConfig',
|
||||
plugin.getName()
|
||||
].join('.');
|
||||
|
||||
var pluginConfig = config.getValue(configKey, {}).toJS();
|
||||
|
||||
var schema = (packageInfos.get('gitbook') || Immutable.Map()).toJS();
|
||||
if (!schema) return book;
|
||||
|
||||
// Normalize schema
|
||||
schema.id = '/' + configKey;
|
||||
schema.type = 'object';
|
||||
|
||||
// Validate and throw if invalid
|
||||
var v = new jsonschema.Validator();
|
||||
var result = v.validate(pluginConfig, schema, {
|
||||
propertyName: configKey
|
||||
});
|
||||
|
||||
// Throw error
|
||||
if (result.errors.length > 0) {
|
||||
throw new error.ConfigurationError(new Error(result.errors[0].stack));
|
||||
}
|
||||
|
||||
// Insert default values
|
||||
var defaults = jsonSchemaDefaults(schema);
|
||||
pluginConfig = mergeDefaults(pluginConfig, defaults);
|
||||
|
||||
|
||||
// Update configuration
|
||||
config = config.setValue(configKey, pluginConfig);
|
||||
|
||||
// Return new book
|
||||
return book.set('config', config);
|
||||
}
|
||||
|
||||
/**
|
||||
Validate a book configuration for plugins and
|
||||
returns an update configuration with default values.
|
||||
|
||||
@param {Book}
|
||||
@param {OrderedMap<String:Plugin>}
|
||||
@return {Promise<Book>}
|
||||
*/
|
||||
function validateConfig(book, plugins) {
|
||||
return Promise.reduce(plugins, function(newBook, plugin) {
|
||||
return validatePluginConfig(newBook, plugin);
|
||||
}, book);
|
||||
}
|
||||
|
||||
module.exports = validateConfig;
|
||||
@@ -1,98 +0,0 @@
|
||||
var path = require('path');
|
||||
|
||||
var TemplateEngine = require('../../models/templateEngine');
|
||||
var renderTemplate = require('../render');
|
||||
var ConrefsLoader = require('../conrefsLoader');
|
||||
|
||||
describe('ConrefsLoader', function() {
|
||||
var dirName = __dirname + '/';
|
||||
var fileName = path.join(dirName, 'test.md');
|
||||
|
||||
describe('Git', function() {
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
expect(filePath).toBeA('string');
|
||||
expect(source).toBeA('string');
|
||||
|
||||
expect(filePath).toBe(path.resolve(__dirname, 'include.md'));
|
||||
expect(source).toBe('Hello World');
|
||||
|
||||
return 'test-' + source + '-endtest';
|
||||
}
|
||||
var engine = TemplateEngine({
|
||||
loader: new ConrefsLoader(dirName, transform)
|
||||
});
|
||||
|
||||
it('should transform included content', function() {
|
||||
return renderTemplate(engine, fileName, '{% include "include.md" %}')
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('test-Hello World-endtest');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
var TemplateEngine = require('../../models/templateEngine');
|
||||
var TemplateBlock = require('../../models/templateBlock');
|
||||
|
||||
var renderTemplate = require('../render');
|
||||
var postRender = require('../postRender');
|
||||
|
||||
describe('postRender', function() {
|
||||
var testPost;
|
||||
var engine = TemplateEngine.create({
|
||||
blocks: [
|
||||
TemplateBlock.create('lower', function(blk) {
|
||||
return blk.body.toLowerCase();
|
||||
}),
|
||||
TemplateBlock.create('prefix', function(blk) {
|
||||
return {
|
||||
body: '_' + blk.body + '_',
|
||||
post: function() {
|
||||
testPost = true;
|
||||
}
|
||||
};
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
it('should correctly replace block', function() {
|
||||
return renderTemplate(engine, 'README.md', 'Hello {% lower %}Samy{% endlower %}')
|
||||
.then(function(output) {
|
||||
expect(output.getContent()).toMatch(/Hello \{\{\-([\S]+)\-\}\}/);
|
||||
expect(output.getBlocks().size).toBe(1);
|
||||
|
||||
return postRender(engine, output);
|
||||
})
|
||||
.then(function(result) {
|
||||
expect(result).toBe('Hello samy');
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly replace blocks', function() {
|
||||
return renderTemplate(engine, 'README.md', 'Hello {% lower %}Samy{% endlower %}{% prefix %}Pesse{% endprefix %}')
|
||||
.then(function(output) {
|
||||
expect(output.getContent()).toMatch(/Hello \{\{\-([\S]+)\-\}\}\{\{\-([\S]+)\-\}\}/);
|
||||
expect(output.getBlocks().size).toBe(2);
|
||||
return postRender(engine, output);
|
||||
})
|
||||
.then(function(result) {
|
||||
expect(result).toBe('Hello samy_Pesse_');
|
||||
expect(testPost).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var TemplateBlock = require('../../models/templateBlock');
|
||||
var replaceShortcuts = require('../replaceShortcuts');
|
||||
|
||||
describe('replaceShortcuts', function() {
|
||||
var blocks = Immutable.List([
|
||||
TemplateBlock.create('math', {
|
||||
shortcuts: {
|
||||
start: '$$',
|
||||
end: '$$',
|
||||
parsers: ['markdown']
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
it('should correctly replace inline matches by block', function() {
|
||||
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(blocks, 'test.md', 'Hello\n$$\na = b\n$$\n');
|
||||
expect(content).toBe('Hello\n{% math %}\na = b\n{% endmath %}\n');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
render: require('./render'),
|
||||
renderFile: require('./renderFile'),
|
||||
postRender: require('./postRender'),
|
||||
replaceShortcuts: require('./replaceShortcuts'),
|
||||
|
||||
ConrefsLoader: require('./conrefsLoader'),
|
||||
ThemesLoader: require('./themesLoader')
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
var Promise = require('../utils/promise');
|
||||
|
||||
|
||||
/**
|
||||
* Replace position markers of blocks by body after processing
|
||||
* This is done to avoid that markdown/asciidoc processer parse the block content
|
||||
*
|
||||
* @param {String} content
|
||||
* @return {Object} {blocks: Set, content: String}
|
||||
*/
|
||||
function replaceBlocks(content, blocks) {
|
||||
var newContent = content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
|
||||
var replacedWith = match;
|
||||
|
||||
var block = blocks.get(key);
|
||||
if (block) {
|
||||
replacedWith = replaceBlocks(block.get('body'), blocks);
|
||||
}
|
||||
|
||||
return replacedWith;
|
||||
});
|
||||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post render a template:
|
||||
* - Execute "post" for blocks
|
||||
* - Replace block content
|
||||
*
|
||||
* @param {TemplateEngine} engine
|
||||
* @param {TemplateOutput} content
|
||||
* @return {Promise<String>}
|
||||
*/
|
||||
function postRender(engine, output) {
|
||||
var content = output.getContent();
|
||||
var blocks = output.getBlocks();
|
||||
|
||||
var result = replaceBlocks(content, blocks);
|
||||
|
||||
return Promise.forEach(blocks, function(block) {
|
||||
var post = block.get('post');
|
||||
|
||||
if (!post) {
|
||||
return;
|
||||
}
|
||||
|
||||
return post();
|
||||
})
|
||||
.thenResolve(result);
|
||||
}
|
||||
|
||||
module.exports = postRender;
|
||||
@@ -1,115 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
var nunjucks = require('nunjucks');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var PathUtils = require('../utils/path');
|
||||
|
||||
|
||||
var ThemesLoader = nunjucks.Loader.extend({
|
||||
init: function(searchPaths) {
|
||||
this.searchPaths = Immutable.List(searchPaths)
|
||||
.map(path.normalize);
|
||||
},
|
||||
|
||||
/**
|
||||
* Read source of a resolved filepath
|
||||
* @param {String}
|
||||
* @return {Object}
|
||||
*/
|
||||
getSource: function(fullpath) {
|
||||
if (!fullpath) return null;
|
||||
|
||||
fullpath = this.resolve(null, fullpath);
|
||||
var templateName = this.getTemplateName(fullpath);
|
||||
|
||||
if(!fullpath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var src = fs.readFileSync(fullpath, 'utf-8');
|
||||
|
||||
src = '{% do %}var template = template || {}; template.stack = template.stack || []; template.stack.push(template.self); template.self = ' + JSON.stringify(templateName) + '{% enddo %}\n' +
|
||||
src +
|
||||
'\n{% do %}template.self = template.stack.pop();{% enddo %}';
|
||||
|
||||
return {
|
||||
src: src,
|
||||
path: fullpath,
|
||||
noCache: true
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Nunjucks calls "isRelative" to determine when to call "resolve".
|
||||
* We handle absolute paths ourselves in ".resolve" so we always return true
|
||||
*/
|
||||
isRelative: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get original search path containing a template
|
||||
* @param {String} filepath
|
||||
* @return {String} searchPath
|
||||
*/
|
||||
getSearchPath: function(filepath) {
|
||||
return this.searchPaths
|
||||
.sortBy(function(s) {
|
||||
return -s.length;
|
||||
})
|
||||
.find(function(basePath) {
|
||||
return (filepath && filepath.indexOf(basePath) === 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get template name from a filepath
|
||||
* @param {String} filepath
|
||||
* @return {String} name
|
||||
*/
|
||||
getTemplateName: function(filepath) {
|
||||
var originalSearchPath = this.getSearchPath(filepath);
|
||||
return originalSearchPath? path.relative(originalSearchPath, filepath) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve a template from a current template
|
||||
* @param {String|null} from
|
||||
* @param {String} to
|
||||
* @return {String|null}
|
||||
*/
|
||||
resolve: function(from, to) {
|
||||
var searchPaths = this.searchPaths;
|
||||
|
||||
// Relative template like "./test.html"
|
||||
if (PathUtils.isPureRelative(to) && from) {
|
||||
return path.resolve(path.dirname(from), to);
|
||||
}
|
||||
|
||||
// Determine in which search folder we currently are
|
||||
var originalSearchPath = this.getSearchPath(from);
|
||||
var originalFilename = this.getTemplateName(from);
|
||||
|
||||
// If we are including same file from a different search path
|
||||
// Slice the search paths to avoid including from previous ones
|
||||
if (originalFilename == to) {
|
||||
var currentIndex = searchPaths.indexOf(originalSearchPath);
|
||||
searchPaths = searchPaths.slice(currentIndex + 1);
|
||||
}
|
||||
|
||||
// Absolute template to resolve in root folder
|
||||
var resultFolder = searchPaths.find(function(basePath) {
|
||||
var p = path.resolve(basePath, to);
|
||||
|
||||
return (
|
||||
p.indexOf(basePath) === 0
|
||||
&& fs.existsSync(p)
|
||||
);
|
||||
});
|
||||
if (!resultFolder) return null;
|
||||
return path.resolve(resultFolder, to);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ThemesLoader;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user