mirror of https://github.com/rancher/webhook.git
150 lines
4.5 KiB
Go
150 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"cmp"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// docFileName defines the name of the files that will be aggregated into overall docs
|
|
const docFileExtension = ".md"
|
|
|
|
type docFile struct {
|
|
content []byte
|
|
resource string
|
|
group string
|
|
version string
|
|
}
|
|
|
|
func generateDocs(resourcesBaseDir, outputFilePath string) (err error) {
|
|
outputFile, err := os.OpenFile(outputFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
|
defer func() {
|
|
closeErr := outputFile.Close()
|
|
if closeErr != nil {
|
|
if err != nil {
|
|
err = fmt.Errorf("%w, error when closing file %s", err, closeErr.Error())
|
|
} else {
|
|
err = closeErr
|
|
}
|
|
}
|
|
}()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
docFiles, err := getDocFiles(resourcesBaseDir)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create documentation: %w", err)
|
|
}
|
|
|
|
currentGroup := ""
|
|
for _, docFile := range docFiles {
|
|
newGroup := docFile.group
|
|
if newGroup != currentGroup {
|
|
// our group has changed, output a new group header
|
|
groupFormatString := "# %s/%s\n"
|
|
if currentGroup != "" {
|
|
groupFormatString = "\n" + groupFormatString
|
|
}
|
|
_, err = fmt.Fprintf(outputFile, groupFormatString, docFile.group, docFile.version)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to write group header for %s/%s: %w", docFile.group, docFile.version, err)
|
|
}
|
|
currentGroup = newGroup
|
|
}
|
|
|
|
_, err = fmt.Fprintf(outputFile, "\n## %s\n\n", docFile.resource)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to write resource header for %s: %w", docFile.resource, err)
|
|
}
|
|
|
|
scanner := bufio.NewScanner(bytes.NewReader(docFile.content))
|
|
for scanner.Scan() {
|
|
line := scanner.Bytes()
|
|
// even if the scanned line is empty, still need to output the newline
|
|
if len(line) != 0 && line[0] == '#' {
|
|
// this line is a markdown header. Since the group header is the top-level indent, indent this down one line
|
|
line = append([]byte{'#'}, line...)
|
|
}
|
|
line = append(line, byte('\n'))
|
|
_, err := outputFile.Write(line)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to write content for %s/%s.%s: %w", docFile.group, docFile.version, docFile.resource, err)
|
|
}
|
|
}
|
|
if scanner.Err() != nil {
|
|
return fmt.Errorf("got an error scanning content for %s/%s.%s: %w", docFile.group, docFile.version, docFile.resource, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getDocFiles finds all markdown files recursively in resourcesBaseDir and converts them to docFiles. Returns in a sorted order,
|
|
// first by group, then by resourceName
|
|
func getDocFiles(baseDir string) ([]docFile, error) {
|
|
entries, err := os.ReadDir(baseDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to list entries in directory %s: %w", baseDir, err)
|
|
}
|
|
|
|
var docFiles []docFile
|
|
for _, entry := range entries {
|
|
entryPath := filepath.Join(baseDir, entry.Name())
|
|
if entry.IsDir() {
|
|
subDocFiles, err := getDocFiles(entryPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
docFiles = append(docFiles, subDocFiles...)
|
|
continue
|
|
}
|
|
if filepath.Ext(entry.Name()) != docFileExtension {
|
|
continue
|
|
}
|
|
content, err := os.ReadFile(filepath.Join(baseDir, entry.Name()))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to read file content for %s: %w", entryPath, err)
|
|
}
|
|
// lop off the last trailing new line to keep consistent spacing for later on
|
|
if content[len(content)-1] == '\n' {
|
|
content = content[:len(content)-1]
|
|
}
|
|
newDir, _ := filepath.Split(baseDir)
|
|
newDir, version := filepath.Split(newDir[:len(newDir)-1])
|
|
newDir, group := filepath.Split(newDir[:len(newDir)-1])
|
|
resource := strings.TrimSuffix(entry.Name(), docFileExtension)
|
|
if newDir == "" || resource == "" || version == "" || group == "" {
|
|
return nil, fmt.Errorf("unable to extract gvr from %s, got group %s, version %s, resource %s", baseDir, group, version, resource)
|
|
}
|
|
// group and version need to have a consistent case so that test.cattle.io/v3 and test.cattle.Io/V3 are grouped the same way
|
|
caser := cases.Lower(language.English)
|
|
docFiles = append(docFiles, docFile{
|
|
content: content,
|
|
resource: resource,
|
|
group: caser.String(group),
|
|
version: caser.String(version),
|
|
})
|
|
}
|
|
// if the groups differ, sort based on the group. If the groups are the same, sort based on the resource
|
|
slices.SortFunc(docFiles, func(a, b docFile) int {
|
|
if a.group == b.group {
|
|
if a.resource == b.resource {
|
|
return cmp.Compare(a.version, b.version)
|
|
}
|
|
return cmp.Compare(a.resource, b.resource)
|
|
}
|
|
return cmp.Compare(a.group, b.group)
|
|
})
|
|
|
|
return docFiles, nil
|
|
}
|