Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Introduce Readers "Filter" and "Transformer" #331

Merged
merged 15 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: {
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
/**
* @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) {
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
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