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  }