Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
250
tools/readme_config_includer/generator.go
Normal file
250
tools/readme_config_includer/generator.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
// This is a tool to embed configuration files into the README.md of all plugins
|
||||
// It searches for TOML sections in the plugins' README.md and detects includes specified in the form
|
||||
//
|
||||
// ```toml [@includeA.conf[ @includeB[ @...]]
|
||||
// Whatever is in here gets replaced.
|
||||
// ```
|
||||
//
|
||||
// Then it will replace everything in this section by the concatenation of the file `includeA.conf`, `includeB` etc.
|
||||
// content. The tool is not stateful, so it can be run multiple time with a stable result as long
|
||||
// as the included files do not change.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/text"
|
||||
)
|
||||
|
||||
var (
|
||||
// Finds all comment section parts `<-- @includefile -->`
|
||||
commentIncludesEx = regexp.MustCompile(`<!--\s+(@.+)+\s+-->`)
|
||||
// Finds all TOML sections of the form `toml @includefile`
|
||||
tomlIncludesEx = regexp.MustCompile(`[\s"]+(@.+)+"?`)
|
||||
// Extracts the `includefile` part
|
||||
includeMatch = regexp.MustCompile(`(?:@([^\s"]+))+`)
|
||||
)
|
||||
|
||||
type includeBlock struct {
|
||||
Includes []string
|
||||
Start int
|
||||
Stop int
|
||||
Newlines bool
|
||||
}
|
||||
|
||||
func extractIncludeBlock(txt []byte, includesEx *regexp.Regexp, root string) *includeBlock {
|
||||
includes := includesEx.FindSubmatch(txt)
|
||||
if len(includes) != 2 {
|
||||
return nil
|
||||
}
|
||||
block := includeBlock{}
|
||||
for _, inc := range includeMatch.FindAllSubmatch(includes[1], -1) {
|
||||
if len(inc) != 2 {
|
||||
continue
|
||||
}
|
||||
include := string(inc[1])
|
||||
// Make absolute paths relative to the include-root if any
|
||||
if strings.HasPrefix(include, "/") {
|
||||
if root == "" {
|
||||
log.Printf("Ignoring absolute include %q without include root...", include)
|
||||
continue
|
||||
}
|
||||
include = filepath.Join(root, include)
|
||||
}
|
||||
include, err := filepath.Abs(include)
|
||||
if err != nil {
|
||||
log.Printf("Cannot resolve include %q...", include)
|
||||
continue
|
||||
}
|
||||
if fi, err := os.Stat(include); err != nil || !fi.Mode().IsRegular() {
|
||||
log.Printf("Ignoring include %q as it cannot be found or is not a regular file...", include)
|
||||
continue
|
||||
}
|
||||
block.Includes = append(block.Includes, include)
|
||||
}
|
||||
return &block
|
||||
}
|
||||
|
||||
func insertInclude(buf *bytes.Buffer, include string) error {
|
||||
file, err := os.Open(include)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening include %q failed: %w", include, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write the include and make sure we get a newline
|
||||
if _, err := io.Copy(buf, file); err != nil {
|
||||
return fmt.Errorf("inserting include %q failed: %w", include, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertIncludes(buf *bytes.Buffer, b *includeBlock) error {
|
||||
// Insert newlines before and after
|
||||
if b.Newlines {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
// Insert all includes in the order they occurred
|
||||
for i, include := range b.Includes {
|
||||
if i > 0 {
|
||||
// Add a separating newline between included blocks
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
if err := insertInclude(buf, include); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Make sure we add a trailing newline
|
||||
if !bytes.HasSuffix(buf.Bytes(), []byte("\n")) || b.Newlines {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Estimate Telegraf root to be able to handle absolute paths
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot get working directory: %v", err)
|
||||
}
|
||||
cwd, err = filepath.Abs(cwd)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot resolve working directory: %v", err)
|
||||
}
|
||||
|
||||
var includeRoot string
|
||||
if idx := strings.LastIndex(cwd, filepath.FromSlash("/plugins/")); idx > 0 {
|
||||
includeRoot = cwd[:idx]
|
||||
}
|
||||
|
||||
// Get the file permission of the README for later use
|
||||
inputFilename := "README.md"
|
||||
inputFileInfo, err := os.Lstat(inputFilename)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot get file permissions: %v", err)
|
||||
}
|
||||
perm := inputFileInfo.Mode().Perm()
|
||||
|
||||
// Read and parse the README markdown file
|
||||
readme, err := os.ReadFile(inputFilename)
|
||||
if err != nil {
|
||||
log.Fatalf("Reading README failed: %v", err)
|
||||
}
|
||||
parser := goldmark.DefaultParser()
|
||||
root := parser.Parse(text.NewReader(readme))
|
||||
|
||||
// Walk the markdown to identify the (TOML) parts to replace
|
||||
blocksToReplace := make([]*includeBlock, 0)
|
||||
for rawnode := root.FirstChild(); rawnode != nil; rawnode = rawnode.NextSibling() {
|
||||
// Only match TOML code nodes
|
||||
var txt []byte
|
||||
var start, stop int
|
||||
var newlines bool
|
||||
var re *regexp.Regexp
|
||||
switch node := rawnode.(type) {
|
||||
case *ast.FencedCodeBlock:
|
||||
if string(node.Language(readme)) != "toml" {
|
||||
// Ignore any other node type or language
|
||||
continue
|
||||
}
|
||||
// Extract the block borders
|
||||
start = node.Info.Segment.Stop + 1
|
||||
stop = start
|
||||
lines := node.Lines()
|
||||
if lines.Len() > 0 {
|
||||
stop = lines.At(lines.Len() - 1).Stop
|
||||
}
|
||||
txt = node.Info.Value(readme)
|
||||
re = tomlIncludesEx
|
||||
case *ast.Heading:
|
||||
if node.ChildCount() < 2 {
|
||||
continue
|
||||
}
|
||||
child, ok := node.LastChild().(*ast.RawHTML)
|
||||
if !ok || child.Segments.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
segment := child.Segments.At(0)
|
||||
if !commentIncludesEx.Match(segment.Value(readme)) {
|
||||
continue
|
||||
}
|
||||
start = segment.Stop + 1
|
||||
stop = len(readme) // necessary for cases with no more headings
|
||||
for rawnode = rawnode.NextSibling(); rawnode != nil; rawnode = rawnode.NextSibling() {
|
||||
if h, ok := rawnode.(*ast.Heading); ok && h.Level <= node.Level {
|
||||
if rawnode.Lines().Len() > 0 {
|
||||
stop = rawnode.Lines().At(0).Start - h.Level - 1
|
||||
} else {
|
||||
//nolint:staticcheck // need to use this since we aren't sure the type
|
||||
log.Printf("heading without lines: %s", string(rawnode.Text(readme)))
|
||||
stop = start // safety measure to prevent removing all text
|
||||
}
|
||||
// Make sure we also iterate the present heading
|
||||
rawnode = h.PreviousSibling()
|
||||
break
|
||||
}
|
||||
}
|
||||
txt = segment.Value(readme)
|
||||
re = commentIncludesEx
|
||||
newlines = true
|
||||
default:
|
||||
// Ignore everything else
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the includes from the node
|
||||
block := extractIncludeBlock(txt, re, includeRoot)
|
||||
if block != nil {
|
||||
block.Start = start
|
||||
block.Stop = stop
|
||||
block.Newlines = newlines
|
||||
blocksToReplace = append(blocksToReplace, block)
|
||||
}
|
||||
|
||||
// Catch the case of heading-end-search exhausted all nodes
|
||||
if rawnode == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the content of the TOML blocks with includes
|
||||
var output bytes.Buffer
|
||||
output.Grow(len(readme))
|
||||
offset := 0
|
||||
for _, b := range blocksToReplace {
|
||||
// Copy everything up to the beginning of the block we want to replace and make sure we get a newline
|
||||
output.Write(readme[offset:b.Start])
|
||||
if !bytes.HasSuffix(output.Bytes(), []byte("\n")) {
|
||||
output.WriteString("\n")
|
||||
}
|
||||
offset = b.Stop
|
||||
|
||||
// Insert the include file
|
||||
if err := insertIncludes(&output, b); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Copy the remaining of the original file...
|
||||
output.Write(readme[offset:])
|
||||
|
||||
// Write output with same permission as input
|
||||
file, err := os.OpenFile(inputFilename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
|
||||
if err != nil {
|
||||
log.Fatalf("Opening output file failed: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err := output.WriteTo(file); err != nil {
|
||||
log.Panicf("Writing output file failed: %v", err)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue