302 lines
6.2 KiB
Go
302 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
|
|
"slices"
|
|
|
|
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
const (
|
|
DUMP_ALL = "all"
|
|
DUMP_TODO = "todo"
|
|
DUMP_SCHEDULED = "scheduled"
|
|
DUMP_DONE = "done"
|
|
)
|
|
|
|
func main() {
|
|
if err := _main(); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func _main() error {
|
|
config := getConfig()
|
|
|
|
if config.target2 != "" {
|
|
return merge(&config)
|
|
} else if config.archive != "" {
|
|
return archive(&config)
|
|
} else if config.dedupe {
|
|
return dedupe(&config)
|
|
} else {
|
|
if err := add(&config); err != nil {
|
|
return err
|
|
}
|
|
if err := edit(&config); err != nil {
|
|
return err
|
|
}
|
|
return dump(config)
|
|
}
|
|
}
|
|
|
|
func dedupe(config *config) error {
|
|
baseReader, err := filePathReader(config.target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
baseB, err := ioutil.ReadAll(baseReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var base pttodo.Root
|
|
if err := yaml.Unmarshal(baseB, &base); err != nil {
|
|
return err
|
|
}
|
|
|
|
do := func(todos []pttodo.Todo) []pttodo.Todo {
|
|
i := len(todos) - 1
|
|
for i >= 0 {
|
|
j := slices.IndexFunc(todos, func(todo pttodo.Todo) bool {
|
|
return todo.Equals(todos[i])
|
|
})
|
|
if j >= 0 && j != i {
|
|
todos[j] = todos[i]
|
|
base.Done = append(base.Done, todos[j])
|
|
todos = todos[:i]
|
|
}
|
|
i -= 1
|
|
}
|
|
return todos
|
|
}
|
|
|
|
base.Todo = do(base.Todo)
|
|
base.Scheduled = do(base.Scheduled)
|
|
|
|
if tmppath, err := marshalRootToTempFile(base); err != nil {
|
|
return err
|
|
} else if config.dryRun {
|
|
log.Println("===before===")
|
|
_dump(os.Stdout, []string{config.target}, config.tags, config.search, config.root)
|
|
log.Println("===after===")
|
|
_dump(os.Stdout, []string{tmppath}, config.tags, config.search, config.root)
|
|
} else if err := rename(tmppath, config.target); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func archive(config *config) error {
|
|
if config.archive == "" {
|
|
return nil
|
|
}
|
|
|
|
baseReader, err := filePathReader(config.target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
baseB, err := ioutil.ReadAll(baseReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
archiveReader, err := filePathReader(config.archive)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
archiveB, err := ioutil.ReadAll(archiveReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var base, archive pttodo.Root
|
|
if err := yaml.Unmarshal(baseB, &base); err != nil {
|
|
return err
|
|
}
|
|
if err := yaml.Unmarshal(archiveB, &archive); err != nil {
|
|
return err
|
|
}
|
|
|
|
archive.Done = append(archive.Done, base.Done...)
|
|
if tmppath2, err := marshalRootToTempFile(archive); err != nil {
|
|
return err
|
|
} else if config.dryRun {
|
|
} else if err := rename(tmppath2, config.archive); err != nil {
|
|
return err
|
|
}
|
|
|
|
base.Done = base.Done[:0]
|
|
if tmppath, err := marshalRootToTempFile(base); err != nil {
|
|
return err
|
|
} else if config.dryRun {
|
|
log.Println("===before===")
|
|
_dump(os.Stdout, []string{config.target}, config.tags, config.search, config.root)
|
|
log.Println("===after===")
|
|
_dump(os.Stdout, []string{tmppath}, config.tags, config.search, config.root)
|
|
} else if err := rename(tmppath, config.target); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func merge(config *config) error {
|
|
if config.target2 == "" {
|
|
return nil
|
|
}
|
|
|
|
baseReader, err := filePathReader(config.target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
baseB, err := ioutil.ReadAll(baseReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mergingReader, err := filePathReader(config.target2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mergingB, err := ioutil.ReadAll(mergingReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var base, merging pttodo.Root
|
|
if err := yaml.Unmarshal(baseB, &base); err != nil {
|
|
return err
|
|
}
|
|
if err := yaml.Unmarshal(mergingB, &merging); err != nil {
|
|
return err
|
|
}
|
|
|
|
slurp := func(dst, src *[]pttodo.Todo) {
|
|
_src := *src
|
|
for i := range _src {
|
|
found := false
|
|
for j := range *dst {
|
|
found = found || _src[i].Equals((*dst)[j])
|
|
}
|
|
if !found {
|
|
*dst = append(*dst, _src[i])
|
|
}
|
|
}
|
|
}
|
|
slurp(&base.Todo, &merging.Todo)
|
|
slurp(&base.Scheduled, &merging.Scheduled)
|
|
slurp(&base.Done, &merging.Done)
|
|
|
|
if tmppath, err := marshalRootToTempFile(base); err != nil {
|
|
return err
|
|
} else if config.dryRun {
|
|
log.Println("===before===")
|
|
_dump(os.Stdout, []string{config.target}, config.tags, config.search, config.root)
|
|
log.Println("===after===")
|
|
_dump(os.Stdout, []string{tmppath}, config.tags, config.search, config.root)
|
|
} else if err := rename(tmppath, config.target); err != nil {
|
|
return err
|
|
}
|
|
|
|
merging.Done = append(merging.Done, merging.Todo...)
|
|
merging.Todo = merging.Todo[:0]
|
|
merging.Scheduled = append(merging.Done, merging.Scheduled...)
|
|
merging.Scheduled = merging.Scheduled[:0]
|
|
if tmppath2, err := marshalRootToTempFile(merging); err != nil {
|
|
return err
|
|
} else if config.dryRun {
|
|
} else if err := rename(tmppath2, config.target2); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func _merge(filepath string, mergeTargetFilePath string) error {
|
|
baseReader, err := filePathReader(filepath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
baseB, err := ioutil.ReadAll(baseReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mergingReader, err := filePathReader(mergeTargetFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mergingB, err := ioutil.ReadAll(mergingReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var base, merging pttodo.Root
|
|
if err := yaml.Unmarshal(baseB, &base); err != nil {
|
|
return err
|
|
}
|
|
if err := yaml.Unmarshal(mergingB, &merging); err != nil {
|
|
return err
|
|
}
|
|
|
|
base.MergeIn(merging)
|
|
|
|
tmppath, err := marshalRootToTempFile(base)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return rename(tmppath, filepath)
|
|
}
|
|
|
|
func rename(oldpath, newpath string) error {
|
|
b, err := os.ReadFile(oldpath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.WriteFile(newpath, b, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Remove(oldpath)
|
|
}
|
|
|
|
func marshalRootToTempFile(root pttodo.Root) (string, error) {
|
|
f, err := ioutil.TempFile(os.TempDir(), "tmp")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
f.Close()
|
|
os.Remove(f.Name())
|
|
b, err := yaml.Marshal(root)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
filepath := f.Name() + ".yaml"
|
|
err = ioutil.WriteFile(filepath, b, os.ModePerm)
|
|
return filepath, err
|
|
}
|
|
|
|
func filePathReader(path string) (io.Reader, error) {
|
|
if path == "-" {
|
|
return os.Stdin, nil
|
|
}
|
|
b, err := ioutil.ReadFile(path)
|
|
if os.IsNotExist(err) {
|
|
return bytes.NewReader([]byte("{}")), nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bytes.NewReader(b), nil
|
|
}
|