55 "context"
66 "fmt"
77 "io"
8+ "os"
89 "strings"
910
1011 "mvdan.cc/sh/v3/interp"
@@ -21,10 +22,13 @@ type Recorder struct {
2122 // Transcript captures the recorded output in cmdt format.
2223 Transcript bytes.Buffer
2324
24- needsBlank bool
25- runner * interp.Runner
26- stdoutBuf bytes.Buffer
27- stderrBuf bytes.Buffer
25+ needsBlank bool
26+ runner * interp.Runner
27+ stdoutBuf bytes.Buffer
28+ stderrBuf bytes.Buffer
29+ fileCount int // Counter for auto-generated binary file names
30+ preferredFiles []string // List of preferred filenames in order (stderr first, then stdout)
31+ fileIndex int // Current position in preferredFiles slice
2832}
2933
3034func (rec * Recorder ) Init () error {
@@ -34,6 +38,8 @@ func (rec *Recorder) Init() error {
3438 io .MultiWriter (& rec .stdoutBuf , orDiscard (rec .Stdout )),
3539 io .MultiWriter (& rec .stderrBuf , orDiscard (rec .Stderr )),
3640 ))
41+ rec .preferredFiles = make ([]string , 0 )
42+ rec .fileIndex = 0
3743 return err
3844}
3945
@@ -44,42 +50,78 @@ func orDiscard(w io.Writer) io.Writer {
4450 return w
4551}
4652
53+ // SetPreferredFiles sets the list of preferred filenames in order.
54+ // Files should be provided in deterministic order (stderr first, then stdout).
55+ func (rec * Recorder ) SetPreferredFiles (files []string ) {
56+ rec .preferredFiles = make ([]string , len (files ))
57+ copy (rec .preferredFiles , files )
58+ rec .fileIndex = 0
59+ }
60+
61+ // generateBinaryFilename creates a filename, preferring existing names when available.
62+ // Uses deterministic ordering (stderr first, then stdout) to consume preferred filenames.
63+ func (rec * Recorder ) generateBinaryFilename () string {
64+ // Check if we have a preferred filename available.
65+ if rec .fileIndex < len (rec .preferredFiles ) {
66+ filename := rec .preferredFiles [rec .fileIndex ]
67+ rec .fileIndex ++
68+ return filename
69+ }
70+
71+ // Fall back to auto-generated filename.
72+ rec .fileCount ++
73+ return fmt .Sprintf ("%03d.bin" , rec .fileCount )
74+ }
75+
4776func (rec * Recorder ) flush () error {
4877 // Write stderr first (usually empty, text-only, important not to miss).
49- if err := rec .flushBuffer (& rec .stderrBuf , "2" ); err != nil {
78+ if err := rec .flushBuffer (& rec .stderrBuf , 2 ); err != nil {
5079 return err
5180 }
5281 // Then write stdout.
53- if err := rec .flushBuffer (& rec .stdoutBuf , "1" ); err != nil {
82+ if err := rec .flushBuffer (& rec .stdoutBuf , 1 ); err != nil {
5483 return err
5584 }
5685 return nil
5786}
5887
59- func (rec * Recorder ) flushBuffer (buf * bytes.Buffer , prefix string ) error {
88+ // flushBuffer processes output from a command and writes it to the transcript.
89+ // Individual command outputs are expected to be reasonably small (not streaming large files).
90+ func (rec * Recorder ) flushBuffer (buf * bytes.Buffer , fd int ) error {
6091 if buf .Len () == 0 {
6192 return nil
6293 }
63-
94+
6495 data := buf .Bytes ()
6596 buf .Reset ()
66-
67- // Add prefix to each line and write to transcript.
97+
98+ // Check if data is binary.
99+ if isBinary (data ) {
100+ // Write binary data to file and reference it.
101+ filename := rec .generateBinaryFilename ()
102+ if err := os .WriteFile (filename , data , 0644 ); err != nil {
103+ return fmt .Errorf ("writing binary file %q: %w" , filename , err )
104+ }
105+ fmt .Fprintf (& rec .Transcript , "%d< %s\n " , fd , filename )
106+ return nil
107+ }
108+
109+ // Handle text output - add prefix to each line and write to transcript.
68110 for line := range bytes .Lines (data ) {
69111 if len (line ) == 1 && line [0 ] == '\n' {
70112 // Empty line - just prefix.
71- fmt .Fprintf (& rec .Transcript , "%s \n " , prefix )
113+ fmt .Fprintf (& rec .Transcript , "%d \n " , fd )
72114 } else {
73115 // Non-empty line - prefix + space + line.
74- fmt .Fprintf (& rec .Transcript , "%s %s" , prefix , line )
116+ fmt .Fprintf (& rec .Transcript , "%d %s" , fd , line )
75117 }
76118 }
77-
119+
78120 // Handle case where original didn't end with newline.
79121 if len (data ) > 0 && data [len (data )- 1 ] != '\n' {
80122 io .WriteString (& rec .Transcript , "\n % no-newline\n " )
81123 }
82-
124+
83125 return nil
84126}
85127
0 commit comments