Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
97a991fa26 | ||
|
e5c37a1106 | ||
|
dbb967d319 | ||
|
2e04f1d233 | ||
|
e29d17784e | ||
|
a0e8e1801d | ||
|
c1f16b56a5 | ||
|
96cc4ce27c | ||
|
e8603bbc85 | ||
|
4af9e2abe8 |
20
README.md
20
README.md
@@ -35,10 +35,30 @@ go-todotxt requires Go1.1 or higher.
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// tasklist now contains a slice of Tasks
|
||||
fmt.Printf("Task 2, todo: %v\n", tasklist[1].Todo)
|
||||
fmt.Printf("Task 3: %v\n", tasklist[2])
|
||||
fmt.Printf("Task 4, has priority: %v\n\n", tasklist[3].HasPriority())
|
||||
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")
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -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
|
||||
|
||||
import (
|
||||
|
121
task.go
121
task.go
@@ -6,12 +6,29 @@ package todotxt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"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.
|
||||
type Task struct {
|
||||
Id int // Internal task id.
|
||||
Original string // Original raw task text.
|
||||
Todo string // Todo part of task text.
|
||||
Priority string
|
||||
@@ -53,12 +70,14 @@ func (task Task) String() string {
|
||||
text += task.Todo
|
||||
|
||||
if len(task.Contexts) > 0 {
|
||||
sort.Strings(task.Contexts)
|
||||
for _, context := range task.Contexts {
|
||||
text += fmt.Sprintf(" @%s", context)
|
||||
}
|
||||
}
|
||||
|
||||
if len(task.Projects) > 0 {
|
||||
sort.Strings(task.Projects)
|
||||
for _, project := range task.Projects {
|
||||
text += fmt.Sprintf(" +%s", project)
|
||||
}
|
||||
@@ -83,6 +102,108 @@ func (task Task) String() string {
|
||||
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.
|
||||
// See *Task.String() for further information.
|
||||
func (task *Task) Task() string {
|
||||
|
182
task_test.go
182
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
|
||||
|
||||
import (
|
||||
@@ -9,6 +13,184 @@ var (
|
||||
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) {
|
||||
testTasklist.LoadFromFilename(testInputTask)
|
||||
taskId := 1
|
||||
|
2
testdata/ouput_todo.txt
vendored
2
testdata/ouput_todo.txt
vendored
@@ -1,6 +1,6 @@
|
||||
2013-02-22 Pick up milk @GroceryStore
|
||||
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-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
|
||||
|
14
testdata/task_todo.txt
vendored
14
testdata/task_todo.txt
vendored
@@ -1,6 +1,6 @@
|
||||
# String test cases
|
||||
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 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
|
||||
@@ -9,15 +9,15 @@ x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todot
|
||||
(B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer Level:5 due:2014-02-17
|
||||
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
|
||||
+Gardening Plan backyard herb garden +Planning @Home +Improving
|
||||
+Gardening Plan backyard herb garden +Planning @Home +Improving
|
||||
|
||||
# CreatedDate test cases
|
||||
(A) 2012-01-30 @Phone Call Mom @Call +Family
|
||||
2013-02-22 Pick up milk @GroceryStore
|
||||
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-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
||||
(C) Add cover sheets @Office +TPSReports
|
||||
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
|
||||
(C) Add cover sheets @Office +TPSReports
|
||||
|
||||
# Contexts test cases
|
||||
(A) 2012-01-30 @Phone Call Mom @Call +Family
|
||||
@@ -31,7 +31,7 @@ Research self-publishing services +Novel +Novel +Novel due:2014-01-01
|
||||
@Home Turn off TV @Electricity @Television @Electricity Importance:Very!
|
||||
|
||||
# DueDate test cases
|
||||
(B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer due:2014-02-17 Level:5
|
||||
(B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer due:2014-02-17 Level:5
|
||||
x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt
|
||||
|
||||
# AdditionalTags test cases
|
||||
@@ -57,7 +57,7 @@ x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todot
|
||||
|
||||
# Overdue test cases
|
||||
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
|
||||
x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt
|
||||
|
||||
|
200
todotxt.go
200
todotxt.go
@@ -10,13 +10,11 @@ package todotxt
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TaskList represents a list of todo.txt task entries.
|
||||
@@ -26,24 +24,18 @@ type TaskList []Task
|
||||
// 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.
|
||||
var (
|
||||
// DateLayout is used for formatting time.Time into todo.txt date format and vice-versa.
|
||||
DateLayout = "2006-01-02"
|
||||
// IgnoreComments is used to switch ignoring of comments (lines starting with "#").
|
||||
// If this is set to 'false', then lines starting with "#" will be parsed as tasks.
|
||||
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) {
|
||||
for _, task := range tasklist {
|
||||
text += fmt.Sprintf("%s\n", task.String())
|
||||
@@ -51,6 +43,84 @@ func (tasklist TaskList) String() (text string) {
|
||||
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.
|
||||
//
|
||||
// Using *os.File instead of a filename allows to also use os.Stdin.
|
||||
@@ -59,102 +129,24 @@ func (tasklist TaskList) String() (text string) {
|
||||
func (tasklist *TaskList) LoadFromFile(file *os.File) error {
|
||||
*tasklist = []Task{} // Empty tasklist
|
||||
|
||||
taskId := 1
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
task := Task{}
|
||||
task.Original = strings.Trim(scanner.Text(), "\t\n\r ") // Read line
|
||||
task.Todo = task.Original
|
||||
text := strings.Trim(scanner.Text(), "\t\n\r ") // Read line
|
||||
|
||||
// Ignore blank or comment lines
|
||||
if task.Todo == "" || (IgnoreComments && strings.HasPrefix(task.Todo, "#")) {
|
||||
if text == "" || (IgnoreComments && strings.HasPrefix(text, "#")) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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 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 '
|
||||
task, err := ParseTask(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.Id = taskId
|
||||
|
||||
// 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 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 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 ")
|
||||
|
||||
*tasklist = append(*tasklist, task)
|
||||
*tasklist = append(*tasklist, *task)
|
||||
taskId++
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
@@ -188,7 +180,7 @@ func (tasklist *TaskList) LoadFromFilename(filename string) error {
|
||||
|
||||
// WriteToFilename writes a TaskList to the specified file (most likely called "todo.txt").
|
||||
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.
|
||||
|
307
todotxt_test.go
307
todotxt_test.go
@@ -8,6 +8,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,8 +38,8 @@ func TestLoadFromFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected := string(data)
|
||||
testGot := testTasklist.String()
|
||||
testExpected = string(data)
|
||||
testGot = testTasklist.String()
|
||||
if testGot != testExpected {
|
||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||
}
|
||||
@@ -57,8 +58,8 @@ func TestLoadFromFilename(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected := string(data)
|
||||
testGot := testTasklist.String()
|
||||
testExpected = string(data)
|
||||
testGot = testTasklist.String()
|
||||
if testGot != testExpected {
|
||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||
}
|
||||
@@ -104,8 +105,8 @@ func TestWriteFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected := string(data)
|
||||
testGot := testTasklist.String()
|
||||
testExpected = string(data)
|
||||
testGot = testTasklist.String()
|
||||
if testGot != testExpected {
|
||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||
}
|
||||
@@ -146,8 +147,8 @@ func TestTaskListWriteFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected := string(data)
|
||||
testGot := testTasklist.String()
|
||||
testExpected = string(data)
|
||||
testGot = testTasklist.String()
|
||||
if testGot != testExpected {
|
||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||
}
|
||||
@@ -171,8 +172,8 @@ func TestWriteFilename(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected := string(data)
|
||||
testGot := testTasklist.String()
|
||||
testExpected = string(data)
|
||||
testGot = testTasklist.String()
|
||||
if testGot != testExpected {
|
||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||
}
|
||||
@@ -196,20 +197,298 @@ func TestTaskListWriteFilename(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testExpected := string(data)
|
||||
testGot := testTasklist.String()
|
||||
testExpected = string(data)
|
||||
testGot = testTasklist.String()
|
||||
if testGot != testExpected {
|
||||
t.Errorf("Expected TaskList to be [%s], but got [%s]", testExpected, testGot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTaskList(t *testing.T) {
|
||||
testTasklist := NewTaskList()
|
||||
|
||||
testExpected = 0
|
||||
testGot = len(testTasklist)
|
||||
if testGot != testExpected {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user