thomasfinstad / terraform-provider-vyos-rolling

Terraform provider for VyOS with a focus on automatic resource generation
6 stars 0 forks source link

improve interface definition generation #205

Open github-actions[bot] opened 7 months ago

github-actions[bot] commented 7 months ago

milestone:6

look into using dst instead of regex replace

Rendered example: Parent:\<REC(&interfacedefinition.Node)>,

Rendered example: LeafNode:[]*interfacedefinition.LeafNode{\<REC()>}

VersionAttr: (&interfacedefinition.VersionAttr)(nil)}

Parent: nil,

OwnerAttr:"",

https://github.com/thomasfinstad/terraform-provider-vyos/blob/c3bd4b16ebe7a43fe5699e92a09c689c41d3aa65/tools/build-vyos-infterface-definition-structs/main.go#L53


package main

import (
    "encoding/xml"
    "fmt"
    "os"
    "path/filepath"
    "regexp"
    "runtime"
    "strings"

    "github.com/dave/dst"
    "github.com/dave/dst/decorator"
    "github.com/dave/dst/dstutil"
    "github.com/gdexlab/go-render/render"
    "github.com/thomasfinstad/terraform-provider-vyos/internal/vyos/schemadefinition"
    "golang.org/x/text/cases"
    "golang.org/x/text/language"
)

func main() {
    args := os.Args[1:]
    inputXMLFilePath := args[0]
    outputDirectory := args[1]
    pkgName := args[2]

    _, thisFilename, _, ok := runtime.Caller(0)
    if !ok {
        panic("Did not get path info")
    }

    outputBaseName := strings.TrimSuffix(filepath.Base(inputXMLFilePath), filepath.Ext(inputXMLFilePath))
    outputFile := fmt.Sprintf("%s/autogen-%s.go", outputDirectory, outputBaseName)

    fmt.Printf("->\tOutput Go file: %s\n", outputFile)

    dat, err := os.ReadFile(inputXMLFilePath)
    die(err)

    topLevelInterface := schemadefinition.InterfaceDefinition{}
    err = xml.Unmarshal(dat, &topLevelInterface)
    die(err)

    rootNode, err := topLevelInterface.GetRootNode()
    die(err)

    mergeNodeParents(rootNode)

    output := render.AsCode(topLevelInterface)

    outputFormatted := []byte(output)

    // TODO improve interface definition generation
    //  milestone:6
    //  look into using dst instead of regex replace

    // Change recursive attributes with nil as these can not be dumped as code
    // Rendered example: Parent:<REC(&interfacedefinition.Node)>,
    // Rendered example: LeafNode:[]*interfacedefinition.LeafNode{<REC()>}
    outputFormatted = regexp.MustCompile(`<REC\([&A-Za-z.]*\)>?`).ReplaceAll(outputFormatted, []byte("nil"))

    // Remove nil values, example:
    // VersionAttr: (&interfacedefinition.VersionAttr)(nil)}
    // Parent: nil,
    outputFormatted = regexp.MustCompile(`\w+:[^:]+nil\),?`).ReplaceAll(outputFormatted, []byte(""))
    outputFormatted = regexp.MustCompile(`\w+:[ ]*nil,?`).ReplaceAll(outputFormatted, []byte(""))

    // Remove empty string values, example:
    // OwnerAttr:"",
    outputFormatted = regexp.MustCompile(`\w+:\s*"",?`).ReplaceAll(outputFormatted, []byte(""))

    file, err := os.Create(outputFile)
    if err != nil {
        return
    }
    defer file.Close()

    funcName := strings.ReplaceAll(cases.Lower(language.Norwegian).String(outputBaseName), "-", "")

    outputBase := fmt.Sprintf(`
            // Code generated by %s. DO NOT EDIT.

            package %s

            import (
                "encoding/xml"

                "github.com/thomasfinstad/terraform-provider-vyos/internal/vyos/schemadefinition"
            )

            func %s() schemadefinition.InterfaceDefinition {
                return %s
            }
            `,
        thisFilename,
        pkgName,
        funcName,
        outputFormatted,
    )

    // Use DST to add linebreaks in generated code for readability
    fset, err := decorator.Parse(outputBase)
    if err != nil {
        os.WriteFile("ERROR-FILE", []byte(outputBase), 0644)
        panic(fmt.Sprintf("%s: '%#v'\nCode written to: %s", err, err, "ERROR-FILE"))
    }

    dstutil.Apply(fset, nil, func(c *dstutil.Cursor) bool {
        n := c.Node()

        switch x := n.(type) {
        case *dst.KeyValueExpr:
            x.Decorations().Before = dst.NewLine

        case *dst.Package:
            fmt.Println("Skipping package node")
        }

        return true
    })

    err = decorator.Fprint(file, fset)
    die(err)
}