diff options
Diffstat (limited to 'vendor/github.com/a-h/templ/runtime')
| -rw-r--r-- | vendor/github.com/a-h/templ/runtime/buffer.go | 62 | ||||
| -rw-r--r-- | vendor/github.com/a-h/templ/runtime/bufferpool.go | 38 | ||||
| -rw-r--r-- | vendor/github.com/a-h/templ/runtime/builder.go | 8 | ||||
| -rw-r--r-- | vendor/github.com/a-h/templ/runtime/runtime.go | 21 | ||||
| -rw-r--r-- | vendor/github.com/a-h/templ/runtime/scriptelement.go | 107 | ||||
| -rw-r--r-- | vendor/github.com/a-h/templ/runtime/styleattribute.go | 217 | ||||
| -rw-r--r-- | vendor/github.com/a-h/templ/runtime/watchmode.go | 148 |
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 +} |
