fs/lib/adapters/AbstractAdapter.js

  1. const log = require("@ui5/logger").getLogger("resources:adapters:AbstractAdapter");
  2. const minimatch = require("minimatch");
  3. const micromatch = require("micromatch");
  4. const AbstractReaderWriter = require("../AbstractReaderWriter");
  5. const Resource = require("../Resource");
  6. /**
  7. * Abstract Resource Adapter
  8. *
  9. * @abstract
  10. * @public
  11. * @memberof module:@ui5/fs.adapters
  12. * @augments module:@ui5/fs.AbstractReaderWriter
  13. */
  14. class AbstractAdapter extends AbstractReaderWriter {
  15. /**
  16. * The constructor
  17. *
  18. * @public
  19. * @param {object} parameters Parameters
  20. * @param {string} parameters.virBasePath Virtual base path
  21. * @param {string[]} [parameters.excludes] List of glob patterns to exclude
  22. * @param {object} [parameters.project] Experimental, internal parameter. Do not use
  23. */
  24. constructor({virBasePath, excludes = [], project}) {
  25. if (new.target === AbstractAdapter) {
  26. throw new TypeError("Class 'AbstractAdapter' is abstract");
  27. }
  28. super();
  29. this._virBasePath = virBasePath;
  30. this._virBaseDir = virBasePath.slice(0, -1);
  31. this._excludes = excludes;
  32. this._excludesNegated = excludes.map((pattern) => `!${pattern}`);
  33. this._project = project;
  34. }
  35. /**
  36. * Locates resources by glob.
  37. *
  38. * @abstract
  39. * @private
  40. * @param {string|string[]} virPattern glob pattern as string or an array of
  41. * glob patterns for virtual directory structure
  42. * @param {object} [options={}] glob options
  43. * @param {boolean} [options.nodir=true] Do not match directories
  44. * @param {module:@ui5/fs.tracing.Trace} trace Trace instance
  45. * @returns {Promise<module:@ui5/fs.Resource[]>} Promise resolving to list of resources
  46. */
  47. _byGlob(virPattern, options = {nodir: true}, trace) {
  48. const excludes = this._excludesNegated;
  49. if (!(virPattern instanceof Array)) {
  50. virPattern = [virPattern];
  51. }
  52. // Append static exclude patterns
  53. virPattern = Array.prototype.concat.apply(virPattern, excludes);
  54. return Promise.all(virPattern.map(this._normalizePattern, this)).then((patterns) => {
  55. patterns = Array.prototype.concat.apply([], patterns);
  56. if (patterns.length === 0) {
  57. return [];
  58. }
  59. if (!options.nodir) {
  60. for (let i = patterns.length - 1; i >= 0; i--) {
  61. const idx = this._virBaseDir.indexOf(patterns[i]);
  62. if (patterns[i] && idx !== -1 && idx < this._virBaseDir.length) {
  63. const subPath = patterns[i];
  64. return Promise.resolve([
  65. new Resource({
  66. project: this.project,
  67. statInfo: { // TODO: make closer to fs stat info
  68. isDirectory: function() {
  69. return true;
  70. }
  71. },
  72. path: subPath
  73. })
  74. ]);
  75. }
  76. }
  77. }
  78. return this._runGlob(patterns, options, trace);
  79. });
  80. }
  81. /**
  82. * Validate if virtual path should be excluded
  83. *
  84. * @param {string} virPath Virtual Path
  85. * @returns {boolean} True if path is excluded, otherwise false
  86. */
  87. isPathExcluded(virPath) {
  88. return micromatch(virPath, this._excludes).length > 0;
  89. }
  90. /**
  91. * Normalizes virtual glob patterns.
  92. *
  93. * @private
  94. * @param {string} virPattern glob pattern for virtual directory structure
  95. * @returns {Promise<string[]>} Promise resolving to list of normalized glob patterns
  96. */
  97. _normalizePattern(virPattern) {
  98. return Promise.resolve().then(() => {
  99. const that = this;
  100. const mm = new minimatch.Minimatch(virPattern);
  101. const basePathParts = this._virBaseDir.split("/");
  102. function matchSubset(subset) {
  103. let i;
  104. for (i = 0; i < basePathParts.length; i++) {
  105. const globPart = subset[i];
  106. if (globPart === undefined) {
  107. log.verbose("Ran out of glob parts to match (this should not happen):");
  108. if (that._project) { // project is optional
  109. log.verbose(`Project: ${that._project.metadata.name}`);
  110. }
  111. log.verbose(`Virtual base path: ${that._virBaseDir}`);
  112. log.verbose(`Pattern to match: ${virPattern}`);
  113. log.verbose(`Current subset (tried index ${i}):`);
  114. log.verbose(subset);
  115. return {idx: i, virtualMatch: true};
  116. }
  117. const basePathPart = basePathParts[i];
  118. if (typeof globPart === "string") {
  119. if (globPart !== basePathPart) {
  120. return null;
  121. } else {
  122. continue;
  123. }
  124. } else if (globPart === minimatch.GLOBSTAR) {
  125. return {idx: i};
  126. } else { // Regex
  127. if (!globPart.test(basePathPart)) {
  128. return null;
  129. } else {
  130. continue;
  131. }
  132. }
  133. }
  134. if (subset.length === basePathParts.length) {
  135. return {rootMatch: true};
  136. }
  137. return {idx: i};
  138. }
  139. const resultGlobs = [];
  140. for (let i = 0; i < mm.set.length; i++) {
  141. const match = matchSubset(mm.set[i]);
  142. if (match) {
  143. let resultPattern;
  144. if (match.virtualMatch) {
  145. resultPattern = basePathParts.slice(0, match.idx).join("/");
  146. } else if (match.rootMatch) { // matched one up
  147. resultPattern = ""; // root "/"
  148. } else { // matched at some part of the glob
  149. resultPattern = mm.globParts[i].slice(match.idx).join("/");
  150. if (resultPattern.startsWith("/")) {
  151. resultPattern = resultPattern.substr(1);
  152. }
  153. }
  154. if (mm.negate) {
  155. resultPattern = "!" + resultPattern;
  156. }
  157. resultGlobs.push(resultPattern);
  158. }
  159. }
  160. return resultGlobs;
  161. });
  162. }
  163. }
  164. module.exports = AbstractAdapter;