Skip to content

Commit 507316b

Browse files
bartdeboerbitfield
andauthored
Update First(n int) to read no more data than necessary (#199)
First(N) now closes its output after it has read N lines, terminating the pipe without consuming unnecessary further input (apart from the 4KiB scanner buffer). Co-authored-by: John Arundel <[email protected]>
1 parent cfa12bb commit 507316b

File tree

2 files changed

+28
-7
lines changed

2 files changed

+28
-7
lines changed

Diff for: script.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -514,21 +514,24 @@ func (p *Pipe) FilterScan(filter func(string, io.Writer)) *Pipe {
514514

515515
// First produces only the first n lines of the pipe's contents, or all the
516516
// lines if there are less than n. If n is zero or negative, there is no output
517-
// at all.
517+
// at all. When n lines have been produced, First stops reading its input and
518+
// sends EOF to its output.
518519
func (p *Pipe) First(n int) *Pipe {
519520
if p.Error() != nil {
520521
return p
521522
}
522523
if n <= 0 {
523524
return NewPipe()
524525
}
525-
i := 0
526-
return p.FilterScan(func(line string, w io.Writer) {
527-
if i >= n {
528-
return
526+
return p.Filter(func(r io.Reader, w io.Writer) error {
527+
scanner := newScanner(r)
528+
for i := 0; i < n && scanner.Scan(); i++ {
529+
_, err := fmt.Fprintln(w, scanner.Text())
530+
if err != nil {
531+
return err
532+
}
529533
}
530-
fmt.Fprintln(w, line)
531-
i++
534+
return scanner.Err()
532535
})
533536
}
534537

Diff for: script_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,24 @@ func TestFirstHasNoEffectGivenLessThanNInputLines(t *testing.T) {
573573
}
574574
}
575575

576+
func TestFirstDoesNotConsumeUnnecessaryData(t *testing.T) {
577+
t.Parallel()
578+
// First uses a 4096-byte buffer, so will always read at least
579+
// that much, but no more (once N lines have been read).
580+
r := strings.NewReader(strings.Repeat("line\n", 1000))
581+
got, err := script.NewPipe().WithReader(r).First(1).String()
582+
if err != nil {
583+
t.Fatal(err)
584+
}
585+
want := "line\n"
586+
if want != got {
587+
t.Errorf("want output %q, got %q", want, got)
588+
}
589+
if r.Len() == 0 {
590+
t.Errorf("no data left in reader")
591+
}
592+
}
593+
576594
func TestFreqHandlesLongLines(t *testing.T) {
577595
t.Parallel()
578596
got, err := script.Echo(longLine).Freq().Slice()

0 commit comments

Comments
 (0)