Generator improvements and cleanup
[govpp.git] / cmd / binapi-generator / main.go
1 // Copyright (c) 2018 Cisco and/or its affiliates.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package main
16
17 import (
18         "bufio"
19         "encoding/json"
20         "flag"
21         "fmt"
22         "io/ioutil"
23         "os"
24         "os/exec"
25         "path/filepath"
26         "strings"
27
28         "github.com/bennyscetbun/jsongo"
29         "github.com/sirupsen/logrus"
30 )
31
32 var (
33         inputFile       = flag.String("input-file", "", "Input file with VPP API in JSON format.")
34         inputDir        = flag.String("input-dir", ".", "Input directory with VPP API files in JSON format.")
35         outputDir       = flag.String("output-dir", ".", "Output directory where package folders will be generated.")
36         includeAPIVer   = flag.Bool("include-apiver", false, "Include APIVersion constant for each module.")
37         includeComments = flag.Bool("include-comments", false, "Include JSON API source in comments for each object.")
38         continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.")
39         debug           = flag.Bool("debug", false, "Enable debug mode.")
40 )
41
42 var log = logrus.Logger{
43         Level:     logrus.InfoLevel,
44         Formatter: &logrus.TextFormatter{},
45         Out:       os.Stdout,
46 }
47
48 func main() {
49         flag.Parse()
50         if *debug {
51                 logrus.SetLevel(logrus.DebugLevel)
52         }
53
54         if *inputFile == "" && *inputDir == "" {
55                 fmt.Fprintln(os.Stderr, "ERROR: input-file or input-dir must be specified")
56                 os.Exit(1)
57         }
58
59         if *inputFile != "" {
60                 // process one input file
61                 if err := generateFromFile(*inputFile, *outputDir); err != nil {
62                         fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", *inputFile, err)
63                         os.Exit(1)
64                 }
65         } else {
66                 // process all files in specified directory
67                 files, err := getInputFiles(*inputDir)
68                 if err != nil {
69                         fmt.Fprintf(os.Stderr, "ERROR: code generation failed: %v\n", err)
70                         os.Exit(1)
71                 }
72                 for _, file := range files {
73                         if err := generateFromFile(file, *outputDir); err != nil {
74                                 fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", file, err)
75                                 if *continueOnError {
76                                         continue
77                                 }
78                                 os.Exit(1)
79                         }
80                 }
81         }
82 }
83
84 // getInputFiles returns all input files located in specified directory
85 func getInputFiles(inputDir string) (res []string, err error) {
86         files, err := ioutil.ReadDir(inputDir)
87         if err != nil {
88                 return nil, fmt.Errorf("reading directory %s failed: %v", inputDir, err)
89         }
90         for _, f := range files {
91                 if strings.HasSuffix(f.Name(), inputFileExt) {
92                         res = append(res, filepath.Join(inputDir, f.Name()))
93                 }
94         }
95         return res, nil
96 }
97
98 // generateFromFile generates Go package from one input JSON file
99 func generateFromFile(inputFile, outputDir string) error {
100         logf("generating from file: %q", inputFile)
101         defer logf("--------------------------------------")
102
103         ctx, err := getContext(inputFile, outputDir)
104         if err != nil {
105                 return err
106         }
107
108         ctx.includeAPIVersionCrc = *includeAPIVer
109         ctx.includeComments = *includeComments
110
111         // read input file contents
112         ctx.inputData, err = readFile(inputFile)
113         if err != nil {
114                 return err
115         }
116         // parse JSON data into objects
117         jsonRoot, err := parseJSON(ctx.inputData)
118         if err != nil {
119                 return err
120         }
121         ctx.packageData, err = parsePackage(ctx, jsonRoot)
122         if err != nil {
123                 return err
124         }
125
126         // create output directory
127         packageDir := filepath.Dir(ctx.outputFile)
128         if err := os.MkdirAll(packageDir, 0777); err != nil {
129                 return fmt.Errorf("creating output directory %q failed: %v", packageDir, err)
130         }
131         // open output file
132         f, err := os.Create(ctx.outputFile)
133         if err != nil {
134                 return fmt.Errorf("creating output file %q failed: %v", ctx.outputFile, err)
135         }
136         defer f.Close()
137
138         // generate Go package code
139         w := bufio.NewWriter(f)
140         if err := generatePackage(ctx, w); err != nil {
141                 return err
142         }
143
144         // go format the output file (fail probably means the output is not compilable)
145         cmd := exec.Command("gofmt", "-w", ctx.outputFile)
146         if output, err := cmd.CombinedOutput(); err != nil {
147                 return fmt.Errorf("gofmt failed: %v\n%s", err, string(output))
148         }
149
150         // count number of lines in generated output file
151         cmd = exec.Command("wc", "-l", ctx.outputFile)
152         if output, err := cmd.CombinedOutput(); err != nil {
153                 log.Warnf("wc command failed: %v\n%s", err, string(output))
154         } else {
155                 logf("generated lines: %s", output)
156         }
157
158         return nil
159 }
160
161 // readFile reads content of a file into memory
162 func readFile(inputFile string) ([]byte, error) {
163         inputData, err := ioutil.ReadFile(inputFile)
164         if err != nil {
165                 return nil, fmt.Errorf("reading data from file failed: %v", err)
166         }
167
168         return inputData, nil
169 }
170
171 // parseJSON parses a JSON data into an in-memory tree
172 func parseJSON(inputData []byte) (*jsongo.JSONNode, error) {
173         root := jsongo.JSONNode{}
174
175         if err := json.Unmarshal(inputData, &root); err != nil {
176                 return nil, fmt.Errorf("unmarshalling JSON failed: %v", err)
177         }
178
179         return &root, nil
180 }
181
182 func logf(f string, v ...interface{}) {
183         if *debug {
184                 logrus.Debugf(f, v...)
185         }
186 }