From 510c61ffb335251043fe06c10b5ad085dd9e679d Mon Sep 17 00:00:00 2001 From: "jamesclonk@jamesclonk.ch" Date: Fri, 3 Jan 2014 17:35:38 +0100 Subject: [PATCH] work in progress --- README.md | 2 +- testdata/expected_todo.txt | 10 ++ testdata/input_todo.txt | 54 ++++++++ todo.txt | 11 +- todotxt.go | 130 ++++++++++++++----- todotxt_test.go | 254 +++++++++++++++++++++++++++++++------ 6 files changed, 387 insertions(+), 74 deletions(-) create mode 100644 testdata/expected_todo.txt create mode 100644 testdata/input_todo.txt diff --git a/README.md b/README.md index cb2d56c..31c8f5b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A Go todo.txt library. [![GoDoc](https://godoc.org/github.com/JamesClonk/go-todotxt?status.png)](https://godoc.org/github.com/JamesClonk/go-todotxt) -The Package todotxt is a Go client library for Gina Trapani's [todo.txt](https://github.com/ginatrapani/todo.txt-cli/) files. +The *todotxt* package is a Go client library for Gina Trapani's [todo.txt](https://github.com/ginatrapani/todo.txt-cli/) files. It allows for parsing and manipulating of task lists and tasks in the todo.txt format. ## Installation diff --git a/testdata/expected_todo.txt b/testdata/expected_todo.txt new file mode 100644 index 0000000..7cba68e --- /dev/null +++ b/testdata/expected_todo.txt @@ -0,0 +1,10 @@ +(A) 2012-01-30 Call Mom @Call @Phone +Family +(A) Schedule annual checkup +Health +(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17 +(C) Add cover sheets @Office +TPSReports +Plan backyard herb garden @Home +Gardening +Improving +Planning +2013-02-22 Pick up milk @GroceryStore +Research self-publishing services +Novel due:2014-01-01 +x Download Todo.txt mobile app @Phone +Turn off TV @Electricity @Home @Television Importance:Very! +x 2014-01-03 Create golang library @Go +go-todotxt due:2014-01-05 diff --git a/testdata/input_todo.txt b/testdata/input_todo.txt new file mode 100644 index 0000000..490f3a1 --- /dev/null +++ b/testdata/input_todo.txt @@ -0,0 +1,54 @@ +# String test cases +2013-02-22 Pick up milk @GroceryStore +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 + +# Priority test cases +(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 + +# CreatedDate test cases +(A) 2012-01-30 @Phone Call Mom @Call +Family +2013-02-22 Pick up milk @GroceryStore +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 +(C) Add cover sheets @Office +TPSReports +@Home Turn off TV @Electricity @Television @Electricity Importance:Very! +Research self-publishing services +Novel +Novel +Novel due:2014-01-01 + +# Projects test cases ++Gardening Plan backyard herb garden +Planning @Home +Improving +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 +x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt + +# AdditionalTags test cases +(B) 2013-12-01 private:false Outline chapter 5 +Novel @Computer Level:5 due:2014-02-17 +@Home Turn off TV @Electricity @Television @Electricity Importance:Very! +x (C) 2014-01-01 Create golang library documentation @Go +go-todotxt due:2014-01-12 +(A) 2012-01-30 @Phone Call Mom @Call +Family + +# Completed test cases +x Create golang library @Go +go-todotxt due:2014-01-05 +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 +xylophone lesson +X 2012-01-01 Make resolutions + +# CompletedDate test cases +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-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt diff --git a/todo.txt b/todo.txt index 477264f..7836996 100644 --- a/todo.txt +++ b/todo.txt @@ -1,9 +1,8 @@ -(A) 2012-01-30 @Phone Call Mom @Call +Family +(A) Call Mom @Phone +Family (A) Schedule annual checkup +Health -(B) 2013-12-01 Outline chapter 5 +Novel @Computer due:2014-02-17 +(B) Outline chapter 5 +Novel @Computer (C) Add cover sheets @Office +TPSReports -+Gardening Plan backyard herb garden +Planning @Home +Improving -2013-02-22 Pick up milk @GroceryStore -Research self-publishing services +Novel +Novel +Novel due:2014-01-01 +Plan backyard herb garden @Home +Pick up milk @GroceryStore +Research self-publishing services +Novel @Computer x Download Todo.txt mobile app @Phone -@Home Turn off TV @Electricity @Television @Electricity diff --git a/todotxt.go b/todotxt.go index e9bc067..432c28d 100644 --- a/todotxt.go +++ b/todotxt.go @@ -20,15 +20,16 @@ import ( // Task represents a todo.txt task entry. type Task struct { - Original string // Original raw task text - Todo string // Todo part of task text - Priority string - Projects []string - Contexts []string - CreatedDate time.Time - DueDate time.Time - CompletedDate time.Time - Completed bool + Original string // Original raw task text + Todo string // Todo part of task text + Priority string + Projects []string + Contexts []string + AdditionalTags map[string]string // Addon tags will be available here + CreatedDate time.Time + DueDate time.Time + CompletedDate time.Time + Completed bool } // TaskList represents a list of todo.txt task entries. @@ -40,20 +41,41 @@ var ( DateLayout = "2006-01-02" // unexported vars - priorityRx = regexp.MustCompile(`^\(([A-Z])\)\s+`) // Match priority: '(A) ...' - createdDateRx = regexp.MustCompile(`^(\([A-Z]\)|)\s*([\d]{4}-[\d]{2}-[\d]{2})\s+`) // Match date: '(A) 2012-12-12 ...' or '2012-12-12 ...' - dueDateRx = regexp.MustCompile(`\s+due:([\d]{4}-[\d]{2}-[\d]{2})`) // Match due date: '... due:2012-12-12 ...' - contextRx = regexp.MustCompile(`(^|\s+)@([[:word:]]+)`) // Match contexts: '@Context ...' or '... @Context ...' - projectRx = regexp.MustCompile(`(^|\s+)\+([[:word:]]+)`) // Match projects: '+Project...' or '... +Project ...' + 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 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 \d{4}-\d{2}-\d{2}|)\s*(\d{4}-\d{2}-\d{2})\s+`) + 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. +func (tasklist *TaskList) String() (text string) { + for _, task := range *tasklist { + text += fmt.Sprintf("%s\n", task.String()) + } + return text +} + // String returns a complete task string in todo.txt format. // +// Contexts, Projects and additional tags are alphabetically sorted, +// and appendend at the end in the following order: +// Contexts, Projects, Tags +// // For example: -// "(A) 2013-07-23 Call Dad @Phone +Family due:2013-07-31" +// "(A) 2013-07-23 Call Dad @Home @Phone +Family due:2013-07-31 customTag1:Important!" func (task *Task) String() string { var text string + if task.Completed { + text += "x " + if task.HasCompletedDate() { + text += fmt.Sprintf("%s ", task.CompletedDate.Format(DateLayout)) + } + } + if task.HasPriority() { text += fmt.Sprintf("(%s) ", task.Priority) } @@ -76,6 +98,18 @@ func (task *Task) String() string { } } + if len(task.AdditionalTags) > 0 { + // Sort map alphabetically by keys + keys := make([]string, 0, len(task.AdditionalTags)) + for key, _ := range task.AdditionalTags { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + text += fmt.Sprintf(" %s:%s", key, task.AdditionalTags[key]) + } + } + if task.HasDueDate() { text += fmt.Sprintf(" due:%s", task.DueDate.Format(DateLayout)) } @@ -84,7 +118,7 @@ func (task *Task) String() string { } // Task returns a complete task string in todo.txt format. -// The same as *Task.String(). +// See *Task.String() for further information. func (task *Task) Task() string { return task.String() } @@ -122,9 +156,27 @@ func (tasklist *TaskList) LoadFromFile(file *os.File) error { task := Task{} task.Original = scanner.Text() + // Ignore blank or comment lines + if strings.Trim(task.Original, "\t\n\r ") == "" || strings.HasPrefix(task.Original, "#") { + continue + } + + // Check for completed + if strings.HasPrefix(task.Original, "x ") { + 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 + } else { + task.CompletedDate = date + } + } + } + // Check for priority if priorityRx.MatchString(task.Original) { - task.Priority = priorityRx.FindStringSubmatch(task.Original)[1] + task.Priority = priorityRx.FindStringSubmatch(task.Original)[2] } // Check for created date @@ -136,15 +188,6 @@ func (tasklist *TaskList) LoadFromFile(file *os.File) error { } } - // Check for due date - if dueDateRx.MatchString(task.Original) { - if date, err := time.Parse(DateLayout, dueDateRx.FindStringSubmatch(task.Original)[1]); err != nil { - return err - } else { - task.DueDate = date - } - } - // function for collecting projects/contexts as slices from text getSlice := func(rx *regexp.Regexp) []string { matches := rx.FindAllStringSubmatch(task.Original, -1) @@ -171,10 +214,37 @@ func (tasklist *TaskList) LoadFromFile(file *os.File) error { task.Projects = getSlice(projectRx) } - // Todo text - // use replacer function here.. strip all other fields from task.Original, then left+right trim --> todo text - text := strings.Replace(task.Original, " ", "", -1) - task.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 + } + + text := task.Original + if task.Completed { + text = text[2:] // Strip 'x ' + text = completedDateRx.ReplaceAllString(text, "") + } + // Remove all matching regular expressions from text, so only the actual todo text is left + text = priorityRx.ReplaceAllString(text, "") + text = createdDateRx.ReplaceAllString(text, "") + text = contextRx.ReplaceAllString(text, "") + text = projectRx.ReplaceAllString(text, "") + text = addonTagRx.ReplaceAllString(text, "") + task.Todo = strings.Trim(text, "\t\n\r\f ") *tasklist = append(*tasklist, task) } diff --git a/todotxt_test.go b/todotxt_test.go index 508d0bb..8a82810 100644 --- a/todotxt_test.go +++ b/todotxt_test.go @@ -5,13 +5,14 @@ package todotxt import ( + "io/ioutil" "os" "testing" "time" ) func TestLoadFromFile(t *testing.T) { - file, err := os.Open("todo.txt") + file, err := os.Open("testdata/input_todo.txt") if err != nil { t.Fatal(err) } @@ -25,7 +26,7 @@ func TestLoadFromFile(t *testing.T) { } func TestLoadFromFilename(t *testing.T) { - if tasklist, err := LoadFromFilename("todo.txt"); err != nil { + if tasklist, err := LoadFromFilename("testdata/input_todo.txt"); err != nil { t.Fatal(err) } else { loadTest(t, *tasklist) @@ -33,12 +34,25 @@ func TestLoadFromFilename(t *testing.T) { } func loadTest(t *testing.T, tasklist TaskList) { + taskId := 1 var expected, got interface{} var err error + // ------------------------------------------------------------------------------------- + // complete tasklist string + data, err := ioutil.ReadFile("testdata/expected_todo.txt") + if err != nil { + t.Fatal(err) + } + expected = string(data) + got = tasklist.String() + if got != expected { + //t.Errorf("Expected TaskList to be [%s], but got [%s]", expected, got)-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + } + // ------------------------------------------------------------------------------------- // count tasks - expected = 9 + expected = 10 got = len(tasklist) if got != expected { t.Errorf("Expected TaskList to contain %d tasks, but got %d", expected, got) @@ -46,29 +60,73 @@ func loadTest(t *testing.T, tasklist TaskList) { // ------------------------------------------------------------------------------------- // complete task strings - expected = "x Download Todo.txt mobile app @Phone" - got = tasklist[7].String() + expected = "2013-02-22 Pick up milk @GroceryStore" + got = tasklist[taskId-1].String() if got != expected { - t.Errorf("Expected eight Task to be [%s], but got [%s]", expected, got) + t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, expected, got) } + taskId++ - expected = "(B) 2013-12-01 Outline chapter 5 @Computer +Novel due:2014-01-01" - got = tasklist[2].Task() + expected = "x Download Todo.txt mobile app @Phone" + got = tasklist[taskId-1].String() if got != expected { - t.Errorf("Expected third Task to be [%s], but got [%s]", expected, got) + t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, expected, got) } + taskId++ + + expected = "(B) 2013-12-01 Outline chapter 5 @Computer +Novel Level:5 private:false due:2014-02-17" + got = tasklist[taskId-1].Task() + if got != expected { + t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, expected, got) + } + taskId++ + + expected = "x 2014-01-02 (B) 2013-12-30 Create golang library test cases @Go +go-todotxt" + got = tasklist[taskId-1].Task() + if got != expected { + t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, expected, got) + } + expected = "1" + got = tasklist[taskId-1].Todo + if got != expected { + t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, expected, got) + } + taskId++ + + expected = "x 2014-01-03 2014-01-01 Create some more golang library test cases @Go +go-todotxt" + got = tasklist[taskId-1].Task() + if got != expected { + t.Errorf("Expected Task[%d] to be [%s], but got [%s]", taskId, expected, got) + } + taskId++ // ------------------------------------------------------------------------------------- // task priority expected = "B" - got = tasklist[2].Priority + got = tasklist[taskId-1].Priority if got != expected { - t.Errorf("Expected third task to have priority '%s', but got '%s'", expected, got) + t.Errorf("Expected Task[%d] to have priority '%s', but got '%s'", taskId, expected, got) } + taskId++ - if tasklist[4].HasPriority() { - t.Errorf("Expected fifth task to have no priority, but got '%s'", tasklist[4].Priority) + expected = "C" + got = tasklist[taskId-1].Priority + if got != expected { + t.Errorf("Expected Task[%d] to have priority '%s', but got '%s'", taskId, expected, got) } + taskId++ + + expected = "B" + got = tasklist[taskId-1].Priority + if got != expected { + t.Errorf("Expected Task[%d] to have priority '%s', but got '%s'", taskId, expected, got) + } + taskId++ + + if tasklist[taskId-1].HasPriority() { + t.Errorf("Expected Task[%d] to have no priority, but got '%s'", taskId, tasklist[4].Priority) + } + taskId++ // ------------------------------------------------------------------------------------- // task created date @@ -76,69 +134,99 @@ func loadTest(t *testing.T, tasklist TaskList) { if err != nil { t.Fatal(err) } - got = tasklist[0].CreatedDate + got = tasklist[taskId-1].CreatedDate if got != expected { - t.Errorf("Expected first task to have created date '%s', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have created date '%s', but got '%v'", taskId, expected, got) } + taskId++ expected, err = time.Parse(DateLayout, "2013-02-22") if err != nil { t.Fatal(err) } - got = tasklist[5].CreatedDate + got = tasklist[taskId-1].CreatedDate if got != expected { - t.Errorf("Expected sixth task to have created date '%s', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have created date '%s', but got '%v'", taskId, expected, got) } + taskId++ - if tasklist[4].HasCreatedDate() { - t.Errorf("Expected fifth task to have no created date, but got '%v'", tasklist[4].CreatedDate) + expected, err = time.Parse(DateLayout, "2013-12-30") + if err != nil { + t.Fatal(err) } + got = tasklist[taskId-1].CreatedDate + if got != expected { + t.Errorf("Expected Task[%d] to have created date '%s', but got '%v'", taskId, expected, got) + } + taskId++ + + expected, err = time.Parse(DateLayout, "2014-01-01") + if err != nil { + t.Fatal(err) + } + got = tasklist[taskId-1].CreatedDate + if got != expected { + t.Errorf("Expected Task[%d] to have created date '%s', but got '%v'", taskId, expected, got) + } + taskId++ + + if tasklist[taskId-1].HasCreatedDate() { + t.Errorf("Expected Task[%d] to have no created date, but got '%v'", taskId, tasklist[4].CreatedDate) + } + taskId++ // ------------------------------------------------------------------------------------- // task contexts expected = []string{"Call", "Phone"} - got = tasklist[0].Contexts + got = tasklist[taskId-1].Contexts if !compareSlices(got.([]string), expected.([]string)) { - t.Errorf("Expected first task to have contexts '%v', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have contexts '%v', but got '%v'", taskId, expected, got) } + taskId++ expected = []string{"Office"} - got = tasklist[3].Contexts + got = tasklist[taskId-1].Contexts if !compareSlices(got.([]string), expected.([]string)) { - t.Errorf("Expected fourth task to have contexts '%v', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have contexts '%v', but got '%v'", taskId, expected, got) } + taskId++ expected = []string{"Electricity", "Home", "Television"} - got = tasklist[8].Contexts + got = tasklist[taskId-1].Contexts if !compareSlices(got.([]string), expected.([]string)) { - t.Errorf("Expected ninth task to have contexts '%v', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have contexts '%v', but got '%v'", taskId, expected, got) } + taskId++ expected = []string{} - got = tasklist[6].Contexts + got = tasklist[taskId-1].Contexts if !compareSlices(got.([]string), expected.([]string)) { - t.Errorf("Expected seventh task to have no contexts, but got '%v'", got) + t.Errorf("Expected Task[%d] to have no contexts, but got '%v'", taskId, got) } + taskId++ // ------------------------------------------------------------------------------------- // task projects expected = []string{"Gardening", "Improving", "Planning"} - got = tasklist[4].Projects + got = tasklist[taskId-1].Projects if !compareSlices(got.([]string), expected.([]string)) { - t.Errorf("Expected fifth task to have projects '%v', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have projects '%v', but got '%v'", taskId, expected, got) } + taskId++ expected = []string{"Novel"} - got = tasklist[6].Projects + got = tasklist[taskId-1].Projects if !compareSlices(got.([]string), expected.([]string)) { - t.Errorf("Expected sixth task to have projects '%v', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have projects '%v', but got '%v'", taskId, expected, got) } + taskId++ expected = []string{} - got = tasklist[8].Projects + got = tasklist[taskId-1].Projects if !compareSlices(got.([]string), expected.([]string)) { - t.Errorf("Expected ninth task to have no projects, but got '%v'", got) + t.Errorf("Expected Task[%d] to have no projects, but got '%v'", taskId, got) } + taskId++ // ------------------------------------------------------------------------------------- // task due date @@ -146,14 +234,87 @@ func loadTest(t *testing.T, tasklist TaskList) { if err != nil { t.Fatal(err) } - got = tasklist[2].DueDate + got = tasklist[taskId-1].DueDate if got != expected { - t.Errorf("Expected third task to have due date '%s', but got '%v'", expected, got) + t.Errorf("Expected Task[%d] to have due date '%s', but got '%v'", taskId, expected, got) } + taskId++ - if tasklist[0].HasDueDate() { - t.Errorf("Expected first task to have no due date, but got '%v'", tasklist[0].DueDate) + if tasklist[taskId-1].HasDueDate() { + t.Errorf("Expected Task[%d] to have no due date, but got '%v'", taskId, tasklist[taskId-1].DueDate) } + taskId++ + + // ------------------------------------------------------------------------------------- + // task addon tags + expected = map[string]string{"Level": "5", "private": "false"} + got = tasklist[taskId-1].AdditionalTags + if len(got.(map[string]string)) != 2 || + !compareMaps(got.(map[string]string), expected.(map[string]string)) { + t.Errorf("Expected Task[%d] to have addon tags '%v', but got '%v'", taskId, expected, got) + } + taskId++ + + expected = map[string]string{"Importance": "Very!"} + got = tasklist[taskId-1].AdditionalTags + if len(got.(map[string]string)) != 1 || + !compareMaps(got.(map[string]string), expected.(map[string]string)) { + t.Errorf("Expected Task[%d] to have projects '%v', but got '%v'", taskId, expected, got) + } + taskId++ + + expected = map[string]string{} + got = tasklist[taskId-1].AdditionalTags + if len(got.(map[string]string)) != 0 || + !compareMaps(got.(map[string]string), expected.(map[string]string)) { + t.Errorf("Expected Task[%d] to have no additional tags, but got '%v'", taskId, got) + } + taskId++ + + expected = map[string]string{} + got = tasklist[taskId-1].AdditionalTags + if len(got.(map[string]string)) != 0 || + !compareMaps(got.(map[string]string), expected.(map[string]string)) { + t.Errorf("Expected Task[%d] to have no additional tags, but got '%v'", taskId, got) + } + taskId++ + + // ------------------------------------------------------------------------------------- + // task completed + expected = true + got = tasklist[taskId-1].Completed + if got != expected { + t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, got) + } + taskId++ + + expected = true + got = tasklist[taskId-1].Completed + if got != expected { + t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, got) + } + taskId++ + + expected = true + got = tasklist[taskId-1].Completed + if got != expected { + t.Errorf("Expected Task[%d] to be completed, but got '%v'", taskId, got) + } + taskId++ + + expected = false + got = tasklist[taskId-1].Completed + if got != expected { + t.Errorf("Expected Task[%d] to not be completed, but got '%v'", taskId, got) + } + taskId++ + + expected = false + got = tasklist[taskId-1].Completed + if got != expected { + t.Errorf("Expected Task[%d] to not be completed, but got '%v'", taskId, got) + } + taskId++ } func compareSlices(list1 []string, list2 []string) bool { @@ -169,3 +330,22 @@ func compareSlices(list1 []string, list2 []string) bool { return true } + +func compareMaps(map1 map[string]string, map2 map[string]string) bool { + if len(map1) != len(map2) { + return false + } + + compare := func(map1 map[string]string, map2 map[string]string) bool { + for key, value := range map1 { + if value2, found := map2[key]; !found { + return false + } else if value != value2 { + return false + } + } + return true + } + + return compare(map1, map2) && compare(map2, map1) +}