Compare commits

...

38 Commits

Author SHA1 Message Date
Samy Pesse fb862f466f Bump version to 3.0.0-pre.9 2016-04-19 14:18:01 +02:00
Samy Pesse 8d51300386 Bump version to 2.1.0 2016-04-19 14:16:51 +02:00
Samy Pesse c4b413563d Update theme-default@1.0.0-pre.8 2016-04-19 14:14:59 +02:00
Samy Pesse 8f852971f9 Cleanup book.js 2016-04-19 14:12:01 +02:00
Johan Preynat 3c54c89464 Use page.content if modified or fallback to page.sections in plugins hooks 2016-04-19 10:02:41 +02:00
Samy Pessé d07aa13822 Add tests for page hook 2016-04-18 17:14:55 +02:00
Samy Pessé c218f7d0e3 Correctly set template.self in all cases 2016-04-18 16:56:50 +02:00
Samy Pessé 72ad872e90 Fix js error when using output without root 2016-04-18 14:26:24 +02:00
Samy Pessé 854f244d22 Fix infinite loop on this.options 2016-04-18 14:17:51 +02:00
Samy Pessé 9acbb223db Add generator and output property as deprecated in config 2016-04-18 14:16:22 +02:00
Johan Preynat c59fc687db Ensure plugins compatibility with page.sections 2016-04-18 14:10:19 +02:00
Samy Pessé 0791b917f1 Add context for readme 2016-04-18 12:49:31 +02:00
Samy Pessé ba182ce8e4 Fix tests for copy of website theme 2016-04-15 11:33:38 +02:00
Samy Pessé 663291c515 Output context as global for themes 2016-04-15 11:33:24 +02:00
Samy Pessé 99253eecea Bump version to 3.0.0-pre.8 2016-04-14 15:02:49 +02:00
Samy Pessé 916a02cb36 Update plugins search@2.0.0 and theme-default@1.0.0-pre.7 2016-04-14 15:00:25 +02:00
Samy Pessé 114c4abcd1 Add method for theme: template.getJSContext 2016-04-14 14:51:42 +02:00
Samy Pessé e1e3d11932 Add lunr as default plugin 2016-04-14 14:51:22 +02:00
Samy Pessé df46217c78 Improve description extraction and adapt unit tests 2016-04-14 10:07:51 +02:00
Samy Pessé d18cc92396 Pass template to block's context 2016-04-14 09:51:56 +02:00
Samy Pessé 8f63b4b790 Remove unused variable 2016-04-13 17:32:01 +02:00
Samy Pessé ce7de93936 Improve block parsing to handle args/kwargs on subblocks 2016-04-13 16:22:53 +02:00
Johan Preynat ef2a24fe6f Add trailing dots to page.description when longer than 300 characters 2016-04-13 16:00:16 +02:00
Samy Pessé 038cd0155b Add filter "getArticleByPath" for theme templates 2016-04-13 11:48:29 +02:00
Samy Pessé fb430ac1ed Increase size of extracted description 2016-04-13 10:55:32 +02:00
Johan Preynat 53cfa3703b Update page context for plugins hooks 2016-04-13 09:33:26 +02:00
Johan Preynat 7ed9e8d145 Update plugin-fontsettings@1.0.3 / plugin-sharing@1.0.2 2016-04-12 17:05:32 +02:00
Samy Pessé 93e0b0cbea Watch _layouts when serving 2016-04-12 13:53:58 +02:00
Samy Pessé 0118bd3168 Cleanup code to list plugins 2016-04-11 12:29:53 +02:00
Samy Pessé 07d5245a5f Bump version to 3.0.0-pre.7 2016-04-11 09:59:58 +02:00
Samy Pessé 6b844213ec Update theme-default@1.0.0-pre.6 2016-04-11 09:59:18 +02:00
Samy Pessé dabdeb8a80 Fix #1195: fix external images and fix test for it 2016-04-11 09:56:13 +02:00
Samy Pessé ecd56315e6 Merge pull request #1220 from seanlane/patch-1
Fix minor spelling error.
2016-04-11 09:34:15 +02:00
Sean Lane 15b9e839e9 Fix minor spelling error. 2016-04-10 14:31:49 -06:00
Samy Pesse d7f7dcd47b Switch to ignore@3.1.1 2016-04-10 22:28:29 +02:00
Samy Pessé b1981e09b8 Fix generator deprecation warning being shown for each call 2016-04-10 18:20:51 +02:00
Samy Pessé c9edd0a795 Load plugins from book parent in multilingual book 2016-04-09 12:16:17 +02:00
Samy Pesse d83e0716bb Add test for main language in json output 2016-04-08 23:58:42 +02:00
31 changed files with 539 additions and 235 deletions
+2
View File
@@ -19,6 +19,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix error in code highlighting for unknown languages
- Accept SSH url as plugin version
- Add templating blocks `markdown`, `asciidoc` and `markup`
- Better search experience
- Better default theme, more responsive and cleaner
## 2.6.7
- Fix bug with filenames including spaces
+1 -2
View File
@@ -6,11 +6,10 @@ module.exports = {
title: 'GitBook Toolchain Documentation',
// Enforce use of GitBook v3
gitbook: pkg.version,
gitbook: '>=3.0.0-pre.0',
// Use the "official" theme
plugins: ['theme-official', 'sitemap'],
theme: 'official',
variables: {
version: pkg.version
+1 -1
View File
@@ -25,7 +25,7 @@ Each chapter has a dedicated page (`part#/README.md`) and is split into subchapt
##### Anchors
Chaptersi n the Table of Contents can be pointing to specific part of a file using anchor.
Chapters in the Table of Contents can be pointing to specific part of a file using anchor.
```markdown
# Summary
+13
View File
@@ -10,6 +10,8 @@ The following is a reference of the available data during book's parsing and the
| `gitbook` | GitBook specific information |
| `page` | Current page specific information |
| `file` | File associated with the current page specific information |
| `readme` | Information about the Readme |
| `glossary` | Information about the Glossary |
| `summary` | Information about the table of contents |
| `languages` | List of languages for multi-lingual books |
| `output` | Information about the output generator |
@@ -71,3 +73,14 @@ Languages are defined by `{ id: 'en', title: 'English' }`.
| `output.name` | Name of the output generator, possible values are `website`, `json`, `ebook` |
| `output.format` | When `output.name == "ebook"`, `format` defines the ebook format that will be generated, possible values are `pdf`, `epub` or `mobi` |
### Readme Variables
| Variable | Description |
| -------- | ----------- |
| `readme.path` | Path to the Readme in the book |
### Glossary Variables
| Variable | Description |
| -------- | ----------- |
| `glossary.path` | Path to the Glossary in the book |
+19 -1
View File
@@ -11,7 +11,25 @@ util.inherits(Readme, BackboneFile);
Readme.prototype.type = 'readme';
// Parse the readme content
/*
Return and extension of context to define the readme
@retrun {Object}
*/
Readme.prototype.getContext = function() {
return {
readme: {
path: this.path
}
};
};
/*
Parse the readme content
@param {String} content
@retrun {Promise}
*/
Readme.prototype.parse = function(content) {
var that = this;
+1
View File
@@ -231,6 +231,7 @@ Summary.prototype.getContext = function() {
return {
summary: {
path: this.path,
parts: _.map(this.parts, function(part) {
return part.getContext(onArticle);
})
+1 -10
View File
@@ -97,16 +97,7 @@ function Book(opts) {
Object.defineProperty(this, 'options', {
get: function () {
this.log.warn.ln('"options" property is deprecated, use config.get(key) instead');
var cfg = this.config.dump();
error.deprecateField(cfg, 'book', (this.output? this.output.name : null), '"options.generator" property is deprecated, use "output.name" instead');
// options.generator
cfg.generator = this.output? this.output.name : null;
// options.output
cfg.output = this.output? this.output.root() : null;
return cfg;
return this.config.options;
}
});
+1 -1
View File
@@ -11,7 +11,7 @@ function watch(dir) {
dir = path.resolve(dir);
var toWatch = [
'book.json', 'book.js'
'book.json', 'book.js', '_layouts/**'
];
// Watch all parsable files
+6 -1
View File
@@ -3,6 +3,7 @@ var semver = require('semver');
var gitbook = require('../gitbook');
var Promise = require('../utils/promise');
var error = require('../utils/error');
var validator = require('./validator');
var plugins = require('./plugins');
@@ -90,6 +91,9 @@ Config.prototype.replace = function(options) {
return that.book.parent? that.book.parent.root : undefined;
}
});
error.deprecateField(this.options, 'generator', (this.book.output? this.book.output.name : null), '"options.generator" property is deprecated, use "output.name" instead');
error.deprecateField(this.options, 'output', (this.book.output && this.book.output.root? this.book.output.root() : null), '"options.output" property is deprecated, use "output.root()" instead');
};
// Return true if book has a configuration file
@@ -119,7 +123,8 @@ Config.prototype.set = function(key, value) {
// Return a dump of the configuration
Config.prototype.dump = function() {
return _.cloneDeep(this.options);
var opts = _.omit(this.options, 'generator', 'output');
return _.cloneDeep(opts);
};
// Return templating context
+1 -1
View File
@@ -1,7 +1,7 @@
var _ = require('lodash');
// Default plugins added to each books
var DEFAULT_PLUGINS = ['highlight', 'search', 'sharing', 'fontsettings', 'theme-default'];
var DEFAULT_PLUGINS = ['highlight', 'search', 'lunr', 'sharing', 'fontsettings', 'theme-default'];
// Return true if a plugin is a default plugin
function isDefaultPlugin(name, version) {
+7 -4
View File
@@ -38,10 +38,8 @@ function Output(book, opts, parent) {
// Files to ignore in output
this.ignore = Ignore();
// Hack to inherits from rules of the book:
// Waiting for https://github.com/kaelzhang/node-ignore/pull/19 to be merged
this.ignore._rules = this.ignore._rules.concat(this.book.ignore._rules);
this.ignore._cache = {};
// Hack to inherits from rules of the book
this.ignore.add(this.book.ignore);
}
// Default name for generator
@@ -209,6 +207,10 @@ Output.prototype.onOutputSVG = function(page, svg) {
// Output an image as a file
// Normalize the relative link
Output.prototype.onOutputImage = function(page, imgFile) {
if (location.isExternal(imgFile)) {
return imgFile;
}
imgFile = page.resolveLocal(imgFile);
return page.relative(imgFile);
};
@@ -246,6 +248,7 @@ Output.prototype.getContext = function() {
},
this.book.getContext(),
(this.book.isLanguageBook()? this.book.parent: this.book).langs.getContext(),
this.book.readme.getContext(),
this.book.summary.getContext(),
this.book.glossary.getContext(),
this.book.config.getContext(),
+2 -8
View File
@@ -44,13 +44,7 @@ EbookOutput.prototype.finish = function() {
// Generate SUMMARY.html
.then(function() {
return that.render('summary', that.getContext())
.then(function(html) {
return that.writeFile(
'SUMMARY.html',
html
);
});
return that.render('summary', 'SUMMARY.html', that.getContext());
})
// Start ebook-convert
@@ -100,7 +94,7 @@ EbookOutput.prototype.getPDFTemplate = function(tpl) {
this.getContext()
);
return this.render('pdf_'+tpl, context)
return this.renderAsString('pdf_'+tpl, context)
// Inline css, include css relative to the output folder
.then(function(output) {
@@ -1,22 +1,14 @@
var _ = require('lodash');
var path = require('path');
var util = require('util');
var nunjucks = require('nunjucks');
var I18n = require('i18n-t');
var Promise = require('../utils/promise');
var location = require('../utils/location');
var fs = require('../utils/fs');
var defaultFilters = require('../template/filters');
var FSLoader = require('../template/fs-loader');
var conrefsLoader = require('./conrefs');
var Output = require('./base');
// Directory for a theme with the templates
function templatesPath(dir) {
return path.join(dir, '_layouts');
}
var Promise = require('../../utils/promise');
var location = require('../../utils/location');
var fs = require('../../utils/fs');
var conrefsLoader = require('../conrefs');
var Output = require('../base');
var setupTemplateEnv = require('./templateEnv');
function _WebsiteOutput() {
Output.apply(this, arguments);
@@ -68,50 +60,7 @@ WebsiteOutput.prototype.prepare = function() {
that.i18n.load(i18nRoot);
});
that.env = new nunjucks.Environment(new FSLoader(_.map(searchPaths, templatesPath)));
// Add GitBook default filters
_.each(defaultFilters, function(fn, filter) {
that.env.addFilter(filter, fn);
});
// Translate using _i18n locales
that.env.addFilter('t', function(s) {
return that.i18n.t(that.book.config.get('language'), s);
});
// Transform an absolute path into a relative path
// using this.ctx.page.path
that.env.addFilter('resolveFile', function(href) {
return location.normalize(that.resolveForPage(this.ctx.file.path, href));
});
// Test if a file exists
that.env.addFilter('fileExists', function(href) {
return fs.existsSync(that.resolve(href));
});
// Transform a '.md' into a '.html' (README -> index)
that.env.addFilter('contentURL', function(s) {
return that.toURL(s);
});
// Relase path to an asset
that.env.addFilter('resolveAsset', function(href) {
href = path.join('gitbook', href);
// Resolve for current file
if (this.ctx.file) {
href = that.resolveForPage(this.ctx.file.path, '/' + href);
}
// Use assets from parent
if (that.book.isLanguageBook()) {
href = path.join('../', href);
}
return location.normalize(href);
});
that.searchPaths = searchPaths;
})
// Copy assets from themes before copying files from book
@@ -156,15 +105,7 @@ WebsiteOutput.prototype.onPage = function(page) {
// Render the page template with the same context as the json output
.then(function() {
return that.render('page', page.getOutputContext(that));
})
// Write the HTML file
.then(function(html) {
return that.writeFile(
that.outputPath(page.path),
html
);
return that.render('page', that.outputPath(page.path), page.getOutputContext(that));
});
};
@@ -196,25 +137,24 @@ WebsiteOutput.prototype.finish = function() {
WebsiteOutput.prototype.outputMultilingualIndex = function() {
var that = this;
return that.render('languages', that.getContext())
.then(function(html) {
return that.writeFile(
'index.html',
html
);
});
return that.render('languages', 'index.html', that.getContext());
};
// Render a template using nunjucks
// Templates are stored in `_layouts` folders
WebsiteOutput.prototype.render = function(tpl, context) {
/*
Render a template as an HTML string
Templates are stored in `_layouts` folders
@param {String} tpl: template name (ex: "page")
@param {String} outputFile: filename to write, relative to the output
@param {Object} context: context for the page
@return {Promise}
*/
WebsiteOutput.prototype.renderAsString = function(tpl, context) {
// Calcul template name
var filename = this.templateName(tpl);
context = _.extend(context, {
template: {
self: filename
},
plugins: {
resources: this.resources
},
@@ -222,7 +162,56 @@ WebsiteOutput.prototype.render = function(tpl, context) {
options: this.opts
});
return Promise.nfcall(this.env.render.bind(this.env), filename, context);
// Create environment
var env = setupTemplateEnv(this, context);
return Promise.nfcall(env.render.bind(env), filename, context);
};
/*
Render a template using nunjucks
Templates are stored in `_layouts` folders
@param {String} tpl: template name (ex: "page")
@param {String} outputFile: filename to write, relative to the output
@param {Object} context: context for the page
@return {Promise}
*/
WebsiteOutput.prototype.render = function(tpl, outputFile, context) {
var that = this;
// Calcul relative path to the root
var outputDirName = path.dirname(outputFile);
var basePath = location.normalize(path.relative(outputDirName, './'));
// Setup complete context
context = _.extend(context, {
basePath: basePath,
template: {
getJSContext: function() {
return {
page: _.omit(context.page, 'content'),
config: context.config,
file: context.file,
gitbook: context.gitbook,
basePath: basePath,
book: {
language: context.book.language
}
};
}
}
});
return this.renderAsString(tpl, context)
.then(function(html) {
return that.writeFile(
outputFile,
html
);
});
};
// Return a complete name for a template
@@ -231,3 +220,6 @@ WebsiteOutput.prototype.templateName = function(name) {
};
module.exports = WebsiteOutput;
+91
View File
@@ -0,0 +1,91 @@
var _ = require('lodash');
var nunjucks = require('nunjucks');
var path = require('path');
var fs = require('fs');
var DoExtension = require('nunjucks-do')(nunjucks);
var location = require('../../utils/location');
var defaultFilters = require('../../template/filters');
var ThemeLoader = require('./themeLoader');
// Directory for a theme with the templates
function templatesPath(dir) {
return path.join(dir, '_layouts');
}
/*
Create and setup at Nunjucks template environment
@return {Nunjucks.Environment}
*/
function setupTemplateEnv(output, context) {
var loader = new ThemeLoader(
_.map(output.searchPaths, templatesPath)
);
var env = new nunjucks.Environment(loader);
env.addExtension('DoExtension', new DoExtension());
// Add context as global
_.each(context, function(value, key) {
env.addGlobal(key, value);
});
// Add GitBook default filters
_.each(defaultFilters, function(fn, filter) {
env.addFilter(filter, fn);
});
// Translate using _i18n locales
env.addFilter('t', function t(s) {
return output.i18n.t(output.book.config.get('language'), s);
});
// Transform an absolute path into a relative path
// using this.ctx.page.path
env.addFilter('resolveFile', function resolveFile(href) {
return location.normalize(output.resolveForPage(context.file.path, href));
});
// Test if a file exists
env.addFilter('fileExists', function fileExists(href) {
return fs.existsSync(output.resolve(href));
});
// Transform a '.md' into a '.html' (README -> index)
env.addFilter('contentURL', function contentURL(s) {
return output.toURL(s);
});
// Get an article using its path
env.addFilter('getArticleByPath', function getArticleByPath(s) {
var article = output.book.summary.getArticle(s);
if (!article) return undefined;
return article.getContext();
});
// Relase path to an asset
env.addFilter('resolveAsset', function resolveAsset(href) {
href = path.join('gitbook', href);
// Resolve for current file
if (context.file) {
href = output.resolveForPage(context.file.path, '/' + href);
}
// Use assets from parent
if (output.book.isLanguageBook()) {
href = path.join('../', href);
}
return location.normalize(href);
});
return env;
}
module.exports = setupTemplateEnv;
@@ -7,36 +7,86 @@ var nunjucks = require('nunjucks');
Nunjucks loader similar to FileSystemLoader, but avoid infinite looping
*/
/*
Return true if a filename is relative.
*/
function isRelative(filename) {
return (filename.indexOf('./') === 0 || filename.indexOf('../') === 0);
}
var Loader = nunjucks.Loader.extend({
var ThemeLoader = nunjucks.Loader.extend({
init: function(searchPaths) {
this.searchPaths = _.map(searchPaths, 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;
}
return {
src: fs.readFileSync(fullpath, 'utf-8'),
src: '{% do %}template = template || {}; template.stack = template.stack || []; template.stack.push(template.self); template.self = ' + JSON.stringify(templateName) + '{% enddo %}\n' +
fs.readFileSync(fullpath, 'utf-8') +
'\n{% do %}template.self = template.stack.pop();{% enddo %}',
path: fullpath,
noCache: true
};
},
// We handle absolute paths ourselves in ".resolve"
/*
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 _.chain(this.searchPaths)
.sortBy(function(s) {
return -s.length;
})
.find(function(basePath) {
return (filepath && filepath.indexOf(basePath) === 0);
})
.value();
},
/*
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;
@@ -46,15 +96,8 @@ var Loader = nunjucks.Loader.extend({
}
// Determine in which search folder we currently are
var originalSearchPath = _.chain(this.searchPaths)
.sortBy(function(s) {
return -s.length;
})
.find(function(basePath) {
return (from && from.indexOf(basePath) === 0);
})
.value();
var originalFilename = originalSearchPath? path.relative(originalSearchPath, from) : null;
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
@@ -77,4 +120,4 @@ var Loader = nunjucks.Loader.extend({
}
});
module.exports = Loader;
module.exports = ThemeLoader;
+12 -2
View File
@@ -184,8 +184,18 @@ HTMLPipeline.prototype.applyAnnotations = function() {
// Extract page description from html
// This can totally be improved
HTMLPipeline.prototype.extractDescription = function() {
var $p = this.$('p').first();
var description = $p.text().trim().slice(0, 155);
var $ = this.$;
var $p = $('p').first();
var $next = $p.nextUntil('h1,h2,h3,h4,h5,h6,pre,blockquote,ul,ol,div');
var description = $p.text().trim();
$next.each(function() {
description += ' ' + $(this).text().trim();
});
// Truncate description
description = _.trunc(description, 300);
this.opts.onDescription(description);
};
-1
View File
@@ -7,7 +7,6 @@ var error = require('../utils/error');
var pathUtil = require('../utils/path');
var location = require('../utils/location');
var parsers = require('../parsers');
var gitbook = require('../gitbook');
var pluginCompatibility = require('../plugins/compatibility');
var HTMLPipeline = require('./html');
+25 -9
View File
@@ -12,27 +12,43 @@ function pluginCtx(plugin) {
return ctx;
}
// Call a function "fn" with a context of page similar to the one in GitBook v2
/*
Call a function "fn" with a context of page similar to the one in GitBook v2
@params {Page}
@returns {String|undefined} new content of the page
*/
function pageHook(page, fn) {
var ctx = {
type: page.type,
content: page.content,
path: page.path,
rawPath: page.rawPath
};
// Get page context
var ctx = page.getContext().page;
// Add other informations
ctx.type = page.type;
ctx.rawPath = page.rawPath;
ctx.path = page.path;
// Deprecate sections
error.deprecateField(ctx, 'sections', [
{ content: ctx.content }
{ content: ctx.content, type: 'normal' }
], '"sections" property is deprecated, use page.content instead');
// Keep reference of original content for compatibility
var originalContent = ctx.content;
return fn(ctx)
.then(function(result) {
// No returned value
// Existing content will be used
if (!result) return undefined;
if (result.content) {
// GitBook 3
// Use returned page.content if different from original content
if (result.content != originalContent) {
return result.content;
}
// GitBook 2 compatibility
// Finally, use page.sections
if (result.sections) {
return _.pluck(result.sections, 'content').join('\n');
}
+9 -3
View File
@@ -144,10 +144,16 @@ function listInstalled(folder) {
function listPlugins(book) {
return Promise.all([
listInstalled(path.resolve(__dirname, '../..')),
listInstalled(book.root)
listInstalled(book.root),
book.isLanguageBook()? listInstalled(book.parent.root) : Promise([])
])
.spread(function(defaultPlugins, plugins) {
var results = plugins.concat(defaultPlugins);
.spread(function() {
var args = _.toArray(arguments);
var results = _.reduce(args, function(out, a) {
return out.concat(a);
}, []);
return _.uniq(results, 'name');
});
}
+79 -57
View File
@@ -10,6 +10,8 @@ var defaultBlocks = require('./blocks');
var defaultFilters = require('./filters');
var Loader = require('./loader');
var NODE_ENDARGS = '%%endargs%%';
// Return extension name for a specific block
function blockExtName(name) {
return 'Block'+name+'Extension';
@@ -21,6 +23,12 @@ function normBlockResult(blk) {
return blk;
}
// Extract kwargs from an arguments array
function extractKwargs(args) {
var last = _.last(args);
return (_.isObject(last) && last.__keywords)? args.pop() : {};
}
function TemplateEngine(output) {
this.output = output;
this.book = output.book;
@@ -63,6 +71,14 @@ function TemplateEngine(output) {
// Add default blocks and filters
this.addBlocks(defaultBlocks);
this.addFilters(defaultFilters);
// Build context for this book with depreacted fields
this.ctx = {
template: this,
book: this.book,
output: this.output
};
error.deprecateField(this.ctx, 'generator', this.output.name, '"generator" property is deprecated, use "output.generator" instead');
}
// Bind a function to a context
@@ -71,12 +87,9 @@ TemplateEngine.prototype.bindContext = function(func) {
var that = this;
return function() {
var ctx = {
ctx: this.ctx,
book: that.book,
output: that.output
};
error.deprecateField(ctx, 'generator', that.output.name, '"generator" property is deprecated, use "output.generator" instead');
var ctx = _.extend({
ctx: this.ctx
}, that.ctx);
return func.apply(ctx, arguments);
};
@@ -174,91 +187,100 @@ TemplateEngine.prototype.addBlock = function(name, block) {
this.tags = [name];
this.parse = function(parser, nodes) {
var body = null;
var lastBlockName = null;
var lastBlockArgs = null;
var allBlocks = block.blocks.concat([block.end]);
var subbodies = {};
// Parse first block
var tok = parser.nextToken();
var args = parser.parseSignature(null, true);
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
if (lastBlockName) {
subbodies[lastBlockName] = subbodies[lastBlockName] || [];
subbodies[lastBlockName].push({
body: currentBody,
args: lastBlockArgs
});
} else {
body = currentBody;
}
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
_.each(lastBlockArgs.children, function(child) {
args.addChild(child);
});
// Read new block
lastBlockName = parser.peekToken().value;
lastBlockName = parser.nextToken().value;
// Parse signature and move to the end of the block
if (lastBlockName != block.end) {
lastBlockArgs = parser.parseSignature(null, true);
parser.advanceAfterBlockEnd(lastBlockName);
}
parser.advanceAfterBlockEnd(lastBlockName);
} while (lastBlockName != block.end);
parser.advanceAfterBlockEnd();
var bodies = [body];
_.each(block.blocks, function(blockName) {
subbodies[blockName] = subbodies[blockName] || [];
if (subbodies[blockName].length === 0) {
subbodies[blockName].push({
args: new nodes.NodeList(),
body: new nodes.NodeList()
});
}
bodies.push(subbodies[blockName][0].body);
});
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 args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
var fnArgs = Array.prototype.slice.call(arguments, 1);
// Extract blocks
var blocks = args
.concat([])
.slice(-block.blocks.length);
var args;
var blocks = [];
var bodies = [];
var blockNames;
var blockArgCounts;
var callback;
// Eliminate blocks from list
if (block.blocks.length > 0) args = args.slice(0, -block.blocks.length);
// Extract callback
callback = fnArgs.pop();
// Extract main body and kwargs
var body = args.pop();
var kwargs = _.isObject(_.last(args))? args.pop() : {};
// Detect end of arguments
var endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
// Extract blocks body
var _blocks = _.map(block.blocks, function(blockName, i){
return {
name: blockName,
body: blocks[i]()
};
// 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
_.each(blockNames, 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() {
return that.applyBlock(name, {
body: body(),
args: args,
kwargs: kwargs,
blocks: _blocks
}, context);
return that.applyBlock(name, mainBlock, context);
})
// Process the block returned
+8 -7
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "3.0.0-pre.6",
"version": "3.0.0-pre.9",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -21,16 +21,17 @@
"front-matter": "2.0.7",
"gitbook-asciidoc": "1.1.0",
"gitbook-markdown": "1.2.0",
"gitbook-plugin-fontsettings": "1.0.2",
"gitbook-plugin-fontsettings": "1.0.3",
"gitbook-plugin-highlight": "2.0.2",
"gitbook-plugin-livereload": "0.0.1",
"gitbook-plugin-search": "1.2.0",
"gitbook-plugin-sharing": "1.0.1",
"gitbook-plugin-theme-default": "1.0.0-pre.5",
"gitbook-plugin-search": "2.1.0",
"gitbook-plugin-lunr": "1.0.0",
"gitbook-plugin-theme-default": "1.0.0-pre.8",
"gitbook-plugin-sharing": "1.0.2",
"github-slugid": "1.0.1",
"graceful-fs": "4.1.3",
"i18n-t": "1.0.0",
"ignore": "3.0.14",
"ignore": "3.1.1",
"json-schema-defaults": "0.1.1",
"jsonschema": "1.1.0",
"juice": "1.10.0",
@@ -41,7 +42,7 @@
"npm": "3.8.6",
"npmi": "1.0.1",
"nunjucks": "2.4.1",
"nunjucks-autoescape": "1.0.1",
"nunjucks-do": "1.0.0",
"q": "1.4.1",
"read-installed": "^4.0.3",
"request": "2.70.0",
+43 -22
View File
@@ -68,24 +68,8 @@ function setupDefaultBook(files, summary, opts) {
}), opts);
}
// Output a book with a specific generator
function outputDefaultBook(Output, files, summary, opts) {
return setupDefaultBook(files, summary, opts)
.then(function(book) {
// Parse the book
return book.parse()
// Start generation
.then(function() {
var output = new Output(book);
return output.generate()
.thenResolve(output);
});
});
}
// Output a book with a specific generator
function outputBook(Output, files, opts) {
// Prepare output for a book
function setupOutput(Output, files, opts) {
return setupBook(files, opts)
.then(function(book) {
// Parse the book
@@ -93,13 +77,43 @@ function outputBook(Output, files, opts) {
// Start generation
.then(function() {
var output = new Output(book);
return output.generate()
.thenResolve(output);
return new Output(book);
});
});
}
// Prepare output for a book
function setupDefaultOutput(Output, files, summary, opts) {
return setupDefaultBook(files, summary, opts)
.then(function(book) {
// Parse the book
return book.parse()
// Start generation
.then(function() {
return new Output(book);
});
});
}
// Output a book with a specific generator
function outputDefaultBook(Output, files, summary, opts) {
return setupDefaultOutput(Output, files, summary, opts)
.then(function(output) {
return output.generate()
.thenResolve(output);
});
}
// Output a book with a specific generator
function outputBook(Output, files, opts) {
return setupOutput(Output, files, opts)
.then(function(output) {
return output.generate()
.thenResolve(output);
});
}
// Log an error
function logError(err) {
console.log(err.stack || err);
@@ -107,10 +121,17 @@ function logError(err) {
module.exports = {
fs: nodeFS,
setupFS: setupFS,
setupBook: setupBook,
outputBook: outputBook,
setupDefaultBook: setupDefaultBook,
setupOutput: setupOutput,
setupDefaultOutput: setupDefaultOutput,
outputBook: outputBook,
outputDefaultBook: outputDefaultBook,
logError: logError
};
+10
View File
@@ -0,0 +1,10 @@
{
"name": "gitbook-plugin-dep1",
"version": "1.0.0",
"engines": {
"gitbook": "*"
},
"dependencies": {
"gitbook-plugin-dep2": "*"
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "gitbook-plugin-dep2",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
hooks: {
page: function(page) {
page.sections[0].content = 'Hello (sections) ' + page.content;
return page;
}
}
};
+7
View File
@@ -0,0 +1,7 @@
{
"name": "gitbook-plugin-test-deprecated",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
+4 -2
View File
@@ -1,5 +1,3 @@
var should = require('should');
module.exports = {
hooks: {
'init': function() {
@@ -11,6 +9,10 @@ module.exports = {
},
'finish:before': function() {
global._hooks.push('finish:before');
},
page: function(page) {
page.content = 'Hello ' + page.content;
return page;
}
}
};
+2 -1
View File
@@ -53,9 +53,10 @@ describe('JSON Output', function() {
it('should correctly generate a README.json for the whole book', function() {
output.should.have.file('README.json');
var json = require(output.resolve('README.json'));
json.book.language.should.equal('en');
json.should.have.property('languages')
.with.property('list').with.lengthOf(2);
});
+9 -4
View File
@@ -20,7 +20,8 @@ describe('Website Output', function() {
});
it('should correctly copy assets', function() {
output.should.have.file('gitbook/app.js');
output.should.have.file('gitbook/gitbook.js');
output.should.have.file('gitbook/theme.js');
output.should.have.file('gitbook/images/favicon.ico');
});
@@ -84,12 +85,16 @@ describe('Website Output', function() {
});
it('should correctly copy assets', function() {
output.should.have.file('gitbook/app.js');
output.should.have.file('gitbook/gitbook.js');
output.should.have.file('gitbook/theme.js');
});
it('should not copy assets for each language', function() {
output.should.have.not.file('en/gitbook/app.js');
output.should.have.not.file('fr/gitbook/app.js');
output.should.have.not.file('en/gitbook/gitbook.js');
output.should.have.not.file('fr/gitbook/gitbook.js');
output.should.have.not.file('en/gitbook/theme.js');
output.should.have.not.file('fr/gitbook/theme.js');
});
it('should correctly generate an index.html', function() {
+3 -3
View File
@@ -8,7 +8,7 @@ describe('Page', function() {
return mock.setupDefaultBook({
'README.md': ' # Hello World\n\nThis is a description',
'heading.md': '# Hello\n\n## World',
'description.md': '# This is a title\n\nThis is the short description.\n\nNot this one.',
'description.md': '# This is a title\n\nThis is the short description.\n\nAnd the rest of the description.\n\n# Heading\n\nThis is not in the description',
'frontmatter/description.md': '---\ndescription: Hello World\n---\n\n# This is a title\n\nThis is not the description',
'frontmatter/var.md': '---\ntest: Hello World\n---\n\n{{ page.test }}',
@@ -141,7 +141,7 @@ describe('Page', function() {
return page.toHTML(output)
.then(function() {
page.attributes.description.should.equal('This is the short description.');
page.attributes.description.should.equal('This is the short description. And the rest of the description.');
});
});
});
@@ -289,7 +289,7 @@ describe('Page', function() {
it('should keep external images path', function() {
page.content.should.be.html({
'img[src="https:/upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"]': {
'img[src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"]': {
count: 1
}
});
+37
View File
@@ -6,6 +6,7 @@ var registry = require('../lib/plugins/registry');
var Output = require('../lib/output/base');
var PluginsManager = require('../lib/plugins');
var BookPlugin = require('../lib/plugins/plugin');
var JSONOutput = require('../lib/output/json');
var PLUGINS_ROOT = path.resolve(__dirname, 'node_modules');
@@ -229,6 +230,42 @@ describe('Plugins', function() {
global._hooks.should.deepEqual(['init']);
});
});
describe('Hook "page"', function() {
var pluginDeprecated;
before(function() {
pluginDeprecated = TestPlugin(book, 'test-deprecated');
return pluginDeprecated.load(PLUGINS_ROOT);
});
it('should update content using "content" property', function() {
return mock.setupDefaultOutput(JSONOutput)
.then(function(output) {
output.plugins.load(plugin);
return output.generate()
.then(function() {
var json = require(output.resolve('README.json'));
json.page.content.should.equal('Hello <p>Hello</p>\n');
});
});
});
it('should update content using deprecated "sections" property', function() {
return mock.setupDefaultOutput(JSONOutput)
.then(function(output) {
output.plugins.load(pluginDeprecated);
return output.generate()
.then(function() {
var json = require(output.resolve('README.json'));
json.page.content.should.equal('Hello (sections) <p>Hello</p>\n');
});
});
});
});
});
});