aboutsummaryrefslogtreecommitdiff
path: root/examples/countlines.go
blob: 000efa57d536c33ecbe79c6bccecb6a211601c22 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"unicode/utf8"

	"git.sr.ht/~gabrielgio/pipe"
)

var (
	InvalidInput = errors.New("It was given a file instead of a folder.")
	ColorGreen   = "\033[32m"
	ColorRed     = "\033[31m"
	ColorReset   = "\033[0m"
)

type (
	NonTextError struct {
		Filename string
	}
)

func NewNonTextError(filename string) *NonTextError {
	return &NonTextError{Filename: filename}
}

func (b *NonTextError) Error() string {
	return fmt.Sprintf("Non text file %s", b.Filename)
}

func main() {
	err := run()
	if err != nil {
		fmt.Printf("Error on main process: %s", err.Error())
	}
}

func run() error {
	var (
		dir   = flag.String("dir", "", "Folder to recursively search for files. Default: \"\"")
		quiet = flag.Bool("quiet", false, "If true only outputs the result. Default: false")
		nproc = flag.Int("nproc", 4, "Number of goroutines used to count. Default: 4")
	)

	flag.Parse()

	info, err := os.Stat(*dir)
	if err != nil {
		return err
	}

	if !info.IsDir() {
		return InvalidInput
	}

	cerr := make(chan error)
	go func(c <-chan error) {
		for e := range c {
			if !*quiet {
				fmt.Printf("%sERROR%s: %s\n", ColorRed, ColorReset, e.Error())
			}
		}
	}(cerr)

	c := count(*dir, *nproc, cerr)
	fmt.Printf("%sCOUNT%s: %d\n", ColorGreen, ColorReset, c)

	return nil
}

func count(dir string, nproc int, cerr chan<- error) int {
	cfiles := walkFolder(dir)
	ccount := pipe.ProcWithError(cfiles, cerr, nproc, countLines)
	return pipe.TailReduceWithError(ccount, cerr, sum, 0)
}

func sum(acc, lines int) (int, error) {
	return acc + lines, nil
}

func countLines(filename string) (int, error) {

	file, err := os.Open(filename)
	if err != nil {
		return 0, err
	}

	defer file.Close()

	fileScanner := bufio.NewScanner(file)
	fileScanner.Split(bufio.ScanLines)

	var count int
	for fileScanner.Scan() {
		if !utf8.ValidString(string(fileScanner.Text())) {
			return 0, NewNonTextError(filename)
		}
		count++
	}

	return count, nil
}

func walkFolder(folder string) <-chan string {
	c := make(chan string)

	go func(folder string, c chan string) {
		filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
			file, err := os.Open(path)
			if err != nil {
				return filepath.SkipDir
			}
			defer file.Close()

			fileInfo, err := file.Stat()
			if err != nil {
				return filepath.SkipDir
			}

			if !fileInfo.IsDir() {
				c <- path
			}
			return nil
		})
		close(c)

	}(folder, c)

	return c
}