summaryrefslogtreecommitdiff
path: root/vendor/github.com/a-h/templ/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/a-h/templ/runtime')
-rw-r--r--vendor/github.com/a-h/templ/runtime/buffer.go62
-rw-r--r--vendor/github.com/a-h/templ/runtime/bufferpool.go38
-rw-r--r--vendor/github.com/a-h/templ/runtime/builder.go8
-rw-r--r--vendor/github.com/a-h/templ/runtime/runtime.go21
-rw-r--r--vendor/github.com/a-h/templ/runtime/scriptelement.go107
-rw-r--r--vendor/github.com/a-h/templ/runtime/styleattribute.go217
-rw-r--r--vendor/github.com/a-h/templ/runtime/watchmode.go148
7 files changed, 601 insertions, 0 deletions
diff --git a/vendor/github.com/a-h/templ/runtime/buffer.go b/vendor/github.com/a-h/templ/runtime/buffer.go
new file mode 100644
index 0000000..63e4acd
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime/buffer.go
@@ -0,0 +1,62 @@
+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)
+}
diff --git a/vendor/github.com/a-h/templ/runtime/bufferpool.go b/vendor/github.com/a-h/templ/runtime/bufferpool.go
new file mode 100644
index 0000000..ca2a131
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime/bufferpool.go
@@ -0,0 +1,38 @@
+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
+}
diff --git a/vendor/github.com/a-h/templ/runtime/builder.go b/vendor/github.com/a-h/templ/runtime/builder.go
new file mode 100644
index 0000000..0f4c9d4
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime/builder.go
@@ -0,0 +1,8 @@
+package runtime
+
+import "strings"
+
+// GetBuilder returns a strings.Builder.
+func GetBuilder() (sb strings.Builder) {
+ return sb
+}
diff --git a/vendor/github.com/a-h/templ/runtime/runtime.go b/vendor/github.com/a-h/templ/runtime/runtime.go
new file mode 100644
index 0000000..aaa4a2c
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime/runtime.go
@@ -0,0 +1,21 @@
+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})
+ })
+}
diff --git a/vendor/github.com/a-h/templ/runtime/scriptelement.go b/vendor/github.com/a-h/templ/runtime/scriptelement.go
new file mode 100644
index 0000000..a742e93
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime/scriptelement.go
@@ -0,0 +1,107 @@
+package runtime
+
+import (
+ "encoding/json"
+ "errors"
+ "strings"
+ "unicode/utf8"
+)
+
+func ScriptContentInsideStringLiteral[T any](v T, errs ...error) (string, error) {
+ return scriptContent(v, true, errs...)
+}
+
+func ScriptContentOutsideStringLiteral[T any](v T, errs ...error) (string, error) {
+ return scriptContent(v, false, errs...)
+}
+
+func scriptContent[T any](v T, insideStringLiteral bool, errs ...error) (string, error) {
+ if errors.Join(errs...) != nil {
+ return "", errors.Join(errs...)
+ }
+ if vs, ok := any(v).(string); ok && insideStringLiteral {
+ return replace(vs, jsStrReplacementTable), nil
+ }
+ jd, err := json.Marshal(v)
+ if err != nil {
+ return "", err
+ }
+ if insideStringLiteral {
+ return replace(string(jd), jsStrReplacementTable), nil
+ }
+ return string(jd), nil
+}
+
+// See https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/html/template/js.go
+
+// replace replaces each rune r of s with replacementTable[r], provided that
+// r < len(replacementTable). If replacementTable[r] is the empty string then
+// no replacement is made.
+// It also replaces runes U+2028 and U+2029 with the raw strings `\u2028` and
+// `\u2029`.
+func replace(s string, replacementTable []string) string {
+ var b strings.Builder
+ r, w, written := rune(0), 0, 0
+ for i := 0; i < len(s); i += w {
+ // See comment in htmlEscaper.
+ r, w = utf8.DecodeRuneInString(s[i:])
+ var repl string
+ switch {
+ case int(r) < len(lowUnicodeReplacementTable):
+ repl = lowUnicodeReplacementTable[r]
+ case int(r) < len(replacementTable) && replacementTable[r] != "":
+ repl = replacementTable[r]
+ case r == '\u2028':
+ repl = `\u2028`
+ case r == '\u2029':
+ repl = `\u2029`
+ default:
+ continue
+ }
+ if written == 0 {
+ b.Grow(len(s))
+ }
+ b.WriteString(s[written:i])
+ b.WriteString(repl)
+ written = i + w
+ }
+ if written == 0 {
+ return s
+ }
+ b.WriteString(s[written:])
+ return b.String()
+}
+
+var lowUnicodeReplacementTable = []string{
+ 0: `\u0000`, 1: `\u0001`, 2: `\u0002`, 3: `\u0003`, 4: `\u0004`, 5: `\u0005`, 6: `\u0006`,
+ '\a': `\u0007`,
+ '\b': `\u0008`,
+ '\t': `\t`,
+ '\n': `\n`,
+ '\v': `\u000b`, // "\v" == "v" on IE 6.
+ '\f': `\f`,
+ '\r': `\r`,
+ 0xe: `\u000e`, 0xf: `\u000f`, 0x10: `\u0010`, 0x11: `\u0011`, 0x12: `\u0012`, 0x13: `\u0013`,
+ 0x14: `\u0014`, 0x15: `\u0015`, 0x16: `\u0016`, 0x17: `\u0017`, 0x18: `\u0018`, 0x19: `\u0019`,
+ 0x1a: `\u001a`, 0x1b: `\u001b`, 0x1c: `\u001c`, 0x1d: `\u001d`, 0x1e: `\u001e`, 0x1f: `\u001f`,
+}
+
+var jsStrReplacementTable = []string{
+ 0: `\u0000`,
+ '\t': `\t`,
+ '\n': `\n`,
+ '\v': `\u000b`, // "\v" == "v" on IE 6.
+ '\f': `\f`,
+ '\r': `\r`,
+ // Encode HTML specials as hex so the output can be embedded
+ // in HTML attributes without further encoding.
+ '"': `\u0022`,
+ '`': `\u0060`,
+ '&': `\u0026`,
+ '\'': `\u0027`,
+ '+': `\u002b`,
+ '/': `\/`,
+ '<': `\u003c`,
+ '>': `\u003e`,
+ '\\': `\\`,
+}
diff --git a/vendor/github.com/a-h/templ/runtime/styleattribute.go b/vendor/github.com/a-h/templ/runtime/styleattribute.go
new file mode 100644
index 0000000..c94f4e3
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime/styleattribute.go
@@ -0,0 +1,217 @@
+package runtime
+
+import (
+ "errors"
+ "fmt"
+ "html"
+ "maps"
+ "reflect"
+ "slices"
+ "strings"
+
+ "github.com/a-h/templ"
+ "github.com/a-h/templ/safehtml"
+)
+
+// SanitizeStyleAttributeValues renders a style attribute value.
+// The supported types are:
+// - string
+// - templ.SafeCSS
+// - map[string]string
+// - map[string]templ.SafeCSSProperty
+// - templ.KeyValue[string, string] - A map of key/values where the key is the CSS property name and the value is the CSS property value.
+// - templ.KeyValue[string, templ.SafeCSSProperty] - A map of key/values where the key is the CSS property name and the value is the CSS property value.
+// - templ.KeyValue[string, bool] - The bool determines whether the value should be included.
+// - templ.KeyValue[templ.SafeCSS, bool] - The bool determines whether the value should be included.
+// - func() (anyOfTheAboveTypes)
+// - func() (anyOfTheAboveTypes, error)
+// - []anyOfTheAboveTypes
+//
+// In the above, templ.SafeCSS and templ.SafeCSSProperty are types that are used to indicate that the value is safe to render as CSS without sanitization.
+// All other types are sanitized before rendering.
+//
+// If an error is returned by any function, or a non-nil error is included in the input, the error is returned.
+func SanitizeStyleAttributeValues(values ...any) (string, error) {
+ if err := getJoinedErrorsFromValues(values...); err != nil {
+ return "", err
+ }
+ sb := new(strings.Builder)
+ for _, v := range values {
+ if v == nil {
+ continue
+ }
+ if err := sanitizeStyleAttributeValue(sb, v); err != nil {
+ return "", err
+ }
+ }
+ return sb.String(), nil
+}
+
+func sanitizeStyleAttributeValue(sb *strings.Builder, v any) error {
+ // Process concrete types.
+ switch v := v.(type) {
+ case string:
+ return processString(sb, v)
+
+ case templ.SafeCSS:
+ return processSafeCSS(sb, v)
+
+ case map[string]string:
+ return processStringMap(sb, v)
+
+ case map[string]templ.SafeCSSProperty:
+ return processSafeCSSPropertyMap(sb, v)
+
+ case templ.KeyValue[string, string]:
+ return processStringKV(sb, v)
+
+ case templ.KeyValue[string, bool]:
+ if v.Value {
+ return processString(sb, v.Key)
+ }
+ return nil
+
+ case templ.KeyValue[templ.SafeCSS, bool]:
+ if v.Value {
+ return processSafeCSS(sb, v.Key)
+ }
+ return nil
+ }
+
+ // Fall back to reflection.
+
+ // Handle functions first using reflection.
+ if handled, err := handleFuncWithReflection(sb, v); handled {
+ return err
+ }
+
+ // Handle slices using reflection before concrete types.
+ if handled, err := handleSliceWithReflection(sb, v); handled {
+ return err
+ }
+
+ _, err := sb.WriteString(TemplUnsupportedStyleAttributeValue)
+ return err
+}
+
+func processSafeCSS(sb *strings.Builder, v templ.SafeCSS) error {
+ if v == "" {
+ return nil
+ }
+ sb.WriteString(html.EscapeString(string(v)))
+ if !strings.HasSuffix(string(v), ";") {
+ sb.WriteRune(';')
+ }
+ return nil
+}
+
+func processString(sb *strings.Builder, v string) error {
+ if v == "" {
+ return nil
+ }
+ sanitized := strings.TrimSpace(safehtml.SanitizeStyleValue(v))
+ sb.WriteString(html.EscapeString(sanitized))
+ if !strings.HasSuffix(sanitized, ";") {
+ sb.WriteRune(';')
+ }
+ return nil
+}
+
+var ErrInvalidStyleAttributeFunctionSignature = errors.New("invalid function signature, should be in the form func() (string, error)")
+
+// handleFuncWithReflection handles functions using reflection.
+func handleFuncWithReflection(sb *strings.Builder, v any) (bool, error) {
+ rv := reflect.ValueOf(v)
+ if rv.Kind() != reflect.Func {
+ return false, nil
+ }
+
+ t := rv.Type()
+ if t.NumIn() != 0 || (t.NumOut() != 1 && t.NumOut() != 2) {
+ return false, ErrInvalidStyleAttributeFunctionSignature
+ }
+
+ // Check the types of the return values
+ if t.NumOut() == 2 {
+ // Ensure the second return value is of type `error`
+ secondReturnType := t.Out(1)
+ if !secondReturnType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
+ return false, fmt.Errorf("second return value must be of type error, got %v", secondReturnType)
+ }
+ }
+
+ results := rv.Call(nil)
+
+ if t.NumOut() == 2 {
+ // Check if the second return value is an error
+ if errVal := results[1].Interface(); errVal != nil {
+ if err, ok := errVal.(error); ok && err != nil {
+ return true, err
+ }
+ }
+ }
+
+ return true, sanitizeStyleAttributeValue(sb, results[0].Interface())
+}
+
+// handleSliceWithReflection handles slices using reflection.
+func handleSliceWithReflection(sb *strings.Builder, v any) (bool, error) {
+ rv := reflect.ValueOf(v)
+ if rv.Kind() != reflect.Slice {
+ return false, nil
+ }
+ for i := range rv.Len() {
+ elem := rv.Index(i).Interface()
+ if err := sanitizeStyleAttributeValue(sb, elem); err != nil {
+ return true, err
+ }
+ }
+ return true, nil
+}
+
+// processStringMap processes a map[string]string.
+func processStringMap(sb *strings.Builder, m map[string]string) error {
+ for _, name := range slices.Sorted(maps.Keys(m)) {
+ name, value := safehtml.SanitizeCSS(name, m[name])
+ sb.WriteString(html.EscapeString(name))
+ sb.WriteRune(':')
+ sb.WriteString(html.EscapeString(value))
+ sb.WriteRune(';')
+ }
+ return nil
+}
+
+// processSafeCSSPropertyMap processes a map[string]templ.SafeCSSProperty.
+func processSafeCSSPropertyMap(sb *strings.Builder, m map[string]templ.SafeCSSProperty) error {
+ for _, name := range slices.Sorted(maps.Keys(m)) {
+ sb.WriteString(html.EscapeString(safehtml.SanitizeCSSProperty(name)))
+ sb.WriteRune(':')
+ sb.WriteString(html.EscapeString(string(m[name])))
+ sb.WriteRune(';')
+ }
+ return nil
+}
+
+// processStringKV processes a templ.KeyValue[string, string].
+func processStringKV(sb *strings.Builder, kv templ.KeyValue[string, string]) error {
+ name, value := safehtml.SanitizeCSS(kv.Key, kv.Value)
+ sb.WriteString(html.EscapeString(name))
+ sb.WriteRune(':')
+ sb.WriteString(html.EscapeString(value))
+ sb.WriteRune(';')
+ return nil
+}
+
+// getJoinedErrorsFromValues collects and joins errors from the input values.
+func getJoinedErrorsFromValues(values ...any) error {
+ var errs []error
+ for _, v := range values {
+ if err, ok := v.(error); ok {
+ errs = append(errs, err)
+ }
+ }
+ return errors.Join(errs...)
+}
+
+// TemplUnsupportedStyleAttributeValue is the default value returned for unsupported types.
+var TemplUnsupportedStyleAttributeValue = "zTemplUnsupportedStyleAttributeValue:Invalid;"
diff --git a/vendor/github.com/a-h/templ/runtime/watchmode.go b/vendor/github.com/a-h/templ/runtime/watchmode.go
new file mode 100644
index 0000000..b083eb0
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime/watchmode.go
@@ -0,0 +1,148 @@
+package runtime
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+var developmentMode = os.Getenv("TEMPL_DEV_MODE") == "true"
+
+func GetDevModeTextFileName(templFileName string) string {
+ if strings.HasSuffix(templFileName, "_templ.go") {
+ templFileName = strings.TrimSuffix(templFileName, "_templ.go") + ".templ"
+ }
+ absFileName, err := filepath.Abs(templFileName)
+ if err != nil {
+ absFileName = templFileName
+ }
+ absFileName, err = filepath.EvalSymlinks(absFileName)
+ if err != nil {
+ absFileName = templFileName
+ }
+ absFileName = normalizePath(absFileName)
+
+ hashedFileName := sha256.Sum256([]byte(absFileName))
+ outputFileName := fmt.Sprintf("templ_%s.txt", hex.EncodeToString(hashedFileName[:]))
+
+ root := os.TempDir()
+ if os.Getenv("TEMPL_DEV_MODE_ROOT") != "" {
+ root = os.Getenv("TEMPL_DEV_MODE_ROOT")
+ }
+
+ return filepath.Join(root, outputFileName)
+}
+
+// normalizePath converts Windows paths to Unix style paths.
+func normalizePath(p string) string {
+ p = strings.ReplaceAll(filepath.Clean(p), `\`, `/`)
+ parts := strings.SplitN(p, ":", 2)
+ if len(parts) == 2 && len(parts[0]) == 1 {
+ drive := strings.ToLower(parts[0])
+ p = "/" + drive + parts[1]
+ }
+ return p
+}
+
+// WriteString writes the string to the writer. If development mode is enabled
+// s is replaced with the string at the index in the _templ.txt file.
+func WriteString(w io.Writer, index int, s string) (err error) {
+ if developmentMode {
+ _, path, _, _ := runtime.Caller(1)
+ if !strings.HasSuffix(path, "_templ.go") {
+ return errors.New("templ: attempt to use WriteString from a non templ file")
+ }
+ path, err := filepath.EvalSymlinks(path)
+ if err != nil {
+ return fmt.Errorf("templ: failed to eval symlinks for %q: %w", path, err)
+ }
+
+ txtFilePath := GetDevModeTextFileName(path)
+ literals, err := getWatchedStrings(txtFilePath)
+ if err != nil {
+ return fmt.Errorf("templ: failed to get watched strings for %q: %w", path, err)
+ }
+ if index > len(literals) {
+ return fmt.Errorf("templ: failed to find line %d in %s", index, txtFilePath)
+ }
+
+ s, err = strconv.Unquote(`"` + literals[index-1] + `"`)
+ if err != nil {
+ return err
+ }
+ }
+ _, err = io.WriteString(w, s)
+ 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 func() {
+ _ = 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
+}