package main import ( "fmt" "io/ioutil" "log" "os" "os/exec" "path" "syscall" "time" "github.com/google/uuid" "gogs.inhome.blapointe.com/bel/pttodo/pttodo" "gopkg.in/yaml.v2" ) func edit(config *config) error { if !config.edit { return nil } return _edit(config.targets) } func _edit(filepaths []string) error { tempDir, err := ioutil.TempDir(os.TempDir(), "edit-pttodo-*") if err != nil { return err } originals := map[string]pttodo.Root{} lastModified := map[string]time.Time{} for _, target := range filepaths { var original pttodo.Root if r, err := filePathReader(target); err != nil { return err } else if err := yaml.NewDecoder(r).Decode(&original); err != nil { return err } else if c, err := yaml.Marshal(original.Todo); err != nil { return err } else if err := ioutil.WriteFile(path.Join(tempDir, path.Base(target)), c, os.ModePerm); err != nil { return err } originals[target] = original if info, _ := os.Stat(target); info != nil { lastModified[target] = info.ModTime() } else { lastModified[target] = time.Time{} } } if err := vimd(tempDir); err != nil { return err } for _, target := range filepaths { for { err := func() error { var todos []pttodo.Todo if r, err := filePathReader(path.Join(tempDir, path.Base(target))); err != nil { return err } else if err := yaml.NewDecoder(r).Decode(&todos); err != nil { return err } return nil }() if err == nil { break } log.Printf("%s, press to resume editing", err) b := make([]byte, 1) if _, err := os.Stdin.Read(b); err != nil { return err } if err := vimd(tempDir); err != nil { return err } } } for _, target := range filepaths { r, err := filePathReader(path.Join(tempDir, path.Base(target))) if err != nil { return err } var newTodos []pttodo.Todo if err := yaml.NewDecoder(r).Decode(&newTodos); err != nil { return err } original := originals[target] newTodoIDs := map[string]struct{}{} for i := range newTodos { newTodoIDs[newTodos[i].ID()] = struct{}{} } for i := range original.Todo { if _, ok := newTodoIDs[original.Todo[i].ID()]; !ok { original.Todo[i].TS = pttodo.TS(time.Now().Unix()) if string(original.Todo[i].Schedule) != "" && !original.Todo[i].Triggered() { original.Scheduled = append(original.Scheduled, original.Todo[i]) } original.Done = append(original.Done, original.Todo[i]) } } original.Todo = newTodos original.AutoMove() c, err := yaml.Marshal(original) if err != nil { return err } outputPath := target if info, _ := os.Stat(target); info != nil && info.ModTime() != lastModified[target] { outputPath = fmt.Sprintf("%s.%s.%s", outputPath, uuid.New().String(), path.Ext(outputPath)) } if err := ioutil.WriteFile(outputPath, c, os.ModePerm); err != nil { return err } } return nil } func vimd(d string) error { bin := "vim" editorbin, err := exec.LookPath(bin) if err != nil { editorbin, err = exec.LookPath("vi") } if err != nil { return err } args := []string{editorbin, "-p"} files, err := listDir(d) if err != nil { return err } args = append(args, files...) cpid, err := syscall.ForkExec( editorbin, args, &syscall.ProcAttr{ Dir: d, Env: os.Environ(), Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, Sys: nil, }, ) if err != nil { return err } proc, err := os.FindProcess(cpid) if err != nil { return err } state, err := proc.Wait() if err != nil { return err } if exitCode := state.ExitCode(); exitCode != 0 { return fmt.Errorf("bad exit code on vim: %d, state: %+v", exitCode, state) } return nil }