2014-01-03 00:29:54 +00:00
|
|
|
/* 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/. */
|
|
|
|
|
2014-01-03 12:01:04 +00:00
|
|
|
// 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
|
2014-01-03 00:29:54 +00:00
|
|
|
package todotxt
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2014-01-13 19:27:58 +00:00
|
|
|
"errors"
|
2014-01-03 12:01:04 +00:00
|
|
|
"fmt"
|
2014-01-03 19:00:40 +00:00
|
|
|
"io/ioutil"
|
2014-01-03 00:29:54 +00:00
|
|
|
"os"
|
2014-01-03 13:21:27 +00:00
|
|
|
"strings"
|
2014-01-03 00:29:54 +00:00
|
|
|
)
|
|
|
|
|
2014-01-03 12:01:04 +00:00
|
|
|
// TaskList represents a list of todo.txt task entries.
|
|
|
|
// It is usually loaded from a whole todo.txt file.
|
2014-01-03 00:29:54 +00:00
|
|
|
type TaskList []Task
|
|
|
|
|
2019-02-04 04:38:59 +00:00
|
|
|
// IgnoreComments can be set to 'true', to ignore lines that start with #
|
2014-01-03 19:00:40 +00:00
|
|
|
// The todo.txt format does not define comments.
|
2014-01-03 00:29:54 +00:00
|
|
|
var (
|
2014-01-04 13:13:37 +00:00
|
|
|
// 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.
|
2019-02-04 04:38:59 +00:00
|
|
|
IgnoreComments = false
|
2014-01-03 00:29:54 +00:00
|
|
|
)
|
|
|
|
|
2014-01-13 19:27:58 +00:00
|
|
|
// NewTaskList creates a new empty TaskList.
|
|
|
|
func NewTaskList() TaskList {
|
|
|
|
tasklist := TaskList{}
|
|
|
|
return tasklist
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a complete list of tasks in todo.txt format.
|
2014-01-03 19:00:40 +00:00
|
|
|
func (tasklist TaskList) String() (text string) {
|
|
|
|
for _, task := range tasklist {
|
2014-01-03 16:35:38 +00:00
|
|
|
text += fmt.Sprintf("%s\n", task.String())
|
|
|
|
}
|
|
|
|
return text
|
|
|
|
}
|
|
|
|
|
2014-01-16 11:18:21 +00:00
|
|
|
// AddTask appends a Task to the current TaskList and takes care to set the Task.Id correctly, modifying the Task by the given pointer!
|
2014-01-13 19:27:58 +00:00
|
|
|
func (tasklist *TaskList) AddTask(task *Task) {
|
2014-01-13 14:37:21 +00:00
|
|
|
task.Id = 0
|
|
|
|
for _, t := range *tasklist {
|
|
|
|
if t.Id > task.Id {
|
|
|
|
task.Id = t.Id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
task.Id += 1
|
|
|
|
|
|
|
|
*tasklist = append(*tasklist, *task)
|
2014-01-13 19:27:58 +00:00
|
|
|
}
|
|
|
|
|
2014-01-16 11:18:21 +00:00
|
|
|
// 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.
|
2014-01-13 19:27:58 +00:00
|
|
|
func (tasklist *TaskList) GetTask(id int) (*Task, error) {
|
2014-01-16 11:18:21 +00:00
|
|
|
for i := range *tasklist {
|
|
|
|
if ([]Task(*tasklist))[i].Id == id {
|
|
|
|
return &([]Task(*tasklist))[i], nil
|
2014-01-13 19:27:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, errors.New("task not found")
|
|
|
|
}
|
|
|
|
|
2014-01-16 11:18:21 +00:00
|
|
|
// RemoveTaskById removes any Task with given Task 'id' from the TaskList.
|
|
|
|
// Returns an error if no Task was removed.
|
2014-01-13 19:27:58 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-01-16 11:18:21 +00:00
|
|
|
// RemoveTask removes any Task from the TaskList with the same String representation as the given Task.
|
|
|
|
// Returns an error if no Task was removed.
|
2014-01-13 19:27:58 +00:00
|
|
|
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
|
2014-01-13 14:37:21 +00:00
|
|
|
}
|
|
|
|
|
2014-01-03 12:01:04 +00:00
|
|
|
// 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.
|
2014-01-03 00:29:54 +00:00
|
|
|
func (tasklist *TaskList) LoadFromFile(file *os.File) error {
|
2014-01-03 12:01:04 +00:00
|
|
|
*tasklist = []Task{} // Empty tasklist
|
2014-01-03 00:29:54 +00:00
|
|
|
|
2014-01-13 14:37:21 +00:00
|
|
|
taskId := 1
|
2014-01-03 00:29:54 +00:00
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
2014-01-13 14:37:21 +00:00
|
|
|
text := strings.Trim(scanner.Text(), "\t\n\r ") // Read line
|
2014-01-03 00:29:54 +00:00
|
|
|
|
2014-01-03 16:35:38 +00:00
|
|
|
// Ignore blank or comment lines
|
2014-01-13 14:37:21 +00:00
|
|
|
if text == "" || (IgnoreComments && strings.HasPrefix(text, "#")) {
|
2014-01-03 16:35:38 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-01-13 14:37:21 +00:00
|
|
|
task, err := ParseTask(text)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-01-03 00:29:54 +00:00
|
|
|
}
|
2014-01-13 14:37:21 +00:00
|
|
|
task.Id = taskId
|
2014-01-03 00:29:54 +00:00
|
|
|
|
2014-01-13 14:37:21 +00:00
|
|
|
*tasklist = append(*tasklist, *task)
|
|
|
|
taskId++
|
2014-01-03 00:29:54 +00:00
|
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:00:40 +00:00
|
|
|
// WriteToFile writes a TaskList to *os.File.
|
|
|
|
//
|
|
|
|
// Using *os.File instead of a filename allows to also use os.Stdout.
|
|
|
|
func (tasklist *TaskList) WriteToFile(file *os.File) error {
|
|
|
|
writer := bufio.NewWriter(file)
|
|
|
|
_, err := writer.WriteString(tasklist.String())
|
|
|
|
writer.Flush()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-01-03 12:01:04 +00:00
|
|
|
// 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.
|
2014-01-03 00:29:54 +00:00
|
|
|
func (tasklist *TaskList) LoadFromFilename(filename string) error {
|
|
|
|
file, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
return tasklist.LoadFromFile(file)
|
|
|
|
}
|
2014-01-03 12:01:04 +00:00
|
|
|
|
2014-01-03 19:00:40 +00:00
|
|
|
// WriteToFilename writes a TaskList to the specified file (most likely called "todo.txt").
|
|
|
|
func (tasklist *TaskList) WriteToFilename(filename string) error {
|
2014-01-04 15:32:40 +00:00
|
|
|
return ioutil.WriteFile(filename, []byte(tasklist.String()), 0640)
|
2014-01-03 19:00:40 +00:00
|
|
|
}
|
|
|
|
|
2014-01-03 12:01:04 +00:00
|
|
|
// LoadFromFile loads and returns a TaskList from *os.File.
|
|
|
|
//
|
|
|
|
// Using *os.File instead of a filename allows to also use os.Stdin.
|
2014-01-03 19:00:40 +00:00
|
|
|
func LoadFromFile(file *os.File) (TaskList, error) {
|
|
|
|
tasklist := TaskList{}
|
2014-01-04 13:13:37 +00:00
|
|
|
if err := tasklist.LoadFromFile(file); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return tasklist, nil
|
2014-01-03 12:01:04 +00:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:00:40 +00:00
|
|
|
// WriteToFile writes a TaskList to *os.File.
|
|
|
|
//
|
|
|
|
// Using *os.File instead of a filename allows to also use os.Stdout.
|
|
|
|
func WriteToFile(tasklist *TaskList, file *os.File) error {
|
|
|
|
return tasklist.WriteToFile(file)
|
|
|
|
}
|
|
|
|
|
2014-01-03 12:01:04 +00:00
|
|
|
// LoadFromFilename loads and returns a TaskList from a file (most likely called "todo.txt").
|
2014-01-03 19:00:40 +00:00
|
|
|
func LoadFromFilename(filename string) (TaskList, error) {
|
|
|
|
tasklist := TaskList{}
|
2014-01-04 13:13:37 +00:00
|
|
|
if err := tasklist.LoadFromFilename(filename); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return tasklist, nil
|
2014-01-03 12:01:04 +00:00
|
|
|
}
|
2014-01-03 19:00:40 +00:00
|
|
|
|
|
|
|
// WriteToFilename writes a TaskList to the specified file (most likely called "todo.txt").
|
|
|
|
func WriteToFilename(tasklist *TaskList, filename string) error {
|
|
|
|
return tasklist.WriteToFilename(filename)
|
|
|
|
}
|