@@ -15,6 +15,8 @@ import (
1515 "sync"
1616 "syscall"
1717 "time"
18+
19+ "golang.org/x/term"
1820)
1921
2022type envMode int
@@ -71,6 +73,7 @@ type Cmd struct {
7173 stderrW io.Writer
7274
7375 sysProcAttr * syscall.SysProcAttr
76+ onExecCmd func (* exec.Cmd )
7477
7578 next * Cmd
7679 root * Cmd
@@ -374,6 +377,9 @@ func (c *Cmd) OnStderr(fn func(string)) *Cmd {
374377// StdoutWriter sets a raw writer for stdout.
375378// @group Streaming
376379//
380+ // When the writer is a terminal and no line callbacks or combined output are enabled,
381+ // execx passes stdout through directly and does not buffer it for results.
382+ //
377383// Example: stdout writer
378384//
379385// var out strings.Builder
@@ -390,6 +396,9 @@ func (c *Cmd) StdoutWriter(w io.Writer) *Cmd {
390396// StderrWriter sets a raw writer for stderr.
391397// @group Streaming
392398//
399+ // When the writer is a terminal and no line callbacks or combined output are enabled,
400+ // execx passes stderr through directly and does not buffer it for results.
401+ //
393402// Example: stderr writer
394403//
395404// var out strings.Builder
@@ -407,6 +416,21 @@ func (c *Cmd) StderrWriter(w io.Writer) *Cmd {
407416 return c
408417}
409418
419+ // OnExecCmd registers a callback to mutate the underlying exec.Cmd before start.
420+ // @group Execution
421+ //
422+ // Example: exec cmd
423+ //
424+ // _, _ = execx.Command("printf", "hi").
425+ // OnExecCmd(func(cmd *exec.Cmd) {
426+ // cmd.SysProcAttr = &syscall.SysProcAttr{}
427+ // }).
428+ // Run()
429+ func (c * Cmd ) OnExecCmd (fn func (* exec.Cmd )) * Cmd {
430+ c .onExecCmd = fn
431+ return c
432+ }
433+
410434// Pipe appends a new command to the pipeline. Pipelines run on all platforms.
411435// @group Pipelining
412436//
@@ -808,10 +832,28 @@ func (c *Cmd) execCmd() *exec.Cmd {
808832 if c .sysProcAttr != nil {
809833 cmd .SysProcAttr = c .sysProcAttr
810834 }
835+ if c .onExecCmd != nil {
836+ c .onExecCmd (cmd )
837+ }
811838 return cmd
812839}
813840
841+ var isTerminalFunc = term .IsTerminal
842+
843+ func isTerminalWriter (w io.Writer ) bool {
844+ f , ok := w .(* os.File )
845+ if ! ok {
846+ return false
847+ }
848+ return isTerminalFunc (int (f .Fd ()))
849+ }
850+
814851func (c * Cmd ) stdoutWriter (buf * bytes.Buffer , withCombined bool , combined * bytes.Buffer , shadow * shadowContext ) io.Writer {
852+ if c .stdoutW != nil && c .onStdout == nil && ! withCombined {
853+ if isTerminalWriter (c .stdoutW ) {
854+ return c .stdoutW
855+ }
856+ }
815857 writers := []io.Writer {}
816858 if c .stdoutW != nil {
817859 writers = append (writers , c .stdoutW )
@@ -831,6 +873,11 @@ func (c *Cmd) stdoutWriter(buf *bytes.Buffer, withCombined bool, combined *bytes
831873}
832874
833875func (c * Cmd ) stderrWriter (buf * bytes.Buffer , withCombined bool , combined * bytes.Buffer , shadow * shadowContext ) io.Writer {
876+ if c .stderrW != nil && c .onStderr == nil && ! withCombined {
877+ if isTerminalWriter (c .stderrW ) {
878+ return c .stderrW
879+ }
880+ }
834881 writers := []io.Writer {}
835882 if c .stderrW != nil {
836883 writers = append (writers , c .stderrW )
@@ -854,6 +901,7 @@ type lineWriter struct {
854901 buf bytes.Buffer
855902}
856903
904+ // Write buffers output and emits completed lines to the callback.
857905func (l * lineWriter ) Write (p []byte ) (int , error ) {
858906 if l .onLine == nil {
859907 return len (p ), nil
@@ -933,8 +981,10 @@ type shadowContext struct {
933981type ShadowPhase string
934982
935983const (
984+ // ShadowBefore labels the pre-execution shadow print.
936985 ShadowBefore ShadowPhase = "before"
937- ShadowAfter ShadowPhase = "after"
986+ // ShadowAfter labels the post-execution shadow print.
987+ ShadowAfter ShadowPhase = "after"
938988)
939989
940990// ShadowEvent captures details for ShadowPrint formatting.
@@ -1015,6 +1065,7 @@ func wrapShadowWriter(out io.Writer, shadow *shadowContext) io.Writer {
10151065 return out
10161066}
10171067
1068+ // Write forwards output while tracking spacing for shadow output.
10181069func (s * shadowOutputWriter ) Write (p []byte ) (int , error ) {
10191070 if s .ctx != nil && s .ctx .spacing && len (p ) > 0 {
10201071 s .ctx .mu .Lock ()
0 commit comments