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 }