Documentation
¶
Overview ¶
Package trilha is a file-based web framework for Go: pages, layouts, API routes and middleware are discovered from the app/ directory tree by the trilha CLI, which generates a typed registration file; this package is the runtime those generated files call into.
Index ¶
- Constants
- Variables
- func AddRule(name string, fn func(Field) bool)
- func CSRFInput(c *Ctx) h.Node
- func CompileErrorPage(output string) string
- func Errorf(code int, format string, a ...any) error
- func Fatal(err error)
- func NonceAttr(c *Ctx) h.Node
- func PublicFS(embedded fs.FS, dir string) fs.FS
- func Redirect(url string) error
- func RedirectCode(url string, code int) error
- func Run(a *App)
- func UseValidationPTBR()
- type App
- func (a *App) AddExportPath(paths ...string)
- func (a *App) Asset(p string) string
- func (a *App) BasePath() string
- func (a *App) Check(name string, fn func(context.Context) error)
- func (a *App) Config() *Config
- func (a *App) Env() Env
- func (a *App) Export(dir string) error
- func (a *App) ExportPaths() []string
- func (a *App) Handler() http.Handler
- func (a *App) HealthReport(ctx context.Context) HealthReport
- func (a *App) ListenAndServe() error
- func (a *App) Logger() *slog.Logger
- func (a *App) Metrics() *Metrics
- func (a *App) OnShutdown(fn func(*App) error)
- func (a *App) Register(r Route)
- func (a *App) Routes() map[string][]string
- func (a *App) Security() *Security
- func (a *App) SetErrorPage(e ErrorPageFunc)
- func (a *App) SetNotFound(p PageFunc)
- func (a *App) SetRootLayout(l LayoutFunc)
- func (a *App) Values() map[string]any
- type CORS
- type CheckResult
- type Config
- type Counter
- type Ctx
- func (c *Ctx) Accepts(offers ...string) string
- func (c *Ctx) AllowBody(n int64)
- func (c *Ctx) App() *App
- func (c *Ctx) Asset(p string) string
- func (c *Ctx) Base() string
- func (c *Ctx) Bind(v any) error
- func (c *Ctx) BindJSON(v any) error
- func (c *Ctx) CSRFToken() string
- func (c *Ctx) CacheControl(v string)
- func (c *Ctx) ClearCookie(name string)
- func (c *Ctx) ClientIP() string
- func (c *Ctx) Context() context.Context
- func (c *Ctx) Cookie(name string) (*http.Cookie, error)
- func (c *Ctx) ETag(tag string) bool
- func (c *Ctx) Env() Env
- func (c *Ctx) File(field string, rules FileRules) (*Upload, error)
- func (c *Ctx) Form(name string) string
- func (c *Ctx) FormErr() error
- func (c *Ctx) Fragment() string
- func (c *Ctx) Get(key string) any
- func (c *Ctx) HTML(code int, n h.Node) error
- func (c *Ctx) Header(k, v string)
- func (c *Ctx) Hijack() (net.Conn, *bufio.ReadWriter, error)
- func (c *Ctx) Island(src string, props any, children ...h.Node) h.Node
- func (c *Ctx) JSON(code int, v any) error
- func (c *Ctx) LastModified(t time.Time) bool
- func (c *Ctx) Log() *slog.Logger
- func (c *Ctx) NoReadDeadline() error
- func (c *Ctx) NoWriteDeadline() error
- func (c *Ctx) Nonce() string
- func (c *Ctx) Param(name string) string
- func (c *Ctx) Query(name string) string
- func (c *Ctx) Redirect(url string) error
- func (c *Ctx) Render(code int, node h.Node) error
- func (c *Ctx) Request() *http.Request
- func (c *Ctx) RequestID() string
- func (c *Ctx) Set(key string, v any)
- func (c *Ctx) SetContext(ctx context.Context)
- func (c *Ctx) SetCookie(ck *http.Cookie)
- func (c *Ctx) SetRequest(r *http.Request)
- func (c *Ctx) SetSigned(name, value string, ttl time.Duration) error
- func (c *Ctx) SetTitle(t string)
- func (c *Ctx) Signed(name string) (value string, ok bool)
- func (c *Ctx) Status(code int)
- func (c *Ctx) Stream() *Stream
- func (c *Ctx) Text(code int, s string) error
- func (c *Ctx) Title() string
- func (c *Ctx) TraceID() string
- func (c *Ctx) Writer() http.ResponseWriter
- func (c *Ctx) Written() bool
- type Env
- type ErrorPageFunc
- type Field
- type FieldErrors
- type FileRules
- type Gauge
- type HTTPError
- type HandlerFunc
- type HealthReport
- type Histogram
- type LayoutFunc
- type Metrics
- type MiddlewareFunc
- type Next
- type Observability
- type PageFunc
- type Problem
- type RateLimit
- type RedirectError
- type Route
- type RouteKind
- type Security
- type SecurityEvent
- type Signer
- type Stream
- type TestClient
- func (c *TestClient) Get(target string, opts ...TestOption) *TestResponse
- func (c *TestClient) PostForm(target string, form url.Values, opts ...TestOption) *TestResponse
- func (c *TestClient) PostJSON(target string, v any, opts ...TestOption) *TestResponse
- func (c *TestClient) Request(method, target string, opts ...TestOption) *TestResponse
- type TestOption
- func WithApp(a *App) TestOption
- func WithBody(contentType, body string) TestOption
- func WithCookie(name, value string) TestOption
- func WithForm(form url.Values) TestOption
- func WithHeader(name, value string) TestOption
- func WithJSON(v any) TestOption
- func WithSigned(name, value string) TestOption
- func WithoutCSRF() TestOption
- type TestResponse
- type TestingT
- type Timeouts
- type Upload
- type Validator
Constants ¶
const ( // StatusPass means every check passed. StatusPass = "pass" // StatusFail means at least one check failed. StatusFail = "fail" )
const CSRFCookie = "trilha_csrf"
CSRFCookie is the name of the double-submit cookie.
const CSRFField = "_csrf"
CSRFField is the hidden form field name.
const CSRFHeader = "X-CSRF-Token"
CSRFHeader is the header accepted instead of the form field.
const MinSecretLen = 32
MinSecretLen is the minimum accepted secret size in bytes.
const NoTimeout time.Duration = -1
NoTimeout disables a Timeouts field (becomes 0 in http.Server).
const Off = "off"
Off disables a security header when assigned to its field.
const ProblemMediaType = "application/problem+json"
ProblemMediaType is what an API error is sent as (RFC 9457).
Variables ¶
var BindInvalid = "invalid value"
BindInvalid is the FieldErrors message Bind records when a value cannot be converted to the field's type. Change it to localise.
var ErrNoSecret = errors.New("trilha: TRILHA_SECRET not set; signed cookies unavailable")
ErrNoSecret is returned by SetSigned when no secret is configured.
var ErrNotFound = errors.New("trilha: not found")
ErrNotFound makes the framework respond with 404 using the app's not-found page (HTML routes) or a JSON error (API routes).
var ErrRateLimited = &HTTPError{Code: http.StatusTooManyRequests, Message: "too many requests"}
ErrRateLimited is the 429 error; handlers may return it themselves.
var ProblemType func(status int) string
ProblemType gives the "type" URI of a status, for an app that documents its errors ("https://example.com/probs/404"). nil keeps "about:blank".
var ValidationMessages = map[string]string{
"required": "required",
"min": "must be {param} or more",
"max": "must be {param} or less",
"minlen": "must have at least {param} characters",
"maxlen": "must have at most {param} characters",
"minitems": "choose at least {param}",
"maxitems": "choose at most {param}",
"mindate": "must not be before {param}",
"maxdate": "must not be after {param}",
"len": "must have exactly {param} characters",
"lenitems": "choose exactly {param}",
"email": "invalid e-mail",
"url": "invalid URL",
"oneof": "invalid option",
"eqfield": "does not match",
"filemax": "file must be at most {param}",
"filetype": "file type not allowed",
}
ValidationMessages is the message of each rule, in English. Change an entry, swap the map, or call UseValidationPTBR; "{param}" is replaced by what came after the "=" in the tag.
Functions ¶
func AddRule ¶ added in v0.18.0
AddRule registers a rule for the validate tag, usually in Setup. It panics on a name that already exists: two meanings for one word in a tag is a bug nobody would find later.
trilha.AddRule("cep", func(f trilha.Field) bool { return cepValido(f.Text) })
trilha.ValidationMessages["cep"] = "CEP inválido"
func CSRFInput ¶
CSRFInput renders the hidden input for forms: h.Form(..., trilha.CSRFInput(c), ...).
func CompileErrorPage ¶
compileErrorPage is used by the CLI dev server; exported for reuse.
func Fatal ¶
func Fatal(err error)
Fatal logs a fatal error and exits, ignoring the normal server-closed error.
func NonceAttr ¶ added in v0.2.0
NonceAttr renders the nonce attribute for an inline <script>: h.Script(trilha.NonceAttr(c), h.Raw(js)).
func PublicFS ¶
PublicFS returns the static file system for the public directory: the embedded copy in prod, the on-disk directory in dev (so edits show up without a rebuild).
func RedirectCode ¶
RedirectCode returns a redirect error with a custom 3xx status.
func Run ¶ added in v0.2.0
func Run(a *App)
Run serves the app, or exports it when TRILHA_EXPORT names a directory. The generated main calls this.
func UseValidationPTBR ¶ added in v0.18.0
func UseValidationPTBR()
UseValidationPTBR switches the validation messages, BindInvalid included, to Brazilian Portuguese. Call it in Setup. These messages are read by the person filling the form, not by the developer, which is why they are the one piece of the runtime that comes translated.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is a configured Trilha application.
func (*App) AddExportPath ¶ added in v0.2.0
AddExportPath registers extra paths to render on Export, typically pages under dynamic routes (e.g. "/blog/ola"). Call it from Setup.
func (*App) Asset ¶ added in v0.8.0
Asset returns the URL of a file in Config.Public carrying a version derived from its content:
h.Link(h.Rel("stylesheet"), h.Href(c.Asset("/site.css")))
// → /site.css?v=8f3a1c92
The point is the address changing when the content changes: a CDN or browser holding the old file is asked for a URL it has never seen, so a deploy cannot leave someone with new HTML and old CSS. It also makes a long StaticCacheControl safe.
BasePath is applied, so Asset replaces Base()+path. An unknown file returns the path unchanged (with a warning in the log) rather than breaking the page.
func (*App) BasePath ¶ added in v0.2.0
BasePath returns the URL prefix the app is served under ("" or "/docs").
func (*App) Check ¶ added in v0.6.0
Check registers a readiness check: a dependency the app needs in order to serve (database, cache, queue). It runs on /_trilha/health and /_trilha/health/ready, with the configured timeout, and its result is cached for Observability.CacheFor. Liveness never runs checks, so a dependency blinking does not restart the process.
func (*App) Config ¶ added in v0.2.0
Config returns the live configuration for adjustment in Setup. Every field may be changed there: per-request fields (Security, Public, MaxBodyBytes, CSRFForAPI, BasePath, OnSecurityEvent, Static*) are read on each request; derived fields (Logger, Secret/PreviousSecret, RateLimit, TrustedProxies) are reapplied when serving starts (ListenAndServe, Handler, Export); Addr and Timeouts are read by ListenAndServe. To build the Config before New, export func Config(cfg *trilha.Config) in app/setup.go.
func (*App) Export ¶ added in v0.2.0
Export renders the app as a static site into dir: one index.html per path, 404.html from the not-found page, and a copy of public/. The directory is emptied first, but only if Export created it before.
func (*App) ExportPaths ¶ added in v0.2.0
ExportPaths lists what Export will render: static page routes plus the paths added with AddExportPath, sorted and deduplicated.
func (*App) Handler ¶
Handler returns the root http.Handler (useful for tests and embedding). Like ListenAndServe, it reapplies Config changes made in Setup.
func (*App) HealthReport ¶ added in v0.6.0
func (a *App) HealthReport(ctx context.Context) HealthReport
HealthReport runs the readiness checks and returns the full report, including error messages. Use it from your own code (a status page, a startup gate); the HTTP endpoint decides what to reveal.
func (*App) ListenAndServe ¶
ListenAndServe serves until SIGINT/SIGTERM, then shuts down gracefully.
func (*App) Metrics ¶ added in v0.6.0
Metrics returns the process metric registry. It always exists; set Config.Observability.Metrics to expose it over HTTP.
func (*App) OnShutdown ¶ added in v0.3.0
OnShutdown registers fn to run after the server stopped accepting requests (close pools, flush logs). Hooks run in reverse registration order; setup.go may export func Shutdown(a *trilha.App) error, which the generated main registers for you.
func (*App) Security ¶ added in v0.2.0
Security returns the security settings for adjustment in Setup.
func (*App) SetErrorPage ¶
func (a *App) SetErrorPage(e ErrorPageFunc)
SetErrorPage sets the page rendered on 500 (app/error.go).
func (*App) SetNotFound ¶
SetNotFound sets the page rendered on 404 (app/not_found.go).
func (*App) SetRootLayout ¶
func (a *App) SetRootLayout(l LayoutFunc)
SetRootLayout sets the layout used by the not-found and error pages.
type CORS ¶ added in v0.20.0
type CORS struct {
// Origins may call this app. Empty disables CORS; "*" (alone) allows any.
Origins []string
// Methods the other origin may use (default GET, HEAD, POST, PUT, PATCH,
// DELETE).
Methods []string
// Headers the other origin may send (default Content-Type, Authorization,
// X-CSRF-Token, Trilha-Fragment).
Headers []string
// Expose lists the response headers the other origin's script may read.
Expose []string
// Credentials allows cookies and Authorization. Incompatible with "*".
Credentials bool
// MaxAge is how long the browser may cache the preflight (zero omits the
// header, and the browser uses its own short default).
MaxAge time.Duration
}
CORS is the cross-origin policy of the app, in Config.CORS. The zero value is off: no header is added and OPTIONS keeps reaching the router.
CORS: trilha.CORS{
Origins: []string{"https://app.example.com"},
Credentials: true,
MaxAge: 10 * time.Minute,
}
Origins are exact ("scheme://host[:port]", no path, no trailing slash), or the single entry "*" for a public API. An unsafe or malformed policy panics in New: a CORS mistake that only shows up on the first request from outside is a mistake nobody sees in development.
type CheckResult ¶ added in v0.6.0
type CheckResult struct {
Name string `json:"name"`
Status string `json:"status"`
DurationMS float64 `json:"duration_ms"`
Error string `json:"error,omitempty"`
}
CheckResult is the outcome of one readiness check. Error is only ever sent to an authorized client; anonymous callers see the status alone.
type Config ¶
type Config struct {
// Addr is the listen address (default ":3000").
Addr string
// Env selects dev (stack traces, live reload, no static cache) or prod.
Env Env
// MaxBodyBytes limits request bodies (default 1 MiB).
MaxBodyBytes int64
// Logger receives structured request logs (default slog.Default()).
Logger *slog.Logger
// Public serves static files at the root. nil disables static files.
Public fs.FS
// Mounts serve static trees at URL prefixes, for the app whose disk tree
// is not shaped like its URL tree. They match before Public, longest
// prefix first, and fall through to it when the file is not there.
Mounts map[string]fs.FS
// CSRFForAPI also enforces CSRF tokens on route.go handlers.
CSRFForAPI bool
// BasePath is the URL prefix the app is served under (e.g. "/docs" on
// GitHub Pages). Read it with Ctx.Base when building links.
BasePath string
// Security tunes the hardening headers (zero value = defaults).
Security Security
// TrustedProxies lists CIDRs whose X-Forwarded-For/Proto are honoured.
TrustedProxies []string
// RateLimit enables a global per-client limit (zero = off).
RateLimit RateLimit
// Secret signs cookies (TRILHA_SECRET); PreviousSecret still verifies.
Secret, PreviousSecret []byte
// Timeouts protect the server from slow clients.
Timeouts Timeouts
// StaticCacheControl replaces the production Cache-Control of files in
// Public (default "public, max-age=3600"; dev always sends no-cache).
StaticCacheControl string
// StaticHeaders runs for every file served from Public, after the
// defaults, and may set any header (immutable for hashed assets, CORP...).
StaticHeaders func(name string, hdr http.Header)
// LogRequest decides, with the response already written, whether a
// request enters the access log. nil logs every one of them.
LogRequest func(c *Ctx, status int, dur time.Duration) bool
// OnSecurityEvent is called for blocked requests (CSRF, 401/403, 413, 429, panic).
OnSecurityEvent func(SecurityEvent)
// DevReload controls the live-reload script injected in Dev pages; Off
// disables it (snapshot tests, HTML diffs). TRILHA_DEV_RELOAD=off does the same.
DevReload string
// Observability configures the health probes, the metrics endpoint and
// what each of them reveals.
Observability Observability
// CORS allows other origins to call this app (zero value = off).
CORS CORS
}
Config configures an App.
func ConfigFromEnv ¶
func ConfigFromEnv() Config
ConfigFromEnv builds a Config from ADDR/PORT and TRILHA_ENV.
type Counter ¶ added in v0.6.0
type Counter struct {
// contains filtered or unexported fields
}
Counter only ever grows: requests served, errors, events.
type Ctx ¶
type Ctx struct {
// contains filtered or unexported fields
}
Ctx wraps one request/response pair. It is created per request and is not safe for use from other goroutines after the handler returns.
func (*Ctx) Accepts ¶ added in v0.21.0
Accepts returns the offer the client prefers, according to the Accept header and its q values, or "" when it accepts none of them. An absent or */* Accept is not a preference: the first offer wins, so put your default first.
switch c.Accepts("text/html", "application/json") {
case "application/json": return c.JSON(200, v)
default: return c.Render(200, page(v))
}
func (*Ctx) AllowBody ¶ added in v0.15.0
AllowBody raises (or lowers) the body limit for this request only, leaving Config.MaxBodyBytes in place for every other route. Call it before reading the body:
func POST(c *trilha.Ctx) error {
c.AllowBody(8 << 20)
…
}
Called after the body started being read, the new limit counts from there. Going over it still answers 413.
func (*Ctx) Base ¶ added in v0.2.0
Base returns the app's base path for building links: c.Base()+"/aprender".
func (*Ctx) Bind ¶ added in v0.5.0
Bind fills a struct from the request: JSON when the Content-Type is application/json (see BindJSON), otherwise form fields and query string. Fields are matched by the `form:"name"` tag (or the field name). Supported types: string, []string, bool (checkbox: on/true/1), int, int64, float64, time.Time (2006-01-02 or 2006-01-02T15:04) and pointers to them (nil when absent). A nested struct is flattened: its fields are read with the struct's tag as prefix (`Cobranca Endereco `+"`"+`form:"cob_"`+"`"+` reads cob_cep...), or with no prefix when it has no tag. Values that fail to convert are reported as FieldErrors, after every field was tried, so all messages reach the user at once.
var in struct {
Nome string `form:"nome"`
Idade int `form:"idade"`
Ativo bool `form:"ativo"`
}
if err := c.Bind(&in); err != nil { return err }
func (*Ctx) BindJSON ¶
BindJSON decodes the request body into v. Returns an HTTPError 400 on malformed JSON and 413 when the body exceeds the limit.
func (*Ctx) CSRFToken ¶
CSRFToken returns the request's CSRF token, creating the cookie on first use. Put it in forms with CSRFInput or send it in the X-CSRF-Token header.
func (*Ctx) CacheControl ¶ added in v0.17.0
CacheControl sets the response policy. A page carrying data that belongs to one person needs private in it: without that, a shared cache is allowed to hand one visitor's page to the next one.
func (*Ctx) ClearCookie ¶ added in v0.2.0
ClearCookie expires a cookie.
func (*Ctx) ClientIP ¶ added in v0.2.0
ClientIP returns the client address: RemoteAddr, or the right-most untrusted entry of X-Forwarded-For when the peer is a trusted proxy.
func (*Ctx) ETag ¶ added in v0.17.0
ETag declares the version of what this route is about to answer and reports whether the browser already has it:
func Page(c *trilha.Ctx) (h.Node, error) {
p, ok := posts.Get(c.Param("slug"))
if !ok {
return nil, trilha.ErrNotFound
}
if c.ETag(p.Rev) {
return nil, nil // 304: nothing else to answer
}
return pagina(p), nil
}
The tag is whatever identifies the version of the data — a revision, an updated_at, a hash of the row. It is not the hash of the response: the CSP nonce changes on every request, so a body hash would never match twice.
Quotes are added when missing, and a tag already written as "abc" or W/"abc" is sent as it is. Only GET and HEAD answer 304; on any other method the header is written and the return is false.
func (*Ctx) File ¶ added in v0.19.0
File reads one file from a multipart form and answers with it only if it passes the rules: size, media type detected in the content (never the extension, never what the client announced) and a name that cannot walk out of a directory.
up, err := c.File("file", trilha.FileRules{
MaxSize: 2 << 20,
Accept: []string{"image/png", "image/jpeg"},
})
A rule that fails is a FieldErrors under the field's name, the same answer Bind gives, so the form shows the message where the person is looking. Any other error (a broken body, a full disk) comes back as itself.
func (*Ctx) Form ¶
Form returns a form field (POST body or query string). Returns "" if the form cannot be parsed; use FormErr to distinguish.
func (*Ctx) Fragment ¶ added in v0.10.0
Fragment returns the part of the page this request asked for, or "" on a normal navigation. It is the whole protocol: the same route serves the page and the piece, and decides what to return.
func Page(c *trilha.Ctx) (h.Node, error) {
lista := listaDe(c)
if c.Fragment() == "lista" {
return lista, nil // sem layouts
}
return h.Div(busca(), lista), nil
}
A fragment response carries no layout, no document envelope and no dev script; every HTML response gets Vary: Trilha-Fragment so a cache never serves one in place of the other.
func (*Ctx) Hijack ¶ added in v0.15.0
Hijack takes the connection over, so a handler can speak another protocol on it — WebSocket, above all. Trilha does not implement WebSocket: it is a transport, it touches nothing else in the framework, and a library in the app's own go.mod does it better than 400 lines here would. What Trilha owes is the way out, and this is it.
The read and write deadlines are cleared before the connection is returned (an idle socket is a WebSocket's normal state, not a stuck request), and the framework writes nothing else to the response: the log records 101 and the connection is the handler's until it closes it. Middleware, CSRF and authentication have already run.
func (*Ctx) Island ¶ added in v0.13.0
Island renders an interactive region of a page that stays static: the server sends the fallback HTML, and a module in public/ takes over on the client. There is no global hydration and no bundler — src is a file in public/, addressed through Asset so it gets the content hash:
c.Island("/editor.js", map[string]any{"wpm": 200},
h.Class("editor"), ui.Textarea(h.Name("corpo")))
The module's default export is the mount function, called once with the element and the props already parsed:
export default function (el, props) { ... }
props is anything encoding/json can serialize, or nil. What the server sends is data, never markup: it is escaped as an attribute and read back with JSON.parse. The children are the fallback, so the page works with the script blocked, failing to load, or still on its way.
func (*Ctx) LastModified ¶ added in v0.17.0
LastModified declares when what this route answers last changed, and reports whether the browser already has that version. Same contract as ETag, with two differences: the comparison happens at the second, which is all an HTTP date carries, and a request that brought If-None-Match is left to it — a strong validator is not overruled by a weak one (RFC 9110 §13.1.3).
func (*Ctx) Log ¶ added in v0.6.0
Log returns a logger already carrying request_id and, when present, trace_id, so every line of one request can be found together (NIST SP 800-53 AU-3).
func (*Ctx) NoReadDeadline ¶ added in v0.15.0
NoReadDeadline disables the server read timeout for this request; call it before reading a body that may take longer than Timeouts.Read (an upload on a slow link).
func (*Ctx) NoWriteDeadline ¶ added in v0.2.0
NoWriteDeadline disables the server write timeout for this response; call it before streaming (SSE, long downloads). A writer with no deadline to remove (a test recorder, an export) is not an error.
func (*Ctx) Render ¶ added in v0.5.0
Render writes node as a page with the route's layouts applied (innermost first), like GET does. Use it in POST handlers to answer a form with validation errors inside the same layouts:
if errs := validate(in); errs.Any() {
return c.Render(422, formulario(c, in, errs))
}
func (*Ctx) SetContext ¶ added in v0.2.0
SetContext replaces the request context, so a middleware can pass values to code that only receives *http.Request (templates, stdlib helpers).
func (*Ctx) SetRequest ¶ added in v0.2.0
SetRequest replaces the request (rewritten URL, wrapped body...). Values already read from the old request (form, request id) are kept.
func (*Ctx) SetSigned ¶ added in v0.2.0
SetSigned stores a tamper-proof cookie (HttpOnly, SameSite=Lax, Secure on HTTPS) that expires after ttl. Returns ErrNoSecret without a secret.
func (*Ctx) Signed ¶ added in v0.2.0
Signed reads a cookie written by SetSigned; ok is false when missing, tampered or expired.
func (*Ctx) Stream ¶ added in v0.2.0
Stream switches the response to text/event-stream, disables the write deadline and returns a writer for events. Use it from a GET route:
func GET(c *trilha.Ctx) error {
s := c.Stream()
for chunk := range chunks {
if err := s.Send("delta", chunk); err != nil { return err }
}
return s.Send("done", "")
}
Compression is skipped for streams, and each event is flushed right away.
func (*Ctx) TraceID ¶ added in v0.6.0
TraceID returns the trace identifier the caller propagated in the traceparent header, or "" when it is absent or malformed. Trilha only carries the identifier into the logs; it does not sample or export spans.
func (*Ctx) Writer ¶
func (c *Ctx) Writer() http.ResponseWriter
Writer returns the underlying http.ResponseWriter.
type ErrorPageFunc ¶
ErrorPageFunc renders the 500 page (error.go: Error).
type Field ¶ added in v0.18.0
type Field struct {
Name string // the form name, prefix included ("cob_cep")
Param string // what came after "=" in the tag ("3" in min=3)
Text string // the value as text
Value any // the converted value
// contains filtered or unexported fields
}
Field is what a validation rule sees. Value is the converted value (string, bool, int64, float64, time.Time, []string, or nil when the field was not sent), and Text is the same thing as text — which is all most rules need.
type FieldErrors ¶ added in v0.5.0
FieldErrors collects validation messages by form field. Returned from a handler it answers 422: JSON with a "fields" object on API routes, or the error page on pages — though a form usually re-renders itself with Ctx.Render and shows each message next to its field.
func (FieldErrors) Add ¶ added in v0.5.0
func (e FieldErrors) Add(field, msg string)
Add records a message for field (the first one wins).
func (FieldErrors) Any ¶ added in v0.5.0
func (e FieldErrors) Any() bool
Any reports whether there is at least one error.
func (FieldErrors) Error ¶ added in v0.5.0
func (e FieldErrors) Error() string
Error joins the messages, sorted by field, for logs and API responses.
func (FieldErrors) Get ¶ added in v0.5.0
func (e FieldErrors) Get(field string) string
Get returns the message for field, or "".
func (FieldErrors) Has ¶ added in v0.5.0
func (e FieldErrors) Has(field string) bool
Has reports whether field has an error.
func (FieldErrors) OrNil ¶ added in v0.5.0
func (e FieldErrors) OrNil() error
OrNil returns e as an error, or nil when empty, for `return errs.OrNil()`.
type FileRules ¶ added in v0.19.0
type FileRules struct {
// MaxSize is the limit for this file, in bytes, apart from
// Config.MaxBodyBytes. Zero leaves the body limit doing the work.
MaxSize int64
// Accept lists the media types allowed, matched against the type detected
// in the content: "image/png", "application/pdf", or "image/*". Empty
// accepts anything.
Accept []string
// Optional makes an absent field return (nil, nil) instead of an error.
Optional bool
}
FileRules is what a route accepts in one form field. The zero value accepts any type and any size the body limit allows, and requires the field.
type Gauge ¶ added in v0.6.0
type Gauge struct {
// contains filtered or unexported fields
}
Gauge goes up and down: queue depth, connections in use.
type HandlerFunc ¶
HandlerFunc handles an API method or a form submission.
type HealthReport ¶ added in v0.6.0
type HealthReport struct {
Status string `json:"status"`
Checks []CheckResult `json:"checks,omitempty"`
UptimeSeconds float64 `json:"uptime_seconds,omitempty"`
}
HealthReport is the readiness of the application.
type Histogram ¶ added in v0.6.0
type Histogram struct {
// contains filtered or unexported fields
}
Histogram counts observations per bucket: durations, sizes.
type LayoutFunc ¶
LayoutFunc wraps rendered children (layout.go: Layout).
type Metrics ¶ added in v0.6.0
type Metrics struct {
// MaxSeries caps the number of label combinations per metric (default
// 1000). Extra combinations are folded into a single "other" series.
MaxSeries int
// contains filtered or unexported fields
}
Metrics is the process metric registry, exposed in the Prometheus text format. Get it with App.Metrics; it exists even when no endpoint serves it.
func (*Metrics) Counter ¶ added in v0.6.0
Counter returns (creating on first use) a counter. labels declares the dimension names; bind the values with With. Invalid names panic: it is a programming error, caught on the first run.
type MiddlewareFunc ¶
MiddlewareFunc intercepts a subtree (middleware.go: Middleware).
func Limit ¶ added in v0.2.0
func Limit(rps float64, burst int) MiddlewareFunc
Limit returns a middleware applying its own per-client limit to a subtree: put `var limit = trilha.Limit(2, 5)` in middleware.go and call it.
type Observability ¶ added in v0.6.0
type Observability struct {
// Health is the base path of the probes (default "/_trilha/health",
// with /live and /ready under it). Off removes them.
Health string
// Metrics is the path of the Prometheus endpoint. Empty (the default)
// means no endpoint and no request instrumentation.
Metrics string
// Token authorizes the detailed health and the metrics
// (Authorization: Bearer ...). It must have at least 32 bytes; shorter
// tokens never authorize anything. Read from TRILHA_OBS_TOKEN.
Token string
// Trusted lists CIDRs (or plain IPs) that skip the token, for a scraper
// on a private network. "0.0.0.0/0" plus "::/0" opens it to everyone:
// only do that when something in front already restricts access.
Trusted []string
// Details is Off to never reveal check names and errors, even to an
// authorized client. Empty means dev shows them and prod requires
// authorization.
Details string
// Timeout is the deadline of each readiness check (default 2s).
// NoTimeout waits forever (not recommended: the probe holds a connection).
Timeout time.Duration
// CacheFor is how long a readiness result is reused (default 1s), so a
// flood of probes cannot amplify into a flood of database queries.
// NoTimeout disables the cache.
CacheFor time.Duration
}
Observability configures the health, metrics and tracing surface. The zero value serves the health probes and nothing else: metrics have to be turned on explicitly, and outside dev the detailed health needs authorization (NIST SP 800-53 AU-9, OWASP API Security 2023 API8).
type Problem ¶ added in v0.21.0
type Problem struct {
// Type is a URI identifying the kind of problem — usually a page of yours
// documenting it. "about:blank" means "nothing beyond the status".
Type string `json:"type,omitempty"`
// Title is the short, human-readable summary, the same for every
// occurrence of this Type.
Title string `json:"title"`
// Status is the HTTP status code.
Status int `json:"status"`
// Detail explains this occurrence. It is read by a person: never put a
// stack trace, a query or a DSN in it.
Detail string `json:"detail,omitempty"`
// Instance identifies this occurrence (default: the request path).
Instance string `json:"instance,omitempty"`
// Fields carries the per-field messages of a 422, unchanged.
Fields FieldErrors `json:"fields,omitempty"`
// Extra members are merged into the top-level object.
Extra map[string]any `json:"-"`
}
Problem is an API error in the RFC 9457 shape. Return one from a handler to say more than a status code:
return &trilha.Problem{
Type: "https://example.com/probs/out-of-credit",
Title: "Out of credit",
Status: http.StatusPaymentRequired,
Detail: "The account has $3 and the operation costs $10.",
Extra: map[string]any{"balance": 300},
}
Empty fields are filled in by the framework: Type becomes "about:blank" (or what ProblemType returns), Title the status text, Instance the request path, and request_id the id of the request. Extra members are written next to the standard ones, at the top level, which is what the RFC calls an extension.
func (*Problem) MarshalJSON ¶ added in v0.21.0
MarshalJSON writes the standard members and then the extension ones, so Extra reads like part of the object instead of a bag inside it.
type RateLimit ¶ added in v0.2.0
type RateLimit struct {
// RPS is the sustained requests per second per client.
RPS float64
// Burst is the bucket size (requests allowed at once).
Burst int
}
RateLimit configures the per-client token bucket. Zero disables it.
type RedirectError ¶
RedirectError is returned by handlers to redirect the client.
func (*RedirectError) Error ¶
func (e *RedirectError) Error() string
type Route ¶
type Route struct {
// Pattern is the path pattern, e.g. "/blog/{slug}" or "/docs/{path...}".
Pattern string
// Page renders GET for page routes; nil for API routes.
Page PageFunc
// Methods maps HTTP methods to handlers (route.go, or form methods in page.go).
Methods map[string]HandlerFunc
// Layouts wrap the page, innermost first.
Layouts []LayoutFunc
// Middlewares run before the handler, outermost first.
Middlewares []MiddlewareFunc
// Kind decides how errors are rendered (HTML page or JSON) and whether
// CSRF applies. KindAuto: page.go routes are pages; route.go routes are
// APIs, except that a browser navigation (Accept: text/html, outside
// /api/) gets HTML error pages. route.go may export `var Kind = trilha.KindPage`.
Kind RouteKind
}
Route is one entry produced by the generator for App.Register.
type RouteKind ¶ added in v0.3.0
type RouteKind int
RouteKind is the error/CSRF behaviour of a Route; see Route.Kind.
type Security ¶ added in v0.2.0
type Security struct {
// CSP is the Content-Security-Policy. Empty = default policy with a
// per-request nonce for scripts; the text may contain {nonce}.
CSP string
// CSPExtra adds sources to directives of the default policy, e.g.
// {"style-src": {"https://fonts.googleapis.com"}}.
CSPExtra map[string][]string
// HSTS is sent only over HTTPS (TLS or a trusted proxy saying so).
HSTS string
// PermissionsPolicy restricts browser features.
PermissionsPolicy string
// COOP is Cross-Origin-Opener-Policy.
COOP string
// FrameOptions is X-Frame-Options.
FrameOptions string
// Referrer is Referrer-Policy.
Referrer string
}
Security configures the hardening headers sent with every response. The zero value means "defaults"; set a field to Off to drop that header.
type SecurityEvent ¶ added in v0.2.0
type SecurityEvent struct {
// Kind is one of csrf, auth, body, rate, panic.
Kind string
Status int
Method string
Path string
IP string
RequestID string
}
SecurityEvent describes a request the framework blocked or flagged.
type Signer ¶ added in v0.2.0
type Signer struct {
// contains filtered or unexported fields
}
Signer signs and verifies values with HMAC-SHA256. The first key signs; every key verifies, so a previous key can be kept during rotation.
func NewSigner ¶ added in v0.2.0
NewSigner creates a signer; the first key signs, the rest only verify.
type Stream ¶ added in v0.2.0
type Stream struct {
// contains filtered or unexported fields
}
Stream is a Server-Sent Events writer returned by Ctx.Stream.
func (*Stream) Comment ¶ added in v0.2.0
Comment sends a comment line (keeps proxies from timing out the stream).
func (*Stream) Done ¶ added in v0.2.0
func (s *Stream) Done() <-chan struct{}
Done reports when the client went away.
func (*Stream) Flush ¶ added in v0.2.0
func (s *Stream) Flush()
Flush pushes buffered bytes to the client.
type TestClient ¶ added in v0.23.0
type TestClient struct {
// contains filtered or unexported fields
}
TestClient sends requests through the app keeping the cookies, so a login and the page after it are two lines.
func NewTestClient ¶ added in v0.23.0
func NewTestClient(t TestingT, a *App) *TestClient
NewTestClient builds a client over the app's handler.
func (*TestClient) Get ¶ added in v0.23.0
func (c *TestClient) Get(target string, opts ...TestOption) *TestResponse
Get sends a GET.
func (*TestClient) PostForm ¶ added in v0.23.0
func (c *TestClient) PostForm(target string, form url.Values, opts ...TestOption) *TestResponse
PostForm sends a form, CSRF included.
func (*TestClient) PostJSON ¶ added in v0.23.0
func (c *TestClient) PostJSON(target string, v any, opts ...TestOption) *TestResponse
PostJSON sends a JSON body.
func (*TestClient) Request ¶ added in v0.23.0
func (c *TestClient) Request(method, target string, opts ...TestOption) *TestResponse
Request sends one request and stores the cookies the app set.
type TestOption ¶ added in v0.23.0
type TestOption func(*testOptions)
TestOption changes one request. The options are shared by every helper.
func WithApp ¶ added in v0.23.0
func WithApp(a *App) TestOption
WithApp runs the route on this app instead of the throwaway one, so the route sees the real Config (secret, Public, CSRFForAPI). Only TestRoute and TestPage read it, and they register the route on the app, so give a fresh app: registering the same pattern twice panics in net/http.
func WithBody ¶ added in v0.23.0
func WithBody(contentType, body string) TestOption
WithBody sends the body as it is.
func WithCookie ¶ added in v0.23.0
func WithCookie(name, value string) TestOption
WithCookie sends a cookie, replacing the one the client holds.
func WithForm ¶ added in v0.23.0
func WithForm(form url.Values) TestOption
WithForm sends the values as an HTML form.
func WithHeader ¶ added in v0.23.0
func WithHeader(name, value string) TestOption
WithHeader adds a request header.
func WithSigned ¶ added in v0.23.0
func WithSigned(name, value string) TestOption
WithSigned sends a cookie signed like Ctx.SetSigned would, valid for an hour. It is how a test opens a session route without replaying the login.
func WithoutCSRF ¶ added in v0.23.0
func WithoutCSRF() TestOption
WithoutCSRF drops the CSRF cookie and header, so the request exercises the rejection the browser would get.
type TestResponse ¶ added in v0.23.0
type TestResponse struct {
*httptest.ResponseRecorder
// Request is what was sent, already carrying cookies and CSRF.
Request *http.Request
// Node is the node the page returned, before the layouts. Only TestPage
// fills it in.
Node h.Node
// contains filtered or unexported fields
}
TestResponse is what the app answered. It embeds the recorder, so Code, Body and Header are the usual ones; the Want methods stop the test with the body in the message.
func TestPage ¶ added in v0.23.0
func TestPage(t TestingT, r Route, target string, opts ...TestOption) *TestResponse
TestPage renders one page with its layouts and fills TestResponse.Node with what the page returned, so a test can look at the node instead of the HTML.
func TestRequest ¶ added in v0.23.0
func TestRequest(t TestingT, a *App, method, target string, opts ...TestOption) *TestResponse
TestRequest sends one request through the whole app: middlewares, CSRF, error pages, everything ListenAndServe would run.
func TestRoute ¶ added in v0.23.0
func TestRoute(t TestingT, r Route, method, target string, opts ...TestOption) *TestResponse
TestRoute sends one request to a single route with its middlewares, without generating the app. Pass WithApp to give it a Config of its own.
func (*TestResponse) Cookie ¶ added in v0.23.0
func (r *TestResponse) Cookie(name string) *http.Cookie
Cookie returns the cookie the response set, or nil.
func (*TestResponse) JSON ¶ added in v0.23.0
func (r *TestResponse) JSON(v any) *TestResponse
JSON decodes the body into v.
func (*TestResponse) WantContains ¶ added in v0.23.0
func (r *TestResponse) WantContains(subs ...string) *TestResponse
WantContains fails unless every string is in the body.
func (*TestResponse) WantHeader ¶ added in v0.23.0
func (r *TestResponse) WantHeader(name, want string) *TestResponse
WantHeader fails unless the response header matches.
func (*TestResponse) WantStatus ¶ added in v0.23.0
func (r *TestResponse) WantStatus(code int) *TestResponse
WantStatus fails unless the status matches.
type TestingT ¶ added in v0.23.0
TestingT is the part of *testing.T these helpers use. It exists so the runtime never imports testing: importing trilha in production must not drag the test flags into the binary.
type Timeouts ¶ added in v0.2.0
type Timeouts struct {
ReadHeader time.Duration // 10s
Read time.Duration // 30s
Write time.Duration // 60s (use Ctx.NoWriteDeadline for streams)
Idle time.Duration // 120s
MaxHeaderBytes int // 64 KiB
// Shutdown is how long ListenAndServe waits for in-flight requests after
// SIGINT/SIGTERM before closing (5s).
Shutdown time.Duration
}
Timeouts are the http.Server limits. Zero fields get defaults; NoTimeout disables one (large uploads, long polls). Write applies to the whole response: streams should call Ctx.Stream or Ctx.NoWriteDeadline instead of disabling it globally.
type Upload ¶ added in v0.19.0
type Upload struct {
// Name is the file name, sanitised: no directory, no separator, no
// control character, at most 100 characters, never empty. It still comes
// from the client, so it says nothing about the content — Ext does.
Name string
// MIME is the media type detected in the first 512 bytes of the file,
// never what the client announced.
MIME string
// Ext is the extension that matches MIME (".pdf"), which may disagree
// with the one in Name.
Ext string
// Size is the file size in bytes.
Size int64
// File is the file itself, positioned at the start. Close it, or call
// Upload.Close.
File multipart.File
}
Upload is a file that already passed the rules.
func (*Upload) Save ¶ added in v0.19.0
Save writes the file inside dir, creating the directory if needed, with mode 0600 and a name that is free: nota.pdf, then nota-1.pdf, and so on. It returns the path written. The name cannot escape dir — that is the whole reason this exists instead of a filepath.Join in the handler.
type Validator ¶ added in v0.18.0
type Validator interface{ Validate() error }
Validator is a type that checks itself. Bind calls it on a field, right after the value was converted, and on the whole struct at the end — the second one only when every field passed, because a check that reads two fields needs both of them to be there.
type CPF string
func (c CPF) Validate() error {
if !cpfValido(string(c)) {
return errors.New("CPF inválido")
}
return nil
}
The error message goes to FieldErrors as it is, so write it in the language of the app. A struct's Validate may return FieldErrors to say which field is at fault; any other error is returned by Bind untouched.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package ai talks to chat-completion APIs that follow the OpenAI protocol (OpenAI, Azure, Ollama, LM Studio, Groq, OpenRouter, vLLM...) and builds agents on top of them: tools, handoffs, parallel and chained agents.
|
Package ai talks to chat-completion APIs that follow the OpenAI protocol (OpenAI, Azure, Ollama, LM Studio, Groq, OpenRouter, vLLM...) and builds agents on top of them: tools, handoffs, parallel and chained agents. |
|
mcp
Package mcp implements the Model Context Protocol (JSON-RPC 2.0 over stdio or Streamable HTTP) as a client, to bring external tools into an ai.Agent, and as a server, to expose your app's tools to MCP hosts.
|
Package mcp implements the Model Context Protocol (JSON-RPC 2.0 over stdio or Streamable HTTP) as a client, to bring external tools into an ai.Agent, and as a server, to expose your app's tools to MCP hosts. |
|
Package auth adds OpenID Connect login, signed sessions and role checks to a Trilha app, using only the standard library.
|
Package auth adds OpenID Connect login, signed sessions and role checks to a Trilha app, using only the standard library. |
|
Package cache is an in-memory cache with TTL, tags and explicit invalidation.
|
Package cache is an in-memory cache with TTL, tags and explicit invalidation. |
|
cmd
|
|
|
trilha
command
Command trilha is the CLI: new, gen, dev, build, routes, export, openapi, audit, ui.
|
Command trilha is the CLI: new, gen, dev, build, routes, export, openapi, audit, ui. |
|
examples
|
|
|
assistente
command
|
|
|
assistente/app/api/chat
Package chat streams agent runs to the browser over Server-Sent Events.
|
Package chat streams agent runs to the browser over Server-Sent Events. |
|
assistente/app/mcp
Package mcp exposes the example's tools to MCP hosts (Streamable HTTP).
|
Package mcp exposes the example's tools to MCP hosts (Streamable HTTP). |
|
assistente/internal/ferramentas
Package ferramentas holds the tools, agents and MCP server of the example.
|
Package ferramentas holds the tools, agents and MCP server of the example. |
|
blog
command
|
|
|
blog/app/anexos
Package anexos shows an upload with progress that still works without JavaScript, checked by c.File: limite por arquivo, tipo lido no conteúdo e nome sem caminho.
|
Package anexos shows an upload with progress that still works without JavaScript, checked by c.File: limite por arquivo, tipo lido no conteúdo e nome sem caminho. |
|
blog/app/api/posts
Package posts exposes the JSON API at /api/posts.
|
Package posts exposes the JSON API at /api/posts. |
|
blog/app/marketing-
Package marketing is a route group: its layout wraps /precos and /sobre without adding a URL segment (folder name ends with "-").
|
Package marketing is a route group: its layout wraps /precos and /sobre without adding a URL segment (folder name ends with "-"). |
|
blog/app/painel-
Package painel is a route group for the app area (/painel, /relatorio).
|
Package painel is a route group for the app area (/painel, /relatorio). |
|
blog/app/painel-/relatorio
Package relatorio renders a page from an html/template file instead of the h DSL, using the tmpl adapter.
|
Package relatorio renders a page from an html/template file instead of the h DSL, using the tmpl adapter. |
|
blog/internal/anexos
Package anexos guarda os anexos enviados.
|
Package anexos guarda os anexos enviados. |
|
blog/internal/icones
Package icones embute os ícones do site.
|
Package icones embute os ícones do site. |
|
blog/internal/posts
Package posts is an in-memory post store for the example app.
|
Package posts is an in-memory post store for the example app. |
|
cadastro
command
|
|
|
cadastro/app/api/cidades
Package cidades serves the dependent select: GET /api/cidades?uf=SP.
|
Package cidades serves the dependent select: GET /api/cidades?uf=SP. |
|
cadastro/internal/clientes
Package clientes holds the domain of the example: the form model, its validation rules and an in-memory store.
|
Package clientes holds the domain of the example: the form model, its validation rules and an in-memory store. |
|
orcamento
command
|
|
|
orcamento/app/api/relatorio.csv
Package relatoriocsv exports the month as CSV at /api/relatorio.csv?mes=.
|
Package relatoriocsv exports the month as CSV at /api/relatorio.csv?mes=. |
|
orcamento/app/lancamentos
Package lancamentos has the standalone entry page (works without JS) and the POST every entry form submits to (dialog included).
|
Package lancamentos has the standalone entry page (works without JS) and the POST every entry form submits to (dialog included). |
|
orcamento/internal/componentes
Package componentes holds the reusable, nested UI pieces of the budget example.
|
Package componentes holds the reusable, nested UI pieces of the budget example. |
|
orcamento/internal/plano
Package plano is the domain of the budget example: a chart of accounts (tree), monthly budgets on analytic accounts, and entries.
|
Package plano is the domain of the budget example: a chart of accounts (tree), monthly budgets on analytic accounts, and entries. |
|
sso
command
|
|
|
sso/app/api
Package api agrupa as rotas de API.
|
Package api agrupa as rotas de API. |
|
sso/app/api/eu
Package eu devolve a sessão como JSON.
|
Package eu devolve a sessão como JSON. |
|
sso/app/entrar
Package entrar começa o login.
|
Package entrar começa o login. |
|
sso/app/entrar/retorno
Package retorno termina o login.
|
Package retorno termina o login. |
|
sso/app/painel
Package painel é a área que exige sessão.
|
Package painel é a área que exige sessão. |
|
sso/app/painel/relatorio
Package relatorio exige um papel, não só sessão.
|
Package relatorio exige um papel, não só sessão. |
|
sso/app/sair
Package sair encerra a sessão.
|
Package sair encerra a sessão. |
|
sso/internal/sso
Package sso monta o fluxo OpenID Connect a partir do ambiente e expõe funções finas para as rotas em app/.
|
Package sso monta o fluxo OpenID Connect a partir do ambiente e expõe funções finas para as rotas em app/. |
|
Package h is a small, dependency-free HTML DSL: every element, attribute and piece of text is a Node that knows how to render itself to an io.Writer.
|
Package h is a small, dependency-free HTML DSL: every element, attribute and piece of text is a Node that knows how to render itself to an io.Writer. |
|
internal
|
|
|
dev
Package dev implements `trilha dev`: a polling file watcher, a builder and a supervisor that runs the app behind a reverse proxy with live reload.
|
Package dev implements `trilha dev`: a polling file watcher, a builder and a supervisor that runs the app behind a reverse proxy with live reload. |
|
gen
Package gen turns a scan.Result into the source of trilha_gen.go.
|
Package gen turns a scan.Result into the source of trilha_gen.go. |
|
openapi
Package openapi turns a scanned app into an OpenAPI 3.1 document.
|
Package openapi turns a scanned app into an OpenAPI 3.1 document. |
|
scaffold
Package scaffold writes a new project from embedded templates.
|
Package scaffold writes a new project from embedded templates. |
|
scan
Package scan walks an app/ directory and turns its file conventions into a list of routes, validating them along the way.
|
Package scan walks an app/ directory and turns its file conventions into a list of routes, validating them along the way. |
|
app
Package app is the documentation site of Trilha, built with Trilha.
|
Package app is the documentation site of Trilha, built with Trilha. |
|
internal/demos
Package demos holds the "code → result" examples used by the home page and by chapters.
|
Package demos holds the "code → result" examples used by the home page and by chapters. |
|
internal/docs
Package docs loads the Markdown content of the documentation site in every locale, builds the navigation and renders pages.
|
Package docs loads the Markdown content of the documentation site in every locale, builds the navigation and renders pages. |
|
internal/home
Package home renders the landing page of the site in each locale.
|
Package home renders the landing page of the site in each locale. |
|
internal/md
Package md is a deliberately small Markdown-to-HTML converter for the documentation site: headings with ids, paragraphs, lists, fenced code (with Go highlighting), tables, blockquotes, callouts (:::nome), inline code, emphasis and links.
|
Package md is a deliberately small Markdown-to-HTML converter for the documentation site: headings with ids, paragraphs, lists, fenced code (with Go highlighting), tables, blockquotes, callouts (:::nome), inline code, emphasis and links. |
|
internal/ui
Package ui holds the shared building blocks of the documentation site: the document shell, sidebar, table of contents and helpers.
|
Package ui holds the shared building blocks of the documentation site: the document shell, sidebar, table of contents and helpers. |
|
Package tmpl adapts html/template to the h.Node pipeline, for developers who prefer template files over the Go DSL.
|
Package tmpl adapts html/template to the h.Node pipeline, for developers who prefer template files over the Go DSL. |
|
Package ui is Trilha's default, customizable UI kit: typed components that render classes consumed by public/ui.css, plus a small ui.js for behavior.
|
Package ui is Trilha's default, customizable UI kit: typed components that render classes consumed by public/ui.css, plus a small ui.js for behavior. |