generator

package
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 14, 2026 License: MIT Imports: 47 Imported by: 0

Documentation

Overview

Package generator implements the code generation engine that powers project scaffolding, command generation, and regeneration from manifest definitions.

The core Generator type orchestrates a shared pipeline: asset generation, command registration, child re-registration, manifest updates, and documentation output. It operates on a manifest-first architecture where .gtb/manifest.yaml is the single source of truth for project structure.

Key entry points are Generator.GenerateSkeleton for new projects, [Generator.GenerateCommand] for adding commands, and Generator.RegenerateProject for rebuilding registration files from an existing manifest.

Index

Constants

View Source
const (
	DefaultFileMode = 0o644
	DefaultDirMode  = 0o755
)

Variables

View Source
var (
	ErrNotGoToolBaseProject      = errors.New("the current project at '%s' is not a gtb project (.gtb/manifest.yaml not found)")
	ErrParentPathNotFound        = errors.New("parent path not found in manifest")
	ErrModuleNotFound            = errors.New("could not find module name in go.mod")
	ErrFuncNotFound              = errors.New("target function not found")
	ErrParentCommandFileNotFound = errors.New("parent command file not found")
)
View Source
var BreakingChanges = map[string]string{
	"v2.10.0": "Breaking changes to Assets interface and command signatures. Please refer to the migration guide.",
}

BreakingChanges is a map of version strings to descriptions of breaking changes introduced in that version. The keys should be valid semantic version strings (e.g., "v2.10.0"). The values are messages displayed to the user when they upgrade across these versions.

View Source
var ErrCommandProtected = errors.New("command is protected")
View Source
var ErrInvalidInput = errors.New("invalid generator input")

ErrInvalidInput is the sentinel wrapped by every Validate* failure. Discriminate with errors.Is in callers that need to distinguish validation failures from other error shapes.

View Source
var ErrInvalidPackageName = errors.Newf("invalid package name")

Functions

func PascalCase

func PascalCase(s string) string

func ValidateDescription

func ValidateDescription(desc string) error

ValidateDescription enforces a bounded-length, control-character-free description that is safe to interpolate into YAML/TOML string values and Markdown prose. The rule explicitly forbids `{{` / `}}` as a belt-and-braces guard: text/template does not re-parse interpolated data, so this is not exploitable today, but matching the pattern lets a future change (e.g. switching to html/template with its `{{`-as-action reparsing) remain safe.

func ValidateEnvPrefix

func ValidateEnvPrefix(prefix string) error

ValidateEnvPrefix accepts an empty string (meaning "no prefix") and otherwise requires an upper-snake-case prefix matching `^[A-Z][A-Z0-9_]{0,31}$`. Shell metacharacters are excluded by the class; length is bounded so the rendered env-var name stays below POSIX limits.

func ValidateHost

func ValidateHost(host string) error

ValidateHost enforces an RFC 1123 hostname (optionally with `:port`). Punycode labels (`xn--...`) are accepted; raw Unicode labels are rejected — callers that need an internationalised host must supply the punycode form explicitly so homoglyph attacks fail visibly at input time rather than in a rendered URL.

func ValidateManifest

func ValidateManifest(m *Manifest) error

ValidateManifest runs every user-influenced field of a loaded Manifest through the validators above. Used by regenerate and manifest-update paths so a tampered manifest fails fast before driving file writes.

Only [Manifest.Properties.Name] is unconditionally required — a manifest missing the tool name is structurally broken. Other fields are optional in the YAML schema and are validated only when populated; empty fields short-circuit to nil, matching the forgiving behaviour of the fine-grained validators above.

func ValidateName

func ValidateName(name string) error

ValidateName enforces the naming rule for the scaffolded tool — lowercase alphanumeric with optional hyphens, a letter first, and at most 64 characters. This tight rule simultaneously forecloses path traversal, Unicode spoofing, and YAML/TOML/Markdown/shell injection because none of the dangerous characters are in the class.

func ValidateOrg

func ValidateOrg(org, releaseProvider string) error

ValidateOrg enforces GitHub-org syntax for the `github` release provider and GitLab-namespace syntax for `gitlab`, including `/`-separated subgroups up to a reasonable depth. CODEOWNERS silently drops invalid `@`-mentions, so catching bad input early prevents the scaffolded project from shipping broken ownership rules.

func ValidateRepo

func ValidateRepo(repo string) error

ValidateRepo enforces Go module path rules: a domain-style first component followed by one or more `[a-zA-Z0-9._~-]+` path segments, no leading/trailing `/`, and no `..` segments. `go mod tidy` would also reject invalid paths, but failing early surfaces a useful error at generation time rather than at first build.

