diff --git a/cmd/pttodo-cli/cli.go b/cmd/pttodo-cli/cli.go index 1e51df6..3cda561 100644 --- a/cmd/pttodo-cli/cli.go +++ b/cmd/pttodo-cli/cli.go @@ -35,8 +35,8 @@ func _main() error { if !ok { defaultFilepath = "-" } - filepath := flag.String("f", defaultFilepath, "($PTTODO_FILE) path to yaml file") - filepathToMergeIn := flag.String("g", "", "path to yaml file to merge into -f") + filepath := flag.String("f", defaultFilepath, "($PTTODO_FILE) path to yaml file or dir (starting with root then alphabetical for dir)") + filepathToMergeIn := flag.String("g", "", "path to yaml file to merge into -f (modifies f)") root := flag.String("root", DUMP_TODO, "path to pretty print ("+fmt.Sprint([]string{DUMP_ALL, DUMP_TODO, DUMP_SCHEDULED, DUMP_DONE})+")") tags := flag.String("tags", "", "csv of all tags to find, -tag to invert") search := flag.String("search", "", "fts case insensitive") @@ -48,8 +48,17 @@ func _main() error { return err } } + filepaths := []string{*filepath} + if stat, err := os.Stat(*filepath); err != nil && !os.IsNotExist(err) { + return err + } else if err == nil && stat.IsDir() { + filepaths, err = listDir(*filepath) + if err != nil { + return err + } + } if *e { - if err := edit(*dry, *filepath); err != nil { + if err := edit(*dry, filepaths); err != nil { return err } } @@ -57,7 +66,7 @@ func _main() error { if *tags != "" { tagslist = strings.Split(*tags, ",") } - return dump(*dry, os.Stdout, *filepath, tagslist, *search, *root) + return dump(*dry, os.Stdout, filepaths, tagslist, *search, *root) } func verifyRoot(root pttodo.Root) error { @@ -79,16 +88,19 @@ func verifyRoot(root pttodo.Root) error { } func verifyFile(path string) error { - if err := dump(true, io.Discard, path, nil, "", DUMP_ALL); err != nil { + if err := dump(true, io.Discard, []string{path}, nil, "", DUMP_ALL); err != nil { return fmt.Errorf("failed verifying file %s: %w", path, err) } return nil } -func edit(dry bool, filepath string) error { - var tempFile string - cp := func() error { - f, err := ioutil.TempFile(os.TempDir(), path.Base(filepath)) +func edit(dry bool, filepaths []string) error { + tempDir, err := ioutil.TempDir(os.TempDir(), "edit-pttodo") + if err != nil { + return err + } + cpOne := func(filepath string) error { + f, err := os.Create(path.Join(tempDir, path.Base(filepath))) if err != nil { return err } @@ -103,14 +115,18 @@ func edit(dry bool, filepath string) error { g.Close() } f.Close() - tempFile = f.Name() + ".yaml" - return os.Rename(f.Name(), tempFile) + return nil + } + cp := func() error { + for _, filepath := range filepaths { + if err := cpOne(filepath); err != nil { + return err + } + } + return nil } vi := func() error { bin := "vim" - if editor := os.Getenv("EDITOR"); editor != "" { - bin = editor - } editorbin, err := exec.LookPath(bin) if err != nil { editorbin, err = exec.LookPath("vi") @@ -118,12 +134,17 @@ func edit(dry bool, filepath string) error { if err != nil { return err } - args := []string{editorbin, tempFile} + args := []string{editorbin, "-p"} + tempfiles, err := listDir(tempDir) + if err != nil { + return err + } + args = append(args, tempfiles...) cpid, err := syscall.ForkExec( editorbin, args, &syscall.ProcAttr{ - Dir: "", + Dir: tempDir, Env: os.Environ(), Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, Sys: nil, @@ -145,7 +166,7 @@ func edit(dry bool, filepath string) error { } return nil } - verify := func() error { + verifyOne := func(tempFile string) error { for { err := verifyFile(tempFile) if err == nil { @@ -162,13 +183,31 @@ func edit(dry bool, filepath string) error { } return verifyFile(tempFile) } - save := func() error { + verify := func() error { + for _, filepath := range filepaths { + tempFile := path.Join(tempDir, path.Base(filepath)) + if err := verifyOne(tempFile); err != nil { + return err + } + } + return nil + } + saveOne := func(filepath string) error { + tempFile := path.Join(tempDir, path.Base(filepath)) if dry { log.Printf("would've saved %s as %s", tempFile, filepath) return nil } return os.Rename(tempFile, filepath) } + save := func() error { + for _, filepath := range filepaths { + if err := saveOne(filepath); err != nil { + return err + } + } + return nil + } for _, foo := range []func() error{cp, vi, verify, save} { if err := foo(); err != nil { @@ -176,7 +215,7 @@ func edit(dry bool, filepath string) error { } } if !dry { - os.Remove(tempFile) + os.RemoveAll(tempDir) } return nil @@ -241,21 +280,38 @@ func marshalRootToTempFile(root pttodo.Root) (string, error) { return filepath, err } -func dump(dry bool, writer io.Writer, filepath string, tags []string, search, rootDisplay string) error { - reader, err := filePathReader(filepath) - if err != nil { - return err - } - - b, err := ioutil.ReadAll(reader) - if err != nil { - return err - } - +func dump(dry bool, writer io.Writer, filepaths []string, tags []string, search, rootDisplay string) error { var root pttodo.Root - if err := yaml.Unmarshal(b, &root); err != nil { - return err + + for _, filepath := range filepaths { + reader, err := filePathReader(filepath) + if err != nil { + return err + } + + b, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + + var root2 pttodo.Root + if err := yaml.Unmarshal(b, &root2); err != nil { + return err + } + root2.MoveScheduledToTodo() + root.MergeIn(root2) + + if !dry { + b3, err := yaml.Marshal(root2) + if err != nil { + return err + } + if err := os.WriteFile(filepath, b3, os.ModePerm); err != nil { + return err + } + } } + root.MoveScheduledToTodo() var v interface{} = root @@ -296,21 +352,13 @@ func dump(dry bool, writer io.Writer, filepath string, tags []string, search, ro } v = todos } + b2, err := yaml.Marshal(v) if err != nil { return err } fmt.Fprintf(writer, "%s\n", b2) - - if dry { - return nil - } - - b3, err := yaml.Marshal(root) - if err != nil { - return err - } - return os.WriteFile(filepath, b3, os.ModePerm) + return nil } func filePathReader(path string) (io.Reader, error) { @@ -326,3 +374,18 @@ func filePathReader(path string) (io.Reader, error) { } return reader, nil } + +func listDir(dname string) ([]string, error) { + entries, err := os.ReadDir(dname) + if err != nil { + return nil, err + } + paths := make([]string, 0, len(entries)) + for i := range entries { + if entries[i].IsDir() { + continue + } + paths = append(paths, path.Join(dname, entries[i].Name())) + } + return paths, nil +} diff --git a/cmd/pttodo-cli/cli_test.go b/cmd/pttodo-cli/cli_test.go new file mode 100644 index 0000000..d6a96c5 --- /dev/null +++ b/cmd/pttodo-cli/cli_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "strconv" + "testing" +) + +func TestListDir(t *testing.T) { + d := t.TempDir() + want := []string{} + for i := 0; i < 3; i++ { + p := path.Join(d, strconv.Itoa(i)) + ioutil.WriteFile(p, []byte{}, os.ModePerm) + want = append(want, p) + } + os.Mkdir(path.Join(d, "d"), os.ModePerm) + + got, err := listDir(d) + if err != nil { + t.Fatal(err) + } + if fmt.Sprint(want) != fmt.Sprint(got) { + t.Fatal(want, got) + } +} diff --git a/cmd/pttodo-cli/testdata/1.yaml b/cmd/pttodo-cli/testdata/1.yaml new file mode 100644 index 0000000..ca66683 --- /dev/null +++ b/cmd/pttodo-cli/testdata/1.yaml @@ -0,0 +1,4 @@ +todo: +- "1" +scheduled: [] +done: [] diff --git a/cmd/pttodo-cli/testdata/2.yaml b/cmd/pttodo-cli/testdata/2.yaml new file mode 100644 index 0000000..2c974d6 --- /dev/null +++ b/cmd/pttodo-cli/testdata/2.yaml @@ -0,0 +1,4 @@ +todo: +- "2" +scheduled: [] +done: [] diff --git a/cmd/pttodo-cli/testdata/3.yaml b/cmd/pttodo-cli/testdata/3.yaml new file mode 100644 index 0000000..10ac22b --- /dev/null +++ b/cmd/pttodo-cli/testdata/3.yaml @@ -0,0 +1,4 @@ +todo: +- "3" +scheduled: [] +done: []