diff --git a/cmd/dispatch/notes.go b/cmd/dispatch/notes.go index 125ef04..cf31a03 100644 --- a/cmd/dispatch/notes.go +++ b/cmd/dispatch/notes.go @@ -1,6 +1,7 @@ package main import ( + "encoding/csv" "encoding/json" "fmt" "io" @@ -40,7 +41,7 @@ func runNotes(w io.Writer, args []string) error { if len(rest) > 0 { rest = rest[1:] } - if len(rest) == 0 || rest[0] == "list" || rest[0] == "--json" { + if len(rest) == 0 || rest[0] == "list" || rest[0] == "--json" || rest[0] == "--csv" { return runNotesList(w, rest) } @@ -58,6 +59,7 @@ func runNotes(w io.Writer, args []string) error { func runNotesList(w io.Writer, args []string) error { jsonOut := false + csvOut := false if len(args) > 0 && args[0] == "list" { args = args[1:] } @@ -65,10 +67,15 @@ func runNotesList(w io.Writer, args []string) error { switch arg { case "--json": jsonOut = true + case "--csv": + csvOut = true default: return fmt.Errorf("notes list does not take arguments, got %q", arg) } } + if jsonOut && csvOut { + return fmt.Errorf("--json and --csv cannot be combined") + } cfg, err := configLoadFn() if err != nil { @@ -84,6 +91,9 @@ func runNotesList(w io.Writer, args []string) error { enc.SetIndent("", " ") return enc.Encode(report) } + if csvOut { + return writeNotesCSV(w, report) + } writeNotesText(w, report) return nil } @@ -215,6 +225,7 @@ func writeNotesText(w io.Writer, report notesReport) { idWidth = len(entry.ID) } } + for _, entry := range report.Notes { fmt.Fprintf(w, " %-*s %s\n", idWidth, entry.ID, entry.Note) if entry.Summary != "" { @@ -222,3 +233,17 @@ func writeNotesText(w io.Writer, report notesReport) { } } } + +func writeNotesCSV(w io.Writer, report notesReport) error { + cw := csv.NewWriter(w) + if err := cw.Write([]string{"id", "summary", "note"}); err != nil { + return err + } + for _, entry := range report.Notes { + if err := cw.Write([]string{entry.ID, csvSafe(entry.Summary), csvSafe(entry.Note)}); err != nil { + return err + } + } + cw.Flush() + return cw.Error() +} diff --git a/cmd/dispatch/notes_test.go b/cmd/dispatch/notes_test.go index 4ef3272..6e2fd35 100644 --- a/cmd/dispatch/notes_test.go +++ b/cmd/dispatch/notes_test.go @@ -82,6 +82,29 @@ func TestRunNotesListJSON(t *testing.T) { } } +func TestRunNotesListCSV(t *testing.T) { + withConfigSeams(t, notedConfig()) + withNotesList(t, func(data.FilterOptions) ([]data.Session, error) { return notedSessions(), nil }) + + var buf bytes.Buffer + if err := runNotes(&buf, []string{"notes", "--csv"}); err != nil { + t.Fatalf("runNotes csv shortcut: %v", err) + } + out := buf.String() + for _, want := range []string{ + "id,summary,note", + "a,Auth fix,follow up", + "b,Build command,ready to ship", + } { + if !strings.Contains(out, want) { + t.Errorf("CSV output missing %q:\n%s", want, out) + } + } + if strings.Contains(out, "orphan note") { + t.Fatalf("orphan note should not appear:\n%s", out) + } +} + func TestRunNotesGetSetClear(t *testing.T) { cfg := withConfigSeams(t, config.Default()) @@ -143,6 +166,7 @@ func TestRunNotesErrors(t *testing.T) { {"notes", "set", "ses-1", "--stdin", "extra"}, {"notes", "clear"}, {"notes", "list", "extra"}, + {"notes", "list", "--json", "--csv"}, } { if err := runNotes(&bytes.Buffer{}, args); err == nil { t.Fatalf("expected error for args %v", args)