Work in progress
Started working on new functionality, like internal task id and new useful methods on tasklists.
This commit is contained in:
parent
96cc4ce27c
commit
c1f16b56a5
119
task.go
119
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
|
||||||
@ -85,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 {
|
||||||
|
178
task_test.go
178
task_test.go
@ -13,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
|
||||||
|
125
todotxt.go
125
todotxt.go
@ -13,10 +13,7 @@ import (
|
|||||||
"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.
|
||||||
@ -26,21 +23,9 @@ type TaskList []Task
|
|||||||
// IgnoreComments can be set to 'false', in order to revert to a 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 (
|
||||||
// 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 "#").
|
// 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.
|
// If this is set to 'false', then lines starting with "#" will be parsed as tasks.
|
||||||
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.
|
// String returns a complete tasklist string in todo.txt format.
|
||||||
@ -51,6 +36,20 @@ 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) (err error) {
|
||||||
|
task.Id = 0
|
||||||
|
for _, t := range *tasklist {
|
||||||
|
if t.Id > task.Id {
|
||||||
|
task.Id = t.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.Id += 1
|
||||||
|
|
||||||
|
*tasklist = append(*tasklist, *task)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@ -59,102 +58,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
|
return err
|
||||||
// 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.Id = taskId
|
||||||
|
|
||||||
// Check for priority
|
*tasklist = append(*tasklist, *task)
|
||||||
if priorityRx.MatchString(task.Original) {
|
taskId++
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
129
todotxt_test.go
129
todotxt_test.go
@ -8,6 +8,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -37,8 +38,8 @@ 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)
|
||||||
}
|
}
|
||||||
@ -57,8 +58,8 @@ 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)
|
||||||
}
|
}
|
||||||
@ -104,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)
|
||||||
}
|
}
|
||||||
@ -146,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)
|
||||||
}
|
}
|
||||||
@ -171,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)
|
||||||
}
|
}
|
||||||
@ -196,25 +197,125 @@ 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 TestNewTaskList(t *testing.T) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
func TestTaskListCount(t *testing.T) {
|
func TestTaskListCount(t *testing.T) {
|
||||||
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testExpected := 63
|
testExpected = 63
|
||||||
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 TestTaskListAddTask(t *testing.T) {
|
||||||
|
if err := testTasklist.LoadFromFilename(testInputTasklist); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new empty task
|
||||||
|
testTasklist.AddTask(NewTask())
|
||||||
|
|
||||||
|
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 TestTaskListRemoveTaskById(t *testing.T) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListRemoveTask(t *testing.T) {
|
||||||
|
// removes by comparing Task.String() with each other
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListFilter(t *testing.T) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskListFilterNot(t *testing.T) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
func TestTaskListReadErrors(t *testing.T) {
|
func TestTaskListReadErrors(t *testing.T) {
|
||||||
if testTasklist, err := LoadFromFilename(testInputTasklistCreatedDateError); testTasklist != nil || err == nil {
|
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)
|
t.Errorf("Expected LoadFromFilename to fail because of invalid created date, but got TaskList back: [%s]", testTasklist)
|
||||||
|
Loading…
Reference in New Issue
Block a user