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 }