package
0.102.0
Repository: https://github.com/elastic/elastic-package.git
Documentation: pkg.go.dev

# README

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License.

package docs

import ( "bytes" "errors" "fmt" "os" "path/filepath" "text/template"

"github.com/pmezard/go-difflib/difflib"

"github.com/elastic/elastic-package/internal/builder"
"github.com/elastic/elastic-package/internal/logger"
"github.com/elastic/elastic-package/internal/packages"

)

// ReadmeFile contains file name and status of each readme file. type ReadmeFile struct { FileName string UpToDate bool Diff string Error error }

// AreReadmesUpToDate function checks if all the .md readme files are up-to-date. func AreReadmesUpToDate() ([]ReadmeFile, error) { packageRoot, err := packages.MustFindPackageRoot() if err != nil { return nil, fmt.Errorf("package root not found: %w", err) }

files, err := filepath.Glob(filepath.Join(packageRoot, "_dev", "build", "docs", "*.md"))
if err != nil && !errors.Is(err, os.ErrNotExist) {
	return nil, fmt.Errorf("reading directory entries failed: %w", err)
}

var readmeFiles []ReadmeFile
for _, filePath := range files {
	fileName := filepath.Base(filePath)
	ok, diff, err := isReadmeUpToDate(fileName, packageRoot)
	if !ok || err != nil {
		readmeFile := ReadmeFile{
			FileName: fileName,
			UpToDate: ok,
			Diff:     diff,
			Error:    err,
		}
		readmeFiles = append(readmeFiles, readmeFile)
	}
}

if readmeFiles != nil {
	return readmeFiles, fmt.Errorf("files do not match")
}
return readmeFiles, nil

}

func isReadmeUpToDate(fileName, packageRoot string) (bool, string, error) { logger.Debugf("Check if %s is up-to-date", fileName)

rendered, shouldBeRendered, err := generateReadme(fileName, packageRoot)
if err != nil {
	return false, "", fmt.Errorf("generating readme file failed: %w", err)
}
if !shouldBeRendered {
	return true, "", nil // README file is static and doesn't use template.
}

existing, found, err := readReadme(fileName, packageRoot)
if err != nil {
	return false, "", fmt.Errorf("reading README file failed: %w", err)
}
if !found {
	return false, "", nil
}
if bytes.Equal(existing, rendered) {
	return true, "", nil
}
var buf bytes.Buffer
err = difflib.WriteUnifiedDiff(&buf, difflib.UnifiedDiff{
	A:        difflib.SplitLines(string(existing)),
	B:        difflib.SplitLines(string(rendered)),
	FromFile: "want",
	ToFile:   "got",
	Context:  1,
})
return false, buf.String(), err

}

// UpdateReadmes function updates all .md readme files using a defined template // files. The function doesn't perform any action if the template file is not present. func UpdateReadmes(packageRoot string) ([]string, error) { readmeFiles, err := filepath.Glob(filepath.Join(packageRoot, "_dev", "build", "docs", "*.md")) if err != nil { return nil, fmt.Errorf("reading directory entries failed: %w", err) }

var targets []string
for _, filePath := range readmeFiles {
	fileName := filepath.Base(filePath)
	target, err := updateReadme(fileName, packageRoot)
	if err != nil {
		return nil, fmt.Errorf("updating readme file %s failed: %w", fileName, err)
	}

	if target != "" {
		targets = append(targets, target)
	}
}
return targets, nil

}

func updateReadme(fileName, packageRoot string) (string, error) { logger.Debugf("Update the %s file", fileName)

rendered, shouldBeRendered, err := generateReadme(fileName, packageRoot)
if err != nil {
	return "", err
}
if !shouldBeRendered {
	return "", nil
}

target, err := writeReadme(fileName, packageRoot, rendered)
if err != nil {
	return "", fmt.Errorf("writing %s file failed: %w", fileName, err)
}

packageBuildRoot, err := builder.BuildPackagesDirectory(packageRoot)
if err != nil {
	return "", fmt.Errorf("package build root not found: %w", err)
}

_, err = writeReadme(fileName, packageBuildRoot, rendered)
if err != nil {
	return "", fmt.Errorf("writing %s file failed: %w", fileName, err)
}
return target, nil

}

