aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaymaekers Luca <raymaekers.luca@gmail.com>2024-11-10 14:06:18 +0100
committerRaymaekers Luca <raymaekers.luca@gmail.com>2024-11-10 14:07:21 +0100
commitad898317914439c33e5670d9018646799f2d6c3f (patch)
tree70047a56ae893441f3d1499d653475bb758d0ed7
parent45e65c66bbcded97135503068881214ee644d2f7 (diff)
Better flags parsing and "top" comamnd
-rw-r--r--README.md7
-rw-r--r--workstack.go12
-rw-r--r--ws/main.go437
3 files changed, 287 insertions, 169 deletions
diff --git a/README.md b/README.md
index 1fe3ba2..3044d73 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,10 @@
# To Do
-- [ ] Put a task at the top
- [ ] ZSH Completion
-- [ ] Better help for commands & options
- [ ] Add descriptions to task
- [ ] Edit task with editor
- [ ] Create a script to add from dump file into tasks
-- [ ] After pc/del do "ls" command
- [ ] Add "start" command
- [ ] Add tore-like reminder for shellrc
-- [ ] Redo project folder structure.
- [ ] Rename a task
- [ ] import: read multiple lines from stdin and import them as taks
- [ ] parsing text as Tasks, maybe helper program?
@@ -31,5 +27,8 @@
- [ ] bug: undo a task with a tag that no longer exists
# Done
+- [x] After pc/del do "ls" command
+- [x] Better help for commands & options
+- [x] Put a task at the top
- [x] implement "undone" command
- [x] Add tags support
diff --git a/workstack.go b/workstack.go
index f7bc41c..45b5d67 100644
--- a/workstack.go
+++ b/workstack.go
@@ -16,10 +16,6 @@ type TaskDone struct {
Date time.Time
}
-func (t TaskDone) String() string {
- return fmt.Sprintf("(%s) %s", t.Date.Format(DateLayout), t.Task)
-}
-
type Tag string
type Task struct {
@@ -27,6 +23,10 @@ type Task struct {
Tag Tag
}
+func (t TaskDone) String() string {
+ return fmt.Sprintf("(%s) %s", t.Date.Format(DateLayout), t.Task)
+}
+
func (t Task) String() string {
if t.Tag != "" {
return fmt.Sprintf("{%s} %s", t.Tag, t.Text)
@@ -36,14 +36,12 @@ func (t Task) String() string {
}
var (
- // Persistent storage for Tasks
- Gobdata string = "tasks.gob"
DateLayout string = "15:04:05 02/01/2006"
)
const (
TASK_LIST_COUNT = 5
- GOBDATA_FILENAME = "tasks.gob"
+ GOBDATA_FILENAME = "workstack.gob"
)
func GetGobdataPath() string {
diff --git a/ws/main.go b/ws/main.go
index 17cc7c0..2030fc5 100644
--- a/ws/main.go
+++ b/ws/main.go
@@ -3,9 +3,9 @@ package main
import (
"encoding/gob"
"errors"
+ "flag"
"fmt"
"os"
- "strconv"
"strings"
"time"
@@ -22,48 +22,149 @@ var (
// Persistent storage for Tasks
)
-// Search for arg in Args and return the index at which it was found
-// Returns -1 if it was not found
-func ArgInArgs(args []string, arg string) int {
- for i := 0; i < len(args); i++ {
- if args[i] == arg {
- return i
+func AddTask(tagName string, taskText string) {
+ if taskText == "" {
+ fmt.Println("Task text is required.")
+ os.Exit(1)
+ }
+
+ if tagName == "" {
+ Tasks = append(Tasks, ws.Task{Text: taskText})
+ return
+ }
+
+ tag := ws.Tag(tagName)
+
+ // Validate tag
+ found := false
+ for _, v := range Tags {
+ if tag == v {
+ found = true
+ break
}
}
- return -1
+ if !found {
+ fmt.Printf("No tag '%s' found.\n", tagName)
+ os.Exit(1)
+ }
+ Tasks = append(Tasks, ws.Task{Text: taskText, Tag: tag})
}
-// Parse a number argument from os.Args where pos is the argument's position
-// returns the int value for the string, if it fails the program will exit.
-// def will be used as default value when there is no argument at the position. If you do not want
-// to pass a default value, you can pass -1.
-func ParseNArg(pos, def, max int) int {
- if max == 0 {
- fmt.Printf("Number out of range: %d\n", 0)
- os.Exit(1)
+func DeleteTask(n int) {
+ Tasks = append(Tasks[:n-1], Tasks[n:]...)
+}
+
+// save, delete and append task
+func ProcrastinateTask(n int) {
+ t := Tasks[n-1]
+ Tasks = append(Tasks[:n-1], Tasks[n:]...)
+ Tasks = append(Tasks, t)
+}
+
+func TopTask(n int) {
+ var NewTasks []ws.Task
+
+ NewTasks = append(NewTasks, Tasks[n-1])
+ NewTasks = append(NewTasks, Tasks[:n-1]...)
+ if n < len(Tasks) {
+ NewTasks = append(NewTasks, Tasks[n:]...)
}
- if len(os.Args) == pos {
- if def == -1 {
- fmt.Println("Argument required: N")
- os.Exit(1)
+ Tasks = NewTasks
+}
+
+func DoneTask(n int) {
+ TasksDone = append(TasksDone, ws.TaskDone{
+ Task: Tasks[n-1],
+ Date: time.Now(),
+ })
+ Tasks = append(Tasks[:n-1], Tasks[n:]...)
+}
+
+func UndoneTask(n int) {
+ Tasks = append(Tasks, TasksDone[n-1].Task)
+ TasksDone = append(TasksDone[:n-1], TasksDone[n:]...)
+}
+
+func ListTasks(showDone bool) {
+ if len(Tasks) == 0 && len(TasksDone) == 0 {
+ fmt.Println("No tasks.")
+ return
+ }
+
+ if showDone && len(TasksDone) <= 0 {
+ showDone = false
+ }
+
+ if len(Tasks) > 0 {
+ if showDone {
+ fmt.Println("Active:")
+ }
+ for i, t := range Tasks {
+ fmt.Printf("% 2d. %s\n", i+1, t)
}
- return def
}
- n, err := strconv.Atoi(os.Args[pos])
- if errors.Is(err, strconv.ErrSyntax) {
- fmt.Printf("'%s' is not a number.\n", os.Args[pos])
- os.Exit(1)
- } else if err != nil {
- panic(err)
+ if showDone {
+ fmt.Println("Done:")
+ for i, t := range TasksDone {
+ fmt.Printf("% 2d. %s\n", i+1, t)
+ }
}
+}
- if n > max {
- fmt.Printf("Number out of range: %d\n", n)
- os.Exit(1)
+func LsTasks() {
+ if len(Tasks) == 0 {
+ fmt.Println("No tasks.")
+ return
+ }
+ for i := 0; i < len(Tasks) && i < ws.TASK_LIST_COUNT; i++ {
+ fmt.Printf("%2d. %s\n", i+1, Tasks[i])
}
- return n
+}
+
+func AddTag(tagName ws.Tag) {
+ for i := 0; i < len(Tags); i++ {
+ if tagName == Tags[i] {
+ fmt.Printf("Tag '%s' already exists.\n", tagName)
+ os.Exit(1)
+ }
+ }
+ Tags = append(Tags, tagName)
+}
+
+func DeleteTag(n int) {
+ Tags = append(Tags[:n-1], Tags[n:]...)
+}
+
+func ListTags() {
+ if len(Tags) == 0 {
+ fmt.Println("No tags.")
+ }
+ for i := 0; i < len(Tags); i++ {
+ fmt.Printf("%2d. %s\n", i+1, Tags[i])
+ }
+}
+
+func wsTagUsage() {
+ fmt.Println(`usage: ws tag <command>
+COMMANDS
+ add Add a new tag
+ del Delete a tag
+ list List tags`)
+}
+
+func wsUsage() {
+ fmt.Println(`usage: ws <command> [flag]
+COMMANDS
+ task Add a new task
+ done Mark an active task as done
+ undone Undo a done task
+ del Delete an active task
+ pc Procrastinate an active task
+ top Put a task to the top of the stack
+ ls Short list of active tasks
+ list List all active and done tasks`)
}
func main() {
@@ -100,172 +201,192 @@ func main() {
// When no arguments are provided display the first task.
if len(os.Args) == 1 {
- if len(Tasks) == 0 {
- fmt.Println("No tasks.")
- return
- }
- fmt.Printf("1. %s\n", Tasks[0])
+ LsTasks()
os.Exit(0)
}
switch os.Args[1] {
// Add a new task
- case "task":
- var tagName, taskText string
- var i, offset int
- // offset of 2 because we are at the second arg
- offset = 2
- i = ArgInArgs(os.Args[offset:], "-t")
-
- // tag argument was provided
- if i != -1 {
- if offset+i+1 >= len(os.Args) {
- fmt.Println("-t requires an argument.")
- os.Exit(1)
- }
- tagName = os.Args[offset+i+1]
- if tagName == "" {
- fmt.Println("-t requires an argument.")
- os.Exit(1)
- }
- // this would mean that -t <arg> are the first and last two arguments
- if len(os.Args) == offset+2 {
- fmt.Println("Task text is required.")
- os.Exit(1)
- }
- if i == 0 {
- // tag is at the start
- taskText = strings.Join(os.Args[offset+i+2:], " ")
- } else if i+4 == len(os.Args) {
- // tag is at the end
- taskText = strings.Join(os.Args[offset:i+2], " ")
- } else {
- // tag is in the middle
- taskText = strings.Join(append(os.Args[offset:i+2], os.Args[offset+i+2:]...), " ")
- }
- } else {
- taskText = strings.Join(os.Args[offset:], " ")
- }
+ case "add":
+ var tagName, taskName string
- // taskText can be provided as "" which is an argument and will pass previous validation
- // tests
- if taskText == "" {
- fmt.Println("Task text is required.")
- os.Exit(1)
- }
+ flagSet := flag.NewFlagSet("add", flag.ExitOnError)
+ flagSet.StringVar(&tagName, "t", "", "Set tag name")
+ flagSet.Parse(os.Args[2:])
- if tagName == "" {
- Tasks = append(Tasks, ws.Task{Text: taskText})
- break
- }
+ taskName = strings.Join(flagSet.Args(), " ")
- tag := ws.Tag(tagName)
+ AddTask(tagName, taskName)
+ ListTasks(false)
- // Validate tag
- found := false
- for _, v := range Tags {
- if tag == v {
- found = true
- break
- }
+ // Delete an active task
+ case "del":
+ var n int
+
+ flagSet := flag.NewFlagSet("del", flag.ExitOnError)
+ flagSet.IntVar(&n, "n", 1, "Set task number")
+ flagSet.Parse(os.Args[2:])
+
+ if len(Tasks) <= 0 {
+ fmt.Println("No tasks to delete.")
+ os.Exit(1)
}
- if !found {
- fmt.Printf("No tag '%s' found.\n", tagName)
+
+ if len(Tasks) < n {
+ fmt.Println("Task number out of range")
os.Exit(1)
}
- Tasks = append(Tasks, ws.Task{Text: taskText, Tag: tag})
- // Delete an active task
- case "del":
- n := ParseNArg(2, 1, len(Tasks))
- Tasks = append(Tasks[:n-1], Tasks[n:]...)
+ DeleteTask(n)
+ ListTasks(false)
// Mark an active task as done
case "done":
- n := ParseNArg(2, 1, len(Tasks))
- TasksDone = append(TasksDone, ws.TaskDone{
- Task: Tasks[n-1],
- Date: time.Now(),
- })
- Tasks = append(Tasks[:n-1], Tasks[n:]...)
+ var n int
+
+ flagSet := flag.NewFlagSet("done", flag.ExitOnError)
+ flagSet.IntVar(&n, "n", 1, "Set task number")
+ flagSet.Parse(os.Args[2:])
+
+ if len(Tasks) <= 0 {
+ fmt.Println("No tasks to be done.")
+ os.Exit(1)
+ }
+
+ if len(Tasks) < n {
+ fmt.Println("Task number out of range")
+ os.Exit(1)
+ }
+
+ DoneTask(n)
+ ListTasks(true)
// Undo a done task
case "undone":
- n := ParseNArg(2, 1, len(TasksDone))
- Tasks = append(Tasks, TasksDone[n-1].Task)
- TasksDone = append(TasksDone[:n-1], TasksDone[n:]...)
+ var n int
+
+ flagSet := flag.NewFlagSet("undone", flag.ExitOnError)
+ flagSet.IntVar(&n, "n", 1, "Set task number")
+ flagSet.Parse(os.Args[2:])
+
+ if len(TasksDone) <= 0 {
+ fmt.Println("No tasks to be undone.")
+ os.Exit(1)
+ }
+
+ if len(TasksDone) < n {
+ fmt.Println("Task number out of range")
+ os.Exit(1)
+ }
+
+ UndoneTask(n)
+ ListTasks(true)
// Procrastinate an active task
case "pc":
- n := ParseNArg(2, 1, len(Tasks))
+ var n int
+
+ flagSet := flag.NewFlagSet("procrastinate", flag.ExitOnError)
+ flagSet.IntVar(&n, "n", 1, "Set task number")
+ flagSet.Parse(os.Args[2:])
+
+ if len(Tasks) <= 0 {
+ fmt.Println("No tasks to procrastinate.")
+ os.Exit(1)
+ }
+
+ if len(Tasks) < n {
+ fmt.Println("Task number out of range")
+ os.Exit(1)
+ }
+
+ ProcrastinateTask(n)
+ ListTasks(false)
+
+ // Put a task at the top
+ case "top":
+ var n int
+
+ flagSet := flag.NewFlagSet("top", flag.ExitOnError)
+ flagSet.IntVar(&n, "n", 0, "Set task number")
+ flagSet.Parse(os.Args[2:])
+
+ if n == 0 {
+ fmt.Println("Task number is required.")
+ os.Exit(1)
+ }
+
+ if len(Tasks) <= 1 {
+ fmt.Println("No tasks to put to the top.")
+ os.Exit(1)
+ }
+
if n == 1 {
- Tasks = append(Tasks[n:], Tasks[n])
- break
+ fmt.Println("Task already at the top")
+ os.Exit(1)
+ }
+
+ if len(Tasks) < n {
+ fmt.Println("Task number out of range")
+ os.Exit(1)
}
- // save, delete and append task
- t := Tasks[n-1]
- Tasks = append(Tasks[:n-1], Tasks[n:]...)
- Tasks = append(Tasks, t)
+
+ TopTask(n)
+ ListTasks(false)
// Short list of active tasks
case "ls":
- if len(Tasks) == 0 {
- fmt.Println("No tasks.")
- break
- }
- for i := 0; i < len(Tasks) && i < ws.TASK_LIST_COUNT; i++ {
- fmt.Printf("%d. %s\n", i+1, Tasks[i])
- }
+ LsTasks()
// List all active and done tasks
case "list":
- if len(Tasks) > 0 {
- fmt.Println("Active:")
- for i, t := range Tasks {
- fmt.Printf("% 2d. %s\n", i+1, t)
- }
+ ListTasks(true)
+
+ // tag subcommand
+ case "tag":
+ if len(os.Args) <= 2 {
+ wsTagUsage()
+ return
}
- if len(TasksDone) > 0 {
- fmt.Println("Done:")
- for i, t := range TasksDone {
- fmt.Printf("% 2d. %s\n", i+1, t)
+
+ switch os.Args[2] {
+
+ // create a new tag
+ case "add":
+ tagName := ws.Tag(strings.Join(os.Args[3:], " "))
+ AddTag(tagName)
+ ListTags()
+
+ // delete a tag
+ case "del":
+ var n int
+
+ flagSet := flag.NewFlagSet("del", flag.ExitOnError)
+ flagSet.IntVar(&n, "n", 1, "Set task number")
+ flagSet.Parse(os.Args[3:])
+
+ if len(Tags) <= 0 {
+ fmt.Println("No tags to delete.")
+ os.Exit(1)
}
- }
- // create a new tag
- case "tag":
- tagName := ws.Tag(strings.Join(os.Args[2:], " "))
- for i := 0; i < len(Tags); i++ {
- if tagName == Tags[i] {
- fmt.Printf("Tag '%s' already exists.\n", tagName)
+ if len(Tags) < n {
+ fmt.Println("Tag number out of range")
os.Exit(1)
}
- }
- Tags = append(Tags, tagName)
- // delete a tag
- case "tagd":
- n := ParseNArg(2, 1, len(Tags))
- Tags = append(Tags[:n-1], Tags[n:]...)
+ DeleteTag(n)
+ ListTags()
- // list tags
- case "tagl":
- for i := 0; i < len(Tags); i++ {
- fmt.Printf("%2d. %s\n", i+1, Tags[i])
+ /* list tags */
+ case "list":
+ ListTags()
+ default:
+ wsTagUsage()
}
+
default:
- fmt.Println(`usage: ws <command>
-COMMANDS
- task Add a new task
- done Mark an active task as done
- undone Undo a done task
- del Delete an active task
- pc Procrastinate an active task
- ls Short list of active tasks
- list List all active and done tasks
- tag Add a new tag
- tagd Delete a tag
- tagl List tags`)
+ wsUsage()
}
// Save data to gobdata