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 }