// Code generated by templ - DO NOT EDIT.
package testhtml
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Render(p Person) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(p.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `benchmarks/templ/template.templ`, Line: 5, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><div style=\"font-family: 'sans-serif'\" id=\"test\" data-contents=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(`something with "quotes" and a <tag>`)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `benchmarks/templ/template.templ`, Line: 6, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div>email:<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.URL("mailto: " + p.Email)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(p.Email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `benchmarks/templ/template.templ`, Line: 7, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div></div></div><hr")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if true {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" noshade")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("><hr optionA")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if true {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" optionB")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" optionC=\"other\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if false {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" optionD")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("><hr noshade>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package fmtcmd
import (
"bytes"
"fmt"
"io"
"log/slog"
"os"
"runtime"
"sync"
"time"
"github.com/a-h/templ/cmd/templ/imports"
"github.com/a-h/templ/cmd/templ/processor"
parser "github.com/a-h/templ/parser/v2"
"github.com/natefinch/atomic"
)
type Arguments struct {
FailIfChanged bool
ToStdout bool
StdinFilepath string
Files []string
WorkerCount int
}
func Run(log *slog.Logger, stdin io.Reader, stdout io.Writer, args Arguments) (err error) {
// If no files are provided, read from stdin and write to stdout.
if len(args.Files) == 0 {
out, _ := format(writeToWriter(stdout), readFromReader(stdin, args.StdinFilepath), true)
return out
}
process := func(fileName string) (error, bool) {
read := readFromFile(fileName)
write := writeToFile
if args.ToStdout {
write = writeToWriter(stdout)
}
writeIfUnchanged := args.ToStdout
return format(write, read, writeIfUnchanged)
}
dir := args.Files[0]
return NewFormatter(log, dir, process, args.WorkerCount, args.FailIfChanged).Run()
}
type Formatter struct {
Log *slog.Logger
Dir string
Process func(fileName string) (error, bool)
WorkerCount int
FailIfChange bool
}
func NewFormatter(log *slog.Logger, dir string, process func(fileName string) (error, bool), workerCount int, failIfChange bool) *Formatter {
f := &Formatter{
Log: log,
Dir: dir,
Process: process,
WorkerCount: workerCount,
FailIfChange: failIfChange,
}
if f.WorkerCount == 0 {
f.WorkerCount = runtime.NumCPU()
}
return f
}
func (f *Formatter) Run() (err error) {
changesMade := 0
start := time.Now()
results := make(chan processor.Result)
f.Log.Debug("Walking directory", slog.String("path", f.Dir))
go processor.Process(f.Dir, f.Process, f.WorkerCount, results)
var successCount, errorCount int
for r := range results {
if r.ChangesMade {
changesMade += 1
}
if r.Error != nil {
f.Log.Error(r.FileName, slog.Any("error", r.Error))
errorCount++
continue
}
f.Log.Debug(r.FileName, slog.Duration("duration", r.Duration))
successCount++
}
if f.FailIfChange && changesMade > 0 {
f.Log.Error("Templates were valid but not properly formatted", slog.Int("count", successCount+errorCount), slog.Int("changed", changesMade), slog.Int("errors", errorCount), slog.Duration("duration", time.Since(start)))
return fmt.Errorf("templates were not formatted properly")
}
f.Log.Info("Format Complete", slog.Int("count", successCount+errorCount), slog.Int("errors", errorCount), slog.Int("changed", changesMade), slog.Duration("duration", time.Since(start)))
if errorCount > 0 {
return fmt.Errorf("formatting failed")
}
return
}
type reader func() (fileName, src string, err error)
func readFromReader(r io.Reader, stdinFilepath string) func() (fileName, src string, err error) {
return func() (fileName, src string, err error) {
b, err := io.ReadAll(r)
if err != nil {
return "", "", fmt.Errorf("failed to read stdin: %w", err)
}
return stdinFilepath, string(b), nil
}
}
func readFromFile(name string) reader {
return func() (fileName, src string, err error) {
b, err := os.ReadFile(name)
if err != nil {
return "", "", fmt.Errorf("failed to read file %q: %w", fileName, err)
}
return name, string(b), nil
}
}
type writer func(fileName, tgt string) error
var mu sync.Mutex
func writeToWriter(w io.Writer) func(fileName, tgt string) error {
return func(fileName, tgt string) error {
mu.Lock()
defer mu.Unlock()
_, err := w.Write([]byte(tgt))
return err
}
}
func writeToFile(fileName, tgt string) error {
return atomic.WriteFile(fileName, bytes.NewBufferString(tgt))
}
func format(write writer, read reader, writeIfUnchanged bool) (err error, fileChanged bool) {
fileName, src, err := read()
if err != nil {
return err, false
}
t, err := parser.ParseString(src)
if err != nil {
return err, false
}
t.Filepath = fileName
t, err = imports.Process(t)
if err != nil {
return err, false
}
w := new(bytes.Buffer)
if err = t.Write(w); err != nil {
return fmt.Errorf("formatting error: %w", err), false
}
fileChanged = (src != w.String())
if !writeIfUnchanged && !fileChanged {
return nil, fileChanged
}
return write(fileName, w.String()), fileChanged
}
package generatecmd
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"path"
"path/filepath"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/a-h/templ"
"github.com/a-h/templ/cmd/templ/generatecmd/modcheck"
"github.com/a-h/templ/cmd/templ/generatecmd/proxy"
"github.com/a-h/templ/cmd/templ/generatecmd/run"
"github.com/a-h/templ/cmd/templ/generatecmd/watcher"
"github.com/a-h/templ/generator"
"github.com/cenkalti/backoff/v4"
"github.com/cli/browser"
"github.com/fsnotify/fsnotify"
)
func NewGenerate(log *slog.Logger, args Arguments) (g *Generate) {
g = &Generate{
Log: log,
Args: &args,
}
if g.Args.WorkerCount == 0 {
g.Args.WorkerCount = runtime.NumCPU()
}
return g
}
type Generate struct {
Log *slog.Logger
Args *Arguments
}
type GenerationEvent struct {
Event fsnotify.Event
GoUpdated bool
TextUpdated bool
}
func (cmd Generate) Run(ctx context.Context) (err error) {
if cmd.Args.NotifyProxy {
return proxy.NotifyProxy(cmd.Args.ProxyBind, cmd.Args.ProxyPort)
}
if cmd.Args.Watch && cmd.Args.FileName != "" {
return fmt.Errorf("cannot watch a single file, remove the -f or -watch flag")
}
writingToWriter := cmd.Args.FileWriter != nil
if cmd.Args.FileName == "" && writingToWriter {
return fmt.Errorf("only a single file can be output to stdout, add the -f flag to specify the file to generate code for")
}
// Default to writing to files.
if cmd.Args.FileWriter == nil {
cmd.Args.FileWriter = FileWriter
}
if cmd.Args.PPROFPort > 0 {
go func() {
_ = http.ListenAndServe(fmt.Sprintf("localhost:%d", cmd.Args.PPROFPort), nil)
}()
}
// Use absolute path.
if !path.IsAbs(cmd.Args.Path) {
cmd.Args.Path, err = filepath.Abs(cmd.Args.Path)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
}
// Configure generator.
var opts []generator.GenerateOpt
if cmd.Args.IncludeVersion {
opts = append(opts, generator.WithVersion(templ.Version()))
}
if cmd.Args.IncludeTimestamp {
opts = append(opts, generator.WithTimestamp(time.Now()))
}
// Check the version of the templ module.
if err := modcheck.Check(cmd.Args.Path); err != nil {
cmd.Log.Warn("templ version check: " + err.Error())
}
fseh := NewFSEventHandler(
cmd.Log,
cmd.Args.Path,
cmd.Args.Watch,
opts,
cmd.Args.GenerateSourceMapVisualisations,
cmd.Args.KeepOrphanedFiles,
cmd.Args.FileWriter,
cmd.Args.Lazy,
)
// If we're processing a single file, don't bother setting up the channels/multithreaing.
if cmd.Args.FileName != "" {
_, _, err = fseh.HandleEvent(ctx, fsnotify.Event{
Name: cmd.Args.FileName,
Op: fsnotify.Create,
})
return err
}
// Start timer.
start := time.Now()
// Create channels:
// For the initial filesystem walk and subsequent (optional) fsnotify events.
events := make(chan fsnotify.Event)
// Count of events currently being processed by the event handler.
var eventsWG sync.WaitGroup
// Used to check that the event handler has completed.
var eventHandlerWG sync.WaitGroup
// For errs from the watcher.
errs := make(chan error)
// Tracks whether errors occurred during the generation process.
var errorCount atomic.Int64
// For triggering actions after generation has completed.
postGeneration := make(chan *GenerationEvent, 256)
// Used to check that the post-generation handler has completed.
var postGenerationWG sync.WaitGroup
var postGenerationEventsWG sync.WaitGroup
// Waitgroup for the push process.
var pushHandlerWG sync.WaitGroup
// Start process to push events into the channel.
pushHandlerWG.Add(1)
go func() {
defer pushHandlerWG.Done()
defer close(events)
cmd.Log.Debug(
"Walking directory",
slog.String("path", cmd.Args.Path),
slog.Bool("devMode", cmd.Args.Watch),
)
if err := watcher.WalkFiles(ctx, cmd.Args.Path, events); err != nil {
cmd.Log.Error("WalkFiles failed, exiting", slog.Any("error", err))
errs <- FatalError{Err: fmt.Errorf("failed to walk files: %w", err)}
return
}
if !cmd.Args.Watch {
cmd.Log.Debug("Dev mode not enabled, process can finish early")
return
}
cmd.Log.Info("Watching files")
rw, err := watcher.Recursive(ctx, cmd.Args.Path, events, errs)
if err != nil {
cmd.Log.Error("Recursive watcher setup failed, exiting", slog.Any("error", err))
errs <- FatalError{Err: fmt.Errorf("failed to setup recursive watcher: %w", err)}
return
}
cmd.Log.Debug("Waiting for context to be cancelled to stop watching files")
<-ctx.Done()
cmd.Log.Debug("Context cancelled, closing watcher")
if err := rw.Close(); err != nil {
cmd.Log.Error("Failed to close watcher", slog.Any("error", err))
}
cmd.Log.Debug("Waiting for events to be processed")
eventsWG.Wait()
cmd.Log.Debug(
"All pending events processed, waiting for pending post-generation events to complete",
)
postGenerationEventsWG.Wait()
cmd.Log.Debug(
"All post-generation events processed, running walk again, but in production mode",
slog.Int64("errorCount", errorCount.Load()),
)
// Reset to reprocess all files in production mode.
fseh = NewFSEventHandler(
cmd.Log,
cmd.Args.Path,
false, // Force production mode.
opts,
cmd.Args.GenerateSourceMapVisualisations,
cmd.Args.KeepOrphanedFiles,
cmd.Args.FileWriter,
cmd.Args.Lazy,
)
errorCount.Store(0)
if err := watcher.WalkFiles(ctx, cmd.Args.Path, events); err != nil {
cmd.Log.Error("Post dev mode WalkFiles failed", slog.Any("error", err))
errs <- FatalError{Err: fmt.Errorf("failed to walk files: %w", err)}
return
}
}()
// Start process to handle events.
eventHandlerWG.Add(1)
sem := make(chan struct{}, cmd.Args.WorkerCount)
go func() {
defer eventHandlerWG.Done()
defer close(postGeneration)
cmd.Log.Debug("Starting event handler")
for event := range events {
eventsWG.Add(1)
sem <- struct{}{}
go func(event fsnotify.Event) {
cmd.Log.Debug("Processing file", slog.String("file", event.Name))
defer eventsWG.Done()
defer func() { <-sem }()
goUpdated, textUpdated, err := fseh.HandleEvent(ctx, event)
if err != nil {
cmd.Log.Error("Event handler failed", slog.Any("error", err))
errs <- err
}
if goUpdated || textUpdated {
postGeneration <- &GenerationEvent{
Event: event,
GoUpdated: goUpdated,
TextUpdated: textUpdated,
}
}
}(event)
}
// Wait for all events to be processed before closing.
eventsWG.Wait()
}()
// Start process to handle post-generation events.
var updates int
postGenerationWG.Add(1)
var firstPostGenerationExecuted bool
go func() {
defer close(errs)
defer postGenerationWG.Done()
cmd.Log.Debug("Starting post-generation handler")
timeout := time.NewTimer(time.Hour * 24 * 365)
var goUpdated, textUpdated bool
var p *proxy.Handler
for {
select {
case ge := <-postGeneration:
if ge == nil {
cmd.Log.Debug("Post-generation event channel closed, exiting")
return
}
goUpdated = goUpdated || ge.GoUpdated
textUpdated = textUpdated || ge.TextUpdated
if goUpdated || textUpdated {
updates++
}
// Reset timer.
if !timeout.Stop() {
<-timeout.C
}
timeout.Reset(time.Millisecond * 100)
case <-timeout.C:
if !goUpdated && !textUpdated {
// Nothing to process, reset timer and wait again.
timeout.Reset(time.Hour * 24 * 365)
break
}
postGenerationEventsWG.Add(1)
if cmd.Args.Command != "" && goUpdated {
cmd.Log.Debug("Executing command", slog.String("command", cmd.Args.Command))
if _, err := run.Run(ctx, cmd.Args.Path, cmd.Args.Command); err != nil {
cmd.Log.Error("Error executing command", slog.Any("error", err))
}
}
if !firstPostGenerationExecuted {
cmd.Log.Debug("First post-generation event received, starting proxy")
firstPostGenerationExecuted = true
p, err = cmd.StartProxy(ctx)
if err != nil {
cmd.Log.Error("Failed to start proxy", slog.Any("error", err))
}
}
// Send server-sent event.
if p != nil && (textUpdated || goUpdated) {
cmd.Log.Debug("Sending reload event")
p.SendSSE("message", "reload")
}
postGenerationEventsWG.Done()
// Reset timer.
timeout.Reset(time.Millisecond * 100)
textUpdated = false
goUpdated = false
}
}
}()
// Read errors.
for err := range errs {
if err == nil {
continue
}
if errors.Is(err, FatalError{}) {
cmd.Log.Debug("Fatal error, exiting")
return err
}
cmd.Log.Error("Error", slog.Any("error", err))
errorCount.Add(1)
}
// Wait for everything to complete.
cmd.Log.Debug("Waiting for push handler to complete")
pushHandlerWG.Wait()
cmd.Log.Debug("Waiting for event handler to complete")
eventHandlerWG.Wait()
cmd.Log.Debug("Waiting for post-generation handler to complete")
postGenerationWG.Wait()
if cmd.Args.Command != "" {
cmd.Log.Debug("Killing command", slog.String("command", cmd.Args.Command))
if err := run.KillAll(); err != nil {
cmd.Log.Error("Error killing command", slog.Any("error", err))
}
}
// Check for errors after everything has completed.
if errorCount.Load() > 0 {
return fmt.Errorf("generation completed with %d errors", errorCount.Load())
}
cmd.Log.Info(
"Complete",
slog.Int("updates", updates),
slog.Duration("duration", time.Since(start)),
)
return nil
}
func (cmd *Generate) StartProxy(ctx context.Context) (p *proxy.Handler, err error) {
if cmd.Args.Proxy == "" {
cmd.Log.Debug("No proxy URL specified, not starting proxy")
return nil, nil
}
var target *url.URL
target, err = url.Parse(cmd.Args.Proxy)
if err != nil {
return nil, FatalError{Err: fmt.Errorf("failed to parse proxy URL: %w", err)}
}
if cmd.Args.ProxyPort == 0 {
cmd.Args.ProxyPort = 7331
}
if cmd.Args.ProxyBind == "" {
cmd.Args.ProxyBind = "127.0.0.1"
}
p = proxy.New(cmd.Log, cmd.Args.ProxyBind, cmd.Args.ProxyPort, target)
go func() {
cmd.Log.Info("Proxying", slog.String("from", p.URL), slog.String("to", p.Target.String()))
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", cmd.Args.ProxyBind, cmd.Args.ProxyPort), p); err != nil {
cmd.Log.Error("Proxy failed", slog.Any("error", err))
}
}()
if !cmd.Args.OpenBrowser {
cmd.Log.Debug("Not opening browser")
return p, nil
}
go func() {
cmd.Log.Debug("Waiting for proxy to be ready", slog.String("url", p.URL))
backoff := backoff.NewExponentialBackOff()
backoff.InitialInterval = time.Second
var client http.Client
client.Timeout = 1 * time.Second
for {
if _, err := client.Get(p.URL); err == nil {
break
}
d := backoff.NextBackOff()
cmd.Log.Debug(
"Proxy not ready, retrying",
slog.String("url", p.URL),
slog.Any("backoff", d),
)
time.Sleep(d)
}
if err := browser.OpenURL(p.URL); err != nil {
cmd.Log.Error("Failed to open browser", slog.Any("error", err))
}
}()
return p, nil
}
package generatecmd
import (
"bufio"
"bytes"
"context"
"crypto/sha256"
"fmt"
"go/format"
"go/scanner"
"go/token"
"io"
"log/slog"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
"github.com/a-h/templ/cmd/templ/visualize"
"github.com/a-h/templ/generator"
"github.com/a-h/templ/parser/v2"
"github.com/fsnotify/fsnotify"
)
type FileWriterFunc func(name string, contents []byte) error
func FileWriter(fileName string, contents []byte) error {
return os.WriteFile(fileName, contents, 0o644)
}
func WriterFileWriter(w io.Writer) FileWriterFunc {
return func(_ string, contents []byte) error {
_, err := w.Write(contents)
return err
}
}
func NewFSEventHandler(
log *slog.Logger,
dir string,
devMode bool,
genOpts []generator.GenerateOpt,
genSourceMapVis bool,
keepOrphanedFiles bool,
fileWriter FileWriterFunc,
lazy bool,
) *FSEventHandler {
if !path.IsAbs(dir) {
dir, _ = filepath.Abs(dir)
}
fseh := &FSEventHandler{
Log: log,
dir: dir,
fileNameToLastModTime: make(map[string]time.Time),
fileNameToLastModTimeMutex: &sync.Mutex{},
fileNameToError: make(map[string]struct{}),
fileNameToErrorMutex: &sync.Mutex{},
hashes: make(map[string][sha256.Size]byte),
hashesMutex: &sync.Mutex{},
genOpts: genOpts,
genSourceMapVis: genSourceMapVis,
DevMode: devMode,
keepOrphanedFiles: keepOrphanedFiles,
writer: fileWriter,
lazy: lazy,
}
if devMode {
fseh.genOpts = append(fseh.genOpts, generator.WithExtractStrings())
}
return fseh
}
type FSEventHandler struct {
Log *slog.Logger
// dir is the root directory being processed.
dir string
fileNameToLastModTime map[string]time.Time
fileNameToLastModTimeMutex *sync.Mutex
fileNameToError map[string]struct{}
fileNameToErrorMutex *sync.Mutex
hashes map[string][sha256.Size]byte
hashesMutex *sync.Mutex
genOpts []generator.GenerateOpt
genSourceMapVis bool
DevMode bool
Errors []error
keepOrphanedFiles bool
writer func(string, []byte) error
lazy bool
}
func (h *FSEventHandler) HandleEvent(ctx context.Context, event fsnotify.Event) (goUpdated, textUpdated bool, err error) {
// Handle _templ.go files.
if !event.Has(fsnotify.Remove) && strings.HasSuffix(event.Name, "_templ.go") {
_, err = os.Stat(strings.TrimSuffix(event.Name, "_templ.go") + ".templ")
if !os.IsNotExist(err) {
return false, false, err
}
// File is orphaned.
if h.keepOrphanedFiles {
return false, false, nil
}
h.Log.Debug("Deleting orphaned Go file", slog.String("file", event.Name))
if err = os.Remove(event.Name); err != nil {
h.Log.Warn("Failed to remove orphaned file", slog.Any("error", err))
}
return true, false, nil
}
// Handle _templ.txt files.
if !event.Has(fsnotify.Remove) && strings.HasSuffix(event.Name, "_templ.txt") {
if h.DevMode {
// Don't delete the file if we're in dev mode, but mark that text was updated.
return false, true, nil
}
h.Log.Debug("Deleting watch mode file", slog.String("file", event.Name))
if err = os.Remove(event.Name); err != nil {
h.Log.Warn("Failed to remove watch mode text file", slog.Any("error", err))
return false, false, nil
}
return false, false, nil
}
// Handle .templ files.
if !strings.HasSuffix(event.Name, ".templ") {
return false, false, nil
}
// If the file hasn't been updated since the last time we processed it, ignore it.
lastModTime, updatedModTime := h.UpsertLastModTime(event.Name)
if !updatedModTime {
h.Log.Debug("Skipping file because it wasn't updated", slog.String("file", event.Name))
return false, false, nil
}
// If the go file is newer than the templ file, skip generation, because it's up-to-date.
if h.lazy && goFileIsUpToDate(event.Name, lastModTime) {
h.Log.Debug("Skipping file because the Go file is up-to-date", slog.String("file", event.Name))
return false, false, nil
}
// Start a processor.
start := time.Now()
goUpdated, textUpdated, diag, err := h.generate(ctx, event.Name)
if err != nil {
h.Log.Error(
"Error generating code",
slog.String("file", event.Name),
slog.Any("error", err),
)
h.SetError(event.Name, true)
return goUpdated, textUpdated, fmt.Errorf("failed to generate code for %q: %w", event.Name, err)
}
if len(diag) > 0 {
for _, d := range diag {
h.Log.Warn(d.Message,
slog.String("from", fmt.Sprintf("%d:%d", d.Range.From.Line, d.Range.From.Col)),
slog.String("to", fmt.Sprintf("%d:%d", d.Range.To.Line, d.Range.To.Col)),
)
}
return
}
if errorCleared, errorCount := h.SetError(event.Name, false); errorCleared {
h.Log.Info("Error cleared", slog.String("file", event.Name), slog.Int("errors", errorCount))
}
h.Log.Debug("Generated code", slog.String("file", event.Name), slog.Duration("in", time.Since(start)))
return goUpdated, textUpdated, nil
}
func goFileIsUpToDate(templFileName string, templFileLastMod time.Time) (upToDate bool) {
goFileName := strings.TrimSuffix(templFileName, ".templ") + "_templ.go"
goFileInfo, err := os.Stat(goFileName)
if err != nil {
return false
}
return goFileInfo.ModTime().After(templFileLastMod)
}
func (h *FSEventHandler) SetError(fileName string, hasError bool) (previouslyHadError bool, errorCount int) {
h.fileNameToErrorMutex.Lock()
defer h.fileNameToErrorMutex.Unlock()
_, previouslyHadError = h.fileNameToError[fileName]
delete(h.fileNameToError, fileName)
if hasError {
h.fileNameToError[fileName] = struct{}{}
}
return previouslyHadError, len(h.fileNameToError)
}
func (h *FSEventHandler) UpsertLastModTime(fileName string) (modTime time.Time, updated bool) {
fileInfo, err := os.Stat(fileName)
if err != nil {
return modTime, false
}
h.fileNameToLastModTimeMutex.Lock()
defer h.fileNameToLastModTimeMutex.Unlock()
previousModTime := h.fileNameToLastModTime[fileName]
currentModTime := fileInfo.ModTime()
if !currentModTime.After(previousModTime) {
return currentModTime, false
}
h.fileNameToLastModTime[fileName] = currentModTime
return currentModTime, true
}
func (h *FSEventHandler) UpsertHash(fileName string, hash [sha256.Size]byte) (updated bool) {
h.hashesMutex.Lock()
defer h.hashesMutex.Unlock()
lastHash := h.hashes[fileName]
if lastHash == hash {
return false
}
h.hashes[fileName] = hash
return true
}
// generate Go code for a single template.
// If a basePath is provided, the filename included in error messages is relative to it.
func (h *FSEventHandler) generate(ctx context.Context, fileName string) (goUpdated, textUpdated bool, diagnostics []parser.Diagnostic, err error) {
t, err := parser.Parse(fileName)
if err != nil {
return false, false, nil, fmt.Errorf("%s parsing error: %w", fileName, err)
}
targetFileName := strings.TrimSuffix(fileName, ".templ") + "_templ.go"
// Only use relative filenames to the basepath for filenames in runtime error messages.
absFilePath, err := filepath.Abs(fileName)
if err != nil {
return false, false, nil, fmt.Errorf("failed to get absolute path for %q: %w", fileName, err)
}
relFilePath, err := filepath.Rel(h.dir, absFilePath)
if err != nil {
return false, false, nil, fmt.Errorf("failed to get relative path for %q: %w", fileName, err)
}
// Convert Windows file paths to Unix-style for consistency.
relFilePath = filepath.ToSlash(relFilePath)
var b bytes.Buffer
sourceMap, literals, err := generator.Generate(t, &b, append(h.genOpts, generator.WithFileName(relFilePath))...)
if err != nil {
return false, false, nil, fmt.Errorf("%s generation error: %w", fileName, err)
}
formattedGoCode, err := format.Source(b.Bytes())
if err != nil {
err = remapErrorList(err, sourceMap, fileName)
return false, false, nil, fmt.Errorf("% source formatting error %w", fileName, err)
}
// Hash output, and write out the file if the goCodeHash has changed.
goCodeHash := sha256.Sum256(formattedGoCode)
if h.UpsertHash(targetFileName, goCodeHash) {
goUpdated = true
if err = h.writer(targetFileName, formattedGoCode); err != nil {
return false, false, nil, fmt.Errorf("failed to write target file %q: %w", targetFileName, err)
}
}
// Add the txt file if it has changed.
if len(literals) > 0 {
txtFileName := strings.TrimSuffix(fileName, ".templ") + "_templ.txt"
txtHash := sha256.Sum256([]byte(literals))
if h.UpsertHash(txtFileName, txtHash) {
textUpdated = true
if err = os.WriteFile(txtFileName, []byte(literals), 0o644); err != nil {
return false, false, nil, fmt.Errorf("failed to write string literal file %q: %w", txtFileName, err)
}
}
}
parsedDiagnostics, err := parser.Diagnose(t)
if err != nil {
return goUpdated, textUpdated, nil, fmt.Errorf("%s diagnostics error: %w", fileName, err)
}
if h.genSourceMapVis {
err = generateSourceMapVisualisation(ctx, fileName, targetFileName, sourceMap)
}
return goUpdated, textUpdated, parsedDiagnostics, err
}
// Takes an error from the formatter and attempts to convert the positions reported in the target file to their positions
// in the source file.
func remapErrorList(err error, sourceMap *parser.SourceMap, fileName string) error {
list, ok := err.(scanner.ErrorList)
if !ok || len(list) == 0 {
return err
}
for i, e := range list {
// The positions in the source map are off by one line because of the package definition.
srcPos, ok := sourceMap.SourcePositionFromTarget(uint32(e.Pos.Line-1), uint32(e.Pos.Column))
if !ok {
continue
}
list[i].Pos = token.Position{
Filename: fileName,
Offset: int(srcPos.Index),
Line: int(srcPos.Line) + 1,
Column: int(srcPos.Col),
}
}
return list
}
func generateSourceMapVisualisation(ctx context.Context, templFileName, goFileName string, sourceMap *parser.SourceMap) error {
if err := ctx.Err(); err != nil {
return err
}
var templContents, goContents []byte
var templErr, goErr error
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
templContents, templErr = os.ReadFile(templFileName)
}()
go func() {
defer wg.Done()
goContents, goErr = os.ReadFile(goFileName)
}()
wg.Wait()
if templErr != nil {
return templErr
}
if goErr != nil {
return templErr
}
targetFileName := strings.TrimSuffix(templFileName, ".templ") + "_templ_sourcemap.html"
w, err := os.Create(targetFileName)
if err != nil {
return fmt.Errorf("%s sourcemap visualisation error: %w", templFileName, err)
}
defer w.Close()
b := bufio.NewWriter(w)
defer b.Flush()
return visualize.HTML(templFileName, string(templContents), string(goContents), sourceMap).Render(ctx, b)
}
package generatecmd
type FatalError struct {
Err error
}
func (e FatalError) Error() string {
return e.Err.Error()
}
func (e FatalError) Unwrap() error {
return e.Err
}
func (e FatalError) Is(target error) bool {
_, ok := target.(FatalError)
return ok
}
func (e FatalError) As(target interface{}) bool {
_, ok := target.(*FatalError)
return ok
}
package generatecmd
import (
"context"
_ "embed"
"log/slog"
_ "net/http/pprof"
)
type Arguments struct {
FileName string
FileWriter FileWriterFunc
Path string
Watch bool
OpenBrowser bool
Command string
ProxyBind string
ProxyPort int
Proxy string
NotifyProxy bool
WorkerCount int
GenerateSourceMapVisualisations bool
IncludeVersion bool
IncludeTimestamp bool
// PPROFPort is the port to run the pprof server on.
PPROFPort int
KeepOrphanedFiles bool
Lazy bool
}
func Run(ctx context.Context, log *slog.Logger, args Arguments) (err error) {
return NewGenerate(log, args).Run(ctx)
}
package modcheck
import (
"fmt"
"os"
"path/filepath"
"github.com/a-h/templ"
"golang.org/x/mod/modfile"
"golang.org/x/mod/semver"
)
// WalkUp the directory tree, starting at dir, until we find a directory containing
// a go.mod file.
func WalkUp(dir string) (string, error) {
dir, err := filepath.Abs(dir)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}
var modFile string
for {
modFile = filepath.Join(dir, "go.mod")
_, err := os.Stat(modFile)
if err != nil && !os.IsNotExist(err) {
return "", fmt.Errorf("failed to stat go.mod file: %w", err)
}
if os.IsNotExist(err) {
// Move up.
prev := dir
dir = filepath.Dir(dir)
if dir == prev {
break
}
continue
}
break
}
// No file found.
if modFile == "" {
return dir, fmt.Errorf("could not find go.mod file")
}
return dir, nil
}
func Check(dir string) error {
dir, err := WalkUp(dir)
if err != nil {
return err
}
// Found a go.mod file.
// Read it and find the templ version.
modFile := filepath.Join(dir, "go.mod")
m, err := os.ReadFile(modFile)
if err != nil {
return fmt.Errorf("failed to read go.mod file: %w", err)
}
mf, err := modfile.Parse(modFile, m, nil)
if err != nil {
return fmt.Errorf("failed to parse go.mod file: %w", err)
}
if mf.Module.Mod.Path == "github.com/a-h/templ" {
// The go.mod file is for templ itself.
return nil
}
for _, r := range mf.Require {
if r.Mod.Path == "github.com/a-h/templ" {
cmp := semver.Compare(r.Mod.Version, templ.Version())
if cmp < 0 {
return fmt.Errorf("generator %v is newer than templ version %v found in go.mod file, consider running `go get -u github.com/a-h/templ` to upgrade", templ.Version(), r.Mod.Version)
}
if cmp > 0 {
return fmt.Errorf("generator %v is older than templ version %v found in go.mod file, consider upgrading templ CLI", templ.Version(), r.Mod.Version)
}
return nil
}
}
return fmt.Errorf("templ not found in go.mod file, run `go get github.com/a-h/templ` to install it")
}
package proxy
import (
"bytes"
"compress/gzip"
"fmt"
"html"
"io"
stdlog "log"
"log/slog"
"math"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/a-h/templ/cmd/templ/generatecmd/sse"
"github.com/andybalholm/brotli"
_ "embed"
)
//go:embed script.js
var script string
type Handler struct {
log *slog.Logger
URL string
Target *url.URL
p *httputil.ReverseProxy
sse *sse.Handler
}
func getScriptTag(nonce string) string {
if nonce != "" {
var sb strings.Builder
sb.WriteString(`<script src="/_templ/reload/script.js" nonce="`)
sb.WriteString(html.EscapeString(nonce))
sb.WriteString(`"></script>`)
return sb.String()
}
return `<script src="/_templ/reload/script.js"></script>`
}
func insertScriptTagIntoBody(nonce, body string) (updated string) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(body))
if err != nil {
return strings.Replace(body, "</body>", getScriptTag(nonce)+"</body>", -1)
}
doc.Find("body").AppendHtml(getScriptTag(nonce))
r, err := doc.Html()
if err != nil {
return strings.Replace(body, "</body>", getScriptTag(nonce)+"</body>", -1)
}
return r
}
type passthroughWriteCloser struct {
io.Writer
}
func (pwc passthroughWriteCloser) Close() error {
return nil
}
const unsupportedContentEncoding = "Unsupported content encoding, hot reload script not inserted."
func (h *Handler) modifyResponse(r *http.Response) error {
log := h.log.With(slog.String("url", r.Request.URL.String()))
if r.Header.Get("templ-skip-modify") == "true" {
log.Debug("Skipping response modification because templ-skip-modify header is set")
return nil
}
if contentType := r.Header.Get("Content-Type"); !strings.HasPrefix(contentType, "text/html") {
log.Debug("Skipping response modification because content type is not text/html", slog.String("content-type", contentType))
return nil
}
// Set up readers and writers.
newReader := func(in io.Reader) (out io.Reader, err error) {
return in, nil
}
newWriter := func(out io.Writer) io.WriteCloser {
return passthroughWriteCloser{out}
}
switch r.Header.Get("Content-Encoding") {
case "gzip":
newReader = func(in io.Reader) (out io.Reader, err error) {
return gzip.NewReader(in)
}
newWriter = func(out io.Writer) io.WriteCloser {
return gzip.NewWriter(out)
}
case "br":
newReader = func(in io.Reader) (out io.Reader, err error) {
return brotli.NewReader(in), nil
}
newWriter = func(out io.Writer) io.WriteCloser {
return brotli.NewWriter(out)
}
case "":
log.Debug("No content encoding header found")
default:
h.log.Warn(unsupportedContentEncoding, slog.String("encoding", r.Header.Get("Content-Encoding")))
}
// Read the encoded body.
encr, err := newReader(r.Body)
if err != nil {
return err
}
defer r.Body.Close()
body, err := io.ReadAll(encr)
if err != nil {
return err
}
// Update it.
csp := r.Header.Get("Content-Security-Policy")
updated := insertScriptTagIntoBody(parseNonce(csp), string(body))
if log.Enabled(r.Request.Context(), slog.LevelDebug) {
if len(updated) == len(body) {
log.Debug("Reload script not inserted")
} else {
log.Debug("Reload script inserted")
}
}
// Encode the response.
var buf bytes.Buffer
encw := newWriter(&buf)
_, err = encw.Write([]byte(updated))
if err != nil {
return err
}
err = encw.Close()
if err != nil {
return err
}
// Update the response.
r.Body = io.NopCloser(&buf)
r.ContentLength = int64(buf.Len())
r.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
return nil
}
func parseNonce(csp string) (nonce string) {
outer:
for _, rawDirective := range strings.Split(csp, ";") {
parts := strings.Fields(rawDirective)
if len(parts) < 2 {
continue
}
if parts[0] != "script-src" {
continue
}
for _, source := range parts[1:] {
source = strings.TrimPrefix(source, "'")
source = strings.TrimSuffix(source, "'")
if strings.HasPrefix(source, "nonce-") {
nonce = source[6:]
break outer
}
}
}
return nonce
}
func New(log *slog.Logger, bind string, port int, target *url.URL) (h *Handler) {
p := httputil.NewSingleHostReverseProxy(target)
p.ErrorLog = stdlog.New(os.Stderr, "Proxy to target error: ", 0)
p.Transport = &roundTripper{
maxRetries: 20,
initialDelay: 100 * time.Millisecond,
backoffExponent: 1.5,
}
h = &Handler{
log: log,
URL: fmt.Sprintf("http://%s:%d", bind, port),
Target: target,
p: p,
sse: sse.New(),
}
p.ModifyResponse = h.modifyResponse
return h
}
func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/_templ/reload/script.js" {
// Provides a script that reloads the page.
w.Header().Add("Content-Type", "text/javascript")
_, err := io.WriteString(w, script)
if err != nil {
fmt.Printf("failed to write script: %v\n", err)
}
return
}
if r.URL.Path == "/_templ/reload/events" {
switch r.Method {
case http.MethodGet:
// Provides a list of messages including a reload message.
p.sse.ServeHTTP(w, r)
return
case http.MethodPost:
// Send a reload message to all connected clients.
p.sse.Send("message", "reload")
return
}
http.Error(w, "only GET or POST method allowed", http.StatusMethodNotAllowed)
return
}
p.p.ServeHTTP(w, r)
}
func (p *Handler) SendSSE(eventType string, data string) {
p.sse.Send(eventType, data)
}
type roundTripper struct {
maxRetries int
initialDelay time.Duration
backoffExponent float64
}
func (rt *roundTripper) setShouldSkipResponseModificationHeader(r *http.Request, resp *http.Response) {
// Instruct the modifyResponse function to skip modifying the response if the
// HTTP request has come from HTMX.
if r.Header.Get("HX-Request") != "true" {
return
}
resp.Header.Set("templ-skip-modify", "true")
}
func (rt *roundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
// Read and buffer the body.
var bodyBytes []byte
if r.Body != nil && r.Body != http.NoBody {
var err error
bodyBytes, err = io.ReadAll(r.Body)
if err != nil {
return nil, err
}
r.Body.Close()
}
// Retry logic.
var resp *http.Response
var err error
for retries := 0; retries < rt.maxRetries; retries++ {
// Clone the request and set the body.
req := r.Clone(r.Context())
if bodyBytes != nil {
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
}
// Execute the request.
resp, err = http.DefaultTransport.RoundTrip(req)
if err != nil {
time.Sleep(rt.initialDelay * time.Duration(math.Pow(rt.backoffExponent, float64(retries))))
continue
}
rt.setShouldSkipResponseModificationHeader(r, resp)
return resp, nil
}
return nil, fmt.Errorf("max retries reached: %q", r.URL.String())
}
func NotifyProxy(host string, port int) error {
urlStr := fmt.Sprintf("http://%s:%d/_templ/reload/events", host, port)
req, err := http.NewRequest(http.MethodPost, urlStr, nil)
if err != nil {
return err
}
_, err = http.DefaultClient.Do(req)
return err
}
//go:build unix
package run
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"time"
)
var (
m = &sync.Mutex{}
running = map[string]*exec.Cmd{}
)
func KillAll() (err error) {
m.Lock()
defer m.Unlock()
var errs []error
for _, cmd := range running {
if err := kill(cmd); err != nil {
errs = append(errs, fmt.Errorf("failed to kill process %d: %w", cmd.Process.Pid, err))
}
}
running = map[string]*exec.Cmd{}
return errors.Join(errs...)
}
func kill(cmd *exec.Cmd) (err error) {
errs := make([]error, 4)
errs[0] = ignoreExited(cmd.Process.Signal(syscall.SIGINT))
errs[1] = ignoreExited(cmd.Process.Signal(syscall.SIGTERM))
errs[2] = ignoreExited(cmd.Wait())
errs[3] = ignoreExited(syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL))
return errors.Join(errs...)
}
func ignoreExited(err error) error {
if errors.Is(err, syscall.ESRCH) {
return nil
}
// Ignore *exec.ExitError
if _, ok := err.(*exec.ExitError); ok {
return nil
}
return err
}
func Run(ctx context.Context, workingDir, input string) (cmd *exec.Cmd, err error) {
m.Lock()
defer m.Unlock()
cmd, ok := running[input]
if ok {
if err := kill(cmd); err != nil {
return cmd, fmt.Errorf("failed to kill process %d: %w", cmd.Process.Pid, err)
}
delete(running, input)
}
parts := strings.Fields(input)
executable := parts[0]
args := []string{}
if len(parts) > 1 {
args = append(args, parts[1:]...)
}
cmd = exec.CommandContext(ctx, executable, args...)
// Wait for the process to finish gracefully before termination.
cmd.WaitDelay = time.Second * 3
cmd.Env = os.Environ()
cmd.Dir = workingDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
running[input] = cmd
err = cmd.Start()
return
}
package sse
import (
_ "embed"
"fmt"
"net/http"
"sync"
"sync/atomic"
"time"
)
func New() *Handler {
return &Handler{
m: new(sync.Mutex),
requests: map[int64]chan event{},
}
}
type Handler struct {
m *sync.Mutex
counter int64
requests map[int64]chan event
}
type event struct {
Type string
Data string
}
// Send an event to all connected clients.
func (s *Handler) Send(eventType string, data string) {
s.m.Lock()
defer s.m.Unlock()
for _, f := range s.requests {
f := f
go func(f chan event) {
f <- event{
Type: eventType,
Data: data,
}
}(f)
}
}
func (s *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
id := atomic.AddInt64(&s.counter, 1)
s.m.Lock()
events := make(chan event)
s.requests[id] = events
s.m.Unlock()
defer func() {
s.m.Lock()
defer s.m.Unlock()
delete(s.requests, id)
close(events)
}()
timer := time.NewTimer(0)
loop:
for {
select {
case <-timer.C:
if _, err := fmt.Fprintf(w, "event: message\ndata: ping\n\n"); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
timer.Reset(time.Second * 5)
case e := <-events:
if _, err := fmt.Fprintf(w, "event: %s\ndata: %s\n\n", e.Type, e.Data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
case <-r.Context().Done():
break loop
}
w.(http.Flusher).Flush()
}
}
package watcher
import (
"context"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
func Recursive(
ctx context.Context,
path string,
out chan fsnotify.Event,
errors chan error,
) (w *RecursiveWatcher, err error) {
fsnw, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
w = &RecursiveWatcher{
ctx: ctx,
w: fsnw,
Events: out,
Errors: errors,
timers: make(map[timerKey]*time.Timer),
}
go w.loop()
return w, w.Add(path)
}
// WalkFiles walks the file tree rooted at path, sending a Create event for each
// file it encounters.
func WalkFiles(ctx context.Context, path string, out chan fsnotify.Event) (err error) {
rootPath := path
fileSystem := os.DirFS(rootPath)
return fs.WalkDir(fileSystem, ".", func(path string, info os.DirEntry, err error) error {
if err != nil {
return nil
}
absPath, err := filepath.Abs(filepath.Join(rootPath, path))
if err != nil {
return nil
}
if info.IsDir() && shouldSkipDir(absPath) {
return filepath.SkipDir
}
if !shouldIncludeFile(absPath) {
return nil
}
out <- fsnotify.Event{
Name: absPath,
Op: fsnotify.Create,
}
return nil
})
}
func shouldIncludeFile(name string) bool {
if strings.HasSuffix(name, ".templ") {
return true
}
if strings.HasSuffix(name, "_templ.go") {
return true
}
if strings.HasSuffix(name, "_templ.txt") {
return true
}
return false
}
type RecursiveWatcher struct {
ctx context.Context
w *fsnotify.Watcher
Events chan fsnotify.Event
Errors chan error
timerMu sync.Mutex
timers map[timerKey]*time.Timer
}
type timerKey struct {
name string
op fsnotify.Op
}
func timerKeyFromEvent(event fsnotify.Event) timerKey {
return timerKey{
name: event.Name,
op: event.Op,
}
}
func (w *RecursiveWatcher) Close() error {
return w.w.Close()
}
func (w *RecursiveWatcher) loop() {
for {
select {
case <-w.ctx.Done():
return
case event, ok := <-w.w.Events:
if !ok {
return
}
if event.Has(fsnotify.Create) {
if err := w.Add(event.Name); err != nil {
w.Errors <- err
}
}
// Only notify on templ related files.
if !shouldIncludeFile(event.Name) {
continue
}
tk := timerKeyFromEvent(event)
w.timerMu.Lock()
t, ok := w.timers[tk]
w.timerMu.Unlock()
if !ok {
t = time.AfterFunc(100*time.Millisecond, func() {
w.Events <- event
})
w.timerMu.Lock()
w.timers[tk] = t
w.timerMu.Unlock()
continue
}
t.Reset(100 * time.Millisecond)
case err, ok := <-w.w.Errors:
if !ok {
return
}
w.Errors <- err
}
}
}
func (w *RecursiveWatcher) Add(dir string) error {
return filepath.WalkDir(dir, func(dir string, info os.DirEntry, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
return nil
}
if shouldSkipDir(dir) {
return filepath.SkipDir
}
return w.w.Add(dir)
})
}
func shouldSkipDir(dir string) bool {
if dir == "." {
return false
}
if dir == "vendor" || dir == "node_modules" {
return true
}
_, name := path.Split(dir)
// These directories are ignored by the Go tool.
if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
return true
}
return false
}
package imports
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"path"
"slices"
"strconv"
"strings"
goparser "go/parser"
"golang.org/x/sync/errgroup"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/imports"
"github.com/a-h/templ/generator"
"github.com/a-h/templ/parser/v2"
)
var internalImports = []string{"github.com/a-h/templ", "github.com/a-h/templ/runtime"}
func convertTemplToGoURI(templURI string) (isTemplFile bool, goURI string) {
base, fileName := path.Split(templURI)
if !strings.HasSuffix(fileName, ".templ") {
return
}
return true, base + (strings.TrimSuffix(fileName, ".templ") + "_templ.go")
}
var fset = token.NewFileSet()
func updateImports(name, src string) (updated []*ast.ImportSpec, err error) {
// Apply auto imports.
updatedGoCode, err := imports.Process(name, []byte(src), nil)
if err != nil {
return updated, fmt.Errorf("failed to process go code %q: %w", src, err)
}
// Get updated imports.
gofile, err := goparser.ParseFile(fset, name, updatedGoCode, goparser.ImportsOnly)
if err != nil {
return updated, fmt.Errorf("failed to get imports from updated go code: %w", err)
}
for _, imp := range gofile.Imports {
if !slices.Contains(internalImports, strings.Trim(imp.Path.Value, "\"")) {
updated = append(updated, imp)
}
}
return updated, nil
}
func Process(t parser.TemplateFile) (parser.TemplateFile, error) {
if t.Filepath == "" {
return t, nil
}
isTemplFile, fileName := convertTemplToGoURI(t.Filepath)
if !isTemplFile {
return t, fmt.Errorf("invalid filepath: %s", t.Filepath)
}
// The first node always contains existing imports.
// If there isn't one, create it.
if len(t.Nodes) == 0 {
t.Nodes = append(t.Nodes, parser.TemplateFileGoExpression{})
}
// If there is one, ensure it is a Go expression.
if _, ok := t.Nodes[0].(parser.TemplateFileGoExpression); !ok {
t.Nodes = append([]parser.TemplateFileNode{parser.TemplateFileGoExpression{}}, t.Nodes...)
}
// Find all existing imports.
importsNode := t.Nodes[0].(parser.TemplateFileGoExpression)
// Generate code.
gw := bytes.NewBuffer(nil)
var updatedImports []*ast.ImportSpec
var eg errgroup.Group
eg.Go(func() (err error) {
if _, _, err := generator.Generate(t, gw); err != nil {
return fmt.Errorf("failed to generate go code: %w", err)
}
updatedImports, err = updateImports(fileName, gw.String())
if err != nil {
return fmt.Errorf("failed to get imports from generated go code: %w", err)
}
return nil
})
var firstGoNodeInTemplate *ast.File
// Update the template with the imports.
// Ensure that there is a Go expression to add the imports to as the first node.
eg.Go(func() (err error) {
firstGoNodeInTemplate, err = goparser.ParseFile(fset, fileName, t.Package.Expression.Value+"\n"+importsNode.Expression.Value, goparser.AllErrors|goparser.ParseComments)
if err != nil {
return fmt.Errorf("failed to parse imports section: %w", err)
}
return nil
})
// Wait for completion of both parts.
if err := eg.Wait(); err != nil {
return t, err
}
// Delete all the existing imports.
for _, imp := range firstGoNodeInTemplate.Imports {
name, path, err := getImportDetails(imp)
if err != nil {
return t, err
}
astutil.DeleteNamedImport(fset, firstGoNodeInTemplate, name, path)
}
// Add imports, if there are any to add.
for _, imp := range updatedImports {
name, path, err := getImportDetails(imp)
if err != nil {
return t, err
}
astutil.AddNamedImport(fset, firstGoNodeInTemplate, name, path)
}
// Write out the Go code with the imports.
updatedGoCode := new(strings.Builder)
err := format.Node(updatedGoCode, fset, firstGoNodeInTemplate)
if err != nil {
return t, fmt.Errorf("failed to write updated go code: %w", err)
}
// Remove the package statement from the node, by cutting the first line of the file.
importsNode.Expression.Value = strings.TrimSpace(strings.SplitN(updatedGoCode.String(), "\n", 2)[1])
if len(updatedImports) == 0 && importsNode.Expression.Value == "" {
t.Nodes = t.Nodes[1:]
return t, nil
}
t.Nodes[0] = importsNode
return t, nil
}
func getImportDetails(imp *ast.ImportSpec) (name, importPath string, err error) {
if imp.Name != nil {
name = imp.Name.Name
}
if imp.Path != nil {
importPath, err = strconv.Unquote(imp.Path.Value)
if err != nil {
err = fmt.Errorf("failed to unquote package path %s: %w", imp.Path.Value, err)
return
}
}
return name, importPath, nil
}
package infocmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"os"
"os/exec"
"runtime"
"strings"
"github.com/a-h/templ"
"github.com/a-h/templ/cmd/templ/lspcmd/pls"
)
type Arguments struct {
JSON bool `flag:"json" help:"Output info as JSON."`
}
type Info struct {
OS struct {
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
} `json:"os"`
Go ToolInfo `json:"go"`
Gopls ToolInfo `json:"gopls"`
Templ ToolInfo `json:"templ"`
}
type ToolInfo struct {
Location string `json:"location"`
Version string `json:"version"`
OK bool `json:"ok"`
Message string `json:"message,omitempty"`
}
func getGoInfo() (d ToolInfo) {
// Find Go.
var err error
d.Location, err = exec.LookPath("go")
if err != nil {
d.Message = fmt.Sprintf("failed to find go: %v", err)
return
}
// Run go to find the version.
cmd := exec.Command(d.Location, "version")
v, err := cmd.Output()
if err != nil {
d.Message = fmt.Sprintf("failed to get go version, check that Go is installed: %v", err)
return
}
d.Version = strings.TrimSpace(string(v))
d.OK = true
return
}
func getGoplsInfo() (d ToolInfo) {
var err error
d.Location, err = pls.FindGopls()
if err != nil {
d.Message = fmt.Sprintf("failed to find gopls: %v", err)
return
}
cmd := exec.Command(d.Location, "version")
v, err := cmd.Output()
if err != nil {
d.Message = fmt.Sprintf("failed to get gopls version: %v", err)
return
}
d.Version = strings.TrimSpace(string(v))
d.OK = true
return
}
func getTemplInfo() (d ToolInfo) {
// Find templ.
var err error
d.Location, err = findTempl()
if err != nil {
d.Message = err.Error()
return
}
// Run templ to find the version.
cmd := exec.Command(d.Location, "version")
v, err := cmd.Output()
if err != nil {
d.Message = fmt.Sprintf("failed to get templ version: %v", err)
return
}
d.Version = strings.TrimSpace(string(v))
if d.Version != templ.Version() {
d.Message = fmt.Sprintf("version mismatch - you're running %q at the command line, but the version in the path is %q", templ.Version(), d.Version)
return
}
d.OK = true
return
}
func findTempl() (location string, err error) {
executableName := "templ"
if runtime.GOOS == "windows" {
executableName = "templ.exe"
}
executableName, err = exec.LookPath(executableName)
if err == nil {
// Found on the path.
return executableName, nil
}
// Unexpected error.
if !errors.Is(err, exec.ErrNotFound) {
return "", fmt.Errorf("unexpected error looking for templ: %w", err)
}
return "", fmt.Errorf("templ is not in the path (%q). You can install templ with `go install github.com/a-h/templ/cmd/templ@latest`", os.Getenv("PATH"))
}
func getInfo() (d Info) {
d.OS.GOOS = runtime.GOOS
d.OS.GOARCH = runtime.GOARCH
d.Go = getGoInfo()
d.Gopls = getGoplsInfo()
d.Templ = getTemplInfo()
return
}
func Run(ctx context.Context, log *slog.Logger, stdout io.Writer, args Arguments) (err error) {
info := getInfo()
if args.JSON {
enc := json.NewEncoder(stdout)
enc.SetIndent("", " ")
return enc.Encode(info)
}
log.Info("os", slog.String("goos", info.OS.GOOS), slog.String("goarch", info.OS.GOARCH))
logInfo(ctx, log, "go", info.Go)
logInfo(ctx, log, "gopls", info.Gopls)
logInfo(ctx, log, "templ", info.Templ)
return nil
}
func logInfo(ctx context.Context, log *slog.Logger, name string, ti ToolInfo) {
level := slog.LevelInfo
if !ti.OK {
level = slog.LevelError
}
args := []any{
slog.String("location", ti.Location),
slog.String("version", ti.Version),
}
if ti.Message != "" {
args = append(args, slog.String("message", ti.Message))
}
log.Log(ctx, level, name, args...)
}
package httpdebug
import (
"encoding/json"
"io"
"net/http"
"net/url"
"github.com/a-h/templ"
"github.com/a-h/templ/cmd/templ/lspcmd/proxy"
"github.com/a-h/templ/cmd/templ/visualize"
"go.uber.org/zap"
)
var log *zap.Logger
func NewHandler(l *zap.Logger, s *proxy.Server) http.Handler {
m := http.NewServeMux()
log = l
m.HandleFunc("/templ", func(w http.ResponseWriter, r *http.Request) {
uri := r.URL.Query().Get("uri")
c, ok := s.TemplSource.Get(uri)
if !ok {
Error(w, "uri not found", http.StatusNotFound)
return
}
String(w, c.String())
})
m.HandleFunc("/sourcemap", func(w http.ResponseWriter, r *http.Request) {
uri := r.URL.Query().Get("uri")
sm, ok := s.SourceMapCache.Get(uri)
if !ok {
Error(w, "uri not found", http.StatusNotFound)
return
}
JSON(w, sm.SourceLinesToTarget)
})
m.HandleFunc("/go", func(w http.ResponseWriter, r *http.Request) {
uri := r.URL.Query().Get("uri")
c, ok := s.GoSource[uri]
if !ok {
Error(w, "uri not found", http.StatusNotFound)
return
}
String(w, c)
})
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
uri := r.URL.Query().Get("uri")
if uri == "" {
// List all URIs.
if err := list(s.TemplSource.URIs()).Render(r.Context(), w); err != nil {
Error(w, "failed to list URIs", http.StatusInternalServerError)
}
return
}
// Assume we've got a URI.
templSource, ok := s.TemplSource.Get(uri)
if !ok {
if !ok {
Error(w, "uri not found in document contents", http.StatusNotFound)
return
}
}
goSource, ok := s.GoSource[uri]
if !ok {
if !ok {
Error(w, "uri not found in document contents", http.StatusNotFound)
return
}
}
sm, ok := s.SourceMapCache.Get(uri)
if !ok {
Error(w, "uri not found", http.StatusNotFound)
return
}
if err := visualize.HTML(uri, templSource.String(), goSource, sm).Render(r.Context(), w); err != nil {
Error(w, "failed to visualize HTML", http.StatusInternalServerError)
}
})
return m
}
func getMapURL(uri string) templ.SafeURL {
return withQuery("/", uri)
}
func getSourceMapURL(uri string) templ.SafeURL {
return withQuery("/sourcemap", uri)
}
func getTemplURL(uri string) templ.SafeURL {
return withQuery("/templ", uri)
}
func getGoURL(uri string) templ.SafeURL {
return withQuery("/go", uri)
}
func withQuery(path, uri string) templ.SafeURL {
q := make(url.Values)
q.Set("uri", uri)
u := &url.URL{
Path: path,
RawPath: path,
RawQuery: q.Encode(),
}
return templ.SafeURL(u.String())
}
func JSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(v); err != nil {
log.Error("failed to write JSON response", zap.Error(err))
}
}
func String(w http.ResponseWriter, s string) {
if _, err := io.WriteString(w, s); err != nil {
log.Error("failed to write string response", zap.Error(err))
}
}
func Error(w http.ResponseWriter, msg string, status int) {
w.WriteHeader(status)
if _, err := io.WriteString(w, msg); err != nil {
log.Error("failed to write error response", zap.Error(err))
}
}
// Code generated by templ - DO NOT EDIT.
package httpdebug
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func list(uris []string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<table><tr><th>File</th><th></th><th></th><th></th><th></th></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, uri := range uris {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(uri)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/lspcmd/httpdebug/list.templ`, Line: 14, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = getMapURL(uri)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Mapping</a></td><td><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = getSourceMapURL(uri)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Source Map</a></td><td><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL = getTemplURL(uri)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Templ</a></td><td><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL = getGoURL(uri)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Go</a></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</table>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package lspdiff
import (
"github.com/a-h/protocol"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
// This package provides a way to compare LSP protocol messages, ignoring irrelevant fields.
func Hover(expected, actual protocol.Hover) string {
return cmp.Diff(expected, actual,
cmpopts.IgnoreFields(protocol.Hover{}, "Range"),
cmpopts.IgnoreFields(protocol.MarkupContent{}, "Kind"),
)
}
func CodeAction(expected, actual []protocol.CodeAction) string {
return cmp.Diff(expected, actual)
}
func CompletionList(expected, actual *protocol.CompletionList) string {
return cmp.Diff(expected, actual,
cmpopts.IgnoreFields(protocol.CompletionList{}, "IsIncomplete"),
)
}
func References(expected, actual []protocol.Location) string {
return cmp.Diff(expected, actual)
}
func CompletionListContainsText(cl *protocol.CompletionList, text string) bool {
if cl == nil {
return false
}
for _, item := range cl.Items {
if item.Label == text {
return true
}
}
return false
}
package lspcmd
import (
"context"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"github.com/a-h/protocol"
"github.com/a-h/templ/cmd/templ/lspcmd/httpdebug"
"github.com/a-h/templ/cmd/templ/lspcmd/pls"
"github.com/a-h/templ/cmd/templ/lspcmd/proxy"
"go.lsp.dev/jsonrpc2"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
_ "net/http/pprof"
)
type Arguments struct {
Log string
GoplsLog string
GoplsRPCTrace bool
// PPROF sets whether to start a profiling server on localhost:9999
PPROF bool
// HTTPDebug sets the HTTP endpoint to listen on. Leave empty for no web debug.
HTTPDebug string
}
func Run(stdin io.Reader, stdout, stderr io.Writer, args Arguments) (err error) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
defer func() {
signal.Stop(signalChan)
cancel()
}()
if args.PPROF {
go func() {
_ = http.ListenAndServe("localhost:9999", nil)
}()
}
go func() {
select {
case <-signalChan: // First signal, cancel context.
cancel()
case <-ctx.Done():
}
<-signalChan // Second signal, hard exit.
os.Exit(2)
}()
log := zap.NewNop()
if args.Log != "" {
cfg := zap.NewProductionConfig()
cfg.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder
cfg.OutputPaths = []string{
args.Log,
}
log, err = cfg.Build()
if err != nil {
_, _ = fmt.Fprintf(stderr, "failed to create logger: %v\n", err)
os.Exit(1)
}
}
defer func() {
_ = log.Sync()
}()
templStream := jsonrpc2.NewStream(newStdRwc(log, "templStream", stdout, stdin))
return run(ctx, log, templStream, args)
}
func run(ctx context.Context, log *zap.Logger, templStream jsonrpc2.Stream, args Arguments) (err error) {
log.Info("lsp: starting up...")
defer func() {
if r := recover(); r != nil {
log.Fatal("handled panic", zap.Any("recovered", r))
}
}()
log.Info("lsp: starting gopls...")
rwc, err := pls.NewGopls(ctx, log, pls.Options{
Log: args.GoplsLog,
RPCTrace: args.GoplsRPCTrace,
})
if err != nil {
log.Error("failed to start gopls", zap.Error(err))
os.Exit(1)
}
cache := proxy.NewSourceMapCache()
diagnosticCache := proxy.NewDiagnosticCache()
log.Info("creating gopls client")
clientProxy, clientInit := proxy.NewClient(log, cache, diagnosticCache)
_, goplsConn, goplsServer := protocol.NewClient(context.Background(), clientProxy, jsonrpc2.NewStream(rwc), log)
defer goplsConn.Close()
log.Info("creating proxy")
// Create the proxy to sit between.
serverProxy := proxy.NewServer(log, goplsServer, cache, diagnosticCache)
// Create templ server.
log.Info("creating templ server")
_, templConn, templClient := protocol.NewServer(context.Background(), serverProxy, templStream, log)
defer templConn.Close()
// Allow both the server and the client to initiate outbound requests.
clientInit(templClient)
// Start the web server if required.
if args.HTTPDebug != "" {
log.Info("starting debug http server", zap.String("addr", args.HTTPDebug))
h := httpdebug.NewHandler(log, serverProxy)
go func() {
if err := http.ListenAndServe(args.HTTPDebug, h); err != nil {
log.Error("web server failed", zap.Error(err))
}
}()
}
log.Info("listening")
select {
case <-ctx.Done():
log.Info("context closed")
case <-templConn.Done():
log.Info("templConn closed")
case <-goplsConn.Done():
log.Info("goplsConn closed")
}
log.Info("shutdown complete")
return
}
package pls
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path"
"runtime"
"go.uber.org/zap"
)
// Options for the gopls client.
type Options struct {
Log string
RPCTrace bool
}
// AsArguments converts the options into command line arguments for gopls.
func (opts Options) AsArguments() []string {
var args []string
if opts.Log != "" {
args = append(args, "-logfile", opts.Log)
}
if opts.RPCTrace {
args = append(args, "-rpc.trace")
}
return args
}
func FindGopls() (location string, err error) {
executableName := "gopls"
if runtime.GOOS == "windows" {
executableName = "gopls.exe"
}
pathLocation, err := exec.LookPath(executableName)
if err == nil {
// Found on the path.
return pathLocation, nil
}
// Unexpected error.
if !errors.Is(err, exec.ErrNotFound) {
return "", fmt.Errorf("unexpected error looking for gopls: %w", err)
}
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("unexpected error looking for gopls: %w", err)
}
// Probe standard locations.
locations := []string{
path.Join(home, "go", "bin", executableName),
path.Join(home, ".local", "bin", executableName),
}
for _, location := range locations {
_, err = os.Stat(location)
if err != nil {
continue
}
// Found in a standard location.
return location, nil
}
return "", fmt.Errorf("cannot find gopls on the path (%q), in $HOME/go/bin or $HOME/.local/bin/gopls. You can install gopls with `go install golang.org/x/tools/gopls@latest`", os.Getenv("PATH"))
}
// NewGopls starts gopls and opens up a jsonrpc2 connection to it.
func NewGopls(ctx context.Context, log *zap.Logger, opts Options) (rwc io.ReadWriteCloser, err error) {
location, err := FindGopls()
if err != nil {
return nil, err
}
cmd := exec.Command(location, opts.AsArguments()...)
return newProcessReadWriteCloser(log, cmd)
}
// newProcessReadWriteCloser creates a processReadWriteCloser to allow stdin/stdout to be used as
// a JSON RPC 2.0 transport.
func newProcessReadWriteCloser(zapLogger *zap.Logger, cmd *exec.Cmd) (rwc processReadWriteCloser, err error) {
stdin, err := cmd.StdinPipe()
if err != nil {
return
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return
}
rwc = processReadWriteCloser{
in: stdin,
out: stdout,
}
go func() {
if err := cmd.Run(); err != nil {
zapLogger.Error("gopls command error", zap.Error(err))
}
}()
return
}
type processReadWriteCloser struct {
in io.WriteCloser
out io.ReadCloser
}
func (prwc processReadWriteCloser) Read(p []byte) (n int, err error) {
return prwc.out.Read(p)
}
func (prwc processReadWriteCloser) Write(p []byte) (n int, err error) {
return prwc.in.Write(p)
}
func (prwc processReadWriteCloser) Close() error {
errInClose := prwc.in.Close()
errOutClose := prwc.out.Close()
if errInClose != nil || errOutClose != nil {
return fmt.Errorf("error closing process - in: %v, out: %v", errInClose, errOutClose)
}
return nil
}
package proxy
import (
"context"
"fmt"
"strings"
lsp "github.com/a-h/protocol"
"go.uber.org/zap"
)
// Client is responsible for rewriting messages that are
// originated from gopls, and are sent to the client.
//
// Since `gopls` is working on Go files, and this is the `templ` LSP,
// the job of this code is to rewrite incoming requests to adjust the
// file name from `*_templ.go` to `*.templ`, and to remap the char
// positions where required.
type Client struct {
Log *zap.Logger
Target lsp.Client
SourceMapCache *SourceMapCache
DiagnosticCache *DiagnosticCache
}
func NewClient(log *zap.Logger, cache *SourceMapCache, diagnosticCache *DiagnosticCache) (c *Client, init func(lsp.Client)) {
c = &Client{
Log: log,
SourceMapCache: cache,
DiagnosticCache: diagnosticCache,
}
return c, func(target lsp.Client) {
c.Target = target
}
}
func (p Client) Progress(ctx context.Context, params *lsp.ProgressParams) (err error) {
p.Log.Info("client <- server: Progress")
return p.Target.Progress(ctx, params)
}
func (p Client) WorkDoneProgressCreate(ctx context.Context, params *lsp.WorkDoneProgressCreateParams) (err error) {
p.Log.Info("client <- server: WorkDoneProgressCreate")
return p.Target.WorkDoneProgressCreate(ctx, params)
}
func (p Client) LogMessage(ctx context.Context, params *lsp.LogMessageParams) (err error) {
p.Log.Info("client <- server: LogMessage", zap.String("message", params.Message))
return p.Target.LogMessage(ctx, params)
}
func (p Client) PublishDiagnostics(ctx context.Context, params *lsp.PublishDiagnosticsParams) (err error) {
p.Log.Info("client <- server: PublishDiagnostics")
if strings.HasSuffix(string(params.URI), "go.mod") {
p.Log.Info("client <- server: PublishDiagnostics: skipping go.mod diagnostics")
return nil
}
// Log diagnostics.
for i, diagnostic := range params.Diagnostics {
p.Log.Info(fmt.Sprintf("client <- server: PublishDiagnostics: [%d]", i), zap.Any("diagnostic", diagnostic))
}
// Get the sourcemap from the cache.
uri := strings.TrimSuffix(string(params.URI), "_templ.go") + ".templ"
sourceMap, ok := p.SourceMapCache.Get(uri)
if !ok {
p.Log.Error("unable to complete because the sourcemap for the URI doesn't exist in the cache", zap.String("uri", uri))
return fmt.Errorf("unable to complete because the sourcemap for %q doesn't exist in the cache, has the didOpen notification been sent yet?", uri)
}
params.URI = lsp.DocumentURI(uri)
// Rewrite the positions.
for i := 0; i < len(params.Diagnostics); i++ {
item := params.Diagnostics[i]
start, ok := sourceMap.SourcePositionFromTarget(item.Range.Start.Line, item.Range.Start.Character)
if !ok {
continue
}
if item.Range.Start.Line == item.Range.End.Line {
length := item.Range.End.Character - item.Range.Start.Character
item.Range.Start.Line = start.Line
item.Range.Start.Character = start.Col
item.Range.End.Line = start.Line
item.Range.End.Character = start.Col + length
params.Diagnostics[i] = item
p.Log.Info(fmt.Sprintf("diagnostic [%d] rewritten", i), zap.Any("diagnostic", item))
continue
}
end, ok := sourceMap.SourcePositionFromTarget(item.Range.End.Line, item.Range.End.Character)
if !ok {
continue
}
item.Range.Start.Line = start.Line
item.Range.Start.Character = start.Col
item.Range.End.Line = end.Line
item.Range.End.Character = end.Col
params.Diagnostics[i] = item
p.Log.Info(fmt.Sprintf("diagnostic [%d] rewritten", i), zap.Any("diagnostic", item))
}
params.Diagnostics = p.DiagnosticCache.AddTemplDiagnostics(uri, params.Diagnostics)
err = p.Target.PublishDiagnostics(ctx, params)
return err
}
func (p Client) ShowMessage(ctx context.Context, params *lsp.ShowMessageParams) (err error) {
p.Log.Info("client <- server: ShowMessage", zap.String("message", params.Message))
if strings.HasPrefix(params.Message, "Do not edit this file!") {
return
}
return p.Target.ShowMessage(ctx, params)
}
func (p Client) ShowMessageRequest(ctx context.Context, params *lsp.ShowMessageRequestParams) (result *lsp.MessageActionItem, err error) {
p.Log.Info("client <- server: ShowMessageRequest", zap.String("message", params.Message))
return p.Target.ShowMessageRequest(ctx, params)
}
func (p Client) Telemetry(ctx context.Context, params interface{}) (err error) {
p.Log.Info("client <- server: Telemetry")
return p.Target.Telemetry(ctx, params)
}
func (p Client) RegisterCapability(ctx context.Context, params *lsp.RegistrationParams) (err error) {
p.Log.Info("client <- server: RegisterCapability")
return p.Target.RegisterCapability(ctx, params)
}
func (p Client) UnregisterCapability(ctx context.Context, params *lsp.UnregistrationParams) (err error) {
p.Log.Info("client <- server: UnregisterCapability")
return p.Target.UnregisterCapability(ctx, params)
}
func (p Client) ApplyEdit(ctx context.Context, params *lsp.ApplyWorkspaceEditParams) (result *lsp.ApplyWorkspaceEditResponse, err error) {
p.Log.Info("client <- server: ApplyEdit")
return p.Target.ApplyEdit(ctx, params)
}
func (p Client) Configuration(ctx context.Context, params *lsp.ConfigurationParams) (result []interface{}, err error) {
p.Log.Info("client <- server: Configuration")
return p.Target.Configuration(ctx, params)
}
func (p Client) WorkspaceFolders(ctx context.Context) (result []lsp.WorkspaceFolder, err error) {
p.Log.Info("client <- server: WorkspaceFolders")
return p.Target.WorkspaceFolders(ctx)
}
package proxy
import (
"sync"
lsp "github.com/a-h/protocol"
)
func NewDiagnosticCache() *DiagnosticCache {
return &DiagnosticCache{
m: &sync.Mutex{},
cache: make(map[string]fileDiagnostic),
}
}
type fileDiagnostic struct {
templDiagnostics []lsp.Diagnostic
goplsDiagnostics []lsp.Diagnostic
}
type DiagnosticCache struct {
m *sync.Mutex
cache map[string]fileDiagnostic
}
func zeroLengthSliceIfNil(diags []lsp.Diagnostic) []lsp.Diagnostic {
if diags == nil {
return make([]lsp.Diagnostic, 0)
}
return diags
}
func (dc *DiagnosticCache) AddTemplDiagnostics(uri string, goDiagnostics []lsp.Diagnostic) []lsp.Diagnostic {
goDiagnostics = zeroLengthSliceIfNil(goDiagnostics)
dc.m.Lock()
defer dc.m.Unlock()
diag := dc.cache[uri]
diag.goplsDiagnostics = goDiagnostics
diag.templDiagnostics = zeroLengthSliceIfNil(diag.templDiagnostics)
dc.cache[uri] = diag
return append(diag.templDiagnostics, goDiagnostics...)
}
func (dc *DiagnosticCache) ClearTemplDiagnostics(uri string) {
dc.m.Lock()
defer dc.m.Unlock()
diag := dc.cache[uri]
diag.templDiagnostics = make([]lsp.Diagnostic, 0)
dc.cache[uri] = diag
}
func (dc *DiagnosticCache) AddGoDiagnostics(uri string, templDiagnostics []lsp.Diagnostic) []lsp.Diagnostic {
templDiagnostics = zeroLengthSliceIfNil(templDiagnostics)
dc.m.Lock()
defer dc.m.Unlock()
diag := dc.cache[uri]
diag.templDiagnostics = templDiagnostics
diag.goplsDiagnostics = zeroLengthSliceIfNil(diag.goplsDiagnostics)
dc.cache[uri] = diag
return append(diag.goplsDiagnostics, templDiagnostics...)
}
package proxy
import (
"fmt"
"strings"
"sync"
lsp "github.com/a-h/protocol"
"go.uber.org/zap"
)
// newDocumentContents creates a document content processing tool.
func newDocumentContents(log *zap.Logger) *DocumentContents {
return &DocumentContents{
m: new(sync.Mutex),
uriToContents: make(map[string]*Document),
log: log,
}
}
type DocumentContents struct {
m *sync.Mutex
uriToContents map[string]*Document
log *zap.Logger
}
// Set the contents of a document.
func (dc *DocumentContents) Set(uri string, d *Document) {
dc.m.Lock()
defer dc.m.Unlock()
dc.uriToContents[uri] = d
}
// Get the contents of a document.
func (dc *DocumentContents) Get(uri string) (d *Document, ok bool) {
dc.m.Lock()
defer dc.m.Unlock()
d, ok = dc.uriToContents[uri]
return
}
// Delete a document from memory.
func (dc *DocumentContents) Delete(uri string) {
dc.m.Lock()
defer dc.m.Unlock()
delete(dc.uriToContents, uri)
}
func (dc *DocumentContents) URIs() (uris []string) {
dc.m.Lock()
defer dc.m.Unlock()
uris = make([]string, len(dc.uriToContents))
var i int
for k := range dc.uriToContents {
uris[i] = k
i++
}
return uris
}
// Apply changes to the document from the client, and return a list of change requests to send back to the client.
func (dc *DocumentContents) Apply(uri string, changes []lsp.TextDocumentContentChangeEvent) (d *Document, err error) {
dc.m.Lock()
defer dc.m.Unlock()
var ok bool
d, ok = dc.uriToContents[uri]
if !ok {
err = fmt.Errorf("document not found")
return
}
for _, change := range changes {
d.Apply(change.Range, change.Text)
}
return
}
func NewDocument(log *zap.Logger, s string) *Document {
return &Document{
Log: log,
Lines: strings.Split(s, "\n"),
}
}
type Document struct {
Log *zap.Logger
Lines []string
}
func (d *Document) LineLengths() (lens []int) {
lens = make([]int, len(d.Lines))
for i, l := range d.Lines {
lens[i] = len(l)
}
return
}
func (d *Document) Len() (line, col int) {
line = len(d.Lines)
col = len(d.Lines[len(d.Lines)-1])
return
}
func (d *Document) Overwrite(fromLine, fromCol, toLine, toCol int, lines []string) {
suffix := d.Lines[toLine][toCol:]
toLen := d.LineLengths()[toLine]
d.Delete(fromLine, fromCol, toLine, toLen)
lines[len(lines)-1] = lines[len(lines)-1] + suffix
d.Insert(fromLine, fromCol, lines)
}
func (d *Document) Insert(line, col int, lines []string) {
prefix := d.Lines[line][:col]
suffix := d.Lines[line][col:]
lines[0] = prefix + lines[0]
d.Lines[line] = lines[0]
if len(lines) > 1 {
d.InsertLines(line+1, lines[1:])
}
d.Lines[line+len(lines)-1] = lines[len(lines)-1] + suffix
}
func (d *Document) InsertLines(i int, withLines []string) {
d.Lines = append(d.Lines[:i], append(withLines, d.Lines[i:]...)...)
}
func (d *Document) Delete(fromLine, fromCol, toLine, toCol int) {
prefix := d.Lines[fromLine][:fromCol]
suffix := d.Lines[toLine][toCol:]
// Delete intermediate lines.
deleteFrom := fromLine
deleteTo := fromLine + (toLine - fromLine)
d.DeleteLines(deleteFrom, deleteTo)
// Merge the contents of the final line.
d.Lines[fromLine] = prefix + suffix
}
func (d *Document) DeleteLines(i, j int) {
d.Lines = append(d.Lines[:i], d.Lines[j:]...)
}
func (d *Document) String() string {
return strings.Join(d.Lines, "\n")
}
func (d *Document) Replace(with string) {
d.Lines = strings.Split(with, "\n")
}
func (d *Document) Apply(r *lsp.Range, with string) {
withLines := strings.Split(with, "\n")
d.normalize(r)
if d.isWholeDocument(r) {
d.Lines = withLines
return
}
if d.isInsert(r, with) {
d.Insert(int(r.Start.Line), int(r.Start.Character), withLines)
return
}
if d.isDelete(r, with) {
d.Delete(int(r.Start.Line), int(r.Start.Character), int(r.End.Line), int(r.End.Character))
return
}
if d.isOverwrite(r, with) {
d.Overwrite(int(r.Start.Line), int(r.Start.Character), int(r.End.Line), int(r.End.Character), withLines)
}
}
func (d *Document) normalize(r *lsp.Range) {
if r == nil {
return
}
lens := d.LineLengths()
if r.Start.Line >= uint32(len(lens)) {
r.Start.Line = uint32(len(lens) - 1)
r.Start.Character = uint32(lens[r.Start.Line])
}
if r.Start.Character > uint32(lens[r.Start.Line]) {
r.Start.Character = uint32(lens[r.Start.Line])
}
if r.End.Line >= uint32(len(lens)) {
r.End.Line = uint32(len(lens) - 1)
r.End.Character = uint32(lens[r.End.Line])
}
if r.End.Character > uint32(lens[r.End.Line]) {
r.End.Character = uint32(lens[r.End.Line])
}
}
func (d *Document) isOverwrite(r *lsp.Range, with string) bool {
return (r.End.Line != r.Start.Line || r.Start.Character != r.End.Character) && with != ""
}
func (d *Document) isInsert(r *lsp.Range, with string) bool {
return r.End.Line == r.Start.Line && r.Start.Character == r.End.Character && with != ""
}
func (d *Document) isDelete(r *lsp.Range, with string) bool {
return (r.End.Line != r.Start.Line || r.Start.Character != r.End.Character) && with == ""
}
func (d *Document) isWholeDocument(r *lsp.Range) bool {
if r == nil {
return true
}
if r.Start.Line != 0 || r.Start.Character != 0 {
return false
}
l, c := d.Len()
return r.End.Line == uint32(l) || r.End.Character == uint32(c)
}
package proxy
import (
"path"
"strings"
lsp "github.com/a-h/protocol"
)
func convertTemplToGoURI(templURI lsp.DocumentURI) (isTemplFile bool, goURI lsp.DocumentURI) {
base, fileName := path.Split(string(templURI))
if !strings.HasSuffix(fileName, ".templ") {
return
}
return true, lsp.DocumentURI(base + (strings.TrimSuffix(fileName, ".templ") + "_templ.go"))
}
func convertTemplGoToTemplURI(goURI lsp.DocumentURI) (isTemplGoFile bool, templURI lsp.DocumentURI) {
base, fileName := path.Split(string(goURI))
if !strings.HasSuffix(fileName, "_templ.go") {
return
}
return true, lsp.DocumentURI(base + (strings.TrimSuffix(fileName, "_templ.go") + ".templ"))
}
package proxy
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/a-h/parse"
lsp "github.com/a-h/protocol"
"go.lsp.dev/uri"
"go.uber.org/zap"
"github.com/a-h/templ"
"github.com/a-h/templ/cmd/templ/imports"
"github.com/a-h/templ/generator"
"github.com/a-h/templ/parser/v2"
)
// Server is responsible for rewriting messages that are
// originated from the text editor, and need to be sent to gopls.
//
// Since the editor is working on `templ` files, and `gopls` works
// on Go files, the job of this code is to rewrite incoming requests
// to adjust the file names from `*.templ` to `*_templ.go` and to
// remap the line/character positions in the `templ` files to their
// corresponding locations in the Go file.
//
// This allows gopls to operate as usual.
//
// This code also rewrites the responses back from gopls to do the
// inverse operation - to put the file names back, and readjust any
// character positions.
type Server struct {
Log *zap.Logger
Target lsp.Server
SourceMapCache *SourceMapCache
DiagnosticCache *DiagnosticCache
TemplSource *DocumentContents
GoSource map[string]string
preLoadURIs []*lsp.DidOpenTextDocumentParams
}
func NewServer(log *zap.Logger, target lsp.Server, cache *SourceMapCache, diagnosticCache *DiagnosticCache) (s *Server) {
return &Server{
Log: log,
Target: target,
SourceMapCache: cache,
DiagnosticCache: diagnosticCache,
TemplSource: newDocumentContents(log),
GoSource: make(map[string]string),
}
}
// updatePosition maps positions and filenames from source templ files into the target *.go files.
func (p *Server) updatePosition(templURI lsp.DocumentURI, current lsp.Position) (ok bool, goURI lsp.DocumentURI, updated lsp.Position) {
log := p.Log.With(zap.String("uri", string(templURI)))
var isTemplFile bool
if isTemplFile, goURI = convertTemplToGoURI(templURI); !isTemplFile {
return false, templURI, current
}
sourceMap, ok := p.SourceMapCache.Get(string(templURI))
if !ok {
log.Warn("completion: sourcemap not found in cache, it could be that didOpen was not called")
return
}
// Map from the source position to target Go position.
to, ok := sourceMap.TargetPositionFromSource(current.Line, current.Character)
if !ok {
log.Info("updatePosition: not found", zap.String("from", fmt.Sprintf("%d:%d", current.Line, current.Character)))
return false, templURI, current
}
log.Info("updatePosition: found", zap.String("fromTempl", fmt.Sprintf("%d:%d", current.Line, current.Character)),
zap.String("toGo", fmt.Sprintf("%d:%d", to.Line, to.Col)))
updated.Line = to.Line
updated.Character = to.Col
return true, goURI, updated
}
func (p *Server) convertTemplRangeToGoRange(templURI lsp.DocumentURI, input lsp.Range) (output lsp.Range, ok bool) {
output = input
var sourceMap *parser.SourceMap
sourceMap, ok = p.SourceMapCache.Get(string(templURI))
if !ok {
p.Log.Warn("templ->go: sourcemap not found in cache")
return
}
// Map from the source position to target Go position.
start, ok := sourceMap.TargetPositionFromSource(input.Start.Line, input.Start.Character)
if ok {
output.Start.Line = start.Line
output.Start.Character = start.Col
}
end, ok := sourceMap.TargetPositionFromSource(input.End.Line, input.End.Character)
if ok {
output.End.Line = end.Line
output.End.Character = end.Col
}
return
}
func (p *Server) convertGoRangeToTemplRange(templURI lsp.DocumentURI, input lsp.Range) (output lsp.Range) {
output = input
sourceMap, ok := p.SourceMapCache.Get(string(templURI))
if !ok {
p.Log.Warn("go->templ: sourcemap not found in cache")
return
}
// Map from the source position to target Go position.
start, ok := sourceMap.SourcePositionFromTarget(input.Start.Line, input.Start.Character)
if ok {
output.Start.Line = start.Line
output.Start.Character = start.Col
}
end, ok := sourceMap.SourcePositionFromTarget(input.End.Line, input.End.Character)
if ok {
output.End.Line = end.Line
output.End.Character = end.Col
}
return
}
// parseTemplate parses the templ file content, and notifies the end user via the LSP about how it went.
func (p *Server) parseTemplate(ctx context.Context, uri uri.URI, templateText string) (template parser.TemplateFile, ok bool, err error) {
template, err = parser.ParseString(templateText)
if err != nil {
msg := &lsp.PublishDiagnosticsParams{
URI: uri,
Diagnostics: []lsp.Diagnostic{
{
Severity: lsp.DiagnosticSeverityError,
Code: "",
Source: "templ",
Message: err.Error(),
},
},
}
if pe, isParserError := err.(parse.ParseError); isParserError {
msg.Diagnostics[0].Range = lsp.Range{
Start: lsp.Position{
Line: uint32(pe.Pos.Line),
Character: uint32(pe.Pos.Col),
},
End: lsp.Position{
Line: uint32(pe.Pos.Line),
Character: uint32(pe.Pos.Col),
},
}
}
msg.Diagnostics = p.DiagnosticCache.AddGoDiagnostics(string(uri), msg.Diagnostics)
err = lsp.ClientFromContext(ctx).PublishDiagnostics(ctx, msg)
if err != nil {
p.Log.Error("failed to publish error diagnostics", zap.Error(err))
}
return
}
template.Filepath = string(uri)
parsedDiagnostics, err := parser.Diagnose(template)
if err != nil {
return
}
ok = true
if len(parsedDiagnostics) > 0 {
msg := &lsp.PublishDiagnosticsParams{
URI: uri,
}
for _, d := range parsedDiagnostics {
msg.Diagnostics = append(msg.Diagnostics, lsp.Diagnostic{
Severity: lsp.DiagnosticSeverityWarning,
Code: "",
Source: "templ",
Message: d.Message,
Range: lsp.Range{
Start: lsp.Position{
Line: uint32(d.Range.From.Line),
Character: uint32(d.Range.From.Col),
},
End: lsp.Position{
Line: uint32(d.Range.To.Line),
Character: uint32(d.Range.To.Col),
},
},
})
}
msg.Diagnostics = p.DiagnosticCache.AddGoDiagnostics(string(uri), msg.Diagnostics)
err = lsp.ClientFromContext(ctx).PublishDiagnostics(ctx, msg)
if err != nil {
p.Log.Error("failed to publish error diagnostics", zap.Error(err))
}
return
}
// Clear templ diagnostics.
p.DiagnosticCache.ClearTemplDiagnostics(string(uri))
err = lsp.ClientFromContext(ctx).PublishDiagnostics(ctx, &lsp.PublishDiagnosticsParams{
URI: uri,
// Cannot be nil as per https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#publishDiagnosticsParams
Diagnostics: []lsp.Diagnostic{},
})
if err != nil {
p.Log.Error("failed to publish diagnostics", zap.Error(err))
return
}
return
}
func (p *Server) Initialize(ctx context.Context, params *lsp.InitializeParams) (result *lsp.InitializeResult, err error) {
p.Log.Info("client -> server: Initialize")
defer p.Log.Info("client -> server: Initialize end")
result, err = p.Target.Initialize(ctx, params)
if err != nil {
p.Log.Error("Initialize failed", zap.Error(err))
}
// Add the '<' and '{' trigger so that we can do snippets for tags.
if result.Capabilities.CompletionProvider == nil {
result.Capabilities.CompletionProvider = &lsp.CompletionOptions{}
}
result.Capabilities.CompletionProvider.TriggerCharacters = append(result.Capabilities.CompletionProvider.TriggerCharacters, "{", "<")
// Remove all the gopls commands.
if result.Capabilities.ExecuteCommandProvider == nil {
result.Capabilities.ExecuteCommandProvider = &lsp.ExecuteCommandOptions{}
}
result.Capabilities.ExecuteCommandProvider.Commands = []string{}
result.Capabilities.DocumentFormattingProvider = true
result.Capabilities.SemanticTokensProvider = nil
result.Capabilities.DocumentRangeFormattingProvider = false
result.Capabilities.TextDocumentSync = lsp.TextDocumentSyncOptions{
OpenClose: true,
Change: lsp.TextDocumentSyncKindFull,
WillSave: false,
WillSaveWaitUntil: false,
Save: &lsp.SaveOptions{IncludeText: true},
}
for _, c := range params.WorkspaceFolders {
path := strings.TrimPrefix(c.URI, "file://")
werr := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
p.Log.Info("found file", zap.String("path", path))
uri := uri.URI("file://" + path)
isTemplFile, goURI := convertTemplToGoURI(uri)
if !isTemplFile {
p.Log.Info("not a templ file", zap.String("uri", string(uri)))
return nil
}
b, err := os.ReadFile(path)
if err != nil {
return err
}
p.TemplSource.Set(string(uri), NewDocument(p.Log, string(b)))
// Parse the template.
template, ok, err := p.parseTemplate(ctx, uri, string(b))
if err != nil {
p.Log.Error("parseTemplate failure", zap.Error(err))
}
if !ok {
p.Log.Info("parsing template did not succeed", zap.String("uri", string(uri)))
return nil
}
w := new(strings.Builder)
sm, _, err := generator.Generate(template, w)
if err != nil {
return fmt.Errorf("generate failure: %w", err)
}
p.Log.Info("setting source map cache contents", zap.String("uri", string(uri)))
p.SourceMapCache.Set(string(uri), sm)
// Set the Go contents.
p.GoSource[string(uri)] = w.String()
didOpenParams := &lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: goURI,
Text: w.String(),
Version: 1,
LanguageID: "go",
},
}
p.preLoadURIs = append(p.preLoadURIs, didOpenParams)
return nil
})
if werr != nil {
p.Log.Error("walk error", zap.Error(werr))
}
}
result.ServerInfo.Name = "templ-lsp"
result.ServerInfo.Version = templ.Version()
return result, err
}
func (p *Server) Initialized(ctx context.Context, params *lsp.InitializedParams) (err error) {
p.Log.Info("client -> server: Initialized")
defer p.Log.Info("client -> server: Initialized end")
goInitErr := p.Target.Initialized(ctx, params)
for i, doParams := range p.preLoadURIs {
doErr := p.Target.DidOpen(ctx, doParams)
if doErr != nil {
return doErr
}
p.preLoadURIs[i] = nil
}
return goInitErr
}
func (p *Server) Shutdown(ctx context.Context) (err error) {
p.Log.Info("client -> server: Shutdown")
defer p.Log.Info("client -> server: Shutdown end")
return p.Target.Shutdown(ctx)
}
func (p *Server) Exit(ctx context.Context) (err error) {
p.Log.Info("client -> server: Exit")
defer p.Log.Info("client -> server: Exit end")
return p.Target.Exit(ctx)
}
func (p *Server) WorkDoneProgressCancel(ctx context.Context, params *lsp.WorkDoneProgressCancelParams) (err error) {
p.Log.Info("client -> server: WorkDoneProgressCancel")
defer p.Log.Info("client -> server: WorkDoneProgressCancel end")
return p.Target.WorkDoneProgressCancel(ctx, params)
}
func (p *Server) LogTrace(ctx context.Context, params *lsp.LogTraceParams) (err error) {
p.Log.Info("client -> server: LogTrace", zap.String("message", params.Message))
defer p.Log.Info("client -> server: LogTrace end")
return p.Target.LogTrace(ctx, params)
}
func (p *Server) SetTrace(ctx context.Context, params *lsp.SetTraceParams) (err error) {
p.Log.Info("client -> server: SetTrace")
defer p.Log.Info("client -> server: SetTrace end")
return p.Target.SetTrace(ctx, params)
}
var supportedCodeActions = map[string]bool{}
func (p *Server) CodeAction(ctx context.Context, params *lsp.CodeActionParams) (result []lsp.CodeAction, err error) {
p.Log.Info("client -> server: CodeAction", zap.Any("params", params))
defer p.Log.Info("client -> server: CodeAction end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return p.Target.CodeAction(ctx, params)
}
templURI := params.TextDocument.URI
var ok bool
if params.Range, ok = p.convertTemplRangeToGoRange(templURI, params.Range); !ok {
// Don't pass the request to gopls if the range is not within a Go code block.
return
}
params.TextDocument.URI = goURI
result, err = p.Target.CodeAction(ctx, params)
if err != nil {
return
}
var updatedResults []lsp.CodeAction
// Filter out commands that are not yet supported.
// For example, "Fill Struct" runs the `gopls.apply_fix` command.
// This command has a set of arguments, including Fix, Range and URI.
// However, these are just a map[string]any so for each command that we want to support,
// we need to know what the arguments are so that we can rewrite them.
for i := 0; i < len(result); i++ {
if !supportedCodeActions[result[i].Title] {
continue
}
r := result[i]
// Rewrite the Diagnostics range field.
for di := 0; di < len(r.Diagnostics); di++ {
r.Diagnostics[di].Range = p.convertGoRangeToTemplRange(templURI, r.Diagnostics[di].Range)
}
// Rewrite the DocumentChanges.
if r.Edit != nil {
for dci := 0; dci < len(r.Edit.DocumentChanges); dci++ {
dc := r.Edit.DocumentChanges[0]
for ei := 0; ei < len(dc.Edits); ei++ {
dc.Edits[ei].Range = p.convertGoRangeToTemplRange(templURI, dc.Edits[ei].Range)
}
dc.TextDocument.URI = templURI
r.Edit.DocumentChanges[dci] = dc
}
}
updatedResults = append(updatedResults, r)
}
return updatedResults, nil
}
func (p *Server) CodeLens(ctx context.Context, params *lsp.CodeLensParams) (result []lsp.CodeLens, err error) {
p.Log.Info("client -> server: CodeLens")
defer p.Log.Info("client -> server: CodeLens end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return p.Target.CodeLens(ctx, params)
}
templURI := params.TextDocument.URI
params.TextDocument.URI = goURI
result, err = p.Target.CodeLens(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
for i := 0; i < len(result); i++ {
cl := result[i]
cl.Range = p.convertGoRangeToTemplRange(templURI, cl.Range)
result[i] = cl
}
return
}
func (p *Server) CodeLensResolve(ctx context.Context, params *lsp.CodeLens) (result *lsp.CodeLens, err error) {
p.Log.Info("client -> server: CodeLensResolve")
defer p.Log.Info("client -> server: CodeLensResolve end")
return p.Target.CodeLensResolve(ctx, params)
}
func (p *Server) ColorPresentation(ctx context.Context, params *lsp.ColorPresentationParams) (result []lsp.ColorPresentation, err error) {
p.Log.Info("client -> server: ColorPresentation ColorPresentation")
defer p.Log.Info("client -> server: ColorPresentation end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return p.Target.ColorPresentation(ctx, params)
}
templURI := params.TextDocument.URI
params.TextDocument.URI = goURI
result, err = p.Target.ColorPresentation(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
for i := 0; i < len(result); i++ {
r := result[i]
if r.TextEdit != nil {
r.TextEdit.Range = p.convertGoRangeToTemplRange(templURI, r.TextEdit.Range)
}
result[i] = r
}
return
}
func (p *Server) Completion(ctx context.Context, params *lsp.CompletionParams) (result *lsp.CompletionList, err error) {
p.Log.Info("client -> server: Completion")
defer p.Log.Info("client -> server: Completion end")
if params.Context != nil && params.Context.TriggerCharacter == "<" {
result = &lsp.CompletionList{
Items: htmlSnippets,
}
return
}
// Get the sourcemap from the cache.
templURI := params.TextDocument.URI
var ok bool
ok, params.TextDocument.URI, params.TextDocumentPositionParams.Position = p.updatePosition(templURI, params.TextDocumentPositionParams.Position)
if !ok {
return nil, nil
}
// Ensure that Go source is available.
gosrc := strings.Split(p.GoSource[string(templURI)], "\n")
if len(gosrc) < int(params.TextDocumentPositionParams.Position.Line) {
p.Log.Info("completion: line position out of range")
return nil, nil
}
if len(gosrc[params.TextDocumentPositionParams.Position.Line]) < int(params.TextDocumentPositionParams.Position.Character) {
p.Log.Info("completion: col position out of range")
return nil, nil
}
// Call the target.
result, err = p.Target.Completion(ctx, params)
if err != nil {
p.Log.Warn("completion: got gopls error", zap.Error(err))
return
}
if result == nil {
return
}
// Rewrite the result positions.
p.Log.Info("completion: received items", zap.Int("count", len(result.Items)))
for i := 0; i < len(result.Items); i++ {
item := result.Items[i]
if item.TextEdit != nil {
if item.TextEdit.TextEdit != nil {
item.TextEdit.TextEdit.Range = p.convertGoRangeToTemplRange(templURI, item.TextEdit.TextEdit.Range)
}
if item.TextEdit.InsertReplaceEdit != nil {
item.TextEdit.InsertReplaceEdit.Insert = p.convertGoRangeToTemplRange(templURI, item.TextEdit.InsertReplaceEdit.Insert)
item.TextEdit.InsertReplaceEdit.Replace = p.convertGoRangeToTemplRange(templURI, item.TextEdit.InsertReplaceEdit.Replace)
}
}
if len(item.AdditionalTextEdits) > 0 {
doc, ok := p.TemplSource.Get(string(templURI))
if !ok {
continue
}
pkg := getPackageFromItemDetail(item.Detail)
imp := addImport(doc.Lines, pkg)
item.AdditionalTextEdits = []lsp.TextEdit{
{
Range: lsp.Range{
Start: lsp.Position{Line: uint32(imp.LineIndex), Character: 0},
End: lsp.Position{Line: uint32(imp.LineIndex), Character: 0},
},
NewText: imp.Text,
},
}
}
result.Items[i] = item
}
// Add templ snippet.
result.Items = append(result.Items, snippet...)
return
}
var completionWithImport = regexp.MustCompile(`^.*\(from\s(".+")\)$`)
func getPackageFromItemDetail(pkg string) string {
if m := completionWithImport.FindStringSubmatch(pkg); len(m) == 2 {
return m[1]
}
return pkg
}
type importInsert struct {
Text string
LineIndex int
}
var nonImportKeywordRegexp = regexp.MustCompile(`^(?:templ|func|css|script|var|const|type)\s`)
func addImport(lines []string, pkg string) (result importInsert) {
var isInMultiLineImport bool
lastSingleLineImportIndex := -1
for lineIndex, line := range lines {
if strings.HasPrefix(line, "import (") {
isInMultiLineImport = true
continue
}
if strings.HasPrefix(line, "import \"") {
lastSingleLineImportIndex = lineIndex
continue
}
if isInMultiLineImport && strings.HasPrefix(line, ")") {
return importInsert{
LineIndex: lineIndex,
Text: fmt.Sprintf("\t%s\n", pkg),
}
}
// Only add import statements before templates, functions, css, and script templates.
if nonImportKeywordRegexp.MatchString(line) {
break
}
}
var suffix string
if lastSingleLineImportIndex == -1 {
lastSingleLineImportIndex = 1
suffix = "\n"
}
return importInsert{
LineIndex: lastSingleLineImportIndex + 1,
Text: fmt.Sprintf("import %s\n%s", pkg, suffix),
}
}
func (p *Server) CompletionResolve(ctx context.Context, params *lsp.CompletionItem) (result *lsp.CompletionItem, err error) {
p.Log.Info("client -> server: CompletionResolve")
defer p.Log.Info("client -> server: CompletionResolve end")
return p.Target.CompletionResolve(ctx, params)
}
func (p *Server) Declaration(ctx context.Context, params *lsp.DeclarationParams) (result []lsp.Location /* Declaration | DeclarationLink[] | null */, err error) {
p.Log.Info("client -> server: Declaration")
defer p.Log.Info("client -> server: Declaration end")
// Rewrite the request.
templURI := params.TextDocument.URI
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(templURI, params.Position)
if !ok {
return nil, nil
}
// Call gopls and get the result.
result, err = p.Target.Declaration(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
for i := 0; i < len(result); i++ {
if isTemplGoFile, templURI := convertTemplGoToTemplURI(result[i].URI); isTemplGoFile {
result[i].URI = templURI
result[i].Range = p.convertGoRangeToTemplRange(templURI, result[i].Range)
}
}
return
}
func (p *Server) Definition(ctx context.Context, params *lsp.DefinitionParams) (result []lsp.Location /* Definition | DefinitionLink[] | null */, err error) {
p.Log.Info("client -> server: Definition")
defer p.Log.Info("client -> server: Definition end")
// Rewrite the request.
templURI := params.TextDocument.URI
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(templURI, params.Position)
if !ok {
return result, nil
}
// Call gopls and get the result.
result, err = p.Target.Definition(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
for i := 0; i < len(result); i++ {
if isTemplGoFile, templURI := convertTemplGoToTemplURI(result[i].URI); isTemplGoFile {
result[i].URI = templURI
result[i].Range = p.convertGoRangeToTemplRange(templURI, result[i].Range)
}
}
return
}
func (p *Server) DidChange(ctx context.Context, params *lsp.DidChangeTextDocumentParams) (err error) {
p.Log.Info("client -> server: DidChange", zap.Any("params", params))
defer p.Log.Info("client -> server: DidChange end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
p.Log.Error("not a templ file")
return
}
// Apply content changes to the cached template.
d, err := p.TemplSource.Apply(string(params.TextDocument.URI), params.ContentChanges)
if err != nil {
p.Log.Error("error applying changes", zap.Error(err))
return
}
// Update the Go code.
p.Log.Info("parsing template")
template, ok, err := p.parseTemplate(ctx, params.TextDocument.URI, d.String())
if err != nil {
p.Log.Error("parseTemplate failure", zap.Error(err))
}
if !ok {
return
}
w := new(strings.Builder)
// In future updates, we may pass `WithSkipCodeGeneratedComment` to the generator.
// This will enable a number of actions within gopls that it doesn't currently apply because
// it recognises templ code as being auto-generated.
//
// This change would increase the surface area of gopls that we use, so may surface a number of issues
// if enabled.
sm, _, err := generator.Generate(template, w)
if err != nil {
p.Log.Error("generate failure", zap.Error(err))
return
}
// Cache the sourcemap.
p.Log.Info("setting cache", zap.String("uri", string(params.TextDocument.URI)))
p.SourceMapCache.Set(string(params.TextDocument.URI), sm)
p.GoSource[string(params.TextDocument.URI)] = w.String()
// Change the path.
params.TextDocument.URI = goURI
params.TextDocument.TextDocumentIdentifier.URI = goURI
// Overwrite all the Go contents.
params.ContentChanges = []lsp.TextDocumentContentChangeEvent{{
Text: w.String(),
}}
return p.Target.DidChange(ctx, params)
}
func (p *Server) DidChangeConfiguration(ctx context.Context, params *lsp.DidChangeConfigurationParams) (err error) {
p.Log.Info("client -> server: DidChangeConfiguration")
defer p.Log.Info("client -> server: DidChangeConfiguration end")
return p.Target.DidChangeConfiguration(ctx, params)
}
func (p *Server) DidChangeWatchedFiles(ctx context.Context, params *lsp.DidChangeWatchedFilesParams) (err error) {
p.Log.Info("client -> server: DidChangeWatchedFiles")
defer p.Log.Info("client -> server: DidChangeWatchedFiles end")
return p.Target.DidChangeWatchedFiles(ctx, params)
}
func (p *Server) DidChangeWorkspaceFolders(ctx context.Context, params *lsp.DidChangeWorkspaceFoldersParams) (err error) {
p.Log.Info("client -> server: DidChangeWorkspaceFolders")
defer p.Log.Info("client -> server: DidChangeWorkspaceFolders end")
return p.Target.DidChangeWorkspaceFolders(ctx, params)
}
func (p *Server) DidClose(ctx context.Context, params *lsp.DidCloseTextDocumentParams) (err error) {
p.Log.Info("client -> server: DidClose")
defer p.Log.Info("client -> server: DidClose end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return p.Target.DidClose(ctx, params)
}
// Delete the template and sourcemaps from caches.
p.TemplSource.Delete(string(params.TextDocument.URI))
p.SourceMapCache.Delete(string(params.TextDocument.URI))
// Get gopls to delete the Go file from its cache.
params.TextDocument.URI = goURI
return p.Target.DidClose(ctx, params)
}
func (p *Server) DidOpen(ctx context.Context, params *lsp.DidOpenTextDocumentParams) (err error) {
p.Log.Info("client -> server: DidOpen", zap.String("uri", string(params.TextDocument.URI)))
defer p.Log.Info("client -> server: DidOpen end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return p.Target.DidOpen(ctx, params)
}
// Cache the template doc.
p.TemplSource.Set(string(params.TextDocument.URI), NewDocument(p.Log, params.TextDocument.Text))
// Parse the template.
template, ok, err := p.parseTemplate(ctx, params.TextDocument.URI, params.TextDocument.Text)
if err != nil {
p.Log.Error("parseTemplate failure", zap.Error(err))
}
if !ok {
p.Log.Info("parsing template did not succeed", zap.String("uri", string(params.TextDocument.URI)))
return nil
}
// Generate the output code and cache the source map and Go contents to use during completion
// requests.
w := new(strings.Builder)
sm, _, err := generator.Generate(template, w)
if err != nil {
return
}
p.Log.Info("setting source map cache contents", zap.String("uri", string(params.TextDocument.URI)))
p.SourceMapCache.Set(string(params.TextDocument.URI), sm)
// Set the Go contents.
params.TextDocument.Text = w.String()
p.GoSource[string(params.TextDocument.URI)] = params.TextDocument.Text
// Change the path.
params.TextDocument.URI = goURI
return p.Target.DidOpen(ctx, params)
}
func (p *Server) DidSave(ctx context.Context, params *lsp.DidSaveTextDocumentParams) (err error) {
p.Log.Info("client -> server: DidSave")
defer p.Log.Info("client -> server: DidSave end")
if isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI); isTemplFile {
params.TextDocument.URI = goURI
}
return p.Target.DidSave(ctx, params)
}
func (p *Server) DocumentColor(ctx context.Context, params *lsp.DocumentColorParams) (result []lsp.ColorInformation, err error) {
p.Log.Info("client -> server: DocumentColor")
defer p.Log.Info("client -> server: DocumentColor end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return p.Target.DocumentColor(ctx, params)
}
templURI := params.TextDocument.URI
params.TextDocument.URI = goURI
result, err = p.Target.DocumentColor(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
for i := 0; i < len(result); i++ {
result[i].Range = p.convertGoRangeToTemplRange(templURI, result[i].Range)
}
return
}
func (p *Server) DocumentHighlight(ctx context.Context, params *lsp.DocumentHighlightParams) (result []lsp.DocumentHighlight, err error) {
p.Log.Info("client -> server: DocumentHighlight")
defer p.Log.Info("client -> server: DocumentHighlight end")
return
}
func (p *Server) DocumentLink(ctx context.Context, params *lsp.DocumentLinkParams) (result []lsp.DocumentLink, err error) {
p.Log.Info("client -> server: DocumentLink", zap.String("uri", string(params.TextDocument.URI)))
defer p.Log.Info("client -> server: DocumentLink end")
return
}
func (p *Server) DocumentLinkResolve(ctx context.Context, params *lsp.DocumentLink) (result *lsp.DocumentLink, err error) {
p.Log.Info("client -> server: DocumentLinkResolve")
defer p.Log.Info("client -> server: DocumentLinkResolve end")
isTemplFile, goURI := convertTemplToGoURI(params.Target)
if !isTemplFile {
return p.Target.DocumentLinkResolve(ctx, params)
}
templURI := params.Target
params.Target = goURI
var ok bool
if params.Range, ok = p.convertTemplRangeToGoRange(templURI, params.Range); !ok {
return
}
// Rewrite the result.
result, err = p.Target.DocumentLinkResolve(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
result.Target = templURI
result.Range = p.convertGoRangeToTemplRange(templURI, result.Range)
return
}
func (p *Server) DocumentSymbol(ctx context.Context, params *lsp.DocumentSymbolParams) (result []interface{} /* []SymbolInformation | []DocumentSymbol */, err error) {
p.Log.Info("client -> server: DocumentSymbol")
defer p.Log.Info("client -> server: DocumentSymbol end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return p.Target.DocumentSymbol(ctx, params)
}
templURI := params.TextDocument.URI
params.TextDocument.URI = goURI
symbols, err := p.Target.DocumentSymbol(ctx, params)
if err != nil {
return nil, err
}
// recursively convert the ranges of the symbols and their children
var convertRange func(s *lsp.DocumentSymbol)
convertRange = func(s *lsp.DocumentSymbol) {
s.Range = p.convertGoRangeToTemplRange(templURI, s.Range)
s.SelectionRange = p.convertGoRangeToTemplRange(templURI, s.SelectionRange)
for i := 0; i < len(s.Children); i++ {
convertRange(&s.Children[i])
}
}
for _, s := range symbols {
if m, ok := s.(map[string]interface{}); ok {
s, err = mapToSymbol(m)
if err != nil {
return nil, err
}
}
switch s := s.(type) {
case lsp.DocumentSymbol:
convertRange(&s)
result = append(result, s)
case lsp.SymbolInformation:
s.Location.URI = templURI
s.Location.Range = p.convertGoRangeToTemplRange(templURI, s.Location.Range)
result = append(result, s)
}
}
return result, err
}
func (p *Server) ExecuteCommand(ctx context.Context, params *lsp.ExecuteCommandParams) (result interface{}, err error) {
p.Log.Info("client -> server: ExecuteCommand")
defer p.Log.Info("client -> server: ExecuteCommand end")
return p.Target.ExecuteCommand(ctx, params)
}
func (p *Server) FoldingRanges(ctx context.Context, params *lsp.FoldingRangeParams) (result []lsp.FoldingRange, err error) {
p.Log.Info("client -> server: FoldingRanges")
defer p.Log.Info("client -> server: FoldingRanges end")
// There are no folding ranges in templ files.
// return p.Target.FoldingRanges(ctx, params)
return []lsp.FoldingRange{}, nil
}
func (p *Server) Formatting(ctx context.Context, params *lsp.DocumentFormattingParams) (result []lsp.TextEdit, err error) {
p.Log.Info("client -> server: Formatting")
defer p.Log.Info("client -> server: Formatting end")
// Format the current document.
d, _ := p.TemplSource.Get(string(params.TextDocument.URI))
template, ok, err := p.parseTemplate(ctx, params.TextDocument.URI, d.String())
if err != nil {
p.Log.Error("parseTemplate failure", zap.Error(err))
return
}
if !ok {
return
}
p.Log.Info("attempting to organise imports", zap.String("uri", template.Filepath))
template, err = imports.Process(template)
if err != nil {
p.Log.Error("organise imports failure", zap.Error(err))
return
}
w := new(strings.Builder)
err = template.Write(w)
if err != nil {
p.Log.Error("handleFormatting: faled to write template", zap.Error(err))
return
}
// Replace everything.
result = append(result, lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{},
End: lsp.Position{Line: uint32(len(d.Lines)), Character: 0},
},
NewText: w.String(),
})
d.Replace(w.String())
return
}
func (p *Server) Hover(ctx context.Context, params *lsp.HoverParams) (result *lsp.Hover, err error) {
p.Log.Info("client -> server: Hover")
defer p.Log.Info("client -> server: Hover end")
// Rewrite the request.
templURI := params.TextDocument.URI
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
if !ok {
return nil, nil
}
// Call gopls.
result, err = p.Target.Hover(ctx, params)
if err != nil {
return
}
// Rewrite the response.
if result != nil && result.Range != nil {
p.Log.Info("hover: result returned")
r := p.convertGoRangeToTemplRange(templURI, *result.Range)
p.Log.Info("hover: setting range")
result.Range = &r
}
return
}
func (p *Server) Implementation(ctx context.Context, params *lsp.ImplementationParams) (result []lsp.Location, err error) {
p.Log.Info("client -> server: Implementation")
defer p.Log.Info("client -> server: Implementation end")
templURI := params.TextDocument.URI
// Rewrite the request.
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
if !ok {
return nil, nil
}
result, err = p.Target.Implementation(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
// Rewrite the response.
for i := 0; i < len(result); i++ {
r := result[i]
r.URI = templURI
r.Range = p.convertGoRangeToTemplRange(templURI, r.Range)
result[i] = r
}
return
}
func (p *Server) OnTypeFormatting(ctx context.Context, params *lsp.DocumentOnTypeFormattingParams) (result []lsp.TextEdit, err error) {
p.Log.Info("client -> server: OnTypeFormatting")
defer p.Log.Info("client -> server: OnTypeFormatting end")
templURI := params.TextDocument.URI
// Rewrite the request.
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
if !ok {
return nil, nil
}
// Get the response.
result, err = p.Target.OnTypeFormatting(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
// Rewrite the response.
for i := 0; i < len(result); i++ {
r := result[i]
r.Range = p.convertGoRangeToTemplRange(templURI, r.Range)
result[i] = r
}
return
}
func (p *Server) PrepareRename(ctx context.Context, params *lsp.PrepareRenameParams) (result *lsp.Range, err error) {
p.Log.Info("client -> server: PrepareRename")
defer p.Log.Info("client -> server: PrepareRename end")
templURI := params.TextDocument.URI
// Rewrite the request.
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
if !ok {
return nil, nil
}
// Get the response.
result, err = p.Target.PrepareRename(ctx, params)
if err != nil {
return
}
if result == nil {
return
}
// Rewrite the response.
output := p.convertGoRangeToTemplRange(templURI, *result)
return &output, nil
}
func (p *Server) RangeFormatting(ctx context.Context, params *lsp.DocumentRangeFormattingParams) (result []lsp.TextEdit, err error) {
p.Log.Info("client -> server: RangeFormatting")
defer p.Log.Info("client -> server: RangeFormatting end")
templURI := params.TextDocument.URI
// Rewrite the request.
var isTemplURI bool
isTemplURI, params.TextDocument.URI = convertTemplToGoURI(params.TextDocument.URI)
if !isTemplURI {
err = fmt.Errorf("not a templ file")
return
}
// Call gopls.
result, err = p.Target.RangeFormatting(ctx, params)
if err != nil {
return
}
// Rewrite the response.
for i := 0; i < len(result); i++ {
r := result[i]
r.Range = p.convertGoRangeToTemplRange(templURI, r.Range)
result[i] = r
}
return result, err
}
func (p *Server) References(ctx context.Context, params *lsp.ReferenceParams) (result []lsp.Location, err error) {
p.Log.Info("client -> server: References")
defer p.Log.Info("client -> server: References end")
// Rewrite the request.
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
if !ok {
return nil, nil
}
// Call gopls.
result, err = p.Target.References(ctx, params)
if err != nil {
return
}
// Rewrite the response.
for i := 0; i < len(result); i++ {
r := result[i]
isTemplURI, templURI := convertTemplGoToTemplURI(r.URI)
if isTemplURI {
p.Log.Info(fmt.Sprintf("references-%d - range conversion for %s", i, r.URI))
r.URI, r.Range = templURI, p.convertGoRangeToTemplRange(templURI, r.Range)
}
p.Log.Info(fmt.Sprintf("references-%d: %+v", i, r))
result[i] = r
}
return result, err
}
func (p *Server) Rename(ctx context.Context, params *lsp.RenameParams) (result *lsp.WorkspaceEdit, err error) {
p.Log.Info("client -> server: Rename")
defer p.Log.Info("client -> server: Rename end")
return p.Target.Rename(ctx, params)
}
func (p *Server) SignatureHelp(ctx context.Context, params *lsp.SignatureHelpParams) (result *lsp.SignatureHelp, err error) {
p.Log.Info("client -> server: SignatureHelp")
defer p.Log.Info("client -> server: SignatureHelp end")
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
if !ok {
return nil, nil
}
return p.Target.SignatureHelp(ctx, params)
}
func (p *Server) Symbols(ctx context.Context, params *lsp.WorkspaceSymbolParams) (result []lsp.SymbolInformation, err error) {
p.Log.Info("client -> server: Symbols")
defer p.Log.Info("client -> server: Symbols end")
return p.Target.Symbols(ctx, params)
}
func (p *Server) TypeDefinition(ctx context.Context, params *lsp.TypeDefinitionParams) (result []lsp.Location, err error) {
p.Log.Info("client -> server: TypeDefinition")
defer p.Log.Info("client -> server: TypeDefinition end")
var ok bool
ok, params.TextDocument.URI, params.Position = p.updatePosition(params.TextDocument.URI, params.Position)
if !ok {
return nil, nil
}
return p.Target.TypeDefinition(ctx, params)
}
func (p *Server) WillSave(ctx context.Context, params *lsp.WillSaveTextDocumentParams) (err error) {
p.Log.Info("client -> server: WillSave")
defer p.Log.Info("client -> server: WillSave end")
var ok bool
ok, params.TextDocument.URI = convertTemplToGoURI(params.TextDocument.URI)
if !ok {
p.Log.Error("not a templ file")
return nil
}
return p.Target.WillSave(ctx, params)
}
func (p *Server) WillSaveWaitUntil(ctx context.Context, params *lsp.WillSaveTextDocumentParams) (result []lsp.TextEdit, err error) {
p.Log.Info("client -> server: WillSaveWaitUntil")
defer p.Log.Info("client -> server: WillSaveWaitUntil end")
return p.Target.WillSaveWaitUntil(ctx, params)
}
func (p *Server) ShowDocument(ctx context.Context, params *lsp.ShowDocumentParams) (result *lsp.ShowDocumentResult, err error) {
p.Log.Info("client -> server: ShowDocument")
defer p.Log.Info("client -> server: ShowDocument end")
return p.Target.ShowDocument(ctx, params)
}
func (p *Server) WillCreateFiles(ctx context.Context, params *lsp.CreateFilesParams) (result *lsp.WorkspaceEdit, err error) {
p.Log.Info("client -> server: WillCreateFiles")
defer p.Log.Info("client -> server: WillCreateFiles end")
return p.Target.WillCreateFiles(ctx, params)
}
func (p *Server) DidCreateFiles(ctx context.Context, params *lsp.CreateFilesParams) (err error) {
p.Log.Info("client -> server: DidCreateFiles")
defer p.Log.Info("client -> server: DidCreateFiles end")
return p.Target.DidCreateFiles(ctx, params)
}
func (p *Server) WillRenameFiles(ctx context.Context, params *lsp.RenameFilesParams) (result *lsp.WorkspaceEdit, err error) {
p.Log.Info("client -> server: WillRenameFiles")
defer p.Log.Info("client -> server: WillRenameFiles end")
return p.Target.WillRenameFiles(ctx, params)
}
func (p *Server) DidRenameFiles(ctx context.Context, params *lsp.RenameFilesParams) (err error) {
p.Log.Info("client -> server: DidRenameFiles")
defer p.Log.Info("client -> server: DidRenameFiles end")
return p.Target.DidRenameFiles(ctx, params)
}
func (p *Server) WillDeleteFiles(ctx context.Context, params *lsp.DeleteFilesParams) (result *lsp.WorkspaceEdit, err error) {
p.Log.Info("client -> server: WillDeleteFiles")
defer p.Log.Info("client -> server: WillDeleteFiles end")
return p.Target.WillDeleteFiles(ctx, params)
}
func (p *Server) DidDeleteFiles(ctx context.Context, params *lsp.DeleteFilesParams) (err error) {
p.Log.Info("client -> server: DidDeleteFiles")
defer p.Log.Info("client -> server: DidDeleteFiles end")
return p.Target.DidDeleteFiles(ctx, params)
}
func (p *Server) CodeLensRefresh(ctx context.Context) (err error) {
p.Log.Info("client -> server: CodeLensRefresh")
defer p.Log.Info("client -> server: CodeLensRefresh end")
return p.Target.CodeLensRefresh(ctx)
}
func (p *Server) PrepareCallHierarchy(ctx context.Context, params *lsp.CallHierarchyPrepareParams) (result []lsp.CallHierarchyItem, err error) {
p.Log.Info("client -> server: PrepareCallHierarchy")
defer p.Log.Info("client -> server: PrepareCallHierarchy end")
return p.Target.PrepareCallHierarchy(ctx, params)
}
func (p *Server) IncomingCalls(ctx context.Context, params *lsp.CallHierarchyIncomingCallsParams) (result []lsp.CallHierarchyIncomingCall, err error) {
p.Log.Info("client -> server: IncomingCalls")
defer p.Log.Info("client -> server: IncomingCalls end")
return p.Target.IncomingCalls(ctx, params)
}
func (p *Server) OutgoingCalls(ctx context.Context, params *lsp.CallHierarchyOutgoingCallsParams) (result []lsp.CallHierarchyOutgoingCall, err error) {
p.Log.Info("client -> server: OutgoingCalls")
defer p.Log.Info("client -> server: OutgoingCalls end")
return p.Target.OutgoingCalls(ctx, params)
}
func (p *Server) SemanticTokensFull(ctx context.Context, params *lsp.SemanticTokensParams) (result *lsp.SemanticTokens, err error) {
p.Log.Info("client -> server: SemanticTokensFull")
defer p.Log.Info("client -> server: SemanticTokensFull end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return nil, nil
}
params.TextDocument.URI = goURI
return p.Target.SemanticTokensFull(ctx, params)
}
func (p *Server) SemanticTokensFullDelta(ctx context.Context, params *lsp.SemanticTokensDeltaParams) (result interface{} /* SemanticTokens | SemanticTokensDelta */, err error) {
p.Log.Info("client -> server: SemanticTokensFullDelta")
defer p.Log.Info("client -> server: SemanticTokensFullDelta end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return nil, nil
}
params.TextDocument.URI = goURI
return p.Target.SemanticTokensFullDelta(ctx, params)
}
func (p *Server) SemanticTokensRange(ctx context.Context, params *lsp.SemanticTokensRangeParams) (result *lsp.SemanticTokens, err error) {
p.Log.Info("client -> server: SemanticTokensRange")
defer p.Log.Info("client -> server: SemanticTokensRange end")
isTemplFile, goURI := convertTemplToGoURI(params.TextDocument.URI)
if !isTemplFile {
return nil, nil
}
params.TextDocument.URI = goURI
return p.Target.SemanticTokensRange(ctx, params)
}
func (p *Server) SemanticTokensRefresh(ctx context.Context) (err error) {
p.Log.Info("client -> server: SemanticTokensRefresh")
defer p.Log.Info("client -> server: SemanticTokensRefresh end")
return p.Target.SemanticTokensRefresh(ctx)
}
func (p *Server) LinkedEditingRange(ctx context.Context, params *lsp.LinkedEditingRangeParams) (result *lsp.LinkedEditingRanges, err error) {
p.Log.Info("client -> server: LinkedEditingRange")
defer p.Log.Info("client -> server: LinkedEditingRange end")
return p.Target.LinkedEditingRange(ctx, params)
}
func (p *Server) Moniker(ctx context.Context, params *lsp.MonikerParams) (result []lsp.Moniker, err error) {
p.Log.Info("client -> server: Moniker")
defer p.Log.Info("client -> server: Moniker end")
templURI := params.TextDocument.URI
var ok bool
ok, params.TextDocument.URI, params.TextDocumentPositionParams.Position = p.updatePosition(templURI, params.TextDocumentPositionParams.Position)
if !ok {
return nil, nil
}
return p.Target.Moniker(ctx, params)
}
func (p *Server) Request(ctx context.Context, method string, params interface{}) (result interface{}, err error) {
p.Log.Info("client -> server: Request")
defer p.Log.Info("client -> server: Request end")
return p.Target.Request(ctx, method, params)
}
func mapToSymbol(m map[string]interface{}) (interface{}, error) {
b, err := json.Marshal(m)
if err != nil {
return nil, err
}
if _, ok := m["selectionRange"]; ok {
var s lsp.DocumentSymbol
if err := json.Unmarshal(b, &s); err != nil {
return nil, err
}
return s, nil
}
var s lsp.SymbolInformation
if err := json.Unmarshal(b, &s); err != nil {
return nil, err
}
return s, nil
}
package proxy
import (
"sync"
"github.com/a-h/templ/parser/v2"
)
// NewSourceMapCache creates a cache of .templ file URIs to the source map.
func NewSourceMapCache() *SourceMapCache {
return &SourceMapCache{
m: new(sync.Mutex),
uriToSourceMap: make(map[string]*parser.SourceMap),
}
}
// SourceMapCache is a cache of .templ file URIs to the source map.
type SourceMapCache struct {
m *sync.Mutex
uriToSourceMap map[string]*parser.SourceMap
}
func (fc *SourceMapCache) Set(uri string, m *parser.SourceMap) {
fc.m.Lock()
defer fc.m.Unlock()
fc.uriToSourceMap[uri] = m
}
func (fc *SourceMapCache) Get(uri string) (m *parser.SourceMap, ok bool) {
fc.m.Lock()
defer fc.m.Unlock()
m, ok = fc.uriToSourceMap[uri]
return
}
func (fc *SourceMapCache) Delete(uri string) {
fc.m.Lock()
defer fc.m.Unlock()
delete(fc.uriToSourceMap, uri)
}
func (fc *SourceMapCache) URIs() (uris []string) {
fc.m.Lock()
defer fc.m.Unlock()
uris = make([]string, len(fc.uriToSourceMap))
var i int
for k := range fc.uriToSourceMap {
uris[i] = k
i++
}
return uris
}
package lspcmd
import (
"errors"
"io"
"go.uber.org/zap"
)
// stdrwc (standard read/write closer) reads from stdin, and writes to stdout.
func newStdRwc(log *zap.Logger, name string, w io.Writer, r io.Reader) stdrwc {
return stdrwc{
log: log,
name: name,
w: w,
r: r,
}
}
type stdrwc struct {
log *zap.Logger
name string
w io.Writer
r io.Reader
}
func (s stdrwc) Read(p []byte) (int, error) {
return s.r.Read(p)
}
func (s stdrwc) Write(p []byte) (int, error) {
return s.w.Write(p)
}
func (s stdrwc) Close() error {
s.log.Info("rwc: closing", zap.String("name", s.name))
var errs []error
if closer, isCloser := s.r.(io.Closer); isCloser {
if err := closer.Close(); err != nil {
s.log.Error("rwc: error closing reader", zap.String("name", s.name), zap.Error(err))
errs = append(errs, err)
}
}
if closer, isCloser := s.w.(io.Closer); isCloser {
if err := closer.Close(); err != nil {
s.log.Error("rwc: error closing writer", zap.String("name", s.name), zap.Error(err))
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
package main
import (
"context"
"flag"
"fmt"
"io"
"log/slog"
"os"
"os/signal"
"runtime"
"github.com/a-h/templ"
"github.com/a-h/templ/cmd/templ/fmtcmd"
"github.com/a-h/templ/cmd/templ/generatecmd"
"github.com/a-h/templ/cmd/templ/infocmd"
"github.com/a-h/templ/cmd/templ/lspcmd"
"github.com/a-h/templ/cmd/templ/sloghandler"
"github.com/fatih/color"
)
func main() {
code := run(os.Stdin, os.Stdout, os.Stderr, os.Args)
if code != 0 {
os.Exit(code)
}
}
const usageText = `usage: templ <command> [<args>...]
templ - build HTML UIs with Go
See docs at https://templ.guide
commands:
generate Generates Go code from templ files
fmt Formats templ files
lsp Starts a language server for templ files
info Displays information about the templ environment
version Prints the version
`
func run(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int) {
if len(args) < 2 {
fmt.Fprint(stderr, usageText)
return 64 // EX_USAGE
}
switch args[1] {
case "info":
return infoCmd(stdout, stderr, args[2:])
case "generate":
return generateCmd(stdout, stderr, args[2:])
case "fmt":
return fmtCmd(stdin, stdout, stderr, args[2:])
case "lsp":
return lspCmd(stdin, stdout, stderr, args[2:])
case "version", "--version":
fmt.Fprintln(stdout, templ.Version())
return 0
case "help", "-help", "--help", "-h":
fmt.Fprint(stdout, usageText)
return 0
}
fmt.Fprint(stderr, usageText)
return 64 // EX_USAGE
}
func newLogger(logLevel string, verbose bool, stderr io.Writer) *slog.Logger {
if verbose {
logLevel = "debug"
}
level := slog.LevelInfo.Level()
switch logLevel {
case "debug":
level = slog.LevelDebug.Level()
case "warn":
level = slog.LevelWarn.Level()
case "error":
level = slog.LevelError.Level()
}
return slog.New(sloghandler.NewHandler(stderr, &slog.HandlerOptions{
AddSource: logLevel == "debug",
Level: level,
}))
}
const infoUsageText = `usage: templ info [<args>...]
Displays information about the templ environment.
Args:
-json
Output information in JSON format to stdout. (default false)
-v
Set log verbosity level to "debug". (default "info")
-log-level
Set log verbosity level. (default "info", options: "debug", "info", "warn", "error")
-help
Print help and exit.
`
func infoCmd(stdout, stderr io.Writer, args []string) (code int) {
cmd := flag.NewFlagSet("diagnose", flag.ExitOnError)
jsonFlag := cmd.Bool("json", false, "")
verboseFlag := cmd.Bool("v", false, "")
logLevelFlag := cmd.String("log-level", "info", "")
helpFlag := cmd.Bool("help", false, "")
err := cmd.Parse(args)
if err != nil {
fmt.Fprint(stderr, infoUsageText)
return 64 // EX_USAGE
}
if *helpFlag {
fmt.Fprint(stdout, infoUsageText)
return
}
log := newLogger(*logLevelFlag, *verboseFlag, stderr)
ctx, cancel := context.WithCancel(context.Background())
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
go func() {
<-signalChan
fmt.Fprintln(stderr, "Stopping...")
cancel()
}()
err = infocmd.Run(ctx, log, stdout, infocmd.Arguments{
JSON: *jsonFlag,
})
if err != nil {
color.New(color.FgRed).Fprint(stderr, "(✗) ")
fmt.Fprintln(stderr, "Command failed: "+err.Error())
return 1
}
return 0
}
const generateUsageText = `usage: templ generate [<args>...]
Generates Go code from templ files.
Args:
-path <path>
Generates code for all files in path. (default .)
-f <file>
Optionally generates code for a single file, e.g. -f header.templ
-stdout
Prints to stdout instead of writing generated files to the filesystem.
Only applicable when -f is used.
-source-map-visualisations
Set to true to generate HTML files to visualise the templ code and its corresponding Go code.
-include-version
Set to false to skip inclusion of the templ version in the generated code. (default true)
-include-timestamp
Set to true to include the current time in the generated code.
-watch
Set to true to watch the path for changes and regenerate code.
-cmd <cmd>
Set the command to run after generating code.
-proxy
Set the URL to proxy after generating code and executing the command.
-proxyport
The port the proxy will listen on. (default 7331)
-proxybind
The address the proxy will listen on. (default 127.0.0.1)
-notify-proxy
If present, the command will issue a reload event to the proxy 127.0.0.1:7331, or use proxyport and proxybind to specify a different address.
-w
Number of workers to use when generating code. (default runtime.NumCPUs)
-lazy
Only generate .go files if the source .templ file is newer.
-pprof
Port to run the pprof server on.
-keep-orphaned-files
Keeps orphaned generated templ files. (default false)
-v
Set log verbosity level to "debug". (default "info")
-log-level
Set log verbosity level. (default "info", options: "debug", "info", "warn", "error")
-help
Print help and exit.
Examples:
Generate code for all files in the current directory and subdirectories:
templ generate
Generate code for a single file:
templ generate -f header.templ
Watch the current directory and subdirectories for changes and regenerate code:
templ generate -watch
`
func generateCmd(stdout, stderr io.Writer, args []string) (code int) {
cmd := flag.NewFlagSet("generate", flag.ExitOnError)
fileNameFlag := cmd.String("f", "", "")
pathFlag := cmd.String("path", ".", "")
toStdoutFlag := cmd.Bool("stdout", false, "")
sourceMapVisualisationsFlag := cmd.Bool("source-map-visualisations", false, "")
includeVersionFlag := cmd.Bool("include-version", true, "")
includeTimestampFlag := cmd.Bool("include-timestamp", false, "")
watchFlag := cmd.Bool("watch", false, "")
openBrowserFlag := cmd.Bool("open-browser", true, "")
cmdFlag := cmd.String("cmd", "", "")
proxyFlag := cmd.String("proxy", "", "")
proxyPortFlag := cmd.Int("proxyport", 7331, "")
proxyBindFlag := cmd.String("proxybind", "127.0.0.1", "")
notifyProxyFlag := cmd.Bool("notify-proxy", false, "")
workerCountFlag := cmd.Int("w", runtime.NumCPU(), "")
pprofPortFlag := cmd.Int("pprof", 0, "")
keepOrphanedFilesFlag := cmd.Bool("keep-orphaned-files", false, "")
verboseFlag := cmd.Bool("v", false, "")
logLevelFlag := cmd.String("log-level", "info", "")
lazyFlag := cmd.Bool("lazy", false, "")
helpFlag := cmd.Bool("help", false, "")
err := cmd.Parse(args)
if err != nil {
fmt.Fprint(stderr, generateUsageText)
return 64 // EX_USAGE
}
if *helpFlag {
fmt.Fprint(stdout, generateUsageText)
return
}
log := newLogger(*logLevelFlag, *verboseFlag, stderr)
ctx, cancel := context.WithCancel(context.Background())
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
go func() {
<-signalChan
fmt.Fprintln(stderr, "Stopping...")
cancel()
}()
var fw generatecmd.FileWriterFunc
if *toStdoutFlag {
fw = generatecmd.WriterFileWriter(stdout)
}
err = generatecmd.Run(ctx, log, generatecmd.Arguments{
FileName: *fileNameFlag,
Path: *pathFlag,
FileWriter: fw,
Watch: *watchFlag,
OpenBrowser: *openBrowserFlag,
Command: *cmdFlag,
Proxy: *proxyFlag,
ProxyPort: *proxyPortFlag,
ProxyBind: *proxyBindFlag,
NotifyProxy: *notifyProxyFlag,
WorkerCount: *workerCountFlag,
GenerateSourceMapVisualisations: *sourceMapVisualisationsFlag,
IncludeVersion: *includeVersionFlag,
IncludeTimestamp: *includeTimestampFlag,
PPROFPort: *pprofPortFlag,
KeepOrphanedFiles: *keepOrphanedFilesFlag,
Lazy: *lazyFlag,
})
if err != nil {
color.New(color.FgRed).Fprint(stderr, "(✗) ")
fmt.Fprintln(stderr, "Command failed: "+err.Error())
return 1
}
return 0
}
const fmtUsageText = `usage: templ fmt [<args> ...]
Format all files in directory:
templ fmt .
Format stdin to stdout:
templ fmt < header.templ
Format file or directory to stdout:
templ fmt -stdout FILE
Args:
-stdout
Prints to stdout instead of in-place format
-stdin-filepath
Provides the formatter with filepath context when using -stdout.
Required for organising imports.
-v
Set log verbosity level to "debug". (default "info")
-log-level
Set log verbosity level. (default "info", options: "debug", "info", "warn", "error")
-w
Number of workers to use when formatting code. (default runtime.NumCPUs).
-fail
Fails with exit code 1 if files are changed. (e.g. in CI)
-help
Print help and exit.
`
func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int) {
cmd := flag.NewFlagSet("fmt", flag.ExitOnError)
helpFlag := cmd.Bool("help", false, "")
workerCountFlag := cmd.Int("w", runtime.NumCPU(), "")
verboseFlag := cmd.Bool("v", false, "")
logLevelFlag := cmd.String("log-level", "info", "")
failIfChanged := cmd.Bool("fail", false, "")
stdoutFlag := cmd.Bool("stdout", false, "")
stdinFilepath := cmd.String("stdin-filepath", "", "")
err := cmd.Parse(args)
if err != nil {
fmt.Fprint(stderr, fmtUsageText)
return 64 // EX_USAGE
}
if *helpFlag {
fmt.Fprint(stdout, fmtUsageText)
return
}
log := newLogger(*logLevelFlag, *verboseFlag, stderr)
err = fmtcmd.Run(log, stdin, stdout, fmtcmd.Arguments{
ToStdout: *stdoutFlag,
Files: cmd.Args(),
WorkerCount: *workerCountFlag,
StdinFilepath: *stdinFilepath,
FailIfChanged: *failIfChanged,
})
if err != nil {
return 1
}
return 0
}
const lspUsageText = `usage: templ lsp [<args> ...]
Starts a language server for templ.
Args:
-log string
The file to log templ LSP output to, or leave empty to disable logging.
-goplsLog string
The file to log gopls output, or leave empty to disable logging.
-goplsRPCTrace
Set gopls to log input and output messages.
-help
Print help and exit.
-pprof
Enable pprof web server (default address is localhost:9999)
-http string
Enable http debug server by setting a listen address (e.g. localhost:7474)
`
func lspCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int) {
cmd := flag.NewFlagSet("lsp", flag.ExitOnError)
logFlag := cmd.String("log", "", "")
goplsLog := cmd.String("goplsLog", "", "")
goplsRPCTrace := cmd.Bool("goplsRPCTrace", false, "")
helpFlag := cmd.Bool("help", false, "")
pprofFlag := cmd.Bool("pprof", false, "")
httpDebugFlag := cmd.String("http", "", "")
err := cmd.Parse(args)
if err != nil {
fmt.Fprint(stderr, lspUsageText)
return 64 // EX_USAGE
}
if *helpFlag {
fmt.Fprint(stdout, lspUsageText)
return
}
err = lspcmd.Run(stdin, stdout, stderr, lspcmd.Arguments{
Log: *logFlag,
GoplsLog: *goplsLog,
GoplsRPCTrace: *goplsRPCTrace,
PPROF: *pprofFlag,
HTTPDebug: *httpDebugFlag,
})
if err != nil {
fmt.Fprintln(stderr, err.Error())
return 1
}
return 0
}
package processor
import (
"io/fs"
"path"
"path/filepath"
"strings"
"sync"
"time"
)
type Result struct {
FileName string
Duration time.Duration
Error error
ChangesMade bool
}
func Process(dir string, f func(fileName string) (error, bool), workerCount int, results chan<- Result) {
templates := make(chan string)
go func() {
defer close(templates)
if err := FindTemplates(dir, templates); err != nil {
results <- Result{Error: err}
}
}()
ProcessChannel(templates, dir, f, workerCount, results)
}
func shouldSkipDir(dir string) bool {
if dir == "." {
return false
}
if dir == "vendor" || dir == "node_modules" {
return true
}
_, name := path.Split(dir)
// These directories are ignored by the Go tool.
if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
return true
}
return false
}
func FindTemplates(srcPath string, output chan<- string) (err error) {
return filepath.WalkDir(srcPath, func(currentPath string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() && shouldSkipDir(currentPath) {
return filepath.SkipDir
}
if !info.IsDir() && strings.HasSuffix(currentPath, ".templ") {
output <- currentPath
}
return nil
})
}
func ProcessChannel(templates <-chan string, dir string, f func(fileName string) (error, bool), workerCount int, results chan<- Result) {
defer close(results)
var wg sync.WaitGroup
wg.Add(workerCount)
for i := 0; i < workerCount; i++ {
go func() {
defer wg.Done()
for sourceFileName := range templates {
start := time.Now()
outErr, outChanged := f(sourceFileName)
results <- Result{
FileName: sourceFileName,
Error: outErr,
Duration: time.Since(start),
ChangesMade: outChanged,
}
}
}()
}
wg.Wait()
}
package sloghandler
import (
"context"
"io"
"log/slog"
"strings"
"sync"
"github.com/fatih/color"
)
var _ slog.Handler = &Handler{}
type Handler struct {
h slog.Handler
m *sync.Mutex
w io.Writer
}
var levelToIcon = map[slog.Level]string{
slog.LevelDebug: "(✓)",
slog.LevelInfo: "(✓)",
slog.LevelWarn: "(!)",
slog.LevelError: "(✗)",
}
var levelToColor = map[slog.Level]*color.Color{
slog.LevelDebug: color.New(color.FgCyan),
slog.LevelInfo: color.New(color.FgGreen),
slog.LevelWarn: color.New(color.FgYellow),
slog.LevelError: color.New(color.FgRed),
}
func NewHandler(w io.Writer, opts *slog.HandlerOptions) *Handler {
if opts == nil {
opts = &slog.HandlerOptions{}
}
return &Handler{
w: w,
h: slog.NewTextHandler(w, &slog.HandlerOptions{
Level: opts.Level,
AddSource: opts.AddSource,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if opts.ReplaceAttr != nil {
a = opts.ReplaceAttr(groups, a)
}
if a.Key == slog.LevelKey {
level, ok := levelToIcon[a.Value.Any().(slog.Level)]
if !ok {
level = a.Value.Any().(slog.Level).String()
}
a.Value = slog.StringValue(level)
return a
}
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
}),
m: &sync.Mutex{},
}
}
func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool {
return h.h.Enabled(ctx, level)
}
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &Handler{h: h.h.WithAttrs(attrs), w: h.w, m: h.m}
}
func (h *Handler) WithGroup(name string) slog.Handler {
return &Handler{h: h.h.WithGroup(name), w: h.w, m: h.m}
}
var keyValueColor = color.New(color.Faint & color.FgBlack)
func (h *Handler) Handle(ctx context.Context, r slog.Record) (err error) {
var sb strings.Builder
sb.WriteString(levelToColor[r.Level].Sprint(levelToIcon[r.Level]))
sb.WriteString(" ")
sb.WriteString(r.Message)
if r.NumAttrs() != 0 {
sb.WriteString(" [")
r.Attrs(func(a slog.Attr) bool {
sb.WriteString(keyValueColor.Sprintf(" %s=%s", a.Key, a.Value.String()))
return true
})
sb.WriteString(" ]")
}
sb.WriteString("\n")
h.m.Lock()
defer h.m.Unlock()
_, err = io.WriteString(h.w, sb.String())
return err
}
package testproject
import (
"bytes"
"embed"
"fmt"
"os"
"path/filepath"
"strings"
)
//go:embed testdata/*
var testdata embed.FS
func Create(moduleRoot string) (dir string, err error) {
dir, err = os.MkdirTemp("", "templ_test_*")
if err != nil {
return dir, fmt.Errorf("failed to make test dir: %w", err)
}
files, err := testdata.ReadDir("testdata")
if err != nil {
return dir, fmt.Errorf("failed to read embedded dir: %w", err)
}
for _, file := range files {
if file.IsDir() {
if err = os.MkdirAll(filepath.Join(dir, file.Name()), 0777); err != nil {
return dir, fmt.Errorf("failed to create dir: %w", err)
}
continue
}
src := filepath.Join("testdata", file.Name())
data, err := testdata.ReadFile(src)
if err != nil {
return dir, fmt.Errorf("failed to read file: %w", err)
}
target := filepath.Join(dir, file.Name())
if file.Name() == "go.mod.embed" {
data = bytes.ReplaceAll(data, []byte("{moduleRoot}"), []byte(moduleRoot))
target = filepath.Join(dir, "go.mod")
}
err = os.WriteFile(target, data, 0660)
if err != nil {
return dir, fmt.Errorf("failed to copy file: %w", err)
}
}
files, err = testdata.ReadDir("testdata/css-classes")
if err != nil {
return dir, fmt.Errorf("failed to read embedded dir: %w", err)
}
for _, file := range files {
src := filepath.Join("testdata", "css-classes", file.Name())
data, err := testdata.ReadFile(src)
if err != nil {
return dir, fmt.Errorf("failed to read file: %w", err)
}
target := filepath.Join(dir, "css-classes", file.Name())
err = os.WriteFile(target, data, 0660)
if err != nil {
return dir, fmt.Errorf("failed to copy file: %w", err)
}
}
return dir, nil
}
func MustReplaceLine(file string, line int, replacement string) string {
lines := strings.Split(file, "\n")
lines[line-1] = replacement
return strings.Join(lines, "\n")
}
// Code generated by templ - DO NOT EDIT.
package visualize
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func row() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`display:flex;`)
templ_7745c5c3_CSSID := templ.CSSID(`row`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func column() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`flex:50%;`)
templ_7745c5c3_CSSBuilder.WriteString(`overflow-y:scroll;`)
templ_7745c5c3_CSSBuilder.WriteString(`max-height:100vh;`)
templ_7745c5c3_CSSID := templ.CSSID(`column`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func code() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`font-family:monospace;`)
templ_7745c5c3_CSSID := templ.CSSID(`code`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func combine(templFileName string, left, right templ.Component) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html><head><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(templFileName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 20, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("- Source Map Visualisation</title><style type=\"text/css\">\n\t\t\t\t.mapped { background-color: green }\n\t\t\t\t.highlighted { background-color: yellow }\n\t\t\t</style></head><body><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templFileName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 27, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 = []any{templ.Classes(row())}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 = []any{templ.Classes(column(), code())}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = left.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 = []any{templ.Classes(column(), code())}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = right.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func highlight(sourceId, targetId string) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_highlight_ae80`,
Function: `function __templ_highlight_ae80(sourceId, targetId){let items = document.getElementsByClassName(sourceId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.add("highlighted");
}
items = document.getElementsByClassName(targetId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.add("highlighted");
}
}`,
Call: templ.SafeScript(`__templ_highlight_ae80`, sourceId, targetId),
CallInline: templ.SafeScriptInline(`__templ_highlight_ae80`, sourceId, targetId),
}
}
func removeHighlight(sourceId, targetId string) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_removeHighlight_58f2`,
Function: `function __templ_removeHighlight_58f2(sourceId, targetId){let items = document.getElementsByClassName(sourceId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.remove("highlighted");
}
items = document.getElementsByClassName(targetId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.remove("highlighted");
}
}`,
Call: templ.SafeScript(`__templ_removeHighlight_58f2`, sourceId, targetId),
CallInline: templ.SafeScriptInline(`__templ_removeHighlight_58f2`, sourceId, targetId),
}
}
func mappedCharacter(s string, sourceID, targetID string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var11 = []any{templ.Classes(templ.Class("mapped"), templ.Class(sourceID), templ.Class(targetID))}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, highlight(sourceID, targetID), removeHighlight(sourceID, targetID))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" onMouseOver=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 templ.ComponentScript = highlight(sourceID, targetID)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" onMouseOut=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 templ.ComponentScript = removeHighlight(sourceID, targetID)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(s)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/templ/visualize/sourcemapvisualisation.templ`, Line: 63, Col: 200}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package visualize
import (
"context"
"fmt"
"html"
"io"
"strconv"
"strings"
"github.com/a-h/templ"
"github.com/a-h/templ/parser/v2"
)
func HTML(templFileName string, templContents, goContents string, sourceMap *parser.SourceMap) templ.Component {
tl := templLines{contents: string(templContents), sourceMap: sourceMap}
gl := goLines{contents: string(goContents), sourceMap: sourceMap}
return combine(templFileName, tl, gl)
}
type templLines struct {
contents string
sourceMap *parser.SourceMap
}
func (tl templLines) Render(ctx context.Context, w io.Writer) (err error) {
templLines := strings.Split(tl.contents, "\n")
for lineIndex, line := range templLines {
if _, err = w.Write([]byte("<span>" + strconv.Itoa(lineIndex) + " </span>\n")); err != nil {
return
}
for colIndex, c := range line {
if tgt, ok := tl.sourceMap.TargetPositionFromSource(uint32(lineIndex), uint32(colIndex)); ok {
sourceID := fmt.Sprintf("src_%d_%d", lineIndex, colIndex)
targetID := fmt.Sprintf("tgt_%d_%d", tgt.Line, tgt.Col)
if err := mappedCharacter(string(c), sourceID, targetID).Render(ctx, w); err != nil {
return err
}
} else {
s := html.EscapeString(string(c))
s = strings.ReplaceAll(s, "\t", " ")
s = strings.ReplaceAll(s, " ", " ")
if _, err := w.Write([]byte(s)); err != nil {
return err
}
}
}
if _, err = w.Write([]byte("\n<br/>\n")); err != nil {
return
}
}
return nil
}
type goLines struct {
contents string
sourceMap *parser.SourceMap
}
func (gl goLines) Render(ctx context.Context, w io.Writer) (err error) {
templLines := strings.Split(gl.contents, "\n")
for lineIndex, line := range templLines {
if _, err = w.Write([]byte("<span>" + strconv.Itoa(lineIndex) + " </span>\n")); err != nil {
return
}
for colIndex, c := range line {
if src, ok := gl.sourceMap.SourcePositionFromTarget(uint32(lineIndex), uint32(colIndex)); ok {
sourceID := fmt.Sprintf("src_%d_%d", src.Line, src.Col)
targetID := fmt.Sprintf("tgt_%d_%d", lineIndex, colIndex)
if err := mappedCharacter(string(c), sourceID, targetID).Render(ctx, w); err != nil {
return err
}
} else {
s := html.EscapeString(string(c))
s = strings.ReplaceAll(s, "\t", " ")
s = strings.ReplaceAll(s, " ", " ")
if _, err := w.Write([]byte(s)); err != nil {
return err
}
}
}
if _, err = w.Write([]byte("\n<br/>\n")); err != nil {
return
}
}
return nil
}
package main
import (
"fmt"
"log"
"net/http"
"github.com/a-h/templ"
)
func main() {
// Use a template that doesn't take parameters.
http.Handle("/", templ.Handler(home()))
// Use a template that accesses data or handles form posts.
http.Handle("/posts", NewPostsHandler())
// Start the server.
fmt.Println("listening on http://localhost:8000")
if err := http.ListenAndServe("localhost:8000", nil); err != nil {
log.Printf("error listening: %v", err)
}
}
func NewPostsHandler() PostsHandler {
// Replace this in-memory function with a call to a database.
postsGetter := func() (posts []Post, err error) {
return []Post{{Name: "templ", Author: "author"}}, nil
}
return PostsHandler{
GetPosts: postsGetter,
Log: log.Default(),
}
}
type PostsHandler struct {
Log *log.Logger
GetPosts func() ([]Post, error)
}
func (ph PostsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ps, err := ph.GetPosts()
if err != nil {
ph.Log.Printf("failed to get posts: %v", err)
http.Error(w, "failed to retrieve posts", http.StatusInternalServerError)
return
}
templ.Handler(posts(ps)).ServeHTTP(w, r)
}
type Post struct {
Name string
Author string
}
// Code generated by templ - DO NOT EDIT.
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"time"
)
func headerTemplate(name string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header data-testid=\"headerTemplate\"><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/blog/posts.templ`, Line: 10, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1></header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func footerTemplate() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer data-testid=\"footerTemplate\"><div>© ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", time.Now().Year()))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/blog/posts.templ`, Line: 16, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func navTemplate() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav data-testid=\"navTemplate\"><ul><li><a href=\"/\">Home</a></li><li><a href=\"/posts\">Posts</a></li></ul></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func layout(name string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html><head><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/blog/posts.templ`, Line: 31, Col: 21}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title></head><body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = headerTemplate(name).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = navTemplate().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var6.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</main></body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = footerTemplate().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func postsTemplate(posts []Post) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div data-testid=\"postsTemplate\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, p := range posts {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div data-testid=\"postsTemplatePost\"><div data-testid=\"postsTemplatePostName\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(p.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/blog/posts.templ`, Line: 47, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div data-testid=\"postsTemplatePostAuthor\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(p.Author)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/blog/posts.templ`, Line: 48, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func home() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div data-testid=\"homeTemplate\">Welcome to my website.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = layout("Home").Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func posts(posts []Post) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var13 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var14 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = postsTemplate(posts).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = layout("Posts").Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package templ
import (
"context"
"io"
)
// Flush flushes the output buffer after all its child components have been rendered.
func Flush() FlushComponent {
return FlushComponent{}
}
type FlushComponent struct {
}
type flusherError interface {
Flush() error
}
type flusher interface {
Flush()
}
func (f FlushComponent) Render(ctx context.Context, w io.Writer) (err error) {
if err = GetChildren(ctx).Render(ctx, w); err != nil {
return err
}
switch w := w.(type) {
case flusher:
w.Flush()
return nil
case flusherError:
return w.Flush()
}
return nil
}
package generator
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"html"
"io"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
"unicode"
_ "embed"
"github.com/a-h/templ/parser/v2"
)
type GenerateOpt func(g *generator) error
// WithVersion enables the version to be included in the generated code.
func WithVersion(v string) GenerateOpt {
return func(g *generator) error {
g.version = v
return nil
}
}
// WithTimestamp enables the generated date to be included in the generated code.
func WithTimestamp(d time.Time) GenerateOpt {
return func(g *generator) error {
g.generatedDate = d.Format(time.RFC3339)
return nil
}
}
// WithFileName sets the filename of the templ file in template rendering error messages.
func WithFileName(name string) GenerateOpt {
return func(g *generator) error {
if filepath.IsAbs(name) {
_, g.fileName = filepath.Split(name)
return nil
}
g.fileName = name
return nil
}
}
func WithExtractStrings() GenerateOpt {
return func(g *generator) error {
g.w.literalWriter = &watchLiteralWriter{
builder: &strings.Builder{},
}
return nil
}
}
// WithSkipCodeGeneratedComment skips the code generated comment at the top of the file.
// gopls disables edit related functionality for generated files, so the templ LSP may
// wish to skip generation of this comment so that gopls provides expected results.
func WithSkipCodeGeneratedComment() GenerateOpt {
return func(g *generator) error {
g.skipCodeGeneratedComment = true
return nil
}
}
// Generate generates Go code from the input template file to w, and returns a map of the location of Go expressions in the template
// to the location of the generated Go code in the output.
func Generate(template parser.TemplateFile, w io.Writer, opts ...GenerateOpt) (sm *parser.SourceMap, literals string, err error) {
g := &generator{
tf: template,
w: NewRangeWriter(w),
sourceMap: parser.NewSourceMap(),
}
for _, opt := range opts {
if err = opt(g); err != nil {
return
}
}
err = g.generate()
sm = g.sourceMap
literals = g.w.literalWriter.literals()
return
}
type generator struct {
tf parser.TemplateFile
w *RangeWriter
sourceMap *parser.SourceMap
variableID int
childrenVar string
// version of templ.
version string
// generatedDate to include as a comment.
generatedDate string
// fileName to include in error messages if string expressions return an error.
fileName string
// skipCodeGeneratedComment skips the code generated comment at the top of the file.
skipCodeGeneratedComment bool
}
func (g *generator) generate() (err error) {
if err = g.writeCodeGeneratedComment(); err != nil {
return
}
if err = g.writeVersionComment(); err != nil {
return
}
if err = g.writeGeneratedDateComment(); err != nil {
return
}
if err = g.writeHeader(); err != nil {
return
}
if err = g.writePackage(); err != nil {
return
}
if err = g.writeImports(); err != nil {
return
}
if err = g.writeTemplateNodes(); err != nil {
return
}
if err = g.writeBlankAssignmentForRuntimeImport(); err != nil {
return
}
return err
}
// See https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source
// Automatically generated files have a comment in the header that instructs the LSP
// to stop operating.
func (g *generator) writeCodeGeneratedComment() (err error) {
if g.skipCodeGeneratedComment {
// Write an empty comment so that the file is the same shape.
_, err = g.w.Write("//\n\n")
return err
}
_, err = g.w.Write("// Code generated by templ - DO NOT EDIT.\n\n")
return err
}
func (g *generator) writeVersionComment() (err error) {
if g.version != "" {
_, err = g.w.Write("// templ: version: " + g.version + "\n")
}
return err
}
func (g *generator) writeGeneratedDateComment() (err error) {
if g.generatedDate != "" {
_, err = g.w.Write("// templ: generated: " + g.generatedDate + "\n")
}
return err
}
func (g *generator) writeHeader() (err error) {
if len(g.tf.Header) == 0 {
return nil
}
for _, n := range g.tf.Header {
if err := g.writeGoExpression(n); err != nil {
return err
}
}
return err
}
func (g *generator) writePackage() error {
var r parser.Range
var err error
// package ...
if r, err = g.w.Write(g.tf.Package.Expression.Value + "\n\n"); err != nil {
return err
}
g.sourceMap.Add(g.tf.Package.Expression, r)
if _, err = g.w.Write("//lint:file-ignore SA4006 This context is only used if a nested component is present.\n\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeImports() error {
var err error
// Always import templ because it's the interface type of all templates.
if _, err = g.w.Write("import \"github.com/a-h/templ\"\n"); err != nil {
return err
}
if _, err = g.w.Write("import templruntime \"github.com/a-h/templ/runtime\"\n"); err != nil {
return err
}
if _, err = g.w.Write("\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeTemplateNodes() error {
for i := 0; i < len(g.tf.Nodes); i++ {
switch n := g.tf.Nodes[i].(type) {
case parser.TemplateFileGoExpression:
if err := g.writeGoExpression(n); err != nil {
return err
}
case parser.HTMLTemplate:
if err := g.writeTemplate(i, n); err != nil {
return err
}
case parser.CSSTemplate:
if err := g.writeCSS(n); err != nil {
return err
}
case parser.ScriptTemplate:
if err := g.writeScript(n); err != nil {
return err
}
default:
return fmt.Errorf("unknown node type: %v", reflect.TypeOf(n))
}
}
return nil
}
func (g *generator) writeCSS(n parser.CSSTemplate) error {
var r parser.Range
var err error
var indentLevel int
// func
if _, err = g.w.Write("func "); err != nil {
return err
}
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
// templ.CSSClass {
if _, err = g.w.Write(" templ.CSSClass {\n"); err != nil {
return err
}
{
indentLevel++
// templ_7745c5c3_CSSBuilder := templruntim.GetBuilder()
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()\n"); err != nil {
return err
}
for i := 0; i < len(n.Properties); i++ {
switch p := n.Properties[i].(type) {
case parser.ConstantCSSProperty:
// Constant CSS property values are not sanitized.
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_CSSBuilder.WriteString("+createGoString(p.String(true))+")\n"); err != nil {
return err
}
case parser.ExpressionCSSProperty:
// templ_7745c5c3_CSSBuilder.WriteString(templ.SanitizeCSS('name', p.Expression()))
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`%s`, ", p.Name)); err != nil {
return err
}
if r, err = g.w.Write(p.Value.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(p.Value.Expression, r)
if _, err = g.w.Write(")))\n"); err != nil {
return err
}
default:
return fmt.Errorf("unknown CSS property type: %v", reflect.TypeOf(p))
}
}
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_CSSID := templ.CSSID(`%s`, templ_7745c5c3_CSSBuilder.String())\n", n.Name)); err != nil {
return err
}
// return templ.CSS {
if _, err = g.w.WriteIndent(indentLevel, "return templ.ComponentCSSClass{\n"); err != nil {
return err
}
{
indentLevel++
// ID: templ_7745c5c3_CSSID,
if _, err = g.w.WriteIndent(indentLevel, "ID: templ_7745c5c3_CSSID,\n"); err != nil {
return err
}
// Class: templ.SafeCSS(".cssID{" + templ.CSSBuilder.String() + "}"),
if _, err = g.w.WriteIndent(indentLevel, "Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),\n"); err != nil {
return err
}
indentLevel--
}
if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil {
return err
}
indentLevel--
}
// }
if _, err = g.w.WriteIndent(indentLevel, "}\n\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeGoExpression(n parser.TemplateFileGoExpression) (err error) {
r, err := g.w.Write(n.Expression.Value)
if err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
v := n.Expression.Value
lineSlice := strings.Split(v, "\n")
lastLine := lineSlice[len(lineSlice)-1]
if strings.HasPrefix(lastLine, "//") {
if _, err = g.w.WriteIndent(0, "\n"); err != nil {
return err
}
return err
}
if _, err = g.w.WriteIndent(0, "\n\n"); err != nil {
return err
}
return err
}
func (g *generator) writeTemplBuffer(indentLevel int) (err error) {
// templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)\n"); err != nil {
return err
}
// if !templ_7745c5c3_IsBuffer {
// defer func() {
// templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
// if templ_7745c5c3_Err == nil {
// templ_7745c5c3_Err = templ_7745c5c3_BufErr
// }
// }()
// }
if _, err = g.w.WriteIndent(indentLevel, "if !templ_7745c5c3_IsBuffer {\n"); err != nil {
return err
}
{
indentLevel++
if _, err = g.w.WriteIndent(indentLevel, "defer func() {\n"); err != nil {
return err
}
{
indentLevel++
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)\n"); err != nil {
return err
}
if _, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_Err == nil {\n"); err != nil {
return err
}
{
indentLevel++
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Err = templ_7745c5c3_BufErr\n"); err != nil {
return err
}
indentLevel--
}
if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil {
return err
}
indentLevel--
}
if _, err = g.w.WriteIndent(indentLevel, "}()\n"); err != nil {
return err
}
indentLevel--
}
if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil {
return err
}
return
}
func (g *generator) writeTemplate(nodeIdx int, t parser.HTMLTemplate) error {
var r parser.Range
var err error
var indentLevel int
// func
if _, err = g.w.Write("func "); err != nil {
return err
}
// (r *Receiver) Name(params []string)
if r, err = g.w.Write(t.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(t.Expression, r)
// templ.Component {
if _, err = g.w.Write(" templ.Component {\n"); err != nil {
return err
}
indentLevel++
// return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
if _, err = g.w.WriteIndent(indentLevel, "return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {\n"); err != nil {
return err
}
{
indentLevel++
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context\n"); err != nil {
return err
}
if _, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {\n"); err != nil {
return err
}
{
indentLevel++
if _, err = g.w.WriteIndent(indentLevel, "return templ_7745c5c3_CtxErr"); err != nil {
return err
}
indentLevel--
}
if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil {
return err
}
if err := g.writeTemplBuffer(indentLevel); err != nil {
return err
}
// ctx = templ.InitializeContext(ctx)
if _, err = g.w.WriteIndent(indentLevel, "ctx = templ.InitializeContext(ctx)\n"); err != nil {
return err
}
g.childrenVar = g.createVariableName()
// templ_7745c5c3_Var1 := templ.GetChildren(ctx)
// if templ_7745c5c3_Var1 == nil {
// templ_7745c5c3_Var1 = templ.NopComponent
// }
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("%s := templ.GetChildren(ctx)\n", g.childrenVar)); err != nil {
return err
}
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("if %s == nil {\n", g.childrenVar)); err != nil {
return err
}
{
indentLevel++
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("%s = templ.NopComponent\n", g.childrenVar)); err != nil {
return err
}
indentLevel--
}
if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil {
return err
}
// ctx = templ.ClearChildren(children)
if _, err = g.w.WriteIndent(indentLevel, "ctx = templ.ClearChildren(ctx)\n"); err != nil {
return err
}
// Nodes.
if err = g.writeNodes(indentLevel, stripWhitespace(t.Children), nil); err != nil {
return err
}
// return templ_7745c5c3_Err
if _, err = g.w.WriteIndent(indentLevel, "return templ_7745c5c3_Err\n"); err != nil {
return err
}
indentLevel--
}
// })
if _, err = g.w.WriteIndent(indentLevel, "})\n"); err != nil {
return err
}
indentLevel--
// }
// Note: gofmt wants to remove a single empty line at the end of a file
// so we have to make sure we don't output one if this is the last node.
closingBrace := "}\n\n"
if nodeIdx+1 >= len(g.tf.Nodes) {
closingBrace = "}\n"
}
if _, err = g.w.WriteIndent(indentLevel, closingBrace); err != nil {
return err
}
return nil
}
func stripWhitespace(input []parser.Node) (output []parser.Node) {
for i, n := range input {
if _, isWhiteSpace := n.(parser.Whitespace); !isWhiteSpace {
output = append(output, input[i])
}
}
return output
}
func stripLeadingWhitespace(nodes []parser.Node) []parser.Node {
for i := 0; i < len(nodes); i++ {
n := nodes[i]
if _, isWhiteSpace := n.(parser.Whitespace); !isWhiteSpace {
return nodes[i:]
}
}
return []parser.Node{}
}
func stripTrailingWhitespace(nodes []parser.Node) []parser.Node {
for i := len(nodes) - 1; i >= 0; i-- {
n := nodes[i]
if _, isWhiteSpace := n.(parser.Whitespace); !isWhiteSpace {
return nodes[0 : i+1]
}
}
return []parser.Node{}
}
func stripLeadingAndTrailingWhitespace(nodes []parser.Node) []parser.Node {
return stripTrailingWhitespace(stripLeadingWhitespace(nodes))
}
func (g *generator) writeNodes(indentLevel int, nodes []parser.Node, next parser.Node) error {
for i, curr := range nodes {
var nextNode parser.Node
if i+1 < len(nodes) {
nextNode = nodes[i+1]
}
if nextNode == nil {
nextNode = next
}
if err := g.writeNode(indentLevel, curr, nextNode); err != nil {
return err
}
}
return nil
}
func (g *generator) writeNode(indentLevel int, current parser.Node, next parser.Node) (err error) {
switch n := current.(type) {
case parser.DocType:
err = g.writeDocType(indentLevel, n)
case parser.Element:
err = g.writeElement(indentLevel, n)
case parser.HTMLComment:
err = g.writeComment(indentLevel, n)
case parser.ChildrenExpression:
err = g.writeChildrenExpression(indentLevel)
case parser.RawElement:
err = g.writeRawElement(indentLevel, n)
case parser.ForExpression:
err = g.writeForExpression(indentLevel, n, next)
case parser.CallTemplateExpression:
err = g.writeCallTemplateExpression(indentLevel, n)
case parser.TemplElementExpression:
err = g.writeTemplElementExpression(indentLevel, n)
case parser.IfExpression:
err = g.writeIfExpression(indentLevel, n, next)
case parser.SwitchExpression:
err = g.writeSwitchExpression(indentLevel, n, next)
case parser.StringExpression:
err = g.writeStringExpression(indentLevel, n.Expression)
case parser.GoCode:
err = g.writeGoCode(indentLevel, n.Expression)
case parser.Whitespace:
err = g.writeWhitespace(indentLevel, n)
case parser.Text:
err = g.writeText(indentLevel, n)
case parser.GoComment:
// Do not render Go comments in the output HTML.
return
default:
return fmt.Errorf("unhandled type: %v", reflect.TypeOf(n))
}
// Write trailing whitespace, if there is a next node that might need the space.
// If the next node is inline or text, we might need it.
// If the current node is a block element, we don't need it.
needed := (isInlineOrText(current) && isInlineOrText(next))
if ws, ok := current.(parser.WhitespaceTrailer); ok && needed {
if err := g.writeWhitespaceTrailer(indentLevel, ws.Trailing()); err != nil {
return err
}
}
return
}
func isInlineOrText(next parser.Node) bool {
// While these are formatted as blocks when they're written in the HTML template.
// They're inline - i.e. there's no whitespace rendered around them at runtime for minification.
if next == nil {
return false
}
switch n := next.(type) {
case parser.IfExpression:
return true
case parser.SwitchExpression:
return true
case parser.ForExpression:
return true
case parser.Element:
return !n.IsBlockElement()
case parser.Text:
return true
case parser.StringExpression:
return true
}
return false
}
func (g *generator) writeWhitespaceTrailer(indentLevel int, n parser.TrailingSpace) (err error) {
if n == parser.SpaceNone {
return nil
}
// Normalize whitespace for minified output. In HTML, a single space is equivalent to
// any number of spaces, tabs, or newlines.
if n == parser.SpaceVertical {
n = parser.SpaceHorizontal
}
if _, err = g.w.WriteStringLiteral(indentLevel, string(n)); err != nil {
return err
}
return nil
}
func (g *generator) writeDocType(indentLevel int, n parser.DocType) (err error) {
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf("<!doctype %s>", n.Value)); err != nil {
return err
}
return nil
}
func (g *generator) writeIfExpression(indentLevel int, n parser.IfExpression, nextNode parser.Node) (err error) {
var r parser.Range
// if
if _, err = g.w.WriteIndent(indentLevel, `if `); err != nil {
return err
}
// x == y {
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
// {
if _, err = g.w.Write(` {` + "\n"); err != nil {
return err
}
{
indentLevel++
if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Then), nextNode); err != nil {
return err
}
indentLevel--
}
for _, elseIf := range n.ElseIfs {
// } else if {
if _, err = g.w.WriteIndent(indentLevel, `} else if `); err != nil {
return err
}
// x == y {
if r, err = g.w.Write(elseIf.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(elseIf.Expression, r)
// {
if _, err = g.w.Write(` {` + "\n"); err != nil {
return err
}
{
indentLevel++
if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(elseIf.Then), nextNode); err != nil {
return err
}
indentLevel--
}
}
if len(n.Else) > 0 {
// } else {
if _, err = g.w.WriteIndent(indentLevel, `} else {`+"\n"); err != nil {
return err
}
{
indentLevel++
if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Else), nextNode); err != nil {
return err
}
indentLevel--
}
}
// }
if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeSwitchExpression(indentLevel int, n parser.SwitchExpression, next parser.Node) (err error) {
var r parser.Range
// switch
if _, err = g.w.WriteIndent(indentLevel, `switch `); err != nil {
return err
}
// val
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
// {
if _, err = g.w.Write(` {` + "\n"); err != nil {
return err
}
if len(n.Cases) > 0 {
for _, c := range n.Cases {
// case x:
// default:
if r, err = g.w.WriteIndent(indentLevel, c.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(c.Expression, r)
indentLevel++
if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(c.Children), next); err != nil {
return err
}
indentLevel--
}
}
// }
if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeChildrenExpression(indentLevel int) (err error) {
if _, err = g.w.WriteIndent(indentLevel, fmt.Sprintf("templ_7745c5c3_Err = %s.Render(ctx, templ_7745c5c3_Buffer)\n", g.childrenVar)); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
return nil
}
func (g *generator) writeTemplElementExpression(indentLevel int, n parser.TemplElementExpression) (err error) {
if len(n.Children) == 0 {
return g.writeSelfClosingTemplElementExpression(indentLevel, n)
}
return g.writeBlockTemplElementExpression(indentLevel, n)
}
func (g *generator) writeBlockTemplElementExpression(indentLevel int, n parser.TemplElementExpression) (err error) {
var r parser.Range
childrenName := g.createVariableName()
if _, err = g.w.WriteIndent(indentLevel, childrenName+" := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {\n"); err != nil {
return err
}
indentLevel++
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context\n"); err != nil {
return err
}
if err := g.writeTemplBuffer(indentLevel); err != nil {
return err
}
// ctx = templ.InitializeContext(ctx)
if _, err = g.w.WriteIndent(indentLevel, "ctx = templ.InitializeContext(ctx)\n"); err != nil {
return err
}
if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Children), nil); err != nil {
return err
}
// return nil
if _, err = g.w.WriteIndent(indentLevel, "return templ_7745c5c3_Err\n"); err != nil {
return err
}
indentLevel--
if _, err = g.w.WriteIndent(indentLevel, "})\n"); err != nil {
return err
}
if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = `); err != nil {
return err
}
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
// .Render(templ.WithChildren(ctx, children), templ_7745c5c3_Buffer)
if _, err = g.w.Write(".Render(templ.WithChildren(ctx, " + childrenName + "), templ_7745c5c3_Buffer)\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
return nil
}
func (g *generator) writeSelfClosingTemplElementExpression(indentLevel int, n parser.TemplElementExpression) (err error) {
if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = `); err != nil {
return err
}
// Template expression.
var r parser.Range
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
// .Render(ctx, templ_7745c5c3_Buffer)
if _, err = g.w.Write(".Render(ctx, templ_7745c5c3_Buffer)\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
return nil
}
func (g *generator) writeCallTemplateExpression(indentLevel int, n parser.CallTemplateExpression) (err error) {
if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = `); err != nil {
return err
}
// Template expression.
var r parser.Range
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
// .Render(ctx, templ_7745c5c3_Buffer)
if _, err = g.w.Write(".Render(ctx, templ_7745c5c3_Buffer)\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
return nil
}
func (g *generator) writeForExpression(indentLevel int, n parser.ForExpression, next parser.Node) (err error) {
var r parser.Range
// for
if _, err = g.w.WriteIndent(indentLevel, `for `); err != nil {
return err
}
// i, v := range p.Stuff
if r, err = g.w.Write(n.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(n.Expression, r)
// {
if _, err = g.w.Write(` {` + "\n"); err != nil {
return err
}
// Children.
indentLevel++
if err = g.writeNodes(indentLevel, stripLeadingAndTrailingWhitespace(n.Children), next); err != nil {
return err
}
indentLevel--
// }
if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeErrorHandler(indentLevel int) (err error) {
_, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_Err != nil {\n")
if err != nil {
return err
}
indentLevel++
_, err = g.w.WriteIndent(indentLevel, "return templ_7745c5c3_Err\n")
if err != nil {
return err
}
indentLevel--
_, err = g.w.WriteIndent(indentLevel, "}\n")
if err != nil {
return err
}
return err
}
func (g *generator) writeExpressionErrorHandler(indentLevel int, expression parser.Expression) (err error) {
_, err = g.w.WriteIndent(indentLevel, "if templ_7745c5c3_Err != nil {\n")
if err != nil {
return err
}
indentLevel++
line := int(expression.Range.To.Line + 1)
col := int(expression.Range.To.Col)
_, err = g.w.WriteIndent(indentLevel, "return templ.Error{Err: templ_7745c5c3_Err, FileName: "+createGoString(g.fileName)+", Line: "+strconv.Itoa(line)+", Col: "+strconv.Itoa(col)+"}\n")
if err != nil {
return err
}
indentLevel--
_, err = g.w.WriteIndent(indentLevel, "}\n")
if err != nil {
return err
}
return err
}
func copyAttributes(attr []parser.Attribute) []parser.Attribute {
o := make([]parser.Attribute, len(attr))
for i, a := range attr {
if c, ok := a.(parser.ConditionalAttribute); ok {
c.Then = copyAttributes(c.Then)
c.Else = copyAttributes(c.Else)
o[i] = c
continue
}
o[i] = a
}
return o
}
func (g *generator) writeElement(indentLevel int, n parser.Element) (err error) {
if len(n.Attributes) == 0 {
// <div>
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`<%s>`, html.EscapeString(n.Name))); err != nil {
return err
}
} else {
attrs := copyAttributes(n.Attributes)
// <style type="text/css"></style>
if err = g.writeElementCSS(indentLevel, attrs); err != nil {
return err
}
// <script type="text/javascript"></script>
if err = g.writeElementScript(indentLevel, attrs); err != nil {
return err
}
// <div
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`<%s`, html.EscapeString(n.Name))); err != nil {
return err
}
if err = g.writeElementAttributes(indentLevel, n.Name, attrs); err != nil {
return err
}
// >
if _, err = g.w.WriteStringLiteral(indentLevel, `>`); err != nil {
return err
}
}
// Skip children and close tag for void elements.
if n.IsVoidElement() && len(n.Children) == 0 {
return nil
}
// Children.
if err = g.writeNodes(indentLevel, stripWhitespace(n.Children), nil); err != nil {
return err
}
// </div>
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`</%s>`, html.EscapeString(n.Name))); err != nil {
return err
}
return err
}
func (g *generator) writeAttributeCSS(indentLevel int, attr parser.ExpressionAttribute) (result parser.ExpressionAttribute, ok bool, err error) {
var r parser.Range
name := html.EscapeString(attr.Name)
if name != "class" {
ok = false
return
}
// Create a class name for the style.
// The expression can either be expecting a templ.Classes call, or an expression that returns
// var templ_7745c5c3_CSSClassess = []any{
classesName := g.createVariableName()
if _, err = g.w.WriteIndent(indentLevel, "var "+classesName+" = []any{"); err != nil {
return
}
// p.Name()
if r, err = g.w.Write(attr.Expression.Value); err != nil {
return
}
g.sourceMap.Add(attr.Expression, r)
// }\n
if _, err = g.w.Write("}\n"); err != nil {
return
}
// Render the CSS before the element if required.
// templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_CSSClassess...)
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, "+classesName+"...)\n"); err != nil {
return
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return
}
// Rewrite the ExpressionAttribute to point at the new variable.
attr.Expression = parser.Expression{
Value: "templ.CSSClasses(" + classesName + ").String()",
}
return attr, true, nil
}
func (g *generator) writeAttributesCSS(indentLevel int, attrs []parser.Attribute) (err error) {
for i := 0; i < len(attrs); i++ {
if attr, ok := attrs[i].(parser.ExpressionAttribute); ok {
attr, ok, err = g.writeAttributeCSS(indentLevel, attr)
if err != nil {
return err
}
if ok {
attrs[i] = attr
}
}
if cattr, ok := attrs[i].(parser.ConditionalAttribute); ok {
err = g.writeAttributesCSS(indentLevel, cattr.Then)
if err != nil {
return err
}
err = g.writeAttributesCSS(indentLevel, cattr.Else)
if err != nil {
return err
}
attrs[i] = cattr
}
}
return nil
}
func (g *generator) writeElementCSS(indentLevel int, attrs []parser.Attribute) (err error) {
return g.writeAttributesCSS(indentLevel, attrs)
}
func isScriptAttribute(name string) bool {
for _, prefix := range []string{"on", "hx-on:"} {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}
func (g *generator) writeElementScript(indentLevel int, attrs []parser.Attribute) (err error) {
var scriptExpressions []string
for _, attr := range attrs {
scriptExpressions = append(scriptExpressions, getAttributeScripts(attr)...)
}
if len(scriptExpressions) == 0 {
return
}
// Render the scripts before the element if required.
// templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, a, b, c)
if _, err = g.w.WriteIndent(indentLevel, "templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, "+strings.Join(scriptExpressions, ", ")+")\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
return err
}
func getAttributeScripts(attr parser.Attribute) (scripts []string) {
if attr, ok := attr.(parser.ConditionalAttribute); ok {
for _, attr := range attr.Then {
scripts = append(scripts, getAttributeScripts(attr)...)
}
for _, attr := range attr.Else {
scripts = append(scripts, getAttributeScripts(attr)...)
}
}
if attr, ok := attr.(parser.ExpressionAttribute); ok {
name := html.EscapeString(attr.Name)
if isScriptAttribute(name) {
scripts = append(scripts, attr.Expression.Value)
}
}
return scripts
}
func (g *generator) writeBoolConstantAttribute(indentLevel int, attr parser.BoolConstantAttribute) (err error) {
name := html.EscapeString(attr.Name)
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s`, name)); err != nil {
return err
}
return nil
}
func (g *generator) writeConstantAttribute(indentLevel int, attr parser.ConstantAttribute) (err error) {
name := html.EscapeString(attr.Name)
value := html.EscapeString(attr.Value)
value = strconv.Quote(value)
value = value[1 : len(value)-1]
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s=\"%s\"`, name, value)); err != nil {
return err
}
return nil
}
func (g *generator) writeBoolExpressionAttribute(indentLevel int, attr parser.BoolExpressionAttribute) (err error) {
name := html.EscapeString(attr.Name)
// if
if _, err = g.w.WriteIndent(indentLevel, `if `); err != nil {
return err
}
// x == y
var r parser.Range
if r, err = g.w.Write(attr.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(attr.Expression, r)
// {
if _, err = g.w.Write(` {` + "\n"); err != nil {
return err
}
{
indentLevel++
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s`, name)); err != nil {
return err
}
indentLevel--
}
// }
if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeExpressionAttribute(indentLevel int, elementName string, attr parser.ExpressionAttribute) (err error) {
attrName := html.EscapeString(attr.Name)
// Name
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(` %s=`, attrName)); err != nil {
return err
}
// Value.
// Open quote.
if _, err = g.w.WriteStringLiteral(indentLevel, `\"`); err != nil {
return err
}
if (elementName == "a" && attr.Name == "href") || (elementName == "form" && attr.Name == "action") {
vn := g.createVariableName()
// var vn templ.SafeURL =
if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" templ.SafeURL = "); err != nil {
return err
}
// p.Name()
var r parser.Range
if r, err = g.w.Write(attr.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(attr.Expression, r)
if _, err = g.w.Write("\n"); err != nil {
return err
}
if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string("+vn+")))\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
} else {
if isScriptAttribute(attr.Name) {
// It's a JavaScript handler, and requires special handling, because we expect a JavaScript expression.
vn := g.createVariableName()
// var vn templ.ComponentScript =
if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" templ.ComponentScript = "); err != nil {
return err
}
// p.Name()
var r parser.Range
if r, err = g.w.Write(attr.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(attr.Expression, r)
if _, err = g.w.Write("\n"); err != nil {
return err
}
if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("+vn+".Call)\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
} else {
var r parser.Range
vn := g.createVariableName()
// var vn string
if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" string\n"); err != nil {
return err
}
// vn, templ_7745c5c3_Err = templ.JoinStringErrs(
if _, err = g.w.WriteIndent(indentLevel, vn+", templ_7745c5c3_Err = templ.JoinStringErrs("); err != nil {
return err
}
// p.Name()
if r, err = g.w.Write(attr.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(attr.Expression, r)
// )
if _, err = g.w.Write(")\n"); err != nil {
return err
}
// Attribute expression error handler.
err = g.writeExpressionErrorHandler(indentLevel, attr.Expression)
if err != nil {
return err
}
// _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(vn)
if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("+vn+"))\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
}
}
// Close quote.
if _, err = g.w.WriteStringLiteral(indentLevel, `\"`); err != nil {
return err
}
return nil
}
func (g *generator) writeSpreadAttributes(indentLevel int, attr parser.SpreadAttributes) (err error) {
// templ.RenderAttributes(ctx, w, spreadAttrs)
if _, err = g.w.WriteIndent(indentLevel, `templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, `); err != nil {
return err
}
// spreadAttrs
var r parser.Range
if r, err = g.w.Write(attr.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(attr.Expression, r)
// )
if _, err = g.w.Write(")\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
return nil
}
func (g *generator) writeConditionalAttribute(indentLevel int, elementName string, attr parser.ConditionalAttribute) (err error) {
// if
if _, err = g.w.WriteIndent(indentLevel, `if `); err != nil {
return err
}
// x == y
var r parser.Range
if r, err = g.w.Write(attr.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(attr.Expression, r)
// {
if _, err = g.w.Write(` {` + "\n"); err != nil {
return err
}
{
indentLevel++
if err = g.writeElementAttributes(indentLevel, elementName, attr.Then); err != nil {
return err
}
indentLevel--
}
if len(attr.Else) > 0 {
// } else {
if _, err = g.w.WriteIndent(indentLevel, `} else {`+"\n"); err != nil {
return err
}
{
indentLevel++
if err = g.writeElementAttributes(indentLevel, elementName, attr.Else); err != nil {
return err
}
indentLevel--
}
}
// }
if _, err = g.w.WriteIndent(indentLevel, `}`+"\n"); err != nil {
return err
}
return nil
}
func (g *generator) writeElementAttributes(indentLevel int, name string, attrs []parser.Attribute) (err error) {
for i := 0; i < len(attrs); i++ {
switch attr := attrs[i].(type) {
case parser.BoolConstantAttribute:
err = g.writeBoolConstantAttribute(indentLevel, attr)
case parser.ConstantAttribute:
err = g.writeConstantAttribute(indentLevel, attr)
case parser.BoolExpressionAttribute:
err = g.writeBoolExpressionAttribute(indentLevel, attr)
case parser.ExpressionAttribute:
err = g.writeExpressionAttribute(indentLevel, name, attr)
case parser.SpreadAttributes:
err = g.writeSpreadAttributes(indentLevel, attr)
case parser.ConditionalAttribute:
err = g.writeConditionalAttribute(indentLevel, name, attr)
default:
err = fmt.Errorf("unknown attribute type %s", reflect.TypeOf(attrs[i]))
}
}
return
}
func (g *generator) writeRawElement(indentLevel int, n parser.RawElement) (err error) {
if len(n.Attributes) == 0 {
// <div>
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`<%s>`, html.EscapeString(n.Name))); err != nil {
return err
}
} else {
// <script type="text/javascript"></script>
if err = g.writeElementScript(indentLevel, n.Attributes); err != nil {
return err
}
// <div
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`<%s`, html.EscapeString(n.Name))); err != nil {
return err
}
if err = g.writeElementAttributes(indentLevel, n.Name, n.Attributes); err != nil {
return err
}
// >
if _, err = g.w.WriteStringLiteral(indentLevel, `>`); err != nil {
return err
}
}
// Contents.
if err = g.writeText(indentLevel, parser.Text{Value: n.Contents}); err != nil {
return err
}
// </div>
if _, err = g.w.WriteStringLiteral(indentLevel, fmt.Sprintf(`</%s>`, html.EscapeString(n.Name))); err != nil {
return err
}
return err
}
func (g *generator) writeComment(indentLevel int, c parser.HTMLComment) (err error) {
// <!--
if _, err = g.w.WriteStringLiteral(indentLevel, "<!--"); err != nil {
return err
}
// Contents.
if err = g.writeText(indentLevel, parser.Text{Value: c.Contents}); err != nil {
return err
}
// -->
if _, err = g.w.WriteStringLiteral(indentLevel, "-->"); err != nil {
return err
}
return err
}
func (g *generator) createVariableName() string {
g.variableID++
return "templ_7745c5c3_Var" + strconv.Itoa(g.variableID)
}
func (g *generator) writeGoCode(indentLevel int, e parser.Expression) (err error) {
if strings.TrimSpace(e.Value) == "" {
return
}
var r parser.Range
if r, err = g.w.WriteIndent(indentLevel, e.Value+"\n"); err != nil {
return err
}
g.sourceMap.Add(e, r)
return nil
}
func (g *generator) writeStringExpression(indentLevel int, e parser.Expression) (err error) {
if strings.TrimSpace(e.Value) == "" {
return
}
var r parser.Range
vn := g.createVariableName()
// var vn string
if _, err = g.w.WriteIndent(indentLevel, "var "+vn+" string\n"); err != nil {
return err
}
// vn, templ_7745c5c3_Err = templ.JoinStringErrs(
if _, err = g.w.WriteIndent(indentLevel, vn+", templ_7745c5c3_Err = templ.JoinStringErrs("); err != nil {
return err
}
// p.Name()
if r, err = g.w.Write(e.Value); err != nil {
return err
}
g.sourceMap.Add(e, r)
// )
if _, err = g.w.Write(")\n"); err != nil {
return err
}
// String expression error handler.
err = g.writeExpressionErrorHandler(indentLevel, e)
if err != nil {
return err
}
// _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(vn)
if _, err = g.w.WriteIndent(indentLevel, "_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("+vn+"))\n"); err != nil {
return err
}
if err = g.writeErrorHandler(indentLevel); err != nil {
return err
}
return nil
}
func (g *generator) writeWhitespace(indentLevel int, n parser.Whitespace) (err error) {
if len(n.Value) == 0 {
return
}
// _, err = templ_7745c5c3_Buffer.WriteString(` `)
if _, err = g.w.WriteStringLiteral(indentLevel, " "); err != nil {
return err
}
return nil
}
func (g *generator) writeText(indentLevel int, n parser.Text) (err error) {
quoted := strconv.Quote(n.Value)
_, err = g.w.WriteStringLiteral(indentLevel, quoted[1:len(quoted)-1])
return err
}
func createGoString(s string) string {
var sb strings.Builder
sb.WriteRune('`')
sects := strings.Split(s, "`")
for i := 0; i < len(sects); i++ {
sb.WriteString(sects[i])
if len(sects) > i+1 {
sb.WriteString("` + \"`\" + `")
}
}
sb.WriteRune('`')
return sb.String()
}
func (g *generator) writeScript(t parser.ScriptTemplate) error {
var r parser.Range
var err error
var indentLevel int
// func
if _, err = g.w.Write("func "); err != nil {
return err
}
if r, err = g.w.Write(t.Name.Value); err != nil {
return err
}
g.sourceMap.Add(t.Name, r)
// (
if _, err = g.w.Write("("); err != nil {
return err
}
// Write parameters.
if r, err = g.w.Write(t.Parameters.Value); err != nil {
return err
}
g.sourceMap.Add(t.Parameters, r)
// ) templ.ComponentScript {
if _, err = g.w.Write(") templ.ComponentScript {\n"); err != nil {
return err
}
indentLevel++
// return templ.ComponentScript{
if _, err = g.w.WriteIndent(indentLevel, "return templ.ComponentScript{\n"); err != nil {
return err
}
{
indentLevel++
fn := functionName(t.Name.Value, t.Value)
goFn := createGoString(fn)
// Name: "scriptName",
if _, err = g.w.WriteIndent(indentLevel, "Name: "+goFn+",\n"); err != nil {
return err
}
// Function: `function scriptName(a, b, c){` + `constantScriptValue` + `}`,
prefix := "function " + fn + "(" + stripTypes(t.Parameters.Value) + "){"
body := strings.TrimLeftFunc(t.Value, unicode.IsSpace)
suffix := "}"
if _, err = g.w.WriteIndent(indentLevel, "Function: "+createGoString(prefix+body+suffix)+",\n"); err != nil {
return err
}
// Call: templ.SafeScript(scriptName, a, b, c)
if _, err = g.w.WriteIndent(indentLevel, "Call: templ.SafeScript("+goFn+", "+stripTypes(t.Parameters.Value)+"),\n"); err != nil {
return err
}
// CallInline: templ.SafeScriptInline(scriptName, a, b, c)
if _, err = g.w.WriteIndent(indentLevel, "CallInline: templ.SafeScriptInline("+goFn+", "+stripTypes(t.Parameters.Value)+"),\n"); err != nil {
return err
}
indentLevel--
}
// }
if _, err = g.w.WriteIndent(indentLevel, "}\n"); err != nil {
return err
}
indentLevel--
// }
if _, err = g.w.WriteIndent(indentLevel, "}\n\n"); err != nil {
return err
}
return nil
}
// writeBlankAssignmentForRuntimeImport writes out a blank identifier assignment.
// This ensures that even if the github.com/a-h/templ/runtime package is not used in the generated code,
// the Go compiler will not complain about the unused import.
func (g *generator) writeBlankAssignmentForRuntimeImport() error {
var err error
if _, err = g.w.Write("var _ = templruntime.GeneratedTemplate"); err != nil {
return err
}
return nil
}
func functionName(name string, body string) string {
h := sha256.New()
h.Write([]byte(body))
hp := hex.EncodeToString(h.Sum(nil))[0:4]
return "__templ_" + name + "_" + hp
}
func stripTypes(parameters string) string {
variableNames := []string{}
params := strings.Split(parameters, ",")
for i := 0; i < len(params); i++ {
p := strings.Split(strings.TrimSpace(params[i]), " ")
variableNames = append(variableNames, strings.TrimSpace(p[0]))
}
return strings.Join(variableNames, ", ")
}
package htmldiff
import (
"context"
"errors"
"fmt"
"io"
"strings"
"sync"
"github.com/a-h/htmlformat"
"github.com/a-h/templ"
"github.com/google/go-cmp/cmp"
)
func DiffStrings(expected, actual string) (diff string, err error) {
// Format both strings.
var wg sync.WaitGroup
wg.Add(2)
var errs []error
// Format expected.
go func() {
defer wg.Done()
e := new(strings.Builder)
err := htmlformat.Fragment(e, strings.NewReader(expected))
if err != nil {
errs = append(errs, fmt.Errorf("expected html formatting error: %w", err))
}
expected = e.String()
}()
// Format actual.
go func() {
defer wg.Done()
a := new(strings.Builder)
err := htmlformat.Fragment(a, strings.NewReader(actual))
if err != nil {
errs = append(errs, fmt.Errorf("actual html formatting error: %w", err))
}
actual = a.String()
}()
// Wait for processing.
wg.Wait()
return cmp.Diff(expected, actual), errors.Join(errs...)
}
func Diff(input templ.Component, expected string) (diff string, err error) {
return DiffCtx(context.Background(), input, expected)
}
func DiffCtx(ctx context.Context, input templ.Component, expected string) (diff string, err error) {
var wg sync.WaitGroup
wg.Add(2)
var errs []error
// Format the expected value.
go func() {
defer wg.Done()
e := new(strings.Builder)
err := htmlformat.Fragment(e, strings.NewReader(expected))
if err != nil {
errs = append(errs, fmt.Errorf("expected html formatting error: %w", err))
}
expected = e.String()
}()
// Pipe via the HTML formatter.
actual := new(strings.Builder)
r, w := io.Pipe()
go func() {
defer wg.Done()
err := htmlformat.Fragment(actual, r)
if err != nil {
errs = append(errs, fmt.Errorf("actual html formatting error: %w", err))
}
}()
// Render the component.
err = input.Render(ctx, w)
if err != nil {
errs = append(errs, fmt.Errorf("failed to render component: %w", err))
}
w.Close()
// Wait for processing.
wg.Wait()
return cmp.Diff(expected, actual.String()), errors.Join(errs...)
}
package generator
import (
"io"
"strconv"
"strings"
"unicode/utf8"
"github.com/a-h/templ/parser/v2"
)
func NewRangeWriter(w io.Writer) *RangeWriter {
return &RangeWriter{
w: w,
literalWriter: prodLiteralWriter{},
}
}
type RangeWriter struct {
Current parser.Position
inLiteral bool
w io.Writer
// Extract strings.
literalWriter literalWriter
}
type literalWriter interface {
writeLiteral(inLiteral bool, s string) string
closeLiteral(indent int) string
literals() string
}
type watchLiteralWriter struct {
index int
builder *strings.Builder
}
func (w *watchLiteralWriter) closeLiteral(indent int) string {
w.index++
w.builder.WriteString("\n")
return ""
}
func (w *watchLiteralWriter) writeLiteral(inLiteral bool, s string) string {
w.builder.WriteString(s)
if inLiteral {
return ""
}
return "templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, " + strconv.Itoa(w.index+1) + ")\n"
}
func (w *watchLiteralWriter) literals() string {
return w.builder.String()
}
type prodLiteralWriter struct{}
func (prodLiteralWriter) closeLiteral(indent int) string {
return "\")\n"
}
func (prodLiteralWriter) writeLiteral(inLiteral bool, s string) string {
if inLiteral {
return s
}
return `_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("` + s
}
func (prodLiteralWriter) literals() string {
return ""
}
func (rw *RangeWriter) closeLiteral(indent int) (r parser.Range, err error) {
rw.inLiteral = false
if _, err := rw.write(rw.literalWriter.closeLiteral(indent)); err != nil {
return r, err
}
err = rw.writeErrorHandler(indent)
return
}
func (rw *RangeWriter) WriteIndent(level int, s string) (r parser.Range, err error) {
if rw.inLiteral {
if _, err = rw.closeLiteral(level); err != nil {
return
}
}
_, err = rw.write(strings.Repeat("\t", level))
if err != nil {
return
}
return rw.write(s)
}
func (rw *RangeWriter) WriteStringLiteral(level int, s string) (r parser.Range, err error) {
if !rw.inLiteral {
_, err = rw.write(strings.Repeat("\t", level))
if err != nil {
return
}
}
if _, err := rw.write(rw.literalWriter.writeLiteral(rw.inLiteral, s)); err != nil {
return r, err
}
rw.inLiteral = true
return
}
func (rw *RangeWriter) Write(s string) (r parser.Range, err error) {
if rw.inLiteral {
if _, err = rw.closeLiteral(0); err != nil {
return
}
}
return rw.write(s)
}
func (rw *RangeWriter) write(s string) (r parser.Range, err error) {
r.From = parser.Position{
Index: rw.Current.Index,
Line: rw.Current.Line,
Col: rw.Current.Col,
}
utf8Bytes := make([]byte, 4)
for _, c := range s {
rlen := utf8.EncodeRune(utf8Bytes, c)
rw.Current.Col += uint32(rlen)
if c == '\n' {
rw.Current.Line++
rw.Current.Col = 0
}
_, err = rw.w.Write(utf8Bytes[:rlen])
rw.Current.Index += int64(rlen)
if err != nil {
return r, err
}
}
r.To = rw.Current
return r, err
}
func (rw *RangeWriter) writeErrorHandler(indentLevel int) (err error) {
_, err = rw.WriteIndent(indentLevel, "if templ_7745c5c3_Err != nil {\n")
if err != nil {
return err
}
indentLevel++
_, err = rw.WriteIndent(indentLevel, "return templ_7745c5c3_Err\n")
if err != nil {
return err
}
indentLevel--
_, err = rw.WriteIndent(indentLevel, "}\n")
if err != nil {
return err
}
return err
}
// Code generated by templ - DO NOT EDIT.
package testahref
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"javascript:alert('unaffected');\">Ignored</a> <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL("javascript:alert('should be sanitized')")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Sanitized</a> <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL("javascript:alert('should not be sanitized')")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Unsanitized</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testattrerrs
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func funcWithNoError() (s string) {
return "OK"
}
func funcWithError(in error) (s string, err error) {
if in != nil {
return "", in
}
return "OK2", nil
}
func TestComponent(err error) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul><li data-attr=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("raw")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-attribute-errors/template.templ`, Line: 16, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></li><li data-attr=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(funcWithNoError())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-attribute-errors/template.templ`, Line: 17, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></li><li data-attr=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(funcWithError(err))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-attribute-errors/template.templ`, Line: 18, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></li></ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testhtml
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func BasicTemplate(url string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(url)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">text</a></div><div><button hx-post=\"/click\" hx-trigger=\"click\" hx-vals=\"{"val":"Value"}\">Click</button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcall
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func showAll() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = a().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = b(c("C")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = d().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = showOne(e()).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>Child content</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = wrapChildren().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func a() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>A</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func b(child templ.Component) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>B</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = child.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func c(text string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-call/template.templ`, Line: 23, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func d() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>Legacy call style</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func e() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("e")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func showOne(component templ.Component) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = component.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func wrapChildren() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"wrapper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var10.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcancelledcontext
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func EmptyComponent() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcomplexattributes
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func ComplexAttributes() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{darkMode: localStorage.getItem('darkMode') || localStorage.setItem('darkMode', 'system')}\" x-init=\"$watch('darkMode', val => localStorage.setItem('darkMode', val))\" :class=\"{'dark': darkMode === 'dark' || (darkMode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)}\"></div><div x-data=\"{ count: 0 }\"><button x-on:click=\"count++\">Increment</button> <span x-text=\"count\"></span></div><div x-data=\"{ count: 0 }\"><button @click=\"count++\">Increment</button> <span x-text=\"count\"></span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testconstantattributeescaping
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func BasicTemplate() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><!-- valid go escape sequences --><input pattern=\"\\a\"> <input pattern=\"\\b\"> <input pattern=\"\\f\"> <input pattern=\"\\n\"> <input pattern=\"\\r\"> <input pattern=\"\\t\"> <input pattern=\"\\v\"> <input pattern=\"\\\\\"> <input pattern=\"\\777\"> <input pattern=\"\\xFF\"> <input pattern=\"\\u00FF\"> <input pattern=\"\\u00FF\\u00FF\\u00FF\"><!-- invalid go escape sequences --><input pattern=\"\\s\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcontext
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
type contextKey string
var contextKeyName contextKey = "name"
func render() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul><li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(ctx.Value(contextKeyName).(string))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-context/template.templ`, Line: 9, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if ctx.Value(contextKeyName).(string) == "test" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>the if passed</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if ctx.Value(contextKeyName).(string) != "test" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>the else if failed</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if ctx.Value(contextKeyName).(string) == "test" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>the else if passed</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcssexpression
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func className() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`background-color:#ffffff;`)
templ_7745c5c3_CSSBuilder.WriteString(`max-height:calc(100vh - 170px);`)
templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`color`, red)))
templ_7745c5c3_CSSID := templ.CSSID(`className`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcssmiddleware
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func red() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`color:red;`)
templ_7745c5c3_CSSID := templ.CSSID(`red`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func render(s string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var2 = []any{red}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-middleware/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(s)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-middleware/template.templ`, Line: 8, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcssusage
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"math"
)
func StyleTagsAreSupported() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<style>\n\t.test {\n\t\tcolor: #ff0000;\n\t}\n\t</style><div class=\"test\">Style tags are supported</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// CSS components.
const red = "#00ff00"
func cssComponentGreen() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`color`, red)))
templ_7745c5c3_CSSID := templ.CSSID(`cssComponentGreen`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func CSSComponentsAreSupported() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var3 = []any{cssComponentGreen()}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">CSS components are supported</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// Both CSS components and constants are supported.
// Only string names are really required. There is no need to use templ.Class or templ.SafeClass.
func CSSComponentsAndConstantsAreSupported() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var6 = []any{cssComponentGreen(), "classA", templ.Class("&&&classB"), templ.SafeClass("classC"), "d e"}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">Both CSS components and constants are supported</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 = []any{templ.Classes(cssComponentGreen(), "classA", templ.Class("&&&classB"), templ.SafeClass("classC")), "d e"}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">Both CSS components and constants are supported</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// Maps can be used to determine if a class should be added or not.
func MapsCanBeUsedToConditionallySetClasses() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var11 = []any{map[string]bool{"a": true, "b": false, "c": true}}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Maps can be used to determine if a class should be added or not.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// The templ.KV function can be used to add a class if a condition is true.
func d() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`font-size:12pt;`)
templ_7745c5c3_CSSID := templ.CSSID(`d`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func e() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`font-size:14pt;`)
templ_7745c5c3_CSSID := templ.CSSID(`e`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func KVCanBeUsedToConditionallySetClasses() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var13 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var14 = []any{"a", templ.KV("b", false), "c", templ.KV(d(), false), templ.KV(e(), true)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">KV can be used to conditionally set classes.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// Pseudo attributes can be used without any special syntax.
func PsuedoAttributesAndComplexClassNamesAreSupported() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var16 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil {
templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var17 = []any{"bg-violet-500", "hover:bg-red-600", "hover:bg-sky-700", "text-[#50d71e]", "w-[calc(100%-4rem)"}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Psuedo attributes and complex class names are supported.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// Class names are HTML escaped.
func ClassNamesAreHTMLEscaped() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
if templ_7745c5c3_Var19 == nil {
templ_7745c5c3_Var19 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var20 = []any{"a\" onClick=\"alert('hello')\""}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Class names are HTML escaped.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// CSS components can be used with arguments.
func loading(percent int) templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`width`, fmt.Sprintf("%d%%", percent))))
templ_7745c5c3_CSSID := templ.CSSID(`loading`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func CSSComponentsCanBeUsedWithArguments() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var22 := templ.GetChildren(ctx)
if templ_7745c5c3_Var22 == nil {
templ_7745c5c3_Var22 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var23 = []any{loading(50)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var23...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var23).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">CSS components can be used with arguments.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 = []any{loading(100)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var25...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var25).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">CSS components can be used with arguments.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func windVaneRotation(degrees float64) templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`transform`, templ.SafeCSSProperty(fmt.Sprintf("rotate(%ddeg)", int(math.Round(degrees)))))))
templ_7745c5c3_CSSID := templ.CSSID(`windVaneRotation`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func Rotate(degrees float64) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var27 := templ.GetChildren(ctx)
if templ_7745c5c3_Var27 == nil {
templ_7745c5c3_Var27 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var28 = []any{windVaneRotation(degrees)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var28...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var28).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-css-usage/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Rotate</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
// Combine all tests.
func TestComponent() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var30 := templ.GetChildren(ctx)
if templ_7745c5c3_Var30 == nil {
templ_7745c5c3_Var30 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = StyleTagsAreSupported().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CSSComponentsAreSupported().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CSSComponentsAndConstantsAreSupported().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = MapsCanBeUsedToConditionallySetClasses().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = KVCanBeUsedToConditionallySetClasses().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = PsuedoAttributesAndComplexClassNamesAreSupported().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = ClassNamesAreHTMLEscaped().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CSSComponentsCanBeUsedWithArguments().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Rotate(45).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testdoctype
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Layout(title, content string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-doctype/template.templ`, Line: 10, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title></head><body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(content)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-doctype/template.templ`, Line: 12, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testelementattributes
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func important() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`width:100;`)
templ_7745c5c3_CSSID := templ.CSSID(`important`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func unimportant() templ.CSSClass {
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
templ_7745c5c3_CSSBuilder.WriteString(`width:50;`)
templ_7745c5c3_CSSID := templ.CSSID(`unimportant`, templ_7745c5c3_CSSBuilder.String())
return templ.ComponentCSSClass{
ID: templ_7745c5c3_CSSID,
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
}
}
func render(p person) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var2 = []any{important()}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"width: 100;\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if p.important {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-element-attributes/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Important</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 = []any{unimportant}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"width: 100;\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !p.important {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-element-attributes/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Unimportant</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 = []any{important}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 = []any{unimportant}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"width: 100;\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if p.important {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-element-attributes/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-element-attributes/template.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Else</div><div data-script=\"on click\n do something\n end\"></div><h2>HTMX Wildcard attribute</h2><form hx-post=\"/api/secret/unlock\" hx-target=\"#secret\" hx-target-*=\"#errors\" hx-indicator=\"#loading-indicator\"><input type=\"button\" value=\"Unlock\"></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package elseif
type data struct {
}
func (d data) IsTrue() bool {
return false
}
// Code generated by templ - DO NOT EDIT.
package elseif
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(d data) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if d.IsTrue() {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("True")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 6, Col: 11}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if !d.IsTrue() {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("False")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 8, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("Else")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 10, Col: 11}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if 1 == 2 {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("If")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 15, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if 1 == 1 {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("ElseIf")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 17, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if 1 == 2 {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs("If")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 22, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if 1 == 3 {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs("ElseIf")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 24, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if 1 == 4 {
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs("ElseIf")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 26, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if 1 == 1 {
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs("OK")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-elseif/template.templ`, Line: 28, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testfor
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(items []string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
for _, item := range items {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(item)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-for/template.templ`, Line: 5, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testahref
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form action=\"javascript:alert('unaffected');\">Ignored</form><form action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL("javascript:alert('should be sanitized')")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Sanitized</form><form action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL("javascript:alert('should not be sanitized')")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Unsanitized</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcomment
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(content string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(content)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-go-comments/template.templ`, Line: 5, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testgotemplates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "html/template"
var goTemplate = template.Must(template.New("example").Parse("<div>{{ . }}</div>"))
func Example() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.FromGoHTML(goTemplate, "Hello, World!").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testcomment
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(content string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!-- simple html comment -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = paragraph(content).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!--\n\t\tmultiline\n\t\tcomment\n\t-->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = paragraph("second paragraph").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!--\n\t\t@paragraph(\"commented out composed element\")\n\t-->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = paragraph("third paragraph").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!-- commented out string expression: { content } --><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(content)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-html-comment/template.templ`, Line: 16, Col: 16}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span><!-- <div>comment with html</div> -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func paragraph(content string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(content)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-html-comment/template.templ`, Line: 21, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testhtml
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(p person) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(p.name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-html/template.templ`, Line: 5, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><div style=\"font-family: 'sans-serif'\" id=\"test\" data-contents=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(`something with "quotes" and a <tag>`)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-html/template.templ`, Line: 6, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div>email:<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.URL("mailto: " + p.email)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(p.email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-html/template.templ`, Line: 7, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div></div></div><hr")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if true {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" noshade")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("><hr optionA")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if true {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" optionB")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" optionC=\"other\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if false {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" optionD")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("><hr noshade><input name=\"test\">Text")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package testif
type data struct {
}
func (d data) IsTrue() bool {
return true
}
// Code generated by templ - DO NOT EDIT.
package testif
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(d data) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if d.IsTrue() {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("True")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-if/template.templ`, Line: 5, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("False")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-if/template.templ`, Line: 7, Col: 11}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package ifelse
type data struct {
}
func (d data) IsTrue() bool {
return false
}
// Code generated by templ - DO NOT EDIT.
package ifelse
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(d data) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if d.IsTrue() {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("True")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-ifelse/template.templ`, Line: 5, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("False")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-ifelse/template.templ`, Line: 7, Col: 11}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testimport
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func listItem() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func list() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var2.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func main() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<u>Item 1</u>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = listItem().Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<u>Item 2</u>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = listItem().Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var7 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<u>Item 3</u>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = listItem().Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = list().Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testmethod
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
type Data struct {
message string
}
func (d Data) Method() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(d.message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-method/template.templ`, Line: 8, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package once
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
var helloHandle = templ.NewOnceHandle()
func hello(label, name string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script type=\"text/javascript\">\n\t\t\tfunction hello(name) {\n\t\t\t\talert('Hello, ' + name + '!');\n\t\t\t}\n\t\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = helloHandle.Once().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"button\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-once/template.templ`, Line: 13, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-once/template.templ`, Line: 13, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" onclick=\"hello(this.getAttribute('data-name'))\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func render() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = hello("Hello User", "user").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = hello("Hello World", "world").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package onlyscripts
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func withParameters(a string, b string, c int) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withParameters_1056`,
Function: `function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
}`,
Call: templ.SafeScript(`__templ_withParameters_1056`, a, b, c),
CallInline: templ.SafeScriptInline(`__templ_withParameters_1056`, a, b, c),
}
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testrawelements
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Example() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html><head></head><body><style><!-- Some stuff --></style><style>\n .customClass {\n border: 1px solid black;\n }\n </style><script type=\"text/javascript\">\n $(\"div\").marquee();\n function test() {\n window.open(\"https://example.com\")\n }\n </script><h1>Hello</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw("<div>World</div>").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testscriptinline
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func withParameters(a string, b string, c int) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withParameters_1056`,
Function: `function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
}`,
Call: templ.SafeScript(`__templ_withParameters_1056`, a, b, c),
CallInline: templ.SafeScriptInline(`__templ_withParameters_1056`, a, b, c),
}
}
func withoutParameters() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withoutParameters_6bbf`,
Function: `function __templ_withoutParameters_6bbf(){alert("hello");
}`,
Call: templ.SafeScript(`__templ_withoutParameters_6bbf`),
CallInline: templ.SafeScriptInline(`__templ_withoutParameters_6bbf`),
}
}
func InlineJavascript(a string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = withoutParameters().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = withParameters(a, "test", 123).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = withoutParameters().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = withParameters(a, "test", 123).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testscriptusage
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func withParameters(a string, b string, c int) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withParameters_1056`,
Function: `function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
}`,
Call: templ.SafeScript(`__templ_withParameters_1056`, a, b, c),
CallInline: templ.SafeScriptInline(`__templ_withParameters_1056`, a, b, c),
}
}
func withoutParameters() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withoutParameters_6bbf`,
Function: `function __templ_withoutParameters_6bbf(){alert("hello");
}`,
Call: templ.SafeScript(`__templ_withoutParameters_6bbf`),
CallInline: templ.SafeScriptInline(`__templ_withoutParameters_6bbf`),
}
}
func onClick() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_onClick_657d`,
Function: `function __templ_onClick_657d(){alert("clicked");
}`,
Call: templ.SafeScript(`__templ_onClick_657d`),
CallInline: templ.SafeScriptInline(`__templ_onClick_657d`),
}
}
func Button(text string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, withParameters("test", text, 123), withoutParameters())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button onClick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.ComponentScript = withParameters("test", text, 123)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" onMouseover=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.ComponentScript = withoutParameters()
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-script-usage-nonce/template.templ`, Line: 16, Col: 111}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func withComment() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withComment_9cf8`,
Function: `function __templ_withComment_9cf8(){//'
}`,
Call: templ.SafeScript(`__templ_withComment_9cf8`),
CallInline: templ.SafeScriptInline(`__templ_withComment_9cf8`),
}
}
func ThreeButtons() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = Button("A").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Button("B").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button onMouseover=\"console.log('mouseover')\" type=\"button\">Button C</button> <button hx-on::click=\"alert('clicked inline')\" type=\"button\">Button D</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, onClick())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button hx-on::click=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.ComponentScript = onClick()
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">Button E</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Conditional(true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func conditionalScript() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_conditionalScript_de41`,
Function: `function __templ_conditionalScript_de41(){alert("conditional");
}`,
Call: templ.SafeScript(`__templ_conditionalScript_de41`),
CallInline: templ.SafeScriptInline(`__templ_conditionalScript_de41`),
}
}
func Conditional(show bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, conditionalScript())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"button\" value=\"Click me\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if show {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 templ.ComponentScript = conditionalScript()
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testscriptusage
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func withParameters(a string, b string, c int) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withParameters_1056`,
Function: `function __templ_withParameters_1056(a, b, c){console.log(a, b, c);
}`,
Call: templ.SafeScript(`__templ_withParameters_1056`, a, b, c),
CallInline: templ.SafeScriptInline(`__templ_withParameters_1056`, a, b, c),
}
}
func withoutParameters() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withoutParameters_6bbf`,
Function: `function __templ_withoutParameters_6bbf(){alert("hello");
}`,
Call: templ.SafeScript(`__templ_withoutParameters_6bbf`),
CallInline: templ.SafeScriptInline(`__templ_withoutParameters_6bbf`),
}
}
func onClick() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_onClick_657d`,
Function: `function __templ_onClick_657d(){alert("clicked");
}`,
Call: templ.SafeScript(`__templ_onClick_657d`),
CallInline: templ.SafeScriptInline(`__templ_onClick_657d`),
}
}
func Button(text string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, withParameters("test", text, 123), withoutParameters())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button onClick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.ComponentScript = withParameters("test", text, 123)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" onMouseover=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.ComponentScript = withoutParameters()
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-script-usage/template.templ`, Line: 16, Col: 111}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func withComment() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_withComment_9cf8`,
Function: `function __templ_withComment_9cf8(){//'
}`,
Call: templ.SafeScript(`__templ_withComment_9cf8`),
CallInline: templ.SafeScriptInline(`__templ_withComment_9cf8`),
}
}
func whenButtonIsClicked(event templ.JSExpression) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_whenButtonIsClicked_253e`,
Function: `function __templ_whenButtonIsClicked_253e(event){console.log(event.target)
}`,
Call: templ.SafeScript(`__templ_whenButtonIsClicked_253e`, event),
CallInline: templ.SafeScriptInline(`__templ_whenButtonIsClicked_253e`, event),
}
}
func ThreeButtons() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = Button("A").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Button("B").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button onMouseover=\"console.log('mouseover')\" type=\"button\">Button C</button> <button hx-on::click=\"alert('clicked inline')\" type=\"button\">Button D</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, onClick())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button hx-on::click=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.ComponentScript = onClick()
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"button\">Button E</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, whenButtonIsClicked(templ.JSExpression("event")))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 templ.ComponentScript = whenButtonIsClicked(templ.JSExpression("event"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Button F</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Conditional(true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = ScriptOnLoad().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func conditionalScript() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_conditionalScript_de41`,
Function: `function __templ_conditionalScript_de41(){alert("conditional");
}`,
Call: templ.SafeScript(`__templ_conditionalScript_de41`),
CallInline: templ.SafeScriptInline(`__templ_conditionalScript_de41`),
}
}
func Conditional(show bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, conditionalScript())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"button\" value=\"Click me\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if show {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 templ.ComponentScript = conditionalScript()
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func alertTest() templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_alertTest_eadf`,
Function: `function __templ_alertTest_eadf(){alert('testing');
}`,
Call: templ.SafeScript(`__templ_alertTest_eadf`),
CallInline: templ.SafeScriptInline(`__templ_alertTest_eadf`),
}
}
func ScriptOnLoad() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, alertTest())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script async crossorigin=\"true\" onload=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 templ.ComponentScript = alertTest()
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" src=\"url.to.some.script\"></script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testspreadattributes
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func BasicTemplate(spread templ.Attributes) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><a")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, spread)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">text</a><div")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if true {
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, spread)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">text2</div><div")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if false {
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, spread)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">text3</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package teststringerrs
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func funcWithNoError() (s string) {
return "OK"
}
func funcWithError(in error) (s string, err error) {
if in != nil {
return "", in
}
return "OK2", nil
}
func TestComponent(err error) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul><li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("raw")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-string-errors/template.templ`, Line: 16, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li><li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(funcWithNoError())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-string-errors/template.templ`, Line: 17, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li><li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(funcWithError(err))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-string-errors/template.templ`, Line: 18, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li></ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package teststring
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(s string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul><li></li><li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(s)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-string/template.templ`, Line: 6, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li><li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("Spaces")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-string/template.templ`, Line: 7, Col: 16}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("are")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-string/template.templ`, Line: 7, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("preserved.")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-string/template.templ`, Line: 7, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li></ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testswitch
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render(input string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch input {
case "a":
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("it was 'a'")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-switch/template.templ`, Line: 6, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("it was something else")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-switch/template.templ`, Line: 8, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testswitchdefault
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func template(input string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch input {
case "a":
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("it was 'a'")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-switchdefault/template.templ`, Line: 6, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("it was something else")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-switchdefault/template.templ`, Line: 8, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testtemplelement
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "fmt"
func wrapper(index int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(index))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-templ-element/template.templ`, Line: 6, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func template() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("child1")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("child2")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("child3")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = wrapper(4).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = wrapper(3).Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = wrapper(2).Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = wrapper(1).Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testgotemplates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "html/template"
var example = template.Must(template.New("example").Parse(`<!DOCTYPE html>
<html>
<body>
{{ . }}
</body>
</html>
`))
func greeting() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>Hello, World!</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testtextwhitespace
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func WhitespaceIsAddedWithinTemplStatements() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>This is some text. ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if true {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("So is this.")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhitespaceIsAddedWithinTemplStatementsExpected = `<p>This is some text. So is this.</p>`
func InlineElementsAreNotPadded() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>Inline text <b>is spaced properly</b> without adding extra spaces.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const InlineElementsAreNotPaddedExpected = `<p>Inline text <b>is spaced properly</b> without adding extra spaces.</p>`
func WhiteSpaceInHTMLIsNormalised() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>newlines and other whitespace are stripped but it is normalised like HTML.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhiteSpaceInHTMLIsNormalisedExpected = `<p>newlines and other whitespace are stripped but it is normalised like HTML.</p>`
func WhiteSpaceAroundValues() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>templ allows ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("strings")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-text-whitespace/template.templ`, Line: 31, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" to be included in sentences.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhiteSpaceAroundValuesExpected = `<p>templ allows strings to be included in sentences.</p>`
const WhiteSpaceAroundTemplatedValuesExpected = `<div>templ allows whitespace around templated values.</div>`
func WhiteSpaceAroundTemplatedValues(prefix, statement string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(prefix)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-text-whitespace/template.templ`, Line: 39, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(statement)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-text-whitespace/template.templ`, Line: 39, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testtext
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func BasicTemplate(name string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>Name: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-text/template.templ`, Line: 4, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div>Text `with backticks`</div><div>Text `with backtick</div><div>Text `with backtick alongside variable: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-text/template.templ`, Line: 7, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testvoid
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func render() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<br><img src=\"https://example.com/image.png\"><br><br>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
// Code generated by templ - DO NOT EDIT.
package testwhitespacearoundgokeywords
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "fmt"
func WhitespaceIsConsistentInIf(firstIf, secondIf bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Start</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if firstIf {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>If</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if secondIf {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>ElseIf</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Else</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>End</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhitespaceIsConsistentInTrueIfExpected = `<button>Start</button> <button>If</button> <button>End</button>`
const WhitespaceIsConsistentInTrueElseIfExpected = `<button>Start</button> <button>ElseIf</button> <button>End</button>`
const WhitespaceIsConsistentInTrueElseExpected = `<button>Start</button> <button>Else</button> <button>End</button>`
func WhitespaceIsConsistentInFalseIf() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Start</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if false {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Will Not Render</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>End</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhitespaceIsConsistentInFalseIfExpected = `<button>Start</button> <button>End</button>`
func WhitespaceIsConsistentInSwitch(i int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Start</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
switch i {
case 1:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>1</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>default</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>End</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhitespaceIsConsistentInOneSwitchExpected = `<button>Start</button> <button>1</button> <button>End</button>`
const WhitespaceIsConsistentInDefaultSwitchExpected = `<button>Start</button> <button>default</button> <button>End</button>`
func WhitespaceIsConsistentInSwitchNoDefault() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Start</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
switch false {
case true:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Will Not Render</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>End</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhitespaceIsConsistentInSwitchNoDefaultExpected = `<button>Start</button> <button>End</button>`
func WhitespaceIsConsistentInFor(i int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>Start</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for j := 0; j < i; j++ {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(j))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-whitespace-around-go-keywords/template.templ`, Line: 59, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button>End</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
const WhitespaceIsConsistentInForZeroExpected = `<button>Start</button> <button>End</button>`
const WhitespaceIsConsistentInForOneExpected = `<button>Start</button> <button>0</button> <button>End</button>`
const WhitespaceIsConsistentInForThreeExpected = `<button>Start</button> <button>0</button> <button>1</button> <button>2</button> <button>End</button>`
var _ = templruntime.GeneratedTemplate
package templ
import "net/http"
// ComponentHandler is a http.Handler that renders components.
type ComponentHandler struct {
Component Component
Status int
ContentType string
ErrorHandler func(r *http.Request, err error) http.Handler
StreamResponse bool
}
const componentHandlerErrorMessage = "templ: failed to render template"
func (ch *ComponentHandler) ServeHTTPBuffered(w http.ResponseWriter, r *http.Request) {
// Since the component may error, write to a buffer first.
// This prevents partial responses from being written to the client.
buf := GetBuffer()
defer ReleaseBuffer(buf)
err := ch.Component.Render(r.Context(), buf)
if err != nil {
if ch.ErrorHandler != nil {
w.Header().Set("Content-Type", ch.ContentType)
ch.ErrorHandler(r, err).ServeHTTP(w, r)
return
}
http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", ch.ContentType)
if ch.Status != 0 {
w.WriteHeader(ch.Status)
}
// Ignore write error like http.Error() does, because there is
// no way to recover at this point.
_, _ = w.Write(buf.Bytes())
}
func (ch *ComponentHandler) ServeHTTPStreamed(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", ch.ContentType)
if ch.Status != 0 {
w.WriteHeader(ch.Status)
}
if err := ch.Component.Render(r.Context(), w); err != nil {
if ch.ErrorHandler != nil {
w.Header().Set("Content-Type", ch.ContentType)
ch.ErrorHandler(r, err).ServeHTTP(w, r)
return
}
http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
}
}
// ServeHTTP implements the http.Handler interface.
func (ch ComponentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if ch.StreamResponse {
ch.ServeHTTPStreamed(w, r)
return
}
ch.ServeHTTPBuffered(w, r)
}
// Handler creates a http.Handler that renders the template.
func Handler(c Component, options ...func(*ComponentHandler)) *ComponentHandler {
ch := &ComponentHandler{
Component: c,
ContentType: "text/html; charset=utf-8",
}
for _, o := range options {
o(ch)
}
return ch
}
// WithStatus sets the HTTP status code returned by the ComponentHandler.
func WithStatus(status int) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.Status = status
}
}
// WithContentType sets the Content-Type header returned by the ComponentHandler.
func WithContentType(contentType string) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.ContentType = contentType
}
}
// WithErrorHandler sets the error handler used if rendering fails.
func WithErrorHandler(eh func(r *http.Request, err error) http.Handler) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.ErrorHandler = eh
}
}
// WithStreaming sets the ComponentHandler to stream the response instead of buffering it.
func WithStreaming() func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.StreamResponse = true
}
}
package templ
import (
"context"
"io"
)
// Join returns a single `templ.Component` that will render provided components in order.
// If any of the components return an error the Join component will immediately return with the error.
func Join(components ...Component) Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
for _, c := range components {
if err = c.Render(ctx, w); err != nil {
return err
}
}
return nil
})
}
package templ
import (
"context"
"encoding/json"
"fmt"
"io"
)
var _ Component = JSONScriptElement{}
// JSONScript renders a JSON object inside a script element.
// e.g. <script type="application/json">{"foo":"bar"}</script>
func JSONScript(id string, data any) JSONScriptElement {
return JSONScriptElement{
ID: id,
Type: "application/json",
Data: data,
Nonce: GetNonce,
}
}
// WithType sets the value of the type attribute of the script element.
func (j JSONScriptElement) WithType(t string) JSONScriptElement {
j.Type = t
return j
}
// WithNonceFromString sets the value of the nonce attribute of the script element to the given string.
func (j JSONScriptElement) WithNonceFromString(nonce string) JSONScriptElement {
j.Nonce = func(context.Context) string {
return nonce
}
return j
}
// WithNonceFrom sets the value of the nonce attribute of the script element to the value returned by the given function.
func (j JSONScriptElement) WithNonceFrom(f func(context.Context) string) JSONScriptElement {
j.Nonce = f
return j
}
type JSONScriptElement struct {
// ID of the element in the DOM.
ID string
// Type of the script element, defaults to "application/json".
Type string
// Data that will be encoded as JSON.
Data any
// Nonce is a function that returns a CSP nonce.
// Defaults to CSPNonceFromContext.
// See https://content-security-policy.com/nonce for more information.
Nonce func(ctx context.Context) string
}
func (j JSONScriptElement) Render(ctx context.Context, w io.Writer) (err error) {
if _, err = io.WriteString(w, "<script"); err != nil {
return err
}
if j.ID != "" {
if _, err = fmt.Fprintf(w, " id=\"%s\"", EscapeString(j.ID)); err != nil {
return err
}
}
if j.Type != "" {
if _, err = fmt.Fprintf(w, " type=\"%s\"", EscapeString(j.Type)); err != nil {
return err
}
}
if nonce := j.Nonce(ctx); nonce != "" {
if _, err = fmt.Fprintf(w, " nonce=\"%s\"", EscapeString(nonce)); err != nil {
return err
}
}
if _, err = io.WriteString(w, ">"); err != nil {
return err
}
if err = json.NewEncoder(w).Encode(j.Data); err != nil {
return err
}
if _, err = io.WriteString(w, "</script>"); err != nil {
return err
}
return nil
}
package templ
import (
"encoding/json"
)
// JSONString returns a JSON encoded string of v.
func JSONString(v any) (string, error) {
b, err := json.Marshal(v)
if err != nil {
return "", err
}
return string(b), nil
}
package templ
import (
"context"
"io"
"sync/atomic"
)
// onceHandleIndex is used to identify unique once handles in a program run.
var onceHandleIndex int64
type OnceOpt func(*OnceHandle)
// WithOnceComponent sets the component to be rendered once per context.
// This can be used instead of setting the children of the `Once` method,
// for example, if creating a code component outside of a templ HTML template.
func WithComponent(c Component) OnceOpt {
return func(o *OnceHandle) {
o.c = c
}
}
// NewOnceHandle creates a OnceHandle used to ensure that the children of its
// `Once` method are only rendered once per context.
func NewOnceHandle(opts ...OnceOpt) *OnceHandle {
oh := &OnceHandle{
id: atomic.AddInt64(&onceHandleIndex, 1),
}
for _, opt := range opts {
opt(oh)
}
return oh
}
// OnceHandle is used to ensure that the children of its `Once` method are are only
// rendered once per context.
type OnceHandle struct {
// id is used to identify which instance of the OnceHandle is being used.
// The OnceHandle can't be an empty struct, because:
//
// | Two distinct zero-size variables may
// | have the same address in memory
//
// https://go.dev/ref/spec#Size_and_alignment_guarantees
id int64
// c is the component to be rendered once per context.
// if c is nil, the children of the `Once` method are rendered.
c Component
}
// Once returns a component that renders its children once per context.
func (o *OnceHandle) Once() Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
_, v := getContext(ctx)
if v.getHasBeenRendered(o) {
return nil
}
v.setHasBeenRendered(o)
if o.c != nil {
return o.c.Render(ctx, w)
}
return GetChildren(ctx).Render(ctx, w)
})
}
package parser
import (
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
var callTemplateExpression callTemplateExpressionParser
var callTemplateExpressionStart = parse.Or(parse.String("{! "), parse.String("{!"))
type callTemplateExpressionParser struct{}
func (p callTemplateExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = callTemplateExpressionStart.Parse(pi); err != nil || !ok {
return
}
// Once we have a prefix, we must have an expression that returns a template.
var r CallTemplateExpression
if r.Expression, err = parseGo("call template expression", pi, goexpression.Expression); err != nil {
return
}
// Eat the final brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("call template expression: missing closing brace", pi.Position())
return
}
return r, true, nil
}
package parser
import (
"github.com/a-h/parse"
)
var childrenExpressionParser = parse.StringFrom(
openBraceWithOptionalPadding,
parse.OptionalWhitespace,
parse.String("children..."),
parse.OptionalWhitespace,
closeBraceWithOptionalPadding,
)
var childrenExpression = parse.Func(func(in *parse.Input) (n Node, ok bool, err error) {
_, ok, err = childrenExpressionParser.Parse(in)
if err != nil || !ok {
return
}
return ChildrenExpression{}, true, nil
})
package parser
import (
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
var conditionalAttribute parse.Parser[ConditionalAttribute] = conditionalAttributeParser{}
type conditionalAttributeParser struct{}
func (conditionalAttributeParser) Parse(pi *parse.Input) (r ConditionalAttribute, ok bool, err error) {
start := pi.Index()
// Strip leading whitespace and look for `if `.
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
return
}
if !peekPrefix(pi, "if ") {
pi.Seek(start)
return
}
// Parse the Go if expression.
if r.Expression, err = parseGo("if attribute", pi, goexpression.If); err != nil {
return
}
// Eat " {\n".
if _, ok, err = openBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("attribute if: unterminated (missing closing '{\n')", pi.PositionAt(start))
return
}
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
return
}
// Read the 'Then' attributes.
// If there's no match, there's a problem reading the attributes.
if r.Then, ok, err = (attributesParser{}).Parse(pi); err != nil || !ok {
err = parse.Error("attribute if: expected attributes in block, but none were found", pi.Position())
return
}
if len(r.Then) == 0 {
err = parse.Error("attribute if: invalid content or no attributes were found in the if block", pi.Position())
return
}
// Read the optional 'Else' Nodes.
if r.Else, ok, err = attributeElseExpression.Parse(pi); err != nil {
return
}
if ok && len(r.Else) == 0 {
err = parse.Error("attribute if: invalid content or no attributes were found in the else block", pi.Position())
return
}
// Clear any optional whitespace.
_, _, _ = parse.OptionalWhitespace.Parse(pi)
// Read the required closing brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("attribute if: missing end (expected '}')", pi.Position())
return
}
return r, true, nil
}
var attributeElseExpression parse.Parser[[]Attribute] = attributeElseExpressionParser{}
type attributeElseExpressionParser struct{}
func (attributeElseExpressionParser) Parse(in *parse.Input) (r []Attribute, ok bool, err error) {
start := in.Index()
// Strip any initial whitespace.
_, _, _ = parse.OptionalWhitespace.Parse(in)
// } else {
var endElseParser = parse.All(
parse.Rune('}'),
parse.OptionalWhitespace,
parse.String("else"),
parse.OptionalWhitespace,
parse.Rune('{'))
if _, ok, err = endElseParser.Parse(in); err != nil || !ok {
in.Seek(start)
return
}
// Else contents
if r, ok, err = (attributesParser{}).Parse(in); err != nil || !ok {
err = parse.Error("attribute if: expected attributes in else block, but none were found", in.Position())
return
}
return r, true, nil
}
package parser
import (
"github.com/a-h/parse"
)
// CSS.
// CSS Parser.
var cssParser = parse.Func(func(pi *parse.Input) (r CSSTemplate, ok bool, err error) {
from := pi.Position()
r = CSSTemplate{
Properties: []CSSProperty{},
}
// Parse the name.
var exp cssExpression
if exp, ok, err = cssExpressionParser.Parse(pi); err != nil || !ok {
return
}
r.Name = exp.Name
r.Expression = exp.Expression
for {
var cssProperty CSSProperty
// Try for an expression CSS declaration.
// background-color: { constants.BackgroundColor };
cssProperty, ok, err = expressionCSSPropertyParser.Parse(pi)
if err != nil {
return
}
if ok {
r.Properties = append(r.Properties, cssProperty)
continue
}
// Try for a constant CSS declaration.
// color: #ffffff;
cssProperty, ok, err = constantCSSPropertyParser.Parse(pi)
if err != nil {
return
}
if ok {
r.Properties = append(r.Properties, cssProperty)
continue
}
// Eat any whitespace.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
// Try for }
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("css property expression: missing closing brace", pi.Position())
return
}
r.Range = NewRange(from, pi.Position())
return r, true, nil
}
})
// css Func() {
type cssExpression struct {
Expression Expression
Name string
}
var cssExpressionParser = parse.Func(func(pi *parse.Input) (r cssExpression, ok bool, err error) {
start := pi.Index()
if !peekPrefix(pi, "css ") {
return r, false, nil
}
// Once we have the prefix, everything to the brace is Go.
// e.g.
// css (x []string) Test() {
// becomes:
// func (x []string) Test() templ.CSSComponent {
if r.Name, r.Expression, err = parseCSSFuncDecl(pi); err != nil {
return r, false, err
}
// Eat " {\n".
if _, ok, err = parse.All(openBraceWithOptionalPadding, parse.NewLine).Parse(pi); err != nil || !ok {
err = parse.Error("css expression: parameters missing open bracket", pi.PositionAt(start))
return
}
return r, true, nil
})
// CSS property name parser.
var cssPropertyNameFirst = "abcdefghijklmnopqrstuvwxyz-"
var cssPropertyNameSubsequent = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
var cssPropertyNameParser = parse.Func(func(in *parse.Input) (name string, ok bool, err error) {
start := in.Position()
var prefix, suffix string
if prefix, ok, err = parse.RuneIn(cssPropertyNameFirst).Parse(in); err != nil || !ok {
return
}
if suffix, ok, err = parse.StringUntil(parse.RuneNotIn(cssPropertyNameSubsequent)).Parse(in); err != nil || !ok {
in.Seek(start.Index)
return
}
if len(suffix)+1 > 128 {
ok = false
err = parse.Error("css property names must be < 128 characters long", in.Position())
return
}
return prefix + suffix, true, nil
})
// background-color: {%= constants.BackgroundColor %};
var expressionCSSPropertyParser = parse.Func(func(pi *parse.Input) (r ExpressionCSSProperty, ok bool, err error) {
start := pi.Index()
// Optional whitespace.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
// Property name.
if r.Name, ok, err = cssPropertyNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// <space>:<space>
if _, ok, err = parse.All(parse.OptionalWhitespace, parse.Rune(':'), parse.OptionalWhitespace).Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// { string }
var se Node
if se, ok, err = stringExpression.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
r.Value = se.(StringExpression)
// ;
if _, ok, err = parse.String(";").Parse(pi); err != nil || !ok {
err = parse.Error("missing expected semicolon (;)", pi.Position())
return
}
// \n
if _, ok, err = parse.NewLine.Parse(pi); err != nil || !ok {
err = parse.Error("missing expected linebreak", pi.Position())
return
}
return r, true, nil
})
// background-color: #ffffff;
var constantCSSPropertyParser = parse.Func(func(pi *parse.Input) (r ConstantCSSProperty, ok bool, err error) {
start := pi.Index()
// Optional whitespace.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
// Property name.
if r.Name, ok, err = cssPropertyNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// <space>:<space>
if _, ok, err = parse.All(parse.OptionalWhitespace, parse.Rune(':'), parse.OptionalWhitespace).Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// Everything until ';\n'
untilEnd := parse.All(
parse.OptionalWhitespace,
parse.Rune(';'),
parse.NewLine,
)
if r.Value, ok, err = parse.StringUntil(untilEnd).Parse(pi); err != nil || !ok {
err = parse.Error("missing expected semicolon and linebreak (;\\n", pi.Position())
return
}
// Chomp the ;\n
if _, ok, err = untilEnd.Parse(pi); err != nil || !ok {
err = parse.Error("failed to chomp semicolon and linebreak (;\\n)", pi.Position())
return
}
return r, true, nil
})
package parser
import (
"errors"
)
type diagnoser func(Node) ([]Diagnostic, error)
// Diagnostic for template file.
type Diagnostic struct {
Message string
Range Range
}
func walkTemplate(t TemplateFile, f func(Node) bool) {
for _, n := range t.Nodes {
hn, ok := n.(HTMLTemplate)
if !ok {
continue
}
walkNodes(hn.Children, f)
}
}
func walkNodes(t []Node, f func(Node) bool) {
for _, n := range t {
if !f(n) {
continue
}
if h, ok := n.(CompositeNode); ok {
walkNodes(h.ChildNodes(), f)
}
}
}
var diagnosers = []diagnoser{
useOfLegacyCallSyntaxDiagnoser,
}
func Diagnose(t TemplateFile) ([]Diagnostic, error) {
var diags []Diagnostic
var errs error
walkTemplate(t, func(n Node) bool {
for _, d := range diagnosers {
diag, err := d(n)
if err != nil {
errs = errors.Join(errs, err)
return false
}
diags = append(diags, diag...)
}
return true
})
return diags, errs
}
func useOfLegacyCallSyntaxDiagnoser(n Node) ([]Diagnostic, error) {
if c, ok := n.(CallTemplateExpression); ok {
return []Diagnostic{{
Message: "`{! foo }` syntax is deprecated. Use `@foo` syntax instead. Run `templ fmt .` to fix all instances.",
Range: c.Expression.Range,
}}, nil
}
return nil, nil
}
package parser
import (
"github.com/a-h/parse"
)
var doctypeStartParser = parse.StringInsensitive("<!doctype ")
var untilLtOrGt = parse.Or(lt, gt)
var stringUntilLtOrGt = parse.StringUntil(untilLtOrGt)
var docType = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
start := pi.Position()
var r DocType
if _, ok, err = doctypeStartParser.Parse(pi); err != nil || !ok {
return
}
// Once a doctype has started, take everything until the end.
if r.Value, ok, err = stringUntilLtOrGt.Parse(pi); err != nil || !ok {
err = parse.Error("unclosed DOCTYPE", start)
return
}
// Clear the final '>'.
if _, ok, err = gt.Parse(pi); err != nil || !ok {
err = parse.Error("unclosed DOCTYPE", start)
return
}
return r, true, nil
})
package parser
import (
"fmt"
"html"
"strings"
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
// Element.
// Element open tag.
type elementOpenTag struct {
Name string
Attributes []Attribute
IndentAttrs bool
NameRange Range
Void bool
}
var elementOpenTagParser = parse.Func(func(pi *parse.Input) (e elementOpenTag, ok bool, err error) {
start := pi.Position()
// <
if _, ok, err = lt.Parse(pi); err != nil || !ok {
return
}
// Element name.
l := pi.Position().Line
if e.Name, ok, err = elementNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start.Index)
return
}
e.NameRange = NewRange(pi.PositionAt(pi.Index()-len(e.Name)), pi.Position())
if e.Attributes, ok, err = (attributesParser{}).Parse(pi); err != nil || !ok {
pi.Seek(start.Index)
return
}
// If any attribute is not on the same line as the element name, indent them.
if pi.Position().Line != l {
e.IndentAttrs = true
}
// Optional whitespace.
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
pi.Seek(start.Index)
return
}
// />
if _, ok, err = parse.String("/>").Parse(pi); err != nil {
return
}
if ok {
e.Void = true
return
}
// >
if _, ok, err = gt.Parse(pi); err != nil {
return
}
// If it's not a self-closing or complete open element, we have an error.
if !ok {
err = parse.Error(fmt.Sprintf("<%s>: malformed open element", e.Name), pi.Position())
return
}
return e, true, nil
})
// Attribute name.
var (
attributeNameFirst = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_@"
attributeNameSubsequent = attributeNameFirst + "-.0123456789*"
attributeNameParser = parse.Func(func(in *parse.Input) (name string, ok bool, err error) {
start := in.Index()
var prefix, suffix string
if prefix, ok, err = parse.RuneIn(attributeNameFirst).Parse(in); err != nil || !ok {
return
}
if suffix, ok, err = parse.StringUntil(parse.RuneNotIn(attributeNameSubsequent)).Parse(in); err != nil {
in.Seek(start)
return
}
if len(suffix)+1 > 128 {
ok = false
err = parse.Error("attribute names must be < 128 characters long", in.Position())
return
}
return prefix + suffix, true, nil
})
)
// Constant attribute.
var (
attributeConstantValueParser = parse.StringUntil(parse.Rune('"'))
attributeConstantValueSingleQuoteParser = parse.StringUntil(parse.Rune('\''))
constantAttributeParser = parse.Func(func(pi *parse.Input) (attr ConstantAttribute, ok bool, err error) {
start := pi.Index()
// Optional whitespace leader.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
// Attribute name.
if attr.Name, ok, err = attributeNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
attr.NameRange = NewRange(pi.PositionAt(pi.Index()-len(attr.Name)), pi.Position())
// ="
result, ok, err := parse.Or(parse.String(`="`), parse.String(`='`)).Parse(pi)
if err != nil || !ok {
pi.Seek(start)
return
}
valueParser := attributeConstantValueParser
closeParser := parse.String(`"`)
if result.B.OK {
valueParser = attributeConstantValueSingleQuoteParser
closeParser = parse.String(`'`)
attr.SingleQuote = true
}
// Attribute value.
if attr.Value, ok, err = valueParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
attr.Value = html.UnescapeString(attr.Value)
// Only use single quotes if actually required, due to double quote in the value (prefer double quotes).
if attr.SingleQuote && !strings.Contains(attr.Value, "\"") {
attr.SingleQuote = false
}
// " - closing quote.
if _, ok, err = closeParser.Parse(pi); err != nil || !ok {
err = parse.Error(fmt.Sprintf("missing closing quote on attribute %q", attr.Name), pi.Position())
return
}
return attr, true, nil
})
)
// BoolConstantAttribute.
var boolConstantAttributeParser = parse.Func(func(pi *parse.Input) (attr BoolConstantAttribute, ok bool, err error) {
start := pi.Index()
// Optional whitespace leader.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
// Attribute name.
if attr.Name, ok, err = attributeNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
attr.NameRange = NewRange(pi.PositionAt(pi.Index()-len(attr.Name)), pi.Position())
// We have a name, but if we have an equals sign, it's not a constant boolean attribute.
next, ok := pi.Peek(1)
if !ok {
err = parse.Error("boolConstantAttributeParser: unexpected EOF after attribute name", pi.Position())
return
}
if next == "=" || next == "?" {
// It's one of the other attribute types.
pi.Seek(start)
return attr, false, nil
}
if !(next == " " || next == "\t" || next == "\r" || next == "\n" || next == "/" || next == ">") {
err = parse.Error(fmt.Sprintf("boolConstantAttributeParser: expected attribute name to end with space, newline, '/>' or '>', but got %q", next), pi.Position())
return attr, false, err
}
return attr, true, nil
})
// BoolExpressionAttribute.
var boolExpressionStart = parse.Or(parse.String("?={ "), parse.String("?={"))
var boolExpressionAttributeParser = parse.Func(func(pi *parse.Input) (r BoolExpressionAttribute, ok bool, err error) {
start := pi.Index()
// Optional whitespace leader.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// Attribute name.
if r.Name, ok, err = attributeNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
r.NameRange = NewRange(pi.PositionAt(pi.Index()-len(r.Name)), pi.Position())
// Check whether this is a boolean expression attribute.
if _, ok, err = boolExpressionStart.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// Once we have a prefix, we must have an expression that returns a boolean.
if r.Expression, err = parseGo("boolean attribute", pi, goexpression.Expression); err != nil {
return r, false, err
}
// Eat the Final brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("boolean expression: missing closing brace", pi.Position())
pi.Seek(start)
return
}
return r, true, nil
})
var expressionAttributeParser = parse.Func(func(pi *parse.Input) (attr ExpressionAttribute, ok bool, err error) {
start := pi.Index()
// Optional whitespace leader.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
// Attribute name.
if attr.Name, ok, err = attributeNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
attr.NameRange = NewRange(pi.PositionAt(pi.Index()-len(attr.Name)), pi.Position())
// ={
if _, ok, err = parse.Or(parse.String("={ "), parse.String("={")).Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// Expression.
if attr.Expression, err = parseGoSliceArgs(pi); err != nil {
return attr, false, err
}
// Eat whitespace, plus the final brace.
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
return attr, false, err
}
if _, ok, err = closeBrace.Parse(pi); err != nil || !ok {
err = parse.Error("string expression attribute: missing closing brace", pi.Position())
return
}
return attr, true, nil
})
var spreadAttributesParser = parse.Func(func(pi *parse.Input) (attr SpreadAttributes, ok bool, err error) {
start := pi.Index()
// Optional whitespace leader.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
// Eat the first brace.
if _, ok, err = openBraceWithOptionalPadding.Parse(pi); err != nil ||
!ok {
pi.Seek(start)
return
}
// Expression.
if attr.Expression, err = parseGo("spread attributes", pi, goexpression.Expression); err != nil {
return
}
// Check if end of expression has "..." for spread.
if !strings.HasSuffix(attr.Expression.Value, "...") {
pi.Seek(start)
ok = false
return
}
// Remove extra spread characters from expression.
attr.Expression.Value = strings.TrimSuffix(attr.Expression.Value, "...")
attr.Expression.Range.To.Col -= 3
attr.Expression.Range.To.Index -= 3
// Eat the final brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("attribute spread expression: missing closing brace", pi.Position())
return
}
return attr, true, nil
})
// Attributes.
type attributeParser struct{}
func (attributeParser) Parse(in *parse.Input) (out Attribute, ok bool, err error) {
if out, ok, err = boolExpressionAttributeParser.Parse(in); err != nil || ok {
return
}
if out, ok, err = expressionAttributeParser.Parse(in); err != nil || ok {
return
}
if out, ok, err = conditionalAttribute.Parse(in); err != nil || ok {
return
}
if out, ok, err = boolConstantAttributeParser.Parse(in); err != nil || ok {
return
}
if out, ok, err = spreadAttributesParser.Parse(in); err != nil || ok {
return
}
if out, ok, err = constantAttributeParser.Parse(in); err != nil || ok {
return
}
return
}
var attribute attributeParser
type attributesParser struct{}
func (attributesParser) Parse(in *parse.Input) (attributes []Attribute, ok bool, err error) {
for {
var attr Attribute
attr, ok, err = attribute.Parse(in)
if err != nil {
return
}
if !ok {
break
}
attributes = append(attributes, attr)
}
return attributes, true, nil
}
// Element name.
var (
elementNameFirst = "abcdefghijklmnopqrstuvwxyz"
elementNameSubsequent = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-:"
elementNameParser = parse.Func(func(in *parse.Input) (name string, ok bool, err error) {
start := in.Index()
var prefix, suffix string
if prefix, ok, err = parse.RuneIn(elementNameFirst).Parse(in); err != nil || !ok {
return
}
if suffix, ok, err = parse.StringUntil(parse.RuneNotIn(elementNameSubsequent)).Parse(in); err != nil || !ok {
in.Seek(start)
return
}
if len(suffix)+1 > 128 {
ok = false
err = parse.Error("element names must be < 128 characters long", in.Position())
return
}
return prefix + suffix, true, nil
})
)
// Void element closer.
var voidElementCloser voidElementCloserParser
type voidElementCloserParser struct{}
var voidElementCloseTags = []string{"</area>", "</base>", "</br>", "</col>", "</command>", "</embed>", "</hr>", "</img>", "</input>", "</keygen>", "</link>", "</meta>", "</param>", "</source>", "</track>", "</wbr>"}
func (voidElementCloserParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
var ve string
for _, ve = range voidElementCloseTags {
s, canPeekLen := pi.Peek(len(ve))
if !canPeekLen {
continue
}
if !strings.EqualFold(s, ve) {
continue
}
// Found a match.
ok = true
break
}
if !ok {
return nil, false, nil
}
pi.Take(len(ve))
return nil, true, nil
}
// Element.
var element elementParser
type elementParser struct{}
func (elementParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
var r Element
start := pi.Position()
// Check the open tag.
var ot elementOpenTag
if ot, ok, err = elementOpenTagParser.Parse(pi); err != nil || !ok {
return
}
r.Name = ot.Name
r.Attributes = ot.Attributes
r.IndentAttrs = ot.IndentAttrs
r.NameRange = ot.NameRange
// Once we've got an open tag, the rest must be present.
l := pi.Position().Line
// If the element is self-closing, even if it's not really a void element (br, hr etc.), we can return early.
if ot.Void || r.IsVoidElement() {
// Escape early, no need to try to parse children for self-closing elements.
return addTrailingSpaceAndValidate(start, r, pi)
}
// Parse children.
closer := StripType(parse.All(parse.String("</"), parse.String(ot.Name), parse.Rune('>')))
tnp := newTemplateNodeParser(closer, fmt.Sprintf("<%s>: close tag", ot.Name))
nodes, _, err := tnp.Parse(pi)
if err != nil {
notFoundErr, isNotFoundError := err.(UntilNotFoundError)
if isNotFoundError {
err = notFoundErr.ParseError
}
return r, false, err
}
r.Children = nodes.Nodes
// If the children are not all on the same line, indent them.
if l != pi.Position().Line {
r.IndentChildren = true
}
// Close tag.
_, ok, err = closer.Parse(pi)
if err != nil {
return r, false, err
}
if !ok {
err = parse.Error(fmt.Sprintf("<%s>: expected end tag not present or invalid tag contents", r.Name), pi.Position())
return r, false, err
}
return addTrailingSpaceAndValidate(start, r, pi)
}
func addTrailingSpaceAndValidate(start parse.Position, e Element, pi *parse.Input) (n Node, ok bool, err error) {
// Elide any void close tags.
if _, _, err = voidElementCloser.Parse(pi); err != nil {
return e, false, err
}
// Add trailing space.
ws, _, err := parse.Whitespace.Parse(pi)
if err != nil {
return e, false, err
}
e.TrailingSpace, err = NewTrailingSpace(ws)
if err != nil {
return e, false, err
}
// Validate.
var msgs []string
if msgs, ok = e.Validate(); !ok {
err = parse.Error(fmt.Sprintf("<%s>: %s", e.Name, strings.Join(msgs, ", ")), start)
return e, false, err
}
return e, true, nil
}
package parser
import (
"strings"
"github.com/a-h/parse"
)
// StripType takes the parser and throws away the return value.
func StripType[T any](p parse.Parser[T]) parse.Parser[any] {
return parse.Func(func(in *parse.Input) (out any, ok bool, err error) {
return p.Parse(in)
})
}
func ExpressionOf(p parse.Parser[string]) parse.Parser[Expression] {
return parse.Func(func(in *parse.Input) (out Expression, ok bool, err error) {
from := in.Position()
var exp string
if exp, ok, err = p.Parse(in); err != nil || !ok {
return
}
return NewExpression(exp, from, in.Position()), true, nil
})
}
var lt = parse.Rune('<')
var gt = parse.Rune('>')
var openBrace = parse.String("{")
var optionalSpaces = parse.StringFrom(parse.Optional(
parse.AtLeast(1, parse.Rune(' '))))
var openBraceWithPadding = parse.StringFrom(optionalSpaces,
openBrace,
optionalSpaces)
var openBraceWithOptionalPadding = parse.Any(openBraceWithPadding, openBrace)
var closeBrace = parse.String("}")
var closeBraceWithOptionalPadding = parse.StringFrom(optionalSpaces, closeBrace)
var dblCloseBrace = parse.String("}}")
var dblCloseBraceWithOptionalPadding = parse.StringFrom(optionalSpaces, dblCloseBrace)
var openBracket = parse.String("(")
var closeBracket = parse.String(")")
var stringUntilNewLine = parse.StringUntil[string](parse.NewLine)
var newLineOrEOF = parse.Or(parse.NewLine, parse.EOF[string]())
var stringUntilNewLineOrEOF = parse.StringUntil(newLineOrEOF)
var jsOrGoSingleLineComment = parse.StringFrom(parse.String("//"), parse.StringUntil(parse.Any(parse.NewLine, parse.EOF[string]())))
var jsOrGoMultiLineComment = parse.StringFrom(parse.String("/*"), parse.StringUntil(parse.String("*/")))
var exp = expressionParser{
startBraceCount: 1,
}
type expressionParser struct {
startBraceCount int
}
func (p expressionParser) Parse(pi *parse.Input) (s Expression, ok bool, err error) {
from := pi.Position()
braceCount := p.startBraceCount
sb := new(strings.Builder)
loop:
for {
var result string
// Try to parse a single line comment.
if result, ok, err = jsOrGoSingleLineComment.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Try to parse a multi-line comment.
if result, ok, err = jsOrGoMultiLineComment.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Try to read a string literal.
if result, ok, err = string_lit.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Also try for a rune literal.
if result, ok, err = rune_lit.Parse(pi); err != nil {
return
}
if ok {
sb.WriteString(result)
continue
}
// Try opener.
if result, ok, err = openBrace.Parse(pi); err != nil {
return
}
if ok {
braceCount++
sb.WriteString(result)
continue
}
// Try closer.
startOfCloseBrace := pi.Index()
if result, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil {
return
}
if ok {
braceCount--
if braceCount < 0 {
err = parse.Error("expression: too many closing braces", pi.Position())
return
}
if braceCount == 0 {
pi.Seek(startOfCloseBrace)
break loop
}
sb.WriteString(result)
continue
}
// Read anything else.
var c string
c, ok = pi.Take(1)
if !ok {
break loop
}
if rune(c[0]) == 65533 { // Invalid Unicode.
break loop
}
sb.WriteString(c)
}
if braceCount != 0 {
err = parse.Error("expression: unexpected brace count", pi.Position())
return
}
return NewExpression(sb.String(), from, pi.Position()), true, nil
}
// Letters and digits
var octal_digit = parse.RuneIn("01234567")
var hex_digit = parse.RuneIn("0123456789ABCDEFabcdef")
// https://go.dev/ref/spec#Rune_literals
var rune_lit = parse.StringFrom(
parse.Rune('\''),
parse.StringFrom(parse.Until(
parse.Any(unicode_value_rune, byte_value),
parse.Rune('\''),
)),
parse.Rune('\''),
)
var unicode_value_rune = parse.Any(little_u_value, big_u_value, escaped_char, parse.RuneNotIn("'"))
// byte_value = octal_byte_value | hex_byte_value .
var byte_value = parse.Any(octal_byte_value, hex_byte_value)
// octal_byte_value = `\` octal_digit octal_digit octal_digit .
var octal_byte_value = parse.StringFrom(
parse.String(`\`),
octal_digit, octal_digit, octal_digit,
)
// hex_byte_value = `\` "x" hex_digit hex_digit .
var hex_byte_value = parse.StringFrom(
parse.String(`\x`),
hex_digit, hex_digit,
)
// little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
var little_u_value = parse.StringFrom(
parse.String(`\u`),
hex_digit, hex_digit,
hex_digit, hex_digit,
)
// big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
var big_u_value = parse.StringFrom(
parse.String(`\U`),
hex_digit, hex_digit, hex_digit, hex_digit,
hex_digit, hex_digit, hex_digit, hex_digit,
)
// escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
var escaped_char = parse.StringFrom(
parse.Rune('\\'),
parse.Any(
parse.Rune('a'),
parse.Rune('b'),
parse.Rune('f'),
parse.Rune('n'),
parse.Rune('r'),
parse.Rune('t'),
parse.Rune('v'),
parse.Rune('\\'),
parse.Rune('\''),
parse.Rune('"'),
),
)
// https://go.dev/ref/spec#String_literals
var string_lit = parse.Any(parse.String(`""`), parse.String(`''`), interpreted_string_lit, raw_string_lit)
var interpreted_string_lit = parse.StringFrom(
parse.Rune('"'),
parse.StringFrom(parse.Until(
parse.Any(unicode_value_interpreted, byte_value),
parse.Rune('"'),
)),
parse.Rune('"'),
)
var unicode_value_interpreted = parse.Any(little_u_value, big_u_value, escaped_char, parse.RuneNotIn("\n\""))
var raw_string_lit = parse.StringFrom(
parse.Rune('`'),
parse.StringFrom(parse.Until(
unicode_value_raw,
parse.Rune('`'),
)),
parse.Rune('`'),
)
var unicode_value_raw = parse.Any(little_u_value, big_u_value, escaped_char, parse.RuneNotIn("`"))
package parser
import (
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
var forExpression parse.Parser[Node] = forExpressionParser{}
type forExpressionParser struct{}
func (forExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
var r ForExpression
start := pi.Index()
// Strip leading whitespace and look for `for `.
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
return r, false, err
}
if !peekPrefix(pi, "for ") {
pi.Seek(start)
return r, false, nil
}
// Parse the Go for expression.
if r.Expression, err = parseGo("for", pi, goexpression.For); err != nil {
return r, false, err
}
// Eat " {\n".
if _, ok, err = parse.All(openBraceWithOptionalPadding, parse.NewLine).Parse(pi); err != nil || !ok {
pi.Seek(start)
return r, false, err
}
// Node contents.
tnp := newTemplateNodeParser(closeBraceWithOptionalPadding, "for expression closing brace")
var nodes Nodes
if nodes, ok, err = tnp.Parse(pi); err != nil || !ok {
err = parse.Error("for: expected nodes, but none were found", pi.Position())
return
}
r.Children = nodes.Nodes
// Read the required closing brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("for: "+unterminatedMissingEnd, pi.Position())
return
}
return r, true, nil
}
package parser
import (
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
var goCode = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.Or(parse.String("{{ "), parse.String("{{")).Parse(pi); err != nil || !ok {
return
}
// Once we have a prefix, we must have an expression that returns a string, with optional err.
l := pi.Position().Line
var r GoCode
if r.Expression, err = parseGo("go code", pi, goexpression.Expression); err != nil {
return r, false, err
}
if l != pi.Position().Line {
r.Multiline = true
}
// Clear any optional whitespace.
_, _, _ = parse.OptionalWhitespace.Parse(pi)
// }}
if _, ok, err = dblCloseBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("go code: missing close braces", pi.Position())
return
}
// Parse trailing whitespace.
ws, _, err := parse.Whitespace.Parse(pi)
if err != nil {
return r, false, err
}
r.TrailingSpace, err = NewTrailingSpace(ws)
if err != nil {
return r, false, err
}
return r, true, nil
})
package parser
import (
"github.com/a-h/parse"
)
var goSingleLineCommentStart = parse.String("//")
var goSingleLineCommentEnd = parse.Any(parse.NewLine, parse.EOF[string]())
type goSingleLineCommentParser struct {
}
var goSingleLineComment = goSingleLineCommentParser{}
func (p goSingleLineCommentParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Comment start.
var c GoComment
if _, ok, err = goSingleLineCommentStart.Parse(pi); err != nil || !ok {
return
}
// Once we've got the comment start sequence, parse anything until the end
// sequence as the comment contents.
if c.Contents, ok, err = parse.StringUntil(goSingleLineCommentEnd).Parse(pi); err != nil || !ok {
err = parse.Error("expected end comment literal '\n' not found", pi.Position())
return
}
// Move past the end element.
_, _, _ = goSingleLineCommentEnd.Parse(pi)
// Return the comment.
c.Multiline = false
return c, true, nil
}
var goMultiLineCommentStart = parse.String("/*")
var goMultiLineCommentEnd = parse.String("*/")
type goMultiLineCommentParser struct {
}
var goMultiLineComment = goMultiLineCommentParser{}
func (p goMultiLineCommentParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Comment start.
start := pi.Position()
var c GoComment
if _, ok, err = goMultiLineCommentStart.Parse(pi); err != nil || !ok {
return
}
// Once we've got the comment start sequence, parse anything until the end
// sequence as the comment contents.
if c.Contents, ok, err = parse.StringUntil(goMultiLineCommentEnd).Parse(pi); err != nil || !ok {
err = parse.Error("expected end comment literal '*/' not found", start)
return
}
// Move past the end element.
_, _, _ = goMultiLineCommentEnd.Parse(pi)
// Return the comment.
c.Multiline = true
return c, true, nil
}
var goComment = parse.Any[Node](goSingleLineComment, goMultiLineComment)
package goexpression
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/scanner"
"go/token"
"regexp"
"strings"
"unicode"
)
var (
ErrContainerFuncNotFound = errors.New("parser error: templ container function not found")
ErrExpectedNodeNotFound = errors.New("parser error: expected node not found")
)
var defaultRegexp = regexp.MustCompile(`^default\s*:`)
func Case(content string) (start, end int, err error) {
if !(strings.HasPrefix(content, "case ") || defaultRegexp.MatchString(content)) {
return 0, 0, ErrExpectedNodeNotFound
}
prefix := "switch {\n"
src := prefix + content
start, end, err = extract(src, func(body []ast.Stmt) (start, end int, err error) {
sw, ok := body[0].(*ast.SwitchStmt)
if !ok {
return 0, 0, ErrExpectedNodeNotFound
}
if sw.Body == nil || len(sw.Body.List) == 0 {
return 0, 0, ErrExpectedNodeNotFound
}
stmt, ok := sw.Body.List[0].(*ast.CaseClause)
if !ok {
return 0, 0, ErrExpectedNodeNotFound
}
start = int(stmt.Case) - 1
end = int(stmt.Colon)
return start, end, nil
})
if err != nil {
return 0, 0, err
}
// Since we added a `switch {` prefix, we need to remove it.
start -= len(prefix)
end -= len(prefix)
return start, end, nil
}
func If(content string) (start, end int, err error) {
if !strings.HasPrefix(content, "if") {
return 0, 0, ErrExpectedNodeNotFound
}
return extract(content, func(body []ast.Stmt) (start, end int, err error) {
stmt, ok := body[0].(*ast.IfStmt)
if !ok {
return 0, 0, ErrExpectedNodeNotFound
}
start = int(stmt.If) + len("if")
end = latestEnd(start, stmt.Init, stmt.Cond)
return start, end, nil
})
}
func For(content string) (start, end int, err error) {
if !strings.HasPrefix(content, "for") {
return 0, 0, ErrExpectedNodeNotFound
}
return extract(content, func(body []ast.Stmt) (start, end int, err error) {
stmt := body[0]
switch stmt := stmt.(type) {
case *ast.ForStmt:
start = int(stmt.For) + len("for")
end = latestEnd(start, stmt.Init, stmt.Cond, stmt.Post)
return start, end, nil
case *ast.RangeStmt:
start = int(stmt.For) + len("for")
end = latestEnd(start, stmt.Key, stmt.Value, stmt.X)
return start, end, nil
}
return 0, 0, ErrExpectedNodeNotFound
})
}
func Switch(content string) (start, end int, err error) {
if !strings.HasPrefix(content, "switch") {
return 0, 0, ErrExpectedNodeNotFound
}
return extract(content, func(body []ast.Stmt) (start, end int, err error) {
stmt := body[0]
switch stmt := stmt.(type) {
case *ast.SwitchStmt:
start = int(stmt.Switch) + len("switch")
end = latestEnd(start, stmt.Init, stmt.Tag)
return start, end, nil
case *ast.TypeSwitchStmt:
start = int(stmt.Switch) + len("switch")
end = latestEnd(start, stmt.Init, stmt.Assign)
return start, end, nil
}
return 0, 0, ErrExpectedNodeNotFound
})
}
func TemplExpression(src string) (start, end int, err error) {
var s scanner.Scanner
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
errorHandler := func(pos token.Position, msg string) {
err = fmt.Errorf("error parsing expression: %v", msg)
}
s.Init(file, []byte(src), errorHandler, scanner.ScanComments)
// Read chains of identifiers, e.g.:
// components.Variable
// components[0].Variable
// components["name"].Function()
// functionCall(withLots(), func() { return true })
ep := NewExpressionParser()
for {
pos, tok, lit := s.Scan()
stop, err := ep.Insert(pos, tok, lit)
if err != nil {
return 0, 0, err
}
if stop {
break
}
}
return 0, ep.End, nil
}
func Expression(src string) (start, end int, err error) {
var s scanner.Scanner
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
errorHandler := func(pos token.Position, msg string) {
err = fmt.Errorf("error parsing expression: %v", msg)
}
s.Init(file, []byte(src), errorHandler, scanner.ScanComments)
// Read chains of identifiers and constants up until RBRACE, e.g.:
// true
// 123.45 == true
// components.Variable
// components[0].Variable
// components["name"].Function()
// functionCall(withLots(), func() { return true })
// !true
parenDepth := 0
bracketDepth := 0
braceDepth := 0
loop:
for {
pos, tok, lit := s.Scan()
if tok == token.EOF {
break loop
}
switch tok {
case token.LPAREN: // (
parenDepth++
case token.RPAREN: // )
end = int(pos)
parenDepth--
case token.LBRACK: // [
bracketDepth++
case token.RBRACK: // ]
end = int(pos)
bracketDepth--
case token.LBRACE: // {
braceDepth++
case token.RBRACE: // }
braceDepth--
if braceDepth < 0 {
// We've hit the end of the expression.
break loop
}
end = int(pos)
case token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING:
end = int(pos) + len(lit) - 1
case token.SEMICOLON:
continue
case token.COMMENT:
end = int(pos) + len(lit) - 1
case token.ILLEGAL:
return 0, 0, fmt.Errorf("illegal token: %v", lit)
default:
end = int(pos) + len(tok.String()) - 1
}
}
return start, end, nil
}
func SliceArgs(content string) (expr string, err error) {
prefix := "package main\nvar templ_args = []any{"
src := prefix + content + "}"
node, parseErr := parser.ParseFile(token.NewFileSet(), "", src, parser.AllErrors)
if node == nil {
return expr, parseErr
}
var from, to int
inspectFirstNode(node, func(n ast.Node) bool {
decl, ok := n.(*ast.CompositeLit)
if !ok {
return true
}
from = int(decl.Lbrace)
to = int(decl.Rbrace) - 1
for _, e := range decl.Elts {
to = int(e.End()) - 1
}
if to > int(decl.Rbrace)-1 {
to = int(decl.Rbrace) - 1
}
betweenEndAndBrace := src[to : decl.Rbrace-1]
var hasCodeBetweenEndAndBrace bool
for _, r := range betweenEndAndBrace {
if !unicode.IsSpace(r) {
hasCodeBetweenEndAndBrace = true
break
}
}
if hasCodeBetweenEndAndBrace {
to = int(decl.Rbrace) - 1
}
return false
})
return src[from:to], err
}
// Func returns the Go code up to the opening brace of the function body.
func Func(content string) (name, expr string, err error) {
prefix := "package main\n"
src := prefix + content
node, parseErr := parser.ParseFile(token.NewFileSet(), "", src, parser.AllErrors)
if node == nil {
return name, expr, parseErr
}
inspectFirstNode(node, func(n ast.Node) bool {
// Find the first function declaration.
fn, ok := n.(*ast.FuncDecl)
if !ok {
return true
}
start := int(fn.Pos()) + len("func")
end := fn.Type.Params.End() - 1
if len(src) < int(end) {
err = errors.New("parser error: function identifier")
return false
}
expr = strings.Clone(src[start:end])
name = fn.Name.Name
return false
})
return name, expr, err
}
func latestEnd(start int, nodes ...ast.Node) (end int) {
end = start
for _, n := range nodes {
if n == nil {
continue
}
if int(n.End())-1 > end {
end = int(n.End()) - 1
}
}
return end
}
func inspectFirstNode(node ast.Node, f func(ast.Node) bool) {
var stop bool
ast.Inspect(node, func(n ast.Node) bool {
if stop {
return true
}
if f(n) {
return true
}
stop = true
return false
})
}
// Extract a Go expression from the content.
// The Go expression starts at "start" and ends at "end".
// The reader should skip until "length" to pass over the expression and into the next
// logical block.
type Extractor func(body []ast.Stmt) (start, end int, err error)
func extract(content string, extractor Extractor) (start, end int, err error) {
prefix := "package main\nfunc templ_container() {\n"
src := prefix + content
node, parseErr := parser.ParseFile(token.NewFileSet(), "", src, parser.AllErrors)
if node == nil {
return 0, 0, parseErr
}
var found bool
inspectFirstNode(node, func(n ast.Node) bool {
// Find the "templ_container" function.
fn, ok := n.(*ast.FuncDecl)
if !ok {
return true
}
if fn.Name.Name != "templ_container" {
err = ErrContainerFuncNotFound
return false
}
if fn.Body.List == nil || len(fn.Body.List) == 0 {
err = ErrExpectedNodeNotFound
return false
}
found = true
start, end, err = extractor(fn.Body.List)
return false
})
if !found {
return 0, 0, ErrExpectedNodeNotFound
}
start -= len(prefix)
end -= len(prefix)
if end > len(content) {
end = len(content)
}
if start > end {
start = end
}
return start, end, err
}
package goexpression
import (
"fmt"
"go/token"
)
type Stack[T any] []T
func (s *Stack[T]) Push(v T) {
*s = append(*s, v)
}
func (s *Stack[T]) Pop() (v T) {
if len(*s) == 0 {
return v
}
v = (*s)[len(*s)-1]
*s = (*s)[:len(*s)-1]
return v
}
func (s *Stack[T]) Peek() (v T) {
if len(*s) == 0 {
return v
}
return (*s)[len(*s)-1]
}
var goTokenOpenToClose = map[token.Token]token.Token{
token.LPAREN: token.RPAREN,
token.LBRACE: token.RBRACE,
token.LBRACK: token.RBRACK,
}
var goTokenCloseToOpen = map[token.Token]token.Token{
token.RPAREN: token.LPAREN,
token.RBRACE: token.LBRACE,
token.RBRACK: token.LBRACK,
}
type ErrUnbalanced struct {
Token token.Token
}
func (e ErrUnbalanced) Error() string {
return fmt.Sprintf("unbalanced '%s'", e.Token)
}
func NewExpressionParser() *ExpressionParser {
return &ExpressionParser{
Stack: make(Stack[token.Token], 0),
Previous: token.PERIOD,
Fns: make(Stack[int], 0),
}
}
type ExpressionParser struct {
Stack Stack[token.Token]
End int
Previous token.Token
Fns Stack[int] // Stack of function depths.
}
func (ep *ExpressionParser) setEnd(pos token.Pos, tok token.Token, lit string) {
ep.End = int(pos) + len(tokenString(tok, lit)) - 1
}
func (ep *ExpressionParser) hasSpaceBeforeCurrentToken(pos token.Pos) bool {
return (int(pos) - 1) > ep.End
}
func (ep *ExpressionParser) isTopLevel() bool {
return len(ep.Fns) == 0 && len(ep.Stack) == 0
}
func (ep *ExpressionParser) Insert(
pos token.Pos,
tok token.Token,
lit string,
) (stop bool, err error) {
defer func() {
ep.Previous = tok
}()
// Handle function literals e.g. func() { fmt.Println("Hello") }
// By pushing the current depth onto the stack, we prevent stopping
// until we've closed the function.
if tok == token.FUNC {
ep.Fns.Push(len(ep.Stack))
ep.setEnd(pos, tok, lit)
return false, nil
}
// If we're opening a pair, we don't stop until we've closed it.
if _, isOpener := goTokenOpenToClose[tok]; isOpener {
// If we're at an open brace, at the top level, where a space has been used, stop.
if tok == token.LBRACE && ep.isTopLevel() {
// Previous was paren, e.g. () {
if ep.Previous == token.RPAREN {
return true, nil
}
// Previous was ident that isn't a type.
// In `name {`, `name` is considered to be a variable.
// In `name{`, `name` is considered to be a type name.
if ep.Previous == token.IDENT && ep.hasSpaceBeforeCurrentToken(pos) {
return true, nil
}
}
ep.Stack.Push(tok)
ep.setEnd(pos, tok, lit)
return false, nil
}
if opener, isCloser := goTokenCloseToOpen[tok]; isCloser {
if len(ep.Stack) == 0 {
// We've got a close token, but there's nothing to close, so we must be done.
return true, nil
}
actual := ep.Stack.Pop()
if !isCloser {
return false, ErrUnbalanced{tok}
}
if actual != opener {
return false, ErrUnbalanced{tok}
}
if tok == token.RBRACE {
// If we're closing a function, pop the function depth.
if len(ep.Stack) == ep.Fns.Peek() {
ep.Fns.Pop()
}
}
ep.setEnd(pos, tok, lit)
return false, nil
}
// If we're in a function literal slice, or pair, we allow anything until we close it.
if len(ep.Fns) > 0 || len(ep.Stack) > 0 {
ep.setEnd(pos, tok, lit)
return false, nil
}
// We allow an ident to follow a period or a closer.
// e.g. "package.name", "typeName{field: value}.name()".
// or "call().name", "call().name()".
// But not "package .name" or "typeName{field: value} .name()".
if tok == token.IDENT && (ep.Previous == token.PERIOD || isCloser(ep.Previous)) {
if isCloser(ep.Previous) && ep.hasSpaceBeforeCurrentToken(pos) {
// This token starts later than the last ending, which means
// there's a space.
return true, nil
}
ep.setEnd(pos, tok, lit)
return false, nil
}
if tok == token.PERIOD && (ep.Previous == token.IDENT || isCloser(ep.Previous)) {
ep.setEnd(pos, tok, lit)
return false, nil
}
// No match, so stop.
return true, nil
}
func tokenString(tok token.Token, lit string) string {
if tok.IsKeyword() || tok.IsOperator() {
return tok.String()
}
return lit
}
func isCloser(tok token.Token) bool {
_, ok := goTokenCloseToOpen[tok]
return ok
}
package parser
import (
"fmt"
"strings"
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
func parseGoFuncDecl(prefix string, pi *parse.Input) (name string, expression Expression, err error) {
prefix = prefix + " "
from := pi.Index()
src, _ := pi.Peek(-1)
src = strings.TrimPrefix(src, prefix)
name, expr, err := goexpression.Func("func " + src)
if err != nil {
return name, expression, parse.Error(fmt.Sprintf("invalid %s declaration: %v", prefix, err.Error()), pi.Position())
}
pi.Take(len(prefix) + len(expr))
to := pi.Position()
return name, NewExpression(expr, pi.PositionAt(from+len(prefix)), to), nil
}
func parseTemplFuncDecl(pi *parse.Input) (name string, expression Expression, err error) {
return parseGoFuncDecl("templ", pi)
}
func parseCSSFuncDecl(pi *parse.Input) (name string, expression Expression, err error) {
return parseGoFuncDecl("css", pi)
}
func parseGoSliceArgs(pi *parse.Input) (r Expression, err error) {
from := pi.Position()
src, _ := pi.Peek(-1)
expr, err := goexpression.SliceArgs(src)
if err != nil {
return r, err
}
pi.Take(len(expr))
to := pi.Position()
return NewExpression(expr, from, to), nil
}
func peekPrefix(pi *parse.Input, prefixes ...string) bool {
for _, prefix := range prefixes {
pp, ok := pi.Peek(len(prefix))
if !ok {
continue
}
if prefix == pp {
return true
}
}
return false
}
type extractor func(content string) (start, end int, err error)
func parseGo(name string, pi *parse.Input, e extractor) (r Expression, err error) {
from := pi.Index()
src, _ := pi.Peek(-1)
start, end, err := e(src)
if err != nil {
return r, parse.Error(fmt.Sprintf("%s: invalid go expression: %v", name, err.Error()), pi.Position())
}
expr := src[start:end]
pi.Take(end)
return NewExpression(expr, pi.PositionAt(from+start), pi.PositionAt(from+end)), nil
}
package parser
import (
"github.com/a-h/parse"
)
var htmlCommentStart = parse.String("<!--")
var htmlCommentEnd = parse.String("--")
type htmlCommentParser struct {
}
var htmlComment = htmlCommentParser{}
func (p htmlCommentParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Comment start.
start := pi.Position()
var c HTMLComment
if _, ok, err = htmlCommentStart.Parse(pi); err != nil || !ok {
return
}
// Once we've got the comment start sequence, parse anything until the end
// sequence as the comment contents.
if c.Contents, ok, err = parse.StringUntil(htmlCommentEnd).Parse(pi); err != nil || !ok {
err = parse.Error("expected end comment literal '-->' not found", start)
return
}
// Cut the end element.
_, _, _ = htmlCommentEnd.Parse(pi)
// Cut the gt.
if _, ok, err = gt.Parse(pi); err != nil || !ok {
err = parse.Error("comment contains invalid sequence '--'", pi.Position())
return
}
return c, true, nil
}
package parser
import (
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
var ifExpression ifExpressionParser
var untilElseIfElseOrEnd = parse.Any(StripType(elseIfExpression), StripType(elseExpression), StripType(closeBraceWithOptionalPadding))
type ifExpressionParser struct{}
func (ifExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
var r IfExpression
start := pi.Index()
if !peekPrefix(pi, "if ") {
return r, false, nil
}
// Parse the Go if expresion.
if r.Expression, err = parseGo("if", pi, goexpression.If); err != nil {
return r, false, err
}
// Eat " {\n".
if _, ok, err = parse.All(openBraceWithOptionalPadding, parse.NewLine).Parse(pi); err != nil || !ok {
err = parse.Error("if: "+unterminatedMissingCurly, pi.PositionAt(start))
return
}
// Once we've had the start of an if block, we must conclude the block.
// Read the 'Then' nodes.
// If there's no match, there's a problem in the template nodes.
np := newTemplateNodeParser(untilElseIfElseOrEnd, "else expression or closing brace")
var thenNodes Nodes
if thenNodes, ok, err = np.Parse(pi); err != nil || !ok {
err = parse.Error("if: expected nodes, but none were found", pi.Position())
return
}
r.Then = thenNodes.Nodes
// Read the optional 'ElseIf' Nodes.
if r.ElseIfs, _, err = parse.ZeroOrMore(elseIfExpression).Parse(pi); err != nil {
return
}
// Read the optional 'Else' Nodes.
var elseNodes Nodes
if elseNodes, _, err = elseExpression.Parse(pi); err != nil {
return
}
r.Else = elseNodes.Nodes
// Read the required closing brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("if: "+unterminatedMissingEnd, pi.Position())
return
}
return r, true, nil
}
var elseIfExpression parse.Parser[ElseIfExpression] = elseIfExpressionParser{}
type elseIfExpressionParser struct{}
func (elseIfExpressionParser) Parse(pi *parse.Input) (r ElseIfExpression, ok bool, err error) {
start := pi.Index()
// Check the prefix first.
if _, ok, err = parse.All(parse.OptionalWhitespace, closeBrace, parse.OptionalWhitespace, parse.String("else if")).Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// Rewind to the start of the `if` statement.
pi.Seek(pi.Index() - 2)
// Parse the Go if expresion.
if r.Expression, err = parseGo("else if", pi, goexpression.If); err != nil {
return r, false, err
}
// Eat " {\n".
if _, ok, err = parse.All(openBraceWithOptionalPadding, parse.NewLine).Parse(pi); err != nil || !ok {
err = parse.Error("else if: "+unterminatedMissingCurly, pi.PositionAt(start))
return
}
// Once we've had the start of an if block, we must conclude the block.
// Read the 'Then' nodes.
// If there's no match, there's a problem in the template nodes.
np := newTemplateNodeParser(untilElseIfElseOrEnd, "else expression or closing brace")
var thenNodes Nodes
if thenNodes, ok, err = np.Parse(pi); err != nil || !ok {
err = parse.Error("if: expected nodes, but none were found", pi.Position())
return
}
r.Then = thenNodes.Nodes
return r, true, nil
}
var endElseParser = parse.All(
parse.Rune('}'),
parse.OptionalWhitespace,
parse.String("else"),
parse.OptionalWhitespace,
parse.Rune('{'),
parse.OptionalWhitespace)
var elseExpression parse.Parser[Nodes] = elseExpressionParser{}
type elseExpressionParser struct{}
func (elseExpressionParser) Parse(in *parse.Input) (r Nodes, ok bool, err error) {
start := in.Index()
// } else {
if _, ok, err = endElseParser.Parse(in); err != nil || !ok {
in.Seek(start)
return
}
// Else contents
if r, ok, err = newTemplateNodeParser(closeBraceWithOptionalPadding, "else expression closing brace").Parse(in); err != nil || !ok {
in.Seek(start)
return
}
return r, true, nil
}
package parser
import (
"github.com/a-h/parse"
)
// Package.
var pkg = parse.Func(func(pi *parse.Input) (pkg Package, ok bool, err error) {
start := pi.Position()
// Package prefix.
if _, ok, err = parse.String("package ").Parse(pi); err != nil || !ok {
return
}
// Once we have the prefix, it's an expression until the end of the line.
var exp string
if exp, ok, err = stringUntilNewLine.Parse(pi); err != nil || !ok {
err = parse.Error("package literal not terminated", pi.Position())
return
}
if len(exp) == 0 {
ok = false
err = parse.Error("package literal not terminated", start)
return
}
// Success!
pkg.Expression = NewExpression("package "+exp, start, pi.Position())
return pkg, true, nil
})
package parser
import (
"github.com/a-h/parse"
)
// ) {
var expressionFuncEnd = parse.All(parse.Rune(')'), openBraceWithOptionalPadding)
// Template
var template = parse.Func(func(pi *parse.Input) (r HTMLTemplate, ok bool, err error) {
start := pi.Position()
// templ FuncName(p Person, other Other) {
var te templateExpression
if te, ok, err = templateExpressionParser.Parse(pi); err != nil || !ok {
return
}
r.Expression = te.Expression
// Once we're in a template, we should expect some template whitespace, if/switch/for,
// or node string expressions etc.
var nodes Nodes
nodes, ok, err = newTemplateNodeParser(closeBraceWithOptionalPadding, "template closing brace").Parse(pi)
if err != nil {
return
}
if !ok {
err = parse.Error("templ: expected nodes in templ body, but found none", pi.Position())
return
}
r.Children = nodes.Nodes
// Eat any whitespace.
_, _, err = parse.OptionalWhitespace.Parse(pi)
if err != nil {
return
}
// Try for }
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("template: missing closing brace", pi.Position())
return
}
r.Range = NewRange(start, pi.Position())
return r, true, nil
})
package parser
import (
"fmt"
"github.com/a-h/parse"
)
var styleElement = rawElementParser{
name: "style",
}
var scriptElement = rawElementParser{
name: "script",
}
type rawElementParser struct {
name string
}
func (p rawElementParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
start := pi.Index()
// <
if _, ok, err = lt.Parse(pi); err != nil || !ok {
return
}
// Element name.
var e RawElement
if e.Name, ok, err = elementNameParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
if e.Name != p.name {
pi.Seek(start)
ok = false
return
}
if e.Attributes, ok, err = (attributesParser{}).Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// Optional whitespace.
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
pi.Seek(start)
return
}
// >
if _, ok, err = gt.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
// Once we've got an open tag, parse anything until the end tag as the tag contents.
// It's going to be rendered out raw.
end := parse.All(parse.String("</"), parse.String(p.name), parse.String(">"))
if e.Contents, ok, err = parse.StringUntil(end).Parse(pi); err != nil || !ok {
err = parse.Error(fmt.Sprintf("<%s>: expected end tag not present", e.Name), pi.Position())
return
}
// Cut the end element.
_, _, _ = end.Parse(pi)
return e, true, nil
}
package parser
import (
"github.com/a-h/parse"
)
var scriptTemplateParser = parse.Func(func(pi *parse.Input) (r ScriptTemplate, ok bool, err error) {
start := pi.Position()
// Parse the name.
var se scriptExpression
if se, ok, err = scriptExpressionParser.Parse(pi); err != nil || !ok {
pi.Seek(start.Index)
return
}
r.Name = se.Name
r.Parameters = se.Parameters
// Read code expression.
var e Expression
if e, ok, err = exp.Parse(pi); err != nil || !ok {
pi.Seek(start.Index)
return
}
r.Value = e.Value
// Try for }
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("script template: missing closing brace", pi.Position())
return
}
r.Range = NewRange(start, pi.Position())
return r, true, nil
})
// script Func() {
type scriptExpression struct {
Name Expression
Parameters Expression
}
var scriptExpressionNameParser = ExpressionOf(parse.StringFrom(
parse.Letter,
parse.StringFrom(parse.AtMost(1000, parse.Any(parse.Letter, parse.ZeroToNine))),
))
var scriptExpressionParser = parse.Func(func(pi *parse.Input) (r scriptExpression, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.String("script ").Parse(pi); err != nil || !ok {
return
}
// Once we have the prefix, we must have a name and parameters.
// Read the name of the function.
if r.Name, ok, err = scriptExpressionNameParser.Parse(pi); err != nil || !ok {
err = parse.Error("script expression: invalid name", pi.Position())
return
}
// Eat the open bracket.
if _, ok, err = openBracket.Parse(pi); err != nil || !ok {
err = parse.Error("script expression: parameters missing open bracket", pi.Position())
return
}
// Read the parameters.
// p Person, other Other, t thing.Thing)
if r.Parameters, ok, err = ExpressionOf(parse.StringUntil(closeBracket)).Parse(pi); err != nil || !ok {
err = parse.Error("script expression: parameters missing close bracket", pi.Position())
return
}
// Eat ") {".
if _, ok, err = expressionFuncEnd.Parse(pi); err != nil || !ok {
err = parse.Error("script expression: unterminated (missing ') {')", pi.Position())
return
}
// Expect a newline.
if _, ok, err = parse.NewLine.Parse(pi); err != nil || !ok {
err = parse.Error("script expression: missing terminating newline", pi.Position())
return
}
return r, true, nil
})
package parser
import (
"strings"
"unicode/utf8"
)
// NewSourceMap creates a new lookup to map templ source code to items in the
// parsed template.
func NewSourceMap() *SourceMap {
return &SourceMap{
SourceLinesToTarget: make(map[uint32]map[uint32]Position),
TargetLinesToSource: make(map[uint32]map[uint32]Position),
}
}
type SourceMap struct {
SourceLinesToTarget map[uint32]map[uint32]Position
TargetLinesToSource map[uint32]map[uint32]Position
}
// Add an item to the lookup.
func (sm *SourceMap) Add(src Expression, tgt Range) (updatedFrom Position) {
srcIndex := src.Range.From.Index
tgtIndex := tgt.From.Index
lines := strings.Split(src.Value, "\n")
for lineIndex, line := range lines {
srcLine := src.Range.From.Line + uint32(lineIndex)
tgtLine := tgt.From.Line + uint32(lineIndex)
var srcCol, tgtCol uint32
if lineIndex == 0 {
// First line can have an offset.
srcCol += src.Range.From.Col
tgtCol += tgt.From.Col
}
// Process the cols.
for _, r := range line {
if _, ok := sm.SourceLinesToTarget[srcLine]; !ok {
sm.SourceLinesToTarget[srcLine] = make(map[uint32]Position)
}
sm.SourceLinesToTarget[srcLine][srcCol] = NewPosition(tgtIndex, tgtLine, tgtCol)
if _, ok := sm.TargetLinesToSource[tgtLine]; !ok {
sm.TargetLinesToSource[tgtLine] = make(map[uint32]Position)
}
sm.TargetLinesToSource[tgtLine][tgtCol] = NewPosition(srcIndex, srcLine, srcCol)
// Ignore invalid runes.
rlen := utf8.RuneLen(r)
if rlen < 0 {
rlen = 1
}
srcCol += uint32(rlen)
tgtCol += uint32(rlen)
srcIndex += int64(rlen)
tgtIndex += int64(rlen)
}
// LSPs include the newline char as a col.
if _, ok := sm.SourceLinesToTarget[srcLine]; !ok {
sm.SourceLinesToTarget[srcLine] = make(map[uint32]Position)
}
sm.SourceLinesToTarget[srcLine][srcCol] = NewPosition(tgtIndex, tgtLine, tgtCol)
if _, ok := sm.TargetLinesToSource[tgtLine]; !ok {
sm.TargetLinesToSource[tgtLine] = make(map[uint32]Position)
}
sm.TargetLinesToSource[tgtLine][tgtCol] = NewPosition(srcIndex, srcLine, srcCol)
srcIndex++
tgtIndex++
}
return src.Range.From
}
// TargetPositionFromSource looks up the target position using the source position.
func (sm *SourceMap) TargetPositionFromSource(line, col uint32) (tgt Position, ok bool) {
lm, ok := sm.SourceLinesToTarget[line]
if !ok {
return
}
tgt, ok = lm[col]
return
}
// SourcePositionFromTarget looks the source position using the target position.
// If a source exists on the line but not the col, the function will search backwards.
func (sm *SourceMap) SourcePositionFromTarget(line, col uint32) (src Position, ok bool) {
lm, ok := sm.TargetLinesToSource[line]
if !ok {
return
}
for {
src, ok = lm[col]
if ok || col == 0 {
return
}
col--
}
}
package parser
import (
"github.com/a-h/parse"
)
var stringExpression = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.Or(parse.String("{ "), parse.String("{")).Parse(pi); err != nil || !ok {
return
}
// Once we have a prefix, we must have an expression that returns a string, with optional err.
var r StringExpression
if r.Expression, err = parseGoSliceArgs(pi); err != nil {
return r, false, err
}
// Clear any optional whitespace.
_, _, _ = parse.OptionalWhitespace.Parse(pi)
// }
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("string expression: missing close brace", pi.Position())
return
}
// Parse trailing whitespace.
ws, _, err := parse.Whitespace.Parse(pi)
if err != nil {
return r, false, err
}
r.TrailingSpace, err = NewTrailingSpace(ws)
if err != nil {
return r, false, err
}
return r, true, nil
})
package parser
import (
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
var switchExpression parse.Parser[Node] = switchExpressionParser{}
type switchExpressionParser struct{}
func (switchExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
var r SwitchExpression
start := pi.Index()
// Check the prefix first.
if !peekPrefix(pi, "switch ") {
pi.Seek(start)
return
}
// Parse the Go switch expresion.
if r.Expression, err = parseGo("switch", pi, goexpression.Switch); err != nil {
return r, false, err
}
// Eat " {\n".
if _, ok, err = parse.All(openBraceWithOptionalPadding, parse.NewLine).Parse(pi); err != nil || !ok {
err = parse.Error("switch: "+unterminatedMissingCurly, pi.PositionAt(start))
return
}
// Once we've had the start of a switch block, we must conclude the block.
// Read the optional 'case' nodes.
for {
var ce CaseExpression
ce, ok, err = caseExpressionParser.Parse(pi)
if err != nil {
return
}
if !ok {
break
}
r.Cases = append(r.Cases, ce)
}
// Read the required closing brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("switch: "+unterminatedMissingEnd, pi.Position())
return
}
return r, true, nil
}
var caseExpressionStartParser = parse.Func(func(pi *parse.Input) (r Expression, ok bool, err error) {
start := pi.Index()
// Optional whitespace.
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
return
}
// Strip leading whitespace and look for `case ` or `default`.
if !peekPrefix(pi, "case ", "default") {
pi.Seek(start)
return r, false, nil
}
// Parse the Go expresion.
if r, err = parseGo("case", pi, goexpression.Case); err != nil {
return r, false, err
}
// Eat terminating newline.
_, _, _ = parse.ZeroOrMore(parse.String(" ")).Parse(pi)
_, _, _ = parse.NewLine.Parse(pi)
return r, true, nil
})
var caseExpressionParser = parse.Func(func(pi *parse.Input) (r CaseExpression, ok bool, err error) {
if r.Expression, ok, err = caseExpressionStartParser.Parse(pi); err != nil || !ok {
return
}
// Read until the next case statement, default, or end of the block.
pr := newTemplateNodeParser(parse.Any(StripType(closeBraceWithOptionalPadding), StripType(caseExpressionStartParser)), "closing brace or case expression")
var nodes Nodes
if nodes, ok, err = pr.Parse(pi); err != nil || !ok {
err = parse.Error("case: expected nodes, but none were found", pi.Position())
return
}
r.Children = nodes.Nodes
// Optional whitespace.
if _, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
return r, true, nil
})
package parser
import (
"errors"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/a-h/parse"
)
func Parse(fileName string) (TemplateFile, error) {
fc, err := os.ReadFile(fileName)
if err != nil {
return TemplateFile{}, err
}
return ParseString(string(fc))
}
func getDefaultPackageName(fileName string) (pkg string) {
parent := filepath.Base(filepath.Dir(fileName))
if !isGoIdentifier(parent) {
return "main"
}
return parent
}
func isGoIdentifier(s string) bool {
if len(s) == 0 {
return false
}
for i, r := range s {
if unicode.IsLetter(r) || r == '_' {
continue
}
if i > 0 && unicode.IsDigit(r) {
continue
}
return false
}
return true
}
func ParseString(template string) (TemplateFile, error) {
tf, ok, err := NewTemplateFileParser("main").Parse(parse.NewInput(template))
if err != nil {
return tf, err
}
if !ok {
err = ErrTemplateNotFound
}
return tf, err
}
// NewTemplateFileParser creates a new TemplateFileParser.
func NewTemplateFileParser(pkg string) TemplateFileParser {
return TemplateFileParser{
DefaultPackage: pkg,
}
}
var ErrLegacyFileFormat = errors.New("legacy file format - run templ migrate")
var ErrTemplateNotFound = errors.New("template not found")
type TemplateFileParser struct {
DefaultPackage string
}
var legacyPackageParser = parse.String("{% package")
func (p TemplateFileParser) Parse(pi *parse.Input) (tf TemplateFile, ok bool, err error) {
// If we're parsing a legacy file, complain that migration needs to happen.
_, ok, err = legacyPackageParser.Parse(pi)
if err != nil {
return
}
if ok {
return tf, false, ErrLegacyFileFormat
}
// Read until the package.
for {
// Package.
// package name
from := pi.Position()
tf.Package, ok, err = pkg.Parse(pi)
if err != nil {
return
}
if ok {
break
}
var line string
line, ok, err = stringUntilNewLine.Parse(pi)
if err != nil {
return
}
if !ok {
break
}
var newLine string
newLine, _, _ = parse.NewLine.Parse(pi)
tf.Header = append(tf.Header, TemplateFileGoExpression{Expression: NewExpression(line+newLine, from, pi.Position()), BeforePackage: true})
}
// Strip any whitespace between the template declaration and the first template.
_, _, _ = parse.OptionalWhitespace.Parse(pi)
outer:
for {
// Optional templates, CSS, and script templates.
// templ Name(p Parameter)
var tn HTMLTemplate
tn, ok, err = template.Parse(pi)
if err != nil {
return tf, false, err
}
if ok {
tf.Nodes = append(tf.Nodes, tn)
_, _, _ = parse.OptionalWhitespace.Parse(pi)
continue
}
// css Name()
var cn CSSTemplate
cn, ok, err = cssParser.Parse(pi)
if err != nil {
return tf, false, err
}
if ok {
tf.Nodes = append(tf.Nodes, cn)
_, _, _ = parse.OptionalWhitespace.Parse(pi)
continue
}
// script Name()
var sn ScriptTemplate
sn, ok, err = scriptTemplateParser.Parse(pi)
if err != nil {
return tf, false, err
}
if ok {
tf.Nodes = append(tf.Nodes, sn)
_, _, _ = parse.OptionalWhitespace.Parse(pi)
continue
}
// Anything that isn't template content is Go code.
code := new(strings.Builder)
from := pi.Position()
inner:
for {
// Check to see if this line isn't Go code.
last := pi.Index()
var l string
if l, ok, err = stringUntilNewLineOrEOF.Parse(pi); err != nil {
return
}
hasTemplatePrefix := strings.HasPrefix(l, "templ ") || strings.HasPrefix(l, "css ") || strings.HasPrefix(l, "script ")
if hasTemplatePrefix && strings.Contains(l, "(") {
// Unread the line.
pi.Seek(last)
// Take the code so far.
if code.Len() > 0 {
expr := NewExpression(strings.TrimSpace(code.String()), from, pi.Position())
tf.Nodes = append(tf.Nodes, TemplateFileGoExpression{Expression: expr})
}
// Carry on parsing.
break inner
}
code.WriteString(l)
// Eat the newline or EOF that we read until.
var newLine string
if newLine, ok, err = parse.NewLine.Parse(pi); err != nil {
return
}
code.WriteString(newLine)
if _, isEOF, _ := parse.EOF[string]().Parse(pi); isEOF {
if code.Len() > 0 {
expr := NewExpression(strings.TrimSpace(code.String()), from, pi.Position())
tf.Nodes = append(tf.Nodes, TemplateFileGoExpression{Expression: expr})
}
// Stop parsing.
break outer
}
}
}
return tf, true, nil
}
package parser
import (
"fmt"
"github.com/a-h/parse"
)
// TemplateExpression.
// TemplateExpression.
// templ Func(p Parameter) {
// templ (data Data) Func(p Parameter) {
// templ (data []string) Func(p Parameter) {
type templateExpression struct {
Expression Expression
}
var templateExpressionParser = parse.Func(func(pi *parse.Input) (r templateExpression, ok bool, err error) {
start := pi.Index()
if !peekPrefix(pi, "templ ") {
return r, false, nil
}
// Once we have the prefix, everything to the brace is Go.
// e.g.
// templ (x []string) Test() {
// becomes:
// func (x []string) Test() templ.Component {
if _, r.Expression, err = parseTemplFuncDecl(pi); err != nil {
return r, false, err
}
// Eat " {\n".
if _, ok, err = parse.All(openBraceWithOptionalPadding, parse.StringFrom(parse.Optional(parse.NewLine))).Parse(pi); err != nil || !ok {
err = parse.Error("templ: malformed templ expression, expected `templ functionName() {`", pi.PositionAt(start))
return
}
return r, true, nil
})
const (
unterminatedMissingCurly = `unterminated (missing closing '{\n') - https://templ.guide/syntax-and-usage/statements#incomplete-statements`
unterminatedMissingEnd = `missing end (expected '}') - https://templ.guide/syntax-and-usage/statements#incomplete-statements`
)
// Template node (element, call, if, switch, for, whitespace etc.)
func newTemplateNodeParser[TUntil any](until parse.Parser[TUntil], untilName string) templateNodeParser[TUntil] {
return templateNodeParser[TUntil]{
until: until,
untilName: untilName,
}
}
type templateNodeParser[TUntil any] struct {
until parse.Parser[TUntil]
untilName string
}
var rawElements = parse.Any(styleElement, scriptElement)
var templateNodeSkipParsers = []parse.Parser[Node]{
voidElementCloser, // </br>, </img> etc. - should be ignored.
}
var templateNodeParsers = []parse.Parser[Node]{
docType, // <!DOCTYPE html>
htmlComment, // <!--
goComment, // // or /*
rawElements, // <text>, <>, or <style> element (special behaviour - contents are not parsed).
element, // <a>, <br/> etc.
ifExpression, // if {}
forExpression, // for {}
switchExpression, // switch {}
callTemplateExpression, // {! TemplateName(a, b, c) }
templElementExpression, // @TemplateName(a, b, c) { <div>Children</div> }
childrenExpression, // { children... }
goCode, // {{ myval := x.myval }}
stringExpression, // { "abc" }
whitespaceExpression, // { " " }
textParser, // anything & everything accepted...
}
func (p templateNodeParser[T]) Parse(pi *parse.Input) (op Nodes, ok bool, err error) {
outer:
for {
// Check if we've reached the end.
if p.until != nil {
start := pi.Index()
_, ok, err = p.until.Parse(pi)
if err != nil {
return
}
if ok {
pi.Seek(start)
return op, true, nil
}
}
// Skip any nodes that we don't care about.
for _, p := range templateNodeSkipParsers {
_, matched, err := p.Parse(pi)
if err != nil {
return Nodes{}, false, err
}
if matched {
continue outer
}
}
// Attempt to parse a node.
// Loop through the parsers and try to parse a node.
var matched bool
for _, p := range templateNodeParsers {
var node Node
node, matched, err = p.Parse(pi)
if err != nil {
return Nodes{}, false, err
}
if matched {
op.Nodes = append(op.Nodes, node)
break
}
}
if matched {
continue
}
if p.until == nil {
// In this case, we're just reading as many nodes as we can until we can't read any more.
// If we've reached here, we couldn't find a node.
// The element parser checks the final node returned to make sure it's the expected close tag.
break
}
err = UntilNotFoundError{
ParseError: parse.Error(fmt.Sprintf("%v not found", p.untilName), pi.Position()),
}
return
}
return op, true, nil
}
type UntilNotFoundError struct {
parse.ParseError
}
package parser
import (
"github.com/a-h/parse"
"github.com/a-h/templ/parser/v2/goexpression"
)
type templElementExpressionParser struct{}
func (p templElementExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.Rune('@').Parse(pi); err != nil || !ok {
return
}
var r TemplElementExpression
// Parse the Go expression.
if r.Expression, err = parseGo("templ element", pi, goexpression.TemplExpression); err != nil {
return r, false, err
}
// Once we've got a start expression, check to see if there's an open brace for children. {\n.
var hasOpenBrace bool
_, hasOpenBrace, err = openBraceWithOptionalPadding.Parse(pi)
if err != nil {
return
}
if !hasOpenBrace {
return r, true, nil
}
// Once we've had the start of an element's children, we must conclude the block.
// Node contents.
np := newTemplateNodeParser(closeBraceWithOptionalPadding, "templ element closing brace")
var nodes Nodes
if nodes, ok, err = np.Parse(pi); err != nil || !ok {
err = parse.Error("@"+r.Expression.Value+": expected nodes, but none were found", pi.Position())
return
}
r.Children = nodes.Nodes
// Read the required closing brace.
if _, ok, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !ok {
err = parse.Error("@"+r.Expression.Value+": missing end (expected '}')", pi.Position())
return
}
return r, true, nil
}
var templElementExpression templElementExpressionParser
package parser
import (
"unicode"
"github.com/a-h/parse"
)
var tagTemplOrNewLine = parse.Any(parse.Rune('<'), parse.Rune('{'), parse.Rune('}'), parse.String("\r\n"), parse.Rune('\n'))
var textParser = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
from := pi.Position()
// Read until a tag or templ expression opens.
var t Text
if t.Value, ok, err = parse.StringUntil(tagTemplOrNewLine).Parse(pi); err != nil || !ok {
return
}
if isWhitespace(t.Value) {
return t, false, nil
}
if _, ok = pi.Peek(1); !ok {
err = parse.Error("textParser: unterminated text, expected tag open, templ expression open, or newline", from)
return
}
t.Range = NewRange(from, pi.Position())
// Elide any void element closing tags.
if _, _, err = voidElementCloser.Parse(pi); err != nil {
return
}
// Parse trailing whitespace.
ws, _, err := parse.Whitespace.Parse(pi)
if err != nil {
return t, false, err
}
t.TrailingSpace, err = NewTrailingSpace(ws)
if err != nil {
return t, false, err
}
return t, true, nil
})
func isWhitespace(s string) bool {
for _, r := range s {
if !unicode.IsSpace(r) {
return false
}
}
return true
}
package parser
import (
"bytes"
"errors"
"fmt"
"go/format"
"io"
"strings"
"unicode"
"github.com/a-h/parse"
)
// package parser
//
// import "strings"
// import strs "strings"
//
// css AddressLineStyle() {
// background-color: #ff0000;
// color: #ffffff;
// }
//
// templ RenderAddress(addr Address) {
// <div style={ AddressLineStyle() }>{ addr.Address1 }</div>
// <div>{ addr.Address2 }</div>
// <div>{ addr.Address3 }</div>
// <div>{ addr.Address4 }</div>
// }
//
// templ Render(p Person) {
// <div>
// <div>{ p.Name() }</div>
// <a href={ p.URL }>{ strings.ToUpper(p.Name()) }</a>
// <div>
// if p.Type == "test" {
// <span>{ "Test user" }</span>
// } else {
// <span>{ "Not test user" }</span>
// }
// for _, v := range p.Addresses {
// {! call RenderAddress(v) }
// }
// </div>
// </div>
// }
// Source mapping to map from the source code of the template to the
// in-memory representation.
type Position struct {
Index int64
Line uint32
Col uint32
}
func (p Position) String() string {
return fmt.Sprintf("line %d, col %d (index %d)", p.Line, p.Col, p.Index)
}
// NewPosition initialises a position.
func NewPosition(index int64, line, col uint32) Position {
return Position{
Index: index,
Line: line,
Col: col,
}
}
// NewExpression creates a Go expression.
func NewExpression(value string, from, to parse.Position) Expression {
return Expression{
Value: value,
Range: Range{
From: Position{
Index: int64(from.Index),
Line: uint32(from.Line),
Col: uint32(from.Col),
},
To: Position{
Index: int64(to.Index),
Line: uint32(to.Line),
Col: uint32(to.Col),
},
},
}
}
// NewRange creates a Range expression.
func NewRange(from, to parse.Position) Range {
return Range{
From: Position{
Index: int64(from.Index),
Line: uint32(from.Line),
Col: uint32(from.Col),
},
To: Position{
Index: int64(to.Index),
Line: uint32(to.Line),
Col: uint32(to.Col),
},
}
}
// Range of text within a file.
type Range struct {
From Position
To Position
}
// Expression containing Go code.
type Expression struct {
Value string
Range Range
}
type TemplateFile struct {
// Header contains comments or whitespace at the top of the file.
Header []TemplateFileGoExpression
// Package expression.
Package Package
// Filepath is where the file was loaded from. It is not always available.
Filepath string
// Nodes in the file.
Nodes []TemplateFileNode
}
func (tf TemplateFile) Write(w io.Writer) error {
for _, n := range tf.Header {
if err := n.Write(w, 0); err != nil {
return err
}
}
var indent int
if err := tf.Package.Write(w, indent); err != nil {
return err
}
if _, err := io.WriteString(w, "\n\n"); err != nil {
return err
}
for i := 0; i < len(tf.Nodes); i++ {
if err := tf.Nodes[i].Write(w, indent); err != nil {
return err
}
if _, err := io.WriteString(w, getNodeWhitespace(tf.Nodes, i)); err != nil {
return err
}
}
return nil
}
func getNodeWhitespace(nodes []TemplateFileNode, i int) string {
if i == len(nodes)-1 {
return "\n"
}
if _, nextIsTemplate := nodes[i+1].(HTMLTemplate); nextIsTemplate {
if e, isGo := nodes[i].(TemplateFileGoExpression); isGo && endsWithComment(e.Expression.Value) {
return "\n"
}
}
return "\n\n"
}
func endsWithComment(s string) bool {
lineSlice := strings.Split(s, "\n")
return strings.HasPrefix(lineSlice[len(lineSlice)-1], "//")
}
// TemplateFileNode can be a Template, CSS, Script or Go.
type TemplateFileNode interface {
IsTemplateFileNode() bool
Write(w io.Writer, indent int) error
}
// TemplateFileGoExpression within a TemplateFile
type TemplateFileGoExpression struct {
Expression Expression
BeforePackage bool
}
func (exp TemplateFileGoExpression) IsTemplateFileNode() bool { return true }
func (exp TemplateFileGoExpression) Write(w io.Writer, indent int) error {
in := exp.Expression.Value
if exp.BeforePackage {
in += "\\\\formatstring\npackage p\n\\\\formatstring"
}
data, err := format.Source([]byte(in))
if err != nil {
return writeIndent(w, indent, exp.Expression.Value)
}
if exp.BeforePackage {
data = bytes.TrimSuffix(data, []byte("\\\\formatstring\npackage p\n\\\\formatstring"))
}
_, err = w.Write(data)
return err
}
func writeIndent(w io.Writer, level int, s ...string) (err error) {
indent := strings.Repeat("\t", level)
if _, err = io.WriteString(w, indent); err != nil {
return err
}
for _, ss := range s {
_, err = io.WriteString(w, ss)
if err != nil {
return
}
}
return
}
type Package struct {
Expression Expression
}
func (p Package) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, p.Expression.Value)
}
// Whitespace.
type Whitespace struct {
Value string
}
func (ws Whitespace) IsNode() bool { return true }
func (ws Whitespace) Write(w io.Writer, indent int) error {
if ws.Value == "" || !strings.Contains(ws.Value, "\n") {
return nil
}
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
// - All spaces and tabs immediately before and after a line break are ignored.
// - All tab characters are handled as space characters.
// - Line breaks are converted to spaces.
// Any space immediately following another space (even across two separate inline elements) is ignored.
// Sequences of spaces at the beginning and end of an element are removed.
// Notes: Since we only have whitespace in this node, we can strip anything that isn't a line break.
// Since any space following another space is ignored, we can collapse to a single rule.
// So, the rule is... if there's a newline, it becomes a single space, or it's stripped.
// We have to remove the start and end space elsewhere.
_, err := io.WriteString(w, " ")
return err
}
// CSS definition.
//
// css Name() {
// color: #ffffff;
// background-color: { constants.BackgroundColor };
// background-image: url('./somewhere.png');
// }
type CSSTemplate struct {
Range Range
Name string
Expression Expression
Properties []CSSProperty
}
func (css CSSTemplate) IsTemplateFileNode() bool { return true }
func (css CSSTemplate) Write(w io.Writer, indent int) error {
source := formatFunctionArguments(css.Expression.Value)
if err := writeIndent(w, indent, "css ", string(source), " {\n"); err != nil {
return err
}
for _, p := range css.Properties {
if err := p.Write(w, indent+1); err != nil {
return err
}
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// CSSProperty is a CSS property and value pair.
type CSSProperty interface {
IsCSSProperty() bool
Write(w io.Writer, indent int) error
}
// color: #ffffff;
type ConstantCSSProperty struct {
Name string
Value string
}
func (c ConstantCSSProperty) IsCSSProperty() bool { return true }
func (c ConstantCSSProperty) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, c.String(false)); err != nil {
return err
}
return nil
}
func (c ConstantCSSProperty) String(minified bool) string {
sb := new(strings.Builder)
sb.WriteString(c.Name)
if minified {
sb.WriteString(":")
} else {
sb.WriteString(": ")
}
sb.WriteString(c.Value)
sb.WriteString(";")
if !minified {
sb.WriteString("\n")
}
return sb.String()
}
// background-color: { constants.BackgroundColor };
type ExpressionCSSProperty struct {
Name string
Value StringExpression
}
func (c ExpressionCSSProperty) IsCSSProperty() bool { return true }
func (c ExpressionCSSProperty) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, c.Name, ": "); err != nil {
return err
}
if err := c.Value.Write(w, 0); err != nil {
return err
}
if _, err := w.Write([]byte(";\n")); err != nil {
return err
}
return nil
}
// <!DOCTYPE html>
type DocType struct {
Value string
}
func (dt DocType) IsNode() bool { return true }
func (dt DocType) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, "<!DOCTYPE ", dt.Value, ">")
}
// HTMLTemplate definition.
//
// templ Name(p Parameter) {
// if ... {
// <Element></Element>
// }
// }
type HTMLTemplate struct {
Range Range
Expression Expression
Children []Node
}
func (t HTMLTemplate) IsTemplateFileNode() bool { return true }
func (t HTMLTemplate) Write(w io.Writer, indent int) error {
source := formatFunctionArguments(t.Expression.Value)
if err := writeIndent(w, indent, "templ ", string(source), " {\n"); err != nil {
return err
}
if err := writeNodesIndented(w, indent+1, t.Children); err != nil {
return err
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// TrailingSpace defines the whitespace that may trail behind the close of an element, a
// text node, or string expression.
type TrailingSpace string
const (
SpaceNone TrailingSpace = ""
SpaceHorizontal TrailingSpace = " "
SpaceVertical TrailingSpace = "\n"
)
var ErrNonSpaceCharacter = errors.New("non space character found")
func NewTrailingSpace(s string) (ts TrailingSpace, err error) {
var hasHorizontalSpace bool
for _, r := range s {
if r == '\n' {
return SpaceVertical, nil
}
if unicode.IsSpace(r) {
hasHorizontalSpace = true
continue
}
return ts, ErrNonSpaceCharacter
}
if hasHorizontalSpace {
return SpaceHorizontal, nil
}
return SpaceNone, nil
}
type Nodes struct {
Nodes []Node
}
// A Node appears within a template, e.g. an StringExpression, Element, IfExpression etc.
type Node interface {
IsNode() bool
// Write out the string.
Write(w io.Writer, indent int) error
}
type CompositeNode interface {
Node
ChildNodes() []Node
}
type WhitespaceTrailer interface {
Trailing() TrailingSpace
}
var (
_ WhitespaceTrailer = Element{}
_ WhitespaceTrailer = Text{}
_ WhitespaceTrailer = StringExpression{}
)
// Text node within the document.
type Text struct {
// Range of the text within the templ file.
Range Range
// Value is the raw HTML encoded value.
Value string
// TrailingSpace lists what happens after the text.
TrailingSpace TrailingSpace
}
func (t Text) Trailing() TrailingSpace {
return t.TrailingSpace
}
func (t Text) IsNode() bool { return true }
func (t Text) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, t.Value)
}
// <a .../> or <div ...>...</div>
type Element struct {
Name string
Attributes []Attribute
IndentAttrs bool
Children []Node
IndentChildren bool
TrailingSpace TrailingSpace
NameRange Range
}
func (e Element) Trailing() TrailingSpace {
return e.TrailingSpace
}
var voidElements = map[string]struct{}{
"area": {}, "base": {}, "br": {}, "col": {}, "command": {}, "embed": {}, "hr": {}, "img": {}, "input": {}, "keygen": {}, "link": {}, "meta": {}, "param": {}, "source": {}, "track": {}, "wbr": {},
}
// https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-element
func (e Element) IsVoidElement() bool {
_, ok := voidElements[e.Name]
return ok
}
func (e Element) hasNonWhitespaceChildren() bool {
for _, c := range e.Children {
if _, isWhitespace := c.(Whitespace); !isWhitespace {
return true
}
}
return false
}
var blockElements = map[string]struct{}{
"address": {}, "article": {}, "aside": {}, "body": {}, "blockquote": {}, "canvas": {}, "dd": {}, "div": {}, "dl": {}, "dt": {}, "fieldset": {}, "figcaption": {}, "figure": {}, "footer": {}, "form": {}, "h1": {}, "h2": {}, "h3": {}, "h4": {}, "h5": {}, "h6": {}, "head": {}, "header": {}, "hr": {}, "html": {}, "li": {}, "main": {}, "meta": {}, "nav": {}, "noscript": {}, "ol": {}, "p": {}, "pre": {}, "script": {}, "section": {}, "table": {}, "template": {}, "tfoot": {}, "turbo-stream": {}, "ul": {}, "video": {},
// Not strictly block but for the purposes of layout, they are.
"title": {}, "style": {}, "link": {}, "td": {}, "th": {}, "tr": {}, "br": {},
}
func (e Element) IsBlockElement() bool {
_, ok := blockElements[e.Name]
return ok
}
// Validate that no invalid expressions have been used.
func (e Element) Validate() (msgs []string, ok bool) {
// Validate that style attributes are constant.
for _, attr := range e.Attributes {
if exprAttr, isExprAttr := attr.(ExpressionAttribute); isExprAttr {
if strings.EqualFold(exprAttr.Name, "style") {
msgs = append(msgs, "invalid style attribute: style attributes cannot be a templ expression")
}
}
}
// Validate that script and style tags don't contain expressions.
if strings.EqualFold(e.Name, "script") || strings.EqualFold(e.Name, "style") {
if containsNonTextNodes(e.Children) {
msgs = append(msgs, "invalid node contents: script and style attributes must only contain text")
}
}
return msgs, len(msgs) == 0
}
func containsNonTextNodes(nodes []Node) bool {
for i := 0; i < len(nodes); i++ {
n := nodes[i]
switch n.(type) {
case Text:
continue
case Whitespace:
continue
default:
return true
}
}
return false
}
func (e Element) ChildNodes() []Node {
return e.Children
}
func (e Element) IsNode() bool { return true }
func (e Element) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, "<", e.Name); err != nil {
return err
}
for i := 0; i < len(e.Attributes); i++ {
a := e.Attributes[i]
// Only the conditional attributes get indented.
var attrIndent int
if e.IndentAttrs {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
attrIndent = indent + 1
} else {
if _, err := w.Write([]byte(" ")); err != nil {
return err
}
}
if err := a.Write(w, attrIndent); err != nil {
return err
}
}
var closeAngleBracketIndent int
if e.IndentAttrs {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
closeAngleBracketIndent = indent
}
if e.hasNonWhitespaceChildren() {
if e.IndentChildren {
if err := writeIndent(w, closeAngleBracketIndent, ">\n"); err != nil {
return err
}
if err := writeNodesIndented(w, indent+1, e.Children); err != nil {
return err
}
if err := writeIndent(w, indent, "</", e.Name, ">"); err != nil {
return err
}
return nil
}
if err := writeIndent(w, closeAngleBracketIndent, ">"); err != nil {
return err
}
if err := writeNodesWithoutIndentation(w, e.Children); err != nil {
return err
}
if _, err := w.Write([]byte("</" + e.Name + ">")); err != nil {
return err
}
return nil
}
if e.IsVoidElement() {
if err := writeIndent(w, closeAngleBracketIndent, "/>"); err != nil {
return err
}
return nil
}
if err := writeIndent(w, closeAngleBracketIndent, "></", e.Name, ">"); err != nil {
return err
}
return nil
}
func writeNodesWithoutIndentation(w io.Writer, nodes []Node) error {
return writeNodes(w, 0, nodes, false)
}
func writeNodesIndented(w io.Writer, level int, nodes []Node) error {
return writeNodes(w, level, nodes, true)
}
func writeNodes(w io.Writer, level int, nodes []Node, indent bool) error {
startLevel := level
for i := 0; i < len(nodes); i++ {
_, isWhitespace := nodes[i].(Whitespace)
// Skip whitespace nodes.
if isWhitespace {
continue
}
if err := nodes[i].Write(w, level); err != nil {
return err
}
// Apply trailing whitespace if present.
trailing := SpaceVertical
if wst, isWhitespaceTrailer := nodes[i].(WhitespaceTrailer); isWhitespaceTrailer {
trailing = wst.Trailing()
}
// Put a newline after the last node in indentation mode.
if indent && ((nextNodeIsBlock(nodes, i) || i == len(nodes)-1) || shouldAlwaysBreakAfter(nodes[i])) {
trailing = SpaceVertical
}
switch trailing {
case SpaceNone:
level = 0
case SpaceHorizontal:
level = 0
case SpaceVertical:
level = startLevel
}
if _, err := w.Write([]byte(trailing)); err != nil {
return err
}
}
return nil
}
func shouldAlwaysBreakAfter(node Node) bool {
if el, isElement := node.(Element); isElement {
return strings.EqualFold(el.Name, "br") || strings.EqualFold(el.Name, "hr")
}
return false
}
func nextNodeIsBlock(nodes []Node, i int) bool {
if len(nodes)-1 < i+1 {
return false
}
return isBlockNode(nodes[i+1])
}
func isBlockNode(node Node) bool {
switch n := node.(type) {
case IfExpression:
return true
case SwitchExpression:
return true
case ForExpression:
return true
case Element:
return n.IsBlockElement() || n.IndentChildren
}
return false
}
type RawElement struct {
Name string
Attributes []Attribute
Contents string
}
func (e RawElement) IsNode() bool { return true }
func (e RawElement) Write(w io.Writer, indent int) error {
// Start.
if err := writeIndent(w, indent, "<", e.Name); err != nil {
return err
}
for i := 0; i < len(e.Attributes); i++ {
if _, err := w.Write([]byte(" ")); err != nil {
return err
}
a := e.Attributes[i]
// Don't indent the attributes, only the conditional attributes get indented.
if err := a.Write(w, 0); err != nil {
return err
}
}
if _, err := w.Write([]byte(">")); err != nil {
return err
}
// Contents.
if _, err := w.Write([]byte(e.Contents)); err != nil {
return err
}
// Close.
if _, err := w.Write([]byte("</" + e.Name + ">")); err != nil {
return err
}
return nil
}
type Attribute interface {
// Write out the string.
Write(w io.Writer, indent int) error
}
// <hr noshade/>
type BoolConstantAttribute struct {
Name string
NameRange Range
}
func (bca BoolConstantAttribute) String() string {
return bca.Name
}
func (bca BoolConstantAttribute) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, bca.String())
}
// href=""
type ConstantAttribute struct {
Name string
Value string
SingleQuote bool
NameRange Range
}
func (ca ConstantAttribute) String() string {
quote := `"`
if ca.SingleQuote {
quote = `'`
}
return ca.Name + `=` + quote + ca.Value + quote
}
func (ca ConstantAttribute) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, ca.String())
}
// noshade={ templ.Bool(...) }
type BoolExpressionAttribute struct {
Name string
Expression Expression
NameRange Range
}
func (bea BoolExpressionAttribute) String() string {
return bea.Name + `?={ ` + bea.Expression.Value + ` }`
}
func (bea BoolExpressionAttribute) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, bea.String())
}
// href={ ... }
type ExpressionAttribute struct {
Name string
Expression Expression
NameRange Range
}
func (ea ExpressionAttribute) String() string {
sb := new(strings.Builder)
_ = ea.Write(sb, 0)
return sb.String()
}
func (ea ExpressionAttribute) formatExpression() (exp []string) {
trimmed := strings.TrimSpace(ea.Expression.Value)
if !strings.Contains(trimmed, "\n") {
formatted, err := format.Source([]byte(trimmed))
if err != nil {
return []string{trimmed}
}
return []string{string(formatted)}
}
buf := bytes.NewBufferString("[]any{\n")
buf.WriteString(trimmed)
buf.WriteString("\n}")
formatted, err := format.Source(buf.Bytes())
if err != nil {
return []string{trimmed}
}
// Trim prefix and suffix.
lines := strings.Split(string(formatted), "\n")
if len(lines) < 3 {
return []string{trimmed}
}
// Return.
return lines[1 : len(lines)-1]
}
func (ea ExpressionAttribute) Write(w io.Writer, indent int) (err error) {
lines := ea.formatExpression()
if len(lines) == 1 {
return writeIndent(w, indent, ea.Name, `={ `, lines[0], ` }`)
}
if err = writeIndent(w, indent, ea.Name, "={\n"); err != nil {
return err
}
for _, line := range lines {
if err = writeIndent(w, indent, line, "\n"); err != nil {
return err
}
}
return writeIndent(w, indent, "}")
}
// <a { spread... } />
type SpreadAttributes struct {
Expression Expression
}
func (sa SpreadAttributes) String() string {
return `{ ` + sa.Expression.Value + `... }`
}
func (sa SpreadAttributes) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, sa.String())
}
// <a href="test" \
// if active {
// class="isActive"
// }
type ConditionalAttribute struct {
Expression Expression
Then []Attribute
Else []Attribute
}
func (ca ConditionalAttribute) String() string {
sb := new(strings.Builder)
_ = ca.Write(sb, 0)
return sb.String()
}
func (ca ConditionalAttribute) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, "if "); err != nil {
return err
}
if _, err := w.Write([]byte(ca.Expression.Value)); err != nil {
return err
}
if _, err := w.Write([]byte(" {\n")); err != nil {
return err
}
{
indent++
for _, attr := range ca.Then {
if err := attr.Write(w, indent); err != nil {
return err
}
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
}
indent--
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
if len(ca.Else) == 0 {
return nil
}
// Write the else blocks.
if _, err := w.Write([]byte(" else {\n")); err != nil {
return err
}
{
indent++
for _, attr := range ca.Else {
if err := attr.Write(w, indent); err != nil {
return err
}
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
}
indent--
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// GoComment.
type GoComment struct {
Contents string
Multiline bool
}
func (c GoComment) IsNode() bool { return true }
func (c GoComment) Write(w io.Writer, indent int) error {
if c.Multiline {
return writeIndent(w, indent, "/*", c.Contents, "*/")
}
return writeIndent(w, indent, "//", c.Contents)
}
// HTMLComment.
type HTMLComment struct {
Contents string
}
func (c HTMLComment) IsNode() bool { return true }
func (c HTMLComment) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, "<!--", c.Contents, "-->")
}
// Nodes.
// CallTemplateExpression can be used to create and render a template using data.
// {! Other(p.First, p.Last) }
// or it can be used to render a template parameter.
// {! v }
type CallTemplateExpression struct {
// Expression returns a template to execute.
Expression Expression
}
func (cte CallTemplateExpression) IsNode() bool { return true }
func (cte CallTemplateExpression) Write(w io.Writer, indent int) error {
// Rewrite to new call syntax
return writeIndent(w, indent, `@`, cte.Expression.Value)
}
// TemplElementExpression can be used to create and render a template using data.
// @Other(p.First, p.Last)
// or it can be used to render a template parameter.
// @v
type TemplElementExpression struct {
// Expression returns a template to execute.
Expression Expression
// Children returns the elements in a block element.
Children []Node
}
func (tee TemplElementExpression) ChildNodes() []Node {
return tee.Children
}
func (tee TemplElementExpression) IsNode() bool { return true }
func (tee TemplElementExpression) Write(w io.Writer, indent int) error {
source, err := format.Source([]byte(tee.Expression.Value))
if err != nil {
source = []byte(tee.Expression.Value)
}
// Indent all lines and re-format, we can then use this to only re-indent lines that gofmt would modify.
reformattedSource, err := format.Source(bytes.ReplaceAll(source, []byte("\n"), []byte("\n\t")))
if err != nil {
reformattedSource = source
}
sourceLines := bytes.Split(source, []byte("\n"))
reformattedSourceLines := bytes.Split(reformattedSource, []byte("\n"))
for i := range sourceLines {
if i == 0 {
if err := writeIndent(w, indent, "@"+string(sourceLines[i])); err != nil {
return err
}
continue
}
if _, err := io.WriteString(w, "\n"); err != nil {
return err
}
if string(sourceLines[i]) != string(reformattedSourceLines[i]) {
if _, err := io.WriteString(w, string(sourceLines[i])); err != nil {
return err
}
continue
}
if err := writeIndent(w, indent, string(sourceLines[i])); err != nil {
return err
}
}
if len(tee.Children) == 0 {
return nil
}
if _, err = io.WriteString(w, " {\n"); err != nil {
return err
}
if err := writeNodesIndented(w, indent+1, tee.Children); err != nil {
return err
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// ChildrenExpression can be used to rended the children of a templ element.
// { children ... }
type ChildrenExpression struct{}
func (ChildrenExpression) IsNode() bool { return true }
func (ChildrenExpression) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, "{ children... }"); err != nil {
return err
}
return nil
}
// if p.Type == "test" && p.thing {
// }
type IfExpression struct {
Expression Expression
Then []Node
ElseIfs []ElseIfExpression
Else []Node
}
type ElseIfExpression struct {
Expression Expression
Then []Node
}
func (n IfExpression) ChildNodes() []Node {
var nodes []Node
nodes = append(nodes, n.Then...)
nodes = append(nodes, n.Else...)
for _, elseIf := range n.ElseIfs {
nodes = append(nodes, elseIf.Then...)
}
return nodes
}
func (n IfExpression) IsNode() bool { return true }
func (n IfExpression) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, "if ", n.Expression.Value, " {\n"); err != nil {
return err
}
indent++
if err := writeNodesIndented(w, indent, n.Then); err != nil {
return err
}
indent--
for _, elseIf := range n.ElseIfs {
if err := writeIndent(w, indent, "} else if ", elseIf.Expression.Value, " {\n"); err != nil {
return err
}
indent++
if err := writeNodesIndented(w, indent, elseIf.Then); err != nil {
return err
}
indent--
}
if len(n.Else) > 0 {
if err := writeIndent(w, indent, "} else {\n"); err != nil {
return err
}
if err := writeNodesIndented(w, indent+1, n.Else); err != nil {
return err
}
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// switch p.Type {
// case "Something":
// }
type SwitchExpression struct {
Expression Expression
Cases []CaseExpression
}
func (se SwitchExpression) ChildNodes() []Node {
var nodes []Node
for _, c := range se.Cases {
nodes = append(nodes, c.Children...)
}
return nodes
}
func (se SwitchExpression) IsNode() bool { return true }
func (se SwitchExpression) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, "switch ", se.Expression.Value, " {\n"); err != nil {
return err
}
indent++
for i := 0; i < len(se.Cases); i++ {
c := se.Cases[i]
if err := writeIndent(w, indent, c.Expression.Value, "\n"); err != nil {
return err
}
if err := writeNodesIndented(w, indent+1, c.Children); err != nil {
return err
}
}
indent--
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// case "Something":
type CaseExpression struct {
Expression Expression
Children []Node
}
// for i, v := range p.Addresses {
// {! Address(v) }
// }
type ForExpression struct {
Expression Expression
Children []Node
}
func (fe ForExpression) ChildNodes() []Node {
return fe.Children
}
func (fe ForExpression) IsNode() bool { return true }
func (fe ForExpression) Write(w io.Writer, indent int) error {
if err := writeIndent(w, indent, "for ", fe.Expression.Value, " {\n"); err != nil {
return err
}
if err := writeNodesIndented(w, indent+1, fe.Children); err != nil {
return err
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// GoCode is used within HTML elements, and allows arbitrary go code.
// {{ ... }}
type GoCode struct {
Expression Expression
// TrailingSpace lists what happens after the expression.
TrailingSpace TrailingSpace
Multiline bool
}
func (gc GoCode) Trailing() TrailingSpace {
return gc.TrailingSpace
}
func (gc GoCode) IsNode() bool { return true }
func (gc GoCode) Write(w io.Writer, indent int) error {
if isWhitespace(gc.Expression.Value) {
gc.Expression.Value = ""
}
source, err := format.Source([]byte(gc.Expression.Value))
if err != nil {
source = []byte(gc.Expression.Value)
}
if !gc.Multiline {
return writeIndent(w, indent, `{{ `, string(source), ` }}`)
}
if err := writeIndent(w, indent, "{{"+string(source)+"\n"); err != nil {
return err
}
return writeIndent(w, indent, "}}")
}
// StringExpression is used within HTML elements, and for style values.
// { ... }
type StringExpression struct {
Expression Expression
// TrailingSpace lists what happens after the expression.
TrailingSpace TrailingSpace
}
func (se StringExpression) Trailing() TrailingSpace {
return se.TrailingSpace
}
func (se StringExpression) IsNode() bool { return true }
func (se StringExpression) IsStyleDeclarationValue() bool { return true }
func (se StringExpression) Write(w io.Writer, indent int) error {
if isWhitespace(se.Expression.Value) {
se.Expression.Value = ""
}
return writeIndent(w, indent, `{ `, se.Expression.Value, ` }`)
}
// ScriptTemplate is a script block.
type ScriptTemplate struct {
Range Range
Name Expression
Parameters Expression
Value string
}
func (s ScriptTemplate) IsTemplateFileNode() bool { return true }
func (s ScriptTemplate) Write(w io.Writer, indent int) error {
source := formatFunctionArguments(s.Name.Value + "(" + s.Parameters.Value + ")")
if err := writeIndent(w, indent, "script ", string(source), " {\n"); err != nil {
return err
}
if _, err := io.WriteString(w, s.Value); err != nil {
return err
}
if err := writeIndent(w, indent, "}"); err != nil {
return err
}
return nil
}
// formatFunctionArguments formats the function arguments, if possible.
func formatFunctionArguments(expression string) string {
source := []byte(expression)
formatted, err := format.Source([]byte("func " + expression))
if err == nil {
formatted = bytes.TrimPrefix(formatted, []byte("func "))
source = formatted
}
return string(source)
}
package parser
import "github.com/a-h/parse"
// Eat any whitespace.
var whitespaceExpression = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
var r Whitespace
if r.Value, ok, err = parse.OptionalWhitespace.Parse(pi); err != nil || !ok {
return
}
return r, len(r.Value) > 0, nil
})
package templ
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"html"
"html/template"
"io"
"net/http"
"os"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/a-h/templ/safehtml"
)
// Types exposed by all components.
// Component is the interface that all templates implement.
type Component interface {
// Render the template.
Render(ctx context.Context, w io.Writer) error
}
// ComponentFunc converts a function that matches the Component interface's
// Render method into a Component.
type ComponentFunc func(ctx context.Context, w io.Writer) error
// Render the template.
func (cf ComponentFunc) Render(ctx context.Context, w io.Writer) error {
return cf(ctx, w)
}
// WithNonce sets a CSP nonce on the context and returns it.
func WithNonce(ctx context.Context, nonce string) context.Context {
ctx, v := getContext(ctx)
v.nonce = nonce
return ctx
}
// GetNonce returns the CSP nonce value set with WithNonce, or an
// empty string if none has been set.
func GetNonce(ctx context.Context) (nonce string) {
if ctx == nil {
return ""
}
_, v := getContext(ctx)
return v.nonce
}
func WithChildren(ctx context.Context, children Component) context.Context {
ctx, v := getContext(ctx)
v.children = &children
return ctx
}
func ClearChildren(ctx context.Context) context.Context {
_, v := getContext(ctx)
v.children = nil
return ctx
}
// NopComponent is a component that doesn't render anything.
var NopComponent = ComponentFunc(func(ctx context.Context, w io.Writer) error { return nil })
// GetChildren from the context.
func GetChildren(ctx context.Context) Component {
_, v := getContext(ctx)
if v.children == nil {
return NopComponent
}
return *v.children
}
// EscapeString escapes HTML text within templates.
func EscapeString(s string) string {
return html.EscapeString(s)
}
// Bool attribute value.
func Bool(value bool) bool {
return value
}
// Classes for CSS.
// Supported types are string, ConstantCSSClass, ComponentCSSClass, map[string]bool.
func Classes(classes ...any) CSSClasses {
return CSSClasses(classes)
}
// CSSClasses is a slice of CSS classes.
type CSSClasses []any
// String returns the names of all CSS classes.
func (classes CSSClasses) String() string {
if len(classes) == 0 {
return ""
}
cp := newCSSProcessor()
for _, v := range classes {
cp.Add(v)
}
return cp.String()
}
func newCSSProcessor() *cssProcessor {
return &cssProcessor{
classNameToEnabled: make(map[string]bool),
}
}
type cssProcessor struct {
classNameToEnabled map[string]bool
orderedNames []string
}
func (cp *cssProcessor) Add(item any) {
switch c := item.(type) {
case []string:
for _, className := range c {
cp.AddClassName(className, true)
}
case string:
cp.AddClassName(c, true)
case ConstantCSSClass:
cp.AddClassName(c.ClassName(), true)
case ComponentCSSClass:
cp.AddClassName(c.ClassName(), true)
case map[string]bool:
// In Go, map keys are iterated in a randomized order.
// So the keys in the map must be sorted to produce consistent output.
keys := make([]string, len(c))
var i int
for key := range c {
keys[i] = key
i++
}
sort.Strings(keys)
for _, className := range keys {
cp.AddClassName(className, c[className])
}
case []KeyValue[string, bool]:
for _, kv := range c {
cp.AddClassName(kv.Key, kv.Value)
}
case KeyValue[string, bool]:
cp.AddClassName(c.Key, c.Value)
case []KeyValue[CSSClass, bool]:
for _, kv := range c {
cp.AddClassName(kv.Key.ClassName(), kv.Value)
}
case KeyValue[CSSClass, bool]:
cp.AddClassName(c.Key.ClassName(), c.Value)
case CSSClasses:
for _, item := range c {
cp.Add(item)
}
case []CSSClass:
for _, item := range c {
cp.Add(item)
}
case func() CSSClass:
cp.AddClassName(c().ClassName(), true)
default:
cp.AddClassName(unknownTypeClassName, true)
}
}
func (cp *cssProcessor) AddClassName(className string, enabled bool) {
cp.classNameToEnabled[className] = enabled
cp.orderedNames = append(cp.orderedNames, className)
}
func (cp *cssProcessor) String() string {
// Order the outputs according to how they were input, and remove disabled names.
rendered := make(map[string]any, len(cp.classNameToEnabled))
var names []string
for _, name := range cp.orderedNames {
if enabled := cp.classNameToEnabled[name]; !enabled {
continue
}
if _, hasBeenRendered := rendered[name]; hasBeenRendered {
continue
}
names = append(names, name)
rendered[name] = struct{}{}
}
return strings.Join(names, " ")
}
// KeyValue is a key and value pair.
type KeyValue[TKey comparable, TValue any] struct {
Key TKey `json:"name"`
Value TValue `json:"value"`
}
// KV creates a new key/value pair from the input key and value.
func KV[TKey comparable, TValue any](key TKey, value TValue) KeyValue[TKey, TValue] {
return KeyValue[TKey, TValue]{
Key: key,
Value: value,
}
}
const unknownTypeClassName = "--templ-css-class-unknown-type"
// Class returns a CSS class name.
// Deprecated: use a string instead.
func Class(name string) CSSClass {
return SafeClass(name)
}
// SafeClass bypasses CSS class name validation.
// Deprecated: use a string instead.
func SafeClass(name string) CSSClass {
return ConstantCSSClass(name)
}
// CSSClass provides a class name.
type CSSClass interface {
ClassName() string
}
// ConstantCSSClass is a string constant of a CSS class name.
// Deprecated: use a string instead.
type ConstantCSSClass string
// ClassName of the CSS class.
func (css ConstantCSSClass) ClassName() string {
return string(css)
}
// ComponentCSSClass is a templ.CSS
type ComponentCSSClass struct {
// ID of the class, will be autogenerated.
ID string
// Definition of the CSS.
Class SafeCSS
}
// ClassName of the CSS class.
func (css ComponentCSSClass) ClassName() string {
return css.ID
}
// CSSID calculates an ID.
func CSSID(name string, css string) string {
sum := sha256.Sum256([]byte(css))
hs := hex.EncodeToString(sum[:])[0:8] // NOTE: See issue #978. Minimum recommended hs length is 6.
// Benchmarking showed this was fastest, and with fewest allocations (1).
// Using strings.Builder (2 allocs).
// Using fmt.Sprintf (3 allocs).
return name + "_" + hs
}
// NewCSSMiddleware creates HTTP middleware that renders a global stylesheet of ComponentCSSClass
// CSS if the request path matches, or updates the HTTP context to ensure that any handlers that
// use templ.Components skip rendering <style> elements for classes that are included in the global
// stylesheet. By default, the stylesheet path is /styles/templ.css
func NewCSSMiddleware(next http.Handler, classes ...CSSClass) CSSMiddleware {
return CSSMiddleware{
Path: "/styles/templ.css",
CSSHandler: NewCSSHandler(classes...),
Next: next,
}
}
// CSSMiddleware renders a global stylesheet.
type CSSMiddleware struct {
Path string
CSSHandler CSSHandler
Next http.Handler
}
func (cssm CSSMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == cssm.Path {
cssm.CSSHandler.ServeHTTP(w, r)
return
}
// Add registered classes to the context.
ctx, v := getContext(r.Context())
for _, c := range cssm.CSSHandler.Classes {
v.addClass(c.ID)
}
// Serve the request. Templ components will use the updated context
// to know to skip rendering <style> elements for any component CSS
// classes that have been included in the global stylesheet.
cssm.Next.ServeHTTP(w, r.WithContext(ctx))
}
// NewCSSHandler creates a handler that serves a stylesheet containing the CSS of the
// classes passed in. This is used by the CSSMiddleware to provide global stylesheets
// for templ components.
func NewCSSHandler(classes ...CSSClass) CSSHandler {
ccssc := make([]ComponentCSSClass, 0, len(classes))
for _, c := range classes {
ccss, ok := c.(ComponentCSSClass)
if !ok {
continue
}
ccssc = append(ccssc, ccss)
}
return CSSHandler{
Classes: ccssc,
}
}
// CSSHandler is a HTTP handler that serves CSS.
type CSSHandler struct {
Logger func(err error)
Classes []ComponentCSSClass
}
func (cssh CSSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/css")
for _, c := range cssh.Classes {
_, err := w.Write([]byte(c.Class))
if err != nil && cssh.Logger != nil {
cssh.Logger(err)
}
}
}
// RenderCSSItems renders the CSS to the writer, if the items haven't already been rendered.
func RenderCSSItems(ctx context.Context, w io.Writer, classes ...any) (err error) {
if len(classes) == 0 {
return nil
}
_, v := getContext(ctx)
sb := new(strings.Builder)
renderCSSItemsToBuilder(sb, v, classes...)
if sb.Len() > 0 {
if _, err = io.WriteString(w, `<style type="text/css">`); err != nil {
return err
}
if _, err = io.WriteString(w, sb.String()); err != nil {
return err
}
if _, err = io.WriteString(w, `</style>`); err != nil {
return err
}
}
return nil
}
func renderCSSItemsToBuilder(sb *strings.Builder, v *contextValue, classes ...any) {
for _, c := range classes {
switch ccc := c.(type) {
case ComponentCSSClass:
if !v.hasClassBeenRendered(ccc.ID) {
sb.WriteString(string(ccc.Class))
v.addClass(ccc.ID)
}
case KeyValue[ComponentCSSClass, bool]:
if !ccc.Value {
continue
}
renderCSSItemsToBuilder(sb, v, ccc.Key)
case KeyValue[CSSClass, bool]:
if !ccc.Value {
continue
}
renderCSSItemsToBuilder(sb, v, ccc.Key)
case CSSClasses:
renderCSSItemsToBuilder(sb, v, ccc...)
case []CSSClass:
for _, item := range ccc {
renderCSSItemsToBuilder(sb, v, item)
}
case func() CSSClass:
renderCSSItemsToBuilder(sb, v, ccc())
case []string:
// Skip. These are class names, not CSS classes.
case string:
// Skip. This is a class name, not a CSS class.
case ConstantCSSClass:
// Skip. This is a class name, not a CSS class.
case CSSClass:
// Skip. This is a class name, not a CSS class.
case map[string]bool:
// Skip. These are class names, not CSS classes.
case KeyValue[string, bool]:
// Skip. These are class names, not CSS classes.
case []KeyValue[string, bool]:
// Skip. These are class names, not CSS classes.
case KeyValue[ConstantCSSClass, bool]:
// Skip. These are class names, not CSS classes.
case []KeyValue[ConstantCSSClass, bool]:
// Skip. These are class names, not CSS classes.
}
}
}
// SafeCSS is CSS that has been sanitized.
type SafeCSS string
type SafeCSSProperty string
var safeCSSPropertyType = reflect.TypeOf(SafeCSSProperty(""))
// SanitizeCSS sanitizes CSS properties to ensure that they are safe.
func SanitizeCSS[T ~string](property string, value T) SafeCSS {
if reflect.TypeOf(value) == safeCSSPropertyType {
return SafeCSS(safehtml.SanitizeCSSProperty(property) + ":" + string(value) + ";")
}
p, v := safehtml.SanitizeCSS(property, string(value))
return SafeCSS(p + ":" + v + ";")
}
// Attributes is an alias to map[string]any made for spread attributes.
type Attributes map[string]any
// sortedKeys returns the keys of a map in sorted order.
func sortedKeys(m map[string]any) (keys []string) {
keys = make([]string, len(m))
var i int
for k := range m {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}
func writeStrings(w io.Writer, ss ...string) (err error) {
for _, s := range ss {
if _, err = io.WriteString(w, s); err != nil {
return err
}
}
return nil
}
func RenderAttributes(ctx context.Context, w io.Writer, attributes Attributes) (err error) {
for _, key := range sortedKeys(attributes) {
value := attributes[key]
switch value := value.(type) {
case string:
if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value), `"`); err != nil {
return err
}
case *string:
if value != nil {
if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(*value), `"`); err != nil {
return err
}
}
case bool:
if value {
if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
return err
}
}
case *bool:
if value != nil && *value {
if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
return err
}
}
case KeyValue[string, bool]:
if value.Value {
if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value.Key), `"`); err != nil {
return err
}
}
case KeyValue[bool, bool]:
if value.Value && value.Key {
if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
return err
}
}
case func() bool:
if value() {
if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
return err
}
}
}
}
return nil
}
// Context.
type contextKeyType int
const contextKey = contextKeyType(0)
type contextValue struct {
ss map[string]struct{}
onceHandles map[*OnceHandle]struct{}
children *Component
nonce string
}
func (v *contextValue) setHasBeenRendered(h *OnceHandle) {
if v.onceHandles == nil {
v.onceHandles = map[*OnceHandle]struct{}{}
}
v.onceHandles[h] = struct{}{}
}
func (v *contextValue) getHasBeenRendered(h *OnceHandle) (ok bool) {
if v.onceHandles == nil {
v.onceHandles = map[*OnceHandle]struct{}{}
}
_, ok = v.onceHandles[h]
return
}
func (v *contextValue) addScript(s string) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
v.ss["script_"+s] = struct{}{}
}
func (v *contextValue) hasScriptBeenRendered(s string) (ok bool) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
_, ok = v.ss["script_"+s]
return
}
func (v *contextValue) addClass(s string) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
v.ss["class_"+s] = struct{}{}
}
func (v *contextValue) hasClassBeenRendered(s string) (ok bool) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
_, ok = v.ss["class_"+s]
return
}
// InitializeContext initializes context used to store internal state used during rendering.
func InitializeContext(ctx context.Context) context.Context {
if _, ok := ctx.Value(contextKey).(*contextValue); ok {
return ctx
}
v := &contextValue{}
ctx = context.WithValue(ctx, contextKey, v)
return ctx
}
func getContext(ctx context.Context) (context.Context, *contextValue) {
v, ok := ctx.Value(contextKey).(*contextValue)
if !ok {
ctx = InitializeContext(ctx)
v = ctx.Value(contextKey).(*contextValue)
}
return ctx, v
}
var bufferPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func GetBuffer() *bytes.Buffer {
return bufferPool.Get().(*bytes.Buffer)
}
func ReleaseBuffer(b *bytes.Buffer) {
b.Reset()
bufferPool.Put(b)
}
// JoinStringErrs joins an optional list of errors.
func JoinStringErrs(s string, errs ...error) (string, error) {
return s, errors.Join(errs...)
}
// Error returned during template rendering.
type Error struct {
Err error
// FileName of the template file.
FileName string
// Line index of the error.
Line int
// Col index of the error.
Col int
}
func (e Error) Error() string {
if e.FileName == "" {
e.FileName = "templ"
}
return fmt.Sprintf("%s: error at line %d, col %d: %v", e.FileName, e.Line, e.Col, e.Err)
}
func (e Error) Unwrap() error {
return e.Err
}
// Raw renders the input HTML to the output without applying HTML escaping.
//
// Use of this component presents a security risk - the HTML should come from
// a trusted source, because it will be included as-is in the output.
func Raw[T ~string](html T, errs ...error) Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
if err = errors.Join(errs...); err != nil {
return err
}
_, err = io.WriteString(w, string(html))
return err
})
}
// FromGoHTML creates a templ Component from a Go html/template template.
func FromGoHTML(t *template.Template, data any) Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
return t.Execute(w, data)
})
}
// ToGoHTML renders the component to a Go html/template template.HTML string.
func ToGoHTML(ctx context.Context, c Component) (s template.HTML, err error) {
b := GetBuffer()
defer ReleaseBuffer(b)
if err = c.Render(ctx, b); err != nil {
return
}
s = template.HTML(b.String())
return
}
// WriteWatchModeString is used when rendering templates in development mode.
// the generator would have written non-go code to the _templ.txt file, which
// is then read by this function and written to the output.
func WriteWatchModeString(w io.Writer, lineNum int) error {
_, path, _, _ := runtime.Caller(1)
if !strings.HasSuffix(path, "_templ.go") {
return errors.New("templ: WriteWatchModeString can only be called from _templ.go")
}
txtFilePath := strings.Replace(path, "_templ.go", "_templ.txt", 1)
literals, err := getWatchedStrings(txtFilePath)
if err != nil {
return fmt.Errorf("templ: failed to cache strings: %w", err)
}
if lineNum > len(literals) {
return errors.New("templ: failed to find line " + strconv.Itoa(lineNum) + " in " + txtFilePath)
}
unquoted, err := strconv.Unquote(`"` + literals[lineNum-1] + `"`)
if err != nil {
return err
}
_, err = io.WriteString(w, unquoted)
return err
}
var (
watchModeCache = map[string]watchState{}
watchStateMutex sync.Mutex
)
type watchState struct {
modTime time.Time
strings []string
}
func getWatchedStrings(txtFilePath string) ([]string, error) {
watchStateMutex.Lock()
defer watchStateMutex.Unlock()
state, cached := watchModeCache[txtFilePath]
if !cached {
return cacheStrings(txtFilePath)
}
if time.Since(state.modTime) < time.Millisecond*100 {
return state.strings, nil
}
info, err := os.Stat(txtFilePath)
if err != nil {
return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
}
if !info.ModTime().After(state.modTime) {
return state.strings, nil
}
return cacheStrings(txtFilePath)
}
func cacheStrings(txtFilePath string) ([]string, error) {
txtFile, err := os.Open(txtFilePath)
if err != nil {
return nil, fmt.Errorf("templ: failed to open %s: %w", txtFilePath, err)
}
defer txtFile.Close()
info, err := txtFile.Stat()
if err != nil {
return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
}
all, err := io.ReadAll(txtFile)
if err != nil {
return nil, fmt.Errorf("templ: failed to read %s: %w", txtFilePath, err)
}
literals := strings.Split(string(all), "\n")
watchModeCache[txtFilePath] = watchState{
modTime: info.ModTime(),
strings: literals,
}
return literals, nil
}
package runtime
import (
"bufio"
"io"
"net/http"
)
// DefaultBufferSize is the default size of buffers. It is set to 4KB by default, which is the
// same as the default buffer size of bufio.Writer.
var DefaultBufferSize = 4 * 1024 // 4KB
// Buffer is a wrapper around bufio.Writer that enables flushing and closing of
// the underlying writer.
type Buffer struct {
Underlying io.Writer
b *bufio.Writer
}
// Write the contents of p into the buffer.
func (b *Buffer) Write(p []byte) (n int, err error) {
return b.b.Write(p)
}
// Flush writes any buffered data to the underlying io.Writer and
// calls the Flush method of the underlying http.Flusher if it implements it.
func (b *Buffer) Flush() error {
if err := b.b.Flush(); err != nil {
return err
}
if f, ok := b.Underlying.(http.Flusher); ok {
f.Flush()
}
return nil
}
// Close closes the buffer and the underlying io.Writer if it implements io.Closer.
func (b *Buffer) Close() error {
if c, ok := b.Underlying.(io.Closer); ok {
return c.Close()
}
return nil
}
// Reset sets the underlying io.Writer to w and resets the buffer.
func (b *Buffer) Reset(w io.Writer) {
if b.b == nil {
b.b = bufio.NewWriterSize(b, DefaultBufferSize)
}
b.Underlying = w
b.b.Reset(w)
}
// Size returns the size of the underlying buffer in bytes.
func (b *Buffer) Size() int {
return b.b.Size()
}
// WriteString writes the contents of s into the buffer.
func (b *Buffer) WriteString(s string) (n int, err error) {
return b.b.WriteString(s)
}
package runtime
import (
"io"
"sync"
)
var bufferPool = sync.Pool{
New: func() any {
return new(Buffer)
},
}
// GetBuffer creates and returns a new buffer if the writer is not already a buffer,
// or returns the existing buffer if it is.
func GetBuffer(w io.Writer) (b *Buffer, existing bool) {
if w == nil {
return nil, false
}
b, ok := w.(*Buffer)
if ok {
return b, true
}
b = bufferPool.Get().(*Buffer)
b.Reset(w)
return b, false
}
// ReleaseBuffer flushes the buffer and returns it to the pool.
func ReleaseBuffer(w io.Writer) (err error) {
b, ok := w.(*Buffer)
if !ok {
return nil
}
err = b.Flush()
bufferPool.Put(b)
return err
}
package runtime
import "strings"
// GetBuilder returns a strings.Builder.
func GetBuilder() (sb strings.Builder) {
return sb
}
package runtime
import (
"context"
"io"
"github.com/a-h/templ"
)
// GeneratedComponentInput is used to avoid generated code needing to import the `context` and `io` packages.
type GeneratedComponentInput struct {
Context context.Context
Writer io.Writer
}
// GeneratedTemplate is used to avoid generated code needing to import the `context` and `io` packages.
func GeneratedTemplate(f func(GeneratedComponentInput) error) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
return f(GeneratedComponentInput{ctx, w})
})
}
// Adapted from https://raw.githubusercontent.com/google/safehtml/3c4cd5b5d8c9a6c5882fba099979e9f50b65c876/style.go
// Copyright (c) 2017 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
package safehtml
import (
"net/url"
"regexp"
"strings"
)
// SanitizeCSS attempts to sanitize CSS properties.
func SanitizeCSS(property, value string) (string, string) {
property = SanitizeCSSProperty(property)
if property == InnocuousPropertyName {
return InnocuousPropertyName, InnocuousPropertyValue
}
return property, SanitizeCSSValue(property, value)
}
func SanitizeCSSValue(property, value string) string {
if sanitizer, ok := cssPropertyNameToValueSanitizer[property]; ok {
return sanitizer(value)
}
return sanitizeRegular(value)
}
func SanitizeCSSProperty(property string) string {
if !identifierPattern.MatchString(property) {
return InnocuousPropertyName
}
return strings.ToLower(property)
}
// identifierPattern matches a subset of valid <ident-token> values defined in
// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram. This pattern matches all generic family name
// keywords defined in https://drafts.csswg.org/css-fonts-3/#family-name-value.
var identifierPattern = regexp.MustCompile(`^[-a-zA-Z]+$`)
var cssPropertyNameToValueSanitizer = map[string]func(string) string{
"background-image": sanitizeBackgroundImage,
"font-family": sanitizeFontFamily,
"display": sanitizeEnum,
"background-color": sanitizeRegular,
"background-position": sanitizeRegular,
"background-repeat": sanitizeRegular,
"background-size": sanitizeRegular,
"color": sanitizeRegular,
"height": sanitizeRegular,
"width": sanitizeRegular,
"left": sanitizeRegular,
"right": sanitizeRegular,
"top": sanitizeRegular,
"bottom": sanitizeRegular,
"font-weight": sanitizeRegular,
"padding": sanitizeRegular,
"z-index": sanitizeRegular,
}
var validURLPrefixes = []string{
`url("`,
`url('`,
`url(`,
}
var validURLSuffixes = []string{
`")`,
`')`,
`)`,
}
func sanitizeBackgroundImage(v string) string {
// Check for <> as per https://github.com/google/safehtml/blob/be23134998433fcf0135dda53593fc8f8bf4df7c/style.go#L87C2-L89C3
if strings.ContainsAny(v, "<>") {
return InnocuousPropertyValue
}
for _, u := range strings.Split(v, ",") {
u = strings.TrimSpace(u)
var found bool
for i, prefix := range validURLPrefixes {
if strings.HasPrefix(u, prefix) && strings.HasSuffix(u, validURLSuffixes[i]) {
found = true
u = strings.TrimPrefix(u, validURLPrefixes[i])
u = strings.TrimSuffix(u, validURLSuffixes[i])
break
}
}
if !found || !urlIsSafe(u) {
return InnocuousPropertyValue
}
}
return v
}
func urlIsSafe(s string) bool {
u, err := url.Parse(s)
if err != nil {
return false
}
if u.IsAbs() {
if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") || strings.EqualFold(u.Scheme, "mailto") {
return true
}
return false
}
return true
}
var genericFontFamilyName = regexp.MustCompile(`^[a-zA-Z][- a-zA-Z]+$`)
func sanitizeFontFamily(s string) string {
for _, f := range strings.Split(s, ",") {
f = strings.TrimSpace(f)
if strings.HasPrefix(f, `"`) {
if !strings.HasSuffix(f, `"`) {
return InnocuousPropertyValue
}
continue
}
if !genericFontFamilyName.MatchString(f) {
return InnocuousPropertyValue
}
}
return s
}
func sanitizeEnum(s string) string {
if !safeEnumPropertyValuePattern.MatchString(s) {
return InnocuousPropertyValue
}
return s
}
func sanitizeRegular(s string) string {
if !safeRegularPropertyValuePattern.MatchString(s) {
return InnocuousPropertyValue
}
return s
}
// InnocuousPropertyName is an innocuous property generated by a sanitizer when its input is unsafe.
const InnocuousPropertyName = "zTemplUnsafeCSSPropertyName"
// InnocuousPropertyValue is an innocuous property generated by a sanitizer when its input is unsafe.
const InnocuousPropertyValue = "zTemplUnsafeCSSPropertyValue"
// safeRegularPropertyValuePattern matches strings that are safe to use as property values.
// Specifically, it matches string where every '*' or '/' is followed by end-of-text or a safe rune
// (i.e. alphanumerics or runes in the set [+-.!#%_ \t]). This regex ensures that the following
// are disallowed:
// - "/*" and "*/", which are CSS comment markers.
// - "//", even though this is not a comment marker in the CSS specification. Disallowing
// this string minimizes the chance that browser peculiarities or parsing bugs will allow
// sanitization to be bypassed.
// - '(' and ')', which can be used to call functions.
// - ',', since it can be used to inject extra values into a property.
// - Runes which could be matched on CSS error recovery of a previously malformed token, such as '@'
// and ':'. See http://www.w3.org/TR/css3-syntax/#error-handling.
var safeRegularPropertyValuePattern = regexp.MustCompile(`^(?:[*/]?(?:[0-9a-zA-Z+-.!#%_ \t]|$))*$`)
// safeEnumPropertyValuePattern matches strings that are safe to use as enumerated property values.
// Specifically, it matches strings that contain only alphabetic and '-' runes.
var safeEnumPropertyValuePattern = regexp.MustCompile(`^[a-zA-Z-]*$`)
package templ
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
)
// ComponentScript is a templ Script template.
type ComponentScript struct {
// Name of the script, e.g. print.
Name string
// Function to render.
Function string
// Call of the function in JavaScript syntax, including parameters, and
// ensures parameters are HTML escaped; useful for injecting into HTML
// attributes like onclick, onhover, etc.
//
// Given:
// functionName("some string",12345)
// It would render:
// __templ_functionName_sha("some string",12345))
//
// This is can be injected into HTML attributes:
// <button onClick="__templ_functionName_sha("some string",12345))">Click Me</button>
Call string
// Call of the function in JavaScript syntax, including parameters. It
// does not HTML escape parameters; useful for directly calling in script
// elements.
//
// Given:
// functionName("some string",12345)
// It would render:
// __templ_functionName_sha("some string",12345))
//
// This is can be used to call the function inside a script tag:
// <script>__templ_functionName_sha("some string",12345))</script>
CallInline string
}
var _ Component = ComponentScript{}
func writeScriptHeader(ctx context.Context, w io.Writer) (err error) {
var nonceAttr string
if nonce := GetNonce(ctx); nonce != "" {
nonceAttr = " nonce=\"" + EscapeString(nonce) + "\""
}
_, err = fmt.Fprintf(w, `<script type="text/javascript"%s>`, nonceAttr)
return err
}
func (c ComponentScript) Render(ctx context.Context, w io.Writer) error {
err := RenderScriptItems(ctx, w, c)
if err != nil {
return err
}
if len(c.Call) > 0 {
if err = writeScriptHeader(ctx, w); err != nil {
return err
}
if _, err = io.WriteString(w, c.CallInline); err != nil {
return err
}
if _, err = io.WriteString(w, `</script>`); err != nil {
return err
}
}
return nil
}
// RenderScriptItems renders a <script> element, if the script has not already been rendered.
func RenderScriptItems(ctx context.Context, w io.Writer, scripts ...ComponentScript) (err error) {
if len(scripts) == 0 {
return nil
}
_, v := getContext(ctx)
sb := new(strings.Builder)
for _, s := range scripts {
if !v.hasScriptBeenRendered(s.Name) {
sb.WriteString(s.Function)
v.addScript(s.Name)
}
}
if sb.Len() > 0 {
if err = writeScriptHeader(ctx, w); err != nil {
return err
}
if _, err = io.WriteString(w, sb.String()); err != nil {
return err
}
if _, err = io.WriteString(w, `</script>`); err != nil {
return err
}
}
return nil
}
// JSExpression represents a JavaScript expression intended for use as an argument for script templates.
// The string value of JSExpression will be inserted directly as JavaScript code in function call arguments.
type JSExpression string
// SafeScript encodes unknown parameters for safety for inside HTML attributes.
func SafeScript(functionName string, params ...any) string {
encodedParams := safeEncodeScriptParams(true, params)
sb := new(strings.Builder)
sb.WriteString(functionName)
sb.WriteRune('(')
sb.WriteString(strings.Join(encodedParams, ","))
sb.WriteRune(')')
return sb.String()
}
// SafeScript encodes unknown parameters for safety for inline scripts.
func SafeScriptInline(functionName string, params ...any) string {
encodedParams := safeEncodeScriptParams(false, params)
sb := new(strings.Builder)
sb.WriteString(functionName)
sb.WriteRune('(')
sb.WriteString(strings.Join(encodedParams, ","))
sb.WriteRune(')')
return sb.String()
}
func safeEncodeScriptParams(escapeHTML bool, params []any) []string {
encodedParams := make([]string, len(params))
for i := 0; i < len(encodedParams); i++ {
if val, ok := params[i].(JSExpression); ok {
encodedParams[i] = string(val)
continue
}
enc, _ := json.Marshal(params[i])
if !escapeHTML {
encodedParams[i] = string(enc)
continue
}
encodedParams[i] = EscapeString(string(enc))
}
return encodedParams
}
package turbo
import (
"context"
"net/http"
"strings"
"github.com/a-h/templ"
)
// Append adds an append action to the output stream.
func Append(w http.ResponseWriter, target string, template templ.Component) error {
return AppendWithContext(context.Background(), w, target, template)
}
// AppendWithContext adds an append action to the output stream.
func AppendWithContext(ctx context.Context, w http.ResponseWriter, target string, template templ.Component) error {
w.Header().Set("Content-Type", "text/vnd.turbo-stream.html")
return actionTemplate("append", target).Render(templ.WithChildren(ctx, template), w)
}
// Prepend adds a prepend action to the output stream.
func Prepend(w http.ResponseWriter, target string, template templ.Component) error {
return PrependWithContext(context.Background(), w, target, template)
}
// PrependWithContext adds a prepend action to the output stream.
func PrependWithContext(ctx context.Context, w http.ResponseWriter, target string, template templ.Component) error {
w.Header().Set("Content-Type", "text/vnd.turbo-stream.html")
return actionTemplate("prepend", target).Render(templ.WithChildren(ctx, template), w)
}
// Replace adds a replace action to the output stream.
func Replace(w http.ResponseWriter, target string, template templ.Component) error {
return ReplaceWithContext(context.Background(), w, target, template)
}
// ReplaceWithContext adds a replace action to the output stream.
func ReplaceWithContext(ctx context.Context, w http.ResponseWriter, target string, template templ.Component) error {
w.Header().Set("Content-Type", "text/vnd.turbo-stream.html")
return actionTemplate("replace", target).Render(templ.WithChildren(ctx, template), w)
}
// Update adds an update action to the output stream.
func Update(w http.ResponseWriter, target string, template templ.Component) error {
return UpdateWithContext(context.Background(), w, target, template)
}
// UpdateWithContext adds an update action to the output stream.
func UpdateWithContext(ctx context.Context, w http.ResponseWriter, target string, template templ.Component) error {
w.Header().Set("Content-Type", "text/vnd.turbo-stream.html")
return actionTemplate("update", target).Render(templ.WithChildren(ctx, template), w)
}
// Remove adds a remove action to the output stream.
func Remove(w http.ResponseWriter, target string) error {
return RemoveWithContext(context.Background(), w, target)
}
// RemoveWithContext adds a remove action to the output stream.
func RemoveWithContext(ctx context.Context, w http.ResponseWriter, target string) error {
w.Header().Set("Content-Type", "text/vnd.turbo-stream.html")
return removeTemplate("remove", target).Render(ctx, w)
}
// IsTurboRequest returns true if the incoming request is able to receive a Turbo stream.
// This is determined by checking the request header for "text/vnd.turbo-stream.html"
func IsTurboRequest(r *http.Request) bool {
return strings.Contains(r.Header.Get("accept"), "text/vnd.turbo-stream.html")
}
// Code generated by templ - DO NOT EDIT.
package turbo
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func actionTemplate(action string, target string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<turbo-stream action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(action)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `turbo/stream.templ`, Line: 4, Col: 30}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(target)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `turbo/stream.templ`, Line: 4, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><template>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</template></turbo-stream>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func removeTemplate(action string, target string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<turbo-stream action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(action)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `turbo/stream.templ`, Line: 12, Col: 30}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(target)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `turbo/stream.templ`, Line: 12, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></turbo-stream>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
package templ
import "strings"
// FailedSanitizationURL is returned if a URL fails sanitization checks.
const FailedSanitizationURL = SafeURL("about:invalid#TemplFailedSanitizationURL")
// URL sanitizes the input string s and returns a SafeURL.
func URL(s string) SafeURL {
if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
protocol := s[:i]
if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") && !strings.EqualFold(protocol, "tel") && !strings.EqualFold(protocol, "ftp") && !strings.EqualFold(protocol, "ftps") {
return FailedSanitizationURL
}
}
return SafeURL(s)
}
// SafeURL is a URL that has been sanitized.
type SafeURL string
package templ
import _ "embed"
//go:embed .version
var version string
func Version() string {
return "v" + version
}