Use editor for add and edit by default (#54)

* Open text editor for add and edit

* Remove unused test

* Improve output

* Remove support for current book

* Improve ls output and add an alias

* Simplify logic

* v0.2.0-alpha.3

* Add migration for editor

* Add ASCII art
This commit is contained in:
Sung Won Cho 2018-01-09 21:31:52 +11:00 committed by GitHub
commit 41ee8eba0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 316 additions and 288 deletions

View file

@ -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
}
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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 <book name?>",
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

View file

@ -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
}
}

View file

@ -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")
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -10,7 +10,7 @@ type DnoteCtx struct {
// Config holds dnote configuration
type Config struct {
Book string
Editor string
APIKey string
}

View file

@ -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...))
}

View file

@ -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))

View file

@ -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")

View file

@ -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)
}

View file

@ -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")
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}