func ValidateSlackChannel

func ValidateSlackChannel(channel string) error

ValidateSlackChannel accepts an empty string and otherwise enforces Slack's own channel-name rules — lowercase, alphanumeric, hyphens, 1–80 characters.

func ValidateSlackTeam

func ValidateSlackTeam(team string) error

ValidateSlackTeam accepts an empty string and otherwise enforces Slack's workspace-name rules.

func ValidateTeamsChannel

func ValidateTeamsChannel(channel string) error

ValidateTeamsChannel accepts an empty string and otherwise enforces generic "safe for YAML and Markdown" rules: bounded length, no control characters, no template-brace sequences.

func ValidateTeamsTeam

func ValidateTeamsTeam(team string) error

ValidateTeamsTeam mirrors ValidateTeamsChannel.

func ValidateTelemetryEndpoint

func ValidateTelemetryEndpoint(endpoint string) error

ValidateTelemetryEndpoint accepts an empty string (meaning "no endpoint") and otherwise requires a syntactically valid HTTP or HTTPS URL, bounded in length and free of control characters.

Types

type CommandContext

type CommandContext struct {
	// Identity
	Name       string
	ParentPath []string // empty = direct child of root

	// Display
	Short string
	Long  string

	// Routing / feature options
	Aliases                       []string
	Args                          string
	WithAssets                    bool
	WithInitializer               bool
	WithConfigValidation          bool
	WrapSubcommandsWithMiddleware bool
	PersistentPreRun              bool
	PreRun                        bool
	Protected                     *bool
	Hidden                        bool

	// Project-level settings (carried from the originating generator)
	ProjectPath string
	DryRun      bool
	Force       bool
	UpdateDocs  bool
}

CommandContext holds the fully resolved configuration for a single command generation or regeneration pass. It is a value type so recursive invocations cannot accidentally share or mutate each other's state.

func (CommandContext) ToConfig

func (c CommandContext) ToConfig() *Config

ToConfig converts the CommandContext into a *Config suitable for constructing a Generator scoped to this specific command.

type CommandFlag

type CommandFlag struct {
	Default       string
	DefaultIsCode bool
	Description   string
	Hidden        bool
	Name          string
	Persistent    bool
	Required      bool
	Shorthand     string
	Type          string
}

type CommandPipeline

type CommandPipeline struct {
	// contains filtered or unexported fields
}

CommandPipeline owns the ordered post-generation steps that are shared by both generate command and regenerate project. Constructing a pipeline and calling Run centralises all registration, hash, manifest, and documentation logic so a fix in one place applies to both entrypoints.

func (*CommandPipeline) Run

Run executes the pipeline steps in order for the given command data and directory. Fatal steps (asset generation) return an error immediately. Advisory steps (registration, manifest) log a warning and accumulate into PipelineResult so callers can inspect partial failures.

type Config

type Config struct {
	Agentless                     bool
	AIModel                       string
	AIProvider                    string
	Aliases                       []string
	Args                          string
	DryRun                        bool
	Flags                         []string
	Force                         bool
	Hidden                        bool
	Overwrite                     string // allow, deny, or ask (default ask)
	Long                          string
	Name                          string
	Parent                        string
	Path                          string
	PersistentPreRun              bool
	PreRun                        bool
	Prompt                        string
	Protected                     *bool
	ScriptPath                    string
	Short                         string
	UpdateDocs                    bool
	WithAssets                    bool
	WithConfigValidation          bool
	WithInitializer               bool
	WrapSubcommandsWithMiddleware *bool
}

type DryRunResult

type DryRunResult struct {
	Created  []FilePreview `json:"created,omitempty"`
	Modified []FilePreview `json:"modified,omitempty"`
}

DryRunResult contains the preview of planned file operations.

func (*DryRunResult) Print

func (r *DryRunResult) Print(w io.Writer)

Print writes a human-readable preview of the dry-run result to w.

type FilePreview

type FilePreview struct {
	Path    string `json:"path"`
	Content []byte `json:"content,omitempty"`
	Diff    string `json:"diff,omitempty"`
}

FilePreview represents a single file operation in a dry run.

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

func New

func New(p *props.Props, cfg *Config) *Generator

func (*Generator) FindCommandParentPath

func (g *Generator) FindCommandParentPath(name string) ([]string, error)

func (*Generator) Generate

func (g *Generator) Generate(ctx context.Context) error

func (*Generator) GenerateCommandFile

func (g *Generator) GenerateCommandFile(ctx context.Context, cmdDir string, data *templates.CommandData) error

func (*Generator) GenerateDocs

func (g *Generator) GenerateDocs(ctx context.Context, target string, isPackage bool) error

