UNPKG

8.11 kBJavaScriptView Raw
1'use strict';
2
3var fs = require('fs');
4var utils = require('lazy-cache')(require);
5var fn = require;
6require = utils; // eslint-disable-line
7
8/**
9 * Lazily invoked module dependencies
10 */
11
12require('array-sort', 'sortBy');
13require('async-each', 'each');
14require('base-data');
15require('base-engines', 'engines');
16require('base-helpers', 'helpers');
17require('base-option', 'option');
18require('base-plugins', 'plugin');
19require('base-routes', 'routes');
20require('deep-bind', 'bindAll');
21require('define-property', 'define');
22require('engine-base', 'engine');
23require('extend-shallow', 'extend');
24require('falsey', 'isFalsey');
25require('get-value', 'get');
26require('get-view');
27require('group-array', 'groupBy');
28require('has-glob');
29require('has-value', 'has');
30require('inflection', 'inflect');
31require('is-valid-app', 'isValid');
32require('layouts');
33require('match-file');
34require('mixin-deep', 'merge');
35require('paginationator');
36require('pascalcase', 'pascal');
37require('set-value', 'set');
38require('template-error', 'rethrow');
39require = fn; // eslint-disable-line
40
41/**
42 * Expose default router methods used in all Template instances
43 */
44
45utils.methods = [
46 'onLoad',
47 'preCompile',
48 'preLayout',
49 'onLayout',
50 'postLayout',
51 'onMerge',
52 'onStream',
53 'postCompile',
54 'preRender',
55 'postRender',
56 'preWrite',
57 'postWrite'
58];
59
60utils.constructorKeys = [
61 'Collection',
62 'Group',
63 'Item',
64 'List',
65 'View',
66 'Views'
67];
68
69/**
70 * Options keys
71 */
72
73utils.optsKeys = [
74 'renameKey',
75 'namespaceData',
76 'mergePartials',
77 'rethrow',
78 'nocase',
79 'nonull',
80 'rename',
81 'cwd'
82];
83
84utils.endsWith = function(str, sub) {
85 return str.slice(-sub.length) === sub;
86};
87
88/**
89 * Return true if file exists and is not a directory.
90 */
91
92utils.fileExists = function(filepath) {
93 try {
94 return fs.statSync(filepath).isDirectory() === false;
95 } catch (err) {}
96 return false;
97};
98
99/**
100 * Keep a history of engines used to compile a view, and
101 * the compiled function for each, allowing the compiled
102 * function to be used again the next time the same view
103 * is rendered by the same engine.
104 *
105 * @param {Object} view
106 * @param {String} engine
107 * @param {Function} fn
108 */
109
110utils.engineStack = function(view, engine, fn, content) {
111 if (typeof view.engineStack === 'undefined') {
112 view.engineStack = {};
113 }
114 if (engine && engine.charAt(0) !== '.') {
115 engine = '.' + engine;
116 }
117 view.engineStack[engine] = fn;
118 view.engineStack[engine].content = content;
119};
120
121/**
122 * Return true if a template is a partial
123 */
124
125utils.isPartial = function(view) {
126 if (typeof view.isType !== 'function') {
127 return false;
128 }
129 if (typeof view.options === 'undefined') {
130 return false;
131 }
132 if (typeof view.options.viewType === 'undefined') {
133 return false;
134 }
135 return view.isType('partial');
136};
137
138/**
139 * Return true if a template is renderable, and not a partial or layout
140 */
141
142utils.isRenderable = function(view) {
143 if (typeof view.isType !== 'function') {
144 return false;
145 }
146 if (typeof view.options === 'undefined') {
147 return false;
148 }
149 if (typeof view.options.viewType === 'undefined') {
150 return false;
151 }
152 return view.isType('renderable') && view.viewType.length === 1;
153};
154
155/**
156 * When a constructor is defined after init, update any underlying
157 * properties that may rely on that option (constructor).
158 */
159
160utils.updateOptions = function(app, key, value) {
161 var k = utils.constructorKeys;
162 if (k.indexOf(key) > -1) {
163 app.define(key, value);
164 }
165 if (key === 'layout') {
166 app.viewTypes.renderable.forEach(function(name) {
167 app[name].option('layout', value);
168 });
169 }
170};
171
172/**
173 * Return the given value as-is.
174 */
175
176utils.identity = function(val) {
177 return val;
178};
179
180/**
181 * Arrayify the given value by casting it to an array.
182 */
183
184utils.arrayify = function(val) {
185 return val ? (Array.isArray(val) ? val : [val]) : [];
186};
187
188/**
189 * Return the last element in an array or array-like object.
190 */
191
192utils.last = function(array, n) {
193 return array[array.length - (n || 1)];
194};
195
196/**
197 * Return true if the given value is an object.
198 * @return {Boolean}
199 */
200
201utils.isObject = function(val) {
202 if (!val || Array.isArray(val)) {
203 return false;
204 }
205 return typeof val === 'function'
206 || typeof val === 'object';
207};
208
209/**
210 * Return true if the given value is a string.
211 */
212
213utils.isString = function(val) {
214 return val && typeof val === 'string';
215};
216
217/**
218 * Return true if the given value is a buffer
219 */
220
221utils.isBuffer = function(val) {
222 if (val && val.constructor && typeof val.constructor.isBuffer === 'function') {
223 return val.constructor.isBuffer(val);
224 }
225 return false;
226};
227
228/**
229 * Return true if the given value is a stream.
230 */
231
232utils.isStream = function(val) {
233 return utils.isObject(val)
234 && (typeof val.pipe === 'function')
235 && (typeof val.on === 'function');
236};
237
238/**
239 * Assign own properties from provider to receiver, but only
240 * if the receiving object does not already have a value.
241 */
242
243utils.defaults = function(target) {
244 var args = [].slice.call(arguments, 1);
245 var len = args.length;
246 var i = -1;
247
248 while (++i < len) {
249 var obj = args[i];
250
251 for (var key in obj) {
252 target[key] = target[key] || obj[key];
253 }
254 }
255 return target;
256};
257
258/**
259 * Return true if the given value is an object.
260 * @return {Boolean}
261 */
262
263utils.isOptions = function(val) {
264 return utils.isObject(val) && val.hasOwnProperty('hash');
265};
266
267/**
268 * Format a helper error.
269 * TODO: create an error class for helpers
270 */
271
272utils.helperError = function(app, helperName, viewName, cb) {
273 var err = new Error('helper "' + helperName + '" cannot find "' + viewName + '"');
274 app.emit('error', err);
275 if (typeof cb === 'function') {
276 return cb(err);
277 } else {
278 throw err;
279 }
280};
281
282/**
283 * Convenience method for setting `this.isFoo` and `this._name = 'Foo'
284 * on the given `app`
285 */
286
287utils.setInstanceNames = function setInstanceNames(app, name) {
288 utils.define(app, 'is' + utils.pascal(name), true);
289 utils.define(app, '_name', name);
290};
291
292/**
293 * Singularize the given `name`
294 */
295
296utils.single = function single(name) {
297 return utils.inflect.singularize(name);
298};
299
300/**
301 * Pluralize the given `name`
302 */
303
304utils.plural = function plural(name) {
305 return utils.inflect.pluralize(name);
306};
307
308/**
309 * Ensure file extensions are formatted properly for lookups.
310 *
311 * ```js
312 * utils.formatExt('hbs');
313 * //=> '.hbs'
314 *
315 * utils.formatExt('.hbs');
316 * //=> '.hbs'
317 * ```
318 *
319 * @param {String} `ext` File extension
320 * @return {String}
321 * @api public
322 */
323
324utils.formatExt = function formatExt(ext) {
325 if (typeof ext !== 'string') {
326 throw new Error('utils.formatExt() expects `ext` to be a string.');
327 }
328 if (ext.charAt(0) !== '.') {
329 return '.' + ext;
330 }
331 return ext;
332};
333
334/**
335 * Return true if the given value looks like a
336 * `view` object.
337 */
338
339utils.isItem = utils.isView = function(val) {
340 if (!utils.isObject(val)) return false;
341 return val.hasOwnProperty('content')
342 || val.hasOwnProperty('contents')
343 || val.hasOwnProperty('path');
344};
345
346/**
347 * Get locals from helper arguments.
348 *
349 * @param {Object} `locals`
350 * @param {Object} `options`
351 */
352
353utils.getLocals = function(locals, options) {
354 options = options || {};
355 locals = locals || {};
356 var ctx = {};
357
358 if (options.hasOwnProperty('hash')) {
359 utils.extend(ctx, options.hash);
360 delete options.hash;
361 }
362 if (locals.hasOwnProperty('hash')) {
363 utils.extend(ctx, locals.hash);
364 delete locals.hash;
365 }
366 utils.extend(ctx, options);
367 utils.extend(ctx, locals);
368 return ctx;
369};
370
371/**
372 * Used on `collection` or `app` to resolve the name of the engine to use, or the file
373 * extension to use for the engine.
374 *
375 * If `options.resolveEngine` is a function, it will be
376 * used to resolve the engine.
377 *
378 * @param {Object} `view`
379 * @param {Object} `locals`
380 * @param {Object} `opts`
381 * @return {String}
382 */
383
384utils.resolveEngineExt = function(view, locals, opts) {
385 var engine = locals.engine || view.engine || opts.engine;
386 var fn = opts.resolveEngine
387 || locals.resolveEngine
388 || utils.identity;
389 return fn(engine);
390};
391
392/**
393 * Expose utils
394 */
395
396module.exports = utils;