package main import ( "bufio" "bytes" "flag" "fmt" "io" "os" "slices" "unicode/utf8" ) func isInputFromPipe() bool { fileInfo, _ := os.Stdin.Stat() return fileInfo.Mode()&os.ModeCharDevice == 0 } func fileByteCounter(f *os.File) int64 { fileInfo, err := f.Stat() if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } return fileInfo.Size() } func stdinByteCounter(f *os.File) int64 { buf := make([]byte, 32*1024) count := int64(0) for { c, err := f.Read(buf) // c==0 if EOF switch { case err == io.EOF: return count case err != nil: fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } count += int64(c) } } func lineCounter(f io.Reader) int64 { buf := make([]byte, 32*1024) count := int64(0) lineSep := []byte{'\n'} for { c, err := f.Read(buf) // c==0 if EOF switch { case err == io.EOF: return count case err != nil: fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } count += int64(bytes.Count(buf[:c], lineSep)) } } func wordCounter(f io.Reader) int64 { buf := make([]byte, 32*1024) count := int64(0) wordSep := []byte{' ', '\t', '\n', '\r', '\f', '\v'} inWord := false for { c, err := f.Read(buf) switch { case err == io.EOF: if inWord { count++ } return count case err != nil: fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } for _, b := range buf[:c] { if slices.Contains(wordSep, b) { if inWord { count++ } inWord = false } else { inWord = true } } } } func charCounter(f *os.File) int64 { var line string var err error r := bufio.NewReader(f) count := int64(0) for err == nil { line, err = r.ReadString('\n') count += int64(utf8.RuneCountInString(line)) } if err != io.EOF { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } return count } func main() { var f *os.File var fileName string var byteCounter func(f *os.File) int64 cFlag := flag.Bool("c", false, "print the byte count") lFlag := flag.Bool("l", false, "print the line count") wFlag := flag.Bool("w", false, "print the word count") mFlag := flag.Bool("m", false, "print the character count") flag.Parse() if isInputFromPipe() { f = os.Stdin if flag.NFlag() != 1 { fmt.Fprintf(os.Stderr, "must provide a single flag to gowc when reading from stdin\n") flag.Usage() os.Exit(1) } byteCounter = stdinByteCounter } else { fileName = flag.Arg(0) if len(fileName) == 0 { fmt.Fprintf(os.Stderr, "gowc [flags] filename\n") flag.Usage() os.Exit(1) } var err error f, err = os.Open(fileName) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } defer f.Close() byteCounter = fileByteCounter } switch { case *cFlag: fmt.Print("\t", byteCounter(f)) case *lFlag: fmt.Print("\t", lineCounter(f)) case *wFlag: fmt.Print("\t", wordCounter(f)) case *mFlag: fmt.Print("\t", charCounter(f)) default: b := byteCounter(f) f.Seek(0, 0) l := lineCounter(f) f.Seek(0, 0) w := wordCounter(f) fmt.Print("\t", l, "\t", w, "\t", b) } if len(fileName) > 0 { fmt.Println("\t", fileName) } else { fmt.Println("") } }