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 }