GenerateDocs generates documentation for the command or package.

func (*Generator) GenerateDryRun

func (g *Generator) GenerateDryRun(ctx context.Context) (*DryRunResult, error)

GenerateDryRun previews what Generate would do without writing to disk.

func (*Generator) GenerateSkeleton

func (g *Generator) GenerateSkeleton(ctx context.Context, config SkeletonConfig) error

func (*Generator) GenerateSkeletonDryRun

func (g *Generator) GenerateSkeletonDryRun(ctx context.Context, config SkeletonConfig) (*DryRunResult, error)

GenerateSkeletonDryRun previews what GenerateSkeleton would do without writing to disk.

func (*Generator) RegenerateManifest

func (g *Generator) RegenerateManifest(ctx context.Context) error

func (*Generator) RegenerateProject

func (g *Generator) RegenerateProject(ctx context.Context) error

func (*Generator) RegenerateProjectDryRun

func (g *Generator) RegenerateProjectDryRun(ctx context.Context) (*DryRunResult, error)

RegenerateProjectDryRun previews what RegenerateProject would do without writing to disk.

func (*Generator) Remove

func (g *Generator) Remove(ctx context.Context) error

func (*Generator) SetProtection

func (g *Generator) SetProtection(ctx context.Context, commandName string, protected bool) error

type IgnoreRules

type IgnoreRules struct {
	// contains filtered or unexported fields
}

IgnoreRules holds compiled ignore patterns from a .gtb/ignore file. Patterns are evaluated top-to-bottom; later patterns override earlier ones. Negation (!) re-includes a previously excluded file.

func LoadIgnoreRules

func LoadIgnoreRules(fs afero.Fs, projectPath string) *IgnoreRules

LoadIgnoreRules reads the .gtb/ignore file from the project directory. Returns empty rules (nothing ignored) if the file doesn't exist.

func (*IgnoreRules) IsIgnored

func (r *IgnoreRules) IsIgnored(relPath string) bool

IsIgnored evaluates all rules top-to-bottom and returns whether the given relative path should be ignored. Negation patterns (!) can re-include files excluded by earlier patterns.

type Manifest

type Manifest struct {
	Properties    ManifestProperties    `yaml:"properties"`
	ReleaseSource ManifestReleaseSource `yaml:"release_source"`
	Version       ManifestVersion       `yaml:"version"`
	Hashes        map[string]string     `yaml:"hashes,omitempty"` // project-level file hashes (relative path → SHA256)
	Commands      []ManifestCommand     `yaml:"commands,omitempty"`
}

func (*Manifest) GetReleaseSource

func (m *Manifest) GetReleaseSource() (sourceType, owner, repo string)

GetReleaseSource returns the release source type, owner, and repo.

type ManifestCommand

type ManifestCommand struct {
	Name                          string            `yaml:"name"`
	Description                   MultilineString   `yaml:"description"`
	LongDescription               MultilineString   `yaml:"long_description,omitempty"`
	Aliases                       []string          `yaml:"aliases,omitempty"`
	Hidden                        bool              `yaml:"hidden,omitempty"`
	Args                          string            `yaml:"args,omitempty"`
	Hash                          string            `yaml:"hash,omitempty"` // Deprecated: use Hashes
	Hashes                        map[string]string `yaml:"hashes,omitempty"`
	WithAssets                    bool              `yaml:"with_assets,omitempty"`
	WithInitializer               bool              `yaml:"with_initializer,omitempty"`
	WithConfigValidation          bool              `yaml:"with_config_validation,omitempty"`
	WrapSubcommandsWithMiddleware bool              `yaml:"wrap_subcommands_with_middleware,omitempty"`
	Protected                     *bool             `yaml:"protected,omitempty"`

	PersistentPreRun  bool              `yaml:"persistent_pre_run,omitempty"`
	PreRun            bool              `yaml:"pre_run,omitempty"`
	MutuallyExclusive [][]string        `yaml:"mutually_exclusive,omitempty"`
	RequiredTogether  [][]string        `yaml:"required_together,omitempty"`
	Flags             []ManifestFlag    `yaml:"flags,omitempty"`
	Commands          []ManifestCommand `yaml:"commands,omitempty"`
	Warning           string            `yaml:"-"` // Used for comments
}

func (ManifestCommand) MarshalYAML

func (c ManifestCommand) MarshalYAML() (any, error)

type ManifestCommandUpdate

type ManifestCommandUpdate struct {
	Name                          string
	Description                   string
	LongDescription               string
	Aliases                       []string
	Args                          string
	Hashes                        map[string]string
	Flags                         []ManifestFlag
	WithAssets                    bool
	WithInitializer               bool
	WithConfigValidation          bool
	WrapSubcommandsWithMiddleware *bool
	PersistentPreRun              bool
	PreRun                        bool
	Protected                     *bool
	Hidden                        bool
}

