/
task.go
276 lines (243 loc) · 7.75 KB
/
task.go
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package data
import (
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
"todogo/core"
)
// =========================================================================
// Implementation of the TaskID concept.
//
// The task index (or identifier) is an positive integer that identifies a task
// in its context.
// TaskID is the data type of a task index (Usage ID or General ID)
type TaskID uint64
// TaskIDArray is a list of TaskID
type TaskIDArray []TaskID
const (
// noIndex is used to specify that there is no array index (whatever the array is)
noIndex int = -1
// NoUID is used to specify that there is no task index (task identifier)
NoUID TaskID = 0
)
//
// The task indeces are used one the command lines to specify the target of
// actions, then we give here an implementation of the flag.Value interface for
// a list of task indeces.
//
func (taskID *TaskID) String() string {
return fmt.Sprintf("%d", *taskID)
}
// Set implement the flag.Value interface
func (taskID *TaskID) Set(value string) error {
index, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
(*taskID) = TaskID(index)
return nil
}
// String implement the flag.Value interface
func (il *TaskIDArray) String() string {
return fmt.Sprintf("%v", *il)
}
// Set implement the flag.Value interface
func (il *TaskIDArray) Set(value string) error {
sl := strings.Split(value, ",")
*il = make(TaskIDArray, len(sl))
for i := 0; i < len(sl); i++ {
index, err := strconv.ParseUint(sl[i], 10, 64)
if err != nil {
return err
}
(*il)[i] = TaskID(index)
}
return nil
}
// =========================================================================
// Implementation of the Task concept
// Task is the data structure for a single task
type Task struct {
UIndex TaskID // Usage Index (could be recycled)
GIndex TaskID // Global Index (invariant and unique)
Timestamp int64 // Date of the task (unix format)
Description string // Description of the Task
Status TaskStatus // Status of the task
OnBoard bool // True if the task is on board
NotePath string // Path to the note file (relative to the db root)
ParentID TaskID // UID of the parent task
}
// initGlobalIndex initialises the global index of this task.
// We create a global index (unique and invariant ever) by creating a hash integer
// from a string representation of the task and its timestamp. The string
// representation is a composition of the usage id, the timestamp and the
// description. The objective is to make it impossible ever to have two
// tasks with the same global index.
func (task *Task) initGlobalIndex() {
taskstr := fmt.Sprintf("%d [%d]: %s", task.UIndex, task.Timestamp, task.Description)
task.GIndex = TaskID(hashdate(taskstr, task.Timestamp))
}
// String returns a string representation of this task
func (task Task) String() string {
return task.OnelineString()
}
// OnelineString returns a string representation of this task on one signe line.
// This shouldbe used for a pretty presentation of task lists.
func (task Task) OnelineString() string {
dtlabel := datelabel(task.Timestamp)
template := "%2d [%s] %s : %s"
s := fmt.Sprintf(template, task.UIndex, dtlabel, task.Status.String(), task.Description)
return s
}
// JSONString returns a json string representation of this task
func (task Task) JSONString() string {
bytes, err := json.MarshalIndent(task, core.JSONPrefix, core.JSONIndent)
if err != nil {
panic(err)
}
return string(bytes)
}
// InfoString returns a string representation of the task attributes
func (task Task) InfoString() string {
return task.JSONString()
}
// CreateTestTask creates a dummy task for test purposes
func CreateTestTask(uindex TaskID, text string) Task {
var task = Task{
UIndex: uindex,
Description: text,
Timestamp: timestamp(),
Status: StatusTodo,
OnBoard: false,
}
task.initGlobalIndex()
return task
}
// =========================================================================
// Implementation of the collection of Tasks TaskArray. The TaskArray is the
// underlying data structure of a task journal. It should not be exposed output
// from the data package.
// TaskArray is the data structure for a list (array) of Tasks
type TaskArray []Task
// String implements the stringable interface for a TaskArray
func (tasks TaskArray) String() string {
s := ""
for i := 0; i < len(tasks); i++ {
s += fmt.Sprintf("%s\n", tasks[i].String())
}
return s
}
// Remove removes from the array the task of order index (index in the array)
func (tasks *TaskArray) remove(index int) error {
if index < 0 || index >= len(*tasks) {
return fmt.Errorf("ERR: index %d is out of range of tasks", index)
}
(*tasks)[index] = (*tasks)[len(*tasks)-1]
*tasks = (*tasks)[:len(*tasks)-1]
return nil
}
// Append adds the task to the array. Returns an error if a task with same uid exists
func (tasks *TaskArray) append(task Task) error {
ptask, err := tasks.getTask(task.UIndex)
if ptask != nil && err == nil {
return fmt.Errorf("ERR: a task with UID %d already exists", task.UIndex)
}
*tasks = append(*tasks, task)
return nil
}
func (tasks TaskArray) index(filter TaskFilter) int {
for i := 0; i < len(tasks); i++ {
if filter(tasks[i]) {
return i
}
}
return noIndex
}
func (tasks TaskArray) indeces(filter TaskFilter) []int {
results := make([]int, 0, len(tasks))
for i := 0; i < len(tasks); i++ {
if filter(tasks[i]) {
results = append(results, i)
}
}
return results
}
// IndexFromUID returns the array index of the task with the given UID
func (tasks TaskArray) indexFromUID(uindex TaskID) int {
filterUID := func(task Task) bool {
return task.UIndex == uindex
}
return tasks.index(filterUID)
}
// GetTask returns a pointer to the task of the sepcified UID
func (tasks *TaskArray) getTask(uindex TaskID) (*Task, error) {
idx := tasks.indexFromUID(uindex)
if idx == noIndex {
err := fmt.Errorf("The task of index %d does not exist", uindex)
return nil, err
}
return &(*tasks)[idx], nil
}
// GetTasksWithFilter returns an array of pointer to the tasks that satisfy the filter
func (tasks TaskArray) getTasksWithFilter(filter TaskFilter) []*Task {
results := make([]*Task, 0, len(tasks))
for i := 0; i < len(tasks); i++ {
if filter(tasks[i]) {
results = append(results, &tasks[i])
}
}
return results
}
func (tasks TaskArray) byUID(i int, j int) bool {
return tasks[i].UIndex < tasks[j].UIndex
}
func (tasks TaskArray) byGID(i int, j int) bool {
return tasks[i].GIndex < tasks[j].GIndex
}
func (tasks TaskArray) byTimestamp(i int, j int) bool {
return tasks[i].Timestamp < tasks[j].Timestamp
}
func (tasks *TaskArray) sortByUID() {
sort.Slice(*tasks, tasks.byUID)
}
func (tasks *TaskArray) sortByGID() {
sort.Slice(*tasks, tasks.byGID)
}
func (tasks *TaskArray) sortByTimestamp() {
sort.Slice(*tasks, tasks.byTimestamp)
}
// getFreeUID() returns the first free UID of this tasks list.
func (tasks TaskArray) getFreeUID() TaskID {
// The free index is determined with the hypothesis that the indeces array
// is a list of consecutive integer indeces. If the difference between two
// consecutif indeces is not 1, then it means that there is at least a free
// index (the index that follows the smallest index of the difference).
tasks.sortByUID()
if len(tasks) == 0 {
return 1
}
var freeUID TaskID = 1
for i := 0; i < len(tasks); i++ {
if tasks[i].UIndex-freeUID > 0 {
return freeUID
}
freeUID = tasks[i].UIndex + 1
}
return freeUID
}
// ancestor returns true if parentId is an ancestor of childID
func (tasks TaskArray) ancestor(childID TaskID, parentID TaskID) bool {
if childID == parentID {
return false
}
task, _ := tasks.getTask(childID)
if task.ParentID == NoUID {
return false
}
if task.ParentID == parentID {
return true
}
return tasks.ancestor(task.ParentID, parentID)
}