github.com/grafana/pyroscope@v1.18.0/tools/doc-generator/main.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/tools/doc-generator/main.go 3 // Provenance-includes-license: Apache-2.0 4 // Provenance-includes-copyright: The Cortex Authors. 5 6 package main 7 8 import ( 9 "flag" 10 "fmt" 11 "os" 12 "path/filepath" 13 "strings" 14 "text/template" 15 16 "github.com/go-kit/log" 17 18 "github.com/grafana/pyroscope/pkg/pyroscope" 19 "github.com/grafana/pyroscope/tools/doc-generator/parse" 20 ) 21 22 const ( 23 maxLineWidth = 80 24 tabWidth = 2 25 ) 26 27 func removeFlagPrefix(block *parse.ConfigBlock, prefix string) { 28 for _, entry := range block.Entries { 29 switch entry.Kind { 30 case parse.KindBlock: 31 // Skip root blocks 32 if !entry.Root { 33 removeFlagPrefix(entry.Block, prefix) 34 } 35 case parse.KindField: 36 if strings.HasPrefix(entry.FieldFlag, prefix) { 37 entry.FieldFlag = "<prefix>" + entry.FieldFlag[len(prefix):] 38 } 39 } 40 } 41 } 42 43 func annotateFlagPrefix(blocks []*parse.ConfigBlock) { 44 // Find duplicated blocks 45 groups := map[string][]*parse.ConfigBlock{} 46 for _, block := range blocks { 47 groups[block.Name] = append(groups[block.Name], block) 48 } 49 50 // For each duplicated block, we need to fix the CLI flags, because 51 // in the documentation each block will be displayed only once but 52 // since they're duplicated they will have a different CLI flag 53 // prefix, which we want to correctly document. 54 for _, group := range groups { 55 if len(group) == 1 { 56 continue 57 } 58 59 // We need to find the CLI flags prefix of each config block. To do it, 60 // we pick the first entry from each config block and then find the 61 // different prefix across all of them. 62 flags := []string{} 63 for _, block := range group { 64 for _, entry := range block.Entries { 65 if entry.Kind == parse.KindField { 66 flags = append(flags, entry.FieldFlag) 67 break 68 } 69 } 70 } 71 72 allPrefixes := []string{} 73 for i, prefix := range parse.FindFlagsPrefix(flags) { 74 group[i].FlagsPrefix = prefix 75 allPrefixes = append(allPrefixes, prefix) 76 } 77 78 // Store all found prefixes into each block so that when we generate the 79 // markdown we also know which are all the prefixes for each root block. 80 for _, block := range group { 81 block.FlagsPrefixes = allPrefixes 82 } 83 } 84 85 // Finally, we can remove the CLI flags prefix from the blocks 86 // which have one annotated. 87 for _, block := range blocks { 88 if block.FlagsPrefix != "" { 89 removeFlagPrefix(block, block.FlagsPrefix) 90 } 91 } 92 } 93 94 func generateBlocksMarkdown(blocks []*parse.ConfigBlock) string { 95 md := &markdownWriter{} 96 md.writeConfigDoc(blocks) 97 return md.string() 98 } 99 100 //nolint:unused 101 func generateBlockMarkdown(blocks []*parse.ConfigBlock, blockName, fieldName string) string { 102 // Look for the requested block. 103 for _, block := range blocks { 104 if block.Name != blockName { 105 continue 106 } 107 108 md := &markdownWriter{} 109 110 // Wrap the root block with another block, so that we can show the name of the 111 // root field containing the block specs. 112 md.writeConfigBlock(&parse.ConfigBlock{ 113 Name: blockName, 114 Desc: block.Desc, 115 Entries: []*parse.ConfigEntry{ 116 { 117 Kind: parse.KindBlock, 118 Name: fieldName, 119 Required: true, 120 Block: block, 121 BlockDesc: "", 122 Root: false, 123 }, 124 }, 125 }) 126 127 return md.string() 128 } 129 130 // If the block has not been found, we return an empty string. 131 return "" 132 } 133 134 func main() { 135 // Parse the generator flags. 136 flag.Parse() 137 if flag.NArg() != 1 { 138 fmt.Fprintf(os.Stderr, "Usage: doc-generator template-file") 139 os.Exit(1) 140 } 141 142 templatePath := flag.Arg(0) 143 144 // In order to match YAML config fields with CLI flags, we map 145 // the memory address of the CLI flag variables and match them with 146 // the config struct fields' addresses. 147 cfg := &pyroscope.Config{} 148 flags := parse.Flags(cfg, log.NewLogfmtLogger(os.Stderr)) 149 150 // Parse the config, mapping each config field with the related CLI flag. 151 blocks, err := parse.Config(cfg, flags, parse.RootBlocks) 152 if err != nil { 153 fmt.Fprintf(os.Stderr, "An error occurred while generating the doc: %s\n", err.Error()) 154 os.Exit(1) 155 } 156 157 // Annotate the flags prefix for each root block, and remove the 158 // prefix wherever encountered in the config blocks. 159 annotateFlagPrefix(blocks) 160 161 // Generate documentation markdown. 162 data := struct { 163 ConfigFile string 164 GeneratedFileWarning string 165 }{ 166 ConfigFile: generateBlocksMarkdown(blocks), 167 GeneratedFileWarning: "<!-- DO NOT EDIT THIS FILE - This file has been automatically generated from its .template -->", 168 } 169 170 // Load the template file. 171 tpl := template.New(filepath.Base(templatePath)) 172 tpl, err = tpl.ParseFiles(templatePath) 173 if err != nil { 174 fmt.Fprintf(os.Stderr, "An error occurred while loading the template %s: %s\n", templatePath, err.Error()) 175 os.Exit(1) 176 } 177 178 // Execute the template to inject generated doc. 179 if err := tpl.Execute(os.Stdout, data); err != nil { 180 fmt.Fprintf(os.Stderr, "An error occurred while executing the template %s: %s\n", templatePath, err.Error()) 181 os.Exit(1) 182 } 183 }