github.com/jmigpin/editor@v1.6.0/util/osutil/cmdi.go (about)

     1  package osutil
     2  
     3  import (
     4  	"context"
     5  	"os/exec"
     6  
     7  	"github.com/jmigpin/editor/util/iout"
     8  )
     9  
    10  type CmdI interface {
    11  	Cmd() *exec.Cmd
    12  	Start() error
    13  	Wait() error
    14  }
    15  
    16  func NewCmdI(cmd *exec.Cmd) CmdI {
    17  	return NewBasicCmd(cmd)
    18  }
    19  func RunCmdI(ci CmdI) error {
    20  	if err := ci.Start(); err != nil {
    21  		return err
    22  	}
    23  	return ci.Wait()
    24  }
    25  
    26  //----------
    27  
    28  type BasicCmd struct {
    29  	cmd *exec.Cmd
    30  }
    31  
    32  func NewBasicCmd(cmd *exec.Cmd) *BasicCmd {
    33  	return &BasicCmd{cmd: cmd}
    34  }
    35  func (c *BasicCmd) Cmd() *exec.Cmd {
    36  	return c.cmd
    37  }
    38  func (c *BasicCmd) Start() error {
    39  	return c.cmd.Start()
    40  }
    41  func (c *BasicCmd) Wait() error {
    42  	return c.cmd.Wait()
    43  }
    44  
    45  //----------
    46  
    47  type ShellCmd struct {
    48  	CmdI
    49  }
    50  
    51  func NewShellCmd(cmdi CmdI) *ShellCmd {
    52  	c := &ShellCmd{CmdI: cmdi}
    53  	cmd := c.CmdI.Cmd()
    54  	cmd.Args = ShellRunArgs(cmd.Args...)
    55  
    56  	// update cmd.path with shell executable
    57  	name := cmd.Args[0]
    58  	cmd.Path = name
    59  	if lp, err := exec.LookPath(name); err == nil {
    60  		cmd.Path = lp
    61  	}
    62  
    63  	return c
    64  }
    65  
    66  //----------
    67  
    68  type SetSidCmd struct {
    69  	CmdI
    70  	done context.CancelFunc
    71  }
    72  
    73  func NewSetSidCmd(ctx context.Context, cmdi CmdI) *SetSidCmd {
    74  	c := &SetSidCmd{CmdI: cmdi}
    75  	SetupExecCmdSysProcAttr(c.CmdI.Cmd())
    76  
    77  	ctx2, cancel := context.WithCancel(ctx)
    78  	c.done = cancel // clear resources
    79  	go func() {
    80  		select {
    81  		case <-ctx2.Done():
    82  			// either the cmd is running and should be killed, or it reached the end and this goroutine should be unblocked
    83  			_ = KillExecCmd(c.CmdI.Cmd()) // effective kill
    84  		}
    85  	}()
    86  	return c
    87  }
    88  func (c *SetSidCmd) Start() error {
    89  	if err := c.CmdI.Start(); err != nil {
    90  		c.done()
    91  		return err
    92  	}
    93  	return nil
    94  }
    95  func (c *SetSidCmd) Wait() error {
    96  	defer c.done()
    97  	return c.CmdI.Wait()
    98  }
    99  
   100  //----------
   101  
   102  // ex: usefull to print something before any cmd output is printed
   103  type CallbackOnStartCmd struct {
   104  	CmdI
   105  	callback func(CmdI)
   106  	stdout   *iout.PausedWriter
   107  	stderr   *iout.PausedWriter
   108  }
   109  
   110  func NewCallbackOnStartCmd(cmdi CmdI, cb func(CmdI)) *CallbackOnStartCmd {
   111  	c := &CallbackOnStartCmd{CmdI: cmdi, callback: cb}
   112  	cmd := c.CmdI.Cmd()
   113  	if cmd.Stdout != nil {
   114  		c.stdout = iout.NewPausedWriter(cmd.Stdout)
   115  	}
   116  	if cmd.Stderr != nil {
   117  		c.stderr = iout.NewPausedWriter(cmd.Stderr)
   118  	}
   119  	return c
   120  }
   121  func (c *CallbackOnStartCmd) Start() error {
   122  	if err := c.CmdI.Start(); err != nil {
   123  		c.unpause()
   124  		return err
   125  	}
   126  	c.callback(c)
   127  	c.unpause()
   128  	return nil
   129  }
   130  func (c *CallbackOnStartCmd) Wait() error {
   131  	c.unpause()
   132  	return c.CmdI.Wait()
   133  }
   134  func (c *CallbackOnStartCmd) unpause() {
   135  	if c.stdout != nil {
   136  		c.stdout.Unpause()
   137  	}
   138  	if c.stderr != nil {
   139  		c.stderr.Unpause()
   140  	}
   141  }