fs/lib/WriterCollection.js

import AbstractReaderWriter from "./AbstractReaderWriter.js";
import ReaderCollection from "./ReaderCollection.js";
import escapeStringRegExp from "escape-string-regexp";

/**
 * Resource Locator WriterCollection
 *
 * @public
 * @class
 * @alias @ui5/fs/WriterCollection
 * @extends @ui5/fs/AbstractReaderWriter
 */
class WriterCollection extends AbstractReaderWriter {
	/**
	 * The constructor.
	 *
	 * @param {object} parameters Parameters
	 * @param {string} parameters.name The collection name
	 * @param {object.<string, @ui5/fs/AbstractReaderWriter>} parameters.writerMapping
	 * 	Mapping of virtual base paths to writers. Path are matched greedy
	 *
	 * @example
	 * new WriterCollection({
	 *     name: "Writer Collection",
	 *     writerMapping: {
	 *	       "/": writerA,
	 *	       "/my/path/": writerB,
	 *     }
	 * });
	 */
	constructor({name, writerMapping}) {
		super(name);

		if (!writerMapping) {
			throw new Error(`Cannot create WriterCollection ${this._name}: Missing parameter 'writerMapping'`);
		}
		const basePaths = Object.keys(writerMapping);
		if (!basePaths.length) {
			throw new Error(`Cannot create WriterCollection ${this._name}: Empty parameter 'writerMapping'`);
		}

		// Create a regular expression (which is greedy by nature) from all paths to easily
		//	find the correct writer for any given resource path
		this._basePathRegex = basePaths.sort().reduce((regex, basePath, idx) => {
			// Validate base path
			if (!basePath) {
				throw new Error(`Empty path in path mapping of WriterCollection ${this._name}`);
			}
			if (!basePath.startsWith("/")) {
				throw new Error(
					`Missing leading slash in path mapping '${basePath}' of WriterCollection ${this._name}`);
			}
			if (!basePath.endsWith("/")) {
				throw new Error(
					`Missing trailing slash in path mapping '${basePath}' of WriterCollection ${this._name}`);
			}

			return `${regex}(?:${escapeStringRegExp(basePath)})??`;
		}, "^(") + ")+.*?$";

		this._writerMapping = writerMapping;
		this._readerCollection = new ReaderCollection({
			name: `Reader collection of writer collection '${this._name}'`,
			readers: Object.values(writerMapping)
		});
	}

	/**
	 * Locates resources by glob.
	 *
	 * @private
	 * @param {string|string[]} pattern glob pattern as string or an array of
	 *         glob patterns for virtual directory structure
	 * @param {object} options glob options
	 * @param {@ui5/fs/tracing.Trace} trace Trace instance
	 * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving to list of resources
	 */
	_byGlob(pattern, options, trace) {
		return this._readerCollection._byGlob(pattern, options, trace);
	}

	/**
	 * Locates resources by path.
	 *
	 * @private
	 * @param {string} virPath Virtual path
	 * @param {object} options Options
	 * @param {@ui5/fs/tracing.Trace} trace Trace instance
	 * @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource
	 */
	_byPath(virPath, options, trace) {
		return this._readerCollection._byPath(virPath, options, trace);
	}

	/**
	 * Writes the content of a resource to a path.
	 *
	 * @private
	 * @param {@ui5/fs/Resource} resource The Resource to write
	 * @param {object} [options] Write options, see above
	 * @returns {Promise<undefined>} Promise resolving once data has been written
	 */
	_write(resource, options) {
		const resourcePath = resource.getPath();

		const basePathMatch = resourcePath.match(this._basePathRegex);
		if (!basePathMatch || basePathMatch.length < 2) {
			throw new Error(
				`Failed to find a writer for resource with path ${resourcePath} in WriterCollection ${this._name}. ` +
				`Base paths handled by this collection are: ${Object.keys(this._writerMapping).join(", ")}`);
		}
		const writer = this._writerMapping[basePathMatch[1]];
		return writer._write(resource, options);
	}
}

export default WriterCollection;