UNPKG

4.37 kBJavaScriptView Raw
1'use strict';
2
3/**
4 * Merge two attribute objects giving precedence
5 * to values in object `b`. Classes are special-cased
6 * allowing for arrays and merging/joining appropriately
7 * resulting in a string.
8 *
9 * @param {Object} a
10 * @param {Object} b
11 * @return {Object} a
12 * @api private
13 */
14
15exports.merge = function merge(a, b) {
16 if (arguments.length === 1) {
17 var attrs = a[0];
18 for (var i = 1; i < a.length; i++) {
19 attrs = merge(attrs, a[i]);
20 }
21 return attrs;
22 }
23 var ac = a['class'];
24 var bc = b['class'];
25
26 if (ac || bc) {
27 ac = ac || [];
28 bc = bc || [];
29 if (!Array.isArray(ac)) ac = [ac];
30 if (!Array.isArray(bc)) bc = [bc];
31 a['class'] = ac.concat(bc).filter(nulls);
32 }
33
34 for (var key in b) {
35 if (key != 'class') {
36 a[key] = b[key];
37 }
38 }
39
40 return a;
41};
42
43/**
44 * Filter null `val`s.
45 *
46 * @param {*} val
47 * @return {Boolean}
48 * @api private
49 */
50
51function nulls(val) {
52 return val != null && val !== '';
53}
54
55/**
56 * join array as classes.
57 *
58 * @param {*} val
59 * @return {String}
60 */
61exports.joinClasses = joinClasses;
62function joinClasses(val) {
63 return Array.isArray(val) ? val.map(joinClasses).filter(nulls).join(' ') : val;
64}
65
66/**
67 * Render the given classes.
68 *
69 * @param {Array} classes
70 * @param {Array.<Boolean>} escaped
71 * @return {String}
72 */
73exports.cls = function cls(classes, escaped) {
74 var buf = [];
75 for (var i = 0; i < classes.length; i++) {
76 if (escaped && escaped[i]) {
77 buf.push(exports.escape(joinClasses([classes[i]])));
78 } else {
79 buf.push(joinClasses(classes[i]));
80 }
81 }
82 var text = joinClasses(buf);
83 if (text.length) {
84 return ' class="' + text + '"';
85 } else {
86 return '';
87 }
88};
89
90/**
91 * Render the given attribute.
92 *
93 * @param {String} key
94 * @param {String} val
95 * @param {Boolean} escaped
96 * @param {Boolean} terse
97 * @return {String}
98 */
99exports.attr = function attr(key, val, escaped, terse) {
100 if ('boolean' == typeof val || null == val) {
101 if (val) {
102 return ' ' + (terse ? key : key + '="' + key + '"');
103 } else {
104 return '';
105 }
106 } else if (0 == key.indexOf('data') && 'string' != typeof val) {
107 return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, '&apos;') + "'";
108 } else if (escaped) {
109 return ' ' + key + '="' + exports.escape(val) + '"';
110 } else {
111 return ' ' + key + '="' + val + '"';
112 }
113};
114
115/**
116 * Render the given attributes object.
117 *
118 * @param {Object} obj
119 * @param {Object} escaped
120 * @return {String}
121 */
122exports.attrs = function attrs(obj, terse){
123 var buf = [];
124
125 var keys = Object.keys(obj);
126
127 if (keys.length) {
128 for (var i = 0; i < keys.length; ++i) {
129 var key = keys[i]
130 , val = obj[key];
131
132 if ('class' == key) {
133 if (val = joinClasses(val)) {
134 buf.push(' ' + key + '="' + val + '"');
135 }
136 } else {
137 buf.push(exports.attr(key, val, false, terse));
138 }
139 }
140 }
141
142 return buf.join('');
143};
144
145/**
146 * Escape the given string of `html`.
147 *
148 * @param {String} html
149 * @return {String}
150 * @api private
151 */
152
153exports.escape = function escape(html){
154 var result = String(html)
155 .replace(/&/g, '&amp;')
156 .replace(/</g, '&lt;')
157 .replace(/>/g, '&gt;')
158 .replace(/"/g, '&quot;');
159 if (result === '' + html) return html;
160 else return result;
161};
162
163/**
164 * Re-throw the given `err` in context to the
165 * the jade in `filename` at the given `lineno`.
166 *
167 * @param {Error} err
168 * @param {String} filename
169 * @param {String} lineno
170 * @api private
171 */
172
173exports.rethrow = function rethrow(err, filename, lineno, str){
174 if (!(err instanceof Error)) throw err;
175 if ((typeof window != 'undefined' || !filename) && !str) {
176 err.message += ' on line ' + lineno;
177 throw err;
178 }
179 try {
180 str = str || require('fs').readFileSync(filename, 'utf8')
181 } catch (ex) {
182 rethrow(err, null, lineno)
183 }
184 var context = 3
185 , lines = str.split('\n')
186 , start = Math.max(lineno - context, 0)
187 , end = Math.min(lines.length, lineno + context);
188
189 // Error context
190 var context = lines.slice(start, end).map(function(line, i){
191 var curr = i + start + 1;
192 return (curr == lineno ? ' > ' : ' ')
193 + curr
194 + '| '
195 + line;
196 }).join('\n');
197
198 // Alter exception message
199 err.path = filename;
200 err.message = (filename || 'Jade') + ':' + lineno
201 + '\n' + context + '\n\n' + err.message;
202 throw err;
203};