runtime.js 5.5 KB

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