diff options
-rw-r--r-- | go.mod | 3 | ||||
-rw-r--r-- | main.go | 237 |
2 files changed, 240 insertions, 0 deletions
@@ -0,0 +1,3 @@ +module ws + +go 1.23.1 @@ -0,0 +1,237 @@ +package main + +// Workstack or ws for short is a program that manages To-Do's in a stack-based fashion. It tries +// to guide your focus to your three most important tasks such that you do not get distracted by +// other tasks. +// Every task added starts as inactive "[ ]" and can be marked as done by changing the status to "[x]". +// When the programs exits Tasks are saved to a tasks.gob file, this will truncate (os.Create) the +// existing file. + +// TODO's +// - tags +// - command to add/delete tags so that we can programatically add them +// - parameter to add to specify a tag could be a format in the string or -flag +// - edit: replace a Task's text +// - import: read multiple lines from stdin and import them as taks + +import ( + "bufio" + "encoding/gob" + "errors" + "fmt" + "io" + "os" + "strconv" + "time" +) + +var ( + // Stack of active tasks + Tasks []string + // Completede tasks + TasksDone []TaskDone + // Persistent storage for Tasks + gobdata string = "tasks.gob" + DateLayout string = "15:04:05 02/01/2006" +) + +const ( + TASK_LIST_COUNT = 5 +) + +type TaskDone struct { + Text string + Date time.Time +} + +func (t TaskDone) String() string { + return fmt.Sprintf("(%s) %s", t.Date.Format(DateLayout), t.Text) +} + +func usage() { + fmt.Println(`usage: ws [add|done [N]|pc [N]|ls|list|reset] +add Adds a task to the end of the stack +done [N] Mark a task as done +undone [N] Mask a task as undone +pc [N] Procrastinate task a task to end of stack +del [N] Delete an active task +ls List active tasks +list List all tasks +reset Recreate the tasks.gob file + +tasks.gob: Where the tasks are saved, the default path is $HOME/sync/share/tasks.gob +N: When N is not provided the default is 1`) + os.Exit(1) +} + +// Helper function for parsing an int argument to a command, takes as parameter the current argument +// count and parses os.Args to return an int. +// If there is no argument provided returns 0 else it returns the argument provided on the command +// line. +func NArg(c int) int { + var n int + var err error + if len(os.Args) < c { // no arguments + n = 0 + } else { + n, err = strconv.Atoi(os.Args[2]) + if err != nil { + panic(err) + } + if !(len(Tasks) > n-1 && n >= 1) { + fmt.Println("N out of range.") + os.Exit(3) + } + n -= 1 + } + return n +} + +func main() { + // create tasks.gob file + var dec *gob.Decoder + var f *os.File + var err error + + // Open/Create gob data file if not exist + { + p := os.Getenv("HOME") + if p == "" { + panic("HOME var not set.") + } + gobdata = p + "/sync/share/" + gobdata + + f, err = os.Open(gobdata) + if errors.Is(err, os.ErrNotExist) { + // Do nothing + } else if err != nil { + panic(err) + } else { + dec = gob.NewDecoder(f) + err = dec.Decode(&TasksDone) + if err != nil { + panic(err) + } + err = dec.Decode(&Tasks) + if err != nil { + panic(err) + } + } + } + + if len(os.Args) == 1 { + // default + if len(Tasks) == 0 { + fmt.Println("No tasks.") + return + } + fmt.Println("1.", Tasks[0]) + os.Exit(0) + } + switch os.Args[1] { + case "add": + // prompt + fmt.Fprint(os.Stderr, ">") + + // read console input + reader := bufio.NewReader(os.Stdin) + t, err := reader.ReadString('\n') + if err == io.EOF { + fmt.Fprint(os.Stderr, "\n") + os.Exit(0) + } + if err != nil { + panic(err) + } + if len(t) == 1 { + os.Exit(0) + } + + // remove newline + t = t[:len(t)-1] + + Tasks = append(Tasks, t) + + case "undone": + + case "del": + n := NArg(3) + if len(Tasks) == 1 { + Tasks = []string{} + return + } + Tasks = append(Tasks[:n], Tasks[n+1:]...) + + case "done": + n := NArg(3) + TasksDone = append(TasksDone, TaskDone{ + Text: Tasks[n], + Date: time.Now(), + }) + if len(Tasks) == 1 { + Tasks = []string{} + return + } + Tasks = append(Tasks[:n], Tasks[n+1:]...) + + case "ls": + if len(Tasks) == 0 { + fmt.Println("No tasks.") + break + } + for i := 0; i < len(Tasks) && i < TASK_LIST_COUNT; i++ { + fmt.Printf("%d. %s\n", i+1, Tasks[i]) + } + + case "list": + if len(Tasks) > 0 { + fmt.Println("Active:") + for i, t := range Tasks { + fmt.Printf("% 2d. %s\n", i+1, t) + } + } + if len(TasksDone) > 0 { + fmt.Println("Done:") + for i, t := range TasksDone { + fmt.Printf("% 2d. %s\n", i+1, t) + } + } + // fmt.Println(Tasks, TasksDone) + + case "reset": + Tasks = []string{} + TasksDone = []TaskDone{} + + case "pc": //procrastinate + // procrastinate top task by default + if len(os.Args) < 3 { + Tasks = append(Tasks[1:], Tasks[0]) + break + } + + var n int = NArg(3) + + t := Tasks[n] + + Tasks = append(Tasks[:n], Tasks[n+1:]...) + Tasks = append(Tasks, t) + + default: + usage() + } + + // Save data to gobdata + f, err = os.Create(gobdata) + if err != nil { + panic(err) + } + enc := gob.NewEncoder(f) + err = enc.Encode(TasksDone) + if err != nil { + panic(err) + } + err = enc.Encode(Tasks) + if err != nil { + panic(err) + } +} |