diff --git a/README.md b/README.md index 2637a517..cdf81ffb 100644 --- a/README.md +++ b/README.md @@ -415,7 +415,7 @@ notesmd-cli create "{note-name}" --content "abcde" --open --editor ### Move / Rename Note -Moves a given note(path from top level of vault) with new name given (top level of vault). If given same path but different name then its treated as a rename. All links inside vault are updated to match new name. +Moves a given note(path from top level of vault) with new name given (top level of vault). If given same path but different name then its treated as a rename. All links inside vault are updated to match new name. Intermediate directories in the destination path are created automatically. ```bash # Renames a note in default obsidian diff --git a/pkg/obsidian/note.go b/pkg/obsidian/note.go index f337447d..2c2c91af 100644 --- a/pkg/obsidian/note.go +++ b/pkg/obsidian/note.go @@ -39,9 +39,19 @@ func (m *Note) Move(originalPath string, newPath string) error { o := AddMdSuffix(originalPath) n := AddMdSuffix(newPath) - err := os.Rename(o, n) + // Check the source first so a missing note is reported as such and does not + // leave behind a destination directory created below. + if _, err := os.Stat(o); err != nil { + return errors.New(NoteDoesNotExistError) + } - if err != nil { + // Create any intermediate directories the destination requires, as create + // and daily already do for their note paths. + if err := os.MkdirAll(filepath.Dir(n), 0755); err != nil { + return fmt.Errorf("failed to create note directory: %w", err) + } + + if err := os.Rename(o, n); err != nil { return errors.New(NoteDoesNotExistError) } diff --git a/pkg/obsidian/note_test.go b/pkg/obsidian/note_test.go index 46357ff1..64270a69 100644 --- a/pkg/obsidian/note_test.go +++ b/pkg/obsidian/note_test.go @@ -201,6 +201,51 @@ func TestMoveNote(t *testing.T) { // Assert assert.Equal(t, err.Error(), obsidian.NoteDoesNotExistError) }) + + t.Run("Move note to destination directory that does not exist", func(t *testing.T) { + // Arrange + tempDir := t.TempDir() + originalPath := filepath.Join(tempDir, "original.md") + newPath := filepath.Join(tempDir, "one", "two", "moved") + expectedNewPath := filepath.Join(tempDir, "one", "two", "moved.md") + + err := os.WriteFile(originalPath, []byte(originalContent), 0644) + if err != nil { + t.Fatal(err) + } + + // Act + noteManager := obsidian.Note{} + err = noteManager.Move(filepath.Join(tempDir, "original"), newPath) + + // Assert + assert.NoError(t, err, "Expected no error while moving note into a missing directory") + + _, err = os.Stat(originalPath) + assert.True(t, os.IsNotExist(err), "Original file still exists at %s, expected it to be moved", originalPath) + + newContent, err := os.ReadFile(expectedNewPath) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, originalContent, string(newContent), "New file content is %q, expected %q", string(newContent), originalContent) + }) + + t.Run("Missing note does not create destination directory", func(t *testing.T) { + // Arrange + tempDir := t.TempDir() + newFolder := filepath.Join(tempDir, "newFolder") + + // Act + noteManager := obsidian.Note{} + err := noteManager.Move(filepath.Join(tempDir, "missing"), filepath.Join(newFolder, "missing")) + + // Assert + assert.Equal(t, obsidian.NoteDoesNotExistError, err.Error()) + + _, statErr := os.Stat(newFolder) + assert.True(t, os.IsNotExist(statErr), "Destination directory %s was created for a note that does not exist", newFolder) + }) } func createTmpDirAndFiles(t *testing.T, perm os.FileMode, files []string, content []byte) string {