UNPKG

5.4 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) :
64 (val && typeof val === 'object') ? Object.keys(val).filter(function (key) { return val[key]; }) :
65 [val]).filter(nulls).join(' ');
66}
67
68/**
69 * Render the given classes.
70 *
71 * @param {Array} classes
72 * @param {Array.<Boolean>} escaped
73 * @return {String}
74 */
75exports.cls = function cls(classes, escaped) {
76 var buf = [];
77 for (var i = 0; i < classes.length; i++) {
78 if (escaped && escaped[i]) {
79 buf.push(exports.escape(joinClasses([classes[i]])));
80 } else {
81 buf.push(joinClasses(classes[i]));
82 }
83 }
84 var text = joinClasses(buf);
85 if (text.length) {
86 return ' class="' + text + '"';
87 } else {
88 return '';
89 }
90};
91
92
93exports.style = function (val) {
94 if (val && typeof val === 'object') {
95 return Object.keys(val).map(function (style) {
96 return style + ':' + val[style];
97 }).join(';');
98 } else {
99 return val;
100 }
101};
102/**
103 * Render the given attribute.
104 *
105 * @param {String} key
106 * @param {String} val
107 * @param {Boolean} escaped
108 * @param {Boolean} terse
109 * @return {String}
110 */
111exports.attr = function attr(key, val, escaped, terse) {
112 if (key === 'style') {
113 val = exports.style(val);
114 }
115 if ('boolean' == typeof val || null == val) {
116 if (val) {
117 return ' ' + (terse ? key : key + '="' + key + '"');
118 } else {
119 return '';
120 }
121 } else if (0 == key.indexOf('data') && 'string' != typeof val) {
122 if (JSON.stringify(val).indexOf('&') !== -1) {
123 console.warn('Since Jade 2.0.0, ampersands (`&`) in data attributes ' +
124 'will be escaped to `&amp;`');
125 };
126 if (val && typeof val.toISOString === 'function') {
127 console.warn('Jade will eliminate the double quotes around dates in ' +
128 'ISO form after 2.0.0');
129 }
130 return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, '&apos;') + "'";
131 } else if (escaped) {
132 if (val && typeof val.toISOString === 'function') {
133 console.warn('Jade will stringify dates in ISO form after 2.0.0');
134 }
135 return ' ' + key + '="' + exports.escape(val) + '"';
136 } else {
137 if (val && typeof val.toISOString === 'function') {
138 console.warn('Jade will stringify dates in ISO form after 2.0.0');
139 }
140 return ' ' + key + '="' + val + '"';
141 }
142};
143
144/**
145 * Render the given attributes object.
146 *
147 * @param {Object} obj
148 * @param {Object} escaped
149 * @return {String}
150 */
151exports.attrs = function attrs(obj, terse){
152 var buf = [];
153
154 var keys = Object.keys(obj);
155
156 if (keys.length) {
157 for (var i = 0; i < keys.length; ++i) {
158 var key = keys[i]
159 , val = obj[key];
160
161 if ('class' == key) {
162 if (val = joinClasses(val)) {
163 buf.push(' ' + key + '="' + val + '"');
164 }
165 } else {
166 buf.push(exports.attr(key, val, false, terse));
167 }
168 }
169 }
170
171 return buf.join('');
172};
173
174/**
175 * Escape the given string of `html`.
176 *
177 * @param {String} html
178 * @return {String}
179 * @api private
180 */
181
182exports.escape = function escape(html){
183 var result = String(html)
184 .replace(/&/g, '&amp;')
185 .replace(/</g, '&lt;')
186 .replace(/>/g, '&gt;')
187 .replace(/"/g, '&quot;');
188 if (result === '' + html) return html;
189 else return result;
190};
191
192/**
193 * Re-throw the given `err` in context to the
194 * the jade in `filename` at the given `lineno`.
195 *
196 * @param {Error} err
197 * @param {String} filename
198 * @param {String} lineno
199 * @api private
200 */
201
202exports.rethrow = function rethrow(err, filename, lineno, str){
203 if (!(err instanceof Error)) throw err;
204 if ((typeof window != 'undefined' || !filename) && !str) {
205 err.message += ' on line ' + lineno;
206 throw err;
207 }
208 try {
209 str = str || require('fs').readFileSync(filename, 'utf8')
210 } catch (ex) {
211 rethrow(err, null, lineno)
212 }
213 var context = 3
214 , lines = str.split('\n')
215 , start = Math.max(lineno - context, 0)
216 , end = Math.min(lines.length, lineno + context);
217
218 // Error context
219 var context = lines.slice(start, end).map(function(line, i){
220 var curr = i + start + 1;
221 return (curr == lineno ? ' > ' : ' ')
222 + curr
223 + '| '
224 + line;
225 }).join('\n');
226
227 // Alter exception message
228 err.path = filename;
229 err.message = (filename || 'Jade') + ':' + lineno
230 + '\n' + context + '\n\n' + err.message;
231 throw err;
232};