github.com/charlievieth/fastwalk@v1.0.3/examples/fwwc/main.go (about)

     1  // fwwc is a an example program that recursively walks directories and
     2  // prints the number of lines in each file it encounters.
     3  package main
     4  
     5  import (
     6  	"bytes"
     7  	"flag"
     8  	"fmt"
     9  	"io"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/charlievieth/fastwalk"
    15  )
    16  
    17  var newLine = []byte{'\n'}
    18  
    19  // countLinesInFile returns the number of newlines ('\n') in file name.
    20  func countLinesInFile(name string) (int64, error) {
    21  	f, err := os.Open(name)
    22  	if err != nil {
    23  		return 0, err
    24  	}
    25  	defer f.Close()
    26  
    27  	buf := make([]byte, 16*1024)
    28  	var lines int64
    29  	for {
    30  		n, e := f.Read(buf)
    31  		if n > 0 {
    32  			lines += int64(bytes.Count(buf[:n], newLine))
    33  		}
    34  		if e != nil {
    35  			if e != io.EOF {
    36  				err = e
    37  			}
    38  			break
    39  		}
    40  	}
    41  	return lines, err
    42  }
    43  
    44  func LineCount(root string, followLinks bool) error {
    45  	countLinesWalkFn := func(path string, d fs.DirEntry, err error) error {
    46  		// We wrap this with fastwalk.IgnorePermissionErrors so we know the
    47  		// error is not a permission error (common when walking outside a users
    48  		// home directory) and is likely something worse so we should return it
    49  		// and abort the walk.
    50  		//
    51  		// A common error here is "too many open files", which can occur if the
    52  		// walkFn opens, but does not close, files.
    53  		if err != nil {
    54  			return err
    55  		}
    56  
    57  		// If the entry is a symbolic link get the type of file that
    58  		// it references.
    59  		typ := d.Type()
    60  		if typ&fs.ModeSymlink != 0 {
    61  			if fi, err := fastwalk.StatDirEntry(path, d); err == nil {
    62  				typ = fi.Mode().Type()
    63  			}
    64  		}
    65  
    66  		// Skip dot (".") files (but allow "." / PWD as the path)
    67  		if path != "." && typ.IsDir() {
    68  			name := d.Name()
    69  			if name == "" || name[0] == '.' || name[0] == '_' {
    70  				return fastwalk.SkipDir
    71  			}
    72  			return nil
    73  		}
    74  		if typ.IsRegular() {
    75  			lines, err := countLinesInFile(path)
    76  			if err == nil {
    77  				fmt.Printf("%8d %s\n", lines, path)
    78  			} else {
    79  				// Print but do not return the error.
    80  				fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
    81  			}
    82  		}
    83  		return nil
    84  	}
    85  
    86  	// Ignore permission errors traversing directories.
    87  	//
    88  	// Note: this only ignores permission errors when traversing directories.
    89  	// Permission errors may still be encountered when accessing files.
    90  	walkFn := fastwalk.IgnorePermissionErrors(countLinesWalkFn)
    91  
    92  	conf := fastwalk.Config{
    93  		// Safely follow symbolic links. This can also be achieved by
    94  		// wrapping walkFn with fastwalk.FollowSymlinks().
    95  		Follow: followLinks,
    96  
    97  		// If NumWorkers is ≤ 0 the default is used, which is sufficient
    98  		// for most use cases.
    99  	}
   100  	// Note: Walk can also be called with a nil Config, in which case
   101  	// fastwalk.DefaultConfig is used.
   102  	if err := fastwalk.Walk(&conf, root, walkFn); err != nil {
   103  		return fmt.Errorf("walking directory %s: %w", root, err)
   104  	}
   105  	return nil
   106  }
   107  
   108  const UsageMsg = `Usage: %[1]s [-L] [PATH...]:
   109  
   110  %[1]s prints the number of lines in each file it finds,
   111  ignoring directories that start with '.' or '_'.
   112  
   113  `
   114  
   115  func main() {
   116  	flag.Usage = func() {
   117  		fmt.Fprintf(os.Stdout, UsageMsg, filepath.Base(os.Args[0]))
   118  		flag.PrintDefaults()
   119  	}
   120  	followLinks := flag.Bool("L", false, "Follow symbolic links")
   121  	flag.Parse()
   122  
   123  	args := flag.Args()
   124  	if len(args) == 0 {
   125  		args = append(args, ".")
   126  	}
   127  	for _, root := range args {
   128  		// fmt.Println("ROOT:", root)
   129  		if err := LineCount(root, *followLinks); err != nil {
   130  			fmt.Fprintln(os.Stderr, "error:", err)
   131  			os.Exit(1)
   132  		}
   133  	}
   134  }