Improve output and edit command (#52)

* Refine output

* Show note content while editing

* v0.2.0-alpha.2
This commit is contained in:
Sung Won Cho 2018-01-07 18:18:47 +11:00 committed by GitHub
commit d31b22aed0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 260 additions and 85 deletions

2
Gopkg.lock generated
View file

@ -52,6 +52,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "dc231aa7e3a8f7286e2b20dac68d566c430b0c7a3a0de97c3a3534280a6f85b4"
inputs-digest = "a6df84725e42f485baf9261b9d48ad577c4687d5d7f5b90353d1e40606f40e26"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -1,11 +1,11 @@
package add
import (
"fmt"
"time"
"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"
)
@ -69,7 +69,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
return errors.Wrap(err, "Failed to write note")
}
fmt.Printf("[+] Added to %s\n", bookName)
log.Infof("added to %s\n", bookName)
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/spf13/cobra"
)
@ -37,13 +38,12 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
for _, book := range books {
if book == currentBook {
fmt.Printf("* %v\n", book)
fmt.Printf(" %s\033[%dm%s\033[0m\n", "* ", log.ColorBlue, book)
} else {
fmt.Printf(" %v\n", book)
fmt.Printf(" %s%s\n", " ", book)
}
}
return nil
}
}

View file

@ -1,22 +1,28 @@
package edit
import (
"fmt"
"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"
)
var newContent string
var example = `
* Edit the note by index in the current book
dnote edit 3 "new content"
dnote edit 3
* Edit the note by index in a certain book
dnote edit JS 3 "new content"`
dnote edit js 3
* Skip the prompt by providing new content directly
dntoe eidt js 3 -c "new content"`
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
@ -28,11 +34,14 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
RunE: newRun(ctx),
}
f := cmd.Flags()
f.StringVarP(&newContent, "content", "c", "", "The new content for the note")
return cmd
}
func preRun(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
if len(args) < 1 {
return errors.New("Missing argument")
}
@ -47,54 +56,63 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
var targetBookName string
var index int
var content string
var targetIdx int
if len(args) == 2 {
if len(args) == 1 {
targetBookName, err = core.GetCurrentBook(ctx)
if err != nil {
return err
}
index, err = strconv.Atoi(args[0])
targetIdx, err = strconv.Atoi(args[0])
if err != nil {
return err
}
content = args[1]
} else if len(args) == 3 {
} else if len(args) == 2 {
targetBookName = args[0]
index, err = strconv.Atoi(args[1])
targetIdx, err = strconv.Atoi(args[1])
if err != nil {
return err
}
content = args[2]
}
targetBook, exists := dnote[targetBookName]
if !exists {
return errors.Errorf("Book with the name '%s' does not exist", targetBookName)
return errors.Errorf("Book %s does not exist", targetBookName)
}
if targetIdx > len(targetBook.Notes)-1 {
return errors.Errorf("Book %s does not have note with index %d", targetBookName, targetIdx)
}
targetNote := targetBook.Notes[targetIdx]
if newContent == "" {
log.Printf("content: %s\n", targetNote.Content)
log.Printf("new content: ")
newContent, err = utils.GetInput()
if err != nil {
return errors.Wrap(err, "Failed to get new content")
}
}
ts := time.Now().Unix()
for i, note := range dnote[targetBookName].Notes {
if i == index {
note.Content = content
note.EditedOn = ts
dnote[targetBookName].Notes[i] = note
targetNote.Content = utils.SanitizeContent(newContent)
targetNote.EditedOn = ts
targetBook.Notes[targetIdx] = targetNote
dnote[targetBookName] = targetBook
err := core.LogActionEditNote(ctx, note.UUID, targetBook.Name, note.Content, ts)
if err != nil {
return errors.Wrap(err, "Failed to log action")
}
err = core.WriteDnote(ctx, dnote)
fmt.Printf("Edited Note : %d \n", index)
return err
}
err = core.LogActionEditNote(ctx, targetNote.UUID, targetBook.Name, targetNote.Content, ts)
if err != nil {
return errors.Wrap(err, "Failed to log action")
}
// If loop finishes without returning, note did not exist
fmt.Println("Error : The note with that index is not found.")
err = core.WriteDnote(ctx, dnote)
if err != nil {
return errors.Wrap(err, "Failed to write dnote")
}
log.Info("edited the note")
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/spf13/cobra"
)
@ -24,7 +25,9 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
fmt.Print("Please enter your APIKey: ")
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: ")
var apiKey string
fmt.Scanln(&apiKey)
@ -40,6 +43,8 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
return err
}
log.Infof("credential configured\n")
return nil
}

View file

@ -2,9 +2,9 @@ package ls
import (
"fmt"
"github.com/dnote-io/cli/core"
"github.com/dnote-io/cli/infra"
"github.com/dnote-io/cli/log"
"github.com/spf13/cobra"
)
@ -43,7 +43,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
}
fmt.Printf("On note %s\n", bookName)
log.Infof("on book %s\n", bookName)
dnote, err := core.GetDnote(ctx)
if err != nil {
@ -53,7 +53,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
for k, v := range dnote {
if k == bookName {
for i, note := range v.Notes {
fmt.Printf("* [%d] - %s\n", i, note.Content)
fmt.Printf(" \033[%dm(%d)\033[0m %s\n", log.ColorYellow, i, note.Content)
}
}
}

View file

@ -6,6 +6,7 @@ import (
"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"
@ -38,7 +39,10 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
if targetBookName != "" {
book(ctx, targetBookName)
err := book(ctx, targetBookName)
if err != nil {
return errors.Wrap(err, "Failed to delete the book")
}
} else {
if len(args) < 2 {
return errors.New("Missing argument")
@ -79,13 +83,14 @@ func note(ctx infra.DnoteCtx, index int, bookName string) error {
}
content := notes[index].Content
fmt.Printf("Deleting note: %s\n", content)
log.Printf("content: \"%s\"\n", content)
ok, err := utils.AskConfirmation("Are you sure?")
ok, err := utils.AskConfirmation("remove this note?")
if err != nil {
return errors.Wrap(err, "Failed to get confirmation")
}
if !ok {
log.Warnf("aborted by user\n")
return nil
}
@ -102,17 +107,18 @@ func note(ctx infra.DnoteCtx, index int, bookName string) error {
return errors.Wrap(err, "Failed to write dnote")
}
fmt.Printf("Deleted!\n")
log.Infof("removed from %s\n", bookName)
return nil
}
// book deletes a book with the given name
func book(ctx infra.DnoteCtx, bookName string) error {
ok, err := utils.AskConfirmation("Are you sure?")
ok, err := utils.AskConfirmation(fmt.Sprintf("delete book '%s' and all its notes?", bookName))
if err != nil {
return err
}
if !ok {
log.Warnf("aborted by user\n")
return nil
}
@ -134,11 +140,10 @@ func book(ctx infra.DnoteCtx, bookName string) error {
return err
}
fmt.Printf("[-] Deleted book : %s \n", bookName)
log.Info("removed book\n")
return nil
}
}
fmt.Println("Error : The book with that name is not found.")
return nil
return errors.Errorf("Book '%s' was not found", bookName)
}

View file

@ -10,8 +10,10 @@ import (
)
var root = &cobra.Command{
Use: "dnote",
Short: "Dnote - Instantly capture what you learn while coding",
Use: "dnote",
Short: "Dnote - Instantly capture what you learn while coding",
SilenceErrors: true,
SilenceUsage: true,
}
// Register adds a new command

View file

@ -10,6 +10,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"
)
@ -48,23 +49,27 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
if err != nil {
return errors.Wrap(err, "Failed to read the timestamp")
}
actions, err := core.ReadActionLog(ctx)
if err != nil {
return errors.Wrap(err, "Failed to read the action log")
}
if config.APIKey == "" {
fmt.Println("Login required. Please run `dnote login`")
return nil
}
fmt.Println("Compressing dnote...")
payload, err := getPayload(ctx, timestamp)
payload, err := getPayload(actions, timestamp)
if err != nil {
return errors.Wrap(err, "Failed to get dnote payload")
}
fmt.Println("Syncing with the server...")
log.Infof("writing changes (total %d).", len(actions))
resp, err := postActions(ctx, config.APIKey, payload)
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 {
@ -84,10 +89,12 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
return errors.Wrap(err, "Failed to unmarshal payload")
}
log.Infof("resolving delta (total %d).", len(respData.Actions))
err = core.ReduceAll(ctx, respData.Actions)
if err != nil {
return errors.Wrap(err, "Failed to reduce returned actions")
}
fmt.Println(" done.")
// Update bookmark
ts, err := core.ReadTimestamp(ctx)
@ -98,7 +105,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
return errors.Wrap(err, "Failed to update bookmark")
}
fmt.Println("Successfully synced all notes")
log.Infof("synced\n")
if err := core.ClearActionLog(ctx); err != nil {
return errors.Wrap(err, "Failed to clear the action log")
}
@ -107,15 +114,15 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
}
func getPayload(ctx infra.DnoteCtx, timestamp infra.Timestamp) (*bytes.Buffer, error) {
actions, err := compressActions(ctx)
func getPayload(actions []core.Action, timestamp infra.Timestamp) (*bytes.Buffer, error) {
compressedActions, err := compressActions(actions)
if err != nil {
return &bytes.Buffer{}, errors.Wrap(err, "Failed to compress actions")
}
payload := syncPayload{
Bookmark: timestamp.Bookmark,
Actions: actions,
Actions: compressedActions,
}
b, err := json.Marshal(payload)
@ -127,10 +134,10 @@ func getPayload(ctx infra.DnoteCtx, timestamp infra.Timestamp) (*bytes.Buffer, e
return ret, nil
}
func compressActions(ctx infra.DnoteCtx) ([]byte, error) {
b, err := core.ReadActionLogContent(ctx)
func compressActions(actions []core.Action) ([]byte, error) {
b, err := json.Marshal(&actions)
if err != nil {
return nil, errors.Wrap(err, "Failed to read the action log content")
return nil, errors.Wrap(err, "failed to marshal actions into JSON")
}
var buf bytes.Buffer

View file

@ -1,10 +1,9 @@
package use
import (
"fmt"
"github.com/dnote-io/cli/core"
"github.com/dnote-io/cli/infra"
"github.com/dnote-io/cli/log"
"github.com/spf13/cobra"
)
@ -32,8 +31,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
return err
}
fmt.Printf("Now using %s\n", targetBookName)
log.Infof("now on book '%s'\n", targetBookName)
return nil
}
}

View file

@ -17,7 +17,7 @@ import (
const (
// Version is the current version of dnote
Version = "0.2.0-alpha"
Version = "0.2.0-alpha.2"
// TimestampFilename is the name of the file containing upgrade info
TimestampFilename = "timestamps"

39
log/log.go Normal file
View file

@ -0,0 +1,39 @@
package log
import (
"fmt"
)
var (
ColorRed = 31
ColorGreen = 32
ColorYellow = 33
ColorBlue = 34
ColorGray = 37
)
var indent = " "
func Info(msg string) {
fmt.Printf("%s\033[%dm%s\033[0m %s\n", indent, ColorBlue, "•", msg)
}
func Infof(msg string, v ...interface{}) {
fmt.Printf("%s\033[%dm%s\033[0m %s", indent, ColorBlue, "•", 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...))
}
func Error(msg string) {
fmt.Printf("%s\033[%dm%s\033[0m %s\n", indent, ColorRed, "", msg)
}
func Printf(msg string, v ...interface{}) {
fmt.Printf("%s\033[%dm%s\033[0m %s", indent, ColorGray, "•", fmt.Sprintf(msg, v...))
}
func WithPrefixf(prefixColor int, prefix, msg string, v ...interface{}) {
fmt.Printf(" \033[%dm%s\033[0m %s\n", prefixColor, prefix, fmt.Sprintf(msg, v...))
}

View file

@ -8,6 +8,7 @@ import (
"github.com/dnote-io/cli/cmd/root"
"github.com/dnote-io/cli/core"
"github.com/dnote-io/cli/infra"
"github.com/dnote-io/cli/log"
"github.com/pkg/errors"
// commands
@ -49,6 +50,7 @@ func main() {
root.Register(upgrade.NewCmd(ctx))
if err := root.Execute(); err != nil {
log.Error(err.Error())
os.Exit(1)
}
}

View file

@ -191,7 +191,77 @@ func TestEdit(t *testing.T) {
testutils.WriteFile(ctx, "./testutils/fixtures/dnote2.json", "dnote")
// Execute
runDnoteCmd(ctx, "edit", "js", "1", "foo bar")
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")
testutils.SetupTmp(ctx)
defer testutils.ClearTmp(ctx)
// init files by running root command
runDnoteCmd(ctx)
testutils.WriteFile(ctx, "./testutils/fixtures/dnote2.json", "dnote")
// Execute
runDnoteCmd(ctx, "edit", "js", "1", "-c", "foo bar")
// Test
dnote, err := core.GetDnote(ctx)
@ -253,8 +323,8 @@ func TestRemoveNote(t *testing.T) {
panic(errors.Wrap(err, "Failed to start command"))
}
// Hit return to confirm
_, err = io.WriteString(stdin, "\n")
// confirm
_, err = io.WriteString(stdin, "y\n")
if err != nil {
panic(errors.Wrap(err, "Failed to write to stdin"))
}
@ -326,8 +396,8 @@ func TestRemoveBook(t *testing.T) {
panic(errors.Wrap(err, "Failed to start command"))
}
// Hit return to confirm
_, err = io.WriteString(stdin, "\n")
// confirm
_, err = io.WriteString(stdin, "y\n")
if err != nil {
panic(errors.Wrap(err, "Failed to write to stdin"))
}

View file

@ -13,6 +13,7 @@ import (
"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/google/go-github/github"
"github.com/pkg/errors"
@ -69,7 +70,7 @@ func AutoUpgrade(ctx infra.DnoteCtx) error {
}
if shouldCheck {
willCheck, err := utils.AskConfirmation("Would you like to check for an update?")
willCheck, err := utils.AskConfirmation("check for upgrade?")
if err != nil {
return err
}
@ -86,6 +87,8 @@ func AutoUpgrade(ctx infra.DnoteCtx) error {
}
func Upgrade(ctx infra.DnoteCtx) error {
log.Infof("current version is %s\n", core.Version)
// Fetch the latest version
gh := github.NewClient(nil)
releases, _, err := gh.Repositories.ListReleases(context.Background(), "dnote-io", "cli", nil)
@ -101,22 +104,31 @@ func Upgrade(ctx infra.DnoteCtx) error {
return err
}
log.Infof("latest version is %s\n", latestVersion)
// Check if up to date
if latestVersion == core.Version {
fmt.Printf("Up-to-date: %s\n", core.Version)
core.InitTimestampFile(ctx)
log.Info("you are up-to-date")
err = touchLastUpgrade(ctx)
if err != nil {
return errors.Wrap(err, "Failed to update the upgrade timestamp")
}
return nil
}
asset := getAsset(latest)
if asset == nil {
core.InitTimestampFile(ctx)
fmt.Printf("Could not find the release for %s %s", runtime.GOOS, runtime.GOARCH)
return nil
err = touchLastUpgrade(ctx)
if err != nil {
return errors.Wrap(err, "Failed to update the upgrade timestamp")
}
return errors.Errorf("Could not find the release for %s %s", runtime.GOOS, runtime.GOARCH)
}
// Download temporary file
fmt.Printf("Downloading: %s\n", latestVersion)
log.Infof("Downloading: %s\n", latestVersion)
tmpPath := path.Join(os.TempDir(), "dnote_update")
out, err := os.Create(tmpPath)
@ -158,7 +170,7 @@ func Upgrade(ctx infra.DnoteCtx) error {
return errors.Wrap(err, "Upgrade is done, but failed to update the last_upgrade timestamp.")
}
fmt.Printf("Updated: v%s -> v%s\n", core.Version, latestVersion)
fmt.Println("Changelog: https://github.com/dnote-io/cli/releases")
log.Infof("Updated: v%s -> v%s\n", core.Version, latestVersion)
log.Infof("Changelog: https://github.com/dnote-io/cli/releases")
return nil
}

View file

@ -2,14 +2,15 @@ package utils
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"strings"
"time"
"github.com/dnote-io/cli/log"
"github.com/pkg/errors"
"github.com/satori/go.uuid"
)
@ -27,18 +28,25 @@ func GenerateUID() string {
return uuid.NewV4().String()
}
func AskConfirmation(question string) (bool, error) {
fmt.Printf("%s [Y/n]: ", question)
func GetInput() (string, error) {
reader := bufio.NewReader(os.Stdin)
res, err := reader.ReadString('\n')
input, err := reader.ReadString('\n')
if err != nil {
return false, err
return "", errors.Wrap(err, "Failed to read stdin")
}
ok := res == "y\n" || res == "Y\n" || res == "\n"
return input, nil
}
return ok, nil
func AskConfirmation(question string) (bool, error) {
log.Printf("%s (y/N): ", question)
res, err := GetInput()
if err != nil {
return false, errors.Wrap(err, "Failed to get user input")
}
return res == "y\n", nil
}
// FileExists checks if the file exists at the given path
@ -144,3 +152,12 @@ 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
}