115 lines
2.2 KiB
Go
115 lines
2.2 KiB
Go
package pttodo
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"os"
|
|
"time"
|
|
|
|
yaml "gopkg.in/yaml.v2"
|
|
)
|
|
|
|
type Todo struct {
|
|
Todo string
|
|
Details string `yaml:",omitempty"`
|
|
Schedule Schedule `yaml:",omitempty"`
|
|
Tags string `yaml:",omitempty"`
|
|
Subtasks []Todo `yaml:",omitempty"`
|
|
TS TS `yaml:",omitempty"`
|
|
writeTS bool
|
|
}
|
|
|
|
func NewTodosFromFile(p string) ([]Todo, error) {
|
|
f, err := os.Open(p)
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var result []Todo
|
|
if err := yaml.NewDecoder(f).Decode(&result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse yaml from %s: %w", p, err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (todo Todo) ID() string {
|
|
hash := crc32.NewIEEE()
|
|
fmt.Fprintf(hash, "%d:%s", 0, todo.Todo)
|
|
fmt.Fprintf(hash, "%d:%s", 1, todo.Details)
|
|
fmt.Fprintf(hash, "%d:%s", 2, todo.Schedule)
|
|
fmt.Fprintf(hash, "%d:%s", 3, todo.Tags)
|
|
for i := range todo.Subtasks {
|
|
fmt.Fprintf(hash, "%d:%s", 4, todo.Subtasks[i].ID())
|
|
}
|
|
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
|
}
|
|
|
|
func (todo Todo) Triggered() bool {
|
|
last := todo.TS
|
|
next, err := todo.Schedule.Next(last.time())
|
|
return err == nil && time.Now().After(next)
|
|
}
|
|
|
|
func (todo Todo) MarshalYAML() (interface{}, error) {
|
|
if !todo.writeTS {
|
|
todo.TS = 0
|
|
} else {
|
|
todo.TS = TS(todo.TS.time().Unix())
|
|
}
|
|
if fmt.Sprint(todo) == fmt.Sprint(Todo{Todo: todo.Todo}) {
|
|
return todo.Todo, nil
|
|
}
|
|
type Alt Todo
|
|
return (Alt)(todo), nil
|
|
}
|
|
|
|
func (todo *Todo) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
*todo = Todo{}
|
|
if err := unmarshal(&todo.Todo); err == nil {
|
|
return nil
|
|
}
|
|
type Alt Todo
|
|
alt := (*Alt)(todo)
|
|
return unmarshal(alt)
|
|
}
|
|
|
|
func (todo Todo) Equals(other Todo) bool {
|
|
if !equalTodoSlices(todo.Subtasks, other.Subtasks) {
|
|
return false
|
|
}
|
|
if todo.TS != other.TS {
|
|
return false
|
|
}
|
|
if todo.Tags != other.Tags {
|
|
return false
|
|
}
|
|
if todo.Schedule != other.Schedule {
|
|
return false
|
|
}
|
|
if todo.Details != other.Details {
|
|
return false
|
|
}
|
|
if todo.Todo != other.Todo {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func equalTodoSlices(a, b []Todo) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if !a[i].Equals(b[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|