GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - nanoninja/render: A lightweight Go package for rendering content in multiple formats

BranchesTags
Open more actions menu

Latest commit

 

History

5 Commits
5 Commits

Folders and files

NameName
Last commit message
Last commit date
.github
.github
 
 
_example
_example
 
 
forms
forms
 
 
tmpl
tmpl
 
 
.codecov.yml
.codecov.yml
 
 
.gitignore
.gitignore
 
 
.golangci.yml
.golangci.yml
 
 
CHANGELOG
CHANGELOG
 
 
LICENSE
LICENSE
 
 
README.md
README.md
 
 
benchmark_test.go
benchmark_test.go
 
 
binary.go
binary.go
 
 
binary_test.go
binary_test.go
 
 
buffer.go
buffer.go
 
 
buffer_test.go
buffer_test.go
 
 
cache.go
cache.go
 
 
cache_test.go
cache_test.go
 
 
csv.go
csv.go
 
 
csv_test.go
csv_test.go
 
 
doc.go
doc.go
 
 
errors.go
errors.go
 
 
example_test.go
example_test.go
 
 
go.mod
go.mod
 
 
go.sum
go.sum
 
 
gzip.go
gzip.go
 
 
gzip_test.go
gzip_test.go
 
 
html.go
html.go
 
 
html_test.go
html_test.go
 
 
json.go
json.go
 
 
json_test.go
json_test.go
 
 
markdown.go
markdown.go
 
 
markdown_test.go
markdown_test.go
 
 
multi.go
multi.go
 
 
multi_test.go
multi_test.go
 
 
options.go
options.go
 
 
options_test.go
options_test.go
 
 
pipe.go
pipe.go
 
 
pipe_test.go
pipe_test.go
 
 
render.go
render.go
 
 
render_test.go
render_test.go
 
 
text.go
text.go
 
 
text_test.go
text_test.go
 
 
xml.go
xml.go
 
 
xml_test.go
xml_test.go
 
 
yaml.go
yaml.go
 
 
yaml_test.go
yaml_test.go
 
 

Repository files navigation

Render

A lightweight Go package for rendering content in multiple formats.
Zero allocations on the hot path — configuration is a plain value struct, not closures.

Golang Tests codecov Go Reference License

Features

  • Built-in renderers: JSON, XML, CSV, Text, HTML, Binary, YAML, Markdown, Gzip
  • Composable renderers: Buffer, Cache, Multi, Pipe
  • Template rendering via the tmpl sub-package (html/template and text/template)
  • Form rendering via the forms sub-package — themable, Symfony-inspired field/form rendering on top of tmpl
  • Context support — cancellation checked on every render call; timeout supported by composable renderers (Buffer, Cache, Pipe, Multi)
  • Zero-allocation options — Options is a plain struct passed by value
  • Thread-safe by design — renderers hold no mutable state after construction

Installation

go get github.com/nanoninja/render

Interface

All renderers implement a two-method interface:

type Renderer interface {
    ContentType() string
    Render(ctx context.Context, w io.Writer, data any, opts Options) error
}

Options is a plain value struct — set only the fields you need:

type Options struct {
    Timeout    time.Duration // zero means no limit
    Name       string        // template name (tmpl renderers)
    Pretty     bool          // enable indented output
    Indent     string        // indentation string (JSON, XML)
    Prefix     string        // per-line prefix (JSON, XML)
    LineEnding string        // CSV line ending ("\n" or "\r\n")
    Args       []any         // printf-style args (Text renderer)
    Separator  rune          // CSV field separator (default ',')
    Padding    string        // JSONP callback name (JSON renderer)
    Headers    map[string][]string
}

var NoOptions = Options{} // zero value, no allocation

Quick Start

// Prefer structs over maps — structs encode with 1 alloc, maps require
// reflect.MapIter and allocate one value per key (9 allocs for 4 fields).
type Response struct {
    Message string `json:"message"`
}

ctx := context.Background()
data := Response{Message: "hello"}

// Compact JSON
render.JSON().Render(ctx, os.Stdout, data, render.NoOptions)

// Pretty-printed JSON
render.JSON().Render(ctx, os.Stdout, data, render.Options{Pretty: true})

JSON

// Default (HTML-safe, 2-space indent when pretty)
render.JSON().Render(ctx, w, data, render.NoOptions)

