diff --git a/.auto-claude-security.json b/.auto-claude-security.json new file mode 100644 index 0000000..fe53dfe --- /dev/null +++ b/.auto-claude-security.json @@ -0,0 +1,168 @@ +{ + "base_commands": [ + ".", + "[", + "[[", + "ag", + "awk", + "basename", + "bash", + "bc", + "break", + "cat", + "cd", + "chmod", + "clear", + "cmp", + "column", + "comm", + "command", + "continue", + "cp", + "curl", + "cut", + "date", + "df", + "diff", + "dig", + "dirname", + "du", + "echo", + "egrep", + "env", + "eval", + "exec", + "exit", + "expand", + "export", + "expr", + "false", + "fd", + "fgrep", + "file", + "find", + "fmt", + "fold", + "gawk", + "gh", + "git", + "grep", + "gunzip", + "gzip", + "head", + "help", + "host", + "iconv", + "id", + "jobs", + "join", + "jq", + "kill", + "killall", + "less", + "let", + "ln", + "ls", + "lsof", + "man", + "mkdir", + "mktemp", + "more", + "mv", + "nl", + "paste", + "pgrep", + "ping", + "pkill", + "popd", + "printenv", + "printf", + "ps", + "pushd", + "pwd", + "read", + "readlink", + "realpath", + "reset", + "return", + "rev", + "rg", + "rm", + "rmdir", + "sed", + "seq", + "set", + "sh", + "shuf", + "sleep", + "sort", + "source", + "split", + "stat", + "tail", + "tar", + "tee", + "test", + "time", + "timeout", + "touch", + "tr", + "tree", + "true", + "type", + "uname", + "unexpand", + "uniq", + "unset", + "unzip", + "watch", + "wc", + "wget", + "whereis", + "which", + "whoami", + "xargs", + "yes", + "yq", + "zip", + "zsh" + ], + "stack_commands": [ + "dotnet", + "msbuild", + "node", + "npm", + "npx", + "nuget", + "ts-node", + "tsc", + "tsx" + ], + "script_commands": [], + "custom_commands": [], + "detected_stack": { + "languages": [ + "javascript", + "typescript", + "csharp" + ], + "package_managers": [], + "frameworks": [], + "databases": [], + "infrastructure": [], + "cloud_providers": [], + "code_quality_tools": [], + "version_managers": [] + }, + "custom_scripts": { + "npm_scripts": [], + "make_targets": [], + "poetry_scripts": [], + "cargo_aliases": [], + "shell_scripts": [] + }, + "project_dir": "/Users/phmatray/Repositories/github-atyp/VirtualFileSystem", + "created_at": "2026-01-23T16:06:29.737577", + "project_hash": "7416031e5a474c1bcba54ef0d9d353ac", + "inherited_from": "/Users/phmatray/Repositories/github-atyp/VirtualFileSystem" +} \ No newline at end of file diff --git a/.auto-claude-status b/.auto-claude-status new file mode 100644 index 0000000..12e48fb --- /dev/null +++ b/.auto-claude-status @@ -0,0 +1,25 @@ +{ + "active": true, + "spec": "003-fix-rename-directory-operation", + "state": "building", + "subtasks": { + "completed": 9, + "total": 10, + "in_progress": 1, + "failed": 0 + }, + "phase": { + "current": "Integration Verification", + "id": null, + "total": 2 + }, + "workers": { + "active": 0, + "max": 1 + }, + "session": { + "number": 11, + "started_at": "2026-01-23T16:17:25.470510" + }, + "last_update": "2026-01-23T16:49:14.315697" +} \ No newline at end of file diff --git a/.claude_settings.json b/.claude_settings.json new file mode 100644 index 0000000..7828bbe --- /dev/null +++ b/.claude_settings.json @@ -0,0 +1,39 @@ +{ + "sandbox": { + "enabled": true, + "autoAllowBashIfSandboxed": true + }, + "permissions": { + "defaultMode": "acceptEdits", + "allow": [ + "Read(./**)", + "Write(./**)", + "Edit(./**)", + "Glob(./**)", + "Grep(./**)", + "Read(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/**)", + "Write(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/**)", + "Edit(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/**)", + "Glob(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/**)", + "Grep(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/**)", + "Read(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/.auto-claude/specs/003-fix-rename-directory-operation/**)", + "Write(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/.auto-claude/specs/003-fix-rename-directory-operation/**)", + "Edit(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/worktrees/tasks/003-fix-rename-directory-operation/.auto-claude/specs/003-fix-rename-directory-operation/**)", + "Read(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/**)", + "Write(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/**)", + "Edit(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/**)", + "Glob(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/**)", + "Grep(/Users/phmatray/Repositories/github-atyp/VirtualFileSystem/.auto-claude/**)", + "Bash(*)", + "WebFetch(*)", + "WebSearch(*)", + "mcp__context7__resolve-library-id(*)", + "mcp__context7__get-library-docs(*)", + "mcp__graphiti-memory__search_nodes(*)", + "mcp__graphiti-memory__search_facts(*)", + "mcp__graphiti-memory__add_episode(*)", + "mcp__graphiti-memory__get_episodes(*)", + "mcp__graphiti-memory__get_entity_edge(*)" + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index dfcfd56..64e2fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,6 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +# Auto Claude data directory +.auto-claude/ diff --git a/docs/api/VFSDirectoryRenamedArgs.NewName.md b/docs/api/VFSDirectoryRenamedArgs.NewName.md index 3622fd0..efa09c2 100644 --- a/docs/api/VFSDirectoryRenamedArgs.NewName.md +++ b/docs/api/VFSDirectoryRenamedArgs.NewName.md @@ -3,7 +3,7 @@ ## VFSDirectoryRenamedArgs\.NewName Property -Gets the new name of the renamed file\. +Gets the new name of the renamed directory\. ```csharp public string NewName { get; } diff --git a/docs/api/VFSDirectoryRenamedArgs.NewPath.md b/docs/api/VFSDirectoryRenamedArgs.NewPath.md new file mode 100644 index 0000000..610b6f0 --- /dev/null +++ b/docs/api/VFSDirectoryRenamedArgs.NewPath.md @@ -0,0 +1,13 @@ +#### [Atypical\.VirtualFileSystem\.Core](VirtualFileSystem.md 'VirtualFileSystem') +### [Atypical\.VirtualFileSystem\.Core](VirtualFileSystem.md#Atypical.VirtualFileSystem.Core 'Atypical\.VirtualFileSystem\.Core').[VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs') + +## VFSDirectoryRenamedArgs\.NewPath Property + +Gets the new path of the renamed directory\. + +```csharp +public Atypical.VirtualFileSystem.Core.VFSDirectoryPath NewPath { get; } +``` + +#### Property Value +[VFSDirectoryPath](VFSDirectoryPath.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath') \ No newline at end of file diff --git a/docs/api/VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string).md b/docs/api/VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string,VFSDirectoryPath).md similarity index 61% rename from docs/api/VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string).md rename to docs/api/VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string,VFSDirectoryPath).md index 2a10f73..ea3cb87 100644 --- a/docs/api/VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string).md +++ b/docs/api/VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string,VFSDirectoryPath).md @@ -1,29 +1,35 @@ #### [Atypical\.VirtualFileSystem\.Core](VirtualFileSystem.md 'VirtualFileSystem') ### [Atypical\.VirtualFileSystem\.Core](VirtualFileSystem.md#Atypical.VirtualFileSystem.Core 'Atypical\.VirtualFileSystem\.Core').[VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs') -## VFSDirectoryRenamedArgs\(VFSDirectoryPath, string, string\) Constructor +## VFSDirectoryRenamedArgs\(VFSDirectoryPath, string, string, VFSDirectoryPath\) Constructor Initializes a new instance of the [VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs') class\. ```csharp -public VFSDirectoryRenamedArgs(Atypical.VirtualFileSystem.Core.VFSDirectoryPath path, string oldName, string newName); +public VFSDirectoryRenamedArgs(Atypical.VirtualFileSystem.Core.VFSDirectoryPath path, string oldName, string newName, Atypical.VirtualFileSystem.Core.VFSDirectoryPath newPath); ``` #### Parameters - + `path` [VFSDirectoryPath](VFSDirectoryPath.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath') The path of the renamed directory\. - + `oldName` [System\.String](https://learn.microsoft.com/en-us/dotnet/api/system.string 'System\.String') The old name of the renamed directory\. - + `newName` [System\.String](https://learn.microsoft.com/en-us/dotnet/api/system.string 'System\.String') -The new name of the renamed directory\. \ No newline at end of file +The new name of the renamed directory\. + + + +`newPath` [VFSDirectoryPath](VFSDirectoryPath.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath') + +The new path of the renamed directory\. \ No newline at end of file diff --git a/docs/api/VFSDirectoryRenamedArgs.md b/docs/api/VFSDirectoryRenamedArgs.md index 1779aa8..20f8448 100644 --- a/docs/api/VFSDirectoryRenamedArgs.md +++ b/docs/api/VFSDirectoryRenamedArgs.md @@ -13,14 +13,15 @@ Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system | Constructors | | | :--- | :--- | -| [VFSDirectoryRenamedArgs\(VFSDirectoryPath, string, string\)](VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string).md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.VFSDirectoryRenamedArgs\(Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath, string, string\)') | Initializes a new instance of the [VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs') class\. | +| [VFSDirectoryRenamedArgs\(VFSDirectoryPath, string, string, VFSDirectoryPath\)](VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string,VFSDirectoryPath).md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.VFSDirectoryRenamedArgs\(Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath, string, string, Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath\)') | Initializes a new instance of the [VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs') class\. | | Properties | | | :--- | :--- | | [Message](VFSDirectoryRenamedArgs.Message.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.Message') | Gets the message\. | | [MessageTemplate](VFSDirectoryRenamedArgs.MessageTemplate.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.MessageTemplate') | Gets the message template\. | | [MessageWithMarkup](VFSDirectoryRenamedArgs.MessageWithMarkup.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.MessageWithMarkup') | Gets the message with markup\. | -| [NewName](VFSDirectoryRenamedArgs.NewName.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.NewName') | Gets the new name of the renamed file\. | +| [NewName](VFSDirectoryRenamedArgs.NewName.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.NewName') | Gets the new name of the renamed directory\. | +| [NewPath](VFSDirectoryRenamedArgs.NewPath.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.NewPath') | Gets the new path of the renamed directory\. | | [OldName](VFSDirectoryRenamedArgs.OldName.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.OldName') | Gets the old name of the renamed directory\. | | [Path](VFSDirectoryRenamedArgs.Path.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.Path') | Gets the old path of the renamed directory\. | | [Timestamp](VFSDirectoryRenamedArgs.Timestamp.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.Timestamp') | Gets the timestamp when the directory was renamed\. | diff --git a/docs/api/VirtualFileSystem.md b/docs/api/VirtualFileSystem.md index 8137b49..71ab475 100644 --- a/docs/api/VirtualFileSystem.md +++ b/docs/api/VirtualFileSystem.md @@ -149,11 +149,12 @@ - **[implicit operator VFSDirectoryPath\(string\)](VFSDirectoryPath.implicitoperatorVFSDirectoryPath(string).md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath\.op\_Implicit Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath\(string\)')** `Operator` Implicit conversion from string\. This allows you to use a string as a [VFSDirectoryPath](VFSDirectoryPath.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath')\. - **[implicit operator string\(VFSDirectoryPath\)](VFSDirectoryPath.implicitoperatorstring(VFSDirectoryPath).md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath\.op\_Implicit string\(Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath\)')** `Operator` Implicit conversion to string This allows you to use a [VFSDirectoryPath](VFSDirectoryPath.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath') as a string\. - **[VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs')** `Class` Provides data for the DirectoryRenamed event\. - - **[VFSDirectoryRenamedArgs\(VFSDirectoryPath, string, string\)](VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string).md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.VFSDirectoryRenamedArgs\(Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath, string, string\)')** `Constructor` Initializes a new instance of the [VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs') class\. + - **[VFSDirectoryRenamedArgs\(VFSDirectoryPath, string, string, VFSDirectoryPath\)](VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string,VFSDirectoryPath).md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.VFSDirectoryRenamedArgs\(Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath, string, string, Atypical\.VirtualFileSystem\.Core\.VFSDirectoryPath\)')** `Constructor` Initializes a new instance of the [VFSDirectoryRenamedArgs](VFSDirectoryRenamedArgs.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs') class\. - **[Message](VFSDirectoryRenamedArgs.Message.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.Message')** `Property` Gets the message\. - **[MessageTemplate](VFSDirectoryRenamedArgs.MessageTemplate.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.MessageTemplate')** `Property` Gets the message template\. - **[MessageWithMarkup](VFSDirectoryRenamedArgs.MessageWithMarkup.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.MessageWithMarkup')** `Property` Gets the message with markup\. - - **[NewName](VFSDirectoryRenamedArgs.NewName.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.NewName')** `Property` Gets the new name of the renamed file\. + - **[NewName](VFSDirectoryRenamedArgs.NewName.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.NewName')** `Property` Gets the new name of the renamed directory\. + - **[NewPath](VFSDirectoryRenamedArgs.NewPath.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.NewPath')** `Property` Gets the new path of the renamed directory\. - **[OldName](VFSDirectoryRenamedArgs.OldName.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.OldName')** `Property` Gets the old name of the renamed directory\. - **[Path](VFSDirectoryRenamedArgs.Path.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.Path')** `Property` Gets the old path of the renamed directory\. - **[Timestamp](VFSDirectoryRenamedArgs.Timestamp.md 'Atypical\.VirtualFileSystem\.Core\.VFSDirectoryRenamedArgs\.Timestamp')** `Property` Gets the timestamp when the directory was renamed\. diff --git a/docs/links b/docs/links index c8dd6ba..5cde5cc 100644 --- a/docs/links +++ b/docs/links @@ -251,11 +251,12 @@ T:Atypical.VirtualFileSystem.Core.VFSFileMovedArgs|VFSFileMovedArgs.md|VFSFileMo P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.Path|VFSDirectoryRenamedArgs.Path.md|Path P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.OldName|VFSDirectoryRenamedArgs.OldName.md|OldName P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.NewName|VFSDirectoryRenamedArgs.NewName.md|NewName +P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.NewPath|VFSDirectoryRenamedArgs.NewPath.md|NewPath P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.Timestamp|VFSDirectoryRenamedArgs.Timestamp.md|Timestamp P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.MessageTemplate|VFSDirectoryRenamedArgs.MessageTemplate.md|MessageTemplate P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.Message|VFSDirectoryRenamedArgs.Message.md|Message P:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.MessageWithMarkup|VFSDirectoryRenamedArgs.MessageWithMarkup.md|MessageWithMarkup -M:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.#ctor(Atypical.VirtualFileSystem.Core.VFSDirectoryPath,System.String,System.String)|VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string).md|VFSDirectoryRenamedArgs(VFSDirectoryPath, string, string) +M:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs.#ctor(Atypical.VirtualFileSystem.Core.VFSDirectoryPath,System.String,System.String,Atypical.VirtualFileSystem.Core.VFSDirectoryPath)|VFSDirectoryRenamedArgs.VFSDirectoryRenamedArgs(VFSDirectoryPath,string,string,VFSDirectoryPath).md|VFSDirectoryRenamedArgs(VFSDirectoryPath, string, string, VFSDirectoryPath) T:Atypical.VirtualFileSystem.Core.VFSDirectoryRenamedArgs|VFSDirectoryRenamedArgs.md|VFSDirectoryRenamedArgs P:Atypical.VirtualFileSystem.Core.VFSFileRenamedArgs.Path|VFSFileRenamedArgs.Path.md|Path P:Atypical.VirtualFileSystem.Core.VFSFileRenamedArgs.OldName|VFSFileRenamedArgs.OldName.md|OldName diff --git a/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFS.Rename.cs b/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFS.Rename.cs index 59f6b52..d981fea 100644 --- a/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFS.Rename.cs +++ b/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFS.Rename.cs @@ -37,6 +37,11 @@ public IVirtualFileSystem RenameDirectory( // update the directory node with the new path var oldName = directoryNode.Name; var newPath = new VFSDirectoryPath($"{directoryPath.Parent}/{newName}"); + + // Validate that the destination path doesn't already exist + if (Index.ContainsKey(newPath)) + ThrowVirtualNodeAlreadyExists(newPath); + var updatedDirectoryNode = directoryNode.UpdatePath(newPath); // Add the directory to its old parent directory with the new name @@ -93,7 +98,7 @@ public IVirtualFileSystem RenameDirectory( } } - DirectoryRenamed?.Invoke(new VFSDirectoryRenamedArgs(directoryPath, oldName, newName)); + DirectoryRenamed?.Invoke(new VFSDirectoryRenamedArgs(directoryPath, oldName, newName, newPath)); return this; } diff --git a/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFSDirectoryRenamedArgs.cs b/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFSDirectoryRenamedArgs.cs index c446620..0d50684 100644 --- a/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFSDirectoryRenamedArgs.cs +++ b/src/Atypical.VirtualFileSystem.Core/SystemOperations/Commands/Rename/VFSDirectoryRenamedArgs.cs @@ -17,11 +17,13 @@ public sealed class VFSDirectoryRenamedArgs : VFSEventArgs /// The path of the renamed directory. /// The old name of the renamed directory. /// The new name of the renamed directory. - public VFSDirectoryRenamedArgs(VFSDirectoryPath path, string oldName, string newName) + /// The new path of the renamed directory. + public VFSDirectoryRenamedArgs(VFSDirectoryPath path, string oldName, string newName, VFSDirectoryPath newPath) { Path = path; OldName = oldName; NewName = newName; + NewPath = newPath; Timestamp = DateTimeOffset.Now; } @@ -36,10 +38,15 @@ public VFSDirectoryRenamedArgs(VFSDirectoryPath path, string oldName, string new public string OldName { get; } /// - /// Gets the new name of the renamed file. + /// Gets the new name of the renamed directory. /// public string NewName { get; } + /// + /// Gets the new path of the renamed directory. + /// + public VFSDirectoryPath NewPath { get; } + /// /// Gets the timestamp when the directory was renamed. /// @@ -51,11 +58,11 @@ public override string MessageTemplate /// public override string Message - => string.Format(MessageTemplate, Path, NewName, Timestamp); + => string.Format(MessageTemplate, Path, NewPath, Timestamp); /// public override string MessageWithMarkup - => ToMarkup("blue", Path, NewName, Timestamp); + => ToMarkup("blue", Path, NewPath, Timestamp); /// public override string ToString() diff --git a/src/Atypical.VirtualFileSystem.Core/UndoRedo/ChangeHistory.cs b/src/Atypical.VirtualFileSystem.Core/UndoRedo/ChangeHistory.cs index 06bfd77..32281c2 100644 --- a/src/Atypical.VirtualFileSystem.Core/UndoRedo/ChangeHistory.cs +++ b/src/Atypical.VirtualFileSystem.Core/UndoRedo/ChangeHistory.cs @@ -122,7 +122,7 @@ public IVirtualFileSystem Undo() _vfs.MoveDirectory(directoryMoved.DestinationPath, directoryMoved.SourcePath); break; case VFSDirectoryRenamedArgs directoryRenamed: - _vfs.RenameDirectory(directoryRenamed.Path, directoryRenamed.OldName); + _vfs.RenameDirectory(directoryRenamed.NewPath, directoryRenamed.OldName); break; case VFSFileCreatedArgs fileCreated: _vfs.DeleteFile(fileCreated.Path); diff --git a/src/Atypical.VirtualFileSystem.DemoCli/Commands/DemonstrateVFS.cs b/src/Atypical.VirtualFileSystem.DemoCli/Commands/DemonstrateVFS.cs index e167e72..1b34469 100644 --- a/src/Atypical.VirtualFileSystem.DemoCli/Commands/DemonstrateVFS.cs +++ b/src/Atypical.VirtualFileSystem.DemoCli/Commands/DemonstrateVFS.cs @@ -62,9 +62,8 @@ public override int Execute(CommandContext context, CancellationToken cancellati () => vfs.MoveDirectory(new VFSDirectoryPath("/heroes"), new VFSDirectoryPath("/avengers"))); // Rename a directory - // TODO: fix rename directory - // ProcessStep(vfs, "RENAME DIRECTORY", - // () => vfs.RenameDirectory(new VFSDirectoryPath("/avengers"), new VFSDirectoryPath("/heroes"))); + ProcessStep(vfs, "RENAME DIRECTORY", + () => vfs.RenameDirectory(new VFSDirectoryPath("/avengers"), "heroes")); return 0; } diff --git a/tests/Atypical.VirtualFileSystem.UnitTests/SystemOperations/Commands/VirtualFileSystem_MethodRenameDirectory_Tests.cs b/tests/Atypical.VirtualFileSystem.UnitTests/SystemOperations/Commands/VirtualFileSystem_MethodRenameDirectory_Tests.cs index 2c488f4..d99c158 100644 --- a/tests/Atypical.VirtualFileSystem.UnitTests/SystemOperations/Commands/VirtualFileSystem_MethodRenameDirectory_Tests.cs +++ b/tests/Atypical.VirtualFileSystem.UnitTests/SystemOperations/Commands/VirtualFileSystem_MethodRenameDirectory_Tests.cs @@ -80,7 +80,7 @@ public void RenameDirectory_raises_a_DirectoryRenamed_event() _vfs.CreateDirectory(_directoryPath); var eventRaised = false; - _vfs.DirectoryRenamed += args => + _vfs.DirectoryRenamed += args => { eventRaised = true; args.Path.Should().Be(_directoryPath); @@ -93,7 +93,31 @@ public void RenameDirectory_raises_a_DirectoryRenamed_event() // Assert eventRaised.Should().BeTrue(); } - + + [Fact] + public void RenameDirectory_event_args_contain_correct_paths() + { + // Arrange + _vfs.CreateDirectory(_directoryPath); + VFSDirectoryRenamedArgs? capturedArgs = null; + + _vfs.DirectoryRenamed += args => + { + capturedArgs = args; + }; + + // Act + Act(); + + // Assert + capturedArgs.Should().NotBeNull(); + capturedArgs!.Path.Should().Be(new VFSDirectoryPath("vfs://dir1/dir2/dir3")); + capturedArgs.OldName.Should().Be("dir3"); + capturedArgs.NewName.Should().Be("new_dir"); + capturedArgs.NewPath.Should().Be(new VFSDirectoryPath("vfs://dir1/dir2/new_dir")); + capturedArgs.Timestamp.Should().BeCloseTo(DateTimeOffset.Now, TimeSpan.FromSeconds(1)); + } + [Fact] public void RenameDirectory_adds_a_change_to_the_ChangeHistory() { @@ -105,10 +129,184 @@ public void RenameDirectory_adds_a_change_to_the_ChangeHistory() // Retrieve the change from the UndoStack var change = _vfs.ChangeHistory.UndoStack.First(); - + // Assert _vfs.ChangeHistory.UndoStack.Should().ContainEquivalentOf(change); _vfs.ChangeHistory.UndoStack.Should().HaveCount(4); _vfs.ChangeHistory.RedoStack.Should().BeEmpty(); } + + [Fact] + public void RenameDirectory_can_be_undone_and_redone() + { + // Arrange + _vfs.CreateDirectory(_directoryPath); + var originalPath = _directoryPath; + var newPath = new VFSDirectoryPath("dir1/dir2/new_dir"); + + // Act - Rename directory + Act(); + + // Assert - Directory should be renamed + _vfs.Index.RawIndex.Should().NotContainKey(originalPath); + _vfs.Index.RawIndex.Should().ContainKey(newPath); + + // Act - Undo rename + _vfs.ChangeHistory.Undo(); + + // Assert - Directory should be back to original name + _vfs.Index.RawIndex.Should().ContainKey(originalPath); + _vfs.Index.RawIndex.Should().NotContainKey(newPath); + + // Act - Redo rename + _vfs.ChangeHistory.Redo(); + + // Assert - Directory should be renamed again + _vfs.Index.RawIndex.Should().NotContainKey(originalPath); + _vfs.Index.RawIndex.Should().ContainKey(newPath); + } + + [Fact] + public void RenameDirectory_updates_nested_file_paths() + { + // Arrange + _vfs.CreateDirectory(_directoryPath); + var nestedFilePath = new VFSFilePath("dir1/dir2/dir3/file.txt"); + var deepNestedFilePath = new VFSFilePath("dir1/dir2/dir3/subdir/nested.txt"); + _vfs.CreateFile(nestedFilePath, "content1"); + _vfs.CreateDirectory(new VFSDirectoryPath("dir1/dir2/dir3/subdir")); + _vfs.CreateFile(deepNestedFilePath, "content2"); + var indexLength = _vfs.Index.Count; + + // Act + Act(); + + // Assert + _vfs.Index.Count.Should().Be(indexLength); + + // Verify old paths no longer exist + _vfs.Index.RawIndex.Should().NotContainKey(nestedFilePath); + _vfs.Index.RawIndex.Should().NotContainKey(deepNestedFilePath); + + // Verify new paths exist + var newNestedFilePath = new VFSFilePath("dir1/dir2/new_dir/file.txt"); + var newDeepNestedFilePath = new VFSFilePath("dir1/dir2/new_dir/subdir/nested.txt"); + _vfs.Index.RawIndex.Should().ContainKey(newNestedFilePath); + _vfs.Index.RawIndex.Should().ContainKey(newDeepNestedFilePath); + + // Verify file contents are preserved + _vfs.Index[newNestedFilePath].As().Content.Should().Be("content1"); + _vfs.Index[newDeepNestedFilePath].As().Content.Should().Be("content2"); + + // Verify file paths are updated + _vfs.Index[newNestedFilePath].Path.Value.Should().Be("vfs://dir1/dir2/new_dir/file.txt"); + _vfs.Index[newDeepNestedFilePath].Path.Value.Should().Be("vfs://dir1/dir2/new_dir/subdir/nested.txt"); + } + + [Fact] + public void RenameDirectory_handles_deeply_nested_structure() + { + // Arrange - Create deeply nested structure: /a/b/c/d/e with files at each level + var deepPath = new VFSDirectoryPath("a/b/c/d/e"); + _vfs.CreateDirectory(deepPath); + + // Create files at various levels + var fileAtB = new VFSFilePath("a/b/file_b.txt"); + var fileAtC = new VFSFilePath("a/b/c/file_c.txt"); + var fileAtD = new VFSFilePath("a/b/c/d/file_d.txt"); + var fileAtE = new VFSFilePath("a/b/c/d/e/file_e.txt"); + + _vfs.CreateFile(fileAtB, "content_b"); + _vfs.CreateFile(fileAtC, "content_c"); + _vfs.CreateFile(fileAtD, "content_d"); + _vfs.CreateFile(fileAtE, "content_e"); + + // Create additional subdirectories at different levels + var subdirAtC = new VFSDirectoryPath("a/b/c/subdir_c"); + var subdirAtE = new VFSDirectoryPath("a/b/c/d/e/subdir_e"); + _vfs.CreateDirectory(subdirAtC); + _vfs.CreateDirectory(subdirAtE); + + // Create files in subdirectories + var fileInSubdirC = new VFSFilePath("a/b/c/subdir_c/nested.txt"); + var fileInSubdirE = new VFSFilePath("a/b/c/d/e/subdir_e/deep.txt"); + _vfs.CreateFile(fileInSubdirC, "nested_content"); + _vfs.CreateFile(fileInSubdirE, "deep_content"); + + var indexLength = _vfs.Index.Count; + + // Act - Rename directory 'c' to 'renamed_c' + var pathToRename = new VFSDirectoryPath("a/b/c"); + _vfs.RenameDirectory(pathToRename, "renamed_c"); + + // Assert + _vfs.Index.Count.Should().Be(indexLength); + + // Verify old paths no longer exist + _vfs.Index.RawIndex.Should().NotContainKey(new VFSDirectoryPath("vfs://a/b/c")); + _vfs.Index.RawIndex.Should().NotContainKey(fileAtC); + _vfs.Index.RawIndex.Should().NotContainKey(fileAtD); + _vfs.Index.RawIndex.Should().NotContainKey(fileAtE); + _vfs.Index.RawIndex.Should().NotContainKey(subdirAtC); + _vfs.Index.RawIndex.Should().NotContainKey(subdirAtE); + _vfs.Index.RawIndex.Should().NotContainKey(fileInSubdirC); + _vfs.Index.RawIndex.Should().NotContainKey(fileInSubdirE); + + // Verify new paths exist + var newFileAtC = new VFSFilePath("a/b/renamed_c/file_c.txt"); + var newFileAtD = new VFSFilePath("a/b/renamed_c/d/file_d.txt"); + var newFileAtE = new VFSFilePath("a/b/renamed_c/d/e/file_e.txt"); + var newSubdirAtC = new VFSDirectoryPath("a/b/renamed_c/subdir_c"); + var newSubdirAtE = new VFSDirectoryPath("a/b/renamed_c/d/e/subdir_e"); + var newFileInSubdirC = new VFSFilePath("a/b/renamed_c/subdir_c/nested.txt"); + var newFileInSubdirE = new VFSFilePath("a/b/renamed_c/d/e/subdir_e/deep.txt"); + + _vfs.Index.RawIndex.Should().ContainKey(new VFSDirectoryPath("vfs://a/b/renamed_c")); + _vfs.Index.RawIndex.Should().ContainKey(newFileAtC); + _vfs.Index.RawIndex.Should().ContainKey(newFileAtD); + _vfs.Index.RawIndex.Should().ContainKey(newFileAtE); + _vfs.Index.RawIndex.Should().ContainKey(newSubdirAtC); + _vfs.Index.RawIndex.Should().ContainKey(newSubdirAtE); + _vfs.Index.RawIndex.Should().ContainKey(newFileInSubdirC); + _vfs.Index.RawIndex.Should().ContainKey(newFileInSubdirE); + + // Verify file contents are preserved + _vfs.Index[fileAtB].As().Content.Should().Be("content_b"); // Not affected by rename + _vfs.Index[newFileAtC].As().Content.Should().Be("content_c"); + _vfs.Index[newFileAtD].As().Content.Should().Be("content_d"); + _vfs.Index[newFileAtE].As().Content.Should().Be("content_e"); + _vfs.Index[newFileInSubdirC].As().Content.Should().Be("nested_content"); + _vfs.Index[newFileInSubdirE].As().Content.Should().Be("deep_content"); + + // Verify file paths are correctly updated + _vfs.Index[newFileAtC].Path.Value.Should().Be("vfs://a/b/renamed_c/file_c.txt"); + _vfs.Index[newFileAtD].Path.Value.Should().Be("vfs://a/b/renamed_c/d/file_d.txt"); + _vfs.Index[newFileAtE].Path.Value.Should().Be("vfs://a/b/renamed_c/d/e/file_e.txt"); + _vfs.Index[newFileInSubdirC].Path.Value.Should().Be("vfs://a/b/renamed_c/subdir_c/nested.txt"); + _vfs.Index[newFileInSubdirE].Path.Value.Should().Be("vfs://a/b/renamed_c/d/e/subdir_e/deep.txt"); + + // Verify directory paths are correctly updated + _vfs.Index[new VFSDirectoryPath("vfs://a/b/renamed_c")].Path.Value.Should().Be("vfs://a/b/renamed_c"); + _vfs.Index[newSubdirAtC].Path.Value.Should().Be("vfs://a/b/renamed_c/subdir_c"); + _vfs.Index[newSubdirAtE].Path.Value.Should().Be("vfs://a/b/renamed_c/d/e/subdir_e"); + } + + [Fact] + public void RenameDirectory_throws_exception_when_target_name_already_exists() + { + // Arrange + _vfs.CreateDirectory(new VFSDirectoryPath("dir1/dir2/dir3")); + _vfs.CreateDirectory(new VFSDirectoryPath("dir1/dir2/existing")); + + // Act + Action action = () => _vfs.RenameDirectory( + new VFSDirectoryPath("dir1/dir2/dir3"), + "existing" + ); + + // Assert + action.Should() + .Throw() + .WithMessage("The node 'vfs://dir1/dir2/existing' already exists in the index."); + } } \ No newline at end of file