Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
97a991fa26 | ||
|
e5c37a1106 | ||
|
dbb967d319 | ||
|
2e04f1d233 | ||
|
e29d17784e | ||
|
a0e8e1801d | ||
|
c1f16b56a5 | ||
|
96cc4ce27c | ||
|
e8603bbc85 | ||
|
4af9e2abe8 | ||
|
48dacccdf1 |
20
README.md
20
README.md
@@ -35,10 +35,30 @@ go-todotxt requires Go1.1 or higher.
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tasklist now contains a slice of Tasks
|
||||||
fmt.Printf("Task 2, todo: %v\n", tasklist[1].Todo)
|
fmt.Printf("Task 2, todo: %v\n", tasklist[1].Todo)
|
||||||
fmt.Printf("Task 3: %v\n", tasklist[2])
|
fmt.Printf("Task 3: %v\n", tasklist[2])
|
||||||
fmt.Printf("Task 4, has priority: %v\n\n", tasklist[3].HasPriority())
|
fmt.Printf("Task 4, has priority: %v\n\n", tasklist[3].HasPriority())
|
||||||
fmt.Print(tasklist)
|
fmt.Print(tasklist)
|
||||||
|
|
||||||
|
// Filter list to get only completed tasks
|
||||||
|
completedList := tasklist.Filter(func(t Task) bool {
|
||||||
|
return t.Completed
|
||||||
|
})
|
||||||
|
fmt.Print(completedList)
|
||||||
|
|
||||||
|
// Add a new empty Task to tasklist
|
||||||
|
task := NewTask()
|
||||||
|
tasklist.AddTask(&task)
|
||||||
|
|
||||||
|
// Or a parsed Task from a string
|
||||||
|
parsedTask, _ := ParseTask("x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12")
|
||||||
|
tasklist.AddTask(parsed)
|
||||||
|
|
||||||
|
// Update an existing task
|
||||||
|
task, _ := tasklist.GetTask(2) // Task pointer
|
||||||
|
task.Todo = "Do something different.."
|
||||||
|
tasklist.WriteToFilename("todo.txt")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
48
example_test.go
Normal file
48
example_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package todotxt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleLoadFromFilename() {
|
||||||
|
if tasklist, err := LoadFromFilename("todo.txt"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
} else {
|
||||||
|
fmt.Print(tasklist) // String representation of TaskList works as expected.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (A) Call Mom @Phone +Family
|
||||||
|
// (A) Schedule annual checkup +Health
|
||||||
|
// (B) Outline chapter 5 @Computer +Novel
|
||||||
|
// (C) Add cover sheets @Office +TPSReports
|
||||||
|
// Plan backyard herb garden @Home
|
||||||
|
// Pick up milk @GroceryStore
|
||||||
|
// Research self-publishing services @Computer +Novel
|
||||||
|
// x Download Todo.txt mobile app @Phone
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleTaskList_LoadFromFilename() {
|
||||||
|
var tasklist TaskList
|
||||||
|
|
||||||
|
// This will overwrite whatever was in the tasklist before.
|
||||||
|
// Irrelevant here since the list is still empty.
|
||||||
|
if err := tasklist.LoadFromFilename("todo.txt"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(tasklist[0].Todo) // Text part of first task (Call Mom)
|
||||||
|
fmt.Println(tasklist[2].Contexts) // Slice of contexts from third task ([Computer])
|
||||||
|
fmt.Println(tasklist[3].Priority) // Priority of fourth task (C)
|
||||||
|
fmt.Println(tasklist[7].Completed) // Completed flag of eigth task (true)
|
||||||
|
// Output:
|
||||||
|
// Call Mom
|
||||||
|
// [Computer]
|
||||||
|
// C
|
||||||
|
// true
|
||||||
|
}
|
24
sort.go
24
sort.go
@@ -35,7 +35,7 @@ func (tasklist *TaskList) Sort(sortFlag int) error {
|
|||||||
case SORT_DUE_DATE_ASC, SORT_DUE_DATE_DESC:
|
case SORT_DUE_DATE_ASC, SORT_DUE_DATE_DESC:
|
||||||
tasklist.sortByDueDate(sortFlag)
|
tasklist.sortByDueDate(sortFlag)
|
||||||
default:
|
default:
|
||||||
return errors.New("Unrecognized sort option")
|
return errors.New("unrecognized sort option")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -68,19 +68,17 @@ func (tasklist *TaskList) sortBy(by func(t1, t2 *Task) bool) *TaskList {
|
|||||||
|
|
||||||
func (tasklist *TaskList) sortByPriority(order int) *TaskList {
|
func (tasklist *TaskList) sortByPriority(order int) *TaskList {
|
||||||
tasklist.sortBy(func(t1, t2 *Task) bool {
|
tasklist.sortBy(func(t1, t2 *Task) bool {
|
||||||
if order == SORT_PRIORITY_DESC { // DESC
|
if order == SORT_PRIORITY_ASC { // ASC
|
||||||
if t1.HasPriority() && t2.HasPriority() {
|
|
||||||
return t1.Priority > t2.Priority
|
|
||||||
} else {
|
|
||||||
return !t1.HasPriority()
|
|
||||||
}
|
|
||||||
} else { // ASC
|
|
||||||
if t1.HasPriority() && t2.HasPriority() {
|
if t1.HasPriority() && t2.HasPriority() {
|
||||||
return t1.Priority < t2.Priority
|
return t1.Priority < t2.Priority
|
||||||
} else {
|
}
|
||||||
return t1.HasPriority()
|
return t1.HasPriority()
|
||||||
}
|
}
|
||||||
|
// DESC
|
||||||
|
if t1.HasPriority() && t2.HasPriority() {
|
||||||
|
return t1.Priority > t2.Priority
|
||||||
}
|
}
|
||||||
|
return !t1.HasPriority()
|
||||||
})
|
})
|
||||||
return tasklist
|
return tasklist
|
||||||
}
|
}
|
||||||
@@ -89,17 +87,15 @@ func sortByDate(asc bool, hasDate1, hasDate2 bool, date1, date2 time.Time) bool
|
|||||||
if asc { // ASC
|
if asc { // ASC
|
||||||
if hasDate1 && hasDate2 {
|
if hasDate1 && hasDate2 {
|
||||||
return date1.Before(date2)
|
return date1.Before(date2)
|
||||||
} else {
|
}
|
||||||
return hasDate2
|
return hasDate2
|
||||||
}
|
}
|
||||||
} else { // DESC
|
// DESC
|
||||||
if hasDate1 && hasDate2 {
|
if hasDate1 && hasDate2 {
|
||||||
return date1.After(date2)
|
return date1.After(date2)
|
||||||
} else {
|
}
|
||||||
return !hasDate2
|
return !hasDate2
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tasklist *TaskList) sortByCreatedDate(order int) *TaskList {
|
func (tasklist *TaskList) sortByCreatedDate(order int) *TaskList {
|
||||||
tasklist.sortBy(func(t1, t2 *Task) bool {
|
tasklist.sortBy(func(t1, t2 *Task) bool {
|
||||||
|
46
sort_test.go
46
sort_test.go
@@ -1,3 +1,7 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package todotxt
|
package todotxt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -14,7 +18,9 @@ func TestTaskSortByPriority(t *testing.T) {
|
|||||||
|
|
||||||
testTasklist = testTasklist[taskId : taskId+6]
|
testTasklist = testTasklist[taskId : taskId+6]
|
||||||
|
|
||||||
testTasklist.Sort(SORT_PRIORITY_ASC)
|
if err := testTasklist.Sort(SORT_PRIORITY_ASC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "(A) 2012-01-30 Call Mom @Call @Phone +Family"
|
testExpected = "(A) 2012-01-30 Call Mom @Call @Phone +Family"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -52,7 +58,9 @@ func TestTaskSortByPriority(t *testing.T) {
|
|||||||
t.Errorf("Expected Task[6] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected Task[6] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
|
|
||||||
testTasklist.Sort(SORT_PRIORITY_DESC)
|
if err := testTasklist.Sort(SORT_PRIORITY_DESC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "x 2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05"
|
testExpected = "x 2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -97,7 +105,9 @@ func TestTaskSortByCreatedDate(t *testing.T) {
|
|||||||
|
|
||||||
testTasklist = testTasklist[taskId : taskId+5]
|
testTasklist = testTasklist[taskId : taskId+5]
|
||||||
|
|
||||||
testTasklist.Sort(SORT_CREATED_DATE_ASC)
|
if err := testTasklist.Sort(SORT_CREATED_DATE_ASC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "x 2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05"
|
testExpected = "x 2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -129,7 +139,9 @@ func TestTaskSortByCreatedDate(t *testing.T) {
|
|||||||
t.Errorf("Expected Task[5] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected Task[5] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
|
|
||||||
testTasklist.Sort(SORT_CREATED_DATE_DESC)
|
if err := testTasklist.Sort(SORT_CREATED_DATE_DESC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12"
|
testExpected = "x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -168,7 +180,9 @@ func TestTaskSortByCompletedDate(t *testing.T) {
|
|||||||
|
|
||||||
testTasklist = testTasklist[taskId : taskId+6]
|
testTasklist = testTasklist[taskId : taskId+6]
|
||||||
|
|
||||||
testTasklist.Sort(SORT_COMPLETED_DATE_ASC)
|
if err := testTasklist.Sort(SORT_COMPLETED_DATE_ASC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "x Download Todo.txt mobile app @Phone"
|
testExpected = "x Download Todo.txt mobile app @Phone"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -206,7 +220,9 @@ func TestTaskSortByCompletedDate(t *testing.T) {
|
|||||||
t.Errorf("Expected Task[6] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected Task[6] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
|
|
||||||
testTasklist.Sort(SORT_COMPLETED_DATE_DESC)
|
if err := testTasklist.Sort(SORT_COMPLETED_DATE_DESC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "x 2014-01-04 2014-01-01 Create some more golang library test cases @Go +go-todotxt"
|
testExpected = "x 2014-01-04 2014-01-01 Create some more golang library test cases @Go +go-todotxt"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -251,7 +267,9 @@ func TestTaskSortByDueDate(t *testing.T) {
|
|||||||
|
|
||||||
testTasklist = testTasklist[taskId : taskId+4]
|
testTasklist = testTasklist[taskId : taskId+4]
|
||||||
|
|
||||||
testTasklist.Sort(SORT_DUE_DATE_ASC)
|
if err := testTasklist.Sort(SORT_DUE_DATE_ASC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt"
|
testExpected = "x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -277,7 +295,9 @@ func TestTaskSortByDueDate(t *testing.T) {
|
|||||||
t.Errorf("Expected Task[4] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected Task[4] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
|
|
||||||
testTasklist.Sort(SORT_DUE_DATE_DESC)
|
if err := testTasklist.Sort(SORT_DUE_DATE_DESC); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
testExpected = "(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17"
|
testExpected = "(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17"
|
||||||
testGot = testTasklist[0].Task()
|
testGot = testTasklist[0].Task()
|
||||||
@@ -303,3 +323,13 @@ func TestTaskSortByDueDate(t *testing.T) {
|
|||||||
t.Errorf("Expected Task[4] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected Task[4] after Sort() to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskSortError(t *testing.T) {
|
||||||
|
testTasklist.LoadFromFilename(testInputSort)
|
||||||
|
|
||||||
|
if err := testTasklist.Sort(123); err == nil {
|
||||||
|
t.Errorf("Expected Sort() to fail because of unrecognized sort option, but it didn't!")
|
||||||
|
} else if err.Error() != "unrecognized sort option" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
149
task.go
149
task.go
@@ -6,12 +6,29 @@ package todotxt
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DateLayout is used for formatting time.Time into todo.txt date format and vice-versa.
|
||||||
|
DateLayout = "2006-01-02"
|
||||||
|
|
||||||
|
priorityRx = regexp.MustCompile(`^(x|x \d{4}-\d{2}-\d{2}|)\s*\(([A-Z])\)\s+`) // Match priority: '(A) ...' or 'x (A) ...' or 'x 2012-12-12 (A) ...'
|
||||||
|
// Match created date: '(A) 2012-12-12 ...' or 'x 2012-12-12 (A) 2012-12-12 ...' or 'x (A) 2012-12-12 ...'or 'x 2012-12-12 2012-12-12 ...' or '2012-12-12 ...'
|
||||||
|
createdDateRx = regexp.MustCompile(`^(\([A-Z]\)|x \d{4}-\d{2}-\d{2} \([A-Z]\)|x \([A-Z]\)|x \d{4}-\d{2}-\d{2}|)\s*(\d{4}-\d{2}-\d{2})\s+`)
|
||||||
|
completedRx = regexp.MustCompile(`^x\s+`) // Match completed: 'x ...'
|
||||||
|
completedDateRx = regexp.MustCompile(`^x\s*(\d{4}-\d{2}-\d{2})\s+`) // Match completed date: 'x 2012-12-12 ...'
|
||||||
|
addonTagRx = regexp.MustCompile(`(^|\s+)([\w-]+):(\S+)`) // Match additional tags date: '... due:2012-12-12 ...'
|
||||||
|
contextRx = regexp.MustCompile(`(^|\s+)@(\S+)`) // Match contexts: '@Context ...' or '... @Context ...'
|
||||||
|
projectRx = regexp.MustCompile(`(^|\s+)\+(\S+)`) // Match projects: '+Project...' or '... +Project ...')
|
||||||
|
)
|
||||||
|
|
||||||
// Task represents a todo.txt task entry.
|
// Task represents a todo.txt task entry.
|
||||||
type Task struct {
|
type Task struct {
|
||||||
|
Id int // Internal task id.
|
||||||
Original string // Original raw task text.
|
Original string // Original raw task text.
|
||||||
Todo string // Todo part of task text.
|
Todo string // Todo part of task text.
|
||||||
Priority string
|
Priority string
|
||||||
@@ -53,12 +70,14 @@ func (task Task) String() string {
|
|||||||
text += task.Todo
|
text += task.Todo
|
||||||
|
|
||||||
if len(task.Contexts) > 0 {
|
if len(task.Contexts) > 0 {
|
||||||
|
sort.Strings(task.Contexts)
|
||||||
for _, context := range task.Contexts {
|
for _, context := range task.Contexts {
|
||||||
text += fmt.Sprintf(" @%s", context)
|
text += fmt.Sprintf(" @%s", context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(task.Projects) > 0 {
|
if len(task.Projects) > 0 {
|
||||||
|
sort.Strings(task.Projects)
|
||||||
for _, project := range task.Projects {
|
for _, project := range task.Projects {
|
||||||
text += fmt.Sprintf(" +%s", project)
|
text += fmt.Sprintf(" +%s", project)
|
||||||
}
|
}
|
||||||
@@ -67,7 +86,7 @@ func (task Task) String() string {
|
|||||||
if len(task.AdditionalTags) > 0 {
|
if len(task.AdditionalTags) > 0 {
|
||||||
// Sort map alphabetically by keys
|
// Sort map alphabetically by keys
|
||||||
keys := make([]string, 0, len(task.AdditionalTags))
|
keys := make([]string, 0, len(task.AdditionalTags))
|
||||||
for key, _ := range task.AdditionalTags {
|
for key := range task.AdditionalTags {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
@@ -83,6 +102,108 @@ func (task Task) String() string {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTask creates a new empty Task with default values. (CreatedDate is set to Now())
|
||||||
|
func NewTask() Task {
|
||||||
|
task := Task{}
|
||||||
|
task.CreatedDate = time.Now()
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTask parses the input text string into a Task struct.
|
||||||
|
func ParseTask(text string) (*Task, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
task := Task{}
|
||||||
|
task.Original = strings.Trim(text, "\t\n\r ")
|
||||||
|
task.Todo = task.Original
|
||||||
|
|
||||||
|
// Check for completed
|
||||||
|
if completedRx.MatchString(task.Original) {
|
||||||
|
task.Completed = true
|
||||||
|
// Check for completed date
|
||||||
|
if completedDateRx.MatchString(task.Original) {
|
||||||
|
if date, err := time.Parse(DateLayout, completedDateRx.FindStringSubmatch(task.Original)[1]); err == nil {
|
||||||
|
task.CompletedDate = date
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from Todo text
|
||||||
|
task.Todo = completedDateRx.ReplaceAllString(task.Todo, "") // Strip CompletedDate first, otherwise it wouldn't match anymore (^x date...)
|
||||||
|
task.Todo = completedRx.ReplaceAllString(task.Todo, "") // Strip 'x '
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for priority
|
||||||
|
if priorityRx.MatchString(task.Original) {
|
||||||
|
task.Priority = priorityRx.FindStringSubmatch(task.Original)[2]
|
||||||
|
task.Todo = priorityRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for created date
|
||||||
|
if createdDateRx.MatchString(task.Original) {
|
||||||
|
if date, err := time.Parse(DateLayout, createdDateRx.FindStringSubmatch(task.Original)[2]); err == nil {
|
||||||
|
task.CreatedDate = date
|
||||||
|
task.Todo = createdDateRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function for collecting projects/contexts as slices from text
|
||||||
|
getSlice := func(rx *regexp.Regexp) []string {
|
||||||
|
matches := rx.FindAllStringSubmatch(task.Original, -1)
|
||||||
|
slice := make([]string, 0, len(matches))
|
||||||
|
seen := make(map[string]bool, len(matches))
|
||||||
|
for _, match := range matches {
|
||||||
|
word := strings.Trim(match[2], "\t\n\r ")
|
||||||
|
if _, found := seen[word]; !found {
|
||||||
|
slice = append(slice, word)
|
||||||
|
seen[word] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(slice)
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for contexts
|
||||||
|
if contextRx.MatchString(task.Original) {
|
||||||
|
task.Contexts = getSlice(contextRx)
|
||||||
|
task.Todo = contextRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for projects
|
||||||
|
if projectRx.MatchString(task.Original) {
|
||||||
|
task.Projects = getSlice(projectRx)
|
||||||
|
task.Todo = projectRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for additional tags
|
||||||
|
if addonTagRx.MatchString(task.Original) {
|
||||||
|
matches := addonTagRx.FindAllStringSubmatch(task.Original, -1)
|
||||||
|
tags := make(map[string]string, len(matches))
|
||||||
|
for _, match := range matches {
|
||||||
|
key, value := match[2], match[3]
|
||||||
|
if key == "due" { // due date is a known addon tag, it has its own struct field
|
||||||
|
if date, err := time.Parse(DateLayout, value); err == nil {
|
||||||
|
task.DueDate = date
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if key != "" && value != "" {
|
||||||
|
tags[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.AdditionalTags = tags
|
||||||
|
task.Todo = addonTagRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim any remaining whitespaces from Todo text
|
||||||
|
task.Todo = strings.Trim(task.Todo, "\t\n\r\f ")
|
||||||
|
|
||||||
|
return &task, err
|
||||||
|
}
|
||||||
|
|
||||||
// Task returns a complete task string in todo.txt format.
|
// Task returns a complete task string in todo.txt format.
|
||||||
// See *Task.String() for further information.
|
// See *Task.String() for further information.
|
||||||
func (task *Task) Task() string {
|
func (task *Task) Task() string {
|
||||||
@@ -106,7 +227,25 @@ func (task *Task) HasDueDate() bool {
|
|||||||
|
|
||||||
// HasCompletedDate returns true if the task has a completed date.
|
// HasCompletedDate returns true if the task has a completed date.
|
||||||
func (task *Task) HasCompletedDate() bool {
|
func (task *Task) HasCompletedDate() bool {
|
||||||
return !task.CompletedDate.IsZero()
|
return !task.CompletedDate.IsZero() && task.Completed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete sets Task.Completed to 'true' if the task was not already completed.
|
||||||
|
// Also sets Task.CompletedDate to time.Now()
|
||||||
|
func (task *Task) Complete() {
|
||||||
|
if !task.Completed {
|
||||||
|
task.Completed = true
|
||||||
|
task.CompletedDate = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen sets Task.Completed to 'false' if the task was completed.
|
||||||
|
// Also resets Task.CompletedDate.
|
||||||
|
func (task *Task) Reopen() {
|
||||||
|
if task.Completed {
|
||||||
|
task.Completed = false
|
||||||
|
task.CompletedDate = time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC) // time.IsZero() value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOverdue returns true if due date is in the past.
|
// IsOverdue returns true if due date is in the past.
|
||||||
@@ -116,9 +255,8 @@ func (task *Task) HasCompletedDate() bool {
|
|||||||
func (task *Task) IsOverdue() bool {
|
func (task *Task) IsOverdue() bool {
|
||||||
if task.HasDueDate() {
|
if task.HasDueDate() {
|
||||||
return task.DueDate.Before(time.Now())
|
return task.DueDate.Before(time.Now())
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due returns the duration passed since due date, or until due date from now.
|
// Due returns the duration passed since due date, or until due date from now.
|
||||||
@@ -129,7 +267,6 @@ func (task *Task) IsOverdue() bool {
|
|||||||
func (task *Task) Due() time.Duration {
|
func (task *Task) Due() time.Duration {
|
||||||
if task.IsOverdue() {
|
if task.IsOverdue() {
|
||||||
return time.Now().Sub(task.DueDate)
|
return time.Now().Sub(task.DueDate)
|
||||||
} else {
|
}
|
||||||
return task.DueDate.Sub(time.Now())
|
return task.DueDate.Sub(time.Now())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
327
task_test.go
327
task_test.go
@@ -1,3 +1,7 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package todotxt
|
package todotxt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -9,6 +13,184 @@ var (
|
|||||||
testInputTask = "testdata/task_todo.txt"
|
testInputTask = "testdata/task_todo.txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNewTask(t *testing.T) {
|
||||||
|
task := NewTask()
|
||||||
|
|
||||||
|
testExpected = 0
|
||||||
|
testGot = task.Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to have default Id [%d], but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = ""
|
||||||
|
testGot = task.Original
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to be empty, but got [%s]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = ""
|
||||||
|
testGot = task.Todo
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to be empty, but got [%s]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = false
|
||||||
|
testGot = task.HasPriority()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to have no priority, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 0
|
||||||
|
testGot = len(task.Projects)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to have %d projects, but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 0
|
||||||
|
testGot = len(task.Contexts)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to have %d contexts, but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 0
|
||||||
|
testGot = len(task.AdditionalTags)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to have %d additional tags, but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = true
|
||||||
|
testGot = task.HasCreatedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to have a created date, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = false
|
||||||
|
testGot = task.HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to not have a completed date, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = false
|
||||||
|
testGot = task.HasDueDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to not have a due date, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = false
|
||||||
|
testGot = task.Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected new Task to not be completed, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTask(t *testing.T) {
|
||||||
|
task, err := ParseTask("x (C) 2014-01-01 @Go due:2014-01-12 Create golang library documentation +go-todotxt ")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = "x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12"
|
||||||
|
testGot = task.Task()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to be [%s], but got [%s]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 0
|
||||||
|
testGot = task.Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have default Id [%d], but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = "x (C) 2014-01-01 @Go due:2014-01-12 Create golang library documentation +go-todotxt"
|
||||||
|
testGot = task.Original
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to be [%s], but got [%s]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = "Create golang library documentation"
|
||||||
|
testGot = task.Todo
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to be [%s], but got [%s]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = true
|
||||||
|
testGot = task.HasPriority()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have no priority, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = "C"
|
||||||
|
testGot = task.Priority
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have priority [%v], but got [%v]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 1
|
||||||
|
testGot = len(task.Projects)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have %d projects, but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 1
|
||||||
|
testGot = len(task.Contexts)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have %d contexts, but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 0
|
||||||
|
testGot = len(task.AdditionalTags)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have %d additional tags, but got [%d]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = true
|
||||||
|
testGot = task.HasCreatedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have a created date, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = false
|
||||||
|
testGot = task.HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to not have a completed date, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = true
|
||||||
|
testGot = task.HasDueDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to have a due date, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = true
|
||||||
|
testGot = task.Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task to be completed, but got [%v]", testGot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskId(t *testing.T) {
|
||||||
|
testTasklist.LoadFromFilename(testInputTask)
|
||||||
|
|
||||||
|
taskId := 1
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != taskId {
|
||||||
|
t.Errorf("Expected Task[%d] to have Id [%d], but got [%d]", taskId, taskId, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId = 5
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != taskId {
|
||||||
|
t.Errorf("Expected Task[%d] to have Id [%d], but got [%d]", taskId, taskId, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId = 27
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != taskId {
|
||||||
|
t.Errorf("Expected Task[%d] to have Id [%d], but got [%d]", taskId, taskId, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
func TestTaskString(t *testing.T) {
|
func TestTaskString(t *testing.T) {
|
||||||
testTasklist.LoadFromFilename(testInputTask)
|
testTasklist.LoadFromFilename(testInputTask)
|
||||||
taskId := 1
|
taskId := 1
|
||||||
@@ -263,21 +445,21 @@ func TestTaskCompleted(t *testing.T) {
|
|||||||
testExpected = true
|
testExpected = true
|
||||||
testGot = testTasklist[taskId-1].Completed
|
testGot = testTasklist[taskId-1].Completed
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected Task[%d] not to be completed, but got '%v'", taskId, testGot)
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
}
|
}
|
||||||
taskId++
|
taskId++
|
||||||
|
|
||||||
testExpected = true
|
testExpected = true
|
||||||
testGot = testTasklist[taskId-1].Completed
|
testGot = testTasklist[taskId-1].Completed
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected Task[%d] not to be completed, but got '%v'", taskId, testGot)
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
}
|
}
|
||||||
taskId++
|
taskId++
|
||||||
|
|
||||||
testExpected = true
|
testExpected = true
|
||||||
testGot = testTasklist[taskId-1].Completed
|
testGot = testTasklist[taskId-1].Completed
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected Task[%d] not to be completed, but got '%v'", taskId, testGot)
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
}
|
}
|
||||||
taskId++
|
taskId++
|
||||||
|
|
||||||
@@ -379,6 +561,145 @@ func TestTaskIsOverdue(t *testing.T) {
|
|||||||
t.Errorf("Expected Task[%d] to be due since 72 hours, but got '%v'", taskId, testGot)
|
t.Errorf("Expected Task[%d] to be due since 72 hours, but got '%v'", taskId, testGot)
|
||||||
}
|
}
|
||||||
taskId++
|
taskId++
|
||||||
|
|
||||||
|
testGot = testTasklist[taskId-1].IsOverdue()
|
||||||
|
if testGot.(bool) {
|
||||||
|
t.Errorf("Expected Task[%d] not to be overdue, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskComplete(t *testing.T) {
|
||||||
|
testTasklist.LoadFromFilename(testInputTask)
|
||||||
|
taskId := 44
|
||||||
|
|
||||||
|
// first 4 tasks should all match the same tests
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
testExpected = false
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] not to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] not to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testTasklist[taskId-1].Complete()
|
||||||
|
testExpected = true
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testExpected = time.Now().Format(DateLayout)
|
||||||
|
testGot = testTasklist[taskId-1].CompletedDate.Format(DateLayout)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date of '%v', but got '%v'", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = true
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testTasklist[taskId-1].Complete()
|
||||||
|
testGot = testTasklist[taskId-1].Completed // should be unchanged
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate() // should be unchanged
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testExpected = "2012-01-01" // should be unchanged
|
||||||
|
testGot = testTasklist[taskId-1].CompletedDate.Format(DateLayout)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date of '%v', but got '%v'", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskReopen(t *testing.T) {
|
||||||
|
testTasklist.LoadFromFilename(testInputTask)
|
||||||
|
taskId := 49
|
||||||
|
|
||||||
|
// the first 2 tasks should match the same tests
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
testExpected = true
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testExpected = false
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testTasklist[taskId-1].Reopen()
|
||||||
|
testExpected = false
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to not be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to not have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
|
// the next 3 tasks should all match the same tests
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
testExpected = true
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testTasklist[taskId-1].Reopen()
|
||||||
|
testExpected = false
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to not be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to not have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = false
|
||||||
|
testGot = testTasklist[taskId-1].Completed
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testTasklist[taskId-1].Reopen()
|
||||||
|
testGot = testTasklist[taskId-1].Completed // should be unchanged
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
testGot = testTasklist[taskId-1].HasCompletedDate() // should be unchanged
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to have a completed date, but got '%v'", taskId, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareSlices(list1 []string, list2 []string) bool {
|
func compareSlices(list1 []string, list2 []string) bool {
|
||||||
|
2
testdata/ouput_todo.txt
vendored
2
testdata/ouput_todo.txt
vendored
@@ -1,6 +1,6 @@
|
|||||||
2013-02-22 Pick up milk @GroceryStore
|
2013-02-22 Pick up milk @GroceryStore
|
||||||
x Download Todo.txt mobile app @Phone
|
x Download Todo.txt mobile app @Phone
|
||||||
(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17
|
(C) 2013-12-01 Go home! @Computer +Novel Level:5 private:false due:2011-11-11
|
||||||
x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||||
x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
||||||
(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17
|
(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17
|
||||||
|
19
testdata/task_todo.txt
vendored
19
testdata/task_todo.txt
vendored
@@ -57,5 +57,22 @@ x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todot
|
|||||||
|
|
||||||
# Overdue test cases
|
# Overdue test cases
|
||||||
x 2014-01-04 (B) 2013-12-30 Create golang library @Go +go-todotxt due:2014-01-02
|
x 2014-01-04 (B) 2013-12-30 Create golang library @Go +go-todotxt due:2014-01-02
|
||||||
(B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer due:2017-07-17 Level:5
|
(B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer due:2027-07-17 Level:5
|
||||||
Research self-publishing services +Novel +Novel +Novel due:2014-01-01
|
Research self-publishing services +Novel +Novel +Novel due:2014-01-01
|
||||||
|
x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
||||||
|
|
||||||
|
# Complete test cases
|
||||||
|
Create golang library @Go +go-todotxt due:2014-01-05
|
||||||
|
(A) @Phone Call Mom @Call +Family
|
||||||
|
2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05
|
||||||
|
(B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||||
|
x 2012-01-01 More Go!
|
||||||
|
|
||||||
|
# Reopen test cases
|
||||||
|
x Download Todo.txt mobile app @Phone
|
||||||
|
x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12
|
||||||
|
x 2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05
|
||||||
|
x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||||
|
x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
||||||
|
(B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||||
|
|
||||||
|
6
testdata/tasklist_completedDate_error.txt
vendored
Normal file
6
testdata/tasklist_completedDate_error.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12
|
||||||
|
x Download Todo.txt mobile app @Phone
|
||||||
|
x 2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05
|
||||||
|
x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12
|
||||||
|
x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||||
|
x 2014-25-04 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
5
testdata/tasklist_createdDate_error.txt
vendored
Normal file
5
testdata/tasklist_createdDate_error.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
2013-02-22 Pick up milk @GroceryStore
|
||||||
|
x Download Todo.txt mobile app @Phone
|
||||||
|
(B) 2013-13-01 private:false Outline chapter 5 +Novel @Computer Level:5 due:2014-02-17
|
||||||
|
x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||||
|
x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
5
testdata/tasklist_dueDate_error.txt
vendored
Normal file
5
testdata/tasklist_dueDate_error.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer Level:5 due:2014-02-17
|
||||||
|
x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||||
|
x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
||||||
|
(B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer Level:5 due:2014-02-32
|
||||||
|
x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12
|
2
testdata/tasklist_scanner_error.txt
vendored
Normal file
2
testdata/tasklist_scanner_error.txt
vendored
Normal file
File diff suppressed because one or more lines are too long
215
todotxt.go
215
todotxt.go
@@ -10,39 +10,32 @@ package todotxt
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TaskList represents a list of todo.txt task entries.
|
// TaskList represents a list of todo.txt task entries.
|
||||||
// It is usually loaded from a whole todo.txt file.
|
// It is usually loaded from a whole todo.txt file.
|
||||||
type TaskList []Task
|
type TaskList []Task
|
||||||
|
|
||||||
// IgnoreComments can be set to 'false', in order to revert to more standard todo.txt behaviour.
|
// IgnoreComments can be set to 'false', in order to revert to a more standard todo.txt behaviour.
|
||||||
// The todo.txt format does not define comments.
|
// The todo.txt format does not define comments.
|
||||||
var (
|
var (
|
||||||
// Used for formatting time.Time into todo.txt date format and vice-versa.
|
// IgnoreComments is used to switch ignoring of comments (lines starting with "#").
|
||||||
DateLayout = "2006-01-02"
|
// If this is set to 'false', then lines starting with "#" will be parsed as tasks.
|
||||||
// Ignores comments (Lines/Text starting with "#").
|
|
||||||
IgnoreComments = true
|
IgnoreComments = true
|
||||||
|
|
||||||
// unexported vars
|
|
||||||
priorityRx = regexp.MustCompile(`^(x|x \d{4}-\d{2}-\d{2}|)\s*\(([A-Z])\)\s+`) // Match priority: '(A) ...' or 'x (A) ...' or 'x 2012-12-12 (A) ...'
|
|
||||||
// Match created date: '(A) 2012-12-12 ...' or 'x 2012-12-12 (A) 2012-12-12 ...' or 'x (A) 2012-12-12 ...'or 'x 2012-12-12 2012-12-12 ...' or '2012-12-12 ...'
|
|
||||||
createdDateRx = regexp.MustCompile(`^(\([A-Z]\)|x \d{4}-\d{2}-\d{2} \([A-Z]\)|x \([A-Z]\)|x \d{4}-\d{2}-\d{2}|)\s*(\d{4}-\d{2}-\d{2})\s+`)
|
|
||||||
completedRx = regexp.MustCompile(`^x\s+`) // Match completed: 'x ...'
|
|
||||||
completedDateRx = regexp.MustCompile(`^x\s*(\d{4}-\d{2}-\d{2})\s+`) // Match completed date: 'x 2012-12-12 ...'
|
|
||||||
addonTagRx = regexp.MustCompile(`(^|\s+)([\w-]+):(\S+)`) // Match additional tags date: '... due:2012-12-12 ...'
|
|
||||||
contextRx = regexp.MustCompile(`(^|\s+)@(\S+)`) // Match contexts: '@Context ...' or '... @Context ...'
|
|
||||||
projectRx = regexp.MustCompile(`(^|\s+)\+(\S+)`) // Match projects: '+Project...' or '... +Project ...'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a complete tasklist string in todo.txt format.
|
// NewTaskList creates a new empty TaskList.
|
||||||
|
func NewTaskList() TaskList {
|
||||||
|
tasklist := TaskList{}
|
||||||
|
return tasklist
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a complete list of tasks in todo.txt format.
|
||||||
func (tasklist TaskList) String() (text string) {
|
func (tasklist TaskList) String() (text string) {
|
||||||
for _, task := range tasklist {
|
for _, task := range tasklist {
|
||||||
text += fmt.Sprintf("%s\n", task.String())
|
text += fmt.Sprintf("%s\n", task.String())
|
||||||
@@ -50,6 +43,84 @@ func (tasklist TaskList) String() (text string) {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddTask appends a Task to the current TaskList and takes care to set the Task.Id correctly, modifying the Task by the given pointer!
|
||||||
|
func (tasklist *TaskList) AddTask(task *Task) {
|
||||||
|
task.Id = 0
|
||||||
|
for _, t := range *tasklist {
|
||||||
|
if t.Id > task.Id {
|
||||||
|
task.Id = t.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.Id += 1
|
||||||
|
|
||||||
|
*tasklist = append(*tasklist, *task)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTask returns a Task by given task 'id' from the TaskList. The returned Task pointer can be used to update the Task inside the TaskList.
|
||||||
|
// Returns an error if Task could not be found.
|
||||||
|
func (tasklist *TaskList) GetTask(id int) (*Task, error) {
|
||||||
|
for i := range *tasklist {
|
||||||
|
if ([]Task(*tasklist))[i].Id == id {
|
||||||
|
return &([]Task(*tasklist))[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("task not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTaskById removes any Task with given Task 'id' from the TaskList.
|
||||||
|
// Returns an error if no Task was removed.
|
||||||
|
func (tasklist *TaskList) RemoveTaskById(id int) error {
|
||||||
|
var newList TaskList
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, t := range *tasklist {
|
||||||
|
if t.Id != id {
|
||||||
|
newList = append(newList, t)
|
||||||
|
} else {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return errors.New("task not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*tasklist = newList
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTask removes any Task from the TaskList with the same String representation as the given Task.
|
||||||
|
// Returns an error if no Task was removed.
|
||||||
|
func (tasklist *TaskList) RemoveTask(task Task) error {
|
||||||
|
var newList TaskList
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, t := range *tasklist {
|
||||||
|
if t.String() != task.String() {
|
||||||
|
newList = append(newList, t)
|
||||||
|
} else {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return errors.New("task not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*tasklist = newList
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter filters the current TaskList for the given predicate (a function that takes a task as input and returns a bool),
|
||||||
|
// and returns a new TaskList. The original TaskList is not modified.
|
||||||
|
func (tasklist *TaskList) Filter(predicate func(Task) bool) *TaskList {
|
||||||
|
var newList TaskList
|
||||||
|
for _, t := range *tasklist {
|
||||||
|
if predicate(t) {
|
||||||
|
newList = append(newList, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &newList
|
||||||
|
}
|
||||||
|
|
||||||
// LoadFromFile loads a TaskList from *os.File.
|
// LoadFromFile loads a TaskList from *os.File.
|
||||||
//
|
//
|
||||||
// Using *os.File instead of a filename allows to also use os.Stdin.
|
// Using *os.File instead of a filename allows to also use os.Stdin.
|
||||||
@@ -58,102 +129,24 @@ func (tasklist TaskList) String() (text string) {
|
|||||||
func (tasklist *TaskList) LoadFromFile(file *os.File) error {
|
func (tasklist *TaskList) LoadFromFile(file *os.File) error {
|
||||||
*tasklist = []Task{} // Empty tasklist
|
*tasklist = []Task{} // Empty tasklist
|
||||||
|
|
||||||
|
taskId := 1
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
task := Task{}
|
text := strings.Trim(scanner.Text(), "\t\n\r ") // Read line
|
||||||
task.Original = strings.Trim(scanner.Text(), "\t\n\r ") // Read line
|
|
||||||
task.Todo = task.Original
|
|
||||||
|
|
||||||
// Ignore blank or comment lines
|
// Ignore blank or comment lines
|
||||||
if task.Todo == "" || (IgnoreComments && strings.HasPrefix(task.Todo, "#")) {
|
if text == "" || (IgnoreComments && strings.HasPrefix(text, "#")) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for completed
|
task, err := ParseTask(text)
|
||||||
if completedRx.MatchString(task.Original) {
|
if err != nil {
|
||||||
task.Completed = true
|
|
||||||
// Check for completed date
|
|
||||||
if completedDateRx.MatchString(task.Original) {
|
|
||||||
if date, err := time.Parse(DateLayout, completedDateRx.FindStringSubmatch(task.Original)[1]); err != nil {
|
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
task.CompletedDate = date
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
task.Id = taskId
|
||||||
|
|
||||||
// Remove from Todo text
|
*tasklist = append(*tasklist, *task)
|
||||||
task.Todo = completedDateRx.ReplaceAllString(task.Todo, "") // Strip CompletedDate first, otherwise it wouldn't match anymore (^x date...)
|
taskId++
|
||||||
task.Todo = completedRx.ReplaceAllString(task.Todo, "") // Strip 'x '
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for priority
|
|
||||||
if priorityRx.MatchString(task.Original) {
|
|
||||||
task.Priority = priorityRx.FindStringSubmatch(task.Original)[2]
|
|
||||||
task.Todo = priorityRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for created date
|
|
||||||
if createdDateRx.MatchString(task.Original) {
|
|
||||||
if date, err := time.Parse(DateLayout, createdDateRx.FindStringSubmatch(task.Original)[2]); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
task.CreatedDate = date
|
|
||||||
task.Todo = createdDateRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// function for collecting projects/contexts as slices from text
|
|
||||||
getSlice := func(rx *regexp.Regexp) []string {
|
|
||||||
matches := rx.FindAllStringSubmatch(task.Original, -1)
|
|
||||||
slice := make([]string, 0, len(matches))
|
|
||||||
seen := make(map[string]bool, len(matches))
|
|
||||||
for _, match := range matches {
|
|
||||||
word := strings.Trim(match[2], "\t\n\r ")
|
|
||||||
if _, found := seen[word]; !found {
|
|
||||||
slice = append(slice, word)
|
|
||||||
seen[word] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(slice)
|
|
||||||
return slice
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for contexts
|
|
||||||
if contextRx.MatchString(task.Original) {
|
|
||||||
task.Contexts = getSlice(contextRx)
|
|
||||||
task.Todo = contextRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for projects
|
|
||||||
if projectRx.MatchString(task.Original) {
|
|
||||||
task.Projects = getSlice(projectRx)
|
|
||||||
task.Todo = projectRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for additional tags
|
|
||||||
if addonTagRx.MatchString(task.Original) {
|
|
||||||
matches := addonTagRx.FindAllStringSubmatch(task.Original, -1)
|
|
||||||
tags := make(map[string]string, len(matches))
|
|
||||||
for _, match := range matches {
|
|
||||||
key, value := match[2], match[3]
|
|
||||||
if key == "due" { // due date is a known addon tag, it has its own struct field
|
|
||||||
if date, err := time.Parse(DateLayout, value); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
task.DueDate = date
|
|
||||||
}
|
|
||||||
} else if key != "" && value != "" {
|
|
||||||
tags[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task.AdditionalTags = tags
|
|
||||||
task.Todo = addonTagRx.ReplaceAllString(task.Todo, "") // Remove from Todo text
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim any remaining whitespaces from Todo text
|
|
||||||
task.Todo = strings.Trim(task.Todo, "\t\n\r\f ")
|
|
||||||
|
|
||||||
*tasklist = append(*tasklist, task)
|
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -187,7 +180,7 @@ func (tasklist *TaskList) LoadFromFilename(filename string) error {
|
|||||||
|
|
||||||
// WriteToFilename writes a TaskList to the specified file (most likely called "todo.txt").
|
// WriteToFilename writes a TaskList to the specified file (most likely called "todo.txt").
|
||||||
func (tasklist *TaskList) WriteToFilename(filename string) error {
|
func (tasklist *TaskList) WriteToFilename(filename string) error {
|
||||||
return ioutil.WriteFile(filename, []byte(tasklist.String()), 0644)
|
return ioutil.WriteFile(filename, []byte(tasklist.String()), 0640)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFromFile loads and returns a TaskList from *os.File.
|
// LoadFromFile loads and returns a TaskList from *os.File.
|
||||||
@@ -195,8 +188,10 @@ func (tasklist *TaskList) WriteToFilename(filename string) error {
|
|||||||
// Using *os.File instead of a filename allows to also use os.Stdin.
|
// Using *os.File instead of a filename allows to also use os.Stdin.
|
||||||
func LoadFromFile(file *os.File) (TaskList, error) {
|
func LoadFromFile(file *os.File) (TaskList, error) {
|
||||||
tasklist := TaskList{}
|
tasklist := TaskList{}
|
||||||
err := tasklist.LoadFromFile(file)
|
if err := tasklist.LoadFromFile(file); err != nil {
|
||||||
return tasklist, err
|
return nil, err
|
||||||
|
}
|
||||||
|
return tasklist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToFile writes a TaskList to *os.File.
|
// WriteToFile writes a TaskList to *os.File.
|
||||||
@@ -209,8 +204,10 @@ func WriteToFile(tasklist *TaskList, file *os.File) error {
|
|||||||
// LoadFromFilename loads and returns a TaskList from a file (most likely called "todo.txt").
|
// LoadFromFilename loads and returns a TaskList from a file (most likely called "todo.txt").
|
||||||
func LoadFromFilename(filename string) (TaskList, error) {
|
func LoadFromFilename(filename string) (TaskList, error) {
|
||||||
tasklist := TaskList{}
|
tasklist := TaskList{}
|
||||||
err := tasklist.LoadFromFilename(filename)
|
if err := tasklist.LoadFromFilename(filename); err != nil {
|
||||||
return tasklist, err
|
return nil, err
|
||||||
|
}
|
||||||
|
return tasklist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToFilename writes a TaskList to the specified file (most likely called "todo.txt").
|
// WriteToFilename writes a TaskList to the specified file (most likely called "todo.txt").
|
||||||
|
352
todotxt_test.go
352
todotxt_test.go
@@ -8,10 +8,15 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testInputTasklist = "testdata/tasklist_todo.txt"
|
testInputTasklist = "testdata/tasklist_todo.txt"
|
||||||
|
testInputTasklistCreatedDateError = "testdata/tasklist_createdDate_error.txt"
|
||||||
|
testInputTasklistDueDateError = "testdata/tasklist_dueDate_error.txt"
|
||||||
|
testInputTasklistCompletedDateError = "testdata/tasklist_completedDate_error.txt"
|
||||||
|
testInputTasklistScannerError = "testdata/tasklist_scanner_error.txt"
|
||||||
testOutput = "testdata/ouput_todo.txt"
|
testOutput = "testdata/ouput_todo.txt"
|
||||||
testExpectedOutput = "testdata/expected_todo.txt"
|
testExpectedOutput = "testdata/expected_todo.txt"
|
||||||
testTasklist TaskList
|
testTasklist TaskList
|
||||||
@@ -33,12 +38,16 @@ func TestLoadFromFile(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testExpected := string(data)
|
testExpected = string(data)
|
||||||
testGot := testTasklist.String()
|
testGot = testTasklist.String()
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if testTasklist, err := LoadFromFile(nil); testTasklist != nil || err == nil {
|
||||||
|
t.Errorf("Expected LoadFromFile to fail, but got TaskList back: [%s]", testTasklist)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadFromFilename(t *testing.T) {
|
func TestLoadFromFilename(t *testing.T) {
|
||||||
@@ -49,12 +58,16 @@ func TestLoadFromFilename(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testExpected := string(data)
|
testExpected = string(data)
|
||||||
testGot := testTasklist.String()
|
testGot = testTasklist.String()
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if testTasklist, err := LoadFromFilename("some_file_that_does_not_exists.txt"); testTasklist != nil || err == nil {
|
||||||
|
t.Errorf("Expected LoadFromFilename to fail, but got TaskList back: [%s]", testTasklist)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteFile(t *testing.T) {
|
func TestWriteFile(t *testing.T) {
|
||||||
@@ -92,8 +105,8 @@ func TestWriteFile(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testExpected := string(data)
|
testExpected = string(data)
|
||||||
testGot := testTasklist.String()
|
testGot = testTasklist.String()
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
@@ -134,8 +147,8 @@ func TestTaskListWriteFile(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testExpected := string(data)
|
testExpected = string(data)
|
||||||
testGot := testTasklist.String()
|
testGot = testTasklist.String()
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
@@ -159,8 +172,8 @@ func TestWriteFilename(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testExpected := string(data)
|
testExpected = string(data)
|
||||||
testGot := testTasklist.String()
|
testGot = testTasklist.String()
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
@@ -184,19 +197,326 @@ func TestTaskListWriteFilename(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testExpected := string(data)
|
testExpected = string(data)
|
||||||
testGot := testTasklist.String()
|
testGot = testTasklist.String()
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskListCount(t *testing.T) {
|
func TestNewTaskList(t *testing.T) {
|
||||||
testTasklist.LoadFromFilename(testInputTasklist)
|
testTasklist := NewTaskList()
|
||||||
|
|
||||||
testExpected := 63
|
testExpected = 0
|
||||||
testGot := len(testTasklist)
|
testGot = len(testTasklist)
|
||||||
if testGot != testExpected {
|
if testGot != testExpected {
|
||||||
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskListCount(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpected = 63
|
||||||
|
testGot = len(testTasklist)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListAddTask(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new empty task
|
||||||
|
task := NewTask()
|
||||||
|
testTasklist.AddTask(&task)
|
||||||
|
|
||||||
|
testExpected = 64
|
||||||
|
testGot = len(testTasklist)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId := 64
|
||||||
|
testExpected = time.Now().Format(DateLayout) + " " // tasks created by NewTask() have their created date set
|
||||||
|
testGot = testTasklist[taskId-1].String()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
testExpected = 64
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%d], but got [%d]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
|
||||||
|
// add parsed task
|
||||||
|
parsed, err := ParseTask("x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testTasklist.AddTask(parsed)
|
||||||
|
|
||||||
|
testExpected = "x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12"
|
||||||
|
testGot = testTasklist[taskId-1].String()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
testExpected = 65
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%d], but got [%d]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
|
||||||
|
// add selfmade task
|
||||||
|
createdDate := time.Now()
|
||||||
|
testTasklist.AddTask(&Task{
|
||||||
|
CreatedDate: createdDate,
|
||||||
|
Todo: "Go shopping..",
|
||||||
|
Contexts: []string{"GroceryStore"},
|
||||||
|
})
|
||||||
|
|
||||||
|
testExpected = createdDate.Format(DateLayout) + " Go shopping.. @GroceryStore"
|
||||||
|
testGot = testTasklist[taskId-1].String()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
testExpected = 66
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%d], but got [%d]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
|
||||||
|
// add task with explicit Id, AddTask() should ignore this!
|
||||||
|
testTasklist.AddTask(&Task{
|
||||||
|
Id: 101,
|
||||||
|
})
|
||||||
|
|
||||||
|
testExpected = 67
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%d], but got [%d]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListGetTask(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId := 3
|
||||||
|
task, err := testTasklist.GetTask(taskId)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testExpected = "(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17"
|
||||||
|
testGot = task.String()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
testExpected = 3
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%d], but got [%d]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
taskId++
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListUpdateTask(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId := 3
|
||||||
|
task, err := testTasklist.GetTask(taskId)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testExpected = "(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17"
|
||||||
|
testGot = task.String()
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
testExpected = 3
|
||||||
|
testGot = testTasklist[taskId-1].Id
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected Task[%d] to be [%d], but got [%d]", taskId, testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Priority = "C"
|
||||||
|
task.Todo = "Go home!"
|
||||||
|
date, err := time.Parse(DateLayout, "2011-11-11")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
task.DueDate = date
|
||||||
|
testGot := task
|
||||||
|
|
||||||
|
os.Remove(testOutput)
|
||||||
|
if err := testTasklist.WriteToFilename(testOutput); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := testTasklist.LoadFromFilename(testOutput); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testExpected, err := testTasklist.GetTask(taskId)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if testGot.Task() != testExpected.Task() {
|
||||||
|
t.Errorf("Expected Task to be [%v]\n, but got [%v]", testExpected, testGot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListRemoveTaskById(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId := 10
|
||||||
|
if err := testTasklist.RemoveTaskById(taskId); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testExpected = 62
|
||||||
|
testGot = len(testTasklist)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
task, err := testTasklist.GetTask(taskId)
|
||||||
|
if err == nil || task != nil {
|
||||||
|
t.Errorf("Expected no Task to be found anymore, but got %v", task)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId = 27
|
||||||
|
if err := testTasklist.RemoveTaskById(taskId); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testExpected = 61
|
||||||
|
testGot = len(testTasklist)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
task, err = testTasklist.GetTask(taskId)
|
||||||
|
if err == nil || task != nil {
|
||||||
|
t.Errorf("Expected no Task to be found anymore, but got %v", task)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId = 99
|
||||||
|
if err := testTasklist.RemoveTaskById(taskId); err == nil {
|
||||||
|
t.Errorf("Expected no Task to be found for removal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListRemoveTask(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId := 52 // Is "unique" in tasklist
|
||||||
|
task, err := testTasklist.GetTask(taskId)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testTasklist.RemoveTask(*task); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testExpected = 62
|
||||||
|
testGot = len(testTasklist)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
task, err = testTasklist.GetTask(taskId)
|
||||||
|
if err == nil || task != nil {
|
||||||
|
t.Errorf("Expected no Task to be found anymore, but got %v", task)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId = 2 // Exists 3 times in tasklist
|
||||||
|
task, err = testTasklist.GetTask(taskId)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testTasklist.RemoveTask(*task); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testExpected = 59
|
||||||
|
testGot = len(testTasklist)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
task, err = testTasklist.GetTask(taskId)
|
||||||
|
if err == nil || task != nil {
|
||||||
|
t.Errorf("Expected no Task to be found anymore, but got %v", task)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testTasklist.RemoveTask(NewTask()); err == nil {
|
||||||
|
t.Errorf("Expected no Task to be found for removal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListFilter(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter list to get only completed tasks
|
||||||
|
completedList := testTasklist.Filter(func(t Task) bool { return t.Completed })
|
||||||
|
testExpected = 33
|
||||||
|
testGot = len(*completedList)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter list to get only tasks with a due date
|
||||||
|
dueDateList := testTasklist.Filter(func(t Task) bool { return t.HasDueDate() })
|
||||||
|
testExpected = 26
|
||||||
|
testGot = len(*dueDateList)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter list to get only tasks with "B" priority
|
||||||
|
prioBList := testTasklist.Filter(func(t Task) bool {
|
||||||
|
return t.HasPriority() && t.Priority == "B"
|
||||||
|
})
|
||||||
|
testExpected = 17
|
||||||
|
testGot = len(*prioBList)
|
||||||
|
if testGot != testExpected {
|
||||||
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", testExpected, testGot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListReadErrors(t *testing.T) {
|
||||||
|
if testTasklist, err := LoadFromFilename(testInputTasklistCreatedDateError); testTasklist != nil || err == nil {
|
||||||
|
t.Errorf("Expected LoadFromFilename to fail because of invalid created date, but got TaskList back: [%s]", testTasklist)
|
||||||
|
} else if err.Error() != `parsing time "2013-13-01": month out of range` {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testTasklist, err := LoadFromFilename(testInputTasklistDueDateError); testTasklist != nil || err == nil {
|
||||||
|
t.Errorf("Expected LoadFromFilename to fail because of invalid due date, but got TaskList back: [%s]", testTasklist)
|
||||||
|
} else if err.Error() != `parsing time "2014-02-32": day out of range` {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testTasklist, err := LoadFromFilename(testInputTasklistCompletedDateError); testTasklist != nil || err == nil {
|
||||||
|
t.Errorf("Expected LoadFromFilename to fail because of invalid completed date, but got TaskList back: [%s]", testTasklist)
|
||||||
|
} else if err.Error() != `parsing time "2014-25-04": month out of range` {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// really silly test
|
||||||
|
if testTasklist, err := LoadFromFilename(testInputTasklistScannerError); testTasklist != nil || err == nil {
|
||||||
|
t.Errorf("Expected LoadFromFilename to fail because of invalid file, but got TaskList back: [%s]", testTasklist)
|
||||||
|
} else if err.Error() != `bufio.Scanner: token too long` {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user