fs/lib/readers/Link.js

  1. import AbstractReader from "../AbstractReader.js";
  2. import ResourceFacade from "../ResourceFacade.js";
  3. import {prefixGlobPattern} from "../resourceFactory.js";
  4. import {getLogger} from "@ui5/logger";
  5. const log = getLogger("resources:readers:Link");
  6. /**
  7. * A reader that allows for rewriting paths segments of all resources passed through it.
  8. *
  9. * @example
  10. * import Link from "@ui5/fs/readers/Link";
  11. * const linkedReader = new Link({
  12. * reader: sourceReader,
  13. * pathMapping: {
  14. * linkPath: `/app`,
  15. * targetPath: `/resources/my-app-name/`
  16. * }
  17. * });
  18. *
  19. * // The following resolves with a @ui5/fs/ResourceFacade of the resource
  20. * // located at "/resources/my-app-name/Component.js" in the sourceReader
  21. * const resource = await linkedReader.byPath("/app/Component.js");
  22. *
  23. * @public
  24. * @class
  25. * @alias @ui5/fs/readers/Link
  26. * @extends @ui5/fs/AbstractReader
  27. */
  28. class Link extends AbstractReader {
  29. /**
  30. * Path mapping for a [Link]{@link @ui5/fs/readers/Link}
  31. *
  32. * @public
  33. * @typedef {object} @ui5/fs/readers/Link/PathMapping
  34. * @property {string} linkPath Path to match and replace in the requested path or pattern
  35. * @property {string} targetPath Path to use as a replacement in the request for the source reader
  36. */
  37. /**
  38. * Constructor
  39. *
  40. * @public
  41. * @param {object} parameters Parameters
  42. * @param {@ui5/fs/AbstractReader} parameters.reader The resource reader or collection to wrap
  43. * @param {@ui5/fs/readers/Link/PathMapping} parameters.pathMapping
  44. */
  45. constructor({reader, pathMapping}) {
  46. super();
  47. if (!reader) {
  48. throw new Error(`Missing parameter "reader"`);
  49. }
  50. if (!pathMapping) {
  51. throw new Error(`Missing parameter "pathMapping"`);
  52. }
  53. this._reader = reader;
  54. this._pathMapping = pathMapping;
  55. Link._validatePathMapping(pathMapping);
  56. }
  57. /**
  58. * Locates resources by glob.
  59. *
  60. * @private
  61. * @param {string|string[]} patterns glob pattern as string or an array of
  62. * glob patterns for virtual directory structure
  63. * @param {object} options glob options
  64. * @param {@ui5/fs/tracing/Trace} trace Trace instance
  65. * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving to list of resources
  66. */
  67. async _byGlob(patterns, options, trace) {
  68. if (!(patterns instanceof Array)) {
  69. patterns = [patterns];
  70. }
  71. patterns = patterns.map((pattern) => {
  72. if (pattern.startsWith(this._pathMapping.linkPath)) {
  73. pattern = pattern.substr(this._pathMapping.linkPath.length);
  74. }
  75. return prefixGlobPattern(pattern, this._pathMapping.targetPath);
  76. });
  77. // Flatten prefixed patterns
  78. patterns = Array.prototype.concat.apply([], patterns);
  79. // Keep resource's internal path unchanged for now
  80. const resources = await this._reader._byGlob(patterns, options, trace);
  81. return resources.map((resource) => {
  82. const resourcePath = resource.getPath();
  83. if (resourcePath.startsWith(this._pathMapping.targetPath)) {
  84. return new ResourceFacade({
  85. resource,
  86. path: this._pathMapping.linkPath + resourcePath.substr(this._pathMapping.targetPath.length)
  87. });
  88. }
  89. });
  90. }
  91. /**
  92. * Locates resources by path.
  93. *
  94. * @private
  95. * @param {string} virPath Virtual path
  96. * @param {object} options Options
  97. * @param {@ui5/fs/tracing/Trace} trace Trace instance
  98. * @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource
  99. */
  100. async _byPath(virPath, options, trace) {
  101. if (!virPath.startsWith(this._pathMapping.linkPath)) {
  102. return null;
  103. }
  104. const targetPath = this._pathMapping.targetPath + virPath.substr(this._pathMapping.linkPath.length);
  105. log.silly(`byPath: Rewriting virtual path ${virPath} to ${targetPath}`);
  106. const resource = await this._reader._byPath(targetPath, options, trace);
  107. if (resource) {
  108. return new ResourceFacade({
  109. resource,
  110. path: this._pathMapping.linkPath + resource.getPath().substr(this._pathMapping.targetPath.length)
  111. });
  112. }
  113. return null;
  114. }
  115. static _validatePathMapping({linkPath, targetPath}) {
  116. if (!linkPath) {
  117. throw new Error(`Path mapping is missing attribute "linkPath"`);
  118. }
  119. if (!targetPath) {
  120. throw new Error(`Path mapping is missing attribute "targetPath"`);
  121. }
  122. if (!linkPath.endsWith("/")) {
  123. throw new Error(`Link path must end with a slash: ${linkPath}`);
  124. }
  125. if (!targetPath.endsWith("/")) {
  126. throw new Error(`Target path must end with a slash: ${targetPath}`);
  127. }
  128. }
  129. }
  130. export default Link;