index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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(attrs.every(function (attr) {
  22. return attr &&
  23. typeof attr === 'object' &&
  24. typeof attr.name === 'string' &&
  25. (typeof attr.val === 'string' || typeof attr.val === 'boolean') &&
  26. typeof attr.mustEscape === 'boolean';
  27. }), 'All attributes should be supplied as an object of the form {name, val, mustEscape}');
  28. assert(options && typeof options === 'object', 'Options should be an object');
  29. assert(typeof options.terse === 'boolean', 'Options.terse should be a boolean');
  30. assert(
  31. typeof options.runtime === 'function',
  32. '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'
  33. );
  34. assert(
  35. options.format === 'html' || options.format === 'object',
  36. 'Options.format should be "html" or "object"'
  37. );
  38. var buf = [];
  39. var classes = [];
  40. var classEscaping = [];
  41. function addAttribute(key, val, mustEscape, buf) {
  42. if (isConstant(val)) {
  43. if (options.format === 'html') {
  44. var str = stringify(runtime.attr(key, toConstant(val), mustEscape, options.terse));
  45. var last = buf[buf.length - 1];
  46. if (last && last[last.length - 1] === str[0]) {
  47. buf[buf.length - 1] = last.substr(0, last.length - 1) + str.substr(1);
  48. } else {
  49. buf.push(str);
  50. }
  51. } else {
  52. val = toConstant(val);
  53. if (mustEscape) {
  54. val = runtime.escape(val);
  55. }
  56. buf.push(stringify(key) + ': ' + stringify(val));
  57. }
  58. } else {
  59. if (options.format === 'html') {
  60. buf.push(options.runtime('attr') + '("' + key + '", ' + val + ', ' + stringify(mustEscape) + ', ' + stringify(options.terse) + ')');
  61. } else {
  62. if (mustEscape) {
  63. val = options.runtime('escape') + '(' + val + ')';
  64. }
  65. buf.push(stringify(key) + ': ' + val);
  66. }
  67. }
  68. }
  69. attrs.forEach(function(attr){
  70. var key = attr.name;
  71. var val = attr.val;
  72. var mustEscape = attr.mustEscape;
  73. if (key === 'class') {
  74. classes.push(val);
  75. classEscaping.push(mustEscape);
  76. } else {
  77. if (key === 'style') {
  78. if (isConstant(val)) {
  79. val = stringify(runtime.style(toConstant(val)));
  80. } else {
  81. val = options.runtime('style') + '(' + val + ')';
  82. }
  83. }
  84. addAttribute(key, val, mustEscape, buf);
  85. }
  86. });
  87. var classesBuf = [];
  88. if (classes.length) {
  89. if (classes.every(isConstant)) {
  90. addAttribute(
  91. 'class',
  92. stringify(runtime.classes(classes.map(toConstant), classEscaping)),
  93. false,
  94. classesBuf
  95. );
  96. } else {
  97. classes = classes.map(function (cls, i) {
  98. if (isConstant(cls)) {
  99. cls = stringify(classEscaping[i] ? runtime.escape(toConstant(cls)) : toConstant(cls));
  100. classEscaping[i] = false;
  101. }
  102. return cls;
  103. });
  104. addAttribute(
  105. 'class',
  106. options.runtime('classes') + '([' + classes.join(',') + '], ' + stringify(classEscaping) + ')',
  107. false,
  108. classesBuf
  109. );
  110. }
  111. }
  112. buf = classesBuf.concat(buf);
  113. if (options.format === 'html') return buf.length ? buf.join('+') : '""';
  114. else return '{' + buf.join(',') + '}';
  115. }