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, '&#39;') + '\'';
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 = '&quot;'; break;
199      case 38: escape = '&amp;'; break;
200      case 60: escape = '&lt;'; break;
201      case 62: escape = '&gt;'; 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