plugins.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. "use strict";
  2. /**
  3. * Returns the given plugins as an array, rather than an object map.
  4. * All other methods in this module expect an array of plugins rather than an object map.
  5. *
  6. * @param {object} plugins - A map of plugin objects
  7. * @return {object[]}
  8. */
  9. exports.all = function (plugins) {
  10. return Object.keys(plugins)
  11. .filter((key) => {
  12. return typeof plugins[key] === "object";
  13. })
  14. .map((key) => {
  15. plugins[key].name = key;
  16. return plugins[key];
  17. });
  18. };
  19. /**
  20. * Filters the given plugins, returning only the ones return `true` for the given method.
  21. *
  22. * @param {object[]} plugins - An array of plugin objects
  23. * @param {string} method - The name of the filter method to invoke for each plugin
  24. * @param {object} file - A file info object, which will be passed to each method
  25. * @return {object[]}
  26. */
  27. exports.filter = function (plugins, method, file) {
  28. return plugins
  29. .filter((plugin) => {
  30. return !!getResult(plugin, method, file);
  31. });
  32. };
  33. /**
  34. * Sorts the given plugins, in place, by their `order` property.
  35. *
  36. * @param {object[]} plugins - An array of plugin objects
  37. * @returns {object[]}
  38. */
  39. exports.sort = function (plugins) {
  40. for (let plugin of plugins) {
  41. plugin.order = plugin.order || Number.MAX_SAFE_INTEGER;
  42. }
  43. return plugins.sort((a, b) => { return a.order - b.order; });
  44. };
  45. /**
  46. * Runs the specified method of the given plugins, in order, until one of them returns a successful result.
  47. * Each method can return a synchronous value, a Promise, or call an error-first callback.
  48. * If the promise resolves successfully, or the callback is called without an error, then the result
  49. * is immediately returned and no further plugins are called.
  50. * If the promise rejects, or the callback is called with an error, then the next plugin is called.
  51. * If ALL plugins fail, then the last error is thrown.
  52. *
  53. * @param {object[]} plugins - An array of plugin objects
  54. * @param {string} method - The name of the method to invoke for each plugin
  55. * @param {object} file - A file info object, which will be passed to each method
  56. * @returns {Promise}
  57. */
  58. exports.run = function (plugins, method, file, $refs) {
  59. let plugin, lastError, index = 0;
  60. return new Promise(((resolve, reject) => {
  61. runNextPlugin();
  62. function runNextPlugin () {
  63. plugin = plugins[index++];
  64. if (!plugin) {
  65. // There are no more functions, so re-throw the last error
  66. return reject(lastError);
  67. }
  68. try {
  69. // console.log(' %s', plugin.name);
  70. let result = getResult(plugin, method, file, callback, $refs);
  71. if (result && typeof result.then === "function") {
  72. // A promise was returned
  73. result.then(onSuccess, onError);
  74. }
  75. else if (result !== undefined) {
  76. // A synchronous result was returned
  77. onSuccess(result);
  78. }
  79. else if (index === plugins.length) {
  80. throw new Error("No promise has been returned or callback has been called.");
  81. }
  82. }
  83. catch (e) {
  84. onError(e);
  85. }
  86. }
  87. function callback (err, result) {
  88. if (err) {
  89. onError(err);
  90. }
  91. else {
  92. onSuccess(result);
  93. }
  94. }
  95. function onSuccess (result) {
  96. // console.log(' success');
  97. resolve({
  98. plugin,
  99. result
  100. });
  101. }
  102. function onError (error) {
  103. // console.log(' %s', err.message || err);
  104. lastError = {
  105. plugin,
  106. error,
  107. };
  108. runNextPlugin();
  109. }
  110. }));
  111. };
  112. /**
  113. * Returns the value of the given property.
  114. * If the property is a function, then the result of the function is returned.
  115. * If the value is a RegExp, then it will be tested against the file URL.
  116. * If the value is an aray, then it will be compared against the file extension.
  117. *
  118. * @param {object} obj - The object whose property/method is called
  119. * @param {string} prop - The name of the property/method to invoke
  120. * @param {object} file - A file info object, which will be passed to the method
  121. * @param {function} [callback] - A callback function, which will be passed to the method
  122. * @returns {*}
  123. */
  124. function getResult (obj, prop, file, callback, $refs) {
  125. let value = obj[prop];
  126. if (typeof value === "function") {
  127. return value.apply(obj, [file, callback, $refs]);
  128. }
  129. if (!callback) {
  130. // The synchronous plugin functions (canParse and canRead)
  131. // allow a "shorthand" syntax, where the user can match
  132. // files by RegExp or by file extension.
  133. if (value instanceof RegExp) {
  134. return value.test(file.url);
  135. }
  136. else if (typeof value === "string") {
  137. return value === file.extension;
  138. }
  139. else if (Array.isArray(value)) {
  140. return value.indexOf(file.extension) !== -1;
  141. }
  142. }
  143. return value;
  144. }