From 9b78f0d41c78916af2e7cf0115535e517b700b92 Mon Sep 17 00:00:00 2001 From: wancat Date: Wed, 16 Aug 2023 17:06:21 +0800 Subject: [PATCH 1/8] feat: create FileNode class, addChild method --- packages/file-graph/file.js | 16 +++++++ packages/file-graph/fileNode.js | 41 ++++++++++++++++++ packages/file-graph/fileNode.test.js | 64 ++++++++++++++++++++++++++++ packages/file-graph/package.json | 10 +++++ 4 files changed, 131 insertions(+) create mode 100644 packages/file-graph/file.js create mode 100644 packages/file-graph/fileNode.js create mode 100644 packages/file-graph/fileNode.test.js create mode 100644 packages/file-graph/package.json diff --git a/packages/file-graph/file.js b/packages/file-graph/file.js new file mode 100644 index 0000000..3b1feed --- /dev/null +++ b/packages/file-graph/file.js @@ -0,0 +1,16 @@ +export default class File { + constructor({ path, name, type, discovered }) { + this.path = path + this.name = name + this.type = type + this.discovered = discovered + } + + isDirectory() { + return this.type === 'folder' + } + + contains(file) { + return this.isDirectory() && file.path.startsWith(this.path) + } +} diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js new file mode 100644 index 0000000..8b4ec8d --- /dev/null +++ b/packages/file-graph/fileNode.js @@ -0,0 +1,41 @@ +import path from 'path' +import File from './file.js' + +export default class FileNode extends File { + constructor(file) { + super(file) + this.children = [] + } + + addChild(fileInput) { + const file = new File(fileInput) + if (file.path === this.path) { + throw new Error('File already exists in the tree') + } + + const parentPath = path.dirname(file.path) + if (parentPath === this.path) { + // Direct child of this node + this.children.push(new FileNode(file)) + } else { + // Recursively call addChild on the correct child node + const childNode = this.children.find((child) => child.contains(file)) + if (childNode) { + childNode.addChild(file) + } else { + console.log(fileInput, this) + throw new Error('Parent path not found in the tree') + } + } + } + + toString() { + const indent = ' '.repeat(this.path.split('/').length - 1) + const childrenString = this.children + .map((child) => child.toString()) + .join('') + return `${indent}${this.name}${ + this.isDirectory() ? '/' : '' + }\n${childrenString}` + } +} diff --git a/packages/file-graph/fileNode.test.js b/packages/file-graph/fileNode.test.js new file mode 100644 index 0000000..fdb9ec0 --- /dev/null +++ b/packages/file-graph/fileNode.test.js @@ -0,0 +1,64 @@ +import FileNode from './fileNode.js' + +test('addChild method adds a child node to the tree', () => { + const files = [ + { + path: '/home/rudeus', + name: 'rudeus', + type: 'folder', + discovered: 'test', + }, + { + path: '/home/rudeus/.bash_history', + name: '.bash_history', + type: 'file', + discovered: 'test', + }, + { + path: '/home/rudeus/.viminfo', + name: '.viminfo', + type: 'file', + discovered: 'test', + }, + { + path: '/home/rudeus/.zshrc', + name: '.zshrc', + type: 'file', + discovered: 'test', + }, + { + path: '/home/rudeus/forgotten_scroll.txt', + name: 'forgotten_scroll.txt', + type: 'file', + discovered: 'test', + }, + { + path: '/home/rudeus/test', + name: 'test', + type: 'folder', + discovered: 'test', + }, + { + path: '/home/rudeus/test/.b', + name: '.b', + type: 'file', + discovered: false, + }, + { + path: '/home/rudeus/test/a', + name: 'a', + type: 'file', + discovered: false, + }, + ] + + const root = new FileNode(files[0]) + + files.slice(1).forEach((file) => { + root.addChild(file) + }) + + console.log(root.toString()) + + expect(root.children.length).toBe(5) +}) diff --git a/packages/file-graph/package.json b/packages/file-graph/package.json new file mode 100644 index 0000000..69b2535 --- /dev/null +++ b/packages/file-graph/package.json @@ -0,0 +1,10 @@ +{ + "name": "file-graph", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "type": "module", + "scripts": { + "test": "yarn node --experimental-vm-modules $(yarn bin jest)" + } +} From 5a1fc6499a5de81ef4efc9797913f91386a69749 Mon Sep 17 00:00:00 2001 From: wancat Date: Wed, 16 Aug 2023 17:24:40 +0800 Subject: [PATCH 2/8] feat: add error handling for duplicate or not exists --- packages/file-graph/errors.js | 14 ++++ packages/file-graph/fileNode.js | 15 ++-- packages/file-graph/fileNode.test.js | 113 +++++++++++++++------------ 3 files changed, 86 insertions(+), 56 deletions(-) create mode 100644 packages/file-graph/errors.js diff --git a/packages/file-graph/errors.js b/packages/file-graph/errors.js new file mode 100644 index 0000000..16539c1 --- /dev/null +++ b/packages/file-graph/errors.js @@ -0,0 +1,14 @@ +/* eslint-disable max-classes-per-file */ +export class DuplicateItemError extends Error { + constructor(message) { + super(message) + this.name = 'DuplicateItemError' + } +} + +export class ParentNotExistsError extends Error { + constructor(message) { + super(message) + this.name = 'ParentNotExistsError' + } +} diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js index 8b4ec8d..a224cd4 100644 --- a/packages/file-graph/fileNode.js +++ b/packages/file-graph/fileNode.js @@ -1,5 +1,6 @@ import path from 'path' import File from './file.js' +import { DuplicateItemError, ParentNotExistsError } from './errors.js' export default class FileNode extends File { constructor(file) { @@ -10,7 +11,11 @@ export default class FileNode extends File { addChild(fileInput) { const file = new File(fileInput) if (file.path === this.path) { - throw new Error('File already exists in the tree') + throw new DuplicateItemError('File already exists in the tree') + } + + if (this.children.some((child) => child.path === file.path)) { + throw new DuplicateItemError('File already exists in the tree') } const parentPath = path.dirname(file.path) @@ -23,19 +28,17 @@ export default class FileNode extends File { if (childNode) { childNode.addChild(file) } else { - console.log(fileInput, this) - throw new Error('Parent path not found in the tree') + throw new ParentNotExistsError('Parent path not found in the tree') } } } toString() { const indent = ' '.repeat(this.path.split('/').length - 1) + const suffix = this.isDirectory() ? '/' : '' const childrenString = this.children .map((child) => child.toString()) .join('') - return `${indent}${this.name}${ - this.isDirectory() ? '/' : '' - }\n${childrenString}` + return `${indent}${this.name}${suffix}\n${childrenString}` } } diff --git a/packages/file-graph/fileNode.test.js b/packages/file-graph/fileNode.test.js index fdb9ec0..b1584c7 100644 --- a/packages/file-graph/fileNode.test.js +++ b/packages/file-graph/fileNode.test.js @@ -1,64 +1,77 @@ +import { DuplicateItemError, ParentNotExistsError } from './errors.js' import FileNode from './fileNode.js' -test('addChild method adds a child node to the tree', () => { - const files = [ - { +describe('FileNode', () => { + let rootNode + + beforeEach(() => { + rootNode = new FileNode({ path: '/home/rudeus', name: 'rudeus', type: 'folder', - discovered: 'test', - }, - { - path: '/home/rudeus/.bash_history', - name: '.bash_history', - type: 'file', - discovered: 'test', - }, - { - path: '/home/rudeus/.viminfo', - name: '.viminfo', - type: 'file', - discovered: 'test', - }, - { - path: '/home/rudeus/.zshrc', - name: '.zshrc', - type: 'file', - discovered: 'test', - }, - { - path: '/home/rudeus/forgotten_scroll.txt', - name: 'forgotten_scroll.txt', - type: 'file', - discovered: 'test', - }, - { + discovered: true, + }) + }) + + test('should throw error when adding duplicate item', () => { + const child = { path: '/home/rudeus/test', name: 'test', type: 'folder', - discovered: 'test', - }, - { - path: '/home/rudeus/test/.b', - name: '.b', - type: 'file', - discovered: false, - }, - { - path: '/home/rudeus/test/a', - name: 'a', - type: 'file', - discovered: false, - }, - ] + } - const root = new FileNode(files[0]) + // Adding the child for the first time + rootNode.addChild(child) - files.slice(1).forEach((file) => { - root.addChild(file) + expect(rootNode.children.length).toBe(1) + + // Trying to add the same child again should throw an error + expect(() => { + rootNode.addChild(child) + }).toThrow(DuplicateItemError) }) - console.log(root.toString()) + test('should throw error when parent does not exist', () => { + const child = { + path: '/home/rudeus/alphabet/test', + name: 'test', + type: 'folder', + } - expect(root.children.length).toBe(5) + // Trying to add to a nonexistent parent should throw an error + expect(() => { + rootNode.addChild(child) + }).toThrow(ParentNotExistsError) + }) + + test('adds nested children to the tree', () => { + const files = [ + { + path: '/home/rudeus/forgotten_scroll.txt', + name: 'forgotten_scroll.txt', + type: 'file', + discovered: 'test', + }, + { + path: '/home/rudeus/test', + name: 'test', + type: 'folder', + discovered: 'test', + }, + { + path: '/home/rudeus/test/a', + name: 'a', + type: 'file', + discovered: false, + }, + ] + + files.forEach((file) => { + rootNode.addChild(file) + }) + + expect(rootNode.children.length).toBe(2) + expect(rootNode.children[1].children.length).toBe(1) + console.log(rootNode.toString()) + }) }) From ba610a3fc55f18a607c6478384445e4dd047ec5e Mon Sep 17 00:00:00 2001 From: wancat Date: Thu, 17 Aug 2023 19:43:00 +0800 Subject: [PATCH 3/8] draft: add fileGraph handler draft --- packages/file-graph/fileGraph.js | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 packages/file-graph/fileGraph.js diff --git a/packages/file-graph/fileGraph.js b/packages/file-graph/fileGraph.js new file mode 100644 index 0000000..1be06d2 --- /dev/null +++ b/packages/file-graph/fileGraph.js @@ -0,0 +1,40 @@ +import FileNode from './fileNode.js' + +export default class FileGraph { + constructor(root) { + this.root = new FileNode(root) + } + + add(file) { + // Logic to add the file node to the graph + // You can implement the logic to find the appropriate location in the file graph + // If the file node already exists, throw an error + } + + remove(file) { + // Logic to remove a file node from the graph + // If the file node is a folder, also remove all children + // If the file node doesn't exist, throw an error + } + + discover(eventFiles) { + // Logic to handle the discover event + // Overwrite the file graph with the provided files + // Remove file nodes that don't exist anymore and add new ones + } + + handleEvent(event) { + switch (true) { + case !!event.add: + this.add(event.add) + break + case !!event.remove: + this.remove(event.remove) + break + case !!event.discover: + this.discover(event.discover) + break + // Implement other cases if needed + } + } +} From fd26be5e646be631070ab036a2b2360515f05e5f Mon Sep 17 00:00:00 2001 From: wancat Date: Fri, 18 Aug 2023 21:45:05 +0800 Subject: [PATCH 4/8] feat: add makeParent options to addChild --- packages/file-graph/fileNode.js | 21 +++++++++++++++------ packages/file-graph/fileNode.test.js | 19 +++++++++++++++++++ packages/file-graph/utils.js | 10 ++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 packages/file-graph/utils.js diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js index a224cd4..e537c94 100644 --- a/packages/file-graph/fileNode.js +++ b/packages/file-graph/fileNode.js @@ -1,15 +1,15 @@ -import path from 'path' import File from './file.js' import { DuplicateItemError, ParentNotExistsError } from './errors.js' +import { basename, dirname } from './utils.js' export default class FileNode extends File { constructor(file) { super(file) - this.children = [] + this.children = file.children || [] } - addChild(fileInput) { - const file = new File(fileInput) + addChild(fileInput, { makeParents } = {}) { + const file = new FileNode(fileInput) if (file.path === this.path) { throw new DuplicateItemError('File already exists in the tree') } @@ -18,15 +18,24 @@ export default class FileNode extends File { throw new DuplicateItemError('File already exists in the tree') } - const parentPath = path.dirname(file.path) + const parentPath = dirname(file.path) if (parentPath === this.path) { // Direct child of this node - this.children.push(new FileNode(file)) + this.children.push(file) } else { // Recursively call addChild on the correct child node const childNode = this.children.find((child) => child.contains(file)) if (childNode) { childNode.addChild(file) + } else if (makeParents) { + const parent = new FileNode({ + path: parentPath, + name: basename(parentPath), + type: 'folder', + discovered: false, + children: [file], + }) + this.addChild(parent, { makeParents }) } else { throw new ParentNotExistsError('Parent path not found in the tree') } diff --git a/packages/file-graph/fileNode.test.js b/packages/file-graph/fileNode.test.js index b1584c7..97d0c06 100644 --- a/packages/file-graph/fileNode.test.js +++ b/packages/file-graph/fileNode.test.js @@ -74,4 +74,23 @@ describe('FileNode', () => { expect(rootNode.children[1].children.length).toBe(1) console.log(rootNode.toString()) }) + + test('adds nested children to the tree with makeParents option', () => { + const file = { + path: '/home/rudeus/test/a/b/c/d/e', + name: 'a', + type: 'file', + discovered: false, + } + + rootNode.addChild(file, { makeParents: true }) + + console.log(rootNode.toString()) + let currentNode = rootNode + for (let i = 0; i < 5; i += 1) { + expect(currentNode.children.length).toBe(1) + // eslint-disable-next-line prefer-destructuring + currentNode = currentNode.children[0] + } + }) }) diff --git a/packages/file-graph/utils.js b/packages/file-graph/utils.js new file mode 100644 index 0000000..afc3f45 --- /dev/null +++ b/packages/file-graph/utils.js @@ -0,0 +1,10 @@ +export function dirname(path) { + const parts = path.split('/') + parts.pop() + return parts.join('/') +} + +export function basename(path) { + const parts = path.split('/') + return parts.pop() +} From 7405f786a0a34a7cc9dfd9fb9f703af36b39b6c4 Mon Sep 17 00:00:00 2001 From: wancat Date: Mon, 21 Aug 2023 17:51:38 +0800 Subject: [PATCH 5/8] feat: add merge function Able to deal with: 1. merge a subtree --- .vscode/launch.json | 15 +++++++++ packages/file-graph/file.js | 6 ++-- packages/file-graph/fileNode.js | 46 ++++++++++++++++++++++++-- packages/file-graph/fileNode.test.js | 49 ++++++++++++++++++++++++++++ packages/file-graph/utils.js | 3 ++ 5 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..91e6bfc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "File Graph Yarn Jest", + "runtimeExecutable": "yarn", + "args": ["test", "--runInBand"], + "cwd": "${workspaceFolder}/packages/file-graph", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ] +} diff --git a/packages/file-graph/file.js b/packages/file-graph/file.js index 3b1feed..b7622f5 100644 --- a/packages/file-graph/file.js +++ b/packages/file-graph/file.js @@ -1,7 +1,9 @@ +import { basename } from './utils' + export default class File { - constructor({ path, name, type, discovered }) { + constructor({ path, type, discovered }) { this.path = path - this.name = name + this.name = basename(path) this.type = type this.discovered = discovered } diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js index e537c94..88fa5b3 100644 --- a/packages/file-graph/fileNode.js +++ b/packages/file-graph/fileNode.js @@ -11,11 +11,15 @@ export default class FileNode extends File { addChild(fileInput, { makeParents } = {}) { const file = new FileNode(fileInput) if (file.path === this.path) { - throw new DuplicateItemError('File already exists in the tree') + throw new DuplicateItemError( + `File ${file.path} already exists in the tree: ${this.toString()}` + ) } if (this.children.some((child) => child.path === file.path)) { - throw new DuplicateItemError('File already exists in the tree') + throw new DuplicateItemError( + `File ${file.path} already exists in the tree: ${this.toString()}` + ) } const parentPath = dirname(file.path) @@ -37,11 +41,47 @@ export default class FileNode extends File { }) this.addChild(parent, { makeParents }) } else { - throw new ParentNotExistsError('Parent path not found in the tree') + throw new ParentNotExistsError( + `Parent path not found in the tree, ${file.path}, ${this.path}` + ) } } } + merge(fileNode) { + // Merge the children of the provided file node into this node + // If a child already exists, merge the children of that child + // If a child does not exist, add it to this node + if (fileNode.path === this.path) { + fileNode.children.forEach((child) => { + const existingChild = this.children.find((c) => c.path === child.path) + if (existingChild) { + existingChild.merge(child) + } else { + this.children.push(child) + } + }) + + // If a child exists in this node but not in the provided node, remove it + this.children = this.children.filter((child) => + fileNode.children.some((c) => c.path === child.path) + ) + } else if (this.contains(fileNode)) { + const childNode = this.children.find((child) => child.contains(fileNode)) + if (childNode) { + childNode.merge(fileNode) + } else { + this.addChild(fileNode) + } + } else { + throw new Error( + 'Cannot merge file nodes with different paths', + this.path, + fileNode.path + ) + } + } + toString() { const indent = ' '.repeat(this.path.split('/').length - 1) const suffix = this.isDirectory() ? '/' : '' diff --git a/packages/file-graph/fileNode.test.js b/packages/file-graph/fileNode.test.js index 97d0c06..5c82b5b 100644 --- a/packages/file-graph/fileNode.test.js +++ b/packages/file-graph/fileNode.test.js @@ -93,4 +93,53 @@ describe('FileNode', () => { currentNode = currentNode.children[0] } }) + + test('merge-two-trees', () => { + const root = { + path: '/', + type: 'folder', + } + const files1 = [ + { path: '/a', type: 'file' }, + { path: '/b', type: 'folder' }, + { path: '/b/c', type: 'folder' }, + { path: '/b/d', type: 'file' }, + { path: '/b/c/f', type: 'file' }, + ] + + const files2 = [ + { path: '/b', type: 'folder' }, + { path: '/b/c', type: 'folder' }, + { path: '/b/e', type: 'file' }, + { path: '/b/c/g', type: 'file' }, + ] + + const expected = [ + { path: '/a', type: 'file' }, + { path: '/b', type: 'folder' }, + { path: '/b/c', type: 'folder' }, + { path: '/b/e', type: 'file' }, + { path: '/b/c/g', type: 'file' }, + ] + + const node1 = new FileNode(root) + files1.forEach((file) => node1.addChild(file)) + + const node2 = new FileNode(files2[0]) + files2.slice(1).forEach((file) => node2.addChild(file)) + + console.log('node1', node1.toString()) + console.log('node2', node2.toString()) + + const expectedNode = new FileNode(root) + expected.forEach((file) => expectedNode.addChild(file)) + + console.log('expected', expectedNode.toString()) + + node1.merge(node2) + + console.log('node1-after', node1.toString()) + + expect(node1.toString()).toBe(expectedNode.toString()) + }) }) diff --git a/packages/file-graph/utils.js b/packages/file-graph/utils.js index afc3f45..48fffd9 100644 --- a/packages/file-graph/utils.js +++ b/packages/file-graph/utils.js @@ -1,5 +1,8 @@ export function dirname(path) { const parts = path.split('/') + if (parts.length <= 2) { + return '/' + } parts.pop() return parts.join('/') } From 4255eb5910c3f7e8e7e8a3724c1a65b64ef67966 Mon Sep 17 00:00:00 2001 From: wancat Date: Mon, 21 Aug 2023 18:09:51 +0800 Subject: [PATCH 6/8] feat: merge middle part --- packages/file-graph/fileNode.js | 8 +-- packages/file-graph/fileNode.test.js | 73 ++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js index 88fa5b3..0582974 100644 --- a/packages/file-graph/fileNode.js +++ b/packages/file-graph/fileNode.js @@ -63,9 +63,11 @@ export default class FileNode extends File { }) // If a child exists in this node but not in the provided node, remove it - this.children = this.children.filter((child) => - fileNode.children.some((c) => c.path === child.path) - ) + if (fileNode.children.length > 0) { + this.children = this.children.filter((child) => + fileNode.children.some((c) => c.path === child.path) + ) + } } else if (this.contains(fileNode)) { const childNode = this.children.find((child) => child.contains(fileNode)) if (childNode) { diff --git a/packages/file-graph/fileNode.test.js b/packages/file-graph/fileNode.test.js index 5c82b5b..2b200b6 100644 --- a/packages/file-graph/fileNode.test.js +++ b/packages/file-graph/fileNode.test.js @@ -1,6 +1,28 @@ import { DuplicateItemError, ParentNotExistsError } from './errors.js' import FileNode from './fileNode.js' +function createNodeFromFiles(files) { + const node = new FileNode(files[0]) + files.slice(1).forEach((file) => node.addChild(file)) + return node +} + +function mergeTest(files1, files2, expectedFiles) { + const node1 = createNodeFromFiles(files1) + const node2 = createNodeFromFiles(files2) + const expectedNode = createNodeFromFiles(expectedFiles) + + console.log('node1', node1.toString()) + console.log('node2', node2.toString()) + console.log('expected', expectedNode.toString()) + + node1.merge(node2) + + console.log('after-merged', node1.toString()) + + expect(node1.toString()).toBe(expectedNode.toString()) +} + describe('FileNode', () => { let rootNode @@ -94,12 +116,13 @@ describe('FileNode', () => { } }) - test('merge-two-trees', () => { + test('merge-subtree', () => { const root = { path: '/', type: 'folder', } const files1 = [ + root, { path: '/a', type: 'file' }, { path: '/b', type: 'folder' }, { path: '/b/c', type: 'folder' }, @@ -115,6 +138,7 @@ describe('FileNode', () => { ] const expected = [ + root, { path: '/a', type: 'file' }, { path: '/b', type: 'folder' }, { path: '/b/c', type: 'folder' }, @@ -122,24 +146,41 @@ describe('FileNode', () => { { path: '/b/c/g', type: 'file' }, ] - const node1 = new FileNode(root) - files1.forEach((file) => node1.addChild(file)) + mergeTest(files1, files2, expected) + }) - const node2 = new FileNode(files2[0]) - files2.slice(1).forEach((file) => node2.addChild(file)) + test('merge-middle-level', () => { + // Test if the node to merge is only the middle part of the tree + // (i.e. the node to merge doesn't contains all level of children) + // The original tree should keep the children that are not in the node to merge + const root = { + path: '/', + type: 'folder', + } + const files1 = [ + root, + { path: '/a', type: 'file' }, + { path: '/b', type: 'folder' }, + { path: '/b/c', type: 'folder' }, + { path: '/b/d', type: 'file' }, + { path: '/b/c/f', type: 'file' }, + ] - console.log('node1', node1.toString()) - console.log('node2', node2.toString()) + const files2 = [ + { path: '/b', type: 'folder' }, + { path: '/b/c', type: 'folder' }, + { path: '/b/e', type: 'file' }, + ] - const expectedNode = new FileNode(root) - expected.forEach((file) => expectedNode.addChild(file)) + const expected = [ + root, + { path: '/a', type: 'file' }, + { path: '/b', type: 'folder' }, + { path: '/b/c', type: 'folder' }, + { path: '/b/e', type: 'file' }, + { path: '/b/c/f', type: 'file' }, + ] - console.log('expected', expectedNode.toString()) - - node1.merge(node2) - - console.log('node1-after', node1.toString()) - - expect(node1.toString()).toBe(expectedNode.toString()) + mergeTest(files1, files2, expected) }) }) From ab570e9c1bcb406c970b9047f8c132d6a1d8552c Mon Sep 17 00:00:00 2001 From: "Chen, Tai-Lin" Date: Thu, 24 Aug 2023 15:05:50 +0800 Subject: [PATCH 7/8] feat: finish file-graph-algorithm (check required) --- packages/file-graph/fileGraph.js | 23 +++++++++++++++++++++++ packages/file-graph/fileNode.js | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/file-graph/fileGraph.js b/packages/file-graph/fileGraph.js index 1be06d2..708d57b 100644 --- a/packages/file-graph/fileGraph.js +++ b/packages/file-graph/fileGraph.js @@ -9,18 +9,41 @@ export default class FileGraph { // Logic to add the file node to the graph // You can implement the logic to find the appropriate location in the file graph // If the file node already exists, throw an error + + const newNode = new FileNode(file) + this.root.addChild(newNode, { makeParents: true }) } remove(file) { // Logic to remove a file node from the graph // If the file node is a folder, also remove all children // If the file node doesn't exist, throw an error + + const nodeToRemove = this.findNode(file) + if (nodeToRemove) { + const parentNode = this.findParentNode(file) + if (parentNode) { + parentNode.children = parentNode.children.filter( + (child) => child !== nodeToRemove + ) + } + } else { + throw new Error(`File node not found: ${file.path}`) + } } discover(eventFiles) { // Logic to handle the discover event // Overwrite the file graph with the provided files // Remove file nodes that don't exist anymore and add new ones + + const updatedNodes = eventFiles.map((file) => new FileNode(file)) + this.root = new FileNode({ + path: '/', + type: 'folder', + discovered: true, + children: updatedNodes, + }) } handleEvent(event) { diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js index 0582974..d0c9874 100644 --- a/packages/file-graph/fileNode.js +++ b/packages/file-graph/fileNode.js @@ -92,4 +92,22 @@ export default class FileNode extends File { .join('') return `${indent}${this.name}${suffix}\n${childrenString}` } + + findNode(fileToFind) { + const pathFolders = fileToFind.path + .split('/') + .filter((folder) => folder.length > 0) + let currentNode = this + + for (const folder of pathFolders) { + currentNode = currentNode.children.find( + (child) => child.name === folder && child.type === 'folder' + ) + if (!currentNode) { + return null + } + } + + return currentNode + } } From 821896f29244bfcf847ac3ee2e8c609fc830e890 Mon Sep 17 00:00:00 2001 From: "Chen, Tai-Lin" Date: Fri, 25 Aug 2023 20:05:28 +0800 Subject: [PATCH 8/8] working: update file-graph-algorithm --- packages/file-graph/errors.js | 7 ++ packages/file-graph/file.js | 2 +- packages/file-graph/fileGraph.js | 55 +++++++++---- packages/file-graph/fileGraph.test.js | 110 ++++++++++++++++++++++++++ packages/file-graph/fileNode.js | 24 ++---- 5 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 packages/file-graph/fileGraph.test.js diff --git a/packages/file-graph/errors.js b/packages/file-graph/errors.js index 16539c1..966cd57 100644 --- a/packages/file-graph/errors.js +++ b/packages/file-graph/errors.js @@ -12,3 +12,10 @@ export class ParentNotExistsError extends Error { this.name = 'ParentNotExistsError' } } + +export class FileNotExistsError extends Error { + constructor(message) { + super(message) + this.name = 'FileNotExistsError' + } +} diff --git a/packages/file-graph/file.js b/packages/file-graph/file.js index b7622f5..986e280 100644 --- a/packages/file-graph/file.js +++ b/packages/file-graph/file.js @@ -1,4 +1,4 @@ -import { basename } from './utils' +import { basename } from './utils.js' export default class File { constructor({ path, type, discovered }) { diff --git a/packages/file-graph/fileGraph.js b/packages/file-graph/fileGraph.js index 708d57b..baab3a7 100644 --- a/packages/file-graph/fileGraph.js +++ b/packages/file-graph/fileGraph.js @@ -1,35 +1,54 @@ import FileNode from './fileNode.js' +import { FileNotExistsError } from './errors.js' export default class FileGraph { constructor(root) { this.root = new FileNode(root) } - add(file) { + add(files) { // Logic to add the file node to the graph // You can implement the logic to find the appropriate location in the file graph // If the file node already exists, throw an error - const newNode = new FileNode(file) - this.root.addChild(newNode, { makeParents: true }) + files.forEach((file) => { + this.root.addChild(file, { makeParents: true }) + }) } - remove(file) { + remove(files) { // Logic to remove a file node from the graph // If the file node is a folder, also remove all children // If the file node doesn't exist, throw an error - const nodeToRemove = this.findNode(file) - if (nodeToRemove) { - const parentNode = this.findParentNode(file) - if (parentNode) { - parentNode.children = parentNode.children.filter( - (child) => child !== nodeToRemove + files.forEach((file) => { + const pathFolders = file.path + .split('/') + .filter((folder) => folder.length > 0) + const fileName = pathFolders.pop() + let currentNode = this.root + + for (const folder of pathFolders) { + currentNode = currentNode.children.find( + (child) => child.name === folder + ) + if (!currentNode) { + throw new FileNotExistsError( + `File not found: ${fileToFind.path}, ${this.toString()}` + ) + } + } + + if (!currentNode.children.some((child) => child.name === fileName)) { + throw new FileNotExistsError( + `File not found: ${file.path}, ${this.toString()}` + ) + } else { + currentNode.children = currentNode.children.filter( + (child) => child.name !== fileName ) } - } else { - throw new Error(`File node not found: ${file.path}`) - } + }) } discover(eventFiles) { @@ -37,13 +56,17 @@ export default class FileGraph { // Overwrite the file graph with the provided files // Remove file nodes that don't exist anymore and add new ones - const updatedNodes = eventFiles.map((file) => new FileNode(file)) - this.root = new FileNode({ + const newRoot = new FileNode({ path: '/', type: 'folder', discovered: true, - children: updatedNodes, }) + + eventFiles.forEach((file) => { + newRoot.addChild(file, { makeParents: true }) + }) + + this.root = newRoot } handleEvent(event) { diff --git a/packages/file-graph/fileGraph.test.js b/packages/file-graph/fileGraph.test.js new file mode 100644 index 0000000..97ab912 --- /dev/null +++ b/packages/file-graph/fileGraph.test.js @@ -0,0 +1,110 @@ +import FileGraph from './fileGraph.js' +import FileNode from './fileNode.js' + +describe('FileGraph', () => { + let root + + beforeEach(() => { + root = new FileNode({ + path: '/', + type: 'folder', + discovered: true, + }) + }) + + test('add files to graph', () => { + const fileGraph = new FileGraph(root) + + const filesToAdd = [ + { + path: '/home/user/file1.txt', + type: 'file', + discovered: true, + }, + { + path: '/home/user/folder1', + type: 'folder', + discovered: true, + }, + ] + + fileGraph.add(filesToAdd) + + console.log(fileGraph.root.toString()) + expect(fileGraph.root.children.length).toBe(1) + expect(fileGraph.root.children[0].children[0].children.length).toBe(2) + }) + + test('remove files from graph', () => { + const fileGraph = new FileGraph(root) + + const filesToAdd = [ + { + path: '/home/user/file1.txt', + type: 'file', + discovered: true, + }, + { + path: '/home/user/folder1', + type: 'folder', + discovered: true, + }, + ] + + fileGraph.add(filesToAdd) + + const filesToRemove = [ + { + path: '/home/user/folder1', + type: 'folder', + }, + ] + + fileGraph.remove(filesToRemove) + + console.log(fileGraph.root.toString()) + expect(fileGraph.root.children[0].children[0].children.length).toBe(1) + }) + + test('discover new files', () => { + const fileGraph = new FileGraph(root) + + const filesToAdd = [ + { + path: '/home/user/file1.txt', + type: 'file', + discovered: true, + }, + { + path: '/home/user/folder1', + type: 'folder', + discovered: true, + }, + ] + + fileGraph.add(filesToAdd) + expect(fileGraph.root.children[0].children[0].children.length).toBe(2) + + const newFiles = [ + { + path: '/home/newfile.txt', + type: 'file', + discovered: true, + }, + { + path: '/home/user/file1.txt', + type: 'file', + discovered: true, + }, + ] + + fileGraph.discover(newFiles) + + console.log(fileGraph.root.toString()) + expect(fileGraph.root.children[0].children.length).toBe(2) + expect(fileGraph.root.children[0].children[0].name).toBe('newfile.txt') + expect(fileGraph.root.children[0].children[0].children[0].name).toBe( + 'file1.txt' + ) + }) +}) diff --git a/packages/file-graph/fileNode.js b/packages/file-graph/fileNode.js index d0c9874..1529976 100644 --- a/packages/file-graph/fileNode.js +++ b/packages/file-graph/fileNode.js @@ -1,5 +1,9 @@ import File from './file.js' -import { DuplicateItemError, ParentNotExistsError } from './errors.js' +import { + DuplicateItemError, + ParentNotExistsError, + FileNotExistsError, +} from './errors.js' import { basename, dirname } from './utils.js' export default class FileNode extends File { @@ -92,22 +96,4 @@ export default class FileNode extends File { .join('') return `${indent}${this.name}${suffix}\n${childrenString}` } - - findNode(fileToFind) { - const pathFolders = fileToFind.path - .split('/') - .filter((folder) => folder.length > 0) - let currentNode = this - - for (const folder of pathFolders) { - currentNode = currentNode.children.find( - (child) => child.name === folder && child.type === 'folder' - ) - if (!currentNode) { - return null - } - } - - return currentNode - } }