mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 02:53:29 +00:00
Adapt tests for TemplateBlock
This commit is contained in:
@@ -5,7 +5,7 @@ describe('Page', function() {
|
||||
|
||||
describe('toText', function() {
|
||||
it('must not prepend frontmatter if no attributes', function() {
|
||||
const page = Page().merge({
|
||||
const page = (new Page()).merge({
|
||||
content: 'Hello World'
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('Page', function() {
|
||||
});
|
||||
|
||||
it('must prepend frontmatter if attributes', function() {
|
||||
const page = Page().merge({
|
||||
const page = (new Page()).merge({
|
||||
content: 'Hello World',
|
||||
attributes: Immutable.fromJS({
|
||||
hello: 'world'
|
||||
@@ -24,4 +24,3 @@ describe('Page', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,49 +5,56 @@ const Promise = require('../../utils/promise');
|
||||
describe('TemplateBlock', function() {
|
||||
const TemplateBlock = require('../templateBlock');
|
||||
|
||||
describe('create', function() {
|
||||
describe('.create', function() {
|
||||
it('must initialize a simple TemplateBlock from a function', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
return { message: 'Hello World' };
|
||||
});
|
||||
|
||||
// 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('.toProps', function() {
|
||||
it('must handle sync method', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return { message: 'Hello World' };
|
||||
});
|
||||
|
||||
return templateBlock.toProps()
|
||||
.then(function(props) {
|
||||
expect(props).toEqual({ message: 'Hello World' });
|
||||
});
|
||||
});
|
||||
|
||||
it('must not fsil if return a string', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return 'Hello World';
|
||||
});
|
||||
|
||||
return templateBlock.toProps()
|
||||
.then(function(props) {
|
||||
expect(props).toEqual({ children: 'Hello World' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getShortcuts', function() {
|
||||
describe('.getShortcuts', function() {
|
||||
it('must return undefined if no shortcuts', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
return { message: 'Hello World' };
|
||||
});
|
||||
|
||||
expect(templateBlock.getShortcuts()).toNotExist();
|
||||
});
|
||||
|
||||
it('must return complete shortcut', function() {
|
||||
it('.must return complete shortcut', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', {
|
||||
process(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
return { message: 'Hello World' };
|
||||
},
|
||||
shortcuts: {
|
||||
parsers: ['markdown'],
|
||||
@@ -66,45 +73,12 @@ describe('TemplateBlock', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNunjucksExt()', function() {
|
||||
it('should replace by block anchor', function() {
|
||||
describe('.toNunjucksExt()', function() {
|
||||
it('should render children correctly', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return 'Hello';
|
||||
});
|
||||
|
||||
let blocks = {};
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
const env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
const Ext = templateBlock.toNunjucksExt({}, blocks);
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
const src = '{% sayhello %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
blocks = Immutable.fromJS(blocks);
|
||||
expect(blocks.size).toBe(1);
|
||||
|
||||
const blockId = blocks.keySeq().get(0);
|
||||
const 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() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
const env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
@@ -116,15 +90,34 @@ describe('TemplateBlock', function() {
|
||||
const src = '{% sayhello %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('<p>Hello, World!</p>');
|
||||
expect(res).toBe('<xblock name="sayhello" props="{}">Hello</xblock>');
|
||||
});
|
||||
});
|
||||
|
||||
it('must apply block arguments correctly', function() {
|
||||
it('must handle HTML children', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
const env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
const Ext = templateBlock.toNunjucksExt();
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
const src = '{% sayhello %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('<xblock name="sayhello" props="{}"><p>Hello, World!</p></xblock>');
|
||||
});
|
||||
});
|
||||
|
||||
it('must inline props without children', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<' + block.kwargs.tag + '>Hello, ' + block.kwargs.name + '!</' + block.kwargs.tag + '>',
|
||||
parse: true
|
||||
message: block.kwargs.tag + ' ' + block.kwargs.name
|
||||
};
|
||||
});
|
||||
|
||||
@@ -139,17 +132,17 @@ describe('TemplateBlock', function() {
|
||||
const 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>');
|
||||
expect(res).toBe('<xblock name="sayhello" props="{"message":"p Samy"}"></xblock>');
|
||||
});
|
||||
});
|
||||
|
||||
it('must accept an async function', function() {
|
||||
const templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return Promise()
|
||||
.delay(1)
|
||||
.then(function() {
|
||||
return {
|
||||
body: 'Hello ' + block.body,
|
||||
parse: true
|
||||
children: 'Hello ' + block.children
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -165,7 +158,7 @@ describe('TemplateBlock', function() {
|
||||
const src = '{% sayhello %}Samy{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
expect(res).toBe('Hello Samy');
|
||||
expect(res).toBe('<xblock name="sayhello" props="{}">Hello Samy</xblock>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -177,13 +170,10 @@ describe('TemplateBlock', function() {
|
||||
const nested = {};
|
||||
|
||||
block.blocks.forEach(function(blk) {
|
||||
nested[blk.name] = blk.body.trim();
|
||||
nested[blk.name] = blk.children.trim();
|
||||
});
|
||||
|
||||
return {
|
||||
body: '<p class="yoda">' + nested.end + ' ' + nested.start + '</p>',
|
||||
parse: true
|
||||
};
|
||||
return '<p class="yoda">' + nested.end + ' ' + nested.start + '</p>';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -198,7 +188,7 @@ describe('TemplateBlock', function() {
|
||||
const 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>');
|
||||
expect(res).toBe('<xblock name="yoda" props="{}"><p class="yoda">inverted this sentence should be</p></xblock>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const URIIndex = require('../uriIndex');
|
||||
|
||||
describe.only('URIIndex', () => {
|
||||
describe('URIIndex', () => {
|
||||
let index;
|
||||
|
||||
before(() => {
|
||||
@@ -40,6 +40,18 @@ describe.only('URIIndex', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('.resolveToURL', () => {
|
||||
|
||||
it('should resolve a basic file path with directory index', () => {
|
||||
expect(index.resolveToURL('README.md')).toBe('./');
|
||||
});
|
||||
|
||||
it('should resolve a basic file path with directory index', () => {
|
||||
expect(index.resolveToURL('hello/README.md')).toBe('hello/');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.resolveFrom', () => {
|
||||
|
||||
it('should resolve correctly in same directory', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const is = require('is');
|
||||
const extend = require('extend');
|
||||
const Immutable = require('immutable');
|
||||
const { Record, List, Map } = require('immutable');
|
||||
const escape = require('escape-html');
|
||||
|
||||
const Promise = require('../utils/promise');
|
||||
@@ -9,232 +9,236 @@ const TemplateShortcut = require('./templateShortcut');
|
||||
const NODE_ENDARGS = '%%endargs%%';
|
||||
const HTML_TAGNAME = 'xblock';
|
||||
|
||||
const TemplateBlock = Immutable.Record({
|
||||
const DEFAULTS = {
|
||||
// Name of block, also the start tag
|
||||
name: String(),
|
||||
|
||||
name: String(),
|
||||
// End tag, default to "end<name>"
|
||||
end: String(),
|
||||
|
||||
end: String(),
|
||||
// Function to process the block content
|
||||
process: Function(),
|
||||
|
||||
process: Function(),
|
||||
// List of String, for inner block tags
|
||||
blocks: Immutable.List(),
|
||||
|
||||
blocks: List(),
|
||||
// List of shortcuts to replace with this block
|
||||
shortcuts: Immutable.Map()
|
||||
}, 'TemplateBlock');
|
||||
|
||||
TemplateBlock.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
shortcuts: Map()
|
||||
};
|
||||
|
||||
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() {
|
||||
const shortcuts = this.get('shortcuts');
|
||||
if (shortcuts.size === 0) {
|
||||
return undefined;
|
||||
class TemplateBlock extends Record(DEFAULTS) {
|
||||
getName() {
|
||||
return this.get('name');
|
||||
}
|
||||
|
||||
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 || {};
|
||||
|
||||
const that = this;
|
||||
const name = this.getName();
|
||||
const endTag = this.getEndTag();
|
||||
const blocks = this.getBlocks().toJS();
|
||||
|
||||
function Ext() {
|
||||
this.tags = [name];
|
||||
|
||||
this.parse = function(parser, nodes) {
|
||||
let lastBlockName = null;
|
||||
let lastBlockArgs = null;
|
||||
const allBlocks = blocks.concat([endTag]);
|
||||
|
||||
// Parse first block
|
||||
const tok = parser.nextToken();
|
||||
lastBlockArgs = parser.parseSignature(null, true);
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
const args = new nodes.NodeList();
|
||||
const bodies = [];
|
||||
const blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
|
||||
const blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
|
||||
|
||||
// Parse while we found "end<block>"
|
||||
do {
|
||||
// Read body
|
||||
const currentBody = parser.parseUntilBlocks(...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, ...fnArgs) {
|
||||
let args;
|
||||
const blocks = [];
|
||||
let bodies = [];
|
||||
|
||||
// Extract callback
|
||||
const callback = fnArgs.pop();
|
||||
|
||||
// Detect end of arguments
|
||||
const endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
|
||||
|
||||
// Extract arguments and bodies
|
||||
args = fnArgs.slice(0, endArgIndex);
|
||||
bodies = fnArgs.slice(endArgIndex + 1);
|
||||
|
||||
// Extract block counts
|
||||
const blockArgCounts = args.pop();
|
||||
const blockNames = args.pop();
|
||||
|
||||
// Recreate list of blocks
|
||||
blockNames.forEach((blkName, i) => {
|
||||
const countArgs = blockArgCounts[i];
|
||||
const blockBody = bodies.shift();
|
||||
|
||||
const blockArgs = countArgs > 0 ? args.slice(0, countArgs) : [];
|
||||
args = args.slice(countArgs);
|
||||
const blockKwargs = extractKwargs(blockArgs);
|
||||
|
||||
blocks.push({
|
||||
name: blkName,
|
||||
children: blockBody(),
|
||||
args: blockArgs,
|
||||
kwargs: blockKwargs
|
||||
});
|
||||
});
|
||||
|
||||
const mainBlock = blocks.shift();
|
||||
mainBlock.blocks = blocks;
|
||||
|
||||
Promise()
|
||||
.then(function() {
|
||||
const ctx = extend({
|
||||
ctx: context
|
||||
}, mainContext || {});
|
||||
|
||||
return that.applyBlock(mainBlock, ctx);
|
||||
})
|
||||
.then(function(props) {
|
||||
return that.blockResultToHtml(props);
|
||||
})
|
||||
.nodeify(callback);
|
||||
};
|
||||
getEndTag() {
|
||||
return this.get('end') || ('end' + this.getName());
|
||||
}
|
||||
|
||||
return Ext;
|
||||
};
|
||||
getProcess() {
|
||||
return this.get('process');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a block to a content.
|
||||
*
|
||||
* @param {Object} inner
|
||||
* @param {Object} context
|
||||
* @return {Promise<Props>|Props}
|
||||
*/
|
||||
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
||||
const processFn = this.getProcess();
|
||||
getBlocks() {
|
||||
return this.get('blocks');
|
||||
}
|
||||
|
||||
inner = inner || {};
|
||||
inner.args = inner.args || [];
|
||||
inner.kwargs = inner.kwargs || {};
|
||||
inner.blocks = inner.blocks || [];
|
||||
|
||||
return processFn.call(context, inner);
|
||||
};
|
||||
/**
|
||||
* Return shortcuts associated with this block or undefined
|
||||
* @return {TemplateShortcut|undefined}
|
||||
*/
|
||||
getShortcuts() {
|
||||
const shortcuts = this.get('shortcuts');
|
||||
if (shortcuts.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a block props to HTML. This HTML is then being
|
||||
* parsed by gitbook-core during rendering, and binded to the right react components.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {String}
|
||||
*/
|
||||
TemplateBlock.prototype.blockResultToHtml = function(props) {
|
||||
const { children, ...innerProps } = props;
|
||||
const payload = escape(JSON.stringify(innerProps));
|
||||
return TemplateShortcut.createForBlock(this, shortcuts);
|
||||
}
|
||||
|
||||
return (
|
||||
`<${HTML_TAGNAME} name="${this.name}" props="${payload}">${children}</${HTML_TAGNAME}>`
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Return name for the nunjucks extension
|
||||
* @return {String}
|
||||
*/
|
||||
getExtensionName() {
|
||||
return 'Block' + this.getName() + 'Extension';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
/**
|
||||
* Return a nunjucks extension to represents this block
|
||||
* @return {Nunjucks.Extension}
|
||||
*/
|
||||
toNunjucksExt(mainContext = {}) {
|
||||
const that = this;
|
||||
const name = this.getName();
|
||||
const endTag = this.getEndTag();
|
||||
const blocks = this.getBlocks().toJS();
|
||||
|
||||
function Ext() {
|
||||
this.tags = [name];
|
||||
|
||||
this.parse = (parser, nodes) => {
|
||||
let lastBlockName = null;
|
||||
let lastBlockArgs = null;
|
||||
const allBlocks = blocks.concat([endTag]);
|
||||
|
||||
// Parse first block
|
||||
const tok = parser.nextToken();
|
||||
lastBlockArgs = parser.parseSignature(null, true);
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
const args = new nodes.NodeList();
|
||||
const bodies = [];
|
||||
const blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
|
||||
const blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
|
||||
|
||||
// Parse while we found "end<block>"
|
||||
do {
|
||||
// Read body
|
||||
const currentBody = parser.parseUntilBlocks(...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 = (context, ...fnArgs) => {
|
||||
let args;
|
||||
const blocks = [];
|
||||
let bodies = [];
|
||||
|
||||
// Extract callback
|
||||
const callback = fnArgs.pop();
|
||||
|
||||
// Detect end of arguments
|
||||
const endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
|
||||
|
||||
// Extract arguments and bodies
|
||||
args = fnArgs.slice(0, endArgIndex);
|
||||
bodies = fnArgs.slice(endArgIndex + 1);
|
||||
|
||||
// Extract block counts
|
||||
const blockArgCounts = args.pop();
|
||||
const blockNames = args.pop();
|
||||
|
||||
// Recreate list of blocks
|
||||
blockNames.forEach((blkName, i) => {
|
||||
const countArgs = blockArgCounts[i];
|
||||
const blockBody = bodies.shift();
|
||||
|
||||
const blockArgs = countArgs > 0 ? args.slice(0, countArgs) : [];
|
||||
args = args.slice(countArgs);
|
||||
const blockKwargs = extractKwargs(blockArgs);
|
||||
|
||||
blocks.push({
|
||||
name: blkName,
|
||||
children: blockBody(),
|
||||
args: blockArgs,
|
||||
kwargs: blockKwargs
|
||||
});
|
||||
});
|
||||
|
||||
const mainBlock = blocks.shift();
|
||||
mainBlock.blocks = blocks;
|
||||
|
||||
Promise()
|
||||
.then(function() {
|
||||
const ctx = extend({
|
||||
ctx: context
|
||||
}, mainContext);
|
||||
|
||||
return that.toProps(mainBlock, ctx);
|
||||
})
|
||||
.then(function(props) {
|
||||
return that.toHTML(props);
|
||||
})
|
||||
.nodeify(callback);
|
||||
};
|
||||
}
|
||||
|
||||
return Ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a block an return the props
|
||||
*
|
||||
* @param {Object} inner
|
||||
* @param {Object} context
|
||||
* @return {Promise<Props>}
|
||||
*/
|
||||
toProps(inner, context) {
|
||||
const processFn = this.getProcess();
|
||||
|
||||
inner = inner || {};
|
||||
inner.args = inner.args || [];
|
||||
inner.kwargs = inner.kwargs || {};
|
||||
inner.blocks = inner.blocks || [];
|
||||
|
||||
return Promise()
|
||||
.then(() => processFn.call(context, inner))
|
||||
.then(props => {
|
||||
if (is.string(props)) {
|
||||
return { children: props };
|
||||
}
|
||||
|
||||
return props;
|
||||
});
|
||||
}
|
||||
|
||||
block = new TemplateBlock(block);
|
||||
block = block.set('name', blockName);
|
||||
return block;
|
||||
};
|
||||
/**
|
||||
* Convert a block props to HTML. This HTML is then being
|
||||
* parsed by gitbook-core during rendering, and binded to the right react components.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {String}
|
||||
*/
|
||||
toHTML(props) {
|
||||
const { children, ...innerProps } = props;
|
||||
const payload = escape(JSON.stringify(innerProps));
|
||||
|
||||
return (
|
||||
`<${HTML_TAGNAME} name="${this.name}" props="${payload}">${children || ''}</${HTML_TAGNAME}>`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a template block from a function or an object
|
||||
* @param {String} blockName
|
||||
* @param {Object} block
|
||||
* @return {TemplateBlock}
|
||||
*/
|
||||
static create(blockName, block) {
|
||||
if (is.fn(block)) {
|
||||
block = new Map({
|
||||
process: block
|
||||
});
|
||||
}
|
||||
|
||||
block = new TemplateBlock(block);
|
||||
block = block.set('name', blockName);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract kwargs from an arguments array
|
||||
|
||||
@@ -1,139 +1,133 @@
|
||||
const nunjucks = require('nunjucks');
|
||||
const Immutable = require('immutable');
|
||||
|
||||
const TemplateEngine = Immutable.Record({
|
||||
// Map of {TemplateBlock}
|
||||
blocks: Immutable.Map(),
|
||||
const { Record, Map, List } = require('immutable');
|
||||
|
||||
const DEFAULTS = {
|
||||
// List of {TemplateBlock}
|
||||
blocks: List(),
|
||||
// Map of Extension
|
||||
extensions: Immutable.Map(),
|
||||
|
||||
extensions: Map(),
|
||||
// Map of filters: {String} name -> {Function} fn
|
||||
filters: Immutable.Map(),
|
||||
|
||||
filters: Map(),
|
||||
// Map of globals: {String} name -> {Mixed}
|
||||
globals: Immutable.Map(),
|
||||
|
||||
globals: 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');
|
||||
};
|
||||
class TemplateEngine extends Record(DEFAULTS) {
|
||||
getBlocks() {
|
||||
return this.get('blocks');
|
||||
}
|
||||
|
||||
TemplateEngine.prototype.getFilters = function() {
|
||||
return this.get('filters');
|
||||
};
|
||||
getGlobals() {
|
||||
return this.get('globals');
|
||||
}
|
||||
|
||||
TemplateEngine.prototype.getShortcuts = function() {
|
||||
return this.get('shortcuts');
|
||||
};
|
||||
getFilters() {
|
||||
return this.get('filters');
|
||||
}
|
||||
|
||||
TemplateEngine.prototype.getLoader = function() {
|
||||
return this.get('loader');
|
||||
};
|
||||
getShortcuts() {
|
||||
return this.get('shortcuts');
|
||||
}
|
||||
|
||||
TemplateEngine.prototype.getContext = function() {
|
||||
return this.get('context');
|
||||
};
|
||||
getLoader() {
|
||||
return this.get('loader');
|
||||
}
|
||||
|
||||
TemplateEngine.prototype.getExtensions = function() {
|
||||
return this.get('extensions');
|
||||
};
|
||||
getContext() {
|
||||
return this.get('context');
|
||||
}
|
||||
|
||||
/**
|
||||
Return a block by its name (or undefined)
|
||||
getExtensions() {
|
||||
return this.get('extensions');
|
||||
}
|
||||
|
||||
@param {String} name
|
||||
@return {TemplateBlock}
|
||||
*/
|
||||
TemplateEngine.prototype.getBlock = function(name) {
|
||||
const blocks = this.getBlocks();
|
||||
return blocks.find(function(block) {
|
||||
return block.getName() === name;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Return a block by its name (or undefined).
|
||||
* @param {String} name
|
||||
* @return {TemplateBlock} block?
|
||||
*/
|
||||
getBlock(name) {
|
||||
const blocks = this.getBlocks();
|
||||
return blocks.find(function(block) {
|
||||
return block.getName() === name;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Return a nunjucks environment from this configuration
|
||||
/**
|
||||
* Return a nunjucks environment from this configuration
|
||||
* @return {Nunjucks.Environment} env
|
||||
*/
|
||||
toNunjucks() {
|
||||
const loader = this.getLoader();
|
||||
const blocks = this.getBlocks();
|
||||
const filters = this.getFilters();
|
||||
const globals = this.getGlobals();
|
||||
const extensions = this.getExtensions();
|
||||
const context = this.getContext();
|
||||
|
||||
@return {Nunjucks.Environment}
|
||||
*/
|
||||
TemplateEngine.prototype.toNunjucks = function(blocksOutput) {
|
||||
const loader = this.getLoader();
|
||||
const blocks = this.getBlocks();
|
||||
const filters = this.getFilters();
|
||||
const globals = this.getGlobals();
|
||||
const extensions = this.getExtensions();
|
||||
const context = this.getContext();
|
||||
const env = new nunjucks.Environment(
|
||||
loader,
|
||||
{
|
||||
// Escaping is done after by the asciidoc/markdown parser
|
||||
autoescape: false,
|
||||
|
||||
const env = new nunjucks.Environment(
|
||||
loader,
|
||||
{
|
||||
// Escaping is done after by the asciidoc/markdown parser
|
||||
autoescape: false,
|
||||
|
||||
// Syntax
|
||||
tags: {
|
||||
blockStart: '{%',
|
||||
blockEnd: '%}',
|
||||
variableStart: '{{',
|
||||
variableEnd: '}}',
|
||||
commentStart: '{###',
|
||||
commentEnd: '###}'
|
||||
// Syntax
|
||||
tags: {
|
||||
blockStart: '{%',
|
||||
blockEnd: '%}',
|
||||
variableStart: '{{',
|
||||
variableEnd: '}}',
|
||||
commentStart: '{###',
|
||||
commentEnd: '###}'
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
// Add filters
|
||||
filters.forEach(function(filterFn, filterName) {
|
||||
env.addFilter(filterName, filterFn.bind(context));
|
||||
});
|
||||
// Add filters
|
||||
filters.forEach(function(filterFn, filterName) {
|
||||
env.addFilter(filterName, filterFn.bind(context));
|
||||
});
|
||||
|
||||
// Add blocks
|
||||
blocks.forEach(function(block) {
|
||||
const extName = block.getExtensionName();
|
||||
const Ext = block.toNunjucksExt(context, blocksOutput);
|
||||
// Add blocks
|
||||
blocks.forEach(function(block) {
|
||||
const extName = block.getExtensionName();
|
||||
const Ext = block.toNunjucksExt(context);
|
||||
|
||||
env.addExtension(extName, new Ext());
|
||||
});
|
||||
env.addExtension(extName, new Ext());
|
||||
});
|
||||
|
||||
// Add globals
|
||||
globals.forEach(function(globalValue, globalName) {
|
||||
env.addGlobal(globalName, globalValue);
|
||||
});
|
||||
// Add globals
|
||||
globals.forEach(function(globalValue, globalName) {
|
||||
env.addGlobal(globalName, globalValue);
|
||||
});
|
||||
|
||||
// Add other extensions
|
||||
extensions.forEach(function(ext, extName) {
|
||||
env.addExtension(extName, ext);
|
||||
});
|
||||
// Add other extensions
|
||||
extensions.forEach(function(ext, extName) {
|
||||
env.addExtension(extName, ext);
|
||||
});
|
||||
|
||||
return env;
|
||||
};
|
||||
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
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Create a template engine.
|
||||
* @param {Object} def
|
||||
* @return {TemplateEngine} engine
|
||||
*/
|
||||
static create(def) {
|
||||
return new TemplateEngine({
|
||||
blocks: List(def.blocks || []),
|
||||
extensions: Map(def.extensions || {}),
|
||||
filters: Map(def.filters || {}),
|
||||
globals: Map(def.globals || {}),
|
||||
context: def.context,
|
||||
loader: def.loader
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateEngine;
|
||||
|
||||
@@ -3,7 +3,7 @@ const { OrderedMap } = require('immutable');
|
||||
const parseURIIndexFromPages = require('../parseURIIndexFromPages');
|
||||
const Page = require('../../models/page');
|
||||
|
||||
describe.only('parseURIIndexFromPages', () => {
|
||||
describe('parseURIIndexFromPages', () => {
|
||||
|
||||
it('should map file to html', () => {
|
||||
const pages = OrderedMap({
|
||||
@@ -14,24 +14,13 @@ describe.only('parseURIIndexFromPages', () => {
|
||||
expect(urls.resolve('page.md')).toBe('page.html');
|
||||
});
|
||||
|
||||
it('should map README to index.html (directoryIndex: false)', () => {
|
||||
const pages = OrderedMap({
|
||||
'hello/README.md': new Page()
|
||||
});
|
||||
const urls = parseURIIndexFromPages(pages, {
|
||||
directoryIndex: false
|
||||
});
|
||||
|
||||
expect(urls.resolve('hello/README.md')).toBe('hello/index.html');
|
||||
});
|
||||
|
||||
it('should map README to folder', () => {
|
||||
const pages = OrderedMap({
|
||||
'hello/README.md': new Page()
|
||||
});
|
||||
const urls = parseURIIndexFromPages(pages);
|
||||
|
||||
expect(urls.resolve('hello/README.md')).toBe('hello/');
|
||||
expect(urls.resolveToURL('hello/README.md')).toBe('hello/');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -4,30 +4,34 @@ const TemplateEngine = require('../../models/templateEngine');
|
||||
const renderTemplate = require('../render');
|
||||
const ConrefsLoader = require('../conrefsLoader');
|
||||
|
||||
describe('ConrefsLoader', function() {
|
||||
describe('ConrefsLoader', () => {
|
||||
const dirName = __dirname + '/';
|
||||
const fileName = path.join(dirName, 'test.md');
|
||||
|
||||
describe('Git', function() {
|
||||
const engine = TemplateEngine({
|
||||
loader: new ConrefsLoader(dirName)
|
||||
describe('Git', () => {
|
||||
let engine;
|
||||
|
||||
before(() => {
|
||||
engine = new TemplateEngine({
|
||||
loader: new ConrefsLoader(dirName)
|
||||
});
|
||||
});
|
||||
|
||||
it('should include content from git', function() {
|
||||
it('should include content from git', () => {
|
||||
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() {
|
||||
it('should handle deep inclusion (1)', () => {
|
||||
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() {
|
||||
it('should handle deep inclusion (2)', () => {
|
||||
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');
|
||||
@@ -35,20 +39,24 @@ describe('ConrefsLoader', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Local', function() {
|
||||
const engine = TemplateEngine({
|
||||
loader: new ConrefsLoader(dirName)
|
||||
describe('Local', () => {
|
||||
let engine;
|
||||
|
||||
before(() => {
|
||||
engine = new TemplateEngine({
|
||||
loader: new ConrefsLoader(dirName)
|
||||
});
|
||||
});
|
||||
|
||||
describe('Relative', function() {
|
||||
it('should resolve basic relative filepath', function() {
|
||||
describe('Relative', () => {
|
||||
it('should resolve basic relative filepath', () => {
|
||||
return renderTemplate(engine, fileName, '{% include "include.md" %}')
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('Hello World');
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve basic parent filepath', function() {
|
||||
it('should resolve basic parent filepath', () => {
|
||||
return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "../include.md" %}')
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('Hello World');
|
||||
@@ -57,23 +65,24 @@ describe('ConrefsLoader', function() {
|
||||
});
|
||||
|
||||
describe('Absolute', function() {
|
||||
it('should resolve absolute filepath', function() {
|
||||
it('should resolve absolute filepath', () => {
|
||||
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() {
|
||||
it('should resolve absolute filepath when in a directory', () => {
|
||||
return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "/include.md" %}')
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('Hello World');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('transform', function() {
|
||||
describe('transform', () => {
|
||||
function transform(filePath, source) {
|
||||
expect(filePath).toBeA('string');
|
||||
expect(source).toBeA('string');
|
||||
@@ -83,11 +92,16 @@ describe('ConrefsLoader', function() {
|
||||
|
||||
return 'test-' + source + '-endtest';
|
||||
}
|
||||
const engine = TemplateEngine({
|
||||
loader: new ConrefsLoader(dirName, transform)
|
||||
|
||||
let engine;
|
||||
|
||||
before(() => {
|
||||
engine = new TemplateEngine({
|
||||
loader: new ConrefsLoader(dirName, transform)
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform included content', function() {
|
||||
it('should transform included content', () => {
|
||||
return renderTemplate(engine, fileName, '{% include "include.md" %}')
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('test-Hello World-endtest');
|
||||
@@ -95,4 +109,3 @@ describe('ConrefsLoader', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user