UNPKG

5.67 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
182var jade_encode_html_rules = {
183 '&': '&amp;',
184 '<': '&lt;',
185 '>': '&gt;',
186 '"': '&quot;'
187};
188var jade_match_html = /[&<>"]/g;
189
190function jade_encode_char(c) {
191 return jade_encode_html_rules[c] || c;
192}
193
194exports.escape = jade_escape;
195function jade_escape(html){
196 var result = String(html).replace(jade_match_html, jade_encode_char);
197 if (result === '' + html) return html;
198 else return result;
199};
200
201/**
202 * Re-throw the given `err` in context to the
203 * the jade in `filename` at the given `lineno`.
204 *
205 * @param {Error} err
206 * @param {String} filename
207 * @param {String} lineno
208 * @api private
209 */
210
211exports.rethrow = function rethrow(err, filename, lineno, str){
212 if (!(err instanceof Error)) throw err;
213 if ((typeof window != 'undefined' || !filename) && !str) {
214 err.message += ' on line ' + lineno;
215 throw err;
216 }
217 try {
218 str = str || require('fs').readFileSync(filename, 'utf8')
219 } catch (ex) {
220 rethrow(err, null, lineno)
221 }
222 var context = 3
223 , lines = str.split('\n')
224 , start = Math.max(lineno - context, 0)
225 , end = Math.min(lines.length, lineno + context);
226
227 // Error context
228 var context = lines.slice(start, end).map(function(line, i){
229 var curr = i + start + 1;
230 return (curr == lineno ? ' > ' : ' ')
231 + curr
232 + '| '
233 + line;
234 }).join('\n');
235
236 // Alter exception message
237 err.path = filename;
238 err.message = (filename || 'Jade') + ':' + lineno
239 + '\n' + context + '\n\n' + err.message;
240 throw err;
241};
242
243exports.DebugItem = function DebugItem(lineno, filename) {
244 this.lineno = lineno;
245 this.filename = filename;
246}