Skip to content

Commit

Permalink
[FEATURE] Introduce Readers "Filter" and "Transformer" (#331)
Browse files Browse the repository at this point in the history
  • Loading branch information
RandomByte authored Jan 13, 2022
1 parent 5cdd926 commit f46e6d1
Show file tree
Hide file tree
Showing 8 changed files with 528 additions and 24 deletions.
15 changes: 15 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ module.exports = {
*/
Memory: "./lib/adapters/Memory"
},
/**
* @public
* @alias module:@ui5/fs.readers
* @namespace
*/
readers: {
/**
* @type {typeof import('./lib/readers/Filter')}
*/
Filter: "./lib/readers/Filter",
/**
* @type {typeof import('./lib/readers/Transformer')}
*/
Transformer: "./lib/readers/Transformer",
},
/**
* @type {typeof import('./lib/AbstractReader')}
*/
Expand Down
33 changes: 33 additions & 0 deletions lib/AbstractReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,39 @@ class AbstractReader {
});
}


/**
* Create a [Filter-Reader]{@link module:@ui5/fs.readers.Filter} from the current reader
*
* @public
* @param {module:@ui5/fs.readers.Filter~callback} callback
* Filter function. Will be called for every resource passed through this reader.
* @returns {module:@ui5/fs.reader.Filter} Filter instance
*/
filter(callback) {
const Filter = require("./readers/Filter");
return new Filter({
reader: this,
callback
});
}

/**
* Create a [Transform-Reader]{@link module:@ui5/fs.readers.Transform} from the current reader
*
* @public
* @param {module:@ui5/fs.readers.Transformer~callback} callback
* Callback to check and eventually transform any resource passed through the reader
* @returns {module:@ui5/fs.reader.Transformer} Transformer instance
*/
transformer(callback) {
const Transformer = require("./readers/Transformer");
return new Transformer({
reader: this,
callback
});
}

/**
* Locates resources by one or more glob patterns.
*
Expand Down
50 changes: 37 additions & 13 deletions lib/ResourceTagCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,77 @@ const tagNamespaceRegExp = new RegExp("^[a-z][a-z0-9]*$"); // part before the co
const tagNameRegExp = new RegExp("^[A-Z][A-Za-z0-9]+$"); // part after the colon

class ResourceTagCollection {
constructor({allowedTags}) {
constructor({allowedTags, superCollection}) {
if (!allowedTags || !allowedTags.length) {
throw new Error(`Missing parameter 'allowedTags'`);
}

if (superCollection) {
this._superCollection = superCollection;
this._superTags = this._superCollection.getAcceptedTags();
} else {
this._superTags = [];
}

// No validation of tag names here since we might remove/ignore
// this parameter in the future and generally allow all tags
this._allowedTags = Object.freeze(allowedTags);
this._pathTags = {};
}

setTag(resource, tag, value = true) {
this._validateResource(resource);
setTag(resourcePath, tag, value = true) {
if (this._superTags.includes(tag)) {
return this._superCollection.setTag(resourcePath, tag, value);
}

resourcePath = this._getPath(resourcePath);
this._validateTag(tag);
this._validateValue(value);

const resourcePath = resource.getPath();
if (!this._pathTags[resourcePath]) {
this._pathTags[resourcePath] = {};
}
this._pathTags[resourcePath][tag] = value;
}

clearTag(resource, tag) {
this._validateResource(resource);
clearTag(resourcePath, tag) {
if (this._superTags.includes(tag)) {
return this._superCollection.clearTag(resourcePath, tag);
}

resourcePath = this._getPath(resourcePath);
this._validateTag(tag);

const resourcePath = resource.getPath();
if (this._pathTags[resourcePath]) {
this._pathTags[resourcePath][tag] = undefined;
}
}

getTag(resource, tag) {
this._validateResource(resource);
getTag(resourcePath, tag) {
if (this._superTags.includes(tag)) {
return this._superCollection.getTag(resourcePath, tag);
}

resourcePath = this._getPath(resourcePath);
this._validateTag(tag);

const resourcePath = resource.getPath();
if (this._pathTags[resourcePath]) {
return this._pathTags[resourcePath][tag];
}
}

_validateResource(resource) {
const path = resource.getPath();
if (!path) {
getAcceptedTags() {
return [...this._allowedTags, ...this._superTags];
}

_getPath(resourcePath) {
if (typeof resourcePath !== "string") {
resourcePath = resourcePath.getPath();
}
if (!resourcePath) {
throw new Error(`Invalid Resource: Resource path must not be empty`);
}
return resourcePath;
}

_validateTag(tag) {
Expand Down
73 changes: 73 additions & 0 deletions lib/readers/Filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const AbstractReader = require("../AbstractReader");

/**
* A reader that allows dynamic filtering of resources passed through it
*
* @public
* @memberof module:@ui5/fs
* @augments module:@ui5/fs.AbstractReader
*/
class Filter extends AbstractReader {
/**
* Filter callback
*
* @public
* @callback module:@ui5/fs.readers.Filter~callback
* @param {module:@ui5/fs.Resource} resource Resource to test
* @returns {boolean} Whether to keep the resource
*/

/**
* Constructor
*
* @param {object} parameters Parameters
* @param {module:@ui5/fs.AbstractReader} parameters.reader The resource reader to wrap
* @param {module:@ui5/fs.readers.Filter~callback} parameters.callback
* Filter function. Will be called for every resource read through this reader.
*/
constructor({reader, callback}) {
super();
if (!reader) {
throw new Error(`Missing parameter "reader"`);
}
if (!callback) {
throw new Error(`Missing parameter "callback"`);
}
this._reader = reader;
this._callback = callback;
}

/**
* 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 {module:@ui5/fs.tracing.Trace} trace Trace instance
* @returns {Promise<module:@ui5/fs.Resource[]>} Promise resolving to list of resources
*/
async _byGlob(pattern, options, trace) {
const result = await this._reader._byGlob(pattern, options, trace);
return result.filter(this._callback);
}

/**
* Locates resources by path.
*
* @private
* @param {string} virPath Virtual path
* @param {object} options Options
* @param {module:@ui5/fs.tracing.Trace} trace Trace instance
* @returns {Promise<module:@ui5/fs.Resource>} Promise resolving to a single resource
*/
async _byPath(virPath, options, trace) {
const result = await this._reader._byPath(virPath, options, trace);
if (result && !this._callback(result)) {
return null;
}
return result;
}
}

