templar

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 4, 2026 License: MIT Imports: 25 Imported by: 8

README

Templar: Go Template Loader

Go Reference

Templar is a powerful extension to Go's standard templating libraries that adds dependency management, simplifies template composition, and solves common pain points in template organization.

Why Templar?

Templar is designed to integrate smoothly with Go's standard templating libraries while solving common issues:

  1. Minimal Learning Curve: If you know Go templates, you already know 99% of Templar.
  2. Zero New Runtime Syntax: The include directives are processed before rendering (variable based inclusion in the works).
  3. Flexible and Extensible: Create custom loaders for any template source (file loader for now, more in the works).
  4. Production Ready: Handles complex dependencies, prevents cycles, and provides clear error messages (and aiming to get better at this).

Background

Go's built-in templating libraries (text/template and html/template) are powerful but have limitations when working with complex template structures:

  1. No Native Dependency Management: When templates reference other templates, you must manually ensure they're loaded in the correct order.

  2. Global Template Namespace: All template definitions share a global namespace, making it challenging to use different versions of the same template in different contexts.

  3. Brittle Template Resolution: In large applications, templates often load differently in development vs. production environments.

  4. Verbose Template Loading: Loading templates with their dependencies typically requires repetitive boilerplate code:

// Standard approach - verbose and error-prone
func renderIndexPage(w http.ResponseWriter, r *http.Request) {
  t := template.ParseFiles("Base1.tmpl", "a2.tmpl", "IndexPage.tmpl")
  t.Execute(w, data)
}

func renderProductListPage(w http.ResponseWriter, r *http.Request) {
  t := template.ParseFiles("AnotherBase.tmpl", "a2.tmpl", "ProductListPage.tmpl")
  t.Execute(w, data)
}

Proposal

Templar solves these problems by providing:

  1. Dependency Declaration: Templates can declare their own dependencies using a simple include syntax:
{{# include "base.tmpl" #}}
{{# include "components/header.tmpl" #}}

<div class="content">
  {{ template "content" . }}
</div>
  1. Automatic Template Loading: Templar automatically loads and processes all dependencies:
// With Templar - clean and maintainable
func renderIndexPage(w http.ResponseWriter, r *http.Request) {
  tmpl := loadTemplate("IndexPage.tmpl")  // Dependencies automatically handled
  tmpl.Execute(w, data)
}
  1. Flexible Template Resolution: Multiple loaders can be configured to find templates in different locations.

  2. Template Reuse: The same template name can be reused in different contexts without conflict.

Getting Started

Basic Example
package main

import (
    "os"
    "github.com/panyam/templar"
)

func main() {
  // Create a template group
  group := templar.NewTemplateGroup()
  
  // Create a filesystem loader that searches multiple directories
  group.Loader = templar.NewFileSystemLoader(
      "templates/",
      "templates/shared/",
  )
  
  // Load a root template (dependencies handled automatically)
  rootTemplate := group.MustLoad("pages/homepage.tmpl", "")

  // Prepare data for the template
  data := map[string]any{
    "Title": "Home Page",
    "User": User{
      ID:   1,
      Name: "John Doe",
    },
    "Updates": []Update{
      {Title: "New Feature Released", Date: "2023-06-15"},
      {Title: "System Maintenance", Date: "2023-06-10"},
      {Title: "Welcome to our New Site", Date: "2023-06-01"},
    },
    "Featured": FeaturedContent{
      Title:       "Summer Sale",
      Description: "Get 20% off on all products until July 31st!",
      URL:         "/summer-sale",
    },
  }

  // Render the template to stdout (for this example)
  if err = group.RenderHtmlTemplate(os.Stdout, rootTemplate[0], "", data, nil); err != nil {
    fmt.Printf("Error rendering template: %v\n", err)
  }
}

Key Features

1. Template Dependencies

In your templates, use the {{# include "path/to/template" #}} directive to include dependencies:

{{# include "layouts/base.tmpl" #}}
{{# include "components/navbar.tmpl" #}}

{{ define "content" }}
  <h1>Welcome to our site</h1>
  <p>This is the homepage content.</p>
{{ end }}

You can also selectively include only specific templates (tree-shaking):

{{# include "forms.tmpl" "button" "input" #}}

{{ define "page" }}
  {{ template "button" . }}  {{/* Only button and input are included */}}
{{ end }}
2. Template Namespacing

Avoid template name collisions by importing templates into namespaces:

{{# namespace "UI" "widgets/buttons.tmpl" #}}
{{# namespace "Theme" "themes/bootstrap.tmpl" #}}

{{ define "page" }}
  {{ template "UI:button" dict "Text" "Click Me" }}
  {{ template "Theme:header" . }}
{{ end }}

Namespace resolution rules:

  • Plain names like button are prefixed with the current namespace → NS:button
  • Names with : like Other:button are kept as-is (cross-namespace reference)
  • Names starting with :: like ::global become global (no namespace)

Tree-shaking is also supported with namespaces:

{{# namespace "UI" "widgets.tmpl" "button" "icon" #}}
{{/* Only button, icon, and their dependencies are included */}}

See namespace.md for detailed examples, the diamond problem, and common gotchas.

3. Template Extension (Inheritance)

Extend base templates while overriding specific blocks:

{{# namespace "Base" "layouts/base.tmpl" #}}
{{# extend "Base:layout" "MyLayout" "Base:title" "myTitle" "Base:content" "myContent" #}}

{{ define "myTitle" }}Custom Page Title{{ end }}
{{ define "myContent" }}
  <h1>My Custom Content</h1>
{{ end }}

{{ template "MyLayout" . }}

This creates a new template MyLayout by copying Base:layout, but rewiring:

  • {{ template "Base:title" . }}{{ template "myTitle" . }}
  • {{ template "Base:content" . }}{{ template "myContent" . }}

Non-overridden blocks retain their original references to the base templates.

Important: The extend directive only rewrites template calls within the copied template itself, not in templates it calls. For nested overrides, you need to extend each level of the hierarchy. See extend.md for detailed examples, visual diagrams, and common gotchas.

4. Multiple Template Loaders

Templar allows you to configure multiple template loaders with fallback behavior:

// Create a list of loaders to search in order
loaderList := &templar.LoaderList{}

// Add loaders in priority order
loaderList.AddLoader(templar.NewFileSystemLoader("app/templates/"))
loaderList.AddLoader(templar.NewFileSystemLoader("shared/templates/"))

// Set a default loader as final fallback
loaderList.DefaultLoader = templar.NewFileSystemLoader("default/templates/")
5. Template Groups

Template groups manage collections of templates and their dependencies:

group := templar.NewTemplateGroup()
group.Loader = loaderList
group.AddFuncs(map[string]any{
    "formatDate": func(t time.Time) string {
        return t.Format("2006-01-02")
    },
})
6. External Template Sources (Vendoring)

Load templates from external sources like GitHub repositories:

# templar.yaml
sources:
  goapplib:
    url: github.com/panyam/goapplib
    path: templates
    ref: v1.2.0

vendor_dir: ./templar_modules
search_paths:
  - ./templates
  - ./templar_modules

Reference external templates with the @sourcename prefix:

{{# namespace "EL" "@goapplib/components/EntityListing.html" #}}

{{ define "MyPage" }}
    {{ template "EL:EntityListing" .Items }}
{{ end }}

Fetch dependencies with:

templar get              # Fetch all sources
templar get --update     # Update to latest versions
templar get --verify     # Verify local matches lock file

See vendoring.md for deployment strategies, configuration reference, and examples.

7. Library Embedding

Tools can embed templar as a library and customize all file names and generated content:

info := templar.ToolInfo{
    Name:        "mytool",
    ConfigNames: []string{".mytool.yaml"},
    VendorDir:   "./.mytool-modules",
    LockFile:    ".mytool.lock",
    FetchCmd:    "mytool fetch",
}

configPath, _ := templar.FindVendorConfigWithNames(dir, info.ConfigNames)
config, _ := templar.LoadVendorConfigWithDefaults(configPath, info)

See the Integration Guide for full details.

Advanced Usage

Conditional Template Loading

You can implement conditional template loading based on application state:

folder := "desktop"
if isMobile {
  folder = "mobile"
}
tmpl, err := loader.Load(fmt.Sprintf("%s/homepage.tmpl", folder))
Dynamic Templates

Generate templates dynamically and use them immediately:

dynamicTemplate := &templar.Template{
    Name:      "dynamic-template",
    RawSource: []byte(`Hello, {{.Name}}!`),
}

group.RenderTextTemplate(w, dynamicTemplate, "", map[string]any{"Name": "World"}, nil)

Command Line Interface

Templar provides a CLI tool for serving templates, debugging dependencies, and managing external sources:

# Install
go install github.com/panyam/templar/cmd/templar@latest

# Initialize a new project
templar init

# Fetch external template sources
templar get

# List configured sources
templar sources

# Start development server
templar serve -t ./templates -s /static:./public

# Debug template dependencies
templar debug -p templates homepage.html

Key commands:

  • templar init - Create a templar.yaml configuration file
  • templar get - Fetch external template sources for vendoring
  • templar sources - List configured sources and their status
  • templar serve - Start HTTP server to serve and test templates
  • templar debug - Analyze dependencies, detect cycles, visualize with GraphViz
  • templar version - Print version information

Configuration via .templar.yaml or environment variables (TEMPLAR_ prefix).

See cli.md for complete command reference, flags, configuration options, and examples.

Comparison with Other Solutions

Feature Standard Go Templates Templar
Dependency Management
Self-describing Templates (*)
Template Namespacing
Template Extension/Inheritance
Tree-shaking
Standard Go Template Syntax
Supports Cycles Prevention (**)
HTML Escaping
Template Grouping (***) ⚠️ Partial

*: Self-describing here refers to a template specifying all the dependencies it needs so a template author can be clear about what is required and include them instead of hoping they exist somehow. **: Cycles are caught by the preprocessor and is clearer. ***: Grouping in standard templates is done in code by the template user instead of the author.

Other alternatives
  • Pongo2 is amazing for its reverence for Django syntax.
  • Templ is amazing as a typed template library and being able to perform compile time validations of templates.

My primary goal here was to have as much alignment with Go's template stdlib. Beyond this library for managing dependencies, the goal itself was to have strict adherence to Go's templating syntax. Using the same Go template syntax also allows extra features during preprocessing of templates. (eg using same set of variables for both pre-processing as well as for final rendering).

Documentation

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	// DefaultVendorDir is the default directory for vendored template sources.
	DefaultVendorDir = "./templar_modules"

	// DefaultLockFile is the default lock file name.
	DefaultLockFile = "templar.lock"
)

Variables

View Source
var DefaultConfigNames = []string{"templar.yaml", ".templar.yaml"}

DefaultConfigNames is the ordered list of config file names to search for.

View Source
var TemplateNotFound = errors.New("template not found")

TemplateNotFound is returned when a template could not be found by a loader.

Functions

func ApplyNamespaceToTree added in v0.0.23

func ApplyNamespaceToTree(tree *parse.Tree, namespace string)

ApplyNamespaceToTree applies a namespace transformation to all template references within a parse tree. It modifies the tree in place.

This transforms:

  • {{ template "foo" }} → {{ template "NS:foo" }}
  • {{ template "Other:bar" }} → {{ template "Other:bar" }} (unchanged)
  • {{ template "::global" }} → {{ template "global" }}

func CollectLocalReferences added in v0.0.23

func CollectLocalReferences(tree *parse.Tree) []string

CollectLocalReferences collects all local (non-namespaced, non-global) template references from a parse tree. These are the references that would be transformed when applying a namespace.

func CollectTemplateNames added in v0.0.23

func CollectTemplateNames(tree *parse.Tree) []string

CollectTemplateNames walks a parse tree and collects all template names that are referenced via {{ template "name" }} calls.

func ComputeReachableTemplates added in v0.0.23

func ComputeReachableTemplates(templates map[string]*parse.Tree, entryPoints []string) map[string]bool

ComputeReachableTemplates computes the transitive closure of templates reachable from the given entry points. This is used for tree-shaking: only namespace templates that are actually used.

Parameters:

  • templates: map of template name to parse tree (all available templates)
  • entryPoints: starting template names to trace from

Returns: set of template names that are reachable (including entry points)

func CopyTreeWithNamespace added in v0.0.23

func CopyTreeWithNamespace(tree *parse.Tree, namespace string) *parse.Tree

CopyTreeWithNamespace creates a deep copy of a parse tree and applies namespace transformation to both the tree name and all template references.

func CopyTreeWithRewrites added in v0.0.23

func CopyTreeWithRewrites(tree *parse.Tree, rewrites map[string]string) *parse.Tree

CopyTreeWithRewrites creates a deep copy of a parse tree and rewrites template references according to the provided mapping.

Parameters:

  • tree: the source parse tree to copy
  • rewrites: map of old name -> new name for template references

Returns: a new tree with references rewritten

func CreateDelegationTree added in v0.0.23

func CreateDelegationTree(treeName string, delegateTo string) *parse.Tree

CreateDelegationTree creates a parse tree that simply delegates to another template. The resulting tree, when executed, will call {{ template "delegateTo" . }}

This is used for template extension: when a child template overrides a block, we replace the base template's block with a delegation to the child's definition.

func FetchAllSources added in v0.0.27

func FetchAllSources(config *VendorConfig) (map[string]*FetchResult, error)

FetchAllSources fetches all sources defined in the config

func FetchAllSourcesFS added in v0.1.0

func FetchAllSourcesFS(fsys WritableFS, config *VendorConfig) (map[string]*FetchResult, error)

FetchAllSourcesFS fetches all sources and writes to the WritableFS.

func FindVendorConfig added in v0.0.27

func FindVendorConfig(startDir string) (string, error)

FindVendorConfig searches for templar.yaml starting from the given directory and walking up to parent directories until found or root is reached. For custom config file names, use FindVendorConfigWithNames.

func FindVendorConfigWithNames added in v0.0.34

func FindVendorConfigWithNames(startDir string, configNames []string) (string, error)

FindVendorConfigWithNames searches for a config file with any of the given names, starting from startDir and walking up to parent directories. Embedding applications use this to search for their own config file names (e.g. ".slyds.yaml").

func IsLocalReference added in v0.0.23

func IsLocalReference(name string) bool

IsLocalReference returns true if the name is a local reference (not namespaced, not global). Local references are plain names like "header" that should be namespaced. Non-local references include:

  • "NS:header" (already namespaced)
  • "::header" (explicitly global)

func TransformName added in v0.0.23

func TransformName(name, namespace string) string

TransformName applies namespace resolution rules to a template reference name.

Resolution rules:

  • If name starts with "::" → strip "::", return as global (no namespace)
  • If name contains ":" → return unchanged (explicit cross-namespace reference)
  • Otherwise → prepend namespace (e.g., "icon" → "NS:icon")

func WalkParseTree added in v0.0.23

func WalkParseTree(node parse.Node, visitor func(*parse.TemplateNode))

WalkParseTree walks a parse tree and calls the visitor function for each TemplateNode. The visitor can modify the node's Name field to apply namespace transformations.

func WriteLockFile added in v0.0.27

func WriteLockFile(path string, lock *VendorLock) error

WriteLockFile writes a VendorLock to the specified path using templar's default branding in the header. For custom branding, use WriteLockFileFor.

func WriteLockFileFS added in v0.1.0

func WriteLockFileFS(fsys WritableFS, name string, lock *VendorLock, info ToolInfo) error

WriteLockFileFS writes a VendorLock to the WritableFS.

func WriteLockFileFor added in v0.0.34

func WriteLockFileFor(path string, lock *VendorLock, info ToolInfo) error

WriteLockFileFor writes a VendorLock to the specified path with a header branded for the given tool. Embedding applications use this so the lock file header references their tool name and commands instead of templar's.

func WriteVendorReadme added in v0.0.32

func WriteVendorReadme(vendorDir string) error

WriteVendorReadme writes a README.md inside the vendor directory using templar's default branding. For custom branding, use WriteVendorReadmeFor.

func WriteVendorReadmeFS added in v0.1.0

func WriteVendorReadmeFS(fsys WritableFS, dirName string, info ToolInfo) error

WriteVendorReadmeFS writes a README.md to the WritableFS.

func WriteVendorReadmeFor added in v0.0.34

func WriteVendorReadmeFor(vendorDir string, info ToolInfo) error

WriteVendorReadmeFor writes a README.md inside the vendor directory with content branded for the given tool. Embedding applications use this so the generated README references their tool name and commands instead of templar's.

Types

type EmbedFSLoader added in v0.0.21

type EmbedFSLoader struct {
	// Embeds is a list of directories to search for templates.
	Embeds []embed.FS

	// Extensions is a list of file extensions to consider as templates.
	Extensions []string
}

EmbedFSLoader loads templates from the file system based on a set of directories and file extensions.

func NewEmbedFSLoader added in v0.0.21

func NewEmbedFSLoader(fss ...embed.FS) *EmbedFSLoader

NewEmbedFSLoader creates a new file system loader that will search in the provided folders for template files. By default, it recognizes files with .tmpl, .tmplus, and .html extensions.

func (*EmbedFSLoader) Load added in v0.0.21

func (g *EmbedFSLoader) Load(name string, _ string) (template []*Template, err error)

Load attempts to find and load a template with the given name. If the name includes an extension, only files with that extension are considered. Otherwise, files with any of the loader's recognized extensions are searched. The cwd parameter is ignored as we can only provided templates from embedded FS Returns the loaded templates or TemplateNotFound if no matching templates were found.

type Extension added in v0.0.23

type Extension struct {
	// SourceTemplate is the template to copy from (e.g., "Base:layout")
	SourceTemplate string

	// DestTemplate is the name for the new template (e.g., "Page:layout")
	DestTemplate string

	// Rewrites maps block names to their replacements.
	// Key is the original reference, value is the replacement.
	Rewrites map[string]string
}

Extension represents an extend directive that creates a new template by copying a source template and rewiring specific template references.

Syntax: {{# extend "SourceTemplate" "DestTemplate" "block1" "override1" "block2" "override2" ... #}}

This creates DestTemplate as a copy of SourceTemplate, but with:

  • {{ template "block1" . }} replaced with {{ template "override1" . }}
  • {{ template "block2" . }} replaced with {{ template "override2" . }}

type FSFolder added in v0.1.0

type FSFolder struct {
	FS   fs.FS  // filesystem to search in
	Path string // folder path within the FS
}

FSFolder pairs a filesystem with a folder path within it.

func LocalFolder added in v0.1.0

func LocalFolder(dir string) FSFolder

LocalFolder is a convenience for creating an FSFolder from a local directory path.

func LocalFolders added in v0.1.0

func LocalFolders(dirs ...string) []FSFolder

LocalFolders converts a list of directory paths to FSFolder entries. Convenience for migrating code that passes string paths.

type FetchResult added in v0.0.27

type FetchResult struct {
	SourceName     string
	URL            string
	Version        string
	Ref            string
	ResolvedCommit string
	DestDir        string
	FilesExtracted int
	FetchedAt      time.Time
}

FetchResult contains the result of fetching a source

func FetchSource added in v0.0.27

func FetchSource(config *VendorConfig, sourceName string) (*FetchResult, error)

FetchSource fetches a single source from the config

func FetchSourceFS added in v0.1.0

func FetchSourceFS(fsys WritableFS, config *VendorConfig, sourceName string) (*FetchResult, error)

FetchSourceFS fetches a single source and writes the extracted files to the given WritableFS. The destination is {sourceName}/ within the FS. All bytes flow from network → gzip → tar → WritableFS. No temp files.

type FileSystemLoader

type FileSystemLoader struct {
	// Folders is the list of FS+path pairs to search for templates.
	Folders []FSFolder

	// Extensions is a list of file extensions to consider as templates.
	Extensions []string
}

FileSystemLoader loads templates from one or more filesystem folders. Each folder is backed by an fs.FS — use NewLocalFS for local disk, NewMemFS for tests.

func NewFileSystemLoader

func NewFileSystemLoader(folders ...FSFolder) *FileSystemLoader

NewFileSystemLoader creates a loader that searches the given FS+path pairs. Default extensions: .tmpl, .tmplus, .html.

func (*FileSystemLoader) Load

func (g *FileSystemLoader) Load(name string, cwd string) (template []*Template, err error)

Load attempts to find and load a template with the given name.

type LoaderList

type LoaderList struct {
	// DefaultLoader is used as a fallback if no other loaders succeed.
	DefaultLoader TemplateLoader
	// contains filtered or unexported fields
}

LoaderList is a composite loader that tries multiple loaders in sequence and returns the first successful match.

func (*LoaderList) AddLoader

func (t *LoaderList) AddLoader(loader TemplateLoader) *LoaderList

AddLoader adds a new loader to the list of loaders to try.

func (*LoaderList) Load

func (t *LoaderList) Load(name string, cwd string) (matched []*Template, err error)

Load attempts to load a template with the given name by trying each loader in sequence.

type LocalFS added in v0.0.35

type LocalFS struct {
	// Root is the absolute base directory. All operations are relative to it.
	Root string
}

LocalFS implements WritableFS backed by the local operating system filesystem. All paths are relative to Root.

func NewLocalFS added in v0.0.35

func NewLocalFS(root string) *LocalFS

NewLocalFS creates a WritableFS backed by the local filesystem at the given root. The root should be an absolute path.

func (*LocalFS) AbsPath added in v0.0.35

func (f *LocalFS) AbsPath(name string) string

AbsPath returns the absolute path for a relative name within the FS.

func (*LocalFS) MkdirAll added in v0.0.35

func (f *LocalFS) MkdirAll(path string, perm fs.FileMode) error

MkdirAll implements WritableFS.

func (*LocalFS) Open added in v0.0.35

func (f *LocalFS) Open(name string) (fs.File, error)

Open implements fs.FS.

func (*LocalFS) ReadDir added in v0.0.35

func (f *LocalFS) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir implements fs.ReadDirFS.

func (*LocalFS) ReadFile added in v0.0.35

func (f *LocalFS) ReadFile(name string) ([]byte, error)

ReadFile implements fs.ReadFileFS.

func (*LocalFS) Remove added in v0.0.35

func (f *LocalFS) Remove(name string) error

Remove implements WritableFS.

func (*LocalFS) Rename added in v0.0.35

func (f *LocalFS) Rename(oldname, newname string) error

Rename implements WritableFS.

func (*LocalFS) Stat added in v0.0.35

func (f *LocalFS) Stat(name string) (fs.FileInfo, error)

Stat implements fs.StatFS.

func (*LocalFS) WriteFile added in v0.0.35

func (f *LocalFS) WriteFile(name string, data []byte, perm fs.FileMode) error

WriteFile implements WritableFS.

type LockedSource added in v0.0.27

type LockedSource struct {
	URL            string `yaml:"url"`
	Version        string `yaml:"version,omitempty"`
	Ref            string `yaml:"ref,omitempty"`
	ResolvedCommit string `yaml:"resolved_commit"`
	FetchedAt      string `yaml:"fetched_at"`
}

LockedSource represents a locked source in the lock file

type MemDelta added in v0.0.24

type MemDelta struct {
	FromName         string
	ToName           string
	Duration         time.Duration
	AllocDelta       int64
	TotalAllocDelta  int64
	HeapObjectsDelta int64
	HeapInuseDelta   int64
	NumGCDelta       int32
}

MemDelta represents the difference between two memory snapshots.

func NewMemDelta added in v0.0.24

func NewMemDelta(from, to *MemSnapshot) *MemDelta

NewMemDelta calculates the delta between two snapshots.

func (*MemDelta) String added in v0.0.24

func (d *MemDelta) String() string

String returns a human-readable summary of the delta.

type MemFS added in v0.0.35

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

MemFS implements WritableFS as a fully in-memory filesystem. Useful for testing, WASM/IndexedDB backends, and any context where files don't live on a local filesystem.

Not safe for concurrent use without external synchronization.

func NewMemFS added in v0.0.35

func NewMemFS() *MemFS

NewMemFS creates an empty in-memory filesystem.

func (*MemFS) FileCount added in v0.0.35

func (m *MemFS) FileCount() int

FileCount returns the total number of files in the FS. Convenience for tests.

func (*MemFS) GetFile added in v0.0.35

func (m *MemFS) GetFile(name string) []byte

GetFile returns the raw bytes of a file, or nil if it doesn't exist. Convenience for test assertions.

func (*MemFS) HasFile added in v0.0.35

func (m *MemFS) HasFile(name string) bool

HasFile returns true if the named file exists.

func (*MemFS) MkdirAll added in v0.0.35

func (m *MemFS) MkdirAll(_ string, _ fs.FileMode) error

MkdirAll implements WritableFS (no-op for in-memory FS).

func (*MemFS) Open added in v0.0.35

func (m *MemFS) Open(name string) (fs.File, error)

Open implements fs.FS.

func (*MemFS) ReadDir added in v0.0.35

func (m *MemFS) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir implements fs.ReadDirFS.

func (*MemFS) ReadFile added in v0.0.35

func (m *MemFS) ReadFile(name string) ([]byte, error)

ReadFile implements fs.ReadFileFS.

func (*MemFS) Remove added in v0.0.35

func (m *MemFS) Remove(name string) error

Remove implements WritableFS.

func (*MemFS) Rename added in v0.0.35

func (m *MemFS) Rename(oldname, newname string) error

Rename implements WritableFS.

func (*MemFS) SetFile added in v0.0.35

func (m *MemFS) SetFile(name string, data []byte)

SetFile adds or overwrites a file. Convenience for test setup.

func (*MemFS) Stat added in v0.0.35

func (m *MemFS) Stat(name string) (fs.FileInfo, error)

Stat implements fs.StatFS. Returns file info for files, and a synthetic directory entry for any prefix that has files under it (e.g., "slides" exists if "slides/01.html" exists).

func (*MemFS) WriteFile added in v0.0.35

func (m *MemFS) WriteFile(name string, data []byte, _ fs.FileMode) error

WriteFile implements WritableFS.

type MemSnapshot added in v0.0.24

type MemSnapshot struct {
	// Name identifies this snapshot (e.g., "before-load", "after-render")
	Name string

	// Timestamp when the snapshot was taken
	Timestamp time.Time

	// Alloc is bytes of allocated heap objects.
	// This is the most useful metric for tracking "live" memory.
	Alloc uint64

	// TotalAlloc is cumulative bytes allocated (never decreases).
	// Useful for tracking allocation pressure.
	TotalAlloc uint64

	// HeapObjects is the number of allocated heap objects.
	HeapObjects uint64

	// HeapInuse is bytes in in-use spans.
	HeapInuse uint64

	// NumGC is the number of completed GC cycles.
	NumGC uint32

	// PauseTotalNs is cumulative nanoseconds in GC stop-the-world pauses.
	PauseTotalNs uint64
}

MemSnapshot captures memory statistics at a point in time.

type MemStats added in v0.0.24

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

MemStats collects memory snapshots for analysis.

func NewMemStats added in v0.0.24

func NewMemStats() *MemStats

NewMemStats creates a new memory statistics collector.

func (*MemStats) Delta added in v0.0.24

func (m *MemStats) Delta(fromName, toName string) *MemDelta

Delta calculates the difference between two named snapshots. Returns nil if either snapshot is not found.

func (*MemStats) Report added in v0.0.24

func (m *MemStats) Report(w io.Writer)

Report writes a formatted report of all snapshots to the writer.

func (*MemStats) Reset added in v0.0.24

func (m *MemStats) Reset()

Reset clears all captured snapshots.

func (*MemStats) Snapshot added in v0.0.24

func (m *MemStats) Snapshot(name string) *MemSnapshot

Snapshot captures current memory statistics with the given name. Call this before and after operations you want to measure.

func (*MemStats) SnapshotWithGC added in v0.0.24

func (m *MemStats) SnapshotWithGC(name string) *MemSnapshot

SnapshotWithGC forces a garbage collection before taking the snapshot. This gives a more accurate picture of "live" memory but is slower.

func (*MemStats) Snapshots added in v0.0.24

func (m *MemStats) Snapshots() []*MemSnapshot

Snapshots returns all captured snapshots.

type SourceConfig added in v0.0.27

type SourceConfig struct {
	URL     string   `yaml:"url"`               // Repository URL (e.g., github.com/user/repo)
	Path    string   `yaml:"path"`              // Directory within repo to fetch (e.g., templates)
	Version string   `yaml:"version,omitempty"` // Semantic version tag (e.g., v1.2.0)
	Ref     string   `yaml:"ref,omitempty"`     // Git ref - branch or commit (fallback if no version)
	Include []string `yaml:"include,omitempty"` // Glob patterns to include (e.g., ["**/*.html"])
	Exclude []string `yaml:"exclude,omitempty"` // Glob patterns to exclude (e.g., ["*_test.*"])
}

SourceConfig represents a single external template source configuration

func (*SourceConfig) GetRef added in v0.0.28

func (s *SourceConfig) GetRef() string

GetRef returns the effective git ref (version takes precedence over ref)

type SourceLoader added in v0.0.27

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

SourceLoader is a template loader that resolves @source prefixed paths to vendored template locations, while falling back to a FileSystemLoader for regular paths.

func NewSourceLoader added in v0.0.27

func NewSourceLoader(config *VendorConfig) *SourceLoader

NewSourceLoader creates a new SourceLoader with the given configuration. The config.FS field must be set — it's the filesystem for template resolution.

func NewSourceLoaderFromConfig added in v0.0.27

func NewSourceLoaderFromConfig(configPath string) (*SourceLoader, error)

NewSourceLoaderFromConfig creates a SourceLoader from a config file path. It loads the config, resolves all paths relative to the config file location, and creates the appropriate loader.

func NewSourceLoaderFromDir added in v0.0.27

func NewSourceLoaderFromDir(dir string) (*SourceLoader, error)

NewSourceLoaderFromDir finds templar.yaml starting from the given directory and creates a SourceLoader from it.

func (*SourceLoader) Load added in v0.0.27

func (s *SourceLoader) Load(pattern string, cwd string) ([]*Template, error)

Load attempts to load templates matching the given pattern. If the pattern starts with @sourcename/, it resolves to the vendored location. Otherwise, it delegates to the underlying FileSystemLoader.

type Template

type Template struct {
	// Name is an identifier for this template.
	Name string

	// RawSource contains the original, unprocessed template content.
	RawSource []byte

	// ParsedSource contains the template content after preprocessing.
	ParsedSource string

	// Path is the file path for this template if it was loaded from a file.
	Path string

	// Status indicates whether the template has been loaded and parsed.
	Status int

	// AsHtml determines whether the content should be treated as HTML (with escaping)
	// or as plain text.
	AsHtml bool

	// Error contains any error encountered during template processing.
	Error error

	// Metadata stores extracted information from the template (e.g., FrontMatter).
	Metadata map[string]any

	// Namespace is set when this template was included via namespace directive.
	// When set, all template definitions and references will be prefixed with this namespace.
	Namespace string

	// NamespaceEntryPoints specifies which templates to include when namespacing.
	// If empty, all templates are included. If set, only these templates and their
	// transitive dependencies are included (tree-shaking).
	NamespaceEntryPoints []string

	// Extensions records extend directives to be processed after all templates are parsed.
	// Each extension creates a new template by copying a source and rewiring references.
	Extensions []Extension
	// contains filtered or unexported fields
}

Template is the basic unit of rendering that manages content and dependencies.

func (*Template) AddDependency

func (t *Template) AddDependency(another *Template) bool

AddDependency adds another template as a dependency of this template. It returns false if the dependency would create a cycle, true otherwise.

func (*Template) CleanedSource added in v0.0.18

func (t *Template) CleanedSource() (string, error)

Returns the cleaned source of this template wihtout all the includes removed (but before they are preprocessed)

func (*Template) Dependencies

func (t *Template) Dependencies() []*Template

Dependencies returns all templates that this template directly depends on.

func (*Template) WalkTemplate

func (root *Template) WalkTemplate(loader TemplateLoader, handler func(template *Template) error) (err error)

type TemplateGroup

type TemplateGroup struct {

	// Funcs contains template functions available to all templates in this group.
	Funcs map[string]any

	// Loader is used to resolve and load template dependencies.
	Loader TemplateLoader
	// contains filtered or unexported fields
}

TemplateGroup manages a collection of templates and their dependencies, providing methods to process and render them.

func NewTemplateGroup

func NewTemplateGroup() *TemplateGroup

NewTemplateGroup creates a new empty template group with initialized internals.

func (*TemplateGroup) AddFuncs

func (t *TemplateGroup) AddFuncs(funcs map[string]any) *TemplateGroup

AddFuncs adds template functions to this group, making them available to all templates. Returns the template group for method chaining.

func (*TemplateGroup) MustLoad added in v0.0.12

func (t *TemplateGroup) MustLoad(pattern string, cwd string) []*Template

Calls the underlying Loader to load templates matching a pattern and optional using a cwd for relative paths. Panics if an error is encountered. Returns matching templates or an error if no templates were found.

func (*TemplateGroup) NewHtmlTemplate

func (t *TemplateGroup) NewHtmlTemplate(name string, funcs map[string]any) (out *htmpl.Template)

NewHtmlTemplate creates a new HTML template with the given name. The template will have access to the group's functions and any additional functions provided.

func (*TemplateGroup) NewTextTemplate

func (t *TemplateGroup) NewTextTemplate(name string, funcs map[string]any) (out *ttmpl.Template)

NewTextTemplate creates a new TEXT template with the given name. The template will have access to the group's functions and any additional functions provided.

func (*TemplateGroup) PreProcessHtmlTemplate

func (t *TemplateGroup) PreProcessHtmlTemplate(root *Template, funcs htmpl.FuncMap) (out *htmpl.Template, err error)

PreProcessHtmlTemplate processes a HTML template and its dependencies, creating an html/template that can be used for rendering. It handles template dependencies recursively. Returns the processed template and any error encountered.

func (*TemplateGroup) PreProcessTextTemplate

func (t *TemplateGroup) PreProcessTextTemplate(root *Template, funcs ttmpl.FuncMap) (out *ttmpl.Template, err error)

PreProcessTextTemplate processes a template and its dependencies, creating a text/template that can be used for rendering. It handles template dependencies recursively. Returns the processed template and any error encountered.

func (*TemplateGroup) RenderHtmlTemplate

func (t *TemplateGroup) RenderHtmlTemplate(w io.Writer, root *Template, entry string, data any, funcs map[string]any) (err error)

RenderHtmlTemplate renders a template as HTML to the provided writer.

It processes the template with its dependencies, executes it with the given data, and applies any additional template functions provided.

If entry is specified, it executes that specific template within the processed template.

func (*TemplateGroup) RenderTextTemplate

func (t *TemplateGroup) RenderTextTemplate(w io.Writer, root *Template, entry string, data any, funcs map[string]any) (err error)

RenderTextTemplate renders a template as plain text to the provided writer.

It processes the template with its dependencies, executes it with the given data, and applies any additional template functions provided.

If entry is specified, it executes that specific template within the processed template.

type TemplateLoader

type TemplateLoader interface {
	// Load attempts to load templates matching the given pattern.
	// If cwd is not empty, it's used as the base directory for relative paths.
	// Returns matching templates or an error if no templates were found.
	Load(pattern string, cwd string) (template []*Template, err error)
}

TemplateLoader defines an interface for loading template content by name or pattern.

type ToolInfo added in v0.0.34

type ToolInfo struct {
	Name        string   // Tool name, e.g. "templar", "slyds"
	ConfigNames []string // Config file names to search for, e.g. ["templar.yaml", ".templar.yaml"]
	VendorDir   string   // Default vendor directory name, e.g. "./templar_modules"
	LockFile    string   // Lock file name, e.g. "templar.lock"
	FetchCmd    string   // Command to fetch dependencies, e.g. "templar get"
	ProjectURL  string   // Project URL for generated content, e.g. "https://github.com/panyam/templar"
}

ToolInfo describes the embedding tool for use in generated content, error messages, and file discovery. Embedding applications (like slyds) provide their own ToolInfo to customize all templar-generated artifacts.

func DefaultToolInfo added in v0.0.34

func DefaultToolInfo() ToolInfo

DefaultToolInfo returns a ToolInfo configured with templar's standard defaults.

type VendorConfig added in v0.0.27

type VendorConfig struct {
	Sources     map[string]SourceConfig `yaml:"sources"`
	VendorDir   string                  `yaml:"vendor_dir"`
	SearchPaths []string                `yaml:"search_paths"`
	RequireLock bool                    `yaml:"require_lock"`

	// FS is the filesystem for template resolution. Required.
	// SearchPaths and VendorDir are paths within this FS.
	// Use NewLocalFS(root) for local disk, NewMemFS() for tests.
	FS WritableFS `yaml:"-"`
	// contains filtered or unexported fields
}

VendorConfig represents the templar.yaml configuration

func LoadVendorConfig added in v0.0.27

func LoadVendorConfig(path string) (*VendorConfig, error)

LoadVendorConfig loads a VendorConfig from a config file, applying templar's standard defaults. For custom defaults, use LoadVendorConfigWithDefaults.

func LoadVendorConfigWithDefaults added in v0.0.34

func LoadVendorConfigWithDefaults(path string, info ToolInfo) (*VendorConfig, error)

LoadVendorConfigWithDefaults loads a VendorConfig from a config file, applying defaults from the given ToolInfo. Embedding applications use this to set their own default vendor directory when the config file doesn't specify one.

func (*VendorConfig) ResolveSearchPaths added in v0.0.27

func (c *VendorConfig) ResolveSearchPaths() []string

ResolveSearchPaths returns absolute paths for all search paths

func (*VendorConfig) ResolveVendorDir added in v0.0.27

func (c *VendorConfig) ResolveVendorDir() string

ResolveVendorDir returns the absolute path to the vendor directory

type VendorLock added in v0.0.27

type VendorLock struct {
	Version int                     `yaml:"version"`
	Sources map[string]LockedSource `yaml:"sources"`
}

VendorLock represents a lock file

func LoadLockFile added in v0.0.27

func LoadLockFile(path string) (*VendorLock, error)

LoadLockFile loads a VendorLock from the specified path

func LoadLockFileFS added in v0.1.0

func LoadLockFileFS(fsys WritableFS, name string) (*VendorLock, error)

LoadLockFileFS loads a VendorLock from a WritableFS.

type Walker added in v0.0.18

type Walker struct {
	// Buffer stores the processed template content
	Buffer *bytes.Buffer

	// Loader is used to resolve and load template dependencies
	Loader TemplateLoader

	// FoundInclude is called when an include directive is encountered.
	// If it returns true, the include is skipped and not processed.
	FoundInclude func(included string) bool

	// Called before a template is preprocessed.  This is an opportunity
	// for the handler to control entering/preprocessing etc.  For example
	// This could be a place for the handler to skip processing a template
	EnteringTemplate func(template *Template) (skip bool, err error)

	// ProcessedTemplate is called after a template and all its children
	// have been processed. This allows for custom post-processing.
	ProcessedTemplate func(template *Template) error
	// contains filtered or unexported fields
}

Walker provides a mechanism for walking through templates and their dependencies in a customizable way, applying visitor patterns as templates are processed. Unlike the WalkTemplate method which uses post-order traversal, Walker implements in-order traversal, processing includes immediately when encountered.

func (*Walker) Walk added in v0.0.18

func (w *Walker) Walk(root *Template) (err error)

Walk processes a template and its dependencies using in-order traversal. This means includes are processed as soon as they are encountered in the template. After processing, the template's ParsedSource will contain the processed content. If ProcessedTemplate is defined, it will be called on each processed template.

type WritableFS added in v0.0.35

type WritableFS interface {
	fs.FS

	// ReadFile reads the named file and returns its contents.
	// Equivalent to fs.ReadFileFS but required (not optional) here.
	ReadFile(name string) ([]byte, error)

	// ReadDir reads the named directory and returns a list of directory entries
	// sorted by filename.
	ReadDir(name string) ([]fs.DirEntry, error)

	// WriteFile writes data to the named file, creating it if necessary.
	// If the file exists, it is truncated before writing.
	WriteFile(name string, data []byte, perm fs.FileMode) error

	// MkdirAll creates a directory path and all parents that don't exist.
	MkdirAll(path string, perm fs.FileMode) error

	// Remove deletes the named file or empty directory.
	Remove(name string) error

	// Rename renames (moves) a file within the filesystem.
	Rename(oldname, newname string) error
}

WritableFS is a complete read-write filesystem interface for template storage. It extends fs.FS with efficient read operations (ReadFile, ReadDir) and write operations (WriteFile, MkdirAll, Remove, Rename).

Portable across local filesystems, S3, IndexedDB (WASM), in-memory (tests), etc. All paths are relative to the filesystem root — no absolute paths.

Directories

Path Synopsis
cmd
templar command

Jump to

Keyboard shortcuts

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