github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/pkg/term/runner.go (about)

     1  package term
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"syscall"
    11  )
    12  
    13  type Runner struct {
    14  	stdout io.Writer
    15  	stderr io.Writer
    16  	dir    string
    17  	stdin  io.Reader
    18  }
    19  
    20  type RunnerOption func(*Runner)
    21  
    22  func WithStdout(stdout io.Writer) RunnerOption {
    23  	return func(r *Runner) {
    24  		r.stdout = stdout
    25  	}
    26  }
    27  
    28  func WithStderr(stderr io.Writer) RunnerOption {
    29  	return func(r *Runner) {
    30  		r.stderr = stderr
    31  	}
    32  }
    33  
    34  func WithStdin(stdin io.Reader) RunnerOption {
    35  	return func(r *Runner) {
    36  		r.stdin = stdin
    37  	}
    38  }
    39  
    40  func WithDir(path string) RunnerOption {
    41  	return func(r *Runner) {
    42  		r.dir = path
    43  	}
    44  }
    45  
    46  func New(opts ...RunnerOption) *Runner {
    47  	r := &Runner{
    48  		stderr: os.Stderr,
    49  		stdout: os.Stdout,
    50  		dir:    ".",
    51  	}
    52  
    53  	for _, opt := range opts {
    54  		opt(r)
    55  	}
    56  
    57  	return r
    58  }
    59  
    60  type Option func(cmd *exec.Cmd)
    61  
    62  func (r Runner) Run(cmd *exec.Cmd) (stdout, stderr string, exitCode int, err error) {
    63  	if r.stdin != nil {
    64  		cmd.Stdin = os.Stdin
    65  	}
    66  
    67  	outReader, err := cmd.StdoutPipe()
    68  	if err != nil {
    69  		return
    70  	}
    71  	errReader, err := cmd.StderrPipe()
    72  	if err != nil {
    73  		return
    74  	}
    75  
    76  	var bufOut, bufErr bytes.Buffer
    77  	outReader2 := io.TeeReader(outReader, &bufOut)
    78  	errReader2 := io.TeeReader(errReader, &bufErr)
    79  
    80  	if err = cmd.Start(); err != nil {
    81  		return
    82  	}
    83  
    84  	go r.printOutputWithHeader("", outReader2)
    85  	go r.printOutputWithHeader("", errReader2)
    86  
    87  	err = cmd.Wait()
    88  
    89  	stdout = bufOut.String()
    90  	stderr = bufErr.String()
    91  
    92  	if err != nil {
    93  		if err2, ok := err.(*exec.ExitError); ok {
    94  			if s, ok := err2.Sys().(syscall.WaitStatus); ok {
    95  				err = nil
    96  				exitCode = s.ExitStatus()
    97  			}
    98  		}
    99  	}
   100  	return
   101  }
   102  
   103  func (r Runner) printOutputWithHeader(header string, reader io.Reader) {
   104  	scanner := bufio.NewScanner(reader)
   105  	for scanner.Scan() {
   106  		if r.stdout != nil {
   107  			fmt.Fprintf(r.stdout, "%s%s\n", header, scanner.Text())
   108  		} else {
   109  			fmt.Printf("%s%s\n", header, scanner.Text())
   110  		}
   111  	}
   112  }