index.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. 'use strict';
  2. var assert = require('assert');
  3. var constantinople = require('constantinople');
  4. var runtime = require('pug-runtime');
  5. var stringify = require('js-stringify');
  6. function isConstant(src) {
  7. return constantinople(src, {pug: runtime, pug_interp: undefined});
  8. }
  9. function toConstant(src) {
  10. return constantinople.toConstant(src, {pug: runtime, pug_interp: undefined});
  11. }
  12. module.exports = compileAttrs;
  13. /**
  14. * options:
  15. * - terse
  16. * - runtime
  17. * - format ('html' || 'object')
  18. */
  19. function compileAttrs(attrs, options) {
  20. assert(Array.isArray(attrs), 'Attrs should be an array');
  21. assert(
  22. attrs.every(function(attr) {
  23. return (
  24. attr &&
  25. typeof attr === 'object' &&
  26. typeof attr.name === 'string' &&
  27. (typeof attr.val === 'string' || typeof attr.val === 'boolean') &&
  28. typeof attr.mustEscape === 'boolean'
  29. );
  30. }),
  31. 'All attributes should be supplied as an object of the form {name, val, mustEscape}'
  32. );
  33. assert(options && typeof options === 'object', 'Options should be an object');
  34. assert(
  35. typeof options.terse === 'boolean',
  36. 'Options.terse should be a boolean'
  37. );
  38. assert(
  39. typeof options.runtime === 'function',
  40. 'Options.runtime should be a function that takes a runtime function name and returns the source code that will evaluate to that function at runtime'
  41. );
  42. assert(
  43. options.format === 'html' || options.format === 'object',
  44. 'Options.format should be "html" or "object"'
  45. );
  46. var buf = [];
  47. var classes = [];
  48. var classEscaping = [];
  49. function addAttribute(key, val, mustEscape, buf) {
  50. if (isConstant(val)) {
  51. if (options.format === 'html') {
  52. var str = stringify(
  53. runtime.attr(key, toConstant(val), mustEscape, options.terse)
  54. );
  55. var last = buf[buf.length - 1];
  56. if (last && last[last.length - 1] === str[0]) {
  57. buf[buf.length - 1] = last.substr(0, last.length - 1) + str.substr(1);
  58. } else {
  59. buf.push(str);
  60. }
  61. } else {
  62. val = toConstant(val);
  63. if (mustEscape) {
  64. val = runtime.escape(val);
  65. }
  66. buf.push(stringify(key) + ': ' + stringify(val));
  67. }
  68. } else {
  69. if (options.format === 'html') {
  70. buf.push(
  71. options.runtime('attr') +
  72. '("' +
  73. key +
  74. '", ' +
  75. val +
  76. ', ' +
  77. stringify(mustEscape) +
  78. ', ' +
  79. stringify(options.terse) +
  80. ')'
  81. );
  82. } else {
  83. if (mustEscape) {
  84. val = options.runtime('escape') + '(' + val + ')';
  85. }
  86. buf.push(stringify(key) + ': ' + val);
  87. }
  88. }
  89. }
  90. attrs.forEach(function(attr) {
  91. var key = attr.name;
  92. var val = attr.val;
  93. var mustEscape = attr.mustEscape;
  94. if (key === 'class') {
  95. classes.push(val);
  96. classEscaping.push(mustEscape);
  97. } else {
  98. if (key === 'style') {
  99. if (isConstant(val)) {
  100. val = stringify(runtime.style(toConstant(val)));
  101. } else {
  102. val = options.runtime('style') + '(' + val + ')';
  103. }
  104. }
  105. addAttribute(key, val, mustEscape, buf);
  106. }
  107. });
  108. var classesBuf = [];
  109. if (classes.length) {
  110. if (classes.every(isConstant)) {
  111. addAttribute(
  112. 'class',
  113. stringify(runtime.classes(classes.map(toConstant), classEscaping)),
  114. false,
  115. classesBuf
  116. );
  117. } else {
  118. classes = classes.map(function(cls, i) {
  119. if (isConstant(cls)) {
  120. cls = stringify(
  121. classEscaping[i] ? runtime.escape(toConstant(cls)) : toConstant(cls)
  122. );
  123. classEscaping[i] = false;
  124. }
  125. return cls;
  126. });
  127. addAttribute(
  128. 'class',
  129. options.runtime('classes') +
  130. '([' +
  131. classes.join(',') +
  132. '], ' +
  133. stringify(classEscaping) +
  134. ')',
  135. false,
  136. classesBuf
  137. );
  138. }
  139. }
  140. buf = classesBuf.concat(buf);
  141. if (options.format === 'html') return buf.length ? buf.join('+') : '""';
  142. else return '{' + buf.join(',') + '}';
  143. }