1'use strict'; 2 3var pug_has_own_property = Object.prototype.hasOwnProperty; 4 5/** 6 * Merge two attribute objects giving precedence 7 * to values in object `b`. Classes are special-cased 8 * allowing for arrays and merging/joining appropriately 9 * resulting in a string. 10 * 11 * @param {Object} a 12 * @param {Object} b 13 * @return {Object} a 14 * @api private 15 */ 16 17exports.merge = pug_merge; 18function pug_merge(a, b) { 19 if (arguments.length === 1) { 20 var attrs = a[0]; 21 for (var i = 1; i < a.length; i++) { 22 attrs = pug_merge(attrs, a[i]); 23 } 24 return attrs; 25 } 26 27 for (var key in b) { 28 if (key === 'class') { 29 var valA = a[key] || []; 30 a[key] = (Array.isArray(valA) ? valA : [valA]).concat(b[key] || []); 31 } else if (key === 'style') { 32 var valA = pug_style(a[key]); 33 valA = valA && valA[valA.length - 1] !== ';' ? valA + ';' : valA; 34 var valB = pug_style(b[key]); 35 valB = valB && valB[valB.length - 1] !== ';' ? valB + ';' : valB; 36 a[key] = valA + valB; 37 } else { 38 a[key] = b[key]; 39 } 40 } 41 42 return a; 43}; 44 45/** 46 * Process array, object, or string as a string of classes delimited by a space. 47 * 48 * If `val` is an array, all members of it and its subarrays are counted as 49 * classes. If `escaping` is an array, then whether or not the item in `val` is 50 * escaped depends on the corresponding item in `escaping`. If `escaping` is 51 * not an array, no escaping is done. 52 * 53 * If `val` is an object, all the keys whose value is truthy are counted as 54 * classes. No escaping is done. 55 * 56 * If `val` is a string, it is counted as a class. No escaping is done. 57 * 58 * @param {(Array.<string>|Object.<string, boolean>|string)} val 59 * @param {?Array.<string>} escaping 60 * @return {String} 61 */ 62exports.classes = pug_classes; 63function pug_classes_array(val, escaping) { 64 var classString = '', className, padding = '', escapeEnabled = Array.isArray(escaping); 65 for (var i = 0; i < val.length; i++) { 66 className = pug_classes(val[i]); 67 if (!className) continue; 68 escapeEnabled && escaping[i] && (className = pug_escape(className)); 69 classString = classString + padding + className; 70 padding = ' '; 71 } 72 return classString; 73} 74function pug_classes_object(val) { 75 var classString = '', padding = ''; 76 for (var key in val) { 77 if (key && val[key] && pug_has_own_property.call(val, key)) { 78 classString = classString + padding + key; 79 padding = ' '; 80 } 81 } 82 return classString; 83} 84function pug_classes(val, escaping) { 85 if (Array.isArray(val)) { 86 return pug_classes_array(val, escaping); 87 } else if (val && typeof val === 'object') { 88 return pug_classes_object(val); 89 } else { 90 return val || ''; 91 } 92} 93 94/** 95 * Convert object or string to a string of CSS styles delimited by a semicolon. 96 * 97 * @param {(Object.<string, string>|string)} val 98 * @return {String} 99 */ 100 101exports.style = pug_style; 102function pug_style(val) { 103 if (!val) return ''; 104 if (typeof val === 'object') { 105 var out = ''; 106 for (var style in val) { 107 /* istanbul ignore else */ 108 if (pug_has_own_property.call(val, style)) { 109 out = out + style + ':' + val[style] + ';'; 110 } 111 } 112 return out; 113 } else { 114 return val + ''; 115 } 116}; 117 118/** 119 * Render the given attribute. 120 * 121 * @param {String} key 122 * @param {String} val 123 * @param {Boolean} escaped 124 * @param {Boolean} terse 125 * @return {String} 126 */ 127exports.attr = pug_attr; 128function pug_attr(key, val, escaped, terse) { 129 if (val === false || val == null || !val && (key === 'class' || key === 'style')) { 130 return ''; 131 } 132 if (val === true) { 133 return ' ' + (terse ? key : key + '="' + key + '"'); 134 } 135 var type = typeof val; 136 if ((type === 'object' || type === 'function') && typeof val.toJSON === 'function') { 137 val = val.toJSON(); 138 } 139 if (typeof val !== 'string') { 140 val = JSON.stringify(val); 141 if (!escaped && val.indexOf('"') !== -1) { 142 return ' ' + key + '=\'' + val.replace(/'/g, ''') + '\''; 143 } 144 } 145 if (escaped) val = pug_escape(val); 146 return ' ' + key + '="' + val + '"'; 147}; 148 149/** 150 * Render the given attributes object. 151 * 152 * @param {Object} obj 153 * @param {Object} terse whether to use HTML5 terse boolean attributes 154 * @return {String} 155 */ 156exports.attrs = pug_attrs; 157function pug_attrs(obj, terse){ 158 var attrs = ''; 159 160 for (var key in obj) { 161 if (pug_has_own_property.call(obj, key)) { 162 var val = obj[key]; 163 164 if ('class' === key) { 165 val = pug_classes(val); 166 attrs = pug_attr(key, val, false, terse) + attrs; 167 continue; 168 } 169 if ('style' === key) { 170 val = pug_style(val); 171 } 172 attrs += pug_attr(key, val, false, terse); 173 } 174 } 175 176 return attrs; 177}; 178 179/** 180 * Escape the given string of `html`. 181 * 182 * @param {String} html 183 * @return {String} 184 * @api private 185 */ 186 187var pug_match_html = /["&<>]/; 188exports.escape = pug_escape; 189function pug_escape(_html){ 190 var html = '' + _html; 191 var regexResult = pug_match_html.exec(html); 192 if (!regexResult) return _html; 193 194 var result = ''; 195 var i, lastIndex, escape; 196 for (i = regexResult.index, lastIndex = 0; i < html.length; i++) { 197 switch (html.charCodeAt(i)) { 198 case 34: escape = '"'; break; 199 case 38: escape = '&'; break; 200 case 60: escape = '<'; break; 201 case 62: escape = '>'; break; 202 default: continue; 203 } 204 if (lastIndex !== i) result += html.substring(lastIndex, i); 205 lastIndex = i + 1; 206 result += escape; 207 } 208 if (lastIndex !== i) return result + html.substring(lastIndex, i); 209 else return result; 210}; 211 212/** 213 * Re-throw the given `err` in context to the 214 * the pug in `filename` at the given `lineno`. 215 * 216 * @param {Error} err 217 * @param {String} filename 218 * @param {String} lineno 219 * @param {String} str original source 220 * @api private 221 */ 222 223exports.rethrow = pug_rethrow; 224function pug_rethrow(err, filename, lineno, str){ 225 if (!(err instanceof Error)) throw err; 226 if ((typeof window != 'undefined' || !filename) && !str) { 227 err.message += ' on line ' + lineno; 228 throw err; 229 } 230 try { 231 str = str || require('fs').readFileSync(filename, 'utf8') 232 } catch (ex) { 233 pug_rethrow(err, null, lineno) 234 } 235 var context = 3 236 , lines = str.split('\n') 237 , start = Math.max(lineno - context, 0) 238 , end = Math.min(lines.length, lineno + context); 239 240 // Error context 241 var context = lines.slice(start, end).map(function(line, i){ 242 var curr = i + start + 1; 243 return (curr == lineno ? ' > ' : ' ') 244 + curr 245 + '| ' 246 + line; 247 }).join('\n'); 248 249 // Alter exception message 250 err.path = filename; 251 err.message = (filename || 'Pug') + ':' + lineno 252 + '\n' + context + '\n\n' + err.message; 253 throw err; 254}; 255