diff --git a/cmd/add/add.go b/cmd/add/add.go index 391e6438..9226b49a 100644 --- a/cmd/add/add.go +++ b/cmd/add/add.go @@ -10,6 +10,8 @@ import ( "github.com/spf13/cobra" ) +var content string + var example = ` * Write a note in the current book dnote new "time is a part of the commit hash" @@ -18,8 +20,8 @@ var example = ` dnote new git "time is a part of the commit hash"` func preRun(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("Missing argument") + if len(args) != 1 { + return errors.New("Incorrect number of argument") } return nil @@ -35,41 +37,37 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command { RunE: newRun(ctx), } + f := cmd.Flags() + f.StringVarP(&content, "content", "c", "", "The new content for the note") + return cmd } -func parseArgs(ctx infra.DnoteCtx, args []string) (bookName string, content string, err error) { - if len(args) == 1 { - bookName, err = core.GetCurrentBook(ctx) - if err != nil { - return - } - - content = args[0] - } else { - bookName = args[0] - content = args[1] - } - - return -} - func newRun(ctx infra.DnoteCtx) core.RunEFunc { return func(cmd *cobra.Command, args []string) error { - bookName, content, err := parseArgs(ctx, args) - if err != nil { - return errors.Wrap(err, "Failed to parse args") + bookName := args[0] + + if content == "" { + fpath := core.GetDnoteTmpContentPath(ctx) + err := core.GetEditorInput(ctx, fpath, &content) + if err != nil { + return errors.Wrap(err, "Failed to get editor input") + } + } + + if content == "" { + return errors.New("Empty content") } ts := time.Now().Unix() - note := core.NewNote(content, ts) - err = writeNote(ctx, bookName, note, ts) + err := writeNote(ctx, bookName, note, ts) if err != nil { return errors.Wrap(err, "Failed to write note") } - log.Infof("added to %s\n", bookName) + log.Printf("note: \"%s\"\n", content) + log.Successf("added to %s\n", bookName) return nil } } diff --git a/cmd/books/books.go b/cmd/books/books.go index cb4fdc23..35b33143 100644 --- a/cmd/books/books.go +++ b/cmd/books/books.go @@ -1,11 +1,10 @@ package books import ( - "fmt" - "github.com/dnote-io/cli/core" "github.com/dnote-io/cli/infra" "github.com/dnote-io/cli/log" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -26,22 +25,13 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command { func newRun(ctx infra.DnoteCtx) core.RunEFunc { return func(cmd *cobra.Command, args []string) error { - currentBook, err := core.GetCurrentBook(ctx) + dnote, err := core.GetDnote(ctx) if err != nil { - return err + return errors.Wrap(err, "Failed to read dnote") } - books, err := core.GetBookNames(ctx) - if err != nil { - return err - } - - for _, book := range books { - if book == currentBook { - fmt.Printf(" %s\033[%dm%s\033[0m\n", "* ", log.ColorBlue, book) - } else { - fmt.Printf(" %s%s\n", " ", book) - } + for bookName, book := range dnote { + log.Printf("%s \033[%dm(%d)\033[0m\n", bookName, log.ColorYellow, len(book.Notes)) } return nil diff --git a/cmd/edit/edit.go b/cmd/edit/edit.go index 23a5b495..5d20bbc1 100644 --- a/cmd/edit/edit.go +++ b/cmd/edit/edit.go @@ -1,13 +1,13 @@ package edit import ( + "io/ioutil" "strconv" "time" "github.com/dnote-io/cli/core" "github.com/dnote-io/cli/infra" "github.com/dnote-io/cli/log" - "github.com/dnote-io/cli/utils" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -41,8 +41,8 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command { } func preRun(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("Missing argument") + if len(args) != 2 { + return errors.New("Incorrect number of argument") } return nil @@ -52,27 +52,13 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { return func(cmd *cobra.Command, args []string) error { dnote, err := core.GetDnote(ctx) if err != nil { - return err + return errors.Wrap(err, "Failed to read dnote") } - var targetBookName string - var targetIdx int - - if len(args) == 1 { - targetBookName, err = core.GetCurrentBook(ctx) - if err != nil { - return err - } - targetIdx, err = strconv.Atoi(args[0]) - if err != nil { - return err - } - } else if len(args) == 2 { - targetBookName = args[0] - targetIdx, err = strconv.Atoi(args[1]) - if err != nil { - return err - } + targetBookName := args[0] + targetIdx, err := strconv.Atoi(args[1]) + if err != nil { + return errors.Wrapf(err, "Failed to parse the given index %+v", args[1]) } targetBook, exists := dnote[targetBookName] @@ -85,18 +71,27 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { targetNote := targetBook.Notes[targetIdx] if newContent == "" { - log.Printf("content: %s\n", targetNote.Content) - log.Printf("new content: ") + fpath := core.GetDnoteTmpContentPath(ctx) - newContent, err = utils.GetInput() + e := ioutil.WriteFile(fpath, []byte(targetNote.Content), 0644) if err != nil { - return errors.Wrap(err, "Failed to get new content") + return errors.Wrap(err, "Failed to prepare editor content") } + + e = core.GetEditorInput(ctx, fpath, &newContent) + if e != nil { + return errors.Wrap(err, "Failed to get editor input") + } + + } + + if targetNote.Content == newContent { + return errors.New("Nothing changed") } ts := time.Now().Unix() - targetNote.Content = utils.SanitizeContent(newContent) + targetNote.Content = core.SanitizeContent(newContent) targetNote.EditedOn = ts targetBook.Notes[targetIdx] = targetNote dnote[targetBookName] = targetBook @@ -111,7 +106,8 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { return errors.Wrap(err, "Failed to write dnote") } - log.Info("edited the note") + log.Printf("new content: %s\n", newContent) + log.Success("edited the note\n") return nil } diff --git a/cmd/login/login.go b/cmd/login/login.go index 527b64d0..ea90de88 100644 --- a/cmd/login/login.go +++ b/cmd/login/login.go @@ -25,9 +25,14 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command { func newRun(ctx infra.DnoteCtx) core.RunEFunc { return func(cmd *cobra.Command, args []string) error { - log.Printf("welcome to the dnote cloud :)\n") - log.Printf("you can get the api key from https://dnote.io\n") - log.Infof("api key: ") + log.Plain("\n") + log.Plain(" _( )_( )_\n") + log.Plain(" (_ _ _)\n") + log.Plain(" (_) (__)\n\n") + log.Plain("Welcome to Dnote Cloud :)\n\n") + log.Plain("A home for your engineering microlessons\n") + log.Plain("You can register at https://dnote.io\n\n") + log.Printf("API key: ") var apiKey string fmt.Scanln(&apiKey) @@ -43,7 +48,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { return err } - log.Infof("credential configured\n") + log.Success("success\n") return nil } diff --git a/cmd/ls/ls.go b/cmd/ls/ls.go index b6a0d7ad..f330e699 100644 --- a/cmd/ls/ls.go +++ b/cmd/ls/ls.go @@ -5,6 +5,7 @@ import ( "github.com/dnote-io/cli/core" "github.com/dnote-io/cli/infra" "github.com/dnote-io/cli/log" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -17,13 +18,22 @@ var example = ` dnote ls javascript ` +func preRun(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("Incorrect number of argument") + } + + return nil +} + func NewCmd(ctx infra.DnoteCtx) *cobra.Command { cmd := &cobra.Command{ Use: "ls ", - Aliases: []string{"notes"}, + Aliases: []string{"l", "notes"}, Short: "List all notes", Example: example, RunE: newRun(ctx), + PreRunE: preRun, } return cmd @@ -31,17 +41,7 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command { func newRun(ctx infra.DnoteCtx) core.RunEFunc { return func(cmd *cobra.Command, args []string) error { - var bookName string - - if len(args) == 1 { - bookName = args[0] - } else { - var err error - bookName, err = core.GetCurrentBook(ctx) - if err != nil { - return err - } - } + bookName := args[0] log.Infof("on book %s\n", bookName) @@ -50,12 +50,10 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { return err } - for k, v := range dnote { - if k == bookName { - for i, note := range v.Notes { - fmt.Printf(" \033[%dm(%d)\033[0m %s\n", log.ColorYellow, i, note.Content) - } - } + book := dnote[bookName] + + for i, note := range book.Notes { + fmt.Printf(" \033[%dm(%d)\033[0m %s\n", log.ColorYellow, i, note.Content) } return nil diff --git a/cmd/remove/remove.go b/cmd/remove/remove.go index edcab370..72ffb308 100644 --- a/cmd/remove/remove.go +++ b/cmd/remove/remove.go @@ -107,7 +107,7 @@ func note(ctx infra.DnoteCtx, index int, bookName string) error { return errors.Wrap(err, "Failed to write dnote") } - log.Infof("removed from %s\n", bookName) + log.Successf("removed from %s\n", bookName) return nil } @@ -140,7 +140,7 @@ func book(ctx infra.DnoteCtx, bookName string) error { return err } - log.Info("removed book\n") + log.Success("removed book\n") return nil } } diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 654fd47e..e309a6e2 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -21,6 +21,7 @@ var example = ` func NewCmd(ctx infra.DnoteCtx) *cobra.Command { cmd := &cobra.Command{ Use: "sync", + Aliases: []string{"s"}, Short: "Sync dnote with the dnote server", Example: example, RunE: newRun(ctx), @@ -69,7 +70,6 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { if err != nil { return errors.Wrap(err, "Failed to post to the server ") } - fmt.Println(" done.") body, err := ioutil.ReadAll(resp.Body) if err != nil { @@ -79,10 +79,12 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { if resp.StatusCode != http.StatusOK { bodyStr := string(body) - fmt.Printf("Failed to sync on the server: %s", bodyStr) - return errors.New(bodyStr) + fmt.Println("") + return errors.Errorf("Server error: %s", bodyStr) } + fmt.Println(" done.") + var respData responseData err = json.Unmarshal(body, &respData) if err != nil { @@ -105,7 +107,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { return errors.Wrap(err, "Failed to update bookmark") } - log.Infof("synced\n") + log.Success("success\n") if err := core.ClearActionLog(ctx); err != nil { return errors.Wrap(err, "Failed to clear the action log") } diff --git a/cmd/use/use.go b/cmd/use/use.go deleted file mode 100644 index 5159da06..00000000 --- a/cmd/use/use.go +++ /dev/null @@ -1,37 +0,0 @@ -package use - -import ( - "github.com/dnote-io/cli/core" - "github.com/dnote-io/cli/infra" - "github.com/dnote-io/cli/log" - "github.com/spf13/cobra" -) - -var example = ` - dnote use JS` - -func NewCmd(ctx infra.DnoteCtx) *cobra.Command { - cmd := &cobra.Command{ - Use: "use", - Short: "Change the current book", - Aliases: []string{"u"}, - Example: example, - RunE: newRun(ctx), - } - - return cmd -} - -func newRun(ctx infra.DnoteCtx) core.RunEFunc { - return func(cmd *cobra.Command, args []string) error { - targetBookName := args[0] - - err := core.ChangeBook(ctx, targetBookName) - if err != nil { - return err - } - - log.Infof("now on book '%s'\n", targetBookName) - return nil - } -} diff --git a/core/core.go b/core/core.go index 6cfd9c27..5f13cd57 100644 --- a/core/core.go +++ b/core/core.go @@ -5,7 +5,8 @@ import ( "fmt" "io/ioutil" "os" - "sort" + "os/exec" + "strings" "time" "github.com/dnote-io/cli/infra" @@ -17,15 +18,16 @@ import ( const ( // Version is the current version of dnote - Version = "0.2.0-alpha.2" + Version = "0.2.0-alpha.3" // TimestampFilename is the name of the file containing upgrade info TimestampFilename = "timestamps" // DnoteDirName is the name of the directory containing dnote files - DnoteDirName = ".dnote" - ConfigFilename = "dnoterc" - DnoteFilename = "dnote" - ActionFilename = "actions" + DnoteDirName = ".dnote" + ConfigFilename = "dnoterc" + DnoteFilename = "dnote" + ActionFilename = "actions" + TmpContentFilename = "DNOTE_TMPCONTENT" ) type RunEFunc func(*cobra.Command, []string) error @@ -51,6 +53,12 @@ func GetActionPath(ctx infra.DnoteCtx) string { return fmt.Sprintf("%s/%s", ctx.DnoteDir, ActionFilename) } +// GetDnoteTmpContentPath returns the path to the temporary file containing +// content being added or edited +func GetDnoteTmpContentPath(ctx infra.DnoteCtx) string { + return fmt.Sprintf("%s/%s", ctx.DnoteDir, TmpContentFilename) +} + // InitActionFile populates action file if it does not exist func InitActionFile(ctx infra.DnoteCtx) error { path := GetActionPath(ctx) @@ -68,17 +76,45 @@ func InitActionFile(ctx infra.DnoteCtx) error { return err } +func getEditorCommand() string { + editor := os.Getenv("EDITOR") + + if editor == "atom" { + return "atom -w" + } else if editor == "subl" { + return "subl -n -w" + } else if editor == "mate" { + return "mate -w" + } + + return "vim" +} + // InitConfigFile populates a new config file if it does not exist yet func InitConfigFile(ctx infra.DnoteCtx) error { - content := []byte("book: general\n") path := GetConfigPath(ctx) if utils.FileExists(path) { return nil } - err := ioutil.WriteFile(path, content, 0644) - return errors.Wrapf(err, "Failed to write the config file at '%s'", path) + editor := getEditorCommand() + + config := infra.Config{ + Editor: editor, + } + + b, err := yaml.Marshal(config) + if err != nil { + return errors.Wrap(err, "Failed to marshal config into YAML") + } + + err = ioutil.WriteFile(path, b, 0644) + if err != nil { + return errors.Wrapf(err, "Failed to write the config file at '%s'", path) + } + + return nil } // InitDnoteDir initializes dnote directory if it does not exist yet @@ -340,63 +376,6 @@ func UpdateLastActionTimestamp(ctx infra.DnoteCtx, val int64) error { return nil } -func GetCurrentBook(ctx infra.DnoteCtx) (string, error) { - config, err := ReadConfig(ctx) - if err != nil { - return "", err - } - - return config.Book, nil -} - -func GetBookNames(ctx infra.DnoteCtx) ([]string, error) { - dnote, err := GetDnote(ctx) - if err != nil { - return nil, err - } - - books := make([]string, 0, len(dnote)) - for k := range dnote { - books = append(books, k) - } - - sort.Strings(books) - - return books, nil -} - -// ChangeBook replaces the book name in the dnote config file -func ChangeBook(ctx infra.DnoteCtx, bookName string) error { - config, err := ReadConfig(ctx) - if err != nil { - return err - } - - config.Book = bookName - - err = WriteConfig(ctx, config) - if err != nil { - return err - } - - // Now add this book to the .dnote file, for issue #2 - dnote, err := GetDnote(ctx) - if err != nil { - return err - } - - _, exists := dnote[bookName] - if !exists { - dnote[bookName] = NewBook(bookName) - err := WriteDnote(ctx, dnote) - if err != nil { - return err - } - } - - return nil -} - // NewNote returns a note func NewNote(content string, ts int64) infra.Note { return infra.Note{ @@ -499,3 +478,77 @@ func FilterNotes(notes []infra.Note, testFunc func(infra.Note) bool) []infra.Not return ret } + +// SanitizeContent sanitizes note content +func SanitizeContent(s string) string { + var ret string + + ret = strings.Replace(s, "\n", "", -1) + ret = strings.Replace(ret, "\r\n", "", -1) + ret = strings.Trim(ret, " ") + + return ret +} + +func getEditorCmd(ctx infra.DnoteCtx, fpath string) (*exec.Cmd, error) { + config, err := ReadConfig(ctx) + if err != nil { + return nil, errors.Wrap(err, "Failed to read the config") + } + + args := strings.Fields(config.Editor) + args = append(args, fpath) + + return exec.Command(args[0], args[1:]...), nil +} + +// GetEditorInput gets the user input by launching a text editor and waiting for +// it to exit +func GetEditorInput(ctx infra.DnoteCtx, fpath string, content *string) error { + if !utils.FileExists(fpath) { + f, err := os.Create(fpath) + if err != nil { + return errors.Wrap(err, "Failed to create a temporary file for content") + } + err = f.Close() + if err != nil { + return errors.Wrap(err, "Failed to close the temporary file for content") + } + } + + cmd, err := getEditorCmd(ctx, fpath) + if err != nil { + return errors.Wrap(err, "Failed to create the editor command") + } + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Start() + if err != nil { + return errors.Wrapf(err, "Failed to launch the editor") + } + + err = cmd.Wait() + if err != nil { + return errors.Wrap(err, "Failed to wait for the editor") + } + + b, err := ioutil.ReadFile(fpath) + if err != nil { + return errors.Wrap(err, "Failed to read the file") + } + + err = os.Remove(fpath) + if err != nil { + return errors.Wrap(err, "Failed to remove the temporary content file") + } + + raw := string(b) + c := SanitizeContent(raw) + + *content = c + + return nil +} diff --git a/infra/main.go b/infra/main.go index ced80170..cfa9ee25 100644 --- a/infra/main.go +++ b/infra/main.go @@ -10,7 +10,7 @@ type DnoteCtx struct { // Config holds dnote configuration type Config struct { - Book string + Editor string APIKey string } diff --git a/log/log.go b/log/log.go index 64cdd7db..093fc6bf 100644 --- a/log/log.go +++ b/log/log.go @@ -22,6 +22,22 @@ func Infof(msg string, v ...interface{}) { fmt.Printf("%s\033[%dm%s\033[0m %s", indent, ColorBlue, "•", fmt.Sprintf(msg, v...)) } +func Success(msg string) { + fmt.Printf("%s\033[%dm%s\033[0m %s", indent, ColorGreen, "✔", msg) +} + +func Successf(msg string, v ...interface{}) { + fmt.Printf("%s\033[%dm%s\033[0m %s", indent, ColorGreen, "✔", fmt.Sprintf(msg, v...)) +} + +func Plain(msg string) { + fmt.Printf("%s%s", indent, msg) +} + +func Plainf(msg string, v ...interface{}) { + fmt.Printf("%s%s", indent, fmt.Sprintf(msg, v...)) +} + func Warnf(msg string, v ...interface{}) { fmt.Printf("%s\033[%dm%s\033[0m %s", indent, ColorRed, "•", fmt.Sprintf(msg, v...)) } diff --git a/main.go b/main.go index da2f62c6..fc0375e4 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,6 @@ import ( "github.com/dnote-io/cli/cmd/remove" "github.com/dnote-io/cli/cmd/sync" "github.com/dnote-io/cli/cmd/upgrade" - "github.com/dnote-io/cli/cmd/use" "github.com/dnote-io/cli/cmd/version" ) @@ -45,7 +44,6 @@ func main() { root.Register(add.NewCmd(ctx)) root.Register(ls.NewCmd(ctx)) root.Register(sync.NewCmd(ctx)) - root.Register(use.NewCmd(ctx)) root.Register(version.NewCmd(ctx)) root.Register(upgrade.NewCmd(ctx)) diff --git a/main_test.go b/main_test.go index 8465d0c7..4244dabe 100644 --- a/main_test.go +++ b/main_test.go @@ -82,14 +82,14 @@ func TestInit(t *testing.T) { } } -func TestAdd_NewBook(t *testing.T) { +func TestAdd_NewBook_ContentFlag(t *testing.T) { // Setup ctx := testutils.InitCtx("./tmp") testutils.SetupTmp(ctx) defer testutils.ClearTmp(ctx) // Execute - runDnoteCmd(ctx, "add", "js", "foo") + runDnoteCmd(ctx, "add", "js", "-c", "foo") // Test dnote, err := core.GetDnote(ctx) @@ -135,7 +135,7 @@ func TestAdd_NewBook(t *testing.T) { testutils.AssertNotEqual(t, note.AddedOn, int64(0), "Note added_on mismatch") } -func TestAdd_ExistingBook(t *testing.T) { +func TestAdd_ExistingBook_ContentFlag(t *testing.T) { // Setup ctx := testutils.InitCtx("./tmp") testutils.SetupTmp(ctx) @@ -146,7 +146,7 @@ func TestAdd_ExistingBook(t *testing.T) { testutils.WriteFile(ctx, "./testutils/fixtures/dnote1.json", "dnote") // Execute - runDnoteCmd(ctx, "add", "js", "foo") + runDnoteCmd(ctx, "add", "js", "-c", "foo") // Test dnote, err := core.GetDnote(ctx) @@ -180,76 +180,6 @@ func TestAdd_ExistingBook(t *testing.T) { testutils.AssertEqual(t, book.Notes[1].Content, "foo", "Note content mismatch") } -func TestEdit(t *testing.T) { - // Setup - ctx := testutils.InitCtx("./tmp") - testutils.SetupTmp(ctx) - defer testutils.ClearTmp(ctx) - - // init files by running root command - runDnoteCmd(ctx) - testutils.WriteFile(ctx, "./testutils/fixtures/dnote2.json", "dnote") - - // Execute - cmd, stderr, err := newDnoteCmd(ctx, "edit", "js", "1") - if err != nil { - panic(errors.Wrap(err, "Failed to get command")) - } - stdin, err := cmd.StdinPipe() - if err != nil { - panic(errors.Wrap(err, "Failed to get stdin %s")) - } - defer stdin.Close() - - // Start the program - err = cmd.Start() - if err != nil { - panic(errors.Wrap(err, "Failed to start command")) - } - - // enter content - _, err = io.WriteString(stdin, "foo bar\n") - if err != nil { - panic(errors.Wrap(err, "Failed to write to stdin")) - } - - err = cmd.Wait() - if err != nil { - panic(errors.Wrapf(err, "Failed to run command %s", stderr.String())) - } - - // Test - dnote, err := core.GetDnote(ctx) - if err != nil { - t.Fatal(errors.Wrap(err, "Failed to get dnote")) - } - actions, err := core.ReadActionLog(ctx) - if err != nil { - t.Fatal(errors.Wrap(err, "Failed to read actions")) - } - - book := dnote["js"] - action := actions[0] - - var actionData core.EditNoteData - err = json.Unmarshal(action.Data, &actionData) - if err != nil { - log.Fatalln("Failed to unmarshal the action data: %s", err) - } - - testutils.AssertEqual(t, len(actions), 1, "There should be 1 action") - testutils.AssertEqual(t, action.Type, core.ActionEditNote, "action type mismatch") - testutils.AssertEqual(t, actionData.Content, "foo bar", "action data name mismatch") - testutils.AssertEqual(t, actionData.BookName, "js", "action data book_name mismatch") - testutils.AssertEqual(t, actionData.NoteUUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "action data note_uuis mismatch") - testutils.AssertNotEqual(t, action.Timestamp, 0, "action timestamp mismatch") - testutils.AssertEqual(t, len(book.Notes), 2, "Book should have one note") - testutils.AssertEqual(t, book.Notes[0].UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "Note should have UUID") - testutils.AssertEqual(t, book.Notes[0].Content, "Booleans have toString()", "Note content mismatch") - testutils.AssertEqual(t, book.Notes[1].UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "Note should have UUID") - testutils.AssertEqual(t, book.Notes[1].Content, "foo bar", "Note content mismatch") - testutils.AssertNotEqual(t, book.Notes[1].EditedOn, int64(0), "Note edited_on mismatch") -} func TestEdit_ContentFlag(t *testing.T) { // Setup ctx := testutils.InitCtx("./tmp") diff --git a/migrate/migrate.go b/migrate/migrate.go index d3a565c0..60781481 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -23,12 +23,14 @@ const ( migrationV1 migrationV2 migrationV3 + migrationV4 ) var migrationSequence = []int{ migrationV1, migrationV2, migrationV3, + migrationV4, } type schema struct { @@ -80,6 +82,8 @@ func performMigration(ctx infra.DnoteCtx, migrationID int) error { migrationError = migrateToV2(ctx) case migrationV3: migrationError = migrateToV3(ctx) + case migrationV4: + migrationError = migrateToV4(ctx) default: return errors.Errorf("Unrecognized migration id %d", migrationID) } diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 9ebc7d0a..72cad177 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -5,7 +5,9 @@ import ( "github.com/dnote-io/cli/testutils" "github.com/dnote-io/cli/utils" "github.com/pkg/errors" + "gopkg.in/yaml.v2" "io/ioutil" + "os" "path/filepath" "testing" ) @@ -164,3 +166,29 @@ func TestMigrateToV3(t *testing.T) { } } } + +func TestMigrateToV4(t *testing.T) { + ctx := testutils.InitCtx("../tmp") + + // set up + testutils.SetupTmp(ctx) + testutils.WriteFile(ctx, "./fixtures/4-pre-dnoterc.yaml", "dnoterc") + defer testutils.ClearTmp(ctx) + defer os.Setenv("EDITOR", "") + + // execute + os.Setenv("EDITOR", "vim") + if err := migrateToV4(ctx); err != nil { + t.Fatal(errors.Wrap(err, "Failed to migrate").Error()) + } + + // test + b := testutils.ReadFile(ctx, "dnoterc") + var config migrateToV4PostConfig + if err := yaml.Unmarshal(b, &config); err != nil { + t.Fatal(errors.Wrap(err, "Failed to unmarshal the result into Dnote").Error()) + } + + testutils.AssertEqual(t, config.APIKey, "Oev6e1082ORasdf9rjkfjkasdfjhgei", "api key mismatch") + testutils.AssertEqual(t, config.Editor, "vim", "editor mismatch") +} diff --git a/migrate/migrations.go b/migrate/migrations.go index 594bde21..4e3d335f 100644 --- a/migrate/migrations.go +++ b/migrate/migrations.go @@ -11,6 +11,7 @@ import ( "github.com/dnote-io/cli/utils" "github.com/pkg/errors" "github.com/satori/go.uuid" + "gopkg.in/yaml.v2" ) // migrateToV1 deletes YAML archive if exists @@ -143,3 +144,49 @@ func migrateToV3(ctx infra.DnoteCtx) error { return nil } + +func getEditorCommand() string { + editor := os.Getenv("EDITOR") + + if editor == "atom" { + return "atom -w" + } else if editor == "subl" { + return "subl -n -w" + } else if editor == "mate" { + return "mate -w" + } + + return "vim" +} + +func migrateToV4(ctx infra.DnoteCtx) error { + configPath := fmt.Sprintf("%s/dnoterc", ctx.DnoteDir) + + b, err := ioutil.ReadFile(configPath) + if err != nil { + return errors.Wrap(err, "Failed to read the config file") + } + + var preConfig migrateToV4PreConfig + err = yaml.Unmarshal(b, &preConfig) + if err != nil { + return errors.Wrap(err, "Failed to unmarshal existing config into JSON") + } + + postConfig := migrateToV4PostConfig{ + APIKey: preConfig.APIKey, + Editor: getEditorCommand(), + } + + data, err := yaml.Marshal(postConfig) + if err != nil { + return errors.Wrap(err, "Failed to marshal config into JSON") + } + + err = ioutil.WriteFile(configPath, data, 0644) + if err != nil { + return errors.Wrap(err, "Failed to write the config into a file") + } + + return nil +} diff --git a/migrate/snapshots.go b/migrate/snapshots.go index dc684217..715722e4 100644 --- a/migrate/snapshots.go +++ b/migrate/snapshots.go @@ -43,3 +43,13 @@ type migrateToV3Action struct { Data map[string]interface{} `json:"data"` Timestamp int64 `json:"timestamp"` } + +// v4 +type migrateToV4PreConfig struct { + Book string + APIKey string +} +type migrateToV4PostConfig struct { + Editor string + APIKey string +} diff --git a/upgrade/upgrade.go b/upgrade/upgrade.go index 6ef0ef7b..4e3edadf 100644 --- a/upgrade/upgrade.go +++ b/upgrade/upgrade.go @@ -108,7 +108,7 @@ func Upgrade(ctx infra.DnoteCtx) error { // Check if up to date if latestVersion == core.Version { - log.Info("you are up-to-date") + log.Success("you are up-to-date\n") err = touchLastUpgrade(ctx) if err != nil { return errors.Wrap(err, "Failed to update the upgrade timestamp") @@ -170,7 +170,7 @@ func Upgrade(ctx infra.DnoteCtx) error { return errors.Wrap(err, "Upgrade is done, but failed to update the last_upgrade timestamp.") } - log.Infof("Updated: v%s -> v%s\n", core.Version, latestVersion) - log.Infof("Changelog: https://github.com/dnote-io/cli/releases") + log.Successf("Updated: v%s -> v%s\n", core.Version, latestVersion) + log.Info("Changelog: https://github.com/dnote-io/cli/releases\n") return nil } diff --git a/utils/utils.go b/utils/utils.go index 13f0a1f3..7b98bfc1 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -7,7 +7,6 @@ import ( "math/rand" "os" "path/filepath" - "strings" "time" "github.com/dnote-io/cli/log" @@ -152,12 +151,3 @@ func CopyDir(src, dest string) error { return nil } - -func SanitizeContent(s string) string { - var ret string - - ret = strings.Replace(s, "\n", "", -1) - ret = strings.Replace(ret, "\r\n", "", -1) - - return ret -}