github.com/Diggs/controller-tools@v0.4.2/cmd/helpgen/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/format"
     7  	"io"
     8  	"os"
     9  	"sort"
    10  	"strings"
    11  
    12  	flag "github.com/spf13/pflag"
    13  
    14  	"github.com/Diggs/controller-tools/pkg/genall"
    15  	"github.com/Diggs/controller-tools/pkg/genall/help"
    16  	prettyhelp "github.com/Diggs/controller-tools/pkg/genall/help/pretty"
    17  	"github.com/Diggs/controller-tools/pkg/markers"
    18  )
    19  
    20  var (
    21  	optionsRegistry = &markers.Registry{}
    22  	genMarker       = markers.Must(markers.MakeDefinition("generate", markers.DescribesPackage, Generator{}))
    23  )
    24  
    25  func init() {
    26  	if err := optionsRegistry.Register(genMarker); err != nil {
    27  		panic(err)
    28  	}
    29  	if help := (Generator{}).Help(); help != nil {
    30  		optionsRegistry.AddHelp(genMarker, help)
    31  	}
    32  	if err := genall.RegisterOptionsMarkers(optionsRegistry); err != nil {
    33  		panic(err)
    34  	}
    35  }
    36  
    37  const (
    38  	markerName = "controllertools:marker:generateHelp"
    39  	header     = `
    40  // +build !ignore_autogenerated
    41  
    42  %[2]s
    43  
    44  // Code generated by helpgen. DO NOT EDIT.
    45  
    46  package %[1]s
    47  
    48  import (
    49  	"github.com/Diggs/controller-tools/pkg/markers"
    50  )
    51  
    52  `
    53  	funcBlock = `
    54  func (%[1]s) Help() *markers.DefinitionHelp {
    55  	return &markers.DefinitionHelp{
    56  		Category: %[2]q,
    57  		DetailedHelp: markers.DetailedHelp{
    58  			Summary: %[3]q,
    59  			Details: %[4]q,
    60  		},
    61  		FieldHelp: map[string]markers.DetailedHelp{
    62  			%[5]s
    63  		},
    64  	}
    65  }
    66  `
    67  )
    68  
    69  type generateHelp struct {
    70  	Category string `marker:",optional"`
    71  }
    72  
    73  func godocToDetails(typeName string, doc string) (summary, details string) {
    74  	docParts := strings.SplitN(doc, "\n", 2)
    75  	summary = docParts[0]
    76  	if summaryWords := strings.SplitN(summary, " ", 2); summaryWords[0] == typeName {
    77  		if len(summaryWords) > 1 {
    78  			summary = summaryWords[1]
    79  		} else {
    80  			summary = ""
    81  		}
    82  	}
    83  
    84  	details = ""
    85  	if len(docParts) > 1 {
    86  		details = strings.TrimSpace(docParts[1])
    87  	}
    88  	return summary, details
    89  }
    90  
    91  type Generator struct {
    92  	HeaderFile string `marker:",optional"`
    93  	Year       string `marker:",optional"`
    94  }
    95  
    96  func (Generator) RegisterMarkers(reg *markers.Registry) error {
    97  	defn := markers.Must(markers.MakeDefinition(markerName, markers.DescribesType, generateHelp{}))
    98  	if err := reg.Register(defn); err != nil {
    99  		return err
   100  	}
   101  	reg.AddHelp(defn, &markers.DefinitionHelp{
   102  		Category: "markers",
   103  		DetailedHelp: markers.DetailedHelp{
   104  			Summary: "generate help based on the godoc of this marker type.",
   105  			Details: "Type-level godoc becomes general marker summary (first line) and details (other lines).  Field-level godoc similarly becomes marker field help.",
   106  		},
   107  		FieldHelp: map[string]markers.DetailedHelp{
   108  			"Category": markers.DetailedHelp{
   109  				Summary: "indicates the general category to which this marker belongs",
   110  			},
   111  		},
   112  	})
   113  	return nil
   114  }
   115  func (g Generator) Generate(ctx *genall.GenerationContext) error {
   116  	var headerText string
   117  	if g.HeaderFile != "" {
   118  		headerBytes, err := ctx.ReadFile(g.HeaderFile)
   119  		if err != nil {
   120  			return err
   121  		}
   122  		headerText = string(headerBytes)
   123  	}
   124  	headerText = strings.ReplaceAll(headerText, " YEAR", g.Year)
   125  
   126  	for _, root := range ctx.Roots {
   127  		byType := make(map[string]string)
   128  		if err := markers.EachType(ctx.Collector, root, func(info *markers.TypeInfo) {
   129  			markerVal, hadMarker := info.Markers.Get(markerName).(generateHelp)
   130  			if !hadMarker {
   131  				return
   132  			}
   133  			outContent := new(bytes.Buffer)
   134  
   135  			for _, field := range info.Fields {
   136  				summary, details := godocToDetails(field.Name, field.Doc)
   137  				fmt.Fprintf(outContent, "%[1]q: markers.DetailedHelp{\nSummary: %[2]q,\n Details: %[3]q,\n},\n", field.Name, summary, details)
   138  			}
   139  
   140  			summary, details := godocToDetails(info.Name, info.Doc)
   141  			byType[info.Name] = fmt.Sprintf(funcBlock, info.Name, markerVal.Category, summary, details, outContent.String())
   142  		}); err != nil {
   143  			return err
   144  		}
   145  
   146  		if len(byType) == 0 {
   147  			continue
   148  		}
   149  
   150  		// ensure a stable output order
   151  		typeNames := make([]string, 0, len(byType))
   152  		for typ := range byType {
   153  			typeNames = append(typeNames, typ)
   154  		}
   155  		sort.Strings(typeNames)
   156  
   157  		outContent := new(bytes.Buffer)
   158  		if headerText == "" {
   159  			panic("at the disco!")
   160  		}
   161  		fmt.Fprintf(outContent, header, root.Name, headerText)
   162  
   163  		for _, typ := range typeNames {
   164  			fmt.Fprintln(outContent, byType[typ])
   165  		}
   166  
   167  		outBytes := outContent.Bytes()
   168  		if formatted, err := format.Source(outBytes); err != nil {
   169  			root.AddError(err)
   170  		} else {
   171  			outBytes = formatted
   172  		}
   173  
   174  		outputFile, err := ctx.Open(root, "zz_generated.markerhelp.go")
   175  		if err != nil {
   176  			root.AddError(err)
   177  			continue
   178  		}
   179  		defer outputFile.Close()
   180  		n, err := outputFile.Write(outBytes)
   181  		if err != nil {
   182  			root.AddError(err)
   183  			continue
   184  		}
   185  		if n < len(outBytes) {
   186  			root.AddError(io.ErrShortWrite)
   187  		}
   188  	}
   189  	return nil
   190  }
   191  
   192  func (Generator) Help() *markers.DefinitionHelp {
   193  	// need to write this by hand, otherwise we'd have a bootstrap issue
   194  	return &markers.DefinitionHelp{
   195  		Category: "",
   196  		DetailedHelp: markers.DetailedHelp{
   197  			Summary: "generates marker help using godoc, based on the presence of a particular marker",
   198  		},
   199  		FieldHelp: map[string]markers.DetailedHelp{
   200  			"HeaderFile": markers.DetailedHelp{Summary: "the file containing the header to use for generated files"},
   201  			"Year":       markers.DetailedHelp{Summary: "replace \" YEAR\" in the header with this value."},
   202  		},
   203  	}
   204  }
   205  
   206  func usage() {
   207  	fmt.Fprintf(os.Stderr, "usage: %s generate:headerFile=./boilerplate.go.txt,year=2019 paths=./pkg/...\n", os.Args[0])
   208  
   209  	optInfo := help.ByCategory(optionsRegistry, help.SortByOption)
   210  	for _, cat := range optInfo {
   211  		if cat.Category == "" {
   212  			continue
   213  		}
   214  		contents := prettyhelp.MarkersDetails(true, cat.Category, cat.Markers)
   215  		if err := contents.WriteTo(os.Stderr); err != nil {
   216  			panic(err)
   217  		}
   218  	}
   219  
   220  	reg := &markers.Registry{}
   221  	if err := (Generator{}).RegisterMarkers(reg); err != nil {
   222  		panic(err)
   223  	}
   224  	markerInfo := help.ByCategory(reg, help.SortByCategory)
   225  	contents := prettyhelp.MarkersDetails(true, "markers", markerInfo[0].Markers)
   226  	if err := contents.WriteTo(os.Stderr); err != nil {
   227  		panic(err)
   228  	}
   229  	os.Exit(1)
   230  }
   231  
   232  func main() {
   233  	help := flag.BoolP("help", "h", false, "print help")
   234  	flag.Parse()
   235  
   236  	if *help {
   237  		usage()
   238  	}
   239  
   240  	if len(os.Args) > 1 && os.Args[1] == "--" {
   241  		os.Args = append([]string{os.Args[0]}, os.Args[2:]...)
   242  	}
   243  
   244  	runtime, err := genall.FromOptions(optionsRegistry, os.Args[1:])
   245  	if err != nil {
   246  		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
   247  		usage()
   248  	}
   249  	runtime.OutputRules.Default = genall.OutputArtifacts{}
   250  
   251  	if hadErrs := runtime.Run(); hadErrs {
   252  		fmt.Fprintln(os.Stderr, "not entirely successful")
   253  		os.Exit(1)
   254  	}
   255  }