diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | workstack.go | 1 | ||||
-rw-r--r-- | ws/main.go | 186 |
3 files changed, 192 insertions, 1 deletions
@@ -1,6 +1,8 @@ # To Do - [ ] ZSH Completion -- [ ] Create intermediate text format for editing in editor / importing +- [ ] import command +- [ ] better temp file path & delete on exit +- [ ] nvim command can edit done tasks as well (with flag) - [ ] Add tore-like reminder for shellrc - [ ] clocking with a 'start' command - [ ] tag edit @@ -21,6 +23,8 @@ - [ ] tag list # Done +- [x] Added nvim command +- [x] Create intermediate text format for editing in editor / importing - [x] Edit task - [x] Add descriptions to task - [x] added `-d` flag to commands to act on done tasks diff --git a/workstack.go b/workstack.go index bfff166..d6d1c07 100644 --- a/workstack.go +++ b/workstack.go @@ -32,6 +32,7 @@ var ( const ( TASK_LIST_COUNT = 5 GOBDATA_FILENAME = "tasks.gob" + TMPFILE = "tasks.ws" ) // For formatting @@ -5,7 +5,9 @@ import ( "errors" "flag" "fmt" + "io/ioutil" "os" + "os/exec" "strings" "time" @@ -22,6 +24,167 @@ var ( // Persistent storage for Tasks ) +// Parse and import file +func ImportTasks(tmpfile string) ([]ws.Task, error) { + f, err := os.Open(tmpfile) + if err != nil { + panic(err) + } + + content, err := ioutil.ReadAll(f) + if err != nil { + panic(err) + } + + // TODO: better size choices + parsedTasks := make([]ws.Task, len(content)) + tagName := make([]byte, len(content)) + + tasksLen := 0 + tagLen := 0 + textLen := 0 + + isInsideTag := false + isInsideText := false + + firstTime := true + + for i := 0; i < len(content); i++ { + // consume leading whitespace + if firstTime { + for content[i] == ' ' || content[i] == '\n' || content[i] == '\t' { + i++ + } + } + + if content[i] == '{' { + isInsideTag = true + isInsideText = false + + if firstTime { + firstTime = false + continue + } + + text := trimEnd(content, i-1, textLen) + parsedTasks[tasksLen].Text = string(text) + + textLen = 0 + + tasksLen++ + i++ + } + + if content[i] == '}' { + isInsideTag = false + isInsideText = true + + tag := make([]byte, tagLen) + copy(tag, tagName) + parsedTasks[tasksLen].Tag = ws.Tag(tag) + + tagLen = 0 + tagName[0] = 0 + i++ + + // consume whitespace + for content[i] == ' ' || content[i] == '\n' || content[i] == '\t' { + i++ + } + } + + // this can happen whe consuming whitespace + if i == len(content) { + break + } + + if isInsideTag { + tagName[tagLen] = content[i] + tagLen++ + } else if isInsideText { + textLen++ + } + } + + if !isInsideTag && !isInsideText && tasksLen == 0 { + return nil, nil + } + + if isInsideTag { + return nil, errors.New("Syntax error: Brace not closed.") + } + + // get that last task in + text := trimEnd(content, len(content)-1, textLen) + parsedTasks[tasksLen].Text = string(text) + + tasksLen++ + + // Check if the tags are valid + for i := 0; i < tasksLen; i++ { + if parsedTasks[i].Tag == "" { + continue + } + + found := 0 + for ; found < len(Tags); found++ { + if parsedTasks[i].Tag == Tags[found] { + break + } + } + + if found == len(Tags) { + return nil, errors.New(fmt.Sprintf("Syntax Error: Invalid tag '%s'.", parsedTasks[i].Tag)) + } + } + + return parsedTasks[:tasksLen], nil +} + +// export the tasks to custom file format and edit in nvim +func NvimTask() { + f, err := os.Create(ws.TMPFILE) + if err != nil { + panic(err) + } + + // export tasks + for _, v := range TasksDo { + fmt.Fprintf(f, "{%s} %s\n", v.Tag, v.Text) + if v.Description != "" { + fmt.Fprintf(f, "%s\n", v.Description) + } + } + + var importedTasks []ws.Task + for { + // edit in nvim + nvimCmd := exec.Command("nvim", ws.TMPFILE) + nvimCmd.Stdout = os.Stdout + nvimCmd.Stdin = os.Stdin + nvimCmd.Stderr = os.Stderr + + err = nvimCmd.Run() + if err != nil { + panic(err) + } + + importedTasks, err = ImportTasks(ws.TMPFILE) + if err == nil { + break + } + + var choice string + fmt.Printf("%s\nRetry? [y/N] ", err) + fmt.Scanf("%s", &choice) + if choice == "" || choice[0] == 'n' || choice[0] == 'N' { + os.Exit(1) + } + } + + TasksDo = importedTasks +} + func AddTask(tagName string, taskText string) { if taskText == "" { fmt.Println("Task text is required.") @@ -219,10 +382,26 @@ COMMANDS del Delete an active task pc Procrastinate an active task top Put a task to the top of the stack + nvim Edit tasks in nvim list List all active and done tasks tag tag commands`) } +// Return part of a slice with trailing white space removed +func trimEnd(text []byte, endOffset, length int) []byte { + end := endOffset + start := endOffset - length + 1 + for ; end > start; end-- { + if text[end] != '\n' && + text[end] != ' ' && + text[end] != '\t' && + text[end] != 0 { + break + } + } + return text[start : end+1] +} + func main() { var ( dec *gob.Decoder @@ -406,6 +585,13 @@ func main() { EditTask(shouldEditDone, n, newText, ws.Tag(newTag)) ListTasks(shouldEditDone) + case "nvim": + flagSet := flag.NewFlagSet("nvim", flag.ExitOnError) + flagSet.Parse(os.Args[2:]) + + NvimTask() + ListTasks(false) + // Undo a done task case "undo": var n int |