diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 1645dbb07dd..ac1d67a289f 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -130,6 +130,7 @@ - [Extending](./plugins/extending.md) - [CLI and Plugins](./plugins/extending/extending_cli_features_and_plugins.md) - [External Plugins](./plugins/extending/external-plugins.md) + - [Custom Markers](./plugins/extending/custom-markers.md) - [E2E Tests](./plugins/extending/testing-plugins.md) - [Plugins Versioning](./plugins/plugins-versioning.md) diff --git a/docs/book/src/plugins/extending/custom-markers.md b/docs/book/src/plugins/extending/custom-markers.md new file mode 100644 index 00000000000..3968516cdb8 --- /dev/null +++ b/docs/book/src/plugins/extending/custom-markers.md @@ -0,0 +1,170 @@ +# Creating Custom Markers + +## Overview + +When using Kubebuilder as a library, you may need to scaffold files with extensions that aren't natively supported by Kubebuilder's marker system. This guide shows you how to create custom marker support for any file extension. + +## When to Use Custom Markers + +Custom markers are useful when: + +- You're building an external plugin for languages not natively supported by Kubebuilder +- You want to scaffold files with custom extensions (`.rs`, `.java`, `.py`, `.tpl`, etc.) +- You need scaffolding markers in non-Go files for your own use cases +- Your file extensions aren't (and shouldn't be) part of the core `commentsByExt` map + +## Understanding Markers + +Markers are special comments used by Kubebuilder for scaffolding purposes. They indicate where code can be inserted or modified. The core Kubebuilder marker system only supports `.go`, `.yaml`, and `.yml` files by default. + +Example of a marker in a Go file: +```go +// +kubebuilder:scaffold:imports +``` + +## Implementation Example + +Here's how to implement custom markers for Rust files (`.rs`). This same pattern can be applied to any file extension. + +### Define Your Marker Type + +```go +// pkg/markers/rust.go +package markers + +import ( + "fmt" + "path/filepath" + "strings" +) + +const RustPluginPrefix = "+rust:scaffold:" + +type RustMarker struct { + prefix string + comment string + value string +} + +func NewRustMarker(path string, value string) (RustMarker, error) { + ext := filepath.Ext(path) + if ext != ".rs" { + return RustMarker{}, fmt.Errorf("expected .rs file, got %s", ext) + } + + return RustMarker{ + prefix: formatPrefix(RustPluginPrefix), + comment: "//", + value: value, + }, nil +} + +func (m RustMarker) String() string { + return m.comment + " " + m.prefix + m.value +} + +func formatPrefix(prefix string) string { + trimmed := strings.TrimSpace(prefix) + var builder strings.Builder + if !strings.HasPrefix(trimmed, "+") { + builder.WriteString("+") + } + builder.WriteString(trimmed) + if !strings.HasSuffix(trimmed, ":") { + builder.WriteString(":") + } + return builder.String() +} +``` + +### Use in Template Files + +```go +package templates + +import ( + "fmt" + "path/filepath" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "github.com/yourorg/yourplugin/pkg/markers" +) + +type RustMainFile struct { + machinery.TemplateMixin + machinery.ProjectNameMixin +} + +func (f *RustMainFile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("src", "main.rs") + } + + marker, err := markers.NewRustMarker(f.Path, "imports") + if err != nil { + return err + } + + f.TemplateBody = fmt.Sprintf(`// Generated by Rust Plugin +%s + +use std::error::Error; + +fn main() -> Result<(), Box> { + println!("Hello from %s!"); + Ok(()) +} +`, marker.String(), f.ProjectName) + + return nil +} +``` + +### Integrate with External Plugin + +```go +package main + +import ( + "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin/external" + "github.com/yourorg/yourplugin/pkg/templates" +) + +func main() { + p := &external.Plugin{ + Name: "rust.kubebuilder.io", + Version: plugin.Version{Number: 1, Stage: plugin.Alpha}, + + Init: func(req external.PluginRequest) external.PluginResponse { + mainFile := &templates.RustMainFile{} + + return external.PluginResponse{ + Universe: req.Universe, + Files: []machinery.File{mainFile}, + } + }, + } + + external.Run(p) +} +``` + +## Adapting for Other Languages + +To support other file extensions, modify the marker implementation by changing: + +- The comment syntax (e.g., `//` for Java, `#` for Python, `{{/* ... */}}` for templates) +- The file extension check (e.g., `.java`, `.py`, `.tpl`) +- The marker prefix (e.g., `+java:scaffold:`, `+python:scaffold:`) + +## Key Considerations + +1. **Unique Prefixes**: Choose a unique prefix for your plugin to avoid conflicts (e.g., `+rust:scaffold:`, `+java:scaffold:`) + +2. **Comment Syntax**: Different languages have different comment syntax. Ensure you map the correct comment style for each file extension + +3. **Error Handling**: Validate file extensions and return clear error messages for unsupported files + +4. **Testing**: Test your marker implementation with various scenarios to ensure reliability + +For more information on creating external plugins, see [External Plugins](external-plugins.md). \ No newline at end of file