UNPKG

3.42 kBJavaScriptView Raw
1'use strict';
2
3var isFalsey = require('falsey');
4var delims = require('delimiter-regex');
5var get = require('get-value');
6
7/**
8 * Expose `layouts`
9 */
10
11module.exports = layouts;
12
13/**
14 * Wrap a string one or more layouts.
15 *
16 * @param {String} `str` The content string to be wrapped with a layout.
17 * @param {String} `key` The object key of the starting layout.
18 * @param {Object} `templates` Object of layouts.
19 * @param {Object} `options`
20 * @option {Object} [options] `layoutDelims` Custom delimiters to use.
21 * @option {Object} [options] `defaultLayout` The name (key) of the default layout to use.
22 * @return {String} String wrapped with a layout or layouts.
23 * @api public
24 */
25
26function layouts(str, key, templates, opts, fn) {
27 if (typeof str !== 'string') {
28 throw new TypeError('layouts expects a string');
29 }
30
31 if (typeof opts === 'function') {
32 fn = opts; opts = {};
33 }
34
35 opts = opts || {};
36 var template = {}, prev, i = 0;
37 var res = {options: {}, stack: []};
38
39 while (key && (prev !== key) && (template = templates[key])) {
40 var delims = opts.layoutDelims;
41
42 // `context` is passed to `interpolate` to resolve templates
43 // to the values on the context object.
44 var context = {};
45 context[opts.tag || 'body'] = str;
46
47 // get the context for the layout and push it onto `stack`
48 var obj = {};
49 obj.layout = template;
50 obj.layout.key = key;
51 obj.before = str;
52 obj.depth = i++;
53
54 str = interpolate(template.content, context, delims);
55 obj.after = str;
56
57 if (typeof fn === 'function') {
58 fn(obj, res, i);
59 }
60
61 res.stack.push(obj);
62 prev = key;
63 key = assertLayout(template.layout, opts.defaultLayout);
64 }
65
66 res.options = opts;
67 res.result = str;
68 return res;
69};
70
71/**
72 * Assert whether or not a layout should be used based on
73 * the given `value`.
74 *
75 * - If a layout should be used, the name of the layout is returned.
76 * - If not, `null` is returned.
77 *
78 * @param {*} `value`
79 * @return {String|Null} Returns `true` or `null`.
80 * @api private
81 */
82
83function assertLayout(value, defaultLayout) {
84 if (value === false || (value && isFalsey(value))) {
85 return null;
86 } else if (!value || value === true) {
87 return defaultLayout || null;
88 } else {
89 return value;
90 }
91}
92
93/**
94 * Cache compiled regexps to prevent runtime
95 * compilation for the same delimiter strings
96 * multiple times (this trick can be used for
97 * any compiled regex)
98 */
99
100var cache = {};
101
102/**
103 * Resolve template strings to the values on the given
104 * `context` object.
105 */
106
107function interpolate(content, context, syntax) {
108 var re = makeDelimiterRegex(syntax);
109 return toString(content).replace(re, function(_, $1) {
110 if ($1.indexOf('.') !== -1) {
111 return toString(get(context, $1.trim()));
112 }
113 return context[$1.trim()];
114 });
115}
116
117/**
118 * Make delimiter regex.
119 *
120 * @param {Sring|Array|RegExp} `syntax`
121 * @return {RegExp}
122 */
123
124function makeDelimiterRegex(syntax) {
125 if (!syntax) return /\{% ([^{}]+?) %}/g;
126 if (syntax instanceof RegExp) {
127 return syntax;
128 }
129 if (typeof syntax === 'string') {
130 return new RegExp(syntax, 'g');
131 }
132 var key = syntax.toString();
133 if (cache.hasOwnProperty(key)) {
134 return cache[key];
135 }
136 if (Array.isArray(syntax)) {
137 return (cache[syntax] = delims(syntax));
138 }
139}
140
141/**
142 * Cast `val` to a string.
143 */
144
145function toString(val){
146 return val == null ? '' : val.toString();
147}