github.com/jshiv/can-go@v0.2.1-0.20210224011015-069e90e90bdf/cmd/cantool/main.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "text/scanner" 11 12 "github.com/fatih/color" 13 "go.einride.tech/can/internal/generate" 14 "go.einride.tech/can/pkg/dbc" 15 "go.einride.tech/can/pkg/dbc/analysis" 16 "go.einride.tech/can/pkg/dbc/analysis/passes/definitiontypeorder" 17 "go.einride.tech/can/pkg/dbc/analysis/passes/intervals" 18 "go.einride.tech/can/pkg/dbc/analysis/passes/lineendings" 19 "go.einride.tech/can/pkg/dbc/analysis/passes/messagenames" 20 "go.einride.tech/can/pkg/dbc/analysis/passes/multiplexedsignals" 21 "go.einride.tech/can/pkg/dbc/analysis/passes/newsymbols" 22 "go.einride.tech/can/pkg/dbc/analysis/passes/nodereferences" 23 "go.einride.tech/can/pkg/dbc/analysis/passes/noreservedsignals" 24 "go.einride.tech/can/pkg/dbc/analysis/passes/requireddefinitions" 25 "go.einride.tech/can/pkg/dbc/analysis/passes/signalbounds" 26 "go.einride.tech/can/pkg/dbc/analysis/passes/signalnames" 27 "go.einride.tech/can/pkg/dbc/analysis/passes/singletondefinitions" 28 "go.einride.tech/can/pkg/dbc/analysis/passes/siunits" 29 "go.einride.tech/can/pkg/dbc/analysis/passes/uniquenodenames" 30 "go.einride.tech/can/pkg/dbc/analysis/passes/uniquesignalnames" 31 "go.einride.tech/can/pkg/dbc/analysis/passes/unitsuffixes" 32 "go.einride.tech/can/pkg/dbc/analysis/passes/valuedescriptions" 33 "go.einride.tech/can/pkg/dbc/analysis/passes/version" 34 "gopkg.in/alecthomas/kingpin.v2" 35 ) 36 37 func main() { 38 app := kingpin.New("cantool", "CAN tool for Go programmers") 39 generateCommand(app) 40 lintCommand(app) 41 kingpin.MustParse(app.Parse(os.Args[1:])) 42 } 43 44 func generateCommand(app *kingpin.Application) { 45 command := app.Command("generate", "generate CAN messages") 46 inputDir := command. 47 Arg("input-dir", "input directory"). 48 Required(). 49 ExistingDir() 50 outputDir := command. 51 Arg("output-dir", "output directory"). 52 Required(). 53 String() 54 command.Action(func(c *kingpin.ParseContext) error { 55 return filepath.Walk(*inputDir, func(p string, i os.FileInfo, err error) error { 56 if err != nil { 57 return err 58 } 59 if i.IsDir() || filepath.Ext(p) != ".dbc" { 60 return nil 61 } 62 relPath, err := filepath.Rel(*inputDir, p) 63 if err != nil { 64 return err 65 } 66 outputFile := relPath + ".go" 67 outputPath := filepath.Join(*outputDir, outputFile) 68 return genGo(p, outputPath) 69 }) 70 }) 71 } 72 73 func lintCommand(app *kingpin.Application) { 74 command := app.Command("lint", "lint DBC files") 75 fileOrDir := command. 76 Arg("file-or-dir", "DBC file or directory"). 77 Required(). 78 ExistingFileOrDir() 79 command.Action(func(context *kingpin.ParseContext) error { 80 filesToLint, err := resolveFileOrDirectory(*fileOrDir) 81 if err != nil { 82 return err 83 } 84 var hasFailed bool 85 for _, lintFile := range filesToLint { 86 f, err := os.Open(lintFile) 87 if err != nil { 88 return err 89 } 90 source, err := ioutil.ReadAll(f) 91 if err != nil { 92 return err 93 } 94 p := dbc.NewParser(f.Name(), source) 95 if err := p.Parse(); err != nil { 96 printError(source, err.Position(), err.Reason(), "parse") 97 continue 98 } 99 for _, a := range analyzers() { 100 pass := &analysis.Pass{ 101 Analyzer: a, 102 File: p.File(), 103 } 104 if err := a.Run(pass); err != nil { 105 return err 106 } 107 hasFailed = hasFailed || len(pass.Diagnostics) > 0 108 for _, d := range pass.Diagnostics { 109 printError(source, d.Pos, d.Message, a.Name) 110 } 111 } 112 } 113 if hasFailed { 114 return errors.New("one or more lint errors") 115 } 116 return nil 117 }) 118 } 119 120 func analyzers() []*analysis.Analyzer { 121 return []*analysis.Analyzer{ 122 // TODO: Re-evaluate if we want boolprefix.Analyzer(), since it creates a lot of churn in vendor schemas 123 definitiontypeorder.Analyzer(), 124 intervals.Analyzer(), 125 lineendings.Analyzer(), 126 messagenames.Analyzer(), 127 multiplexedsignals.Analyzer(), 128 newsymbols.Analyzer(), 129 nodereferences.Analyzer(), 130 noreservedsignals.Analyzer(), 131 requireddefinitions.Analyzer(), 132 signalbounds.Analyzer(), 133 signalnames.Analyzer(), 134 singletondefinitions.Analyzer(), 135 siunits.Analyzer(), 136 uniquenodenames.Analyzer(), 137 uniquesignalnames.Analyzer(), 138 unitsuffixes.Analyzer(), 139 valuedescriptions.Analyzer(), 140 version.Analyzer(), 141 } 142 } 143 144 func genGo(inputFile, outputFile string) error { 145 if err := os.MkdirAll(filepath.Dir(outputFile), 0o755); err != nil { 146 return err 147 } 148 input, err := ioutil.ReadFile(inputFile) 149 if err != nil { 150 return err 151 } 152 result, err := generate.Compile(inputFile, input) 153 if err != nil { 154 return err 155 } 156 for _, warning := range result.Warnings { 157 return warning 158 } 159 output, err := generate.Database(result.Database) 160 if err != nil { 161 return err 162 } 163 if err := ioutil.WriteFile(outputFile, output, 0o600); err != nil { 164 return err 165 } 166 fmt.Println("wrote:", outputFile) 167 return nil 168 } 169 170 func resolveFileOrDirectory(fileOrDirectory string) ([]string, error) { 171 fileInfo, err := os.Stat(fileOrDirectory) 172 if err != nil { 173 return nil, err 174 } 175 if !fileInfo.IsDir() { 176 return []string{fileOrDirectory}, nil 177 } 178 var files []string 179 if err := filepath.Walk(fileOrDirectory, func(path string, info os.FileInfo, err error) error { 180 if !info.IsDir() && filepath.Ext(path) == ".dbc" { 181 files = append(files, path) 182 } 183 return nil 184 }); err != nil { 185 return nil, err 186 } 187 return files, nil 188 } 189 190 func printError(source []byte, pos scanner.Position, msg, name string) { 191 fmt.Printf("\n%s: %s (%s)\n", pos, color.RedString("%s", msg), name) 192 fmt.Printf("%s\n", getSourceLine(source, pos)) 193 fmt.Printf("%s\n", caretAtPosition(pos)) 194 } 195 196 func getSourceLine(source []byte, pos scanner.Position) []byte { 197 lineStart := pos.Offset 198 for lineStart > 0 && source[lineStart-1] != '\n' { 199 lineStart-- 200 } 201 lineEnd := pos.Offset 202 for lineEnd < len(source) && source[lineEnd] != '\n' { 203 lineEnd++ 204 } 205 return source[lineStart:lineEnd] 206 } 207 208 func caretAtPosition(pos scanner.Position) string { 209 return strings.Repeat(" ", pos.Column-1) + color.YellowString("^") 210 }