package main import ( "bytes" "fmt" "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 fmt.Errorf("failed to parse yaml in %s: %w", config.target, 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 fmt.Errorf("failed to parse yaml in %s: %w", config.target, err) } if err := yaml.Unmarshal(archiveB, &archive); err != nil { return fmt.Errorf("failed to parse yaml in %s: %w", config.archive, 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 fmt.Errorf("failed to parse yaml in %s: %w", config.target, err) } if err := yaml.Unmarshal(mergingB, &merging); err != nil { return fmt.Errorf("failed to parse yaml in %s: %w", config.target2, 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 fmt.Errorf("failed to parse yaml in %s: %w", filepath, err) } if err := yaml.Unmarshal(mergingB, &merging); err != nil { return fmt.Errorf("failed to parse yaml in %s: %w", mergeTargetFilePath, 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 }