// Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "bufio" "io" ) // NamedScanner is a simple struct to pair a name with a Scanner. type NamedScanner struct { Name string Scanner *bufio.Scanner } // NamedReader is a simple struct to pair a name with a Reader, // which will be converted to a Scanner using bufio.NewScanner. type NamedReader struct { Name string Reader io.Reader } // MultiScanner scans over multiple bufio.Scanners as if they were a single stream. // It also keeps track of the name of the current scanner and the line number. type MultiScanner struct { scanners []NamedScanner scannerIdx int line int totalLine int err error } // NewMultiScanner creates a new MultiScanner from slice of NamedScanners. func NewMultiScanner(scanners []NamedScanner) *MultiScanner { return &MultiScanner{ scanners: scanners, scannerIdx: -1, // Start before the first scanner } } // MultiScannerFromReaders creates a new MultiScanner from a slice of NamedReaders. func MultiScannerFromReaders(readers []NamedReader) *MultiScanner { var scanners []NamedScanner for _, r := range readers { scanners = append(scanners, NamedScanner{ Name: r.Name, Scanner: bufio.NewScanner(r.Reader), }) } return NewMultiScanner(scanners) } // Scan advances the scanner to the next token, which will then be // available through the Text method. It returns false when the scan stops, // either by reaching the end of the input or an error. // After Scan returns false, the Err method will return any error that // occurred during scanning, except that if it was io.EOF, Err // will return nil. func (ms *MultiScanner) Scan() bool { if ms.scannerIdx == -1 { ms.scannerIdx = 0 } for ms.scannerIdx < len(ms.scanners) { current := ms.scanners[ms.scannerIdx] if current.Scanner.Scan() { ms.line++ ms.totalLine++ return true } if err := current.Scanner.Err(); err != nil { ms.err = err return false } // Move to the next scanner ms.scannerIdx++ ms.line = 0 } return false } // Text returns the most recent token generated by a call to Scan. func (ms *MultiScanner) Text() string { if ms.scannerIdx < 0 || ms.scannerIdx >= len(ms.scanners) { return "" } return ms.scanners[ms.scannerIdx].Scanner.Text() } // Err returns the first non-EOF error that was encountered by the MultiScanner. func (ms *MultiScanner) Err() error { return ms.err } // Name returns the name of the current scanner. func (ms *MultiScanner) Name() string { if ms.scannerIdx < 0 { return "" } if ms.scannerIdx >= len(ms.scanners) { return "" } return ms.scanners[ms.scannerIdx].Name } // Line returns the current line number within the current scanner. func (ms *MultiScanner) Line() int { return ms.line } // TotalLine returns the total number of lines scanned across all scanners. func (ms *MultiScanner) TotalLine() int { return ms.totalLine }