summaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/a-h/templ
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/a-h/templ')
-rw-r--r--vendor/github.com/a-h/templ/.dockerignore3
-rw-r--r--vendor/github.com/a-h/templ/.gitignore28
-rw-r--r--vendor/github.com/a-h/templ/.goreleaser.yaml72
-rw-r--r--vendor/github.com/a-h/templ/.ignore7
-rw-r--r--vendor/github.com/a-h/templ/.version1
-rw-r--r--vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md128
-rw-r--r--vendor/github.com/a-h/templ/CONTRIBUTING.md244
-rw-r--r--vendor/github.com/a-h/templ/LICENSE21
-rw-r--r--vendor/github.com/a-h/templ/README.md171
-rw-r--r--vendor/github.com/a-h/templ/SECURITY.md9
-rw-r--r--vendor/github.com/a-h/templ/cosign.pub4
-rw-r--r--vendor/github.com/a-h/templ/flake.lock140
-rw-r--r--vendor/github.com/a-h/templ/flake.nix93
-rw-r--r--vendor/github.com/a-h/templ/flush.go36
-rw-r--r--vendor/github.com/a-h/templ/gomod2nix.toml90
-rw-r--r--vendor/github.com/a-h/templ/handler.go102
-rw-r--r--vendor/github.com/a-h/templ/ide-demo.gifbin0 -> 544148 bytes
-rw-r--r--vendor/github.com/a-h/templ/jsonscript.go85
-rw-r--r--vendor/github.com/a-h/templ/jsonstring.go14
-rw-r--r--vendor/github.com/a-h/templ/once.go64
-rw-r--r--vendor/github.com/a-h/templ/push-tag.sh14
-rw-r--r--vendor/github.com/a-h/templ/runtime.go855
-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/safehtml/style.go168
-rw-r--r--vendor/github.com/a-h/templ/templ.pngbin0 -> 15528 bytes
-rw-r--r--vendor/github.com/a-h/templ/url.go20
-rw-r--r--vendor/github.com/a-h/templ/version.go10
30 files changed, 2508 insertions, 0 deletions
diff --git a/vendor/github.com/a-h/templ/.dockerignore b/vendor/github.com/a-h/templ/.dockerignore
new file mode 100644
index 0000000..17896fe
--- /dev/null
+++ b/vendor/github.com/a-h/templ/.dockerignore
@@ -0,0 +1,3 @@
+.git
+Dockerfile
+.dockerignore
diff --git a/vendor/github.com/a-h/templ/.gitignore b/vendor/github.com/a-h/templ/.gitignore
new file mode 100644
index 0000000..0338eda
--- /dev/null
+++ b/vendor/github.com/a-h/templ/.gitignore
@@ -0,0 +1,28 @@
+# Output.
+cmd/templ/templ
+
+# Logs.
+cmd/templ/lspcmd/*log.txt
+
+# Go code coverage.
+coverage.out
+coverage
+
+# Mac filesystem jank.
+.DS_Store
+
+# Docusaurus.
+docs/build/
+docs/resources/_gen/
+node_modules/
+dist/
+
+# Nix artifacts.
+result
+
+# Editors
+## nvim
+.null-ls*
+
+# Go workspace.
+go.work
diff --git a/vendor/github.com/a-h/templ/.goreleaser.yaml b/vendor/github.com/a-h/templ/.goreleaser.yaml
new file mode 100644
index 0000000..456187c
--- /dev/null
+++ b/vendor/github.com/a-h/templ/.goreleaser.yaml
@@ -0,0 +1,72 @@
+builds:
+ - env:
+ - CGO_ENABLED=0
+ dir: cmd/templ
+ mod_timestamp: '{{ .CommitTimestamp }}'
+ flags:
+ - -trimpath
+ ldflags:
+ - -s -w
+ goos:
+ - linux
+ - windows
+ - darwin
+
+checksum:
+ name_template: 'checksums.txt'
+
+signs:
+ - id: checksums
+ cmd: cosign
+ stdin: '{{ .Env.COSIGN_PASSWORD }}'
+ output: true
+ artifacts: checksum
+ args:
+ - sign-blob
+ - --yes
+ - --key
+ - env://COSIGN_PRIVATE_KEY
+ - '--output-certificate=${certificate}'
+ - '--output-signature=${signature}'
+ - '${artifact}'
+
+archives:
+ - format: tar.gz
+ name_template: >-
+ {{ .ProjectName }}_
+ {{- title .Os }}_
+ {{- if eq .Arch "amd64" }}x86_64
+ {{- else if eq .Arch "386" }}i386
+ {{- else }}{{ .Arch }}{{ end }}
+ {{- if .Arm }}v{{ .Arm }}{{ end }}
+
+kos:
+ - repository: ghcr.io/a-h/templ
+ platforms:
+ - linux/amd64
+ - linux/arm64
+ tags:
+ - latest
+ - '{{.Tag}}'
+ bare: true
+
+docker_signs:
+ - cmd: cosign
+ artifacts: all
+ output: true
+ args:
+ - sign
+ - --yes
+ - --key
+ - env://COSIGN_PRIVATE_KEY
+ - '${artifact}'
+
+snapshot:
+ name_template: "{{ incpatch .Version }}-next"
+
+changelog:
+ sort: asc
+ filters:
+ exclude:
+ - '^docs:'
+ - '^test:'
diff --git a/vendor/github.com/a-h/templ/.ignore b/vendor/github.com/a-h/templ/.ignore
new file mode 100644
index 0000000..21cb25e
--- /dev/null
+++ b/vendor/github.com/a-h/templ/.ignore
@@ -0,0 +1,7 @@
+*_templ.go
+examples/integration-ct/static/index.js
+examples/counter/assets/css/bulma.*
+examples/counter/assets/js/htmx.min.js
+examples/counter-basic/assets/css/bulma.*
+examples/typescript/assets/index.js
+package-lock.json
diff --git a/vendor/github.com/a-h/templ/.version b/vendor/github.com/a-h/templ/.version
new file mode 100644
index 0000000..baee64f
--- /dev/null
+++ b/vendor/github.com/a-h/templ/.version
@@ -0,0 +1 @@
+0.2.747 \ No newline at end of file
diff --git a/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md b/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..08340d3
--- /dev/null
+++ b/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+adrianhesketh@hushail.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/vendor/github.com/a-h/templ/CONTRIBUTING.md b/vendor/github.com/a-h/templ/CONTRIBUTING.md
new file mode 100644
index 0000000..e98d31f
--- /dev/null
+++ b/vendor/github.com/a-h/templ/CONTRIBUTING.md
@@ -0,0 +1,244 @@
+# Contributing to templ
+
+## Vision
+
+Enable Go developers to build strongly typed, component-based HTML user interfaces with first-class developer tooling, and a short learning curve.
+
+## Come up with a design and share it
+
+Before starting work on any major pull requests or code changes, start a discussion at https://github.com/a-h/templ/discussions or raise an issue.
+
+We don't want you to spend time on a PR or feature that ultimately doesn't get merged because it doesn't fit with the project goals, or the design doesn't work for some reason.
+
+For issues, it really helps if you provide a reproduction repo, or can create a failing unit test to describe the behaviour.
+
+In designs, we need to consider:
+
+* Backwards compatibility - Not changing the public API between releases, introducing gradual deprecation - don't break people's code.
+* Correctness over time - How can we reduce the risk of defects both now, and in future releases?
+* Threat model - How could each change be used to inject vulnerabilities into web pages?
+* Go version - We target the oldest supported version of Go as per https://go.dev/doc/devel/release
+* Automatic migration - If we need to force through a change.
+* Compile time vs runtime errors - Prefer compile time.
+* Documentation - New features are only useful if people can understand the new feature, what would the documentation look like?
+* Examples - How will we demonstrate the feature?
+
+## Project structure
+
+templ is structured into a few areas:
+
+### Parser `./parser`
+
+The parser directory currently contains both v1 and v2 parsers.
+
+The v1 parser is not maintained, it's only used to migrate v1 code over to the v2 syntax.
+
+The parser is responsible for parsing templ files into an object model. The types that make up the object model are in `types.go`. Automatic formatting of the types is tested in `types_test.go`.
+
+A templ file is parsed into the `TemplateFile` struct object model.
+
+```go
+type TemplateFile struct {
+ // Header contains comments or whitespace at the top of the file.
+ Header []GoExpression
+ // Package expression.
+ Package Package
+ // Nodes in the file.
+ Nodes []TemplateFileNode
+}
+```
+
+Parsers are individually tested using two types of unit test.
+
+One test covers the successful parsing of text into an object. For example, the `HTMLCommentParser` test checks for successful patterns.
+
+```go
+func TestHTMLCommentParser(t *testing.T) {
+ var tests = []struct {
+ name string
+ input string
+ expected HTMLComment
+ }{
+ {
+ name: "comment - single line",
+ input: `<!-- single line comment -->`,
+ expected: HTMLComment{
+ Contents: " single line comment ",
+ },
+ },
+ {
+ name: "comment - no whitespace",
+ input: `<!--no whitespace between sequence open and close-->`,
+ expected: HTMLComment{
+ Contents: "no whitespace between sequence open and close",
+ },
+ },
+ {
+ name: "comment - multiline",
+ input: `<!-- multiline
+ comment
+ -->`,
+ expected: HTMLComment{
+ Contents: ` multiline
+ comment
+ `,
+ },
+ },
+ {
+ name: "comment - with tag",
+ input: `<!-- <p class="test">tag</p> -->`,
+ expected: HTMLComment{
+ Contents: ` <p class="test">tag</p> `,
+ },
+ },
+ {
+ name: "comments can contain tags",
+ input: `<!-- <div> hello world </div> -->`,
+ expected: HTMLComment{
+ Contents: ` <div> hello world </div> `,
+ },
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ input := parse.NewInput(tt.input)
+ result, ok, err := htmlComment.Parse(input)
+ if err != nil {
+ t.Fatalf("parser error: %v", err)
+ }
+ if !ok {
+ t.Fatalf("failed to parse at %d", input.Index())
+ }
+ if diff := cmp.Diff(tt.expected, result); diff != "" {
+ t.Errorf(diff)
+ }
+ })
+ }
+}
+```
+
+Alongside each success test, is a similar test to check that invalid syntax is detected.
+
+```go
+func TestHTMLCommentParserErrors(t *testing.T) {
+ var tests = []struct {
+ name string
+ input string
+ expected error
+ }{
+ {
+ name: "unclosed HTML comment",
+ input: `<!-- unclosed HTML comment`,
+ expected: parse.Error("expected end comment literal '-->' not found",
+ parse.Position{
+ Index: 26,
+ Line: 0,
+ Col: 26,
+ }),
+ },
+ {
+ name: "comment in comment",
+ input: `<!-- <-- other --> -->`,
+ expected: parse.Error("comment contains invalid sequence '--'", parse.Position{
+ Index: 8,
+ Line: 0,
+ Col: 8,
+ }),
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ input := parse.NewInput(tt.input)
+ _, _, err := htmlComment.Parse(input)
+ if diff := cmp.Diff(tt.expected, err); diff != "" {
+ t.Error(diff)
+ }
+ })
+ }
+}
+```
+
+### Generator
+
+The generator takes the object model and writes out Go code that produces the expected output. Any changes to Go code output by templ are made in this area.
+
+Testing of the generator is carried out by creating a templ file, and a matching expected output file.
+
+For example, `./generator/test-a-href` contains a templ file of:
+
+```templ
+package testahref
+
+templ render() {
+ <a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
+ <a href={ templ.URL("javascript:alert('should be sanitized')") }>Sanitized</a>
+ <a href={ templ.SafeURL("javascript:alert('should not be sanitized')") }>Unsanitized</a>
+}
+```
+
+It also contains an expected output file.
+
+```html
+<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
+<a href="about:invalid#TemplFailedSanitizationURL">Sanitized</a>
+<a href="javascript:alert(&#39;should not be sanitized&#39;)">Unsanitized</a>
+```
+
+These tests contribute towards the code coverage metrics by building an instrumented test CLI program. See the `test-cover` task in the `README.md` file.
+
+### CLI
+
+The command line interface for templ is used to generate Go code from templ files, format templ files, and run the LSP.
+
+The code for this is at `./cmd/templ`.
+
+Testing of the templ command line is done with unit tests to check the argument parsing.
+
+The `templ generate` command is tested by generating templ files in the project, and testing that the expected output HTML is present.
+
+### Runtime
+
+The runtime is used by generated code, and by template authors, to serve template content over HTTP, and to carry out various operations.
+
+It is in the root directory of the project at `./runtime.go`. The runtime is unit tested, as well as being tested as part of the `generate` tests.
+
+### LSP
+
+The LSP is structured within the command line interface, and proxies commands through to the `gopls` LSP.
+
+### Docs
+
+The docs are a Docusaurus project at `./docs`.
+
+## Coding
+
+### Build tasks
+
+templ uses the `xc` task runner - https://github.com/joerdav/xc
+
+If you run `xc` you can get see a list of the development tasks that can be run, or you can read the `README.md` file and see the `Tasks` section.
+
+The most useful tasks for local development are:
+
+* `install-snapshot` - this builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path.
+* `test` - this regenerates all templates, and runs the unit tests.
+* `fmt` - run the `gofmt` tool to format all Go code.
+* `lint` - run the same linting as run in the CI process.
+* `docs-run` - run the Docusaurus documentation site.
+
+### Commit messages
+
+The project using https://www.conventionalcommits.org/en/v1.0.0/
+
+Examples:
+
+* `feat: support Go comments in templates, fixes #234"`
+
+### Coding style
+
+* Reduce nesting - i.e. prefer early returns over an `else` block, as per https://danp.net/posts/reducing-go-nesting/ or https://go.dev/doc/effective_go#if
+* Use line breaks to separate "paragraphs" of code - don't use line breaks in between lines, or at the start/end of functions etc.
+* Use the `fmt` and `lint` build tasks to format and lint your code before submitting a PR.
+
diff --git a/vendor/github.com/a-h/templ/LICENSE b/vendor/github.com/a-h/templ/LICENSE
new file mode 100644
index 0000000..15e6fb8
--- /dev/null
+++ b/vendor/github.com/a-h/templ/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Adrian Hesketh
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/a-h/templ/README.md b/vendor/github.com/a-h/templ/README.md
new file mode 100644
index 0000000..e3087f0
--- /dev/null
+++ b/vendor/github.com/a-h/templ/README.md
@@ -0,0 +1,171 @@
+![templ](https://github.com/a-h/templ/raw/main/templ.png)
+
+## An HTML templating language for Go that has great developer tooling.
+
+![templ](ide-demo.gif)
+
+
+## Documentation
+
+See user documentation at https://templ.guide
+
+<p align="center">
+<a href="https://pkg.go.dev/github.com/a-h/templ"><img src="https://pkg.go.dev/badge/github.com/a-h/templ.svg" alt="Go Reference" /></a>
+<a href="https://xcfile.dev"><img src="https://xcfile.dev/badge.svg" alt="xc compatible" /></a>
+<a href="https://raw.githack.com/wiki/a-h/templ/coverage.html"><img src="https://github.com/a-h/templ/wiki/coverage.svg" alt="Go Coverage" /></a>
+<a href="https://goreportcard.com/report/github.com/a-h/templ"><img src="https://goreportcard.com/badge/github.com/a-h/templ" alt="Go Report Card" /></a<
+</p>
+
+## Tasks
+
+### build
+
+Build a local version.
+
+```sh
+go run ./get-version > .version
+cd cmd/templ
+go build
+```
+
+### nix-update-gomod2nix
+
+```sh
+gomod2nix
+```
+
+### install-snapshot
+
+Build and install current version.
+
+```sh
+# Remove templ from the non-standard ~/bin/templ path
+# that this command previously used.
+rm -f ~/bin/templ
+# Clear LSP logs.
+rm -f cmd/templ/lspcmd/*.txt
+# Update version.
+go run ./get-version > .version
+# Install to $GOPATH/bin or $HOME/go/bin
+cd cmd/templ && go install
+```
+
+### build-snapshot
+
+Use goreleaser to build the command line binary using goreleaser.
+
+```sh
+goreleaser build --snapshot --clean
+```
+
+### generate
+
+Run templ generate using local version.
+
+```sh
+go run ./cmd/templ generate -include-version=false
+```
+
+### test
+
+Run Go tests.
+
+```sh
+go run ./get-version > .version
+go run ./cmd/templ generate -include-version=false
+go test ./...
+```
+
+### test-short
+
+Run Go tests.
+
+```sh
+go run ./get-version > .version
+go run ./cmd/templ generate -include-version=false
+go test ./... -short
+```
+
+### test-cover
+
+Run Go tests.
+
+```sh
+# Create test profile directories.
+mkdir -p coverage/fmt
+mkdir -p coverage/generate
+mkdir -p coverage/version
+mkdir -p coverage/unit
+# Build the test binary.
+go build -cover -o ./coverage/templ-cover ./cmd/templ
+# Run the covered generate command.
+GOCOVERDIR=coverage/fmt ./coverage/templ-cover fmt .
+GOCOVERDIR=coverage/generate ./coverage/templ-cover generate -include-version=false
+GOCOVERDIR=coverage/version ./coverage/templ-cover version
+# Run the unit tests.
+go test -cover ./... -coverpkg ./... -args -test.gocoverdir="$PWD/coverage/unit"
+# Display the combined percentage.
+go tool covdata percent -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit
+# Generate a text coverage profile for tooling to use.
+go tool covdata textfmt -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit -o coverage.out
+# Print total
+go tool cover -func coverage.out | grep total
+```
+
+### test-cover-watch
+
+```sh
+gotestsum --watch -- -coverprofile=coverage.out
+```
+
+### benchmark
+
+Run benchmarks.
+
+```sh
+go run ./cmd/templ generate -include-version=false && go test ./... -bench=. -benchmem
+```
+
+### fmt
+
+Format all Go and templ code.
+
+```sh
+gofmt -s -w .
+go run ./cmd/templ fmt .
+```
+
+### lint
+
+```sh
+golangci-lint run --verbose
+```
+
+### push-release-tag
+
+Push a semantic version number to Github to trigger the release process.
+
+```sh
+./push-tag.sh
+```
+
+### docs-run
+
+Run the development server.
+
+Directory: docs
+
+```sh
+npm run start
+```
+
+### docs-build
+
+Build production docs site.
+
+Directory: docs
+
+```sh
+npm run build
+```
+
diff --git a/vendor/github.com/a-h/templ/SECURITY.md b/vendor/github.com/a-h/templ/SECURITY.md
new file mode 100644
index 0000000..8241f55
--- /dev/null
+++ b/vendor/github.com/a-h/templ/SECURITY.md
@@ -0,0 +1,9 @@
+# Security Policy
+
+## Supported Versions
+
+The latest version of templ is supported.
+
+## Reporting a Vulnerability
+
+Use the "Security" tab in Github and fill out the "Report a vulnerability" form.
diff --git a/vendor/github.com/a-h/templ/cosign.pub b/vendor/github.com/a-h/templ/cosign.pub
new file mode 100644
index 0000000..9d7967b
--- /dev/null
+++ b/vendor/github.com/a-h/templ/cosign.pub
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqHp75uAj8XqKrLO2YvY0M2EddckH
+evQnNAj+0GmBptqdf3NJcUCjL6w4z2Ikh/Zb8lh6b13akAwO/dJQaMLoMA==
+-----END PUBLIC KEY-----
diff --git a/vendor/github.com/a-h/templ/flake.lock b/vendor/github.com/a-h/templ/flake.lock
new file mode 100644
index 0000000..af4e370
--- /dev/null
+++ b/vendor/github.com/a-h/templ/flake.lock
@@ -0,0 +1,140 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1694529238,
+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_2": {
+ "locked": {
+ "lastModified": 1667395993,
+ "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "gomod2nix": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1717050755,
+ "narHash": "sha256-C9IEHABulv2zEDFA+Bf0E1nmfN4y6MIUe5eM2RCrDC0=",
+ "owner": "nix-community",
+ "repo": "gomod2nix",
+ "rev": "31b6d2e40b36456e792cd6cf50d5a8ddd2fa59a1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "gomod2nix",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1720096762,
+ "narHash": "sha256-KvpJIWxTNuaSpN2L/9TmTlEhlwxEnzJ1vCpEcfK/4mQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "638369f687471823770f6d3093f1721dc7b8c897",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "release-24.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "gitignore": "gitignore",
+ "gomod2nix": "gomod2nix",
+ "nixpkgs": "nixpkgs",
+ "xc": "xc"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "xc": {
+ "inputs": {
+ "flake-utils": "flake-utils_2",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1717601811,
+ "narHash": "sha256-+XQvDRXpzjBdZI3JGKP6SAOYXM+JSEbWL5kqtCwRJXE=",
+ "owner": "joerdav",
+ "repo": "xc",
+ "rev": "f8e8e658978d6c9fe49c27b684ca7375a74deef1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "joerdav",
+ "repo": "xc",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/vendor/github.com/a-h/templ/flake.nix b/vendor/github.com/a-h/templ/flake.nix
new file mode 100644
index 0000000..fd8a238
--- /dev/null
+++ b/vendor/github.com/a-h/templ/flake.nix
@@ -0,0 +1,93 @@
+{
+ description = "templ";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/release-24.05";
+ gomod2nix = {
+ url = "github:nix-community/gomod2nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ gitignore = {
+ url = "github:hercules-ci/gitignore.nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ xc = {
+ url = "github:joerdav/xc";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = { self, nixpkgs, gomod2nix, gitignore, xc }:
+ let
+ allSystems = [
+ "x86_64-linux" # 64-bit Intel/AMD Linux
+ "aarch64-linux" # 64-bit ARM Linux
+ "x86_64-darwin" # 64-bit Intel macOS
+ "aarch64-darwin" # 64-bit ARM macOS
+ ];
+ forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
+ inherit system;
+ pkgs = import nixpkgs { inherit system; };
+ });
+ in
+ {
+ packages = forAllSystems ({ system, pkgs, ... }:
+ let
+ buildGoApplication = gomod2nix.legacyPackages.${system}.buildGoApplication;
+ in
+ rec {
+ default = templ;
+
+ templ = buildGoApplication {
+ name = "templ";
+ src = gitignore.lib.gitignoreSource ./.;
+ # Update to latest Go version when https://nixpk.gs/pr-tracker.html?pr=324123 is backported to release-24.05.
+ go = pkgs.go;
+ # Must be added due to bug https://github.com/nix-community/gomod2nix/issues/120
+ pwd = ./.;
+ subPackages = [ "cmd/templ" ];
+ CGO_ENABLED = 0;
+ flags = [
+ "-trimpath"
+ ];
+ ldflags = [
+ "-s"
+ "-w"
+ "-extldflags -static"
+ ];
+ };
+ });
+
+ # `nix develop` provides a shell containing development tools.
+ devShell = forAllSystems ({ system, pkgs }:
+ pkgs.mkShell {
+ buildInputs = with pkgs; [
+ (golangci-lint.override { buildGoModule = buildGo121Module; })
+ cosign # Used to sign container images.
+ esbuild # Used to package JS examples.
+ go_1_21
+ gomod2nix.legacyPackages.${system}.gomod2nix
+ gopls
+ goreleaser
+ gotestsum
+ ko # Used to build Docker images.
+ nodejs # Used to build templ-docs.
+ xc.packages.${system}.xc
+ ];
+ });
+
+ # This flake outputs an overlay that can be used to add templ and
+ # templ-docs to nixpkgs as per https://templ.guide/quick-start/installation/#nix
+ #
+ # Example usage:
+ #
+ # nixpkgs.overlays = [
+ # inputs.templ.overlays.default
+ # ];
+ overlays.default = final: prev: {
+ templ = self.packages.${final.stdenv.system}.templ;
+ templ-docs = self.packages.${final.stdenv.system}.templ-docs;
+ };
+ };
+}
+
diff --git a/vendor/github.com/a-h/templ/flush.go b/vendor/github.com/a-h/templ/flush.go
new file mode 100644
index 0000000..56d7d3a
--- /dev/null
+++ b/vendor/github.com/a-h/templ/flush.go
@@ -0,0 +1,36 @@
+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
+}
diff --git a/vendor/github.com/a-h/templ/gomod2nix.toml b/vendor/github.com/a-h/templ/gomod2nix.toml
new file mode 100644
index 0000000..d572a5f
--- /dev/null
+++ b/vendor/github.com/a-h/templ/gomod2nix.toml
@@ -0,0 +1,90 @@
+schema = 3
+
+[mod]
+ [mod."github.com/PuerkitoBio/goquery"]
+ version = "v1.8.1"
+ hash = "sha256-z2RaB8PVPEzSJdMUfkfNjT616yXWTjW2gkhNOh989ZU="
+ [mod."github.com/a-h/htmlformat"]
+ version = "v0.0.0-20231108124658-5bd994fe268e"
+ hash = "sha256-YSl9GsXhc0L2oKGZLwwjUtpe5W6ra6kk74zvQdsDCMU="
+ [mod."github.com/a-h/parse"]
+ version = "v0.0.0-20240121214402-3caf7543159a"
+ hash = "sha256-ee/g6xwwhtF7vVt3griUSh96Kz4z0hM5/tpXxHW6PZk="
+ [mod."github.com/a-h/pathvars"]
+ version = "v0.0.14"
+ hash = "sha256-2NytUpcO0zbzE5XunCLcK3jDqxYzmyb3WqtYDEudAYg="
+ [mod."github.com/a-h/protocol"]
+ version = "v0.0.0-20240704131721-1e461c188041"
+ hash = "sha256-KSw8m+kVIubEi+nuS3dMdBw2ZZTlmcKD/hGbVRFaE5Q="
+ [mod."github.com/andybalholm/brotli"]
+ version = "v1.1.0"
+ hash = "sha256-njLViV4v++ZdgOWGWzlvkefuFvA/nkugl3Ta/h1nu/0="
+ [mod."github.com/andybalholm/cascadia"]
+ version = "v1.3.1"
+ hash = "sha256-M0u22DXSeXUaYtl1KoW1qWL46niFpycFkraCEQ/luYA="
+ [mod."github.com/cenkalti/backoff/v4"]
+ version = "v4.3.0"
+ hash = "sha256-wfVjNZsGG1WoNC5aL+kdcy6QXPgZo4THAevZ1787md8="
+ [mod."github.com/cli/browser"]
+ version = "v1.3.0"
+ hash = "sha256-06hcvQeOEm31clxkTuZ8ts8ZtdNKY575EsM1osRVpLg="
+ [mod."github.com/fatih/color"]
+ version = "v1.16.0"
+ hash = "sha256-Aq/SM28aPJVzvapllQ64R/DM4aZ5CHPewcm/AUJPyJQ="
+ [mod."github.com/fsnotify/fsnotify"]
+ version = "v1.7.0"
+ hash = "sha256-MdT2rQyQHspPJcx6n9ozkLbsktIOJutOqDuKpNAtoZY="
+ [mod."github.com/google/go-cmp"]
+ version = "v0.6.0"
+ hash = "sha256-qgra5jze4iPGP0JSTVeY5qV5AvEnEu39LYAuUCIkMtg="
+ [mod."github.com/mattn/go-colorable"]
+ version = "v0.1.13"
+ hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8="
+ [mod."github.com/mattn/go-isatty"]
+ version = "v0.0.20"
+ hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
+ [mod."github.com/natefinch/atomic"]
+ version = "v1.0.1"
+ hash = "sha256-fbOVHCwRNI8PFjC4o0YXpKZO0JU2aWTfH5c7WXXKMHg="
+ [mod."github.com/rs/cors"]
+ version = "v1.11.0"
+ hash = "sha256-hF25bVehtWCQsxiOfLuL4Hv8NKVunEqLPk/Vcuheha0="
+ [mod."github.com/segmentio/asm"]
+ version = "v1.2.0"
+ hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs="
+ [mod."github.com/segmentio/encoding"]
+ version = "v0.4.0"
+ hash = "sha256-4pWI9eTZRRDP9kO8rG6vbLCtBVVRLtbCJKd0Z2+8JoU="
+ [mod."github.com/stretchr/testify"]
+ version = "v1.8.4"
+ hash = "sha256-MoOmRzbz9QgiJ+OOBo5h5/LbilhJfRUryvzHJmXAWjo="
+ [mod."go.lsp.dev/jsonrpc2"]
+ version = "v0.10.0"
+ hash = "sha256-RbRsMYVBLR7ZDHHGMooycrkdbIauMXkQjVOGP7ggSgM="
+ [mod."go.lsp.dev/pkg"]
+ version = "v0.0.0-20210717090340-384b27a52fb2"
+ hash = "sha256-TxS0Iqe1wbIaFe7MWZJRQdgqhKE8i8CggaGSV9zU1Vg="
+ [mod."go.lsp.dev/uri"]
+ version = "v0.3.0"
+ hash = "sha256-jGP0N7Gf+bql5oJraUo33sXqWg7AKOTj0D8b4paV4dc="
+ [mod."go.uber.org/multierr"]
+ version = "v1.11.0"
+ hash = "sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0="
+ [mod."go.uber.org/zap"]
+ version = "v1.27.0"
+ hash = "sha256-8655KDrulc4Das3VRduO9MjCn8ZYD5WkULjCvruaYsU="
+ [mod."golang.org/x/mod"]
+ version = "v0.17.0"
+ hash = "sha256-CLaPeF6uTFuRDv4oHwOQE6MCMvrzkUjWN3NuyywZjKU="
+ [mod."golang.org/x/net"]
+ version = "v0.24.0"
+ hash = "sha256-w1c21ljta5wNIyel9CSIn/crPzwOCRofNKhqmfs4aEQ="
+ [mod."golang.org/x/sync"]
+ version = "v0.3.0"
+ hash = "sha256-bCJKLvwExhYacH2ZrWlZ38lr1d6oNenNt2m1QqDCs0o="
+ [mod."golang.org/x/sys"]
+ version = "v0.21.0"
+ hash = "sha256-gapzPWuEqY36V6W2YhIDYR49sEvjJRd7bSuf9K1f4JY="
+ [mod."golang.org/x/tools"]
+ version = "v0.13.0"
+ hash = "sha256-OCgLOwia8fNHxfdogXVApf0/qK6jE2ukegOx7lkOzfo="
diff --git a/vendor/github.com/a-h/templ/handler.go b/vendor/github.com/a-h/templ/handler.go
new file mode 100644
index 0000000..a28d561
--- /dev/null
+++ b/vendor/github.com/a-h/templ/handler.go
@@ -0,0 +1,102 @@
+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
+ }
+}
diff --git a/vendor/github.com/a-h/templ/ide-demo.gif b/vendor/github.com/a-h/templ/ide-demo.gif
new file mode 100644
index 0000000..e35fd68
--- /dev/null
+++ b/vendor/github.com/a-h/templ/ide-demo.gif
Binary files differ
diff --git a/vendor/github.com/a-h/templ/jsonscript.go b/vendor/github.com/a-h/templ/jsonscript.go
new file mode 100644
index 0000000..6e88174
--- /dev/null
+++ b/vendor/github.com/a-h/templ/jsonscript.go
@@ -0,0 +1,85 @@
+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
+}
diff --git a/vendor/github.com/a-h/templ/jsonstring.go b/vendor/github.com/a-h/templ/jsonstring.go
new file mode 100644
index 0000000..425e4e8
--- /dev/null
+++ b/vendor/github.com/a-h/templ/jsonstring.go
@@ -0,0 +1,14 @@
+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
+}
diff --git a/vendor/github.com/a-h/templ/once.go b/vendor/github.com/a-h/templ/once.go
new file mode 100644
index 0000000..7860ab8
--- /dev/null
+++ b/vendor/github.com/a-h/templ/once.go
@@ -0,0 +1,64 @@
+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)
+ })
+}
diff --git a/vendor/github.com/a-h/templ/push-tag.sh b/vendor/github.com/a-h/templ/push-tag.sh
new file mode 100644
index 0000000..9eedeed
--- /dev/null
+++ b/vendor/github.com/a-h/templ/push-tag.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+if [ `git rev-parse --abbrev-ref HEAD` != "main" ]; then
+ echo "Error: Not on main branch. Please switch to main branch.";
+ exit 1;
+fi
+git pull
+if ! git diff --quiet; then
+ echo "Error: Working directory is not clean. Please commit the changes first.";
+ exit 1;
+fi
+export VERSION=`cat .version`
+echo Adding git tag with version v${VERSION};
+git tag v${VERSION};
+git push origin v${VERSION};
diff --git a/vendor/github.com/a-h/templ/runtime.go b/vendor/github.com/a-h/templ/runtime.go
new file mode 100644
index 0000000..d4d5aa0
--- /dev/null
+++ b/vendor/github.com/a-h/templ/runtime.go
@@ -0,0 +1,855 @@
+package templ
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "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))
+ hp := hex.EncodeToString(sum[:])[0:4]
+ // Benchmarking showed this was fastest, and with fewest allocations (1).
+ // Using strings.Builder (2 allocs).
+ // Using fmt.Sprintf (3 allocs).
+ return name + "_" + hp
+}
+
+// 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
+}
+
+// Script handling.
+
+func safeEncodeScriptParams(escapeHTML bool, params []any) []string {
+ encodedParams := make([]string, len(params))
+ for i := 0; i < len(encodedParams); i++ {
+ enc, _ := json.Marshal(params[i])
+ if !escapeHTML {
+ encodedParams[i] = string(enc)
+ continue
+ }
+ encodedParams[i] = EscapeString(string(enc))
+ }
+ return encodedParams
+}
+
+// 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()
+}
+
+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
+}
+
+// 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(&#34;some string&#34;,12345))
+ //
+ // This is can be injected into HTML attributes:
+ // <button onClick="__templ_functionName_sha(&#34;some string&#34;,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
+}
+
+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
+}
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/safehtml/style.go b/vendor/github.com/a-h/templ/safehtml/style.go
new file mode 100644
index 0000000..486df7c
--- /dev/null
+++ b/vendor/github.com/a-h/templ/safehtml/style.go
@@ -0,0 +1,168 @@
+// 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-]*$`)
diff --git a/vendor/github.com/a-h/templ/templ.png b/vendor/github.com/a-h/templ/templ.png
new file mode 100644
index 0000000..1c4bc2f
--- /dev/null
+++ b/vendor/github.com/a-h/templ/templ.png
Binary files differ
diff --git a/vendor/github.com/a-h/templ/url.go b/vendor/github.com/a-h/templ/url.go
new file mode 100644
index 0000000..bf912e1
--- /dev/null
+++ b/vendor/github.com/a-h/templ/url.go
@@ -0,0 +1,20 @@
+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
diff --git a/vendor/github.com/a-h/templ/version.go b/vendor/github.com/a-h/templ/version.go
new file mode 100644
index 0000000..b7fbb6f
--- /dev/null
+++ b/vendor/github.com/a-h/templ/version.go
@@ -0,0 +1,10 @@
+package templ
+
+import _ "embed"
+
+//go:embed .version
+var version string
+
+func Version() string {
+ return "v" + version
+}