013 Project 13: Build a wc Clone

013 Build a wc Clone

Count lines, words, and bytes across files.

file -> scanner/read -> count L/W/B -> per file + total

Full main.go

package main

import (
    "bufio"
    "flag"
    "fmt"
    "io"
    "os"
    "unicode"
)

type counts struct {
    lines int
    words int
    bytes int
}

func countReader(r io.Reader) (counts, error) {
    var c counts
    s := bufio.NewScanner(r)
    for s.Scan() {
        line := s.Text()
        c.lines++
        c.bytes += len(line) + 1
        inWord := false
        for _, ch := range line {
            if unicode.IsSpace(ch) {
                inWord = false
                continue
            }
            if !inWord {
                c.words++
                inWord = true
            }
        }
    }
    return c, s.Err()
}

func main() {
    flag.Parse()
    if flag.NArg() == 0 {
        c, err := countReader(os.Stdin)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
        fmt.Printf("%8d %8d %8d\n", c.lines, c.words, c.bytes)
        return
    }

    var total counts
    for _, path := range flag.Args() {
        f, err := os.Open(path)
        if err != nil {
            fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
            continue
        }
        c, err := countReader(f)
        _ = f.Close()
        if err != nil {
            fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
            continue
        }
        total.lines += c.lines
        total.words += c.words
        total.bytes += c.bytes
        fmt.Printf("%8d %8d %8d %s\n", c.lines, c.words, c.bytes, path)
    }
    if flag.NArg() > 1 {
        fmt.Printf("%8d %8d %8d total\n", total.lines, total.words, total.bytes)
    }
}

Run

go run . README.md
go run . *.qmd

Step-by-Step Explanation

  1. Parse command-line flags and validate inputs early.
  2. Keep the core operation in a small, testable function.
  3. Process data as a stream when possible to reduce memory use.
  4. Print stable output and meaningful exit codes.
  5. Add one extension feature and test edge cases.

Code Anatomy

  • main handles flags, orchestration, and errors.
  • Worker/helper functions hold business logic.
  • Output section should be deterministic for scripting and CI usage.

Learning Goals

  • Write composable Unix-style Go tools.
  • Improve error messages and operator experience.
  • Practice iterative improvement over one clear baseline.