package main import ( "encoding/gob" "errors" "flag" "fmt" "os" "strings" "time" ws "git.spacehb.net/ws.git" ) var ( // Stack of active tasks Tags []ws.Tag // Active tasks TasksDo []ws.Task // Completed tasks TasksDone []ws.TaskDone // Persistent storage for Tasks ) func AddTask(tagName string, taskText string) { if taskText == "" { fmt.Println("Task text is required.") os.Exit(1) } if tagName == "" { TasksDo = append(TasksDo, ws.Task{Text: taskText}) return } tag := ws.Tag(tagName) // Validate tag found := false for _, v := range Tags { if tag == v { found = true break } } if !found { fmt.Printf("No tag '%s' found.\n", tagName) os.Exit(1) } TasksDo = append(TasksDo, ws.Task{Text: taskText, Tag: tag}) } func DeleteTask(shouldDeleteDone bool, n int) { if shouldDeleteDone { TasksDone = append(TasksDone[:n-1], TasksDone[n:]...) } else { TasksDo = append(TasksDo[:n-1], TasksDo[n:]...) } } // save, delete and append task func ProcrastinateTask(n int) { t := TasksDo[n-1] TasksDo = append(TasksDo[:n-1], TasksDo[n:]...) TasksDo = append(TasksDo, t) } func TopTask(n int) { var NewTasks []ws.Task NewTasks = append(NewTasks, TasksDo[n-1]) NewTasks = append(NewTasks, TasksDo[:n-1]...) if n < len(TasksDo) { NewTasks = append(NewTasks, TasksDo[n:]...) } TasksDo = NewTasks } func DoneTask(n int) { TasksDone = append(TasksDone, ws.TaskDone{ Task: TasksDo[n-1], Date: time.Now(), }) TasksDo = append(TasksDo[:n-1], TasksDo[n:]...) } func UndoTask(n int) { TasksDo = append(TasksDo, TasksDone[n-1].Task) TasksDone = append(TasksDone[:n-1], TasksDone[n:]...) } func DescribeTask(shouldDescribeDone bool, n int, description string) { var task *ws.Task if shouldDescribeDone { task = &TasksDone[n-1].Task } else { task = &TasksDo[n-1] } task.Description = description } func EditTask(shouldEditDone bool, n int, newText string, newTag ws.Tag) { var task *ws.Task if shouldEditDone { task = &TasksDone[n-1].Task } else { task = &TasksDo[n-1] } if newTag != "" { found := false for _, v := range Tags { if newTag == v { found = true break } } if !found { fmt.Printf("No tag '%s' found.\n", newTag) os.Exit(1) } task.Tag = newTag } if newText != "" { task.Text = newText } } func ListTasks(showDone bool) { if len(TasksDo) == 0 && len(TasksDone) == 0 { fmt.Println("No tasks.") return } if showDone && len(TasksDone) <= 0 { showDone = false } if len(TasksDo) > 0 { if showDone { fmt.Println("Active:") } for i, t := range TasksDo { fmt.Printf("% 2d. %s\n", i+1, t) } } if showDone { fmt.Println("Done:") for i, t := range TasksDone { fmt.Printf("% 2d. %s\n", i+1, t) } } } 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) } // Delete tag number n // Only deletes if the tag is not used by any task func DeleteTag(n int) { // Check if the tag is currently used for i, v := range TasksDo { if v.Tag == Tags[n-1] { fmt.Printf("Tag is used by task: %d. %s\n", i+1, v) os.Exit(1) } } for i, v := range TasksDone { if v.Task.Tag == Tags[n-1] { fmt.Printf("Tag is used by done task: %d. %s\n", i+1, v.Task) os.Exit(1) } } 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 COMMANDS add Add a new tag del Delete a tag list List tags`) } func wsUsage() { fmt.Println(`usage: ws COMMANDS add Add a new task done Mark an active task as done undo Undo a done task desc Describe a task with more words edit Edit a task's text or tag del Delete an active task pc Procrastinate an active task top Put a task to the top of the stack list List all active and done tasks tag tag commands`) } func main() { var ( dec *gob.Decoder f *os.File err error gobdataPath string = ws.GetGobdataPath() ) // Open/Create gob data file if not exist { f, err = os.Open(gobdataPath) if errors.Is(err, os.ErrNotExist) { // Do nothing } else if err != nil { panic(err) } else { dec = gob.NewDecoder(f) err = dec.Decode(&TasksDo) if err != nil { panic(err) } err = dec.Decode(&TasksDone) if err != nil { panic(err) } err = dec.Decode(&Tags) if err != nil { panic(err) } } } // When no arguments are provided display the first task. if len(os.Args) == 1 { ListTasks(false) os.Exit(0) } switch os.Args[1] { // Add a new task case "add": var tagName, taskName string flagSet := flag.NewFlagSet("add", flag.ExitOnError) flagSet.StringVar(&tagName, "t", "", "Set tag name") flagSet.Parse(os.Args[2:]) taskName = strings.Join(flagSet.Args(), " ") AddTask(tagName, taskName) ListTasks(false) // Delete an active task case "del": var n int var shouldDeleteDone bool flagSet := flag.NewFlagSet("del", flag.ExitOnError) flagSet.IntVar(&n, "n", 1, "Set task number") flagSet.BoolVar(&shouldDeleteDone, "d", false, "Should delete a done task") flagSet.Parse(os.Args[2:]) if !shouldDeleteDone { if len(TasksDo) == 0 { fmt.Println("No tasks to edit.") os.Exit(1) } if len(TasksDo) < n { fmt.Println("Task number out of range.") os.Exit(1) } } else { if len(TasksDone) == 0 { fmt.Println("No done tasks to edit.") os.Exit(1) } if len(TasksDone) < n { fmt.Println("Done task number out of range.") os.Exit(1) } } DeleteTask(shouldDeleteDone, n) ListTasks(shouldDeleteDone) // Mark an active task as done case "done": var n int flagSet := flag.NewFlagSet("done", flag.ExitOnError) flagSet.IntVar(&n, "n", 1, "Set task number") flagSet.Parse(os.Args[2:]) if len(TasksDo) <= 0 { fmt.Println("No tasks to be done.") os.Exit(1) } if len(TasksDo) < n { fmt.Println("Task number out of range") os.Exit(1) } DoneTask(n) ListTasks(true) case "desc": var n int var shouldDescribeDone bool flagSet := flag.NewFlagSet("desc", flag.ExitOnError) flagSet.BoolVar(&shouldDescribeDone, "d", false, "Should describe a done task") flagSet.IntVar(&n, "n", 1, "Set task number") flagSet.Parse(os.Args[2:]) description := strings.Join(flagSet.Args(), " ") if !shouldDescribeDone { if len(TasksDo) == 0 { fmt.Println("No tasks to describe.") os.Exit(1) } if len(TasksDo) < n { fmt.Println("Task number out of range.") os.Exit(1) } } else { if len(TasksDone) == 0 { fmt.Println("No done tasks to describe.") os.Exit(1) } if len(TasksDone) < n { fmt.Println("Done task number out of range.") os.Exit(1) } } DescribeTask(shouldDescribeDone, n, description) ListTasks(shouldDescribeDone) case "edit": var n int var shouldEditDone bool var newTag, newText string flagSet := flag.NewFlagSet("edit", flag.ExitOnError) flagSet.IntVar(&n, "n", 1, "Set task number") flagSet.StringVar(&newTag, "t", "", "Set tag name") flagSet.BoolVar(&shouldEditDone, "d", false, "Should edit a done task") flagSet.Parse(os.Args[2:]) if !shouldEditDone { if len(TasksDo) == 0 { fmt.Println("No tasks to edit.") os.Exit(1) } if len(TasksDo) < n { fmt.Println("Task number out of range.") os.Exit(1) } } else { if len(TasksDone) == 0 { fmt.Println("No done tasks to edit.") os.Exit(1) } if len(TasksDone) < n { fmt.Println("Done task number out of range.") os.Exit(1) } } newText = strings.Join(flagSet.Args(), " ") EditTask(shouldEditDone, n, newText, ws.Tag(newTag)) ListTasks(shouldEditDone) // Undo a done task case "undo": var n int flagSet := flag.NewFlagSet("undo", 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) } UndoTask(n) ListTasks(true) // Procrastinate an active task case "pc": var n int flagSet := flag.NewFlagSet("procrastinate", flag.ExitOnError) flagSet.IntVar(&n, "n", 1, "Set task number") flagSet.Parse(os.Args[2:]) if len(TasksDo) <= 0 { fmt.Println("No tasks to procrastinate.") os.Exit(1) } if len(TasksDo) < 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(TasksDo) <= 1 { fmt.Println("No tasks to put to the top.") os.Exit(1) } if n == 1 { fmt.Println("Task already at the top") os.Exit(1) } if len(TasksDo) < n { fmt.Println("Task number out of range") os.Exit(1) } TopTask(n) ListTasks(false) // List all active and done tasks case "list": ListTasks(true) // tag subcommand case "tag": if len(os.Args) <= 2 { wsTagUsage() return } 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) } if len(Tags) < n { fmt.Println("Tag number out of range") os.Exit(1) } DeleteTag(n) ListTags() /* list tags */ case "list": ListTags() default: wsTagUsage() } default: wsUsage() } // Save data to gobdata f, err = os.Create(gobdataPath) if err != nil { panic(err) } enc := gob.NewEncoder(f) err = enc.Encode(TasksDo) if err != nil { panic(err) } err = enc.Encode(TasksDone) if err != nil { panic(err) } err = enc.Encode(Tags) if err != nil { panic(err) } }