module.exports = Filter;
98 changes: 98 additions & 0 deletions lib/readers/Transformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const AbstractReader = require("../AbstractReader");

/**
* A reader that allows modification of all resources passed through it.
*
* @public
* @memberof module:@ui5/fs.readers
* @augments module:@ui5/fs.AbstractReader
*/
class Transformer extends AbstractReader {
/**
* Callback to check and eventually transform a resource
*
* @public
* @callback module:@ui5/fs.readers.Transformer~callback
* @param {module:@ui5/fs.Resource} resourcePath Path of the resource to process.
* This can be used to decide whether the resource should be transformed
* @param {module:@ui5/fs.readers.Transformer~getResource}
* Function to retrieve the given resource instance in order to transform it
* @returns {Promise} Promise resolving once the transformation is done
*/

/**
* Callback to retrieve a resource for modification. This will create a clone of the original
* resource which then takes its place in the result set of the reader
*
* @public
* @callback module:@ui5/fs.readers.Transformer~getResource
* @returns {Promise<module:@ui5/fs.Resource>} Promise resolving to the resource
*/

/**
* Constructor
*
* @param {object} parameters Parameters
* @param {module:@ui5/fs.AbstractReader} parameters.reader The resource reader to wrap
* @param {module:@ui5/fs.readers.Transformer~callback} parameters.callback
* Filter function. Will be called for every resource read through this reader.
*/
constructor({reader, callback}) {
super();
if (!reader) {
throw new Error(`Missing parameter "reader"`);
}
if (!callback) {
throw new Error(`Missing parameter "callback"`);
}
this._reader = reader;
this._callback = callback;
}

/**
* 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 {module:@ui5/fs.tracing.Trace} trace Trace instance
* @returns {Promise<module:@ui5/fs.Resource[]>} Promise resolving to list of resources
*/
async _byGlob(pattern, options, trace) {
const result = await this._reader._byGlob(pattern, options, trace);
return Promise.all(result.map(async (resource) => {
let resourceClone;
await this._callback(resource.getPath(), async function() {
// Make sure to only clone once
resourceClone = resourceClone || await resource.clone();
return resourceClone;
});
return resourceClone || resource;
}));
}

/**
* Locates resources by path.
*
* @private
* @param {string} virPath Virtual path
* @param {object} options Options
* @param {module:@ui5/fs.tracing.Trace} trace Trace instance
* @returns {Promise<module:@ui5/fs.Resource>} Promise resolving to a single resource
*/
async _byPath(virPath, options, trace) {
const resource = await this._reader._byPath(virPath, options, trace);
let resourceClone;
if (resource) {
await this._callback(resource.getPath(), async function() {
// Make sure to only clone once
resourceClone = resourceClone || await resource.clone();
return resourceClone;
});
}
return resourceClone || resource;
}
}

module.exports = Transformer;
Loading

0 comments on commit f46e6d1

Please sign in to comment.