applause.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*
  2. * applause
  3. *
  4. * Copyright (c) 2014 outaTiME
  5. * Licensed under the MIT license.
  6. * https://github.com/outaTiME/applause/blob/master/LICENSE-MIT
  7. */
  8. // dependencies
  9. var _ = require('lodash');
  10. var plugins = require('./plugins');
  11. // private
  12. var createPluginHandler = function (context, opts) {
  13. return function (plugin) {
  14. var pattern = context.pattern;
  15. if (plugin.match(pattern, opts) === true) {
  16. plugin.transform(pattern, opts, function (items) {
  17. if (items instanceof Error) {
  18. throw items;
  19. } else {
  20. // store transformed pattern in context
  21. context.pattern = items;
  22. }
  23. });
  24. } else {
  25. // plugin doesn't apply
  26. }
  27. };
  28. };
  29. // took from MDN
  30. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
  31. var escapeRegExp = function (string) {
  32. return string.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
  33. };
  34. var normalize = function (applause, patterns) {
  35. var opts = applause.options;
  36. return _.transform(patterns, function (result, pattern) {
  37. // filter empty patterns
  38. if (!_.isEmpty(pattern)) {
  39. var match = pattern.match;
  40. var replacement = pattern.replacement;
  41. var expression = false;
  42. // match check
  43. if (match !== undefined && match !== null) {
  44. if (_.isRegExp(match)) {
  45. expression = true;
  46. } else if (_.isString(match)) {
  47. if (match.length > 0) {
  48. match = new RegExp(opts.prefix + escapeRegExp(match), 'g');
  49. } else {
  50. // empty match
  51. return;
  52. }
  53. } else {
  54. throw new Error('Unsupported match type (RegExp or String expected).');
  55. }
  56. } else {
  57. throw new Error('Match attribute expected in pattern definition.');
  58. }
  59. // replacement check
  60. if (replacement !== undefined && replacement !== null) {
  61. if (!_.isFunction(replacement)) {
  62. if (!_.isString(replacement)) {
  63. // transform object to string
  64. replacement = JSON.stringify(replacement);
  65. } else {
  66. // easy way
  67. if (expression === false && opts.preservePrefix === true) {
  68. replacement = opts.prefix + replacement;
  69. }
  70. }
  71. } else {
  72. // replace using function return value
  73. }
  74. } else {
  75. throw new Error('Replacement attribute expected in pattern definition.');
  76. }
  77. return result.push({
  78. match: match,
  79. replacement: replacement
  80. });
  81. }
  82. });
  83. };
  84. var getPatterns = function (applause) {
  85. var opts = applause.options;
  86. // shallow patterns
  87. var patterns = _.clone(opts.patterns);
  88. // backward compatibility
  89. var variables = opts.variables;
  90. if (!_.isEmpty(variables)) {
  91. patterns.push({
  92. json: variables
  93. });
  94. }
  95. // intercept errors
  96. for (var i = patterns.length - 1; i >= 0; i -= 1) {
  97. var context = {
  98. pattern: patterns[i]
  99. };
  100. // process context with each plugin
  101. plugins.forEach(createPluginHandler(context, opts));
  102. // update current pattern
  103. Array.prototype.splice.apply(patterns, [i, 1].concat(context.pattern));
  104. }
  105. if (opts.preserveOrder !== true) {
  106. // only sort non regex patterns (prevents replace issues like head, header)
  107. patterns.sort(function (a, b) {
  108. var x = a.match;
  109. var y = b.match;
  110. if (_.isString(x) && _.isString(y)) {
  111. return y.length - x.length;
  112. } else if (_.isString(x)) {
  113. return -1;
  114. }
  115. return 1;
  116. });
  117. }
  118. // normalize definition
  119. return normalize(applause, patterns);
  120. };
  121. // applause
  122. var Applause = function (opts) {
  123. this.options = _.defaults(opts, {
  124. patterns: [],
  125. prefix: opts.usePrefix === false ? '': '@@',
  126. usePrefix: true,
  127. preservePrefix: false,
  128. delimiter: '.',
  129. preserveOrder: false
  130. });
  131. };
  132. Applause.prototype.replace = function (contents, process) {
  133. // prepare patterns
  134. var patterns = getPatterns(this);
  135. // by default file not updated
  136. var updated = false;
  137. // iterate over each pattern and make replacement
  138. patterns.forEach(function (pattern) {
  139. var match = pattern.match;
  140. var replacement = pattern.replacement;
  141. // wrap replacement function to add process arguments
  142. if (_.isFunction(replacement)) {
  143. replacement = function () {
  144. var args = Array.prototype.slice.call(arguments);
  145. return pattern.replacement.apply(this, args.concat(process || []));
  146. };
  147. }
  148. updated = updated || contents.match(match);
  149. contents = contents.replace(match, replacement);
  150. });
  151. if (!updated) {
  152. return false;
  153. }
  154. return contents;
  155. };
  156. // static
  157. Applause.create = function (opts) {
  158. return new Applause(opts);
  159. };
  160. // expose
  161. module.exports = Applause;