func generateReadme(fileName, packageRoot string) ([]byte, bool, error) { logger.Debugf("Generate %s file (package: %s)", fileName, packageRoot) templatePath, found, err := findReadmeTemplatePath(fileName, packageRoot) if err != nil { return nil, false, fmt.Errorf("can't locate %s template file: %w", fileName, err) } if !found { logger.Debug("README file is static, can't be generated from the template file") return nil, false, nil } logger.Debugf("Template file for %s found: %s", fileName, templatePath)

linksMap, err := readLinksMap()
if err != nil {
	return nil, false, err
}

rendered, err := renderReadme(fileName, packageRoot, templatePath, linksMap)
if err != nil {
	return nil, true, fmt.Errorf("rendering Readme failed: %w", err)
}
return rendered, true, nil

}

func findReadmeTemplatePath(fileName, packageRoot string) (string, bool, error) { templatePath := filepath.Join(packageRoot, "_dev", "build", "docs", fileName) _, err := os.Stat(templatePath) if err != nil && errors.Is(err, os.ErrNotExist) { return "", false, nil // README.md file not found } if err != nil { return "", false, fmt.Errorf("can't stat the %s file: %w", fileName, err) } return templatePath, true, nil }

func renderReadme(fileName, packageRoot, templatePath string, linksMap linkMap) ([]byte, error) { logger.Debugf("Render %s file (package: %s, templatePath: %s)", fileName, packageRoot, templatePath)

t := template.New(fileName)
t, err := t.Funcs(template.FuncMap{
	"event": func(args ...string) (string, error) {
		if len(args) > 0 {
			return renderSampleEvent(packageRoot, args[0])
		}
		return renderSampleEvent(packageRoot, "")
	},
	"fields": func(args ...string) (string, error) {
		if len(args) > 0 {
			dataStreamPath := filepath.Join(packageRoot, "data_stream", args[0])
			return renderExportedFields(dataStreamPath)
		}
		return renderExportedFields(packageRoot)
	},
	"url": func(args ...string) (string, error) {
		options := linkOptions{}
		if len(args) > 1 {
			options.caption = args[1]
		}
		return linksMap.RenderLink(args[0], options)
	},
}).ParseFiles(templatePath)
if err != nil {
	return nil, fmt.Errorf("parsing README template failed (path: %s): %w", templatePath, err)
}

var rendered bytes.Buffer
err = t.Execute(&rendered, nil)
if err != nil {
	return nil, fmt.Errorf("executing template failed: %w", err)
}
return rendered.Bytes(), nil

}

func readReadme(fileName, packageRoot string) ([]byte, bool, error) { logger.Debugf("Read existing %s file (package: %s)", fileName, packageRoot)

readmePath := filepath.Join(packageRoot, "docs", fileName)
b, err := os.ReadFile(readmePath)
if err != nil && errors.Is(err, os.ErrNotExist) {
	return nil, false, nil
}
if err != nil {
	return nil, false, fmt.Errorf("readfile failed (path: %s): %w", readmePath, err)
}
return b, true, err

}

func writeReadme(fileName, packageRoot string, content []byte) (string, error) { logger.Debugf("Write %s file (package: %s)", fileName, packageRoot)

docsPath := docsPath(packageRoot)
logger.Debugf("Create directories: %s", docsPath)
err := os.MkdirAll(docsPath, 0o755)
if err != nil {
	return "", fmt.Errorf("mkdir failed (path: %s): %w", docsPath, err)
}

aReadmePath := readmePath(fileName, packageRoot)
logger.Debugf("Write %s file to: %s", fileName, aReadmePath)

err = os.WriteFile(aReadmePath, content, 0o644)
if err != nil {
	return "", fmt.Errorf("writing file failed (path: %s): %w", aReadmePath, err)
}
return aReadmePath, nil

}

func readmePath(fileName, packageRoot string) string { return filepath.Join(docsPath(packageRoot), fileName) }

func docsPath(packageRoot string) string { return filepath.Join(packageRoot, "docs") }

# Functions

AreReadmesUpToDate function checks if all the .md readme files are up-to-date.
UpdateReadmes function updates all .md readme files using a defined template files.

# Structs

ReadmeFile contains file name and status of each readme file.