Skip to content

Commit

Permalink
[FEATURE] AbstractAdapter: Add excludes option (#140)
Browse files Browse the repository at this point in the history
- Update globby and micromatch dependencies
- Adapt to globby/micromatch changes
    - globby: 'nodir' option renamed to 'onlyFiles'
    - globby: Now transforms empty GLOB strings into globstar (matching
      everything instead of nothing)
        - mitigated by not globbing with empty strings anymore
    - globby/micromatch: Including excluded paths is now possible
        - before this was inconsistent
    - FileSystem._runGlob now throws errors for glob errors (instead of
      catching and logging them)
- resourceFactory: Add callback for virtual base path prefix determination
    - The useNamespace flag was not sufficient in cases where the namespace
      should only be added for projects that do not already represent it in
      their directory structure.
      With SAP/ui5-builder#255 libraries will have a
      namespace property even though their directory structure already
      represents the namespace.
  • Loading branch information
RandomByte authored May 29, 2019
1 parent cef49a7 commit daef31f
Show file tree
Hide file tree
Showing 16 changed files with 1,998 additions and 212 deletions.
26 changes: 23 additions & 3 deletions lib/adapters/AbstractAdapter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const log = require("@ui5/logger").getLogger("resources:adapters:AbstractAdapter");
const minimatch = require("minimatch");
const micromatch = require("micromatch");
const AbstractReaderWriter = require("../AbstractReaderWriter");
const Resource = require("../Resource");

Expand All @@ -18,17 +19,19 @@ class AbstractAdapter extends AbstractReaderWriter {
* @public
* @param {Object} parameters Parameters
* @param {string} parameters.virBasePath Virtual base path
* @param {string[]} [parameters.excludes] List of glob patterns to exclude
*/
constructor({virBasePath, project}) {
constructor({virBasePath, excludes = [], project}) {
if (new.target === AbstractAdapter) {
throw new TypeError("Class 'AbstractAdapter' is abstract");
}
super();
this._virBasePath = virBasePath;
this._virBaseDir = virBasePath.slice(0, -1);
this._excludes = excludes;
this._excludesNegated = excludes.map((pattern) => `!${pattern}`);
this._project = project;
}

/**
* Locates resources by glob.
*
Expand All @@ -42,14 +45,21 @@ class AbstractAdapter extends AbstractReaderWriter {
* @returns {Promise<module:@ui5/fs.Resource[]>} Promise resolving to list of resources
*/
_byGlob(virPattern, options = {nodir: true}, trace) {
const excludes = this._excludesNegated;

if (!(virPattern instanceof Array)) {
virPattern = [virPattern];
}

// Append static exclude patterns
virPattern = Array.prototype.concat.apply(virPattern, excludes);

return Promise.all(virPattern.map(this._normalizePattern, this)).then((patterns) => {
patterns = Array.prototype.concat.apply([], patterns);
if (patterns.length === 0) {
return [];
}
patterns = Array.prototype.concat.apply([], patterns);

if (!options.nodir) {
for (let i = patterns.length - 1; i >= 0; i--) {
const idx = this._virBaseDir.indexOf(patterns[i]);
Expand All @@ -73,6 +83,16 @@ class AbstractAdapter extends AbstractReaderWriter {
});
}

/**
* Validate if virtual path should be excluded
*
* @param {string} virPath Virtual Path
* @returns {boolean} True if path is excluded, otherwise false
*/
isPathExcluded(virPath) {
return micromatch(virPath, this._excludes).length > 0;
}

/**
* Normalizes virtual glob patterns.
*
Expand Down
131 changes: 69 additions & 62 deletions lib/adapters/FileSystem.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const log = require("@ui5/logger").getLogger("resources:adapters:FileSystem");
const path = require("path");
const fs = require("graceful-fs");
const glob = require("globby");
const globby = require("globby");
const makeDir = require("make-dir");
const {PassThrough} = require("stream");
const Resource = require("../Resource");
Expand All @@ -21,9 +21,10 @@ class FileSystem extends AbstractAdapter {
* @param {Object} parameters Parameters
* @param {string} parameters.virBasePath Virtual base path
* @param {string} parameters.fsBasePath (Physical) File system path
* @param {string[]} [parameters.excludes] List of glob patterns to exclude
*/
constructor({virBasePath, project, fsBasePath}) {
super({virBasePath, project});
constructor({virBasePath, project, fsBasePath, excludes}) {
super({virBasePath, project, excludes});
this._fsBasePath = fsBasePath;
}

Expand All @@ -37,69 +38,71 @@ class FileSystem extends AbstractAdapter {
* @param {module:@ui5/fs.tracing.Trace} trace Trace instance
* @returns {Promise<module:@ui5/fs.Resource[]>} Promise resolving to list of resources
*/
_runGlob(patterns, options = {nodir: true}, trace) {
return new Promise((resolve, reject) => {
const opt = {
cwd: this._fsBasePath,
dot: true,
nodir: options.nodir
};

trace.globCall();
glob(patterns, opt).then((matches) => {
const promises = [];
if (!opt.nodir && patterns[0] === "") { // Match physical root directory
promises.push(new Promise((resolve, reject) => {
fs.stat(this._fsBasePath, (err, stat) => {
if (err) {
reject(err);
} else {
resolve(new Resource({
project: this._project,
statInfo: stat,
path: this._virBaseDir,
createStream: () => {
return fs.createReadStream(this._fsBasePath);
}
}));
}
});
}));
}
async _runGlob(patterns, options = {nodir: true}, trace) {
const opt = {
cwd: this._fsBasePath,
dot: true,
onlyFiles: options.nodir,
followSymlinkedDirectories: false
};
trace.globCall();

for (let i = matches.length - 1; i >= 0; i--) {
promises.push(new Promise((resolve, reject) => {
const fsPath = path.join(this._fsBasePath, matches[i]);
const virPath = (this._virBasePath + matches[i]);

// Workaround for not getting the stat from the glob
fs.stat(fsPath, (err, stat) => {
if (err) {
reject(err);
} else {
resolve(new Resource({
project: this._project,
statInfo: stat,
path: virPath,
createStream: () => {
return fs.createReadStream(fsPath);
}
}));
const promises = [];
if (!opt.onlyFiles && patterns.includes("")) { // Match physical root directory
promises.push(new Promise((resolve, reject) => {
fs.stat(this._fsBasePath, (err, stat) => {
if (err) {
reject(err);
} else {
resolve(new Resource({
project: this._project,
statInfo: stat,
path: this._virBaseDir,
createStream: () => {
return fs.createReadStream(this._fsBasePath);
}
});
}));
}

Promise.all(promises).then(function(results) {
const flat = Array.prototype.concat.apply([], results);
resolve(flat);
}, function(err) {
reject(err);
}));
}
});
}).catch((err) => {
log.error(err);
});
}));
}

// Remove empty string glob patterns
// Starting with globby v8 or v9 empty glob patterns "" act like "**"
// Micromatch throws on empty strings. We just ignore them since they are
// typically caused by our normalization in the AbstractAdapter
const globbyPatterns = patterns.filter((pattern) => {
return pattern !== "";
});
if (globbyPatterns.length > 0) {
const matches = await globby(globbyPatterns, opt);
for (let i = matches.length - 1; i >= 0; i--) {
promises.push(new Promise((resolve, reject) => {
const fsPath = path.join(this._fsBasePath, matches[i]);
const virPath = (this._virBasePath + matches[i]);

// Workaround for not getting the stat from the glob
fs.stat(fsPath, (err, stat) => {
if (err) {
reject(err);
} else {
resolve(new Resource({
project: this._project,
statInfo: stat,
path: virPath,
createStream: () => {
return fs.createReadStream(fsPath);
}
}));
}
});
}));
}
}
const results = await Promise.all(promises);

// Flatten results
return Array.prototype.concat.apply([], results);
}

/**
Expand All @@ -112,6 +115,10 @@ class FileSystem extends AbstractAdapter {
* @returns {Promise<module:@ui5/fs.Resource>} Promise resolving to a single resource or null if not found
*/
_byPath(virPath, options, trace) {
if (this.isPathExcluded(virPath)) {
return Promise.resolve(null);
}

return new Promise((resolve, reject) => {
if (!virPath.startsWith(this._virBasePath) && virPath !== this._virBaseDir) {
// Neither starts with basePath, nor equals baseDirectory
Expand Down
16 changes: 10 additions & 6 deletions lib/adapters/Memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ class Memory extends AbstractAdapter {
* @public
* @param {Object} parameters Parameters
* @param {string} parameters.virBasePath Virtual base path
* @param {string[]} [parameters.excludes] List of glob patterns to exclude
*/
constructor({virBasePath, project}) {
super({virBasePath, project});
constructor({virBasePath, project, excludes}) {
super({virBasePath, project, excludes});
this._virFiles = {}; // map full of files
this._virDirs = {}; // map full of directories
}
Expand All @@ -34,9 +35,9 @@ class Memory extends AbstractAdapter {
* @param {module:@ui5/fs.tracing.Trace} trace Trace instance
* @returns {Promise<module:@ui5/fs.Resource[]>} Promise resolving to list of resources
*/
_runGlob(patterns, options = {nodir: true}, trace) {
async _runGlob(patterns, options = {nodir: true}, trace) {
if (patterns[0] === "" && !options.nodir) { // Match virtual root directory
return Promise.resolve([
return [
new Resource({
project: this.project,
statInfo: { // TODO: make closer to fs stat info
Expand All @@ -46,7 +47,7 @@ class Memory extends AbstractAdapter {
},
path: this._virBasePath.slice(0, -1)
})
]);
];
}

const filePaths = Object.keys(this._virFiles);
Expand All @@ -67,7 +68,7 @@ class Memory extends AbstractAdapter {
}));
}

return Promise.resolve(matchedResources);
return matchedResources;
}

/**
Expand All @@ -80,6 +81,9 @@ class Memory extends AbstractAdapter {
* @returns {Promise<module:@ui5/fs.Resource>} Promise resolving to a single resource
*/
_byPath(virPath, options, trace) {
if (this.isPathExcluded(virPath)) {
return Promise.resolve(null);
}
return new Promise((resolve, reject) => {
if (!virPath.startsWith(this._virBasePath) && virPath !== this._virBaseDir) {
// Neither starts with basePath, nor equals baseDirectory
Expand Down
Loading

0 comments on commit daef31f

Please sign in to comment.