mirror of
https://github.com/dnote/dnote
synced 2026-03-14 22:45:50 +01:00
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:
parent
d31b22aed0
commit
41ee8eba0f
19 changed files with 316 additions and 288 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
34
cmd/ls/ls.go
34
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 <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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
185
core/core.go
185
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ type DnoteCtx struct {
|
|||
|
||||
// Config holds dnote configuration
|
||||
type Config struct {
|
||||
Book string
|
||||
Editor string
|
||||
APIKey string
|
||||
}
|
||||
|
||||
|
|
|
|||
16
log/log.go
16
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...))
|
||||
}
|
||||
|
|
|
|||
2
main.go
2
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))
|
||||
|
||||
|
|
|
|||
78
main_test.go
78
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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue