github.com/KEINOS/go-countline@v1.1.1-0.20221217083629-60710df7606b/cl/cl.go (about)

     1  package cl
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"io"
     7  	"sync"
     8  	"sync/atomic"
     9  
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  // CountLines counts the number of lines that contains a line break (LF) in a file.
    14  func CountLines(inputReader io.Reader) (int, error) {
    15  	if inputReader == nil {
    16  		return 0, errors.New("given reader is nil")
    17  	}
    18  
    19  	wg := new(sync.WaitGroup) //nolint:varnamelen
    20  	bufSize := bufio.MaxScanTokenSize
    21  	count := uint64(0)
    22  	bufReader := bufio.NewReader(inputReader)
    23  	lastBuf := make([]byte, bufSize)
    24  	numIte := 0
    25  
    26  	for {
    27  		numIte++
    28  		buf := make([]byte, bufSize*numIte)
    29  
    30  		numRead, err := bufReader.Read(buf) // loading chunk into buffer
    31  		if err != nil {
    32  			if err == io.EOF {
    33  				break
    34  			}
    35  
    36  			return 0, errors.Wrap(err, "failed to read from reader")
    37  		}
    38  
    39  		task := buf[:numRead]
    40  		lastBuf = task
    41  
    42  		wg.Add(1)
    43  
    44  		go func() {
    45  			found := bytes.Count(task, []byte{'\n'})
    46  
    47  			atomic.AddUint64(&count, uint64(found)) // count++ safely
    48  
    49  			wg.Done()
    50  		}()
    51  	}
    52  
    53  	wg.Wait()
    54  
    55  	// Detect the file ends without a line break and count up if so.
    56  	lenLastBuf := len(lastBuf)
    57  	hasFragment := false
    58  
    59  	for i := lenLastBuf; i > 0; i-- {
    60  		tmpChar := lastBuf[i-1]
    61  		if tmpChar == '\x00' {
    62  			continue
    63  		}
    64  
    65  		if tmpChar == '\n' {
    66  			break
    67  		}
    68  
    69  		hasFragment = true
    70  	}
    71  
    72  	if hasFragment {
    73  		atomic.AddUint64(&count, 1) // count++ safely
    74  	}
    75  
    76  	return int(count), nil
    77  }