mirror of
https://github.com/coder/coder.git
synced 2026-11-02 21:50:08 +08:00
111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
import get from "lodash/get";
|
|
import has from "lodash/has";
|
|
import set from "lodash/set";
|
|
import unset from "lodash/unset";
|
|
|
|
export type FileTree = {
|
|
[key: string]: FileTree | string;
|
|
};
|
|
|
|
export const createFile = (
|
|
path: string,
|
|
fileTree: FileTree,
|
|
value: string,
|
|
): FileTree => {
|
|
if (existsFile(path, fileTree)) {
|
|
throw new Error(`File ${path} already exists`);
|
|
}
|
|
const pathError = validatePath(path, fileTree);
|
|
if (pathError) {
|
|
throw new Error(pathError);
|
|
}
|
|
|
|
const updatedFileTree = structuredClone(fileTree);
|
|
return set(updatedFileTree, path.split("/"), value);
|
|
};
|
|
|
|
export const validatePath = (
|
|
path: string,
|
|
fileTree: FileTree,
|
|
): string | undefined => {
|
|
const paths = path.split("/");
|
|
paths.pop(); // The last item is the filename
|
|
for (let i = 0; i <= paths.length; i++) {
|
|
const path = paths.slice(0, i + 1);
|
|
const pathStr = path.join("/");
|
|
if (existsFile(pathStr, fileTree) && !isFolder(pathStr, fileTree)) {
|
|
return `Invalid path. The path ${path} is not a folder`;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const updateFile = (
|
|
path: string,
|
|
content: FileTree | string,
|
|
fileTree: FileTree,
|
|
): FileTree => {
|
|
const updatedFileTree = structuredClone(fileTree);
|
|
return set(updatedFileTree, path.split("/"), content);
|
|
};
|
|
|
|
export const existsFile = (path: string, fileTree: FileTree) => {
|
|
return has(fileTree, path.split("/"));
|
|
};
|
|
|
|
export const removeFile = (path: string, fileTree: FileTree) => {
|
|
const updatedFileTree = structuredClone(fileTree);
|
|
unset(updatedFileTree, path.split("/"));
|
|
return updatedFileTree;
|
|
};
|
|
|
|
export const moveFile = (
|
|
currentPath: string,
|
|
newPath: string,
|
|
fileTree: FileTree,
|
|
) => {
|
|
const content = getFileContent(currentPath, fileTree);
|
|
if (typeof content !== "string") {
|
|
throw new Error("Move folders is not allowed");
|
|
}
|
|
fileTree = removeFile(currentPath, fileTree);
|
|
fileTree = createFile(newPath, fileTree, content);
|
|
return fileTree;
|
|
};
|
|
|
|
export const getFileContent = (path: string, fileTree: FileTree) => {
|
|
return get(fileTree, path.split("/")) as string | FileTree;
|
|
};
|
|
|
|
export const getFileText = (path: string, fileTree: FileTree) => {
|
|
const content = getFileContent(path, fileTree);
|
|
if (typeof content !== "string") {
|
|
throw new Error("File is not a text file");
|
|
}
|
|
return content;
|
|
};
|
|
|
|
export const isFolder = (path: string, fileTree: FileTree) => {
|
|
const content = getFileContent(path, fileTree);
|
|
return typeof content === "object";
|
|
};
|
|
|
|
export const traverse = (
|
|
fileTree: FileTree,
|
|
callback: (
|
|
content: FileTree | string,
|
|
filename: string,
|
|
fullPath: string,
|
|
) => void,
|
|
parent?: string,
|
|
) => {
|
|
for (const [filename, content] of Object.entries(fileTree).sort(([a], [b]) =>
|
|
a.localeCompare(b),
|
|
)) {
|
|
const fullPath = parent ? `${parent}/${filename}` : filename;
|
|
callback(content, filename, fullPath);
|
|
if (typeof content === "object") {
|
|
traverse(content, callback, fullPath);
|
|
}
|
|
}
|
|
};
|