Documentation
¶
Index ¶
- Variables
- func CopyAssets(srcDir, destDir string) error
- func GroupByString(artifacts []Artifact, field string) map[string][]Artifact
- func GroupByStrings(artifacts []Artifact, field string) map[string][]Artifact
- func Render(ctx *Context, artifacts *[]Artifact) error
- func RunPipeline[T any](ctx *Context, cfg ContentSourceConfig, p Pipeline, input any) (T, error)
- func WriteSitemap(outDir, baseURL string, artifacts []Artifact) error
- type Artifact
- type BranchResult
- type ContentSourceConfig
- func (csc ContentSourceConfig) Clone() ContentSourceConfig
- func (csc ContentSourceConfig) Get(key string) string
- func (csc ContentSourceConfig) InputFile() string
- func (csc ContentSourceConfig) OutputFile() string
- func (csc ContentSourceConfig) SourcePath() string
- func (csc ContentSourceConfig) TemplateName() string
- type Context
- type FanOutResult
- type MetaLoader
- type PathTransformer
- type Pipeline
- type Plugin
- type Rule
- type Stage
- func FanOut(name string, branches ...Pipeline) Stage
- func Must(s Stage, err error) Stage
- func NewPageRender(tdir string, fns template.FuncMap) (Stage, error)
- func NewStage(name string, ...) Stage
- func SetOutputFile(transform PathTransformer) Stage
- func SetTemplateName(name string) Stage
- func Step[I, O any](name string, fn func(*Context, ContentSourceConfig, I) (O, error)) Stage
- type TemplateRouter
Constants ¶
This section is empty.
Variables ¶
var WriteOutput = Step("write-output", writeOutput)
WriteOutput is a Stage that writes pipeline output to disk. The output directory is taken from ctx.OutputDir (default "public"); the file path within it comes from cfg.OutputFile(). Parent directories are created as needed.
Functions ¶
func CopyAssets ¶
CopyAssets copies all files from srcDir to destDir, preserving the directory structure. Directories and files whose names begin with '.' are skipped.
func GroupByString ¶
GroupByString groups artifacts by the string value of a single-value field (e.g. "Category"). Artifacts where the field is absent, empty, or not a string are skipped.
byCategory := ssg.GroupByString(artifacts, "Category")
for cat, catArtifacts := range byCategory { ... }
func GroupByStrings ¶
GroupByStrings groups artifacts by a multi-value string field (e.g. "Tags"). An artifact appears in the result once for each value it contains.
The field value may be []string, []any (as produced by YAML/JSON parsers), or a bare string (a single value written without a list). Empty strings are skipped.
byTag := ssg.GroupByStrings(artifacts, "Tags")
for tag, tagArtifacts := range byTag { ... }
func Render ¶
Render is a Plugin that materializes all artifacts by running each one's Pipeline. Globals from ctx are merged into each artifact's Meta before the pipeline runs; page frontmatter wins on key collision.
func RunPipeline ¶
RunPipeline executes a Pipeline with the given input, returning the final output or any error encountered. The output is type-asserted to T, and a descriptive error is returned if the assertion fails. To just check for execution errors without caring about the output type, use T = any.
func WriteSitemap ¶
WriteSitemap generates a sitemap.xml in outDir listing all artifacts. baseURL is prepended to each artifact's OutputFile to form the full URL (trailing slashes on baseURL are trimmed automatically). Artifacts with an empty OutputFile are skipped.
Types ¶
type Artifact ¶
type Artifact struct {
Meta ContentSourceConfig
Pipeline Pipeline
}
Artifact is a single unit of work: the page metadata plus the pipeline that will materialize it into output. Separating data (Meta) from behavior (Pipeline) keeps ContentSourceConfig a clean data map.
func NewPage ¶
NewPage creates an Artifact for a page not backed by a file — taxonomy indexes, tag listings, RSS feeds, or any other programmatically-generated output.
data is merged first; OutputFile and TemplateName are then set from the explicit arguments and always win. Content is initialised to an empty byte slice so the pipeline has something to read.
p := ssg.NewPage(
"tags/go/index.html", "tag-list/index.html",
map[string]any{"Tag": "go", "Pages": tagPages},
pipeline,
)
type BranchResult ¶
---- Fan-out ---------------------------------------------------------------
FanOut runs multiple branch Pipelines from a single input. All branches run regardless of individual failures — no short-circuit.
Each branch receives a shallow clone of cfg so metadata mutations (e.g. SetOutputFile) in one branch do not affect the others. The content value (in) is shared across branches. Immutability cannot be enforced at this level because the type is erased, but a branch that needs its own copy can open with an explicit clone step:
var CloneBytes = ssg.Step("clone-bytes",
func(_ *ssg.Context, _ ssg.ContentSourceConfig, in []byte) ([]byte, error) {
return bytes.Clone(in), nil
})
Two usage patterns:
Non-terminal: FanOut is followed by another Stage. On success it returns (FanOutResult, nil) and the next stage receives the FanOutResult and can inspect each branch's Name, Value, and Err freely.
Terminal: FanOut is the last stage. On success the pipeline returns cleanly. On failure it returns (nil, FanOutResult): the FanOutResult implements the error interface so the full result — every branch outcome — is preserved and recoverable with errors.As:
var far FanOutResult if errors.As(err, &far) { for _, b := range far.Branches { fmt.Println(b.Name, b.Err) } }
BranchResult holds the name, final output value, and error for one branch.
type ContentSourceConfig ¶
ContentSourceConfig holds metadata and content for a single page. It is a plain map so that frontmatter parsers, synthetic page builders, and templates can all read and write it without a fixed schema.
func (ContentSourceConfig) Clone ¶
func (csc ContentSourceConfig) Clone() ContentSourceConfig
Clone returns a shallow copy of the map.
func (ContentSourceConfig) Get ¶
func (csc ContentSourceConfig) Get(key string) string
func (ContentSourceConfig) InputFile ¶
func (csc ContentSourceConfig) InputFile() string
func (ContentSourceConfig) OutputFile ¶
func (csc ContentSourceConfig) OutputFile() string
func (ContentSourceConfig) SourcePath ¶
func (csc ContentSourceConfig) SourcePath() string
SourcePath returns the path of the source file relative to ContentDir. Used by SetOutputFile to derive the output path via a PathTransformer.
func (ContentSourceConfig) TemplateName ¶
func (csc ContentSourceConfig) TemplateName() string
type Context ¶
Context holds site-wide state shared across all Plugins and Renderers. It is readable and writable by plugins — for example, a nav-building plugin can compute navigation and store it in Globals for templates to consume.
type FanOutResult ¶
type FanOutResult struct {
Branches []BranchResult
}
FanOutResult holds the outcome of every branch. It implements the error interface so it can be returned directly as an error when branches fail, making the full result set recoverable via errors.As even after the pipeline stops.
func (FanOutResult) Error ¶
func (r FanOutResult) Error() string
Error implements the error interface. It is called only when at least one branch failed; the message is the join of all branch error strings.
func (FanOutResult) Unwrap ¶
func (r FanOutResult) Unwrap() []error
Unwrap returns all branch errors, so errors.As and errors.Is traverse into individual branch failures.
type MetaLoader ¶
MetaLoader parses raw file bytes into frontmatter metadata and body content. Returning a nil map signals that the file should be skipped. If the file has no recognisable frontmatter, return an empty map and the full raw bytes as body.
The return type is map[string]any rather than ContentSourceConfig so that loader implementations have no dependency on the ssg module. LoadContent casts the map to ContentSourceConfig before further processing.
var ContentLoader MetaLoader = func(in []byte) (map[string]any, []byte, error) { jbytes, body, err := tojson.FromFrontMatter(in) if err != nil { return nil, nil, err } meta := make(map[string]any) err = json.Unmarshal(jbytes, &meta) if err != nil { return nil, nil, err } return meta, body, nil }
ContentLoader is the default loader. It assumes a text document with "front matter" typically markdown, with YAML meta data between '---' at the begining.
type PathTransformer ¶
PathTransformer maps a relative input path to a relative output path. The input path is relative to ContentDir. Returning an empty string skips the file.
Use CleanURLs or UglyURLs for the common cases, and wrap with modifiers like SlugNormalize to compose behaviour:
ssg.SlugNormalize(ssg.CleanURLs(".md", ".html"))
func CleanURLs ¶
func CleanURLs(inputExt, outputExt string) PathTransformer
CleanURLs returns a PathTransformer that produces directory-style output paths, making pages accessible without a file extension in the browser.
foo.md → foo/index.html (/foo or /foo/) bar/baz.md → bar/baz/index.html index.md → index.html (root index, not index/index.html)
func SlugNormalize ¶
func SlugNormalize(next PathTransformer) PathTransformer
SlugNormalize returns a PathTransformer that lowercases the filename and replaces spaces and underscores with hyphens before passing to next.
"Foo Bar.md" → "foo-bar.md" → (next applies) "my_post.md" → "my-post.md" → (next applies)
func UglyURLs ¶
func UglyURLs(inputExt, outputExt string) PathTransformer
UglyURLs returns a PathTransformer that maps input files directly to same-named output files, replacing the extension.
foo.md → foo.html bar/baz.md → bar/baz.html
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline is a named sequence of stages executed in order.
type Plugin ¶
Plugin is the single interface for all pipeline stages: load, expand, contract, enrich, and materialize. Each Plugin receives the full artifact set and may add, remove, or modify entries.
type Plugin func(ctx *Context, artifacts *[]Artifact) error
func FileWalker ¶
FileWalker returns a Plugin that walks contentDir, matches each file against rules in order, and appends the resulting Artifacts to the slice.
For each matched file the Loader is called once. Each Output in the matched Rule produces one Artifact, sharing the same parsed metadata but carrying its own Pipeline — enabling one-to-many outputs from a single source file.
Files matching no rule, or whose rule has a nil Loader, are skipped. Directories prefixed with "." are skipped entirely.
func FilterArtifacts ¶
func FilterArtifacts(fn func(ContentSourceConfig) bool) Plugin
FilterArtifacts returns a Plugin that retains only the artifacts for which fn returns true. Use it to remove draft pages, future-dated posts, etc.
Example — exclude draft pages:
ssg.FilterArtifacts(func(meta ssg.ContentSourceConfig) bool {
draft, _ := meta["draft"].(bool)
return !draft
})
type Rule ¶
type Rule struct {
Pattern string
Loader MetaLoader
Pipeline Pipeline
}
Rule pairs a doublestar glob pattern with a MetaLoader and a Pipeline. FileWalker tries rules in order; the first pattern that matches the file's relative path wins. Files that match no rule are skipped. A nil Loader skips the file without reading it.
Rule{
Pattern: "**/*.md",
Loader: metayaml.Loader,
Pipeline: ssg.NewPipeline("post",
ssg.SetOutputFile(ssg.CleanURLs(".md", ".html")),
markdown.New(),
ssg.Must(ssg.NewPageRender("layout", fns)),
ssg.WriteOutput,
),
}
Rule{Pattern: "**/_*"} // nil Loader: skip draft files
type Stage ¶
type Stage interface {
Name() string
Run(ctx *Context, cfg ContentSourceConfig, in any) (any, error)
}
Stage is a single named pipeline step with type-erased execution. Use Step to create a Stage from a typed function.
func FanOut ¶
FanOut returns a Stage that runs each branch Pipeline with the same input. All branches run regardless of individual failures. On success it returns (FanOutResult, nil); on failure it returns (nil, FanOutResult) so the full result set is always recoverable via errors.As.
func Must ¶
Must wraps a (Stage, error) constructor result, panicking if err is non-nil. Use it to inline constructors that return errors in a pipeline slice literal.
ssg.Must(ssg.NewPageRender("layout", fns))
func NewPageRender ¶
NewPageRender loads HTML templates from tdir and returns a DynStage that wraps each page's rendered content in the appropriate layout template.
Template selection: each page's ContentSourceConfig must contain a "TemplateName" key (e.g. "blog/single.html"). The directory portion routes to the right template set; the filename selects the template within it.
Content injection: the stage reads the pipeline's current output, stores it as the string value of page["Content"], then executes the named template with the full ContentSourceConfig as its data. Templates access the body via {{.Content}} and other page metadata via {{.Title}}, {{.Date}}, etc.
Template inheritance: templates in a parent directory are available to all templates in child directories. A template in layout/blog/ can call {{template "base.html" .}} because base.html is parsed into the same set. See TemplateRouter for details.
Block override constraint: all templates in the same directory share one template set, so only one template per directory may use {{define "main"}} (or any other block name). If two sibling templates both define the same block, Go's template engine will error. The solution is to give each template that overrides a block its own subdirectory:
layout/
baseof.html ← defines {{block "main" .}}
post/
index.html ← {{define "main"}} for post pages
tag-list/
index.html ← {{define "main"}} for tag listing pages
tag-index/
index.html ← {{define "main"}} for the tag index page
Each subdirectory gets an isolated template set that inherits baseof.html from the parent but does not share its set with siblings.
fns is an optional map of additional template functions made available to all templates. Pass nil for no extra functions.
func NewStage ¶
func NewStage(name string, fn func(ctx *Context, cfg ContentSourceConfig, in any) (any, error)) Stage
NewStage constructs a Stage from a name and a function.
func SetOutputFile ¶
func SetOutputFile(transform PathTransformer) Stage
SetOutputFile returns a DynStage that applies transform to the artifact's SourcePath and stores the result as OutputFile in cfg. Content passes through unchanged, making this a metadata-only pipeline step. If transform returns "" the OutputFile is left unchanged.
func SetTemplateName ¶
SetTemplateName returns a DynStage that sets TemplateName in cfg to name, but only if frontmatter hasn't already set it. Content passes through unchanged, making this a metadata-only pipeline step.
func Step ¶
Step wraps a typed function into a Stage. The any type is confined here; stage functions use concrete types and are self-documenting.
Pass-through stages (metadata-only, content unchanged) use [any, any]:
Step("set-output-file", func(ctx *Context, cfg ContentSourceConfig, in any) (any, error) { ... })
Terminal sinks use struct{} as the output type:
Step("write-output", func(ctx *Context, cfg ContentSourceConfig, in []byte) (struct{}, error) { ... })
type TemplateRouter ¶
TemplateRouter maps layout directory paths (relative to the layout root) to their compiled template sets. Keys are directory paths such as ".", "blog", or "blog/posts".
Each template set contains all templates parsed from the root down to that directory, so child sets inherit parent templates.
func (TemplateRouter) ExecuteTemplate ¶
ExecuteTemplate routes a template name to the correct set and executes it.
The name is expected to be a slash-separated path where the final component is the template filename and everything before it is the directory key. For example:
"base.html" → directory ".", template "base.html" "blog/single.html" → directory "blog", template "single.html" "blog/posts/page.html"→ directory "blog/posts", template "page.html"