-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
109 lines (95 loc) · 3.14 KB
/
main.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
/*
Package main provides a command line tool for filtering JSON lines.
// https://github.com/tidwall/gjson/blob/master/SYNTAX.md
It uses GJSON query syntax, which is designed to query JSON arrays;
this program will ephemerally convert each line is not already a JSON array
in order to be able to use this syntax.
If the query result for the line (optionally wrapped into an array) is Existing,
the original line will be printed to stdout.
*/
package main
import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/tidwall/gjson"
)
var flagMatchAll = flag.String("match-all", "", "match all of these properties (gjson syntax, comma separated queries)")
var flagMatchAny = flag.String("match-any", "", "match any of these properties (gjson syntax, comma separated queries)")
var flagMatchNone = flag.String("match-none", "", "match none of these properties (gjson syntax, comma separated queries)")
var errInvalidMatchAll = errors.New("invalid match-all")
var errInvalidMatchAny = errors.New("invalid match-any")
var errInvalidMatchNone = errors.New("invalid match-none")
var errInvalidLine = errors.New("invalid line (is it valid JSON?)")
func filterStream(reader io.Reader, writer io.Writer, matchAll []string, matchAny []string, matchNone []string) {
breader := bufio.NewReader(reader)
bwriter := bufio.NewWriter(writer)
readLoop:
for {
read, err := breader.ReadBytes('\n')
if err != nil {
if errors.Is(err, os.ErrClosed) || errors.Is(err, io.EOF) {
break
}
log.Fatalln(err)
}
if err := filter(read, matchAll, matchAny, matchNone); err != nil {
if errors.Is(err, errInvalidLine) {
log.Fatalln(err)
}
continue readLoop
}
bwriter.Write(read)
bwriter.Flush()
}
}
// filter filters some read line on the matchAll, matchAny, and matchNone queries.
// These queries should be written in GJSON query syntax.
// https://github.com/tidwall/gjson/blob/master/SYNTAX.md
func filter(read []byte, matchAll []string, matchAny []string, matchNone []string) error {
parsed := gjson.ParseBytes(read)
if !parsed.Exists() {
return errInvalidLine
}
// Here we hack the line into an array containing only this datapoint.
// This allows us to use the GJSON query syntax, which is designed for use with arrays, not single objects.
if !parsed.IsArray() {
read = []byte(fmt.Sprintf("[%s]", string(read)))
}
for _, query := range matchAll {
if res := gjson.GetBytes(read, query); !res.Exists() {
return fmt.Errorf("%w: %s", errInvalidMatchAll, query)
}
}
didMatchAny := len(matchAny) == 0
for _, query := range matchAny {
if gjson.GetBytes(read, query).Exists() {
didMatchAny = true
break
}
}
if !didMatchAny {
return fmt.Errorf("%w: %s", errInvalidMatchAny, matchAny)
}
for _, query := range matchNone {
if gjson.GetBytes(read, query).Exists() {
return fmt.Errorf("%w: %s", errInvalidMatchNone, query)
}
}
return nil
}
func splitFlagStringSlice(s string) []string {
if s == "" {
return []string{}
}
return strings.Split(s, ",")
}
func main() {
flag.Parse()
filterStream(os.Stdin, os.Stdout, splitFlagStringSlice(*flagMatchAll), splitFlagStringSlice(*flagMatchAny), splitFlagStringSlice(*flagMatchNone))
}