work in progress
This commit is contained in:
parent
a5c526da68
commit
1c1b01b803
24
README.md
24
README.md
@ -1,4 +1,26 @@
|
|||||||
go-todotxt
|
go-todotxt
|
||||||
==========
|
==========
|
||||||
|
|
||||||
A Go todo.txt library (http://todotxt.com)
|
A Go todo.txt library.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
go-todotxt requires Go1.1 or higher.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
See [GoDoc - Documentation](https://godoc.org/github.com/JamesClonk/go-todotxt) for further documentation.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The source files are distributed under the [Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/), unless otherwise noted.
|
||||||
|
Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
|
||||||
|
4
todo.txt
4
todo.txt
@ -1,8 +1,8 @@
|
|||||||
(A) 2012-01-30 Call Mom @Phone +Family
|
(A) 2012-01-30 Call Mom @Phone +Family
|
||||||
(A) Schedule annual checkup +Health
|
(A) Schedule annual checkup +Health
|
||||||
(B) Outline chapter 5 +Novel @Computer
|
(B) 2013-12-01 Outline chapter 5 +Novel @Computer due:2014-01-01
|
||||||
(C) Add cover sheets @Office +TPSReports
|
(C) Add cover sheets @Office +TPSReports
|
||||||
Plan backyard herb garden @Home
|
Plan backyard herb garden @Home
|
||||||
2013-02-22 Pick up milk @GroceryStore
|
2013-02-22 Pick up milk @GroceryStore
|
||||||
Research self-publishing services +Novel @Computer
|
Research self-publishing services +Novel @Computer due:2014-01-01
|
||||||
x Download Todo.txt mobile app @Phone
|
x Download Todo.txt mobile app @Phone
|
||||||
|
82
todotxt.go
82
todotxt.go
@ -2,18 +2,24 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// Package todotxt is a Go client library for Gina Trapani's todo.txt files.
|
||||||
|
// It allows for parsing and manipulating of task lists and tasks in the todo.txt format.
|
||||||
|
//
|
||||||
|
// Source code and project home: https://github.com/JamesClonk/go-todotxt
|
||||||
package todotxt
|
package todotxt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Task represents a todo.txt task entry.
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Task string // Raw text
|
Original string // Original raw task text
|
||||||
Todo string // Only actual todo part of text
|
Todo string // Todo part of task text
|
||||||
Priority string
|
Priority string
|
||||||
Projects []string
|
Projects []string
|
||||||
Contexts []string
|
Contexts []string
|
||||||
@ -23,52 +29,86 @@ type Task struct {
|
|||||||
Completed bool
|
Completed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TaskList represents a list of todo.txt task entries.
|
||||||
|
// It is usually loaded from a whole todo.txt file.
|
||||||
type TaskList []Task
|
type TaskList []Task
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// Used for formatting time.Time into todo.txt date format.
|
||||||
|
DateLayout = "2006-01-02"
|
||||||
|
|
||||||
|
// unexported vars
|
||||||
priorityRx = regexp.MustCompile(`^\(([A-Z])\)\s+`) // Match priority value: '(A) ...'
|
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 ...'
|
createdDateRx = regexp.MustCompile(`^(\([A-Z]\)|)\s*([\d]{4}-[\d]{2}-[\d]{2})\s+`) // Match date value: '(A) 2012-12-12 ...' or '2012-12-12 ...'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Return raw task text for String()
|
// String returns a complete task string in todo.txt format.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// "(A) 2013-07-23 Call Dad @Phone +Family due:2013-07-31"
|
||||||
func (task *Task) String() string {
|
func (task *Task) String() string {
|
||||||
return task.Task
|
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))
|
||||||
|
}
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Task returns a complete task string in todo.txt format.
|
||||||
|
// The same as *Task.String().
|
||||||
|
func (task *Task) Task() string {
|
||||||
|
return task.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPriority returns true if the task has a priority.
|
||||||
func (task *Task) HasPriority() bool {
|
func (task *Task) HasPriority() bool {
|
||||||
return task.Priority != ""
|
return task.Priority != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCreatedDate returns true if the task has a created date.
|
||||||
func (task *Task) HasCreatedDate() bool {
|
func (task *Task) HasCreatedDate() bool {
|
||||||
return !task.CreatedDate.IsZero()
|
return !task.CreatedDate.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasDueDate returns true if the task has a due date.
|
||||||
func (task *Task) HasDueDate() bool {
|
func (task *Task) HasDueDate() bool {
|
||||||
return !task.DueDate.IsZero()
|
return !task.DueDate.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCompletedDate returns true if the task has a completed date.
|
||||||
func (task *Task) HasCompletedDate() bool {
|
func (task *Task) HasCompletedDate() bool {
|
||||||
return !task.CompletedDate.IsZero()
|
return !task.CompletedDate.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading from *os.File allows to also use os.Stdin instead of just actual files
|
// LoadFromFile loads a TaskList from *os.File.
|
||||||
|
//
|
||||||
|
// Using *os.File instead of a filename allows to also use os.Stdin.
|
||||||
|
//
|
||||||
|
// Note: This will clear the current TaskList and overwrite it's contents with whatever is in *os.File.
|
||||||
func (tasklist *TaskList) LoadFromFile(file *os.File) error {
|
func (tasklist *TaskList) LoadFromFile(file *os.File) error {
|
||||||
*tasklist = []Task{} // Reset tasklist
|
*tasklist = []Task{} // Empty tasklist
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
task := Task{}
|
task := Task{}
|
||||||
task.Task = scanner.Text()
|
task.Original = scanner.Text()
|
||||||
|
|
||||||
// Check for priority
|
// Check for priority
|
||||||
if priorityRx.MatchString(task.Task) {
|
if priorityRx.MatchString(task.Original) {
|
||||||
task.Priority = priorityRx.FindStringSubmatch(task.Task)[1] // First match is priority value
|
task.Priority = priorityRx.FindStringSubmatch(task.Original)[1] // First match is priority value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for created date
|
// Check for created date
|
||||||
if createdDateRx.MatchString(task.Task) {
|
if createdDateRx.MatchString(task.Original) {
|
||||||
// Second match is created date value
|
// Second match is created date value
|
||||||
if date, err := time.Parse("2006-01-02", createdDateRx.FindStringSubmatch(task.Task)[2]); err != nil {
|
if date, err := time.Parse(DateLayout, createdDateRx.FindStringSubmatch(task.Original)[2]); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
task.CreatedDate = date
|
task.CreatedDate = date
|
||||||
@ -84,7 +124,9 @@ func (tasklist *TaskList) LoadFromFile(file *os.File) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience method, since most of the time tasks will be loaded from an actual file, called "todo.txt" most likely ;)
|
// LoadFromFilename loads a TaskList from a file (most likely called "todo.txt").
|
||||||
|
//
|
||||||
|
// Note: This will clear the current TaskList and overwrite it's contents with whatever is in the file.
|
||||||
func (tasklist *TaskList) LoadFromFilename(filename string) error {
|
func (tasklist *TaskList) LoadFromFilename(filename string) error {
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -94,3 +136,19 @@ func (tasklist *TaskList) LoadFromFilename(filename string) error {
|
|||||||
|
|
||||||
return tasklist.LoadFromFile(file)
|
return tasklist.LoadFromFile(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadFromFile loads and returns a TaskList from *os.File.
|
||||||
|
//
|
||||||
|
// Using *os.File instead of a filename allows to also use os.Stdin.
|
||||||
|
func LoadFromFile(file *os.File) (*TaskList, error) {
|
||||||
|
tasklist := &TaskList{}
|
||||||
|
err := tasklist.LoadFromFile(file)
|
||||||
|
return tasklist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFromFilename loads and returns a TaskList from a file (most likely called "todo.txt").
|
||||||
|
func LoadFromFilename(filename string) (*TaskList, error) {
|
||||||
|
tasklist := &TaskList{}
|
||||||
|
err := tasklist.LoadFromFilename(filename)
|
||||||
|
return tasklist, err
|
||||||
|
}
|
||||||
|
@ -17,31 +17,49 @@ func TestLoadFromFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
var tasklist TaskList
|
if tasklist, err := LoadFromFile(file); err != nil {
|
||||||
if err := tasklist.LoadFromFile(file); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
loadTest(t, *tasklist)
|
||||||
}
|
}
|
||||||
loadTest(t, tasklist)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadFromFilename(t *testing.T) {
|
func TestLoadFromFilename(t *testing.T) {
|
||||||
var tasklist TaskList
|
if tasklist, err := LoadFromFilename("todo.txt"); err != nil {
|
||||||
if err := tasklist.LoadFromFilename("todo.txt"); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
loadTest(t, *tasklist)
|
||||||
}
|
}
|
||||||
loadTest(t, tasklist)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTest(t *testing.T, tasklist TaskList) {
|
func loadTest(t *testing.T, tasklist TaskList) {
|
||||||
var expected, got interface{}
|
var expected, got interface{}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// count tasks
|
||||||
expected = 8
|
expected = 8
|
||||||
got = len(tasklist)
|
got = len(tasklist)
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("Expected TaskList to contain %d tasks, but got %d", expected, got)
|
t.Errorf("Expected TaskList to contain %d tasks, but got %d", expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// complete task strings
|
||||||
|
expected = "x Download Todo.txt mobile app @Phone"
|
||||||
|
got = tasklist[7].String()
|
||||||
|
if got != expected {
|
||||||
|
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"
|
||||||
|
got = tasklist[2].Task()
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("Expected third Task to be [%s], but got [%s]", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// task priority
|
||||||
expected = "B"
|
expected = "B"
|
||||||
got = tasklist[2].Priority
|
got = tasklist[2].Priority
|
||||||
if got != expected {
|
if got != expected {
|
||||||
@ -52,7 +70,9 @@ func loadTest(t *testing.T, tasklist TaskList) {
|
|||||||
t.Errorf("Expected fifth task to have no priority, but got '%s'", tasklist[4].Priority)
|
t.Errorf("Expected fifth task to have no priority, but got '%s'", tasklist[4].Priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err = time.Parse("2006-01-02", "2012-01-30")
|
// -------------------------------------------------------------------------------------
|
||||||
|
// task created date
|
||||||
|
expected, err = time.Parse(DateLayout, "2012-01-30")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -61,7 +81,7 @@ func loadTest(t *testing.T, tasklist TaskList) {
|
|||||||
t.Errorf("Expected first task to have created date '%s', but got '%v'", expected, got)
|
t.Errorf("Expected first task to have created date '%s', but got '%v'", expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err = time.Parse("2006-01-02", "2013-02-22")
|
expected, err = time.Parse(DateLayout, "2013-02-22")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -73,4 +93,19 @@ func loadTest(t *testing.T, tasklist TaskList) {
|
|||||||
if tasklist[4].HasCreatedDate() {
|
if tasklist[4].HasCreatedDate() {
|
||||||
t.Errorf("Expected fifth task to have no created date, but got '%v'", tasklist[4].CreatedDate)
|
t.Errorf("Expected fifth task to have no created date, but got '%v'", tasklist[4].CreatedDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// task due date
|
||||||
|
expected, err = time.Parse(DateLayout, "2014-01-01")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got = tasklist[2].DueDate
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("Expected third task to have due date '%s', but got '%v'", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tasklist[0].HasDueDate() {
|
||||||
|
t.Errorf("Expected first task to have no due date, but got '%v'", tasklist[0].DueDate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user