Skip to content

Commit

Permalink
Add option to split GoStructs by number of files (#409)
Browse files Browse the repository at this point in the history
* Split GoStructs by number of files instead of by first letter

* Remove splitting GoStructs file by leading letter
  • Loading branch information
wenovus authored Jun 30, 2020
1 parent 39ec835 commit 7b2d049
Show file tree
Hide file tree
Showing 6 changed files with 477 additions and 303 deletions.
144 changes: 69 additions & 75 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"flag"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strings"
Expand All @@ -41,13 +42,9 @@ const (
schemaFn = "schema.go"
// interfaceFn is the filename to be used for interface code when outputting to a directory.
interfaceFn = "union.go"
// methodFn is the filename to be used for methods related to the structs when outputting
// to a directory.
methodFn = "methods.go"
// structBaseFn is the base filename to be used for files containing structs when outputting
// to a directory. Structs are divided alphabetically and the first character appended to the
// base specified in this value - e.g., structs beginning with "A" are stored in {structBaseFnA}.go.
structBaseFn = "structs_"
// structsFileFmt is the format string filename (missing index) to be
// used for files containing structs when outputting to a directory.
structsFileFmt = "structs-%d.go"
// pathStructsFileFmt is the format string filename (missing index) to
// be used for the path structs when path struct code is output to a directory.
pathStructsFileFmt = "path_structs-%d.go"
Expand All @@ -57,6 +54,7 @@ var (
generateGoStructs = flag.Bool("generate_structs", true, "If true, then Go code for YANG path construction (schema/Go structs) will be generated.")
generatePathStructs = flag.Bool("generate_path_structs", false, "If true, then Go code for YANG path construction (path structs) will be generated.")
ocStructsOutputFile = flag.String("output_file", "", "The file that the generated Go code for manipulating YANG data (schema/Go structs) should be written to. Specify \"-\" for stdout.")
structsFileN = flag.Int("structs_split_files_count", 0, "The number of files to split the generated schema structs into when output_file is specified.")
ocPathStructsOutputFile = flag.String("path_structs_output_file", "", "The file that the generated Go code for YANG path construction (path structs) will be generated. Specify \"-\" for stdout.")
pathStructsFileN = flag.Int("path_structs_split_files_count", 0, "The number of files to split the generated path structs into when output_file is specified for generating path structs")
outputDir = flag.String("output_dir", "", "The directory that the generated Go code should be written to. This is common between schema structs and path structs.")
Expand Down Expand Up @@ -135,86 +133,76 @@ func writeGoPathCodeSingleFile(w io.Writer, pathCode *ypathgen.GeneratedPathCode
return err
}

// writeIfNotEmpty writes the string s to b if it has a non-zero length.
func writeIfNotEmpty(b io.StringWriter, s string) {
if len(s) != 0 {
b.WriteString(s)
// splitCodeByFileN generates a map, keyed by filename, to a string containing
// the code to be output to that filename. It allows division of a
// ygen.GeneratedGoCode struct into a set of source files. It divides the
// methods, interfaces, and enumeration code snippets into their own files.
// Structs are output into files by splitting them evenly among the input split
// number.
func splitCodeByFileN(goCode *ygen.GeneratedGoCode, fileN int) (map[string]string, error) {
structN := len(goCode.Structs)
if fileN < 1 || fileN > structN {
return nil, fmt.Errorf("requested %d files, but must be between 1 and %d (number of schema structs)", fileN, structN)
}
}

// codeOut describes an output file for Go code.
type codeOut struct {
// contents is the code that is contained in the output file.
contents string
// oneoffHeader indicates whether the one-off header should be included in this
// file.
oneoffHeader bool
}

// makeOutputSpec generates a map, keyed by filename, to a codeOut struct containing
// the code to be output to that filename. It allows division of a ygen.GeneratedGoCode
// struct into a set of source files. It divides the methods, interfaces, and enumeration
// code snippets into their own files. Structs are output into files dependent on the
// first letter of their name within the code.
func makeOutputSpec(goCode *ygen.GeneratedGoCode) map[string]codeOut {
var methodCode, interfaceCode strings.Builder
structCode := map[byte]*strings.Builder{}
for _, s := range goCode.Structs {
// Index by the first character of the struct.
fc := s.StructName[0]
if _, ok := structCode[fc]; !ok {
structCode[fc] = &strings.Builder{}
}
cs := structCode[fc]
writeIfNotEmpty(cs, s.StructDef)
writeIfNotEmpty(cs, fmt.Sprintf("%s\n", s.ListKeys))
writeIfNotEmpty(&methodCode, fmt.Sprintf("%s\n", s.Methods))
writeIfNotEmpty(&interfaceCode, fmt.Sprintf("%s\n", s.Interfaces))
out := map[string]string{
schemaFn: goCode.JSONSchemaCode,
enumFn: strings.Join(goCode.Enums, "\n"),
}

emap := &strings.Builder{}
writeIfNotEmpty(emap, goCode.EnumMap)
if emap.Len() != 0 {
emap.WriteString("\n")
var structFiles []string
var code, interfaceCode strings.Builder
structsPerFile := int(math.Ceil(float64(structN) / float64(fileN)))
// Empty files could appear with certain structN/fileN combinations due
// to the ceiling numbers being used for structsPerFile.
// e.g. 4/3 gives two files of two structs.
// This is a little more complex, but spreads out the structs more evenly.
// If we instead use the floor number, and put all remainder structs in
// the last file, we might double the last file's number of structs if we get unlucky.
// e.g. 99/10 assigns 18 structs to the last file.
emptyFiles := fileN - int(math.Ceil(float64(structN)/float64(structsPerFile)))
code.WriteString(goCode.OneOffHeader)
for i, s := range goCode.Structs {
code.WriteString(s.StructDef)
code.WriteString(s.ListKeys)
code.WriteString("\n")
code.WriteString(s.Methods)
if s.Methods != "" {
code.WriteString("\n")
}
interfaceCode.WriteString(s.Interfaces)
if s.Interfaces != "" {
interfaceCode.WriteString("\n")
}
// The last file contains the remainder of the structs.
if i == structN-1 || (i+1)%structsPerFile == 0 {
structFiles = append(structFiles, code.String())
code.Reset()
}
}
writeIfNotEmpty(emap, goCode.EnumTypeMap)

out := map[string]codeOut{
enumMapFn: {contents: emap.String()},
schemaFn: {contents: goCode.JSONSchemaCode},
interfaceFn: {contents: interfaceCode.String()},
methodFn: {contents: methodCode.String(), oneoffHeader: true},
enumFn: {contents: strings.Join(goCode.Enums, "\n")},
for i := 0; i != emptyFiles; i++ {
structFiles = append(structFiles, "")
}

for fn, code := range structCode {
out[fmt.Sprintf("%s%c.go", structBaseFn, fn)] = codeOut{
contents: code.String(),
}
for i, structFile := range structFiles {
out[fmt.Sprintf(structsFileFmt, i)] = structFile
}

return out
}
code.Reset()
code.WriteString(goCode.EnumMap)
if code.Len() != 0 {
code.WriteString("\n")
}
code.WriteString(goCode.EnumTypeMap)

// writeGoCodeMultipleFiles writes the input goCode to a set of files as specified
// by specification returned by output spec.
func writeGoCodeMultipleFiles(dir string, goCode *ygen.GeneratedGoCode) error {
out := makeOutputSpec(goCode)
out[enumMapFn] = code.String()
out[interfaceFn] = interfaceCode.String()

for fn, f := range out {
if len(f.contents) == 0 {
continue
}
fh := genutil.OpenFile(filepath.Join(dir, fn))
defer genutil.SyncFile(fh)
fmt.Fprintln(fh, goCode.CommonHeader)
if f.oneoffHeader {
fmt.Fprintln(fh, goCode.OneOffHeader)
}
fmt.Fprintln(fh, f.contents)
for name, code := range out {
out[name] = goCode.CommonHeader + code
}

return nil
return out, nil
}

// writeFiles creates or truncates files in a given base directory and writes
Expand Down Expand Up @@ -361,7 +349,13 @@ func main() {
writeGoCodeSingleFile(outfh, generatedGoCode)
case generateGoStructsMultipleFiles:
// Write the Go code to a series of output files.
writeGoCodeMultipleFiles(*outputDir, generatedGoCode)
out, err := splitCodeByFileN(generatedGoCode, *structsFileN)
if err != nil {
log.Exitf("ERROR writing split GoStruct Code: %v\n", err)
}
if err := writeFiles(*outputDir, out); err != nil {
log.Exitf("Error while writing schema struct files: %v", err)
}
}
}

Expand Down
Loading

0 comments on commit 7b2d049

Please sign in to comment.