ManifestCommandUpdate carries all fields that updateCommandRecursive writes to a ManifestCommand entry. Adding a new manifest field means adding it here rather than extending the function signature.

type ManifestFeature

type ManifestFeature struct {
	Name    string `yaml:"name"`
	Enabled bool   `yaml:"enabled"`
}

type ManifestFlag

type ManifestFlag struct {
	Name          string          `yaml:"name"`
	Type          string          `yaml:"type"`
	Description   MultilineString `yaml:"description"`
	Persistent    bool            `yaml:"persistent,omitempty"`
	Shorthand     string          `yaml:"shorthand,omitempty"`
	Default       string          `yaml:"default,omitempty"`
	DefaultIsCode bool            `yaml:"default_is_code,omitempty"`
	Required      bool            `yaml:"required,omitempty"`
	Hidden        bool            `yaml:"hidden,omitempty"`
	Warning       string          `yaml:"-"` // Used for comments
}

func (ManifestFlag) MarshalYAML

func (f ManifestFlag) MarshalYAML() (any, error)

type ManifestHelp

type ManifestHelp struct {
	Type         string `yaml:"type,omitempty"`
	SlackChannel string `yaml:"slack_channel,omitempty"`
	SlackTeam    string `yaml:"slack_team,omitempty"`
	TeamsChannel string `yaml:"teams_channel,omitempty"`
	TeamsTeam    string `yaml:"teams_team,omitempty"`
}

type ManifestProperties

type ManifestProperties struct {
	Name        string            `yaml:"name"`
	Description MultilineString   `yaml:"description"`
	Features    []ManifestFeature `yaml:"features"`
	EnvPrefix   string            `yaml:"env_prefix,omitempty"`
	Help        ManifestHelp      `yaml:"help,omitempty"`
	Telemetry   ManifestTelemetry `yaml:"telemetry,omitempty"`
}

type ManifestReleaseSource

type ManifestReleaseSource struct {
	Type    string `yaml:"type"`
	Host    string `yaml:"host"`
	Owner   string `yaml:"owner"`
	Repo    string `yaml:"repo"`
	Private bool   `yaml:"private,omitempty"`
}

type ManifestTelemetry

type ManifestTelemetry struct {
	Endpoint     string `yaml:"endpoint,omitempty"`
	OTelEndpoint string `yaml:"otel_endpoint,omitempty"`
}

ManifestTelemetry holds telemetry configuration for generated tools.

type ManifestVersion

type ManifestVersion struct {
	GoToolBase string `yaml:"gtb"`
}

type MultilineString

type MultilineString string

func (MultilineString) MarshalYAML

func (s MultilineString) MarshalYAML() (any, error)

type PipelineOptions

type PipelineOptions struct {
	SkipAssets        bool // do not generate asset files
	SkipDocumentation bool // do not run documentation generation
	SkipRegistration  bool // do not modify the parent cmd.go
}

PipelineOptions controls which steps CommandPipeline executes. The zero value enables all steps.

type PipelineResult

type PipelineResult struct {
	Warnings []StepWarning
}

PipelineResult is returned by CommandPipeline.Run and carries any advisory warnings accumulated during execution. A non-empty Warnings slice does not indicate overall failure — the pipeline continued past those steps.

type SkeletonConfig

type SkeletonConfig struct {
	Name                  string
	Repo                  string
	Host                  string
	Description           string
	Path                  string
	GoVersion             string // overrides autodetected version when set
	Features              []ManifestFeature
	Private               bool   // true if the repository requires authentication to access
	HelpType              string // "slack", "teams", or ""
	SlackChannel          string
	SlackTeam             string
	TeamsChannel          string
	TeamsTeam             string
	TelemetryEndpoint     string // populated from manifest telemetry.endpoint
	TelemetryOTelEndpoint string // populated from manifest telemetry.otel_endpoint
	EnvPrefix             string // environment variable prefix for config overrides
}

type StepWarning

type StepWarning struct {
	Step string
	Err  error
}

StepWarning records a non-fatal failure within a pipeline step.

Directories

Path Synopsis
Package templates provides Go template definitions and data structures used by the generator to produce CLI command scaffolding, registration code (cmd.go), and implementation stubs (main.go).
Package templates provides Go template definitions and data structures used by the generator to produce CLI command scaffolding, registration code (cmd.go), and implementation stubs (main.go).
Package verifier provides post-generation verification strategies that validate generated projects compile and pass tests.
Package verifier provides post-generation verification strategies that validate generated projects compile and pass tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL