github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/config/gen.go (about)

     1  // Copyright (c) 2020-2021, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  //go:build ignore
     6  // +build ignore
     7  
     8  package main
     9  
    10  import (
    11  	"fmt"
    12  	"go/ast"
    13  	"go/parser"
    14  	"go/token"
    15  	"log"
    16  	"os"
    17  	"os/exec"
    18  	"path"
    19  	"path/filepath"
    20  	"strings"
    21  	"text/template"
    22  
    23  	"github.com/fatih/structtag"
    24  )
    25  
    26  type keydoc struct {
    27  	Key  string
    28  	Help string
    29  }
    30  
    31  var templ = `
    32  package config
    33  
    34  var docStrings = map[string]string{
    35  {{- range . }}
    36  	{{ .Key | printf "%q" }}: {{ .Help | printf "%q" }},
    37  {{- end }}
    38  }
    39  `
    40  
    41  func parseDocString(d string) string {
    42  	d = strings.TrimSpace(d)
    43  
    44  	idx := strings.Index(d, "@doc")
    45  	if idx == -1 {
    46  		return d
    47  	}
    48  
    49  	d = d[idx+5:]
    50  	d = strings.ReplaceAll(d, "\n", " ")
    51  
    52  	return d
    53  }
    54  
    55  func parseGoFile(f string, structName string) (docs []keydoc, err error) {
    56  	docs = []keydoc{}
    57  
    58  	d, err := parser.ParseFile(token.NewFileSet(), f, nil, parser.ParseComments)
    59  	if err != nil {
    60  		return docs, err
    61  	}
    62  
    63  	var inserr error
    64  
    65  	ast.Inspect(d, func(n ast.Node) bool {
    66  		if inserr != nil {
    67  			return false
    68  		}
    69  
    70  		switch t := n.(type) {
    71  		case *ast.TypeSpec:
    72  			return t.Name.Name == structName
    73  		case *ast.StructType:
    74  			for _, field := range t.Fields.List {
    75  				tag := ""
    76  				doc := ""
    77  
    78  				if field.Tag != nil && field.Tag.Kind == token.STRING {
    79  					tag = strings.TrimRight(strings.TrimLeft(field.Tag.Value, "`"), "`")
    80  				}
    81  
    82  				switch {
    83  				case len(strings.TrimSpace(field.Doc.Text())) > 0:
    84  					doc = field.Doc.Text()
    85  				case len(strings.TrimSpace(field.Comment.Text())) > 0:
    86  					doc = field.Comment.Text()
    87  				}
    88  
    89  				if strings.Contains(tag, "confkey") && doc != "" {
    90  					tags, err := structtag.Parse(tag)
    91  					if err != nil {
    92  						inserr = err
    93  						return false
    94  					}
    95  
    96  					key, err := tags.Get("confkey")
    97  					if err != nil {
    98  						inserr = err
    99  						return false
   100  					}
   101  
   102  					docs = append(docs, keydoc{key.Value(), parseDocString(doc)})
   103  				}
   104  			}
   105  		}
   106  
   107  		return true
   108  	})
   109  
   110  	if inserr != nil {
   111  		return docs, inserr
   112  	}
   113  
   114  	return docs, nil
   115  }
   116  
   117  func goFmt(file string) error {
   118  	c := exec.Command("go", "fmt", file)
   119  	out, err := c.CombinedOutput()
   120  	if err != nil {
   121  		log.Printf("go fmt failed: %s", string(out))
   122  	}
   123  
   124  	return err
   125  }
   126  
   127  func main() {
   128  	docs := []keydoc{}
   129  
   130  	log.Println("Generating configuration doc strings")
   131  	cd, err := parseGoFile(filepath.Join("config", "config.go"), "Config")
   132  	if err != nil {
   133  		panic(err)
   134  	}
   135  	docs = append(docs, cd...)
   136  
   137  	cd, err = parseGoFile(filepath.Join("config", "choria.go"), "ChoriaPluginConfig")
   138  	if err != nil {
   139  		panic(err)
   140  	}
   141  	docs = append(docs, cd...)
   142  
   143  	if len(docs) == 0 {
   144  		panic("no documentation strings were generated")
   145  	}
   146  
   147  	t, err := template.New("templates").Parse(templ)
   148  	if err != nil {
   149  		panic(err)
   150  	}
   151  
   152  	outfile := filepath.Join("config", "docstrings.go")
   153  
   154  	out, err := os.Create(path.Join(outfile))
   155  	if err != nil {
   156  		panic(err)
   157  	}
   158  
   159  	fmt.Fprintf(out, "	// generated code; DO NOT EDIT\n\n")
   160  
   161  	err = t.Execute(out, docs)
   162  	out.Close()
   163  	goFmt(outfile)
   164  
   165  	log.Println("Generated " + outfile)
   166  }