A Twitch.tv viewer reward and games system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
4.3 KiB

  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).filter(nulls).join(' ') : val;
  56. }
  57. /**
  58. * Render the given classes.
  59. *
  60. * @param {Array} classes
  61. * @param {Array.<Boolean>} escaped
  62. * @return {String}
  63. */
  64. exports.cls = function cls(classes, escaped) {
  65. var buf = [];
  66. for (var i = 0; i < classes.length; i++) {
  67. if (escaped && escaped[i]) {
  68. buf.push(exports.escape(joinClasses([classes[i]])));
  69. } else {
  70. buf.push(joinClasses(classes[i]));
  71. }
  72. }
  73. var text = joinClasses(buf);
  74. if (text.length) {
  75. return ' class="' + text + '"';
  76. } else {
  77. return '';
  78. }
  79. };
  80. /**
  81. * Render the given attribute.
  82. *
  83. * @param {String} key
  84. * @param {String} val
  85. * @param {Boolean} escaped
  86. * @param {Boolean} terse
  87. * @return {String}
  88. */
  89. exports.attr = function attr(key, val, escaped, terse) {
  90. if ('boolean' == typeof val || null == val) {
  91. if (val) {
  92. return ' ' + (terse ? key : key + '="' + key + '"');
  93. } else {
  94. return '';
  95. }
  96. } else if (0 == key.indexOf('data') && 'string' != typeof val) {
  97. return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, '&apos;') + "'";
  98. } else if (escaped) {
  99. return ' ' + key + '="' + exports.escape(val) + '"';
  100. } else {
  101. return ' ' + key + '="' + val + '"';
  102. }
  103. };
  104. /**
  105. * Render the given attributes object.
  106. *
  107. * @param {Object} obj
  108. * @param {Object} escaped
  109. * @return {String}
  110. */
  111. exports.attrs = function attrs(obj, terse){
  112. var buf = [];
  113. var keys = Object.keys(obj);
  114. if (keys.length) {
  115. for (var i = 0; i < keys.length; ++i) {
  116. var key = keys[i]
  117. , val = obj[key];
  118. if ('class' == key) {
  119. if (val = joinClasses(val)) {
  120. buf.push(' ' + key + '="' + val + '"');
  121. }
  122. } else {
  123. buf.push(exports.attr(key, val, false, terse));
  124. }
  125. }
  126. }
  127. return buf.join('');
  128. };
  129. /**
  130. * Escape the given string of `html`.
  131. *
  132. * @param {String} html
  133. * @return {String}
  134. * @api private
  135. */
  136. exports.escape = function escape(html){
  137. var result = String(html)
  138. .replace(/&/g, '&amp;')
  139. .replace(/</g, '&lt;')
  140. .replace(/>/g, '&gt;')
  141. .replace(/"/g, '&quot;');
  142. if (result === '' + html) return html;
  143. else return result;
  144. };
  145. /**
  146. * Re-throw the given `err` in context to the
  147. * the jade in `filename` at the given `lineno`.
  148. *
  149. * @param {Error} err
  150. * @param {String} filename
  151. * @param {String} lineno
  152. * @api private
  153. */
  154. exports.rethrow = function rethrow(err, filename, lineno, str){
  155. if (!(err instanceof Error)) throw err;
  156. if ((typeof window != 'undefined' || !filename) && !str) {
  157. err.message += ' on line ' + lineno;
  158. throw err;
  159. }
  160. try {
  161. str = str || require('fs').readFileSync(filename, 'utf8')
  162. } catch (ex) {
  163. rethrow(err, null, lineno)
  164. }
  165. var context = 3
  166. , lines = str.split('\n')
  167. , start = Math.max(lineno - context, 0)
  168. , end = Math.min(lines.length, lineno + context);
  169. // Error context
  170. var context = lines.slice(start, end).map(function(line, i){
  171. var curr = i + start + 1;
  172. return (curr == lineno ? ' > ' : ' ')
  173. + curr
  174. + '| '
  175. + line;
  176. }).join('\n');
  177. // Alter exception message
  178. err.path = filename;
  179. err.message = (filename || 'Jade') + ':' + lineno
  180. + '\n' + context + '\n\n' + err.message;
  181. throw err;
  182. };