diff --git a/src/cli/add.go b/src/cli/add.go index 2503514..32d4ffd 100644 --- a/src/cli/add.go +++ b/src/cli/add.go @@ -30,23 +30,16 @@ func AddEntry(targetLocation string, hidePassword bool, entryType uint8) { url := input("URL:") if inputBinary("Add a note to this entry?") { - note := newNote() + note, _ := editNote([]string{}) unencryptedEntry = append([]string{password, username, url}, note...) } else { unencryptedEntry = []string{password, username, url} } } else { - note := newNote() + note, _ := editNote([]string{}) unencryptedEntry = append([]string{"", "", ""}, note...) } // write and preview the new entry - if offline.EntryIsNotEmpty(unencryptedEntry) { - offline.WriteEntry(targetLocation, unencryptedEntry) - fmt.Println(ansiBold + "\nEntry Preview:" + offline.AnsiReset) - EntryReader(unencryptedEntry, hidePassword) - } else { - fmt.Println(offline.AnsiError + "No data supplied for entry" + offline.AnsiReset) - os.Exit(1) - } + writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword) } diff --git a/src/cli/edit.go b/src/cli/edit.go index 7745d34..9ab421f 100644 --- a/src/cli/edit.go +++ b/src/cli/edit.go @@ -1,9 +1,12 @@ package cli import ( + "bufio" "fmt" "github.com/rwinkhart/MUTN/src/offline" "os" + "os/exec" + "reflect" ) // RenameCli renames an entry at oldLocation to a new location (user input) @@ -42,14 +45,32 @@ func EditEntry(targetLocation string, hidePassword bool, field int) { } // write and preview the modified entry - if offline.EntryIsNotEmpty(unencryptedEntry) { - offline.WriteEntry(targetLocation, unencryptedEntry) - fmt.Println(ansiBold + "\nEntry Preview:" + offline.AnsiReset) - EntryReader(unencryptedEntry, hidePassword) - } else { - fmt.Println(offline.AnsiError + "No data supplied for entry" + offline.AnsiReset) + writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword) +} + +func EditEntryNote(targetLocation string, hidePassword bool) { + // ensure targetLocation exists + offline.TargetIsFile(targetLocation, true, 2) + + // read old entry data + unencryptedEntry := offline.DecryptGPG(targetLocation) + + // store non-note data separately + nonNoteData := unencryptedEntry[:3] + + // store note data separately + noteData := unencryptedEntry[3:] + + // edit the note + editedNote, noteEdited := editNote(noteData) + if !noteEdited { // exit early if the note was not edited + fmt.Println(offline.AnsiError + "Entry is unchanged" + offline.AnsiReset) os.Exit(1) } + unencryptedEntry = append(nonNoteData, editedNote...) + + // write and preview the modified entry + writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword) } // GenUpdate generates a new password for an entry at targetLocation (user input) @@ -64,12 +85,53 @@ func GenUpdate(targetLocation string, hidePassword bool) { unencryptedEntry[0] = offline.StringGen(inputInt("Password length:"), inputBinary("Generate a complex (special characters) password?"), 0.2) // write and preview the modified entry - if offline.EntryIsNotEmpty(unencryptedEntry) { - offline.WriteEntry(targetLocation, unencryptedEntry) - fmt.Println(ansiBold + "\nEntry Preview:" + offline.AnsiReset) - EntryReader(unencryptedEntry, hidePassword) - } else { - fmt.Println(offline.AnsiError + "No data supplied for entry" + offline.AnsiReset) + writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword) +} + +// editNote uses the user-specified text editor to edit an existing note (or create a new one if baseNote is empty) +// returns the edited note and a boolean indicating whether the note was edited +func editNote(baseNote []string) ([]string, bool) { + tempFile := offline.CreateTempFile() + defer os.Remove(tempFile.Name()) + editor := offline.ReadConfig([]string{"textEditor"})[0] + + // write baseNote to tempFile (if it is not empty) + if len(baseNote) > 0 { + for _, line := range baseNote { + _, _ = tempFile.WriteString(line + "\n") + } + } + + // edit the tempFile (note) with the user's text editor + cmd := exec.Command(editor, tempFile.Name()) + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Println(offline.AnsiError + "Failed to write note with " + editor + offline.AnsiReset) os.Exit(1) } + + // TODO does it need to be reopened? + file, err := os.Open(tempFile.Name()) + if err != nil { + fmt.Println(offline.AnsiError + "Failed to open temporary file (\"" + tempFile.Name() + "\") " + err.Error() + offline.AnsiReset) + os.Exit(1) + } + defer file.Close() + + // read the edited note from the tempFile + var note []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + note = append(note, scanner.Text()) + } + + // return the edited note if it is different from baseNote + if !reflect.DeepEqual(offline.RemoveTrailingEmptyStrings(note), baseNote) { + return note, true + } else { + return note, false + } } diff --git a/src/cli/entryReader.go b/src/cli/entryReader.go index 92efc9a..e93419a 100644 --- a/src/cli/entryReader.go +++ b/src/cli/entryReader.go @@ -50,15 +50,17 @@ func EntryReader(decryptedEntry []string, hidePassword bool) { notesFlag = true } default: - // print notes header if first notes line was blank - if !notesFlag { + // print notes header if current line is not empty and notes have not been printed + if !notesFlag && decryptedEntry[i] != "" { fmt.Println(ansiDirectoryHeader + "Notes:" + offline.AnsiReset) // indicate that notes have been printed notesFlag = true } - // print extended notes line - fmt.Println(decryptedEntry[i]) + // print extended notes line if header was printed + if notesFlag { + fmt.Println(decryptedEntry[i]) + } } } // print trailing newline if notes were printed diff --git a/src/cli/utilitiesMisc.go b/src/cli/utilitiesMisc.go index 28dbbfa..f56fb1d 100644 --- a/src/cli/utilitiesMisc.go +++ b/src/cli/utilitiesMisc.go @@ -6,7 +6,6 @@ import ( "github.com/rwinkhart/MUTN/src/offline" "golang.org/x/crypto/ssh/terminal" "os" - "os/exec" "strings" ) @@ -51,33 +50,15 @@ func inputBinary(prompt string) bool { return false } -// newNote uses the user-specified text editor to create a new note and returns the note as a slice of strings -func newNote() []string { - tempFile := offline.CreateTempFile() - defer os.Remove(tempFile.Name()) - editor := offline.ReadConfig([]string{"textEditor"})[0] - cmd := exec.Command(editor, tempFile.Name()) - cmd.Stdout = os.Stdout - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - fmt.Println(offline.AnsiError + "Failed to write note with " + editor + offline.AnsiReset) +// writeEntryShortcut writes an entry to targetLocation (trimming trailing blank lines) and previews it, or errors if no data is supplied +func writeEntryShortcut(targetLocation string, unencryptedEntry []string, hidePassword bool) { + if offline.EntryIsNotEmpty(unencryptedEntry) { + trimmedEntry := offline.RemoveTrailingEmptyStrings(unencryptedEntry) + offline.WriteEntry(targetLocation, trimmedEntry) + fmt.Println(ansiBold + "\nEntry Preview:" + offline.AnsiReset) + EntryReader(trimmedEntry, hidePassword) + } else { + fmt.Println(offline.AnsiError + "No data supplied for entry" + offline.AnsiReset) os.Exit(1) } - - file, err := os.Open(tempFile.Name()) - if err != nil { - fmt.Println(offline.AnsiError + "Failed to open temporary file (\"" + tempFile.Name() + "\") " + err.Error() + offline.AnsiReset) - os.Exit(1) - } - defer file.Close() - - var note []string - scanner := bufio.NewScanner(file) - for scanner.Scan() { - note = append(note, scanner.Text()) - } - - return offline.RemoveTrailingEmptyStrings(note) }