-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.go
229 lines (197 loc) · 6.56 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
// SPDX-License-Identifier: GPL-3.0-or-later
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"golang.org/x/exp/slog" // nee "log/slog"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
"github.com/lmittmann/tint"
"github.com/rockstor/rockon-validator/model"
)
const usage = `Usage:
rockon-validator [--check] [--diff] [--write] [--root FILE] [--verbose|--debug] FILE...
Options:
-c, --check Check the FILE(s) for the correct syntax and return non-zero if invalid.
-d, --diff Check the FILE(s) for the correct syntax and output a diff if different.
-w, --write Check the FILE(s) and write any changes back to disk in-place.
-r, --root root.json file used to verify that the rockon is mentioned in said file.
Default: same directory as FILE
-v, --verbose Enable more logging
--debug Enable debug logging
`
var (
checkFlag, diffFlag, writeFlag, verboseFlag, debugFlag bool
rootFlag, rootFile string
logger *slog.Logger
)
func parseFlags() {
if len(os.Args) == 1 {
flag.Usage()
os.Exit(1)
}
flag.BoolVar(&checkFlag, "c", false, "check the file")
flag.BoolVar(&checkFlag, "check", false, "check the file")
flag.BoolVar(&diffFlag, "d", false, "diff the file")
flag.BoolVar(&diffFlag, "diff", false, "diff the file")
flag.BoolVar(&writeFlag, "w", false, "write the file")
flag.BoolVar(&writeFlag, "write", false, "write the file")
flag.StringVar(&rootFlag, "r", "", "root.json file to check")
flag.StringVar(&rootFlag, "root", "", "root.json file to check")
flag.BoolVar(&verboseFlag, "v", false, "enable more logging")
flag.BoolVar(&verboseFlag, "verbose", false, "enable more logging")
flag.BoolVar(&debugFlag, "debug", false, "enable debug logging")
flag.Parse()
}
func parseFileArgs() (filePaths []string) {
for _, f := range flag.Args() {
glob, _ := filepath.Glob(f)
filePaths = append(filePaths, glob...)
}
for i, f := range filePaths {
files, err := os.ReadDir(f)
if err != nil {
continue // What we got was not a directory, so we can leave it be
}
entries := []string{}
for _, e := range files {
if !e.IsDir() {
entries = append(entries, filepath.Join(f, e.Name()))
}
}
head := filePaths[:i]
if i == 0 {
head = []string{}
}
tail := filePaths[i+1:]
filePaths = append(head, entries...)
filePaths = append(filePaths, tail...)
}
return filePaths
}
func setupLogger(logLevel *slog.LevelVar) *slog.Logger {
logOpts := &tint.Options{
Level: logLevel,
ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
if attr.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return attr
},
}
logHandler := tint.NewHandler(os.Stderr, logOpts)
logger := slog.New(logHandler)
slog.SetDefault(logger)
return logger
}
func checkRootMap(rootMap map[string]string, filename string, rockon model.RockOn) {
var found bool
var foundName string
for k := range rootMap {
found = rootMap[k] == filename
if found {
foundName = k
break
}
}
for name := range rockon {
if found {
if name != foundName {
slog.Warn("RockOn name does not match", slog.String("root.json", foundName), slog.String("rockon", name), slog.String("file", filepath.Base(filename)))
}
} else {
rootMap[name] = filename
logger.Debug("root.json map", slog.Any("rootMap", rootMap))
}
}
}
func main() {
logLevel := &slog.LevelVar{}
logLevel.Set(slog.LevelWarn)
logger = setupLogger(logLevel)
flag.Usage = func() { fmt.Fprint(os.Stderr, usage) }
parseFlags()
if verboseFlag {
logLevel.Set(slog.LevelInfo)
}
if debugFlag {
logLevel.Set(slog.LevelDebug)
}
logger.Debug("Operation flags", slog.Bool("checkFlag", checkFlag), slog.Bool("diffFlag", diffFlag), slog.Bool("writeFlag", writeFlag))
logger.Debug("Verbosity flags", slog.Bool("verboseFlag", verboseFlag), slog.Bool("debugFlag", debugFlag))
logger.Debug("root.json flags", slog.String("rootFlag", rootFlag), slog.String("rootFile", rootFile))
rootMap := map[string]string{}
var numDiffFiles int
for _, f := range parseFileArgs() {
logger.Info("Checking", slog.String("file", f))
data, err := os.ReadFile(f)
if err != nil {
logger.Error("Reading file", slog.String("file", f), slog.Any("err", err))
os.Exit(1) // We should be able to read all the files
}
dataString := string(data)
rootFile = rootFlag
if rootFlag == "" {
rootFile = filepath.Join(filepath.Dir(f), "root.json")
}
rootData, _ := os.ReadFile(rootFile)
json.Unmarshal(rootData, &rootMap)
logger.Debug("root.json flags", slog.String("rootFlag", rootFlag), slog.String("rootFile", rootFile))
var rockon model.RockOn
err = json.Unmarshal(data, &rockon)
if err != nil {
err1 := json.Unmarshal(data, &rootMap)
if err1 == nil {
logger.Warn("Possible root.json, skipping", slog.String("file", f))
continue // It may be the root.json, so skip it
}
if filepath.Ext(f) == ".json" {
logger.Error("Unmarshaling json data", slog.String("file", f), slog.Any("err", err))
os.Exit(1) // File was named `.json`, but couldn't be marshalled as expected, so we need to exit.
}
logger.Warn("Non-json file passed as input, skipping", slog.String("file", f))
continue // Otherwise, it wasn't a json file, so we shouldn't worry about it.
}
checkRootMap(rootMap, filepath.Base(f), rockon)
result, err := rockon.ToJSON()
if err != nil {
logger.Error("Marshaling to JSON", slog.Any("err", err))
os.Exit(1) // This should basically never happen
}
if dataString != result {
numDiffFiles++
}
if diffFlag {
aPath := "a/" + strings.TrimPrefix(f, "/")
bPath := "b/" + strings.TrimPrefix(f, "/")
edits := myers.ComputeEdits(span.URIFromPath(aPath), dataString, result)
fmt.Println(gotextdiff.ToUnified(aPath, bPath, dataString, edits))
}
if writeFlag {
stat, _ := os.Stat(f)
logger.Debug("Writing rockon", slog.String("file", f))
err = os.WriteFile(f, []byte(result), stat.Mode())
if err != nil {
logger.Error("Writing rockon", slog.String("file", f), slog.Any("err", err))
}
rootStat, err := os.Stat(rootFile)
if os.IsNotExist(err) {
rootStat = stat
}
rootJson, _ := json.MarshalIndent(rootMap, "", " ")
logger.Debug("Writing root", slog.String("file", rootFile))
err = os.WriteFile(rootFile, rootJson, rootStat.Mode())
if err != nil {
logger.Error("Writing root", slog.String("file", rootFile), slog.Any("err", err))
}
}
}
if checkFlag {
os.Exit(numDiffFiles)
}
}