/
searchcsv.go
154 lines (140 loc) · 2.96 KB
/
searchcsv.go
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
import (
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"strings"
"github.com/mandolyte/csv-utils"
)
var cs *rangespec.RangeSpec
var re *regexp.Regexp
func main() {
pattern := flag.String("pattern", "", "Search pattern")
suppress := flag.Bool("v", false, "Omit rather than include matched rows")
cols := flag.String("c", "", "Range spec for columns")
input := flag.String("i", "", "Input CSV filename; default STDIN")
output := flag.String("o", "", "Output CSV filename; default STDOUT")
headers := flag.Bool("headers", true, "CSV has headers")
keep := flag.Bool("keep", true, "Keep CSV headers on output")
regex := flag.Bool("re", false, "Search pattern is a regular expression")
help := flag.Bool("help", false, "Show help message")
flag.Parse()
if *help {
usage("Help Message")
os.Exit(0)
}
/* check parameters */
if *pattern == "" {
usage("Required: Missing pattern for search")
os.Exit(0)
}
if *regex {
re = regexp.MustCompile(*pattern)
}
if *cols != "" {
var cserr error
cs, cserr = rangespec.New(*cols)
if cserr != nil {
log.Fatalf("Invalid column range spec:%v, Error:\n%v\n", *cols, cserr)
}
}
if *keep {
if !*headers {
log.Fatal("Cannot keep headers you don't have!")
}
}
// open output file
var w *csv.Writer
if *output == "" {
w = csv.NewWriter(os.Stdout)
} else {
fo, foerr := os.Create(*output)
if foerr != nil {
log.Fatal("os.Create() Error:" + foerr.Error())
}
defer fo.Close()
w = csv.NewWriter(fo)
}
// open input file
var r *csv.Reader
if *input == "" {
r = csv.NewReader(os.Stdin)
} else {
fi, fierr := os.Open(*input)
if fierr != nil {
log.Fatal("os.Open() Error:" + fierr.Error())
}
defer fi.Close()
r = csv.NewReader(fi)
}
// ignore expectations of fields per row
r.FieldsPerRecord = -1
// read loop for CSV
var row uint64
for {
// read the csv file
cells, rerr := r.Read()
if rerr == io.EOF {
break
}
if rerr != nil {
log.Fatalf("csv.Read:\n%v\n", rerr)
}
if (row == 0) && *headers && *keep {
row = 1
err := w.Write(cells)
if err != nil {
log.Fatalf("csv.Write:\n%v\n", err)
}
continue
}
row++
// test row/columns for a match
if patternMatches(cells, *pattern, *suppress) {
err := w.Write(cells)
if err != nil {
log.Fatalf("csv.Write:\n%v\n", err)
}
}
}
w.Flush()
}
func patternMatches(c []string, pattern string, suppress bool) bool {
found := false
for n, v := range c {
if cs == nil {
if re == nil {
found = strings.Contains(v, pattern)
} else {
found = re.MatchString(v)
}
} else {
if cs.InRange(uint64(n + 1)) {
if re == nil {
found = strings.Contains(v, pattern)
} else {
found = re.MatchString(v)
}
}
}
if found {
if suppress {
return false
}
return true
}
}
if suppress {
return true
}
return false
}
func usage(msg string) {
fmt.Println(msg + "\n")
fmt.Print("Usage: searchcsv [options]\n")
flag.PrintDefaults()
}