From 04091f7ddf495da52926f2ebe27fdb324ce3ac0b Mon Sep 17 00:00:00 2001 From: Sung Date: Fri, 7 Nov 2025 23:00:23 -0800 Subject: [PATCH] Remove ls and cat commands --- pkg/cli/cmd/add/add.go | 2 +- pkg/cli/cmd/cat/cat.go | 95 ------------- pkg/cli/cmd/edit/note.go | 2 +- pkg/cli/cmd/remove/remove.go | 3 +- pkg/cli/cmd/{ls/ls.go => view/book.go} | 89 ++---------- pkg/cli/cmd/view/book_test.go | 184 +++++++++++++++++++++++++ pkg/cli/cmd/view/note.go | 47 +++++++ pkg/cli/cmd/view/note_test.go | 90 ++++++++++++ pkg/cli/cmd/view/view.go | 26 ++-- pkg/cli/main.go | 4 - pkg/cli/main_test.go | 63 +++++++++ pkg/cli/output/output.go | 13 +- pkg/cli/testutils/main.go | 4 +- 13 files changed, 425 insertions(+), 197 deletions(-) delete mode 100644 pkg/cli/cmd/cat/cat.go rename pkg/cli/cmd/{ls/ls.go => view/book.go} (63%) create mode 100644 pkg/cli/cmd/view/book_test.go create mode 100644 pkg/cli/cmd/view/note.go create mode 100644 pkg/cli/cmd/view/note_test.go diff --git a/pkg/cli/cmd/add/add.go b/pkg/cli/cmd/add/add.go index be89b829..3e6d089d 100644 --- a/pkg/cli/cmd/add/add.go +++ b/pkg/cli/cmd/add/add.go @@ -131,7 +131,7 @@ func newRun(ctx context.DnoteCtx) infra.RunEFunc { return err } - output.NoteInfo(info) + output.NoteInfo(os.Stdout, info) if err := upgrade.Check(ctx); err != nil { log.Error(errors.Wrap(err, "automatically checking updates").Error()) diff --git a/pkg/cli/cmd/cat/cat.go b/pkg/cli/cmd/cat/cat.go deleted file mode 100644 index c32eb687..00000000 --- a/pkg/cli/cmd/cat/cat.go +++ /dev/null @@ -1,95 +0,0 @@ -/* Copyright 2025 Dnote Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cat - -import ( - "strconv" - - "github.com/dnote/dnote/pkg/cli/context" - "github.com/dnote/dnote/pkg/cli/database" - "github.com/dnote/dnote/pkg/cli/infra" - "github.com/dnote/dnote/pkg/cli/log" - "github.com/dnote/dnote/pkg/cli/output" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var example = ` - * See the notes with index 2 from a book 'javascript' - dnote cat javascript 2 - ` - -var deprecationWarning = `and "view" will replace it in the future version. - - Run "dnote view --help" for more information. -` - -func preRun(cmd *cobra.Command, args []string) error { - if len(args) != 2 { - return errors.New("Incorrect number of arguments") - } - - return nil -} - -// NewCmd returns a new cat command -func NewCmd(ctx context.DnoteCtx) *cobra.Command { - cmd := &cobra.Command{ - Use: "cat ", - Aliases: []string{"c"}, - Short: "See a note", - Example: example, - RunE: NewRun(ctx, false), - PreRunE: preRun, - Deprecated: deprecationWarning, - } - - return cmd -} - -// NewRun returns a new run function -func NewRun(ctx context.DnoteCtx, contentOnly bool) infra.RunEFunc { - return func(cmd *cobra.Command, args []string) error { - var noteRowIDArg string - - if len(args) == 2 { - log.Plain(log.ColorYellow.Sprintf("DEPRECATED: you no longer need to pass book name to the view command. e.g. `dnote view 123`.\n\n")) - - noteRowIDArg = args[1] - } else { - noteRowIDArg = args[0] - } - - noteRowID, err := strconv.Atoi(noteRowIDArg) - if err != nil { - return errors.Wrap(err, "invalid rowid") - } - - db := ctx.DB - info, err := database.GetNoteInfo(db, noteRowID) - if err != nil { - return err - } - - if contentOnly { - output.NoteContent(info) - } else { - output.NoteInfo(info) - } - - return nil - } -} diff --git a/pkg/cli/cmd/edit/note.go b/pkg/cli/cmd/edit/note.go index 84a631f4..cb837e11 100644 --- a/pkg/cli/cmd/edit/note.go +++ b/pkg/cli/cmd/edit/note.go @@ -166,7 +166,7 @@ func runNote(ctx context.DnoteCtx, rowIDArg string) error { } log.Success("edited the note\n") - output.NoteInfo(noteInfo) + output.NoteInfo(os.Stdout, noteInfo) return nil } diff --git a/pkg/cli/cmd/remove/remove.go b/pkg/cli/cmd/remove/remove.go index 89d44b78..18224c0f 100644 --- a/pkg/cli/cmd/remove/remove.go +++ b/pkg/cli/cmd/remove/remove.go @@ -17,6 +17,7 @@ package remove import ( "fmt" + "os" "strconv" "github.com/dnote/dnote/pkg/cli/context" @@ -129,7 +130,7 @@ func runNote(ctx context.DnoteCtx, rowIDArg string) error { return err } - output.NoteInfo(noteInfo) + output.NoteInfo(os.Stdout, noteInfo) ok, err := maybeConfirm("remove this note?", false) if err != nil { diff --git a/pkg/cli/cmd/ls/ls.go b/pkg/cli/cmd/view/book.go similarity index 63% rename from pkg/cli/cmd/ls/ls.go rename to pkg/cli/cmd/view/book.go index f0ddd047..698a7de5 100644 --- a/pkg/cli/cmd/ls/ls.go +++ b/pkg/cli/cmd/view/book.go @@ -13,76 +13,19 @@ * limitations under the License. */ -package ls +package view import ( "database/sql" "fmt" + "io" "strings" "github.com/dnote/dnote/pkg/cli/context" - "github.com/dnote/dnote/pkg/cli/infra" "github.com/dnote/dnote/pkg/cli/log" "github.com/pkg/errors" - "github.com/spf13/cobra" ) -var example = ` - * List all books - dnote ls - - * List notes in a book - dnote ls javascript - ` - -var deprecationWarning = `and "view" will replace it in the future version. - -Run "dnote view --help" for more information. -` - -func preRun(cmd *cobra.Command, args []string) error { - if len(args) > 1 { - return errors.New("Incorrect number of argument") - } - - return nil -} - -// NewCmd returns a new ls command -func NewCmd(ctx context.DnoteCtx) *cobra.Command { - cmd := &cobra.Command{ - Use: "ls ", - Aliases: []string{"l", "notes"}, - Short: "List all notes", - Example: example, - RunE: NewRun(ctx, false), - PreRunE: preRun, - Deprecated: deprecationWarning, - } - - return cmd -} - -// NewRun returns a new run function for ls -func NewRun(ctx context.DnoteCtx, nameOnly bool) infra.RunEFunc { - return func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - if err := printBooks(ctx, nameOnly); err != nil { - return errors.Wrap(err, "viewing books") - } - - return nil - } - - bookName := args[0] - if err := printNotes(ctx, bookName); err != nil { - return errors.Wrapf(err, "viewing book '%s'", bookName) - } - - return nil - } -} - // bookInfo is an information about the book to be printed on screen type bookInfo struct { BookLabel string @@ -97,15 +40,13 @@ type noteInfo struct { // getNewlineIdx returns the index of newline character in a string func getNewlineIdx(str string) int { - var ret int - - ret = strings.Index(str, "\n") - - if ret == -1 { - ret = strings.Index(str, "\r\n") + // Check for \r\n first + if idx := strings.Index(str, "\r\n"); idx != -1 { + return idx } - return ret + // Then check for \n + return strings.Index(str, "\n") } // formatBody returns an excerpt of the given raw note content and a boolean @@ -123,15 +64,15 @@ func formatBody(noteBody string) (string, bool) { return strings.Trim(trimmed, " "), false } -func printBookLine(info bookInfo, nameOnly bool) { +func printBookLine(w io.Writer, info bookInfo, nameOnly bool) { if nameOnly { - fmt.Println(info.BookLabel) + fmt.Fprintln(w, info.BookLabel) } else { - log.Printf("%s %s\n", info.BookLabel, log.ColorYellow.Sprintf("(%d)", info.NoteCount)) + fmt.Fprintf(w, "%s %s\n", info.BookLabel, log.ColorYellow.Sprintf("(%d)", info.NoteCount)) } } -func printBooks(ctx context.DnoteCtx, nameOnly bool) error { +func listBooks(ctx context.DnoteCtx, w io.Writer, nameOnly bool) error { db := ctx.DB rows, err := db.Query(`SELECT books.label, count(notes.uuid) note_count @@ -157,13 +98,13 @@ func printBooks(ctx context.DnoteCtx, nameOnly bool) error { } for _, info := range infos { - printBookLine(info, nameOnly) + printBookLine(w, info, nameOnly) } return nil } -func printNotes(ctx context.DnoteCtx, bookName string) error { +func listNotes(ctx context.DnoteCtx, w io.Writer, bookName string) error { db := ctx.DB var bookUUID string @@ -191,7 +132,7 @@ func printNotes(ctx context.DnoteCtx, bookName string) error { infos = append(infos, info) } - log.Infof("on book %s\n", bookName) + fmt.Fprintf(w, "on book %s\n", bookName) for _, info := range infos { body, isExcerpt := formatBody(info.Body) @@ -201,7 +142,7 @@ func printNotes(ctx context.DnoteCtx, bookName string) error { body = fmt.Sprintf("%s %s", body, log.ColorYellow.Sprintf("[---More---]")) } - log.Plainf("%s %s\n", rowid, body) + fmt.Fprintf(w, "%s %s\n", rowid, body) } return nil diff --git a/pkg/cli/cmd/view/book_test.go b/pkg/cli/cmd/view/book_test.go new file mode 100644 index 00000000..226d5d04 --- /dev/null +++ b/pkg/cli/cmd/view/book_test.go @@ -0,0 +1,184 @@ +/* Copyright 2025 Dnote Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package view + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/dnote/dnote/pkg/assert" + "github.com/dnote/dnote/pkg/cli/context" + "github.com/dnote/dnote/pkg/cli/database" +) + +func TestGetNewlineIdx(t *testing.T) { + testCases := []struct { + input string + expected int + }{ + { + input: "hello\nworld", + expected: 5, + }, + { + input: "hello\r\nworld", + expected: 5, + }, + { + input: "no newline here", + expected: -1, + }, + { + input: "", + expected: -1, + }, + { + input: "\n", + expected: 0, + }, + { + input: "\r\n", + expected: 0, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("input: %q", tc.input), func(t *testing.T) { + got := getNewlineIdx(tc.input) + assert.Equal(t, got, tc.expected, "newline index mismatch") + }) + } +} + +func TestFormatBody(t *testing.T) { + testCases := []struct { + input string + expectedBody string + expectedExcerpt bool + }{ + { + input: "single line", + expectedBody: "single line", + expectedExcerpt: false, + }, + { + input: "first line\nsecond line", + expectedBody: "first line", + expectedExcerpt: true, + }, + { + input: "first line\r\nsecond line", + expectedBody: "first line", + expectedExcerpt: true, + }, + { + input: " spaced line ", + expectedBody: "spaced line", + expectedExcerpt: false, + }, + { + input: " first line \nsecond line", + expectedBody: "first line", + expectedExcerpt: true, + }, + { + input: "", + expectedBody: "", + expectedExcerpt: false, + }, + { + input: "line with trailing newline\n", + expectedBody: "line with trailing newline", + expectedExcerpt: false, + }, + { + input: "line with trailing newlines\n\n", + expectedBody: "line with trailing newlines", + expectedExcerpt: false, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("input: %q", tc.input), func(t *testing.T) { + gotBody, gotExcerpt := formatBody(tc.input) + assert.Equal(t, gotBody, tc.expectedBody, "formatted body mismatch") + assert.Equal(t, gotExcerpt, tc.expectedExcerpt, "excerpt flag mismatch") + }) + } +} + +func TestListNotes(t *testing.T) { + // Setup + db := database.InitTestMemoryDB(t) + defer db.Close() + + bookUUID := "js-book-uuid" + database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", bookUUID, "javascript") + database.MustExec(t, "inserting note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-1", bookUUID, "first note", 1515199943) + database.MustExec(t, "inserting note 2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-2", bookUUID, "multiline note\nwith second line", 1515199945) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + // Execute + err := listNotes(ctx, &buf, "javascript") + if err != nil { + t.Fatal(err) + } + + got := buf.String() + + // Verify output + assert.Equal(t, strings.Contains(got, "on book javascript"), true, "should show book name") + assert.Equal(t, strings.Contains(got, "first note"), true, "should contain first note") + assert.Equal(t, strings.Contains(got, "multiline note"), true, "should show first line of multiline note") + assert.Equal(t, strings.Contains(got, "[---More---]"), true, "should show more indicator for multiline note") + assert.Equal(t, strings.Contains(got, "with second line"), false, "should not show second line of multiline note") +} + +func TestListBooks(t *testing.T) { + // Setup + db := database.InitTestMemoryDB(t) + defer db.Close() + + b1UUID := "js-book-uuid" + b2UUID := "linux-book-uuid" + + database.MustExec(t, "inserting book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "javascript") + database.MustExec(t, "inserting book 2", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b2UUID, "linux") + + // Add notes to test count + database.MustExec(t, "inserting note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-1", b1UUID, "note body 1", 1515199943) + database.MustExec(t, "inserting note 2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "note-2", b1UUID, "note body 2", 1515199944) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + // Execute + err := listBooks(ctx, &buf, false) + if err != nil { + t.Fatal(err) + } + + got := buf.String() + + // Verify output + assert.Equal(t, strings.Contains(got, "javascript"), true, "should contain javascript book") + assert.Equal(t, strings.Contains(got, "linux"), true, "should contain linux book") + assert.Equal(t, strings.Contains(got, "(2)"), true, "should show 2 notes for javascript") +} diff --git a/pkg/cli/cmd/view/note.go b/pkg/cli/cmd/view/note.go new file mode 100644 index 00000000..f853dd9a --- /dev/null +++ b/pkg/cli/cmd/view/note.go @@ -0,0 +1,47 @@ +/* Copyright 2025 Dnote Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package view + +import ( + "io" + "strconv" + + "github.com/dnote/dnote/pkg/cli/context" + "github.com/dnote/dnote/pkg/cli/database" + "github.com/dnote/dnote/pkg/cli/output" + "github.com/pkg/errors" +) + +func viewNote(ctx context.DnoteCtx, w io.Writer, noteRowIDArg string, contentOnly bool) error { + noteRowID, err := strconv.Atoi(noteRowIDArg) + if err != nil { + return errors.Wrap(err, "invalid rowid") + } + + db := ctx.DB + info, err := database.GetNoteInfo(db, noteRowID) + if err != nil { + return err + } + + if contentOnly { + output.NoteContent(w, info) + } else { + output.NoteInfo(w, info) + } + + return nil +} diff --git a/pkg/cli/cmd/view/note_test.go b/pkg/cli/cmd/view/note_test.go new file mode 100644 index 00000000..36e9aa84 --- /dev/null +++ b/pkg/cli/cmd/view/note_test.go @@ -0,0 +1,90 @@ +/* Copyright 2025 Dnote Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package view + +import ( + "bytes" + "strings" + "testing" + + "github.com/dnote/dnote/pkg/assert" + "github.com/dnote/dnote/pkg/cli/context" + "github.com/dnote/dnote/pkg/cli/database" +) + +func TestViewNote(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + bookUUID := "test-book-uuid" + database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", bookUUID, "golang") + database.MustExec(t, "inserting note", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", + "note-uuid", bookUUID, "test note content", 1515199943000000000) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "1", false) + if err != nil { + t.Fatal(err) + } + + got := buf.String() + assert.Equal(t, strings.Contains(got, "test note content"), true, "should contain note content") +} + +func TestViewNoteContentOnly(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + bookUUID := "test-book-uuid" + database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", bookUUID, "golang") + database.MustExec(t, "inserting note", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", + "note-uuid", bookUUID, "test note content", 1515199943000000000) + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "1", true) + if err != nil { + t.Fatal(err) + } + + got := buf.String() + assert.Equal(t, got, "test note content", "should contain only note content") +} + +func TestViewNoteInvalidRowID(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "not-a-number", false) + assert.NotEqual(t, err, nil, "should return error for invalid rowid") +} + +func TestViewNoteNotFound(t *testing.T) { + db := database.InitTestMemoryDB(t) + defer db.Close() + + ctx := context.DnoteCtx{DB: db} + var buf bytes.Buffer + + err := viewNote(ctx, &buf, "999", false) + assert.NotEqual(t, err, nil, "should return error for non-existent note") +} diff --git a/pkg/cli/cmd/view/view.go b/pkg/cli/cmd/view/view.go index 62f50a53..57b17dbd 100644 --- a/pkg/cli/cmd/view/view.go +++ b/pkg/cli/cmd/view/view.go @@ -16,14 +16,13 @@ package view import ( + "os" + "github.com/dnote/dnote/pkg/cli/context" "github.com/dnote/dnote/pkg/cli/infra" + "github.com/dnote/dnote/pkg/cli/utils" "github.com/pkg/errors" "github.com/spf13/cobra" - - "github.com/dnote/dnote/pkg/cli/cmd/cat" - "github.com/dnote/dnote/pkg/cli/cmd/ls" - "github.com/dnote/dnote/pkg/cli/utils" ) var example = ` @@ -68,27 +67,26 @@ func NewCmd(ctx context.DnoteCtx) *cobra.Command { func newRun(ctx context.DnoteCtx) infra.RunEFunc { return func(cmd *cobra.Command, args []string) error { - var run infra.RunEFunc - if len(args) == 0 { - run = ls.NewRun(ctx, nameOnly) + // List all books + return listBooks(ctx, os.Stdout, nameOnly) } else if len(args) == 1 { if nameOnly { return errors.New("--name-only flag is only valid when viewing books") } if utils.IsNumber(args[0]) { - run = cat.NewRun(ctx, contentOnly) + // View a note by index + return viewNote(ctx, os.Stdout, args[0], contentOnly) } else { - run = ls.NewRun(ctx, false) + // List notes in a book + return listNotes(ctx, os.Stdout, args[0]) } } else if len(args) == 2 { - // DEPRECATED: passing book name to view command is deprecated - run = cat.NewRun(ctx, false) - } else { - return errors.New("Incorrect number of arguments") + // View a note in a book (book name + note index) + return viewNote(ctx, os.Stdout, args[1], contentOnly) } - return run(cmd, args) + return errors.New("Incorrect number of arguments") } } diff --git a/pkg/cli/main.go b/pkg/cli/main.go index 2afbaf36..2fb1c564 100644 --- a/pkg/cli/main.go +++ b/pkg/cli/main.go @@ -26,12 +26,10 @@ import ( // commands "github.com/dnote/dnote/pkg/cli/cmd/add" - "github.com/dnote/dnote/pkg/cli/cmd/cat" "github.com/dnote/dnote/pkg/cli/cmd/edit" "github.com/dnote/dnote/pkg/cli/cmd/find" "github.com/dnote/dnote/pkg/cli/cmd/login" "github.com/dnote/dnote/pkg/cli/cmd/logout" - "github.com/dnote/dnote/pkg/cli/cmd/ls" "github.com/dnote/dnote/pkg/cli/cmd/remove" "github.com/dnote/dnote/pkg/cli/cmd/root" "github.com/dnote/dnote/pkg/cli/cmd/sync" @@ -79,10 +77,8 @@ func main() { root.Register(login.NewCmd(*ctx)) root.Register(logout.NewCmd(*ctx)) root.Register(add.NewCmd(*ctx)) - root.Register(ls.NewCmd(*ctx)) root.Register(sync.NewCmd(*ctx)) root.Register(version.NewCmd(*ctx)) - root.Register(cat.NewCmd(*ctx)) root.Register(view.NewCmd(*ctx)) root.Register(find.NewCmd(*ctx)) diff --git a/pkg/cli/main_test.go b/pkg/cli/main_test.go index 5da1915e..727a5c3e 100644 --- a/pkg/cli/main_test.go +++ b/pkg/cli/main_test.go @@ -20,6 +20,7 @@ import ( "log" "os" "os/exec" + "strings" "testing" "github.com/dnote/dnote/pkg/assert" @@ -568,3 +569,65 @@ func TestDBPathFlag(t *testing.T) { db2.QueryRow("SELECT count(*) FROM books WHERE label = ?", "db1-book").Scan(&db2HasDB1Book) assert.Equal(t, db2HasDB1Book, 0, "db2 should not have db1's book") } + +func TestView(t *testing.T) { + t.Run("view note by rowid", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup4(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "1") + + assert.Equal(t, strings.Contains(output, "Booleans have toString()"), true, "should contain note content") + assert.Equal(t, strings.Contains(output, "book name"), true, "should show metadata") + }) + + t.Run("view note content only", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup4(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "1", "--content-only") + + assert.Equal(t, strings.Contains(output, "Booleans have toString()"), true, "should contain note content") + assert.Equal(t, strings.Contains(output, "book name"), false, "should not show metadata") + }) + + t.Run("list books", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup1(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view") + + assert.Equal(t, strings.Contains(output, "js"), true, "should list js book") + assert.Equal(t, strings.Contains(output, "linux"), true, "should list linux book") + }) + + t.Run("list notes in book", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup2(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "js") + + assert.Equal(t, strings.Contains(output, "n1 body"), true, "should list note 1") + assert.Equal(t, strings.Contains(output, "n2 body"), true, "should list note 2") + }) + + t.Run("view note by book name and rowid", func(t *testing.T) { + _, opts := setupTestEnv(t) + + db, dbPath := database.InitTestFileDB(t) + testutils.Setup4(t, db) + + output := testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "view", "js", "2") + + assert.Equal(t, strings.Contains(output, "Date object implements mathematical comparisons"), true, "should contain note content") + assert.Equal(t, strings.Contains(output, "book name"), true, "should show metadata") + }) +} diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go index fe6e3c87..d272ba88 100644 --- a/pkg/cli/output/output.go +++ b/pkg/cli/output/output.go @@ -19,6 +19,7 @@ package output import ( "fmt" + "io" "time" "github.com/dnote/dnote/pkg/cli/database" @@ -26,7 +27,7 @@ import ( ) // NoteInfo prints a note information -func NoteInfo(info database.NoteInfo) { +func NoteInfo(w io.Writer, info database.NoteInfo) { log.Infof("book name: %s\n", info.BookLabel) log.Infof("created at: %s\n", time.Unix(0, info.AddedOn).Format("Jan 2, 2006 3:04pm (MST)")) if info.EditedOn != 0 { @@ -35,13 +36,13 @@ func NoteInfo(info database.NoteInfo) { log.Infof("note id: %d\n", info.RowID) log.Infof("note uuid: %s\n", info.UUID) - fmt.Printf("\n------------------------content------------------------\n") - fmt.Printf("%s", info.Content) - fmt.Printf("\n-------------------------------------------------------\n") + fmt.Fprintf(w, "\n------------------------content------------------------\n") + fmt.Fprintf(w, "%s", info.Content) + fmt.Fprintf(w, "\n-------------------------------------------------------\n") } -func NoteContent(info database.NoteInfo) { - fmt.Printf("%s", info.Content) +func NoteContent(w io.Writer, info database.NoteInfo) { + fmt.Fprintf(w, "%s", info.Content) } // BookInfo prints a note information diff --git a/pkg/cli/testutils/main.go b/pkg/cli/testutils/main.go index 581cbd7f..db3282d7 100644 --- a/pkg/cli/testutils/main.go +++ b/pkg/cli/testutils/main.go @@ -144,7 +144,7 @@ type RunDnoteCmdOptions struct { } // RunDnoteCmd runs a dnote command -func RunDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, binaryName string, arg ...string) { +func RunDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, binaryName string, arg ...string) string { t.Logf("running: %s %s", binaryName, strings.Join(arg, " ")) cmd, stderr, stdout, err := NewDnoteCmd(opts, binaryName, arg...) @@ -162,6 +162,8 @@ func RunDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, binaryName string, arg . // Print stdout if and only if test fails later t.Logf("\n%s", stdout) + + return stdout.String() } // WaitDnoteCmd runs a dnote command and passes stdout to the callback.