Skip to content
30 changes: 30 additions & 0 deletions src/utils/file.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import fs from "fs";
import path from "path";

const FILE_EXTENSION_TO_PROGRAMMING_LANGUAGE_MAP = {
in: "fortran",
sh: "shell",
Expand Down Expand Up @@ -28,3 +31,30 @@ export function formatFileSize(size, decimals = 2) {
const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
return parseFloat((size / 1024 ** index).toFixed(decimals)) + " " + units[index];
}

/** Get list of paths for files in a directory and filter by file extensions if provided.
* @param {string} dirPath - Path to current directory, i.e. $PWD
* @param {string[]} fileExtensions - File extensions to filter, e.g. `.yml`
* @param {boolean} resolvePath - whether to resolve the paths of files
* @returns {string[]} - Array of file paths
*/
export function getFilesInDirectory(dirPath, fileExtensions = [], resolvePath = true) {
let fileNames = fs.readdirSync(dirPath);
if (fileExtensions.length) {
fileNames = fileNames.filter((dirItem) => fileExtensions.includes(path.extname(dirItem)));
}
if (resolvePath) return fileNames.map((fileName) => path.resolve(dirPath, fileName));
return fileNames;
}

/**
* Get list of directories contained in current directory.
* @param {string} currentPath - current directory
* @return {*}
*/
export function getDirectories(currentPath) {
return fs
.readdirSync(currentPath, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
}
53 changes: 53 additions & 0 deletions src/utils/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import lodash from "lodash";

/**
* Check if one path matches regular expression or exact string.
* @param {{path: string}} pathObject - Entity or object with path property
* @param {Array<{path: string}|{regex: RegExp}>} filterObjects - Filter conditions
* @return {boolean}
*/
function isPathSupported(pathObject, filterObjects) {
return filterObjects.some((filterObj) => {
if (filterObj.path) {
return filterObj.path === pathObject.path;
}
if (filterObj.regex) {
return filterObj.regex.test(pathObject.path);
}
return false;
});
}

/**
* Check if _all_ paths in concatenated path match filtering conditions.
* @param {{path: string}} pathObject - Path object with concatenated path (multipath)
* @param {string} multiPathSeparator - String sequence used for concatenation of paths
* @param {Array<{path: string}|{regex: RegExp}>} filterObjects - Filter conditions
* @return {boolean}
*/
function isMultiPathSupported(pathObject, multiPathSeparator, filterObjects) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const expandedPaths = pathObject.path.split(multiPathSeparator).map((p) => ({ path: p }));
return expandedPaths.every((expandedPath) => isPathSupported(expandedPath, filterObjects));
}

/**
* Filter list of entity paths or entities by paths and regular expressions.
* @param {Object[]} entitiesOrPaths - Array of objects defining entity path
* @param {Array<{ path: string }|{ regex: string }|{ regex: RegExp }>} filterObjects - Array of path or regular expression objects
* @param {string} multiPathSeparator - string by which paths should be split
* @return {Object[]} - filtered entity path objects or entities
*/
export function filterEntityList({ entitiesOrPaths, filterObjects = [], multiPathSeparator = "" }) {
const filterObjects_ = filterObjects.map((o) => (o.regex ? { regex: new RegExp(o.regex) } : o));

let filtered;
if (multiPathSeparator) {
filtered = entitiesOrPaths.filter((e) =>
isMultiPathSupported(e, multiPathSeparator, filterObjects_),
);
} else {
filtered = entitiesOrPaths.filter((e) => isPathSupported(e, filterObjects_));
}

return lodash.uniqBy(filtered, "path");
}
11 changes: 10 additions & 1 deletion src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { convertToCompactCSVArrayOfObjects, safeMakeArray } from "./array";
import { cloneClass, extendClass, extendClassStaticProps, extendThis } from "./class";
import { deepClone } from "./clone";
import { refreshCodeMirror } from "./codemirror";
import { formatFileSize, getProgrammingLanguageFromFileExtension } from "./file";
import {
formatFileSize,
getDirectories,
getFilesInDirectory,
getProgrammingLanguageFromFileExtension,
} from "./file";
import { filterEntityList } from "./filter";
import { addUnit, removeUnit, replaceUnit, setNextLinks, setUnitsHead } from "./graph";
import {
calculateHashFromObject,
Expand Down Expand Up @@ -78,4 +84,7 @@ export {
JsYamlTypes,
JsYamlAllSchemas,
renderTextWithSubstitutes,
filterEntityList,
getFilesInDirectory,
getDirectories,
};
74 changes: 74 additions & 0 deletions tests/utils/filter.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { expect } from "chai";

import { filterEntityList } from "../../src/utils/filter";

describe("entity filter", () => {
const entities = [
{ name: "A", path: "/root/entity/a" },
{ name: "B", path: "/root/entity/b" },
{ name: "C", path: "/root/entity/c" },
{ name: "D", path: "/root/entity/d" },
];

it("should filter an entity list with paths", () => {
const filterObjects = [{ path: "/root/entity/b" }, { path: "/root/entity/c" }];
const filtered = filterEntityList({ filterObjects, entitiesOrPaths: entities });
const expected = [
{ name: "B", path: "/root/entity/b" },
{ name: "C", path: "/root/entity/c" },
];
expect(filtered).to.have.deep.members(expected);
});

it("should filter an entity list with regular expressions", () => {
const filterObjects = [{ regex: /\/root\/entity\/[bc]/ }];
const filtered = filterEntityList({ filterObjects, entitiesOrPaths: entities });
const expected = [
{ name: "B", path: "/root/entity/b" },
{ name: "C", path: "/root/entity/c" },
];
expect(filtered).to.have.deep.members(expected);
});

it("should filter an entity list with both paths and regular expressions", () => {
const filterObjects = [{ path: "/root/entity/b" }, { regex: /\/root\/entity\/[c]/ }];
const filtered = filterEntityList({ filterObjects, entitiesOrPaths: entities });
const expected = [
{ name: "B", path: "/root/entity/b" },
{ name: "C", path: "/root/entity/c" },
];
expect(filtered).to.have.deep.members(expected);
});

it("should filter an entity list containing concatenated paths", () => {
const filterObjects = [{ path: "/root/entity/b" }, { path: "/root/entity/c" }];
const multiPathEntities = [
{ name: "AB", path: "/root/entity/a::/root/entity/b" },
{ name: "BC", path: "/root/entity/b::/root/entity/c" },
];
const multiPathSeparator = "::";
const filtered = filterEntityList({
filterObjects,
entitiesOrPaths: multiPathEntities,
multiPathSeparator,
});
const expected = [{ name: "BC", path: "/root/entity/b::/root/entity/c" }];
expect(filtered).to.have.deep.members(expected);
});

it("should filter an entity list containing concatenated paths using regex", () => {
const filterObjects = [{ path: "/root/entity/b" }, { regex: /\/root\/entity\/[c]/ }];
const multiPathEntities = [
{ name: "AB", path: "/root/entity/a::/root/entity/b" },
{ name: "BC", path: "/root/entity/b::/root/entity/c" },
];
const multiPathSeparator = "::";
const filtered = filterEntityList({
filterObjects,
entitiesOrPaths: multiPathEntities,
multiPathSeparator,
});
const expected = [{ name: "BC", path: "/root/entity/b::/root/entity/c" }];
expect(filtered).to.have.deep.members(expected);
});
});