| // 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 "<before first>" |
| } |
| if ms.scannerIdx >= len(ms.scanners) { |
| return "<after last>" |
| } |
| 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 |
| } |