scripts/update-feature-flags-docs/main.go (153 lines of code) (raw):

package main import ( "bytes" "fmt" "io" "os" "path/filepath" "slices" "strings" "text/template" "gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags" ) const ( startPlaceholder = "<!-- feature_flags_list_start -->" endPlaceholder = "<!-- feature_flags_list_end -->" ) var ffTableTemplate = `{{ placeholder "start" }} | Feature flag | Default value | Deprecated | To be removed with | Description | |--------------|---------------|------------|--------------------|-------------| {{ range $_, $flag := . -}} | {{ $flag.Name | raw }} | {{ $flag.DefaultValue | bool }} | {{ $flag.Deprecated | tick }} | {{ $flag.ToBeRemovedWith }} | {{ $flag.Description }} | {{ end }} {{ placeholder "end" }} ` func main() { root, _ := os.Getwd() if len(os.Args) > 1 { root = os.Args[1] } docsFilePath := filepath.Join(root, "docs/configuration/feature-flags.md") fileContent := getFileContent(docsFilePath) tableContent := prepareTable() newFileContent := replace(fileContent, tableContent) saveFileContent(docsFilePath, newFileContent) } func getFileContent(docsFile string) string { data, err := os.ReadFile(docsFile) if err != nil { panic(fmt.Sprintf("Error while reading file %q: %v", docsFile, err)) } return string(data) } func prepareTable() string { tpl := template.New("ffTable") tpl.Funcs(template.FuncMap{ "placeholder": func(placeholderType string) string { switch placeholderType { case "start": return startPlaceholder case "end": return endPlaceholder default: panic(fmt.Sprintf("Undefined placeholder type %q", placeholderType)) } }, "raw": func(input string) string { return fmt.Sprintf("`%s`", input) }, "bool": func(input bool) string { return fmt.Sprintf("`%t`", input) }, "tick": func(input bool) string { if input { return "{{< icon name=\"check-circle\" >}} Yes" } return "{{< icon name=\"dotted-circle\" >}} No" }, }) tpl, err := tpl.Parse(ffTableTemplate) if err != nil { panic(fmt.Sprintf("Error while parsing the template: %v", err)) } buffer := new(bytes.Buffer) ffs := slices.DeleteFunc(featureflags.GetAll(), func(ff featureflags.FeatureFlag) bool { return ff.Name == "FF_TEST_FEATURE" }) err = tpl.Execute(buffer, ffs) if err != nil { panic(fmt.Sprintf("Error while executing the template: %v", err)) } return buffer.String() } func replace(fileContent, tableContent string) string { replacer := newBlockLineReplacer(startPlaceholder, endPlaceholder, fileContent, tableContent) newContent, err := replacer.Replace() if err != nil { panic(fmt.Sprintf("Error while replacing the content: %v", err)) } return newContent } func saveFileContent(docsFile string, newFileContent string) { err := os.WriteFile(docsFile, []byte(newFileContent), 0o644) if err != nil { panic(fmt.Sprintf("Error while writing new content for %q file: %v", docsFile, err)) } } type blockLineReplacer struct { startLine string endLine string replaceContent string input *bytes.Buffer output *bytes.Buffer startFound bool endFound bool } func (r *blockLineReplacer) Replace() (string, error) { for { line, err := r.input.ReadString('\n') if err == io.EOF { break } if err != nil { return "", fmt.Errorf("error while reading issue description: %w", err) } r.handleLine(line) } return r.output.String(), nil } func (r *blockLineReplacer) handleLine(line string) { r.handleStart(line) r.handleRewrite(line) r.handleEnd(line) } func (r *blockLineReplacer) handleStart(line string) { if r.startFound || !strings.Contains(line, r.startLine) { return } r.startFound = true } func (r *blockLineReplacer) handleRewrite(line string) { if r.startFound && !r.endFound { return } r.output.WriteString(line) } func (r *blockLineReplacer) handleEnd(line string) { if !strings.Contains(line, r.endLine) { return } r.endFound = true r.output.WriteString(r.replaceContent) } func newBlockLineReplacer(startLine, endLine string, input, replaceContent string) *blockLineReplacer { return &blockLineReplacer{ startLine: startLine, endLine: endLine, input: bytes.NewBufferString(input), output: new(bytes.Buffer), replaceContent: replaceContent, startFound: false, endFound: false, } }