1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
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)
}
}
|