diff --git a/fs.go b/fs.go new file mode 100644 index 0000000..97183d3 --- /dev/null +++ b/fs.go @@ -0,0 +1,59 @@ +/* +This file has the raw file io operations that are used by FsTree +or elsewhere in the app. Nowhere else should there be a direct +fileopen + +- ruinivist, 30Dec25 +*/ + +package main + +import ( + "errors" + "os" + "path/filepath" +) + +func walkFileSystemAndBuildTree(rootPath string, node *FsNode) error { + if node == nil { + return errors.New("node cannot be nil") + } + if len(node.children) > 0 { + return errors.New("node already has children") + } + + entries, err := os.ReadDir(rootPath) + if err != nil { + return err + } + + for _, entry := range entries { + childPath := filepath.Join(rootPath, entry.Name()) + + var nodeType FsNodeType + if entry.IsDir() { + nodeType = FolderNode + } else { + nodeType = FileNode + } + + childNode := &FsNode{ + nodeType: nodeType, + path: childPath, + children: make([]*FsNode, 0), + parent: node, + expanded: false, + } + + node.children = append(node.children, childNode) + + // Recursively walk subdirectories + if entry.IsDir() { + if err := walkFileSystemAndBuildTree(childPath, childNode); err != nil { + return err + } + } + } + + return nil +} diff --git a/fs_test.go b/fs_test.go new file mode 100644 index 0000000..da0e02d --- /dev/null +++ b/fs_test.go @@ -0,0 +1,153 @@ +/* +another test that is entirely ae ayee generated +- ruinivist, 30Dec25 +*/ + +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestWalkFileSystemAndBuildTree(t *testing.T) { + t.Run("build tree from real directory", func(t *testing.T) { + // Create temporary directory structure + tmpDir := t.TempDir() + + // Create test structure: + // tmpDir/ + // file1.txt + // file2.go + // folder1/ + // nested.txt + // folder2/ + os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("content"), 0644) + os.WriteFile(filepath.Join(tmpDir, "file2.go"), []byte("code"), 0644) + os.Mkdir(filepath.Join(tmpDir, "folder1"), 0755) + os.WriteFile(filepath.Join(tmpDir, "folder1", "nested.txt"), []byte("nested"), 0644) + os.Mkdir(filepath.Join(tmpDir, "folder2"), 0755) + + // Build tree + root := &FsNode{ + nodeType: FolderNode, + path: tmpDir, + children: make([]*FsNode, 0), + expanded: true, + } + + err := walkFileSystemAndBuildTree(tmpDir, root) + if err != nil { + t.Fatalf("walkFileSystemAndBuildTree() error = %v", err) + } + + // Verify structure + if len(root.children) != 4 { + t.Fatalf("root children count = %d, want 4", len(root.children)) + } + + // Check children exist and have correct types + fileCount := 0 + folderCount := 0 + var folder1 *FsNode + + for _, child := range root.children { + if child.parent != root { + t.Errorf("child %q parent pointer incorrect", child.FileName()) + } + + if child.nodeType == FileNode { + fileCount++ + } else if child.nodeType == FolderNode { + folderCount++ + if child.FileName() == "folder1" { + folder1 = child + } + } + } + + if fileCount != 2 { + t.Errorf("file count = %d, want 2", fileCount) + } + if folderCount != 2 { + t.Errorf("folder count = %d, want 2", folderCount) + } + + // Verify nested structure + if folder1 == nil { + t.Fatal("folder1 not found") + } + if len(folder1.children) != 1 { + t.Errorf("folder1 children count = %d, want 1", len(folder1.children)) + } + if folder1.children[0].FileName() != "nested.txt" { + t.Errorf("nested file name = %q, want %q", folder1.children[0].FileName(), "nested.txt") + } + if folder1.children[0].nodeType != FileNode { + t.Errorf("nested node type = %v, want %v", folder1.children[0].nodeType, FileNode) + } + }) + + t.Run("error when node is nil", func(t *testing.T) { + tmpDir := t.TempDir() + err := walkFileSystemAndBuildTree(tmpDir, nil) + if err == nil { + t.Fatal("walkFileSystemAndBuildTree() with nil node should return error") + } + if err.Error() != "node cannot be nil" { + t.Errorf("error message = %q", err.Error()) + } + }) + + t.Run("error when node already has children", func(t *testing.T) { + tmpDir := t.TempDir() + root := &FsNode{ + nodeType: FolderNode, + path: tmpDir, + children: []*FsNode{{nodeType: FileNode, path: "dummy"}}, + expanded: true, + } + + err := walkFileSystemAndBuildTree(tmpDir, root) + if err == nil { + t.Fatal("walkFileSystemAndBuildTree() with existing children should return error") + } + if err.Error() != "node already has children" { + t.Errorf("error message = %q", err.Error()) + } + }) + + t.Run("error when path does not exist", func(t *testing.T) { + root := &FsNode{ + nodeType: FolderNode, + path: "/nonexistent/path", + children: make([]*FsNode, 0), + expanded: true, + } + + err := walkFileSystemAndBuildTree("/nonexistent/path", root) + if err == nil { + t.Fatal("walkFileSystemAndBuildTree() with nonexistent path should return error") + } + }) + + t.Run("empty directory", func(t *testing.T) { + tmpDir := t.TempDir() + root := &FsNode{ + nodeType: FolderNode, + path: tmpDir, + children: make([]*FsNode, 0), + expanded: true, + } + + err := walkFileSystemAndBuildTree(tmpDir, root) + if err != nil { + t.Fatalf("walkFileSystemAndBuildTree() error = %v", err) + } + + if len(root.children) != 0 { + t.Errorf("empty directory should have 0 children, got %d", len(root.children)) + } + }) +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..01d6b14 --- /dev/null +++ b/utils.go @@ -0,0 +1,11 @@ +package main + +// Removes the first occurence from the slice. Worst case O(n) +func RemoveFromSlice[T comparable](slice []T, item T) []T { + for i, v := range slice { + if v == item { + return append(slice[:i], slice[i+1:]...) + } + } + return slice +}