From 9572ff63d98e2d3f77e74ee3a5fc37bbfd0e27b1 Mon Sep 17 00:00:00 2001 From: "jamesclonk@jamesclonk.ch" Date: Fri, 3 Jan 2014 14:21:27 +0100 Subject: [PATCH] work in progress --- README.md | 6 ++-- todo.txt | 9 +++--- todotxt.go | 73 +++++++++++++++++++++++++++++++++++++++++++++---- todotxt_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 138 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c33ed9e..cb2d56c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ go-todotxt ========== -A Go todo.txt library. +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. It allows for parsing and manipulating of task lists and tasks in the todo.txt format. -[![GoDoc](https://godoc.org/github.com/JamesClonk/go-todotxt?status.png)](https://godoc.org/github.com/JamesClonk/go-todotxt) - ## Installation $ go get github.com/JamesClonk/go-todotxt diff --git a/todo.txt b/todo.txt index 3411f96..477264f 100644 --- a/todo.txt +++ b/todo.txt @@ -1,8 +1,9 @@ -(A) 2012-01-30 Call Mom @Phone +Family +(A) 2012-01-30 @Phone Call Mom @Call +Family (A) Schedule annual checkup +Health -(B) 2013-12-01 Outline chapter 5 +Novel @Computer due:2014-01-01 +(B) 2013-12-01 Outline chapter 5 +Novel @Computer due:2014-02-17 (C) Add cover sheets @Office +TPSReports -Plan backyard herb garden @Home ++Gardening Plan backyard herb garden +Planning @Home +Improving 2013-02-22 Pick up milk @GroceryStore -Research self-publishing services +Novel @Computer due:2014-01-01 +Research self-publishing services +Novel +Novel +Novel due:2014-01-01 x Download Todo.txt mobile app @Phone +@Home Turn off TV @Electricity @Television @Electricity diff --git a/todotxt.go b/todotxt.go index 7a46692..e9bc067 100644 --- a/todotxt.go +++ b/todotxt.go @@ -13,6 +13,8 @@ import ( "fmt" "os" "regexp" + "sort" + "strings" "time" ) @@ -38,8 +40,11 @@ var ( DateLayout = "2006-01-02" // unexported vars - priorityRx = regexp.MustCompile(`^\(([A-Z])\)\s+`) // Match priority value: '(A) ...' - createdDateRx = regexp.MustCompile(`^(\([A-Z]\)|)\s*([\d]{4}-[\d]{2}-[\d]{2})\s+`) // Match date value: '(A) 2012-12-12 ...' or '2012-12-12 ...' + 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 ...' ) // String returns a complete task string in todo.txt format. @@ -48,16 +53,33 @@ var ( // "(A) 2013-07-23 Call Dad @Phone +Family due:2013-07-31" func (task *Task) String() string { var text string + if task.HasPriority() { text += fmt.Sprintf("(%s) ", task.Priority) } + if task.HasCreatedDate() { text += fmt.Sprintf("%s ", task.CreatedDate.Format(DateLayout)) } + text += task.Todo - if task.HasDueDate() { - text += fmt.Sprintf(" %s", task.DueDate.Format(DateLayout)) + + if len(task.Contexts) > 0 { + for _, context := range task.Contexts { + text += fmt.Sprintf(" @%s", context) + } } + + if len(task.Projects) > 0 { + for _, project := range task.Projects { + text += fmt.Sprintf(" +%s", project) + } + } + + if task.HasDueDate() { + text += fmt.Sprintf(" due:%s", task.DueDate.Format(DateLayout)) + } + return text } @@ -102,12 +124,11 @@ func (tasklist *TaskList) LoadFromFile(file *os.File) error { // Check for priority if priorityRx.MatchString(task.Original) { - task.Priority = priorityRx.FindStringSubmatch(task.Original)[1] // First match is priority value + task.Priority = priorityRx.FindStringSubmatch(task.Original)[1] } // Check for created date if createdDateRx.MatchString(task.Original) { - // Second match is created date value if date, err := time.Parse(DateLayout, createdDateRx.FindStringSubmatch(task.Original)[2]); err != nil { return err } else { @@ -115,6 +136,46 @@ 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) + 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) + } + + // Check for projects + if projectRx.MatchString(task.Original) { + 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 + *tasklist = append(*tasklist, task) } if err := scanner.Err(); err != nil { diff --git a/todotxt_test.go b/todotxt_test.go index 3cf187d..508d0bb 100644 --- a/todotxt_test.go +++ b/todotxt_test.go @@ -38,7 +38,7 @@ func loadTest(t *testing.T, tasklist TaskList) { // ------------------------------------------------------------------------------------- // count tasks - expected = 8 + expected = 9 got = len(tasklist) if got != expected { t.Errorf("Expected TaskList to contain %d tasks, but got %d", expected, got) @@ -52,7 +52,7 @@ func loadTest(t *testing.T, tasklist TaskList) { t.Errorf("Expected eight Task to be [%s], but got [%s]", expected, got) } - expected = "(B) 2013-12-01 Outline chapter 5 +Novel @Computer due:2014-01-01" + expected = "(B) 2013-12-01 Outline chapter 5 @Computer +Novel due:2014-01-01" got = tasklist[2].Task() if got != expected { t.Errorf("Expected third Task to be [%s], but got [%s]", expected, got) @@ -94,9 +94,55 @@ func loadTest(t *testing.T, tasklist TaskList) { t.Errorf("Expected fifth task to have no created date, but got '%v'", tasklist[4].CreatedDate) } + // ------------------------------------------------------------------------------------- + // task contexts + expected = []string{"Call", "Phone"} + got = tasklist[0].Contexts + if !compareSlices(got.([]string), expected.([]string)) { + t.Errorf("Expected first task to have contexts '%v', but got '%v'", expected, got) + } + + expected = []string{"Office"} + got = tasklist[3].Contexts + if !compareSlices(got.([]string), expected.([]string)) { + t.Errorf("Expected fourth task to have contexts '%v', but got '%v'", expected, got) + } + + expected = []string{"Electricity", "Home", "Television"} + got = tasklist[8].Contexts + if !compareSlices(got.([]string), expected.([]string)) { + t.Errorf("Expected ninth task to have contexts '%v', but got '%v'", expected, got) + } + + expected = []string{} + got = tasklist[6].Contexts + if !compareSlices(got.([]string), expected.([]string)) { + t.Errorf("Expected seventh task to have no contexts, but got '%v'", got) + } + + // ------------------------------------------------------------------------------------- + // task projects + expected = []string{"Gardening", "Improving", "Planning"} + got = tasklist[4].Projects + if !compareSlices(got.([]string), expected.([]string)) { + t.Errorf("Expected fifth task to have projects '%v', but got '%v'", expected, got) + } + + expected = []string{"Novel"} + got = tasklist[6].Projects + if !compareSlices(got.([]string), expected.([]string)) { + t.Errorf("Expected sixth task to have projects '%v', but got '%v'", expected, got) + } + + expected = []string{} + got = tasklist[8].Projects + if !compareSlices(got.([]string), expected.([]string)) { + t.Errorf("Expected ninth task to have no projects, but got '%v'", got) + } + // ------------------------------------------------------------------------------------- // task due date - expected, err = time.Parse(DateLayout, "2014-01-01") + expected, err = time.Parse(DateLayout, "2014-02-17") if err != nil { t.Fatal(err) } @@ -109,3 +155,17 @@ func loadTest(t *testing.T, tasklist TaskList) { t.Errorf("Expected first task to have no due date, but got '%v'", tasklist[0].DueDate) } } + +func compareSlices(list1 []string, list2 []string) bool { + if len(list1) != len(list2) { + return false + } + + for i := range list1 { + if list1[i] != list2[i] { + return false + } + } + + return true +}