@ 260812.1 · fetching
current
// fwwc is a an example program that recursively walks directories and
// prints the number of lines in each file it encounters.
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"

	"github.com/charlievieth/fastwalk"
)

var newLine = []byte{'\n'}

// countLinesInFile returns the number of newlines ('\n') in file name.
func countLinesInFile(name string) (int64, error) {
	f, err := os.Open(name)
	if err != nil {
		return 0, err
	}
	defer f.Close()

	buf := make([]byte, 16*1024)
	var lines int64
	for {
		n, e := f.Read(buf)
		if n > 0 {
			lines += int64(bytes.Count(buf[:n], newLine))
		}
		if e != nil {
			if e != io.EOF {
				err = e
			}
			break
		}
	}
	return lines, err
}

func LineCount(root string, followLinks bool) error {
	countLinesWalkFn := func(path string, d fs.DirEntry, err error) error {
		// We wrap this with fastwalk.IgnorePermissionErrors so we know the
		// error is not a permission error (common when walking outside a users
		// home directory) and is likely something worse so we should return it
		// and abort the walk.
		//
		// A common error here is "too many open files", which can occur if the
		// walkFn opens, but does not close, files.
		if err != nil {
			return err
		}

		// If the entry is a symbolic link get the type of file that
		// it references.
		typ := d.Type()
		if typ&fs.ModeSymlink != 0 {
			if fi, err := fastwalk.StatDirEntry(path, d); err == nil {
				typ = fi.Mode().Type()
			}
		}

		// Skip dot (".") files (but allow "." / PWD as the path)
		if path != "." && typ.IsDir() {
			name := d.Name()
			if name == "" || name[0] == '.' || name[0] == '_' {
				return fastwalk.SkipDir
			}
			return nil
		}
		if typ.IsRegular() {
			lines, err := countLinesInFile(path)
			if err == nil {
				fmt.Printf("%8d %s\n", lines, path)
			} else {
				// Print but do not return the error.
				fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
			}
		}
		return nil
	}

	// Ignore permission errors traversing directories.
	//
	// Note: this only ignores permission errors when traversing directories.
	// Permission errors may still be encountered when accessing files.
	walkFn := fastwalk.IgnorePermissionErrors(countLinesWalkFn)

	conf := fastwalk.Config{
		// Safely follow symbolic links. This can also be achieved by
		// wrapping walkFn with fastwalk.FollowSymlinks().
		Follow: followLinks,

		// If NumWorkers is ≤ 0 the default is used, which is sufficient
		// for most use cases.
	}
	// Note: Walk can also be called with a nil Config, in which case
	// fastwalk.DefaultConfig is used.
	if err := fastwalk.Walk(&conf, root, walkFn); err != nil {
		return fmt.Errorf("walking directory %s: %w", root, err)
	}
	return nil
}

const UsageMsg = `Usage: %[1]s [-L] [PATH...]:

%[1]s prints the number of lines in each file it finds,
ignoring directories that start with '.' or '_'.

`

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stdout, UsageMsg, filepath.Base(os.Args[0]))
		flag.PrintDefaults()
	}
	followLinks := flag.Bool("L", false, "Follow symbolic links")
	flag.Parse()

	args := flag.Args()
	if len(args) == 0 {
		args = append(args, ".")
	}
	for _, root := range args {
		// fmt.Println("ROOT:", root)
		if err := LineCount(root, *followLinks); err != nil {
			fmt.Fprintln(os.Stderr, "error:", err)
			os.Exit(1)
		}
	}
}