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,
}
}