forked from tomnomnom/gron
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
228 lines (188 loc) · 5.35 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
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package main
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"sort"
"github.com/fatih/color"
"github.com/nwidger/jsoncolor"
"github.com/pkg/errors"
)
// Exit codes
const (
exitOK = iota
exitOpenFile
exitReadInput
exitFormStatements
exitFetchURL
exitParseStatements
exitJSONEncode
)
// Output colors
var (
strColor = color.New(color.FgYellow)
braceColor = color.New(color.FgMagenta)
bareColor = color.New(color.FgBlue, color.Bold)
numColor = color.New(color.FgRed)
boolColor = color.New(color.FgCyan)
)
// gronVersion stores the current gron version, set at build
// time with the ldflags -X option
var gronVersion = "dev"
func init() {
flag.Usage = func() {
h := "Transform JSON (from a file, URL, or stdin) into discrete assignments to make it greppable\n\n"
h += "Usage:\n"
h += " gron [OPTIONS] [FILE|URL|-]\n\n"
h += "Options:\n"
h += " -u, --ungron Reverse the operation (turn assignments back into JSON)\n"
h += " -m, --monochrome Monochrome (don't colorize output)\n"
h += " --version Print version information\n\n"
h += "Exit Codes:\n"
h += fmt.Sprintf(" %d\t%s\n", exitOK, "OK")
h += fmt.Sprintf(" %d\t%s\n", exitOpenFile, "Failed to open file")
h += fmt.Sprintf(" %d\t%s\n", exitReadInput, "Failed to read input")
h += fmt.Sprintf(" %d\t%s\n", exitFormStatements, "Failed to form statements")
h += fmt.Sprintf(" %d\t%s\n", exitFetchURL, "Failed to fetch URL")
h += fmt.Sprintf(" %d\t%s\n", exitParseStatements, "Failed to parse statements")
h += fmt.Sprintf(" %d\t%s\n", exitJSONEncode, "Failed to encode JSON")
h += "\n"
h += "Examples:\n"
h += " gron /tmp/apiresponse.json\n"
h += " gron http://jsonplaceholder.typicode.com/users/1 \n"
h += " curl -s http://jsonplaceholder.typicode.com/users/1 | gron\n"
h += " gron http://jsonplaceholder.typicode.com/users/1 | grep company | gron --ungron\n"
fmt.Fprintf(os.Stderr, h)
}
}
func main() {
var (
ungronFlag bool
monochromeFlag bool
versionFlag bool
)
flag.BoolVar(&ungronFlag, "ungron", false, "Turn statements into JSON instead")
flag.BoolVar(&ungronFlag, "u", false, "Turn statements into JSON instead")
flag.BoolVar(&monochromeFlag, "monochrome", false, "Monochrome (don't colorize output)")
flag.BoolVar(&monochromeFlag, "m", false, "Monochrome (don't colorize output)")
flag.BoolVar(&versionFlag, "version", false, "Print version information")
flag.Parse()
// Print version information
if versionFlag {
fmt.Printf("gron version %s\n", gronVersion)
os.Exit(exitOK)
}
var raw io.Reader
filename := flag.Arg(0)
if filename == "" || filename == "-" {
raw = os.Stdin
} else {
if !validURL(filename) {
r, err := os.Open(filename)
if err != nil {
fatal(exitOpenFile, err)
}
raw = r
} else {
r, err := getURL(filename)
if err != nil {
fatal(exitFetchURL, err)
}
raw = r
}
}
var a actionFn = gron
if ungronFlag {
a = ungron
}
exitCode, err := a(raw, os.Stdout, monochromeFlag)
if exitCode != exitOK {
fatal(exitCode, err)
}
os.Exit(exitOK)
}
type actionFn func(io.Reader, io.Writer, bool) (int, error)
func gron(r io.Reader, w io.Writer, monochrome bool) (int, error) {
formatter = colorFormatter{}
if monochrome {
formatter = monoFormatter{}
}
ss, err := makeStatementsFromJSON(r)
if err != nil {
return exitFormStatements, fmt.Errorf("failed to form statements: %s", err)
}
// Go's maps do not have well-defined ordering, but we want a consistent
// output for a given input, so we must sort the statements
sort.Sort(ss)
for _, s := range ss {
fmt.Fprintln(w, s)
}
return exitOK, nil
}
func ungron(r io.Reader, w io.Writer, monochrome bool) (int, error) {
scanner := bufio.NewScanner(r)
// Make a list of statements from the input
var ss statements
for scanner.Scan() {
ss.AddFull(scanner.Text())
}
if err := scanner.Err(); err != nil {
return exitReadInput, fmt.Errorf("failed to read input statements")
}
// ungron the statements
merged, err := ss.ungron()
if err != nil {
return exitParseStatements, err
}
// If there's only one top level key and it's "json", make that the top level thing
mergedMap, ok := merged.(map[string]interface{})
if ok {
if len(mergedMap) == 1 {
if _, exists := mergedMap["json"]; exists {
merged = mergedMap["json"]
}
}
}
// Marshal the output into JSON to display to the user
j, err := json.MarshalIndent(merged, "", " ")
if err != nil {
return exitJSONEncode, errors.Wrap(err, "failed to convert statements to JSON")
}
// If the output isn't monochrome, add color to the JSON
if !monochrome {
c, err := colorizeJSON(j)
// If we failed to colorize the JSON for whatever reason,
// we'll just fall back to monochrome output, otherwise
// replace the monochrome JSON with glorious technicolor
if err == nil {
j = c
}
}
fmt.Fprintf(w, "%s\n", j)
return exitOK, nil
}
func colorizeJSON(src []byte) ([]byte, error) {
out := &bytes.Buffer{}
f := jsoncolor.NewFormatter()
f.StringColor = strColor
f.ObjectColor = braceColor
f.ArrayColor = braceColor
f.FieldColor = bareColor
f.NumberColor = numColor
f.TrueColor = boolColor
f.FalseColor = boolColor
f.NullColor = boolColor
err := f.Format(out, src)
if err != nil {
return out.Bytes(), err
}
return out.Bytes(), nil
}
func fatal(code int, err error) {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(code)
}