-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
257 lines (211 loc) · 5.63 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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
const (
formatCompact = "c"
formatString = "s"
formatTab = "t"
)
const helpText = `jsonf - A simple JSON formatter
When invoked on a single file, the file will be reformatted and the output sent
to stdout. You may rewrite the original file(s) using the -w flag.
When invoked on a directory, the program will only operate on files ending in
.json. To operate on multiple files without the json extension, specify each one
as an additional argument.
When processing multiple files or one or more directories, the program will
output the filename of each file before it is processed, and will attempt to
continue when it encouters errors.
Usage:
jsonf [options] filename or directory ...
Example:
jsonf -i sss myfile.json Indent using 3 spaces and write to stdout
jsonf -w -i t myfile.json Indent using tabs and rewrite the original files
jsonf -w . Rewrite all .json files in the current directory
jsonf -r file1 file2 dir Rewrite file1, file2, and all .json files under dir
Options:
-w Overwrite files in place
-r Recurse into subdirectories
-i Set the indentation string (defaults to 2 spaces). You can use 's' and 't'
to replace literal space and tab characters
-c Compact (minify) rather than indent. -c wins over -i if both are specified
-h Show help
Copyright 2018 Chris Bednarski <[email protected]> - MIT License
Portions copyright 2009 the Go Authors - BSD License https://golang.org/LICENSE
Report issues to https://github.com/cbednarski/jsonf
`
var (
exitCode = 0
stdout = os.Stdout
stderr = os.Stderr
)
func printError(err error) {
stderr.WriteString(fmt.Sprintf("%s\n", err))
exitCode = 1
}
func format(filename string, indent string) (*bytes.Buffer, error) {
output := &bytes.Buffer{}
var input []byte
var err error
if filename == "-" {
input, err = ioutil.ReadAll(os.Stdin)
} else {
input, err = ioutil.ReadFile(filename)
}
if err != nil {
return nil, err
}
input = bytes.TrimSpace(input)
// chunk JSON when there is a linebreak between JSON objects.
// there is a better way to do this, but here we are.
chunks := bytes.Split(input, []byte("}\n{"))
for i, chunk := range chunks {
// We split off some characters so we need to put them back now.
if len(chunks) > 1 {
if i == 0 {
chunk = append(chunk, []byte("}")...)
} else if i == len(chunks)-1 {
chunk = append([]byte("{"), chunk...)
} else {
chunk = append([]byte("{"), chunk...)
chunk = append(chunk, []byte("}")...)
}
}
if indent == formatCompact {
if err := json.Compact(output, chunk); err != nil {
return nil, err
}
} else {
if err := json.Indent(output, chunk, "", indent); err != nil {
return nil, err
}
output.WriteString("\n")
}
}
return output, nil
}
func formatFile(filename string, indent string, replace bool) error {
data, err := format(filename, indentString(indent))
if err != nil {
return err
}
if replace {
info, err := os.Stat(filename)
if err != nil {
return err
}
if err := ioutil.WriteFile(filename, data.Bytes(), info.Mode()); err != nil {
return err
}
} else {
data.WriteTo(stdout)
}
return nil
}
func indentString(input string) string {
// Special case for compaction
if strings.Contains(input, formatCompact) {
return formatCompact
}
input = strings.Replace(input, formatString, " ", -1)
input = strings.Replace(input, formatTab, "\t", -1)
return input
}
func listJSONFiles(path string, recurse bool) ([]string, error) {
files := []string{}
if isDir(path) {
filepath.Walk(path, func(innerPath string, info os.FileInfo, err error) error {
// We always want to talk the current directory, but not any
// subdirectories, unless recurse is true.
if !recurse && info.IsDir() && innerPath != path {
return filepath.SkipDir
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".json") {
files = append(files, innerPath)
}
return nil
})
} else {
files = []string{path}
}
return files, nil
}
func isDir(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
return info.IsDir()
}
func header(filename string) {
fmt.Printf("--- %s\n", filename)
}
func formatPath(path, indent string, replace, recurse, headers bool) error {
if isDir(path) {
files, err := listJSONFiles(flag.Args()[0], recurse)
if err != nil {
return err
}
errors := 0
for _, f := range files {
header(f)
if err := formatFile(f, indent, replace); err != nil {
printError(err)
}
}
if errors > 0 {
return fmt.Errorf("Encountered %d errors in %s", errors, path)
}
} else {
if headers {
header(path)
}
if err := formatFile(path, indent, replace); err != nil {
return err
}
}
return nil
}
func wrappedMain() error {
replace := flag.Bool("w", false, "overwrite files in place")
recurse := flag.Bool("r", false, "recurse into subdirectories")
compact := flag.Bool("c", false, "compact instead of intent")
indent := flag.String("i", " ", `indent string, e.g. " "`)
help := flag.Bool("h", false, "show help")
flag.Parse()
if *help {
fmt.Println(helpText)
os.Exit(0)
}
if *compact {
*indent = formatCompact
}
if len(flag.Args()) < 1 {
return errors.New(helpText)
}
headers := false
if len(flag.Args()) > 1 {
headers = true
}
for _, path := range flag.Args() {
if err := formatPath(path, *indent, *replace, *recurse, headers); err != nil {
printError(err)
}
}
return nil
}
func main() {
if err := wrappedMain(); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
os.Exit(exitCode)
}