// Pretty with custom indent
render.JSON().Render(ctx, w, data, render.Options{Pretty: true, Indent: "    "})

// JSONP
render.NewJSON(render.JSONConfig{Padding: "callback"}).Render(ctx, w, data, render.NoOptions)

// Disable HTML escaping
render.NewJSON(render.JSONConfig{EscapeHTML: false}).Render(ctx, w, data, render.NoOptions)

XML

// Default (with XML header, 2-space indent when pretty)
render.XML().Render(ctx, w, data, render.NoOptions)

// Pretty with custom indent and prefix
render.XML().Render(ctx, w, data, render.Options{Pretty: true, Indent: "    ", Prefix: "//"})

// Without XML declaration header
render.NewXML(render.XMLConfig{Header: false}).Render(ctx, w, data, render.NoOptions)

CSV

data := [][]string{
    {"name", "age"},
    {"Alice", "30"},
}

// Default (comma-separated, LF line endings)
render.CSV().Render(ctx, w, data, render.NoOptions)

// Custom delimiter
render.CSV().Render(ctx, w, data, render.Options{Separator: ';'})

// Windows line endings
render.CSV().Render(ctx, w, data, render.Options{LineEnding: "\r\n"})

Text

// Plain string
render.Text().Render(ctx, w, "Hello, World!", render.NoOptions)

// With printf-style arguments
render.Text().Render(ctx, w, "Hello, %s!", render.Options{Args: []any{"Gopher"}})

// With trailing newline
render.Text().Render(ctx, w, "Hello", render.Options{Pretty: true})

Binary

Accepts []byte or io.Reader.

render.Binary().Render(ctx, w, fileBytes, render.NoOptions)
render.Binary().Render(ctx, w, file, render.NoOptions)

YAML

// Default (2-space indent)
render.YAML().Render(ctx, w, data, render.NoOptions)

// Custom indent
render.NewYAML(render.YAMLConfig{Indent: 4}).Render(ctx, w, data, render.NoOptions)

Markdown

Converts Markdown to HTML using goldmark.

// Standard CommonMark
render.Markdown().Render(ctx, w, "# Hello", render.NoOptions)

// GitHub Flavored Markdown (tables, strikethrough, task lists)
render.NewMarkdown(render.MarkdownConfig{GFM: true}).Render(ctx, w, content, render.NoOptions)

// Allow raw HTML (disabled by default for XSS safety)
render.NewMarkdown(render.MarkdownConfig{Unsafe: true}).Render(ctx, w, content, render.NoOptions)

Gzip

Compresses []byte input. Typically the last step in a Pipe.

render.Pipe(
    render.JSON(),
    render.Gzip(),
).Render(ctx, w, data, render.NoOptions)

Multi

Writes the same rendered output to multiple destinations simultaneously.

var log bytes.Buffer

render.Multi(render.JSON(), &log).Render(ctx, w, data, render.NoOptions)

Pipe

Chains renderers — the output of each becomes the input of the next.

render.Pipe(
    render.JSON(),
    render.Gzip(),
).Render(ctx, w, data, render.NoOptions)

Buffer

Renders to an in-memory buffer first, then writes to the destination. Supports post-processing.

// Simple buffered render
render.Buffer(render.JSON()).Render(ctx, w, data, render.NoOptions)

// With post-processing
render.NewBuffer(render.JSON(), render.BufferConfig{
    PostRender: func(b []byte) ([]byte, error) {
        return bytes.ToUpper(b), nil
    },
}).Render(ctx, w, data, render.NoOptions)

Cache

Renders once, stores the output in memory, and replays it on subsequent calls.
Safe for concurrent use.

// Permanent cache — renders once, reused forever
var countries = render.Cache(render.JSON())

// Cache with TTL — refreshed after expiry
var catalog = render.NewCache(render.JSON(), render.CacheConfig{
    TTL: 5 * time.Minute,
})

Warning: the cache stores rendered bytes, not the data. Never cache user-specific or permission-restricted responses.

Template Rendering

The tmpl sub-package wraps Go's standard template packages:

  • tmpl.HTML — html/template with automatic XSS escaping, for web pages
  • tmpl.Text — text/template, for emails, CLI output, or plain text
import (
    "github.com/nanoninja/render"
    "github.com/nanoninja/render/tmpl"
    "github.com/nanoninja/render/tmpl/loader"
)

t := tmpl.HTML("myapp", tmpl.WithDefaultFuncsHTML())

if err := t.Load(src); err != nil {
    log.Fatal(err)
}

// Render a named template
t.Render(ctx, w, data, render.Options{Name: "index.html"})

Built-in template functions

WithDefaultFuncsHTML / WithDefaultFuncs provide: upper, lower, trim, replace, contains, hasPrefix, hasSuffix, split, join, truncate, default, ternary, first, last, dict, int, float, now, date, add, sub, mul, div, sum, avg, nl2br

Forms

The forms sub-package describes form fields in Go and renders them as HTML through a swappable theme — no HTML written by hand, no struct-tag reflection.

import (
    "github.com/nanoninja/render"
    "github.com/nanoninja/render/forms"
    "github.com/nanoninja/render/tmpl"
)

login := forms.New("login").
    WithAction("/login").
    Add(
        forms.Text("username").WithLabel("Username").WithRequired(),
        forms.Password("password").WithLabel("Password").WithRequired(),
        forms.Submit("submit").WithLabel("Sign in"),
    )

r := forms.NewRenderer("default", forms.ThemeDefault())

// The whole form as one HTML response (e.g. an HTMX partial swap) —
// Renderer implements render.Renderer, so it composes with Buffer,
// Cache, Gzip, and Multi like any other renderer.
render.Buffer(r).Render(ctx, w, login, render.NoOptions)

// Or embedded inside a larger page template:
t := tmpl.HTML("myapp", tmpl.SetFuncsHTML(forms.Funcs(r)))
// {{ form .Form }}

Two themes ship built in — ThemeDefault (dependency-free) and ThemeBootstrap5 — selected per call via the existing render.Options.Name field, exactly like named templates in tmpl:

r.RegisterTheme("bootstrap5", forms.ThemeBootstrap5())
r.Render(ctx, w, login, render.Options{Name: "bootstrap5"})

Redisplaying a form with the user's own input and validation errors after a failed submit is a single call — binding and validation themselves are deliberately out of scope (see the package doc):

login.Fill(req.PostForm, fieldErrs, formErrs...)

Context and Timeout

Every Render call accepts a context.Context. Cancellation is checked before rendering begins in all renderers.

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

render.JSON().Render(ctx, w, data, render.NoOptions)

Timeout is enforced by composable renderers (Buffer, Cache, Pipe, Multi, Markdown) via opts.Timeout, or by passing a context with a deadline:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

render.Buffer(render.JSON()).Render(ctx, w, data, render.NoOptions)

// Or set the timeout inside Options (composable renderers only)
render.Buffer(render.JSON()).Render(context.Background(), w, data, render.Options{
    Timeout: 5 * time.Second,
})

Custom Renderer

Implement two methods:

type MyRenderer struct{}

func (r *MyRenderer) ContentType() string { return "application/x-custom" }

func (r *MyRenderer) Render(ctx context.Context, w io.Writer, data any, opts render.Options) error {
    if err := render.CheckContext(ctx); err != nil {
        return err
    }
    _, cancel := render.ApplyTimeout(ctx, opts)
    defer cancel()

    _, err := io.WriteString(w, fmt.Sprint(data))
    return err
}

Benchmarks

Run locally:

go test -bench=. -benchmem ./...

Results on Apple M4, Go 1.26 (-count=3, struct payload, renderer pre-created):

Renderer ns/op B/op allocs/op
Text 7 0 0
HTML 7 0 0
Binary 16 24 1
JSON 110 48 1
JSON (parallel) 31 48 1
XML 707 4 512 8
CSV 390 4 096 1
YAML 2 060 6 912 32
Buffer(JSON) 161 176 3
Cache hit 30 48 1
HTML template 630 384 13
HTML template (parallel) 215 385 13

The single alloc in JSON is json.NewEncoder. Text, HTML, and Binary are zero-allocation on the hot path. HTML templates use sync.Pool on clones — parallel throughput scales linearly.

License

This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.

About

A lightweight Go package for rendering content in multiple formats

Topics

formatformsgogolangpackagerenderrenderertemplateview

Resources

Readme
BSD-3-Clause license

Contributing

Contributing
Activity

Stars

0 stars

Watchers

0 watching

Forks

0 forks
Report repository

Contributors

Languages

You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.