package cli import ( "bytes" "errors" "flag" "fmt" "io" "io/ioutil" "os" "github.com/pelletier/go-toml/v2" ) type ConvertFn func(r io.Reader, w io.Writer) error type Program struct { Usage string Fn ConvertFn // Inplace allows the command to take more than one file as argument and // perform convertion in place on each provided file. Inplace bool } func (p *Program) Execute() { flag.Usage = func() { fmt.Fprintf(os.Stderr, p.Usage) } flag.Parse() os.Exit(p.main(flag.Args(), os.Stdin, os.Stdout, os.Stderr)) } func (p *Program) main(files []string, input io.Reader, output, error io.Writer) int { err := p.run(files, input, output) if err != nil { var derr *toml.DecodeError if errors.As(err, &derr) { fmt.Fprintln(error, derr.String()) row, col := derr.Position() fmt.Fprintln(error, "error occurred at row", row, "column", col) } else { fmt.Fprintln(error, err.Error()) } return -1 } return 0 } func (p *Program) run(files []string, input io.Reader, output io.Writer) error { if len(files) > 0 { if p.Inplace { return p.runAllFilesInPlace(files) } f, err := os.Open(files[0]) if err != nil { return err } defer f.Close() input = f } return p.Fn(input, output) } func (p *Program) runAllFilesInPlace(files []string) error { for _, path := range files { err := p.runFileInPlace(path) if err != nil { return err } } return nil } func (p *Program) runFileInPlace(path string) error { in, err := ioutil.ReadFile(path) if err != nil { return err } out := new(bytes.Buffer) err = p.Fn(bytes.NewReader(in), out) if err != nil { return err } return ioutil.WriteFile(path, out.Bytes(), 0600) }