github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/scripts/generate-sample-config/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"regexp"
    13  	"strings"
    14  	"unicode"
    15  
    16  	"github.com/sirupsen/logrus"
    17  	"github.com/spf13/pflag"
    18  	"github.com/spf13/viper"
    19  
    20  	"github.com/pyroscope-io/pyroscope/pkg/cli"
    21  	"github.com/pyroscope-io/pyroscope/pkg/config"
    22  	"github.com/pyroscope-io/pyroscope/pkg/util/slices"
    23  )
    24  
    25  // to run this program:
    26  //   go run scripts/generate-sample-config/main.go -format yaml
    27  //   go run scripts/generate-sample-config/main.go -format md
    28  // or:
    29  //   go run scripts/generate-sample-config/main.go -directory ../pyroscope.io/docs
    30  
    31  func main() {
    32  	var (
    33  		format     string
    34  		subcommand string
    35  		directory  string
    36  	)
    37  	flag.StringVar(&format, "format", "yaml", "yaml or md")
    38  	flag.StringVar(&subcommand, "subcommand", "server", "server, agent, exec...")
    39  	flag.StringVar(&directory, "directory", "", "directory to scan and perform auto replacements")
    40  	flag.Parse()
    41  
    42  	if directory != "" {
    43  		err := filepath.Walk(directory, func(path string, f os.FileInfo, err error) error {
    44  			if slices.StringContains([]string{".mdx", ".md"}, filepath.Ext(path)) {
    45  				return processFile(path)
    46  			}
    47  			return nil
    48  		})
    49  		if err != nil {
    50  			panic(err)
    51  		}
    52  	} else {
    53  		writeConfigDocs(os.Stdout, subcommand, format)
    54  	}
    55  }
    56  
    57  var (
    58  	sectionRegexp = regexp.MustCompile(`(?s)<!--\s*generate-sample-config:(.+?):(.+?)\s*-->.*?<!--\s*/generate-sample-config\s*-->`)
    59  	headerRegexp  = regexp.MustCompile("generate-sample-config:(.+?):(.+?)\\s*-")
    60  )
    61  
    62  func processFile(path string) error {
    63  	content, err := os.ReadFile(path)
    64  	if err != nil {
    65  		return fmt.Errorf("reading file: %w", err)
    66  	}
    67  
    68  	newContent := sectionRegexp.ReplaceAllFunc(content, func(b []byte) []byte {
    69  		submatches := headerRegexp.FindSubmatch(b)
    70  		buf := bytes.Buffer{}
    71  		subcommand := string(submatches[1])
    72  		format := string(submatches[2])
    73  		writeConfigDocs(&buf, subcommand, format)
    74  		return buf.Bytes()
    75  	})
    76  
    77  	if bytes.Equal(content, newContent) {
    78  		return nil
    79  	}
    80  
    81  	fmt.Println(path)
    82  	return os.WriteFile(path, newContent, 0640)
    83  }
    84  
    85  func writeConfigDocs(w io.Writer, subcommand, format string) {
    86  	flagSet := pflag.NewFlagSet("pyroscope "+subcommand, pflag.ExitOnError)
    87  
    88  	v := viper.New()
    89  	v.SetEnvPrefix("PYROSCOPE")
    90  	v.AutomaticEnv()
    91  	v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
    92  
    93  	opts := []cli.FlagOption{
    94  		cli.WithReplacement("<supportedProfilers>", "pyspy, rbspy, phpspy, dotnetspy, ebpfspy"),
    95  		cli.WithSkipDeprecated(true),
    96  	}
    97  
    98  	var val interface{}
    99  	switch subcommand {
   100  	case "agent":
   101  		val = new(config.Agent)
   102  		// Skip `targets` only from CLI reference.
   103  		if format == "md" {
   104  			cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("targets"))...)
   105  		} else {
   106  			cli.PopulateFlagSet(val, flagSet, v, opts...)
   107  		}
   108  	case "server":
   109  		val = new(config.Server)
   110  		// Skip `metrics-export-rules` only from CLI reference.
   111  		if format == "md" {
   112  			cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("metrics-export-rules"))...)
   113  		} else {
   114  			cli.PopulateFlagSet(val, flagSet, v, opts...)
   115  		}
   116  	case "convert":
   117  		val = new(config.Convert)
   118  		cli.PopulateFlagSet(val, flagSet, v, opts...)
   119  	case "exec":
   120  		val = new(config.Exec)
   121  		cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("pid"))...)
   122  	case "connect":
   123  		val = new(config.Exec)
   124  		cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("group-name", "user-name", "no-root-drop"))...)
   125  	case "target":
   126  		val = new(config.Target)
   127  		cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("tags"))...)
   128  	case "metric-export-rule":
   129  		val = new(config.MetricsExportRule)
   130  		cli.PopulateFlagSet(val, flagSet, v, opts...)
   131  	default:
   132  		//revive:disable-next-line:deep-exit fine for now
   133  		log.Fatalf("Unknown subcommand %q", subcommand)
   134  	}
   135  
   136  	_, _ = fmt.Fprintf(w, "<!-- generate-sample-config:%s:%s -->\n", subcommand, format)
   137  
   138  	switch format {
   139  	case "yaml":
   140  		writeYaml(w, flagSet)
   141  	case "md":
   142  		writeMarkdown(w, flagSet)
   143  	default:
   144  		logrus.Fatalf("Unknown format %q", format)
   145  	}
   146  
   147  	_, _ = fmt.Fprintf(w, "<!-- /generate-sample-config -->")
   148  }
   149  
   150  func writeYaml(w io.Writer, flagSet *pflag.FlagSet) {
   151  	_, _ = fmt.Fprintf(w, "```yaml\n---\n")
   152  	flagSet.VisitAll(func(f *pflag.Flag) {
   153  		if f.Name == "config" {
   154  			return
   155  		}
   156  		var v string
   157  		switch reflect.TypeOf(f.Value).Elem().Kind() {
   158  		case reflect.Slice, reflect.Map:
   159  			v = f.Value.String()
   160  		default:
   161  			v = fmt.Sprintf("%q", f.Value)
   162  		}
   163  		_, _ = fmt.Fprintf(w, "# %s\n%s: %s\n\n", toPrettySentence(f.Usage), f.Name, v)
   164  	})
   165  	_, _ = fmt.Fprintf(w, "```\n")
   166  }
   167  
   168  func writeMarkdown(w io.Writer, flagSet *pflag.FlagSet) {
   169  	_, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", "Name", "Default Value", "Usage")
   170  	_, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", ":-", ":-", ":-")
   171  	flagSet.VisitAll(func(f *pflag.Flag) {
   172  		if f.Name == "config" {
   173  			return
   174  		}
   175  		// Replace vertical bar glyph with HTML code.
   176  		desc := strings.ReplaceAll(toPrettySentence(f.Usage), "|", `&#124;`)
   177  		_, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", f.Name, f.DefValue, desc)
   178  	})
   179  }
   180  
   181  // Capitalizes the first letter and adds period at the end, if necessary.
   182  func toPrettySentence(s string) string {
   183  	if s == "" {
   184  		return ""
   185  	}
   186  	x := []rune(s)
   187  	x[0] = unicode.ToUpper(x[0])
   188  	if x[len(s)-1] != '.' {
   189  		x = append(x, '.')
   190  	}
   191  	return string(x)
   192  }