···164164 return removed
165165}
166166167167+// QueryLabels returns the active (non-negated) label values for a DID.
168168+// Returns nil with no error if the labeler is unreachable or returns no labels.
169169+func (lc *LabelChecker) QueryLabels(ctx context.Context, did string) ([]string, error) {
170170+ u := lc.labelerURL + "/xrpc/com.atproto.label.queryLabels?" + url.Values{
171171+ "uriPatterns": {did},
172172+ }.Encode()
173173+174174+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
175175+ if err != nil {
176176+ return nil, err
177177+ }
178178+179179+ resp, err := lc.client.Do(req)
180180+ if err != nil {
181181+ return nil, nil // labeler unreachable — don't fail the UI
182182+ }
183183+ defer resp.Body.Close()
184184+185185+ if resp.StatusCode != http.StatusOK {
186186+ return nil, nil
187187+ }
188188+189189+ var result queryLabelsResponse
190190+ if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&result); err != nil {
191191+ return nil, nil
192192+ }
193193+194194+ var labels []string
195195+ for _, l := range result.Labels {
196196+ if !l.Neg {
197197+ labels = append(labels, l.Val)
198198+ }
199199+ }
200200+ return labels, nil
201201+}
202202+167203func (lc *LabelChecker) queryLabeler(ctx context.Context, did string) (bool, error) {
168204 u := lc.labelerURL + "/xrpc/com.atproto.label.queryLabels?" + url.Values{
169205 "uriPatterns": {did},
+50-11
internal/relay/smtp.go
···2828 ReadTimeout time.Duration // default 60s
2929}
30303131-// MemberLookupFunc returns a member for SMTP AUTH.
3232-// Returns nil if the DID is not found.
3333-type MemberLookupFunc func(ctx context.Context, did string) (*AuthMember, error)
3131+// MemberLookupFunc returns a member and all their domains for SMTP AUTH.
3232+// The auth handler iterates domains to match the API key, resolving to a
3333+// specific domain for the session. Returns nil if the DID is not found.
3434+type MemberLookupFunc func(ctx context.Context, did string) (*MemberWithDomains, error)
3535+3636+// MemberWithDomains is the DID-level member info plus all registered domains.
3737+// Returned by MemberLookupFunc so AUTH can match the API key to a domain.
3838+type MemberWithDomains struct {
3939+ DID string
4040+ Status string
4141+ SendCount int64
4242+ HourlyLimit int
4343+ DailyLimit int
4444+ CreatedAt time.Time
4545+ Domains []DomainInfo
4646+}
4747+4848+// DomainInfo holds domain-level credentials for SMTP AUTH matching.
4949+type DomainInfo struct {
5050+ Domain string
5151+ APIKeyHash []byte
5252+ DKIMKeys *DKIMKeys
5353+ DKIMSelector string
5454+}
34553535-// AuthMember is the minimal member info needed for SMTP AUTH + sending.
5656+// AuthMember is the resolved member for a specific domain in this SMTP session.
5757+// Constructed during AUTH by matching the API key to a domain.
3658type AuthMember struct {
3759 DID string
3860 Domain string
3939- APIKeyHash []byte
4061 Status string
4162 SendCount int64
4263 DKIMKeys *DKIMKeys
···157178 }
158179 }
159180160160- member, err := s.server.memberLookup(context.Background(), username)
181181+ mwd, err := s.server.memberLookup(context.Background(), username)
161182 if err != nil {
162183 log.Printf("smtp.auth: did=%q ip=%s success=false failure_reason=lookup_error error=%v", username, s.conn.Hostname(), err)
163184 authFail()
···167188 Message: "Temporary authentication failure",
168189 }
169190 }
170170- if member == nil {
191191+ if mwd == nil {
171192 log.Printf("smtp.auth: did=%q ip=%s success=false failure_reason=unknown_did", username, s.conn.Hostname())
172193 authFail()
173194 return &smtp.SMTPError{
···177198 }
178199 }
179200180180- if !VerifyAPIKey(password, member.APIKeyHash) {
201201+ // Match API key to a specific domain
202202+ var matched *DomainInfo
203203+ for i := range mwd.Domains {
204204+ if VerifyAPIKey(password, mwd.Domains[i].APIKeyHash) {
205205+ matched = &mwd.Domains[i]
206206+ break
207207+ }
208208+ }
209209+ if matched == nil {
181210 log.Printf("smtp.auth: did=%q ip=%s success=false failure_reason=bad_api_key", username, s.conn.Hostname())
182211 authFail()
183212 return &smtp.SMTPError{
···187216 }
188217 }
189218190190- if member.Status == relaystore.StatusSuspended {
219219+ if mwd.Status == relaystore.StatusSuspended {
191220 log.Printf("smtp.auth: did=%q ip=%s success=false failure_reason=suspended", username, s.conn.Hostname())
192221 authFail()
193222 return &smtp.SMTPError{
···197226 }
198227 }
199228200200- log.Printf("smtp.auth: did=%q ip=%s success=true", username, s.conn.Hostname())
229229+ log.Printf("smtp.auth: did=%q domain=%s ip=%s success=true", username, matched.Domain, s.conn.Hostname())
201230 if s.server.metrics != nil {
202231 s.server.metrics.AuthAttempts.WithLabelValues("success").Inc()
203232 }
204233 s.mu.Lock()
205205- s.member = member
234234+ s.member = &AuthMember{
235235+ DID: mwd.DID,
236236+ Domain: matched.Domain,
237237+ Status: mwd.Status,
238238+ SendCount: mwd.SendCount,
239239+ DKIMKeys: matched.DKIMKeys,
240240+ DKIMSelector: matched.DKIMSelector,
241241+ HourlyLimit: mwd.HourlyLimit,
242242+ DailyLimit: mwd.DailyLimit,
243243+ CreatedAt: mwd.CreatedAt,
244244+ }
206245 s.mu.Unlock()
207246 return nil
208247 }), nil
···11+# Contributor Covenant Code of Conduct
22+33+## Our Pledge
44+55+We as members, contributors, and leaders pledge to make participation in our
66+community a harassment-free experience for everyone, regardless of age, body
77+size, visible or invisible disability, ethnicity, sex characteristics, gender
88+identity and expression, level of experience, education, socio-economic status,
99+nationality, personal appearance, race, religion, or sexual identity
1010+and orientation.
1111+1212+We pledge to act and interact in ways that contribute to an open, welcoming,
1313+diverse, inclusive, and healthy community.
1414+1515+## Our Standards
1616+1717+Examples of behavior that contributes to a positive environment for our
1818+community include:
1919+2020+* Demonstrating empathy and kindness toward other people
2121+* Being respectful of differing opinions, viewpoints, and experiences
2222+* Giving and gracefully accepting constructive feedback
2323+* Accepting responsibility and apologizing to those affected by our mistakes,
2424+ and learning from the experience
2525+* Focusing on what is best not just for us as individuals, but for the
2626+ overall community
2727+2828+Examples of unacceptable behavior include:
2929+3030+* The use of sexualized language or imagery, and sexual attention or
3131+ advances of any kind
3232+* Trolling, insulting or derogatory comments, and personal or political attacks
3333+* Public or private harassment
3434+* Publishing others' private information, such as a physical or email
3535+ address, without their explicit permission
3636+* Other conduct which could reasonably be considered inappropriate in a
3737+ professional setting
3838+3939+## Enforcement Responsibilities
4040+4141+Community leaders are responsible for clarifying and enforcing our standards of
4242+acceptable behavior and will take appropriate and fair corrective action in
4343+response to any behavior that they deem inappropriate, threatening, offensive,
4444+or harmful.
4545+4646+Community leaders have the right and responsibility to remove, edit, or reject
4747+comments, commits, code, wiki edits, issues, and other contributions that are
4848+not aligned to this Code of Conduct, and will communicate reasons for moderation
4949+decisions when appropriate.
5050+5151+## Scope
5252+5353+This Code of Conduct applies within all community spaces, and also applies when
5454+an individual is officially representing the community in public spaces.
5555+Examples of representing our community include using an official e-mail address,
5656+posting via an official social media account, or acting as an appointed
5757+representative at an online or offline event.
5858+5959+## Enforcement
6060+6161+Instances of abusive, harassing, or otherwise unacceptable behavior may be
6262+reported to the community leaders responsible for enforcement at
6363+adrianhesketh@hushail.com.
6464+All complaints will be reviewed and investigated promptly and fairly.
6565+6666+All community leaders are obligated to respect the privacy and security of the
6767+reporter of any incident.
6868+6969+## Enforcement Guidelines
7070+7171+Community leaders will follow these Community Impact Guidelines in determining
7272+the consequences for any action they deem in violation of this Code of Conduct:
7373+7474+### 1. Correction
7575+7676+**Community Impact**: Use of inappropriate language or other behavior deemed
7777+unprofessional or unwelcome in the community.
7878+7979+**Consequence**: A private, written warning from community leaders, providing
8080+clarity around the nature of the violation and an explanation of why the
8181+behavior was inappropriate. A public apology may be requested.
8282+8383+### 2. Warning
8484+8585+**Community Impact**: A violation through a single incident or series
8686+of actions.
8787+8888+**Consequence**: A warning with consequences for continued behavior. No
8989+interaction with the people involved, including unsolicited interaction with
9090+those enforcing the Code of Conduct, for a specified period of time. This
9191+includes avoiding interactions in community spaces as well as external channels
9292+like social media. Violating these terms may lead to a temporary or
9393+permanent ban.
9494+9595+### 3. Temporary Ban
9696+9797+**Community Impact**: A serious violation of community standards, including
9898+sustained inappropriate behavior.
9999+100100+**Consequence**: A temporary ban from any sort of interaction or public
101101+communication with the community for a specified period of time. No public or
102102+private interaction with the people involved, including unsolicited interaction
103103+with those enforcing the Code of Conduct, is allowed during this period.
104104+Violating these terms may lead to a permanent ban.
105105+106106+### 4. Permanent Ban
107107+108108+**Community Impact**: Demonstrating a pattern of violation of community
109109+standards, including sustained inappropriate behavior, harassment of an
110110+individual, or aggression toward or disparagement of classes of individuals.
111111+112112+**Consequence**: A permanent ban from any sort of public interaction within
113113+the community.
114114+115115+## Attribution
116116+117117+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118118+version 2.0, available at
119119+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120120+121121+Community Impact Guidelines were inspired by [Mozilla's code of conduct
122122+enforcement ladder](https://github.com/mozilla/diversity).
123123+124124+[homepage]: https://www.contributor-covenant.org
125125+126126+For answers to common questions about this code of conduct, see the FAQ at
127127+https://www.contributor-covenant.org/faq. Translations are available at
128128+https://www.contributor-covenant.org/translations.
+247
vendor/github.com/a-h/templ/CONTRIBUTING.md
···11+# Contributing to templ
22+33+## Vision
44+55+Enable Go developers to build strongly typed, component-based HTML user interfaces with first-class developer tooling, and a short learning curve.
66+77+## Come up with a design and share it
88+99+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.
1010+1111+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.
1212+1313+For issues, it really helps if you provide a reproduction repo, or can create a failing unit test to describe the behaviour.
1414+1515+In designs, we need to consider:
1616+1717+* Backwards compatibility - Not changing the public API between releases, introducing gradual deprecation - don't break people's code.
1818+* Correctness over time - How can we reduce the risk of defects both now, and in future releases?
1919+* Threat model - How could each change be used to inject vulnerabilities into web pages?
2020+* Go version - We target the oldest supported version of Go as per https://go.dev/doc/devel/release
2121+* Automatic migration - If we need to force through a change.
2222+* Compile time vs runtime errors - Prefer compile time.
2323+* Documentation - New features are only useful if people can understand the new feature, what would the documentation look like?
2424+* Examples - How will we demonstrate the feature?
2525+2626+## Project structure
2727+2828+templ is structured into a few areas:
2929+3030+### Parser `./parser`
3131+3232+The parser directory currently contains both v1 and v2 parsers.
3333+3434+The v1 parser is not maintained, it's only used to migrate v1 code over to the v2 syntax.
3535+3636+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`.
3737+3838+A templ file is parsed into the `TemplateFile` struct object model.
3939+4040+```go
4141+type TemplateFile struct {
4242+ // Header contains comments or whitespace at the top of the file.
4343+ Header []GoExpression
4444+ // Package expression.
4545+ Package Package
4646+ // Nodes in the file.
4747+ Nodes []TemplateFileNode
4848+}
4949+```
5050+5151+Parsers are individually tested using two types of unit test.
5252+5353+One test covers the successful parsing of text into an object. For example, the `HTMLCommentParser` test checks for successful patterns.
5454+5555+```go
5656+func TestHTMLCommentParser(t *testing.T) {
5757+ var tests = []struct {
5858+ name string
5959+ input string
6060+ expected HTMLComment
6161+ }{
6262+ {
6363+ name: "comment - single line",
6464+ input: `<!-- single line comment -->`,
6565+ expected: HTMLComment{
6666+ Contents: " single line comment ",
6767+ },
6868+ },
6969+ {
7070+ name: "comment - no whitespace",
7171+ input: `<!--no whitespace between sequence open and close-->`,
7272+ expected: HTMLComment{
7373+ Contents: "no whitespace between sequence open and close",
7474+ },
7575+ },
7676+ {
7777+ name: "comment - multiline",
7878+ input: `<!-- multiline
7979+ comment
8080+ -->`,
8181+ expected: HTMLComment{
8282+ Contents: ` multiline
8383+ comment
8484+ `,
8585+ },
8686+ },
8787+ {
8888+ name: "comment - with tag",
8989+ input: `<!-- <p class="test">tag</p> -->`,
9090+ expected: HTMLComment{
9191+ Contents: ` <p class="test">tag</p> `,
9292+ },
9393+ },
9494+ {
9595+ name: "comments can contain tags",
9696+ input: `<!-- <div> hello world </div> -->`,
9797+ expected: HTMLComment{
9898+ Contents: ` <div> hello world </div> `,
9999+ },
100100+ },
101101+ }
102102+ for _, tt := range tests {
103103+ tt := tt
104104+ t.Run(tt.name, func(t *testing.T) {
105105+ input := parse.NewInput(tt.input)
106106+ result, ok, err := htmlComment.Parse(input)
107107+ if err != nil {
108108+ t.Fatalf("parser error: %v", err)
109109+ }
110110+ if !ok {
111111+ t.Fatalf("failed to parse at %d", input.Index())
112112+ }
113113+ if diff := cmp.Diff(tt.expected, result); diff != "" {
114114+ t.Errorf(diff)
115115+ }
116116+ })
117117+ }
118118+}
119119+```
120120+121121+Alongside each success test, is a similar test to check that invalid syntax is detected.
122122+123123+```go
124124+func TestHTMLCommentParserErrors(t *testing.T) {
125125+ var tests = []struct {
126126+ name string
127127+ input string
128128+ expected error
129129+ }{
130130+ {
131131+ name: "unclosed HTML comment",
132132+ input: `<!-- unclosed HTML comment`,
133133+ expected: parse.Error("expected end comment literal '-->' not found",
134134+ parse.Position{
135135+ Index: 26,
136136+ Line: 0,
137137+ Col: 26,
138138+ }),
139139+ },
140140+ {
141141+ name: "comment in comment",
142142+ input: `<!-- <-- other --> -->`,
143143+ expected: parse.Error("comment contains invalid sequence '--'", parse.Position{
144144+ Index: 8,
145145+ Line: 0,
146146+ Col: 8,
147147+ }),
148148+ },
149149+ }
150150+ for _, tt := range tests {
151151+ tt := tt
152152+ t.Run(tt.name, func(t *testing.T) {
153153+ input := parse.NewInput(tt.input)
154154+ _, _, err := htmlComment.Parse(input)
155155+ if diff := cmp.Diff(tt.expected, err); diff != "" {
156156+ t.Error(diff)
157157+ }
158158+ })
159159+ }
160160+}
161161+```
162162+163163+### Generator
164164+165165+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.
166166+167167+Testing of the generator is carried out by creating a templ file, and a matching expected output file.
168168+169169+For example, `./generator/test-a-href` contains a templ file of:
170170+171171+```templ
172172+package testahref
173173+174174+templ render() {
175175+ <a href="javascript:alert('unaffected');">Ignored</a>
176176+ <a href={ templ.URL("javascript:alert('should be sanitized')") }>Sanitized</a>
177177+ <a href={ templ.SafeURL("javascript:alert('should not be sanitized')") }>Unsanitized</a>
178178+}
179179+```
180180+181181+It also contains an expected output file.
182182+183183+```html
184184+<a href="javascript:alert('unaffected');">Ignored</a>
185185+<a href="about:invalid#TemplFailedSanitizationURL">Sanitized</a>
186186+<a href="javascript:alert('should not be sanitized')">Unsanitized</a>
187187+```
188188+189189+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.
190190+191191+### CLI
192192+193193+The command line interface for templ is used to generate Go code from templ files, format templ files, and run the LSP.
194194+195195+The code for this is at `./cmd/templ`.
196196+197197+Testing of the templ command line is done with unit tests to check the argument parsing.
198198+199199+The `templ generate` command is tested by generating templ files in the project, and testing that the expected output HTML is present.
200200+201201+### Runtime
202202+203203+The runtime is used by generated code, and by template authors, to serve template content over HTTP, and to carry out various operations.
204204+205205+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.
206206+207207+### LSP
208208+209209+The LSP is structured within the command line interface, and proxies commands through to the `gopls` LSP.
210210+211211+### Docs
212212+213213+The docs are a Docusaurus project at `./docs`.
214214+215215+## Coding
216216+217217+### Build tasks
218218+219219+templ uses the `xc` task runner - https://github.com/joerdav/xc
220220+221221+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.
222222+223223+The most useful tasks for local development are:
224224+225225+* `install-snapshot` - this builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path.
226226+* `test` - this regenerates all templates, and runs the unit tests.
227227+* `fmt` - run the `gofmt` tool to format all Go code.
228228+* `lint` - run the same linting as run in the CI process.
229229+* `docs-run` - run the Docusaurus documentation site.
230230+231231+### Commit messages
232232+233233+The project using https://www.conventionalcommits.org/en/v1.0.0/
234234+235235+Examples:
236236+237237+* `feat: support Go comments in templates, fixes #234"`
238238+239239+### Coding style
240240+241241+* 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
242242+* Use line breaks to separate "paragraphs" of code - don't use line breaks in between lines, or at the start/end of functions etc.
243243+* Use the `fmt` and `lint` build tasks to format and lint your code before submitting a PR.
244244+245245+### LLM instructions
246246+247247+See additional coding standards at `.github/copilot-instructions.md`
+21
vendor/github.com/a-h/templ/LICENSE
···11+MIT License
22+33+Copyright (c) 2021 Adrian Hesketh
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+194
vendor/github.com/a-h/templ/README.md
···11+
22+33+## An HTML templating language for Go that has great developer tooling.
44+55+
66+77+88+## Documentation
99+1010+See user documentation at https://templ.guide
1111+1212+<p align="center">
1313+<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>
1414+<a href="https://xcfile.dev"><img src="https://xcfile.dev/badge.svg" alt="xc compatible" /></a>
1515+<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>
1616+<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>
1717+</p>
1818+1919+## Tasks
2020+2121+### version-set
2222+2323+Set the version of templ to the current version.
2424+2525+```sh
2626+version set --template="0.3.%d"
2727+```
2828+2929+### build
3030+3131+Build a local version.
3232+3333+```sh
3434+version set --template="0.3.%d"
3535+cd cmd/templ
3636+go build
3737+```
3838+3939+### install-snapshot
4040+4141+Build and install current version.
4242+4343+```sh
4444+# Remove templ from the non-standard ~/bin/templ path
4545+# that this command previously used.
4646+rm -f ~/bin/templ
4747+# Clear LSP logs.
4848+rm -f cmd/templ/lspcmd/*.txt
4949+# Update version.
5050+version set --template="0.3.%d"
5151+# Install to $GOPATH/bin or $HOME/go/bin
5252+cd cmd/templ && go install
5353+```
5454+5555+### build-snapshot
5656+5757+Use goreleaser to build the command line binary using goreleaser.
5858+5959+```sh
6060+goreleaser build --snapshot --clean
6161+```
6262+6363+### generate
6464+6565+Run templ generate using local version.
6666+6767+```sh
6868+go run ./cmd/templ generate -include-version=false
6969+```
7070+7171+### test
7272+7373+Run Go tests.
7474+7575+```sh
7676+version set --template="0.3.%d"
7777+go run ./cmd/templ generate -include-version=false
7878+go test ./...
7979+```
8080+8181+### test-short
8282+8383+Run Go tests.
8484+8585+```sh
8686+version set --template="0.3.%d"
8787+go run ./cmd/templ generate -include-version=false
8888+go test ./... -short
8989+```
9090+9191+### test-cover
9292+9393+Run Go tests.
9494+9595+```sh
9696+# Create test profile directories.
9797+mkdir -p coverage/fmt
9898+mkdir -p coverage/generate
9999+mkdir -p coverage/version
100100+mkdir -p coverage/unit
101101+# Build the test binary.
102102+go build -cover -o ./coverage/templ-cover ./cmd/templ
103103+# Run the covered generate command.
104104+GOCOVERDIR=coverage/fmt ./coverage/templ-cover fmt .
105105+GOCOVERDIR=coverage/generate ./coverage/templ-cover generate -include-version=false
106106+GOCOVERDIR=coverage/version ./coverage/templ-cover version
107107+# Run the unit tests.
108108+go test -cover ./... -coverpkg ./... -args -test.gocoverdir="$PWD/coverage/unit"
109109+# Display the combined percentage.
110110+go tool covdata percent -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit
111111+# Generate a text coverage profile for tooling to use.
112112+go tool covdata textfmt -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit -o coverage.out
113113+# Print total
114114+go tool cover -func coverage.out | grep total
115115+```
116116+117117+### test-cover-watch
118118+119119+interactive: true
120120+121121+```sh
122122+gotestsum --watch -- -coverprofile=coverage.out
123123+```
124124+125125+### test-fuzz
126126+127127+```sh
128128+./parser/v2/fuzz.sh
129129+./parser/v2/goexpression/fuzz.sh
130130+```
131131+132132+### benchmark
133133+134134+Run benchmarks.
135135+136136+```sh
137137+go run ./cmd/templ generate -include-version=false && go test ./... -bench=. -benchmem
138138+```
139139+140140+### fmt
141141+142142+Format all Go and templ code.
143143+144144+```sh
145145+gofmt -s -w .
146146+go run ./cmd/templ fmt .
147147+```
148148+149149+### lint
150150+151151+Run the lint operations that are run as part of the CI.
152152+153153+```sh
154154+golangci-lint run --verbose
155155+```
156156+157157+### ensure-generated
158158+159159+Ensure that templ files have been generated with the local version of templ, and that those files have been added to git.
160160+161161+Requires: generate
162162+163163+```sh
164164+git diff --exit-code
165165+```
166166+167167+### push-release-tag
168168+169169+Push a semantic version number to GitHub to trigger the release process.
170170+171171+```sh
172172+version push --template="0.3.%d" --prefix="v"
173173+```
174174+175175+### docs-run
176176+177177+Run the development server.
178178+179179+Directory: docs
180180+181181+```sh
182182+npm run start
183183+```
184184+185185+### docs-build
186186+187187+Build production docs site.
188188+189189+Directory: docs
190190+191191+```sh
192192+npm run build
193193+```
194194+
+9
vendor/github.com/a-h/templ/SECURITY.md
···11+# Security Policy
22+33+## Supported Versions
44+55+The latest version of templ is supported.
66+77+## Reporting a Vulnerability
88+99+Use the "Security" tab in GitHub and fill out the "Report a vulnerability" form.
+4
vendor/github.com/a-h/templ/cosign.pub
···11+-----BEGIN PUBLIC KEY-----
22+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqHp75uAj8XqKrLO2YvY0M2EddckH
33+evQnNAj+0GmBptqdf3NJcUCjL6w4z2Ikh/Zb8lh6b13akAwO/dJQaMLoMA==
44+-----END PUBLIC KEY-----
···11+{
22+ description = "templ";
33+44+ inputs = {
55+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
66+ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
77+ gitignore = {
88+ url = "github:hercules-ci/gitignore.nix";
99+ inputs.nixpkgs.follows = "nixpkgs";
1010+ };
1111+ version = {
1212+ url = "github:a-h/version/0.0.10";
1313+ inputs.nixpkgs.follows = "nixpkgs";
1414+ };
1515+ };
1616+1717+ outputs = { self, nixpkgs, nixpkgs-unstable, gitignore, version }:
1818+ let
1919+ allSystems = [
2020+ "x86_64-linux" # 64-bit Intel/AMD Linux
2121+ "aarch64-linux" # 64-bit ARM Linux
2222+ "x86_64-darwin" # 64-bit Intel macOS
2323+ "aarch64-darwin" # 64-bit ARM macOS
2424+ ];
2525+ forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
2626+ inherit system;
2727+ pkgs =
2828+ let
2929+ pkgs-unstable = import nixpkgs-unstable { inherit system; };
3030+ in
3131+ import nixpkgs {
3232+ inherit system;
3333+ overlays = [
3434+ (final: prev: {
3535+ gopls = pkgs-unstable.gopls;
3636+ version = version.packages.${system}.default; # Used to apply version numbers to the repo.
3737+ })
3838+ ];
3939+ };
4040+ });
4141+ in
4242+ {
4343+ packages = forAllSystems ({ pkgs, ... }:
4444+ rec {
4545+ default = templ;
4646+4747+ templ = pkgs.buildGoModule {
4848+ name = "templ";
4949+ subPackages = [ "cmd/templ" ];
5050+ src = gitignore.lib.gitignoreSource ./.;
5151+ vendorHash = "sha256-pVZjZCXT/xhBCMyZdR7kEmB9jqhTwRISFp63bQf6w5A=";
5252+ env = {
5353+ CGO_ENABLED = 0;
5454+ };
5555+ flags = [
5656+ "-trimpath"
5757+ ];
5858+ ldflags = [
5959+ "-s"
6060+ "-w"
6161+ "-extldflags -static"
6262+ ];
6363+ };
6464+ });
6565+6666+ # `nix develop` provides a shell containing development tools.
6767+ devShell = forAllSystems ({ pkgs, ... }:
6868+ pkgs.mkShell {
6969+ buildInputs = [
7070+ pkgs.golangci-lint
7171+ pkgs.cosign # Used to sign container images.
7272+ pkgs.esbuild # Used to package JS examples.
7373+ pkgs.go
7474+ pkgs.gopls
7575+ pkgs.goreleaser
7676+ pkgs.gotestsum
7777+ pkgs.govulncheck
7878+ pkgs.ko # Used to build Docker images.
7979+ pkgs.nodejs # Used to build templ-docs.
8080+ pkgs.nodePackages.prettier # Used for formatting JS and CSS.
8181+ pkgs.version
8282+ pkgs.xc
8383+ ];
8484+ });
8585+8686+ # This flake outputs an overlay that can be used to add templ and
8787+ # templ-docs to nixpkgs as per https://templ.guide/quick-start/installation/#nix
8888+ #
8989+ # Example usage:
9090+ #
9191+ # nixpkgs.overlays = [
9292+ # inputs.templ.overlays.default
9393+ # ];
9494+ overlays.default = final: prev: {
9595+ templ = self.packages.${final.stdenv.system}.templ;
9696+ };
9797+ };
9898+}
9999+
+36
vendor/github.com/a-h/templ/flush.go
···11+package templ
22+33+import (
44+ "context"
55+ "io"
66+)
77+88+// Flush flushes the output buffer after all its child components have been rendered.
99+func Flush() FlushComponent {
1010+ return FlushComponent{}
1111+}
1212+1313+type FlushComponent struct {
1414+}
1515+1616+type flusherError interface {
1717+ Flush() error
1818+}
1919+2020+type flusher interface {
2121+ Flush()
2222+}
2323+2424+func (f FlushComponent) Render(ctx context.Context, w io.Writer) (err error) {
2525+ if err = GetChildren(ctx).Render(ctx, w); err != nil {
2626+ return err
2727+ }
2828+ switch w := w.(type) {
2929+ case flusher:
3030+ w.Flush()
3131+ return nil
3232+ case flusherError:
3333+ return w.Flush()
3434+ }
3535+ return nil
3636+}
+70
vendor/github.com/a-h/templ/fragment.go
···11+package templ
22+33+import (
44+ "context"
55+ "io"
66+ "slices"
77+)
88+99+// RenderFragments renders the specified fragments to w.
1010+func RenderFragments(ctx context.Context, w io.Writer, c Component, ids ...any) error {
1111+ ctx = context.WithValue(ctx, fragmentContextKey, &FragmentContext{
1212+ W: w,
1313+ IDs: ids,
1414+ })
1515+ return c.Render(ctx, io.Discard)
1616+}
1717+1818+type fragmentContextKeyType int
1919+2020+const fragmentContextKey fragmentContextKeyType = iota
2121+2222+// FragmentContext is used to control rendering of fragments within a template.
2323+type FragmentContext struct {
2424+ W io.Writer
2525+ IDs []any
2626+ Active bool
2727+}
2828+2929+// Fragment defines a fragment within a template that can be rendered conditionally based on the id.
3030+// You can use it to render a specific part of a page, e.g. to reduce the amount of HTML returned from a htmx-initiated request.
3131+// Any non-matching contents of the template are rendered, but discarded by the FramentWriter.
3232+func Fragment(id any) Component {
3333+ return &fragment{
3434+ ID: id,
3535+ }
3636+}
3737+3838+type fragment struct {
3939+ ID any
4040+}
4141+4242+func (f *fragment) Render(ctx context.Context, w io.Writer) (err error) {
4343+ // If not in a fragment context, if we're a child fragment, or in a mismatching fragment context, render children normally.
4444+ fragmentCtx := getFragmentContext(ctx)
4545+ if fragmentCtx == nil || fragmentCtx.Active || !slices.Contains(fragmentCtx.IDs, f.ID) {
4646+ return GetChildren(ctx).Render(ctx, w)
4747+ }
4848+4949+ // Instruct child fragments to render their contents normally, because the writer
5050+ // passed to them is already the FragmentContext's writer.
5151+ fragmentCtx.Active = true
5252+ defer func() {
5353+ fragmentCtx.Active = false
5454+ }()
5555+ return GetChildren(ctx).Render(ctx, fragmentCtx.W)
5656+}
5757+5858+// getFragmentContext retrieves the FragmentContext from the provided context. It returns nil if no
5959+// FragmentContext is found or if the context value is of an unexpected type.
6060+func getFragmentContext(ctx context.Context) *FragmentContext {
6161+ ctxValue := ctx.Value(fragmentContextKey)
6262+ if ctxValue == nil {
6363+ return nil
6464+ }
6565+ v, ok := ctxValue.(*FragmentContext)
6666+ if !ok {
6767+ return nil
6868+ }
6969+ return v
7070+}
+163
vendor/github.com/a-h/templ/handler.go
···11+package templ
22+33+import (
44+ "net/http"
55+)
66+77+// ComponentHandler is a http.Handler that renders components.
88+type ComponentHandler struct {
99+ Component Component
1010+ Status int
1111+ ContentType string
1212+ ErrorHandler func(r *http.Request, err error) http.Handler
1313+ StreamResponse bool
1414+ FragmentIDs []any
1515+}
1616+1717+const componentHandlerErrorMessage = "templ: failed to render template"
1818+1919+func (ch *ComponentHandler) handleRenderErr(w http.ResponseWriter, r *http.Request, err error) {
2020+ if ch.ErrorHandler != nil {
2121+ w.Header().Set("Content-Type", ch.ContentType)
2222+ ch.ErrorHandler(r, err).ServeHTTP(w, r)
2323+ return
2424+ }
2525+ http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
2626+}
2727+2828+func (ch *ComponentHandler) ServeHTTPBufferedFragment(w http.ResponseWriter, r *http.Request) {
2929+ // Since the component may error, write to a buffer first.
3030+ // This prevents partial responses from being written to the client.
3131+ buf := GetBuffer()
3232+ defer ReleaseBuffer(buf)
3333+3434+ // Render the component into io.Discard, but use the buffer for fragments.
3535+ if err := RenderFragments(r.Context(), buf, ch.Component, ch.FragmentIDs...); err != nil {
3636+ ch.handleRenderErr(w, r, err)
3737+ return
3838+ }
3939+4040+ // The component rendered successfully, we can write the Content-Type and Status.
4141+ w.Header().Set("Content-Type", ch.ContentType)
4242+ if ch.Status != 0 {
4343+ w.WriteHeader(ch.Status)
4444+ }
4545+ // Ignore write error like http.Error() does, because there is
4646+ // no way to recover at this point.
4747+ _, _ = w.Write(buf.Bytes())
4848+}
4949+5050+func (ch *ComponentHandler) ServeHTTPBufferedComplete(w http.ResponseWriter, r *http.Request) {
5151+ // Since the component may error, write to a buffer first.
5252+ // This prevents partial responses from being written to the client.
5353+ buf := GetBuffer()
5454+ defer ReleaseBuffer(buf)
5555+5656+ // Render the component into the buffer.
5757+ if err := ch.Component.Render(r.Context(), buf); err != nil {
5858+ ch.handleRenderErr(w, r, err)
5959+ return
6060+ }
6161+6262+ // The component rendered successfully, we can write the Content-Type and Status.
6363+ w.Header().Set("Content-Type", ch.ContentType)
6464+ if ch.Status != 0 {
6565+ w.WriteHeader(ch.Status)
6666+ }
6767+ // Ignore write error like http.Error() does, because there is
6868+ // no way to recover at this point.
6969+ _, _ = w.Write(buf.Bytes())
7070+}
7171+7272+func (ch *ComponentHandler) ServeHTTPBuffered(w http.ResponseWriter, r *http.Request) {
7373+ // If fragments are specified, render only those.
7474+ if len(ch.FragmentIDs) > 0 {
7575+ ch.ServeHTTPBufferedFragment(w, r)
7676+ return
7777+ }
7878+7979+ // Otherwise, render the complete component.
8080+ ch.ServeHTTPBufferedComplete(w, r)
8181+}
8282+8383+func (ch *ComponentHandler) ServeHTTPStreamed(w http.ResponseWriter, r *http.Request) {
8484+ // If streaming, we do not buffer the response, so set the headers immediately.
8585+ w.Header().Set("Content-Type", ch.ContentType)
8686+ if ch.Status != 0 {
8787+ w.WriteHeader(ch.Status)
8888+ }
8989+9090+ // Pass fragment names to the context if specified.
9191+ if len(ch.FragmentIDs) > 0 {
9292+9393+ // Render the component into io.Discard, but use the buffer for fragments.
9494+ if err := RenderFragments(r.Context(), w, ch.Component, ch.FragmentIDs...); err != nil {
9595+ ch.handleRenderErr(w, r, err)
9696+ return
9797+ }
9898+ return
9999+ }
100100+101101+ // Render the component into the buffer.
102102+ if err := ch.Component.Render(r.Context(), w); err != nil {
103103+ ch.handleRenderErr(w, r, err)
104104+ return
105105+ }
106106+}
107107+108108+// ServeHTTP implements the http.Handler interface.
109109+func (ch ComponentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
110110+ if ch.StreamResponse {
111111+ ch.ServeHTTPStreamed(w, r)
112112+ return
113113+ }
114114+ ch.ServeHTTPBuffered(w, r)
115115+}
116116+117117+// Handler creates a http.Handler that renders the template.
118118+func Handler(c Component, options ...func(*ComponentHandler)) *ComponentHandler {
119119+ ch := &ComponentHandler{
120120+ Component: c,
121121+ ContentType: "text/html; charset=utf-8",
122122+ }
123123+ for _, o := range options {
124124+ o(ch)
125125+ }
126126+ return ch
127127+}
128128+129129+// WithStatus sets the HTTP status code returned by the ComponentHandler.
130130+func WithStatus(status int) func(*ComponentHandler) {
131131+ return func(ch *ComponentHandler) {
132132+ ch.Status = status
133133+ }
134134+}
135135+136136+// WithContentType sets the Content-Type header returned by the ComponentHandler.
137137+func WithContentType(contentType string) func(*ComponentHandler) {
138138+ return func(ch *ComponentHandler) {
139139+ ch.ContentType = contentType
140140+ }
141141+}
142142+143143+// WithErrorHandler sets the error handler used if rendering fails.
144144+func WithErrorHandler(eh func(r *http.Request, err error) http.Handler) func(*ComponentHandler) {
145145+ return func(ch *ComponentHandler) {
146146+ ch.ErrorHandler = eh
147147+ }
148148+}
149149+150150+// WithStreaming sets the ComponentHandler to stream the response instead of buffering it.
151151+func WithStreaming() func(*ComponentHandler) {
152152+ return func(ch *ComponentHandler) {
153153+ ch.StreamResponse = true
154154+ }
155155+}
156156+157157+// WithFragments sets the ids of the fragments to render.
158158+// If not set, all content is rendered.
159159+func WithFragments(ids ...any) func(*ComponentHandler) {
160160+ return func(ch *ComponentHandler) {
161161+ ch.FragmentIDs = ids
162162+ }
163163+}
vendor/github.com/a-h/templ/ide-demo.gif
This is a binary file and will not be displayed.
+19
vendor/github.com/a-h/templ/join.go
···11+package templ
22+33+import (
44+ "context"
55+ "io"
66+)
77+88+// Join returns a single `templ.Component` that will render provided components in order.
99+// If any of the components return an error the Join component will immediately return with the error.
1010+func Join(components ...Component) Component {
1111+ return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
1212+ for _, c := range components {
1313+ if err = c.Render(ctx, w); err != nil {
1414+ return err
1515+ }
1616+ }
1717+ return nil
1818+ })
1919+}
+40
vendor/github.com/a-h/templ/js.go
···11+package templ
22+33+import (
44+ "crypto/sha256"
55+ "encoding/hex"
66+ "html"
77+)
88+99+// JSUnsafeFuncCall calls arbitrary JavaScript in the js parameter.
1010+//
1111+// Use of this function presents a security risk - the JavaScript must come
1212+// from a trusted source, because it will be included as-is in the output.
1313+func JSUnsafeFuncCall[T ~string](js T) ComponentScript {
1414+ sum := sha256.Sum256([]byte(js))
1515+ return ComponentScript{
1616+ Name: "jsUnsafeFuncCall_" + hex.EncodeToString(sum[:]),
1717+ // Function is empty because the body of the function is defined elsewhere,
1818+ // e.g. in a <script> tag within a templ.Once block.
1919+ Function: "",
2020+ Call: html.EscapeString(string(js)),
2121+ CallInline: string(js),
2222+ }
2323+}
2424+2525+// JSFuncCall calls a JavaScript function with the given arguments.
2626+//
2727+// It can be used in event handlers, e.g. onclick, onhover, etc. or
2828+// directly in HTML.
2929+func JSFuncCall[T ~string](functionName T, args ...any) ComponentScript {
3030+ call := SafeScript(string(functionName), args...)
3131+ sum := sha256.Sum256([]byte(call))
3232+ return ComponentScript{
3333+ Name: "jsFuncCall_" + hex.EncodeToString(sum[:]),
3434+ // Function is empty because the body of the function is defined elsewhere,
3535+ // e.g. in a <script> tag within a templ.Once block.
3636+ Function: "",
3737+ Call: call,
3838+ CallInline: SafeScriptInline(string(functionName), args...),
3939+ }
4040+}
+85
vendor/github.com/a-h/templ/jsonscript.go
···11+package templ
22+33+import (
44+ "context"
55+ "encoding/json"
66+ "fmt"
77+ "io"
88+)
99+1010+var _ Component = JSONScriptElement{}
1111+1212+// JSONScript renders a JSON object inside a script element.
1313+// e.g. <script type="application/json">{"foo":"bar"}</script>
1414+func JSONScript(id string, data any) JSONScriptElement {
1515+ return JSONScriptElement{
1616+ ID: id,
1717+ Type: "application/json",
1818+ Data: data,
1919+ Nonce: GetNonce,
2020+ }
2121+}
2222+2323+// WithType sets the value of the type attribute of the script element.
2424+func (j JSONScriptElement) WithType(t string) JSONScriptElement {
2525+ j.Type = t
2626+ return j
2727+}
2828+2929+// WithNonceFromString sets the value of the nonce attribute of the script element to the given string.
3030+func (j JSONScriptElement) WithNonceFromString(nonce string) JSONScriptElement {
3131+ j.Nonce = func(context.Context) string {
3232+ return nonce
3333+ }
3434+ return j
3535+}
3636+3737+// WithNonceFrom sets the value of the nonce attribute of the script element to the value returned by the given function.
3838+func (j JSONScriptElement) WithNonceFrom(f func(context.Context) string) JSONScriptElement {
3939+ j.Nonce = f
4040+ return j
4141+}
4242+4343+type JSONScriptElement struct {
4444+ // ID of the element in the DOM.
4545+ ID string
4646+ // Type of the script element, defaults to "application/json".
4747+ Type string
4848+ // Data that will be encoded as JSON.
4949+ Data any
5050+ // Nonce is a function that returns a CSP nonce.
5151+ // Defaults to CSPNonceFromContext.
5252+ // See https://content-security-policy.com/nonce for more information.
5353+ Nonce func(ctx context.Context) string
5454+}
5555+5656+func (j JSONScriptElement) Render(ctx context.Context, w io.Writer) (err error) {
5757+ if _, err = io.WriteString(w, "<script"); err != nil {
5858+ return err
5959+ }
6060+ if j.ID != "" {
6161+ if _, err = fmt.Fprintf(w, " id=\"%s\"", EscapeString(j.ID)); err != nil {
6262+ return err
6363+ }
6464+ }
6565+ if j.Type != "" {
6666+ if _, err = fmt.Fprintf(w, " type=\"%s\"", EscapeString(j.Type)); err != nil {
6767+ return err
6868+ }
6969+ }
7070+ if nonce := j.Nonce(ctx); nonce != "" {
7171+ if _, err = fmt.Fprintf(w, " nonce=\"%s\"", EscapeString(nonce)); err != nil {
7272+ return err
7373+ }
7474+ }
7575+ if _, err = io.WriteString(w, ">"); err != nil {
7676+ return err
7777+ }
7878+ if err = json.NewEncoder(w).Encode(j.Data); err != nil {
7979+ return err
8080+ }
8181+ if _, err = io.WriteString(w, "</script>"); err != nil {
8282+ return err
8383+ }
8484+ return nil
8585+}
···11+package templ
22+33+import (
44+ "context"
55+ "io"
66+ "sync/atomic"
77+)
88+99+// onceHandleIndex is used to identify unique once handles in a program run.
1010+var onceHandleIndex int64
1111+1212+type OnceOpt func(*OnceHandle)
1313+1414+// WithOnceComponent sets the component to be rendered once per context.
1515+// This can be used instead of setting the children of the `Once` method,
1616+// for example, if creating a code component outside of a templ HTML template.
1717+func WithComponent(c Component) OnceOpt {
1818+ return func(o *OnceHandle) {
1919+ o.c = c
2020+ }
2121+}
2222+2323+// NewOnceHandle creates a OnceHandle used to ensure that the children of its
2424+// `Once` method are only rendered once per context.
2525+func NewOnceHandle(opts ...OnceOpt) *OnceHandle {
2626+ oh := &OnceHandle{
2727+ id: atomic.AddInt64(&onceHandleIndex, 1),
2828+ }
2929+ for _, opt := range opts {
3030+ opt(oh)
3131+ }
3232+ return oh
3333+}
3434+3535+// OnceHandle is used to ensure that the children of its `Once` method are are only
3636+// rendered once per context.
3737+type OnceHandle struct {
3838+ // id is used to identify which instance of the OnceHandle is being used.
3939+ // The OnceHandle can't be an empty struct, because:
4040+ //
4141+ // | Two distinct zero-size variables may
4242+ // | have the same address in memory
4343+ //
4444+ // https://go.dev/ref/spec#Size_and_alignment_guarantees
4545+ id int64
4646+ // c is the component to be rendered once per context.
4747+ // if c is nil, the children of the `Once` method are rendered.
4848+ c Component
4949+}
5050+5151+// Once returns a component that renders its children once per context.
5252+func (o *OnceHandle) Once() Component {
5353+ return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
5454+ _, v := getContext(ctx)
5555+ if v.getHasBeenRendered(o) {
5656+ return nil
5757+ }
5858+ v.setHasBeenRendered(o)
5959+ if o.c != nil {
6060+ return o.c.Render(ctx, w)
6161+ }
6262+ return GetChildren(ctx).Render(ctx, w)
6363+ })
6464+}
+714
vendor/github.com/a-h/templ/runtime.go
···11+package templ
22+33+import (
44+ "bytes"
55+ "context"
66+ "crypto/sha256"
77+ "encoding/hex"
88+ "errors"
99+ "fmt"
1010+ "html"
1111+ "html/template"
1212+ "io"
1313+ "net/http"
1414+ "reflect"
1515+ "sort"
1616+ "strings"
1717+ "sync"
1818+1919+ "github.com/a-h/templ/safehtml"
2020+)
2121+2222+// Types exposed by all components.
2323+2424+// Component is the interface that all templates implement.
2525+type Component interface {
2626+ // Render the template.
2727+ Render(ctx context.Context, w io.Writer) error
2828+}
2929+3030+// ComponentFunc converts a function that matches the Component interface's
3131+// Render method into a Component.
3232+type ComponentFunc func(ctx context.Context, w io.Writer) error
3333+3434+// Render the template.
3535+func (cf ComponentFunc) Render(ctx context.Context, w io.Writer) error {
3636+ return cf(ctx, w)
3737+}
3838+3939+// WithNonce sets a CSP nonce on the context and returns it.
4040+func WithNonce(ctx context.Context, nonce string) context.Context {
4141+ ctx, v := getContext(ctx)
4242+ v.nonce = nonce
4343+ return ctx
4444+}
4545+4646+// GetNonce returns the CSP nonce value set with WithNonce, or an
4747+// empty string if none has been set.
4848+func GetNonce(ctx context.Context) (nonce string) {
4949+ if ctx == nil {
5050+ return ""
5151+ }
5252+ _, v := getContext(ctx)
5353+ return v.nonce
5454+}
5555+5656+func WithChildren(ctx context.Context, children Component) context.Context {
5757+ ctx, v := getContext(ctx)
5858+ v.children = &children
5959+ return ctx
6060+}
6161+6262+func ClearChildren(ctx context.Context) context.Context {
6363+ _, v := getContext(ctx)
6464+ v.children = nil
6565+ return ctx
6666+}
6767+6868+// NopComponent is a component that doesn't render anything.
6969+var NopComponent = ComponentFunc(func(ctx context.Context, w io.Writer) error { return nil })
7070+7171+// GetChildren from the context.
7272+func GetChildren(ctx context.Context) Component {
7373+ _, v := getContext(ctx)
7474+ if v.children == nil {
7575+ return NopComponent
7676+ }
7777+ return *v.children
7878+}
7979+8080+// EscapeString escapes HTML text within templates.
8181+func EscapeString[T ~string](s T) string {
8282+ return html.EscapeString(string(s))
8383+}
8484+8585+// Bool attribute value.
8686+func Bool(value bool) bool {
8787+ return value
8888+}
8989+9090+// Classes for CSS.
9191+// Supported types are string, ConstantCSSClass, ComponentCSSClass, map[string]bool.
9292+func Classes(classes ...any) CSSClasses {
9393+ return CSSClasses(classes)
9494+}
9595+9696+// CSSClasses is a slice of CSS classes.
9797+type CSSClasses []any
9898+9999+// String returns the names of all CSS classes.
100100+func (classes CSSClasses) String() string {
101101+ if len(classes) == 0 {
102102+ return ""
103103+ }
104104+ cp := newCSSProcessor()
105105+ for _, v := range classes {
106106+ cp.Add(v)
107107+ }
108108+ return cp.String()
109109+}
110110+111111+func newCSSProcessor() *cssProcessor {
112112+ return &cssProcessor{
113113+ classNameToEnabled: make(map[string]bool),
114114+ }
115115+}
116116+117117+type cssProcessor struct {
118118+ classNameToEnabled map[string]bool
119119+ orderedNames []string
120120+}
121121+122122+func (cp *cssProcessor) Add(item any) {
123123+ switch c := item.(type) {
124124+ case []string:
125125+ for _, className := range c {
126126+ cp.AddClassName(className, true)
127127+ }
128128+ case string:
129129+ cp.AddClassName(c, true)
130130+ case ConstantCSSClass:
131131+ cp.AddClassName(c.ClassName(), true)
132132+ case ComponentCSSClass:
133133+ cp.AddClassName(c.ClassName(), true)
134134+ case map[string]bool:
135135+ // In Go, map keys are iterated in a randomized order.
136136+ // So the keys in the map must be sorted to produce consistent output.
137137+ keys := make([]string, len(c))
138138+ var i int
139139+ for key := range c {
140140+ keys[i] = key
141141+ i++
142142+ }
143143+ sort.Strings(keys)
144144+ for _, className := range keys {
145145+ cp.AddClassName(className, c[className])
146146+ }
147147+ case []KeyValue[string, bool]:
148148+ for _, kv := range c {
149149+ cp.AddClassName(kv.Key, kv.Value)
150150+ }
151151+ case KeyValue[string, bool]:
152152+ cp.AddClassName(c.Key, c.Value)
153153+ case []KeyValue[CSSClass, bool]:
154154+ for _, kv := range c {
155155+ cp.AddClassName(kv.Key.ClassName(), kv.Value)
156156+ }
157157+ case KeyValue[CSSClass, bool]:
158158+ cp.AddClassName(c.Key.ClassName(), c.Value)
159159+ case CSSClasses:
160160+ for _, item := range c {
161161+ cp.Add(item)
162162+ }
163163+ case []CSSClass:
164164+ for _, item := range c {
165165+ cp.Add(item)
166166+ }
167167+ case func() CSSClass:
168168+ cp.AddClassName(c().ClassName(), true)
169169+ default:
170170+ cp.AddClassName(unknownTypeClassName, true)
171171+ }
172172+}
173173+174174+func (cp *cssProcessor) AddClassName(className string, enabled bool) {
175175+ cp.classNameToEnabled[className] = enabled
176176+ cp.orderedNames = append(cp.orderedNames, className)
177177+}
178178+179179+func (cp *cssProcessor) String() string {
180180+ // Order the outputs according to how they were input, and remove disabled names.
181181+ rendered := make(map[string]any, len(cp.classNameToEnabled))
182182+ var names []string
183183+ for _, name := range cp.orderedNames {
184184+ if enabled := cp.classNameToEnabled[name]; !enabled {
185185+ continue
186186+ }
187187+ if _, hasBeenRendered := rendered[name]; hasBeenRendered {
188188+ continue
189189+ }
190190+ names = append(names, name)
191191+ rendered[name] = struct{}{}
192192+ }
193193+194194+ return strings.Join(names, " ")
195195+}
196196+197197+// KeyValue is a key and value pair.
198198+type KeyValue[TKey comparable, TValue any] struct {
199199+ Key TKey `json:"name"`
200200+ Value TValue `json:"value"`
201201+}
202202+203203+// KV creates a new key/value pair from the input key and value.
204204+func KV[TKey comparable, TValue any](key TKey, value TValue) KeyValue[TKey, TValue] {
205205+ return KeyValue[TKey, TValue]{
206206+ Key: key,
207207+ Value: value,
208208+ }
209209+}
210210+211211+const unknownTypeClassName = "--templ-css-class-unknown-type"
212212+213213+// Class returns a CSS class name.
214214+// Deprecated: use a string instead.
215215+func Class(name string) CSSClass {
216216+ return SafeClass(name)
217217+}
218218+219219+// SafeClass bypasses CSS class name validation.
220220+// Deprecated: use a string instead.
221221+func SafeClass(name string) CSSClass {
222222+ return ConstantCSSClass(name)
223223+}
224224+225225+// CSSClass provides a class name.
226226+type CSSClass interface {
227227+ ClassName() string
228228+}
229229+230230+// ConstantCSSClass is a string constant of a CSS class name.
231231+// Deprecated: use a string instead.
232232+type ConstantCSSClass string
233233+234234+// ClassName of the CSS class.
235235+func (css ConstantCSSClass) ClassName() string {
236236+ return string(css)
237237+}
238238+239239+// ComponentCSSClass is a templ.CSS
240240+type ComponentCSSClass struct {
241241+ // ID of the class, will be autogenerated.
242242+ ID string
243243+ // Definition of the CSS.
244244+ Class SafeCSS
245245+}
246246+247247+// ClassName of the CSS class.
248248+func (css ComponentCSSClass) ClassName() string {
249249+ return css.ID
250250+}
251251+252252+// CSSID calculates an ID.
253253+func CSSID(name string, css string) string {
254254+ sum := sha256.Sum256([]byte(css))
255255+ hs := hex.EncodeToString(sum[:])[0:8] // NOTE: See issue #978. Minimum recommended hs length is 6.
256256+ // Benchmarking showed this was fastest, and with fewest allocations (1).
257257+ // Using strings.Builder (2 allocs).
258258+ // Using fmt.Sprintf (3 allocs).
259259+ return name + "_" + hs
260260+}
261261+262262+// NewCSSMiddleware creates HTTP middleware that renders a global stylesheet of ComponentCSSClass
263263+// CSS if the request path matches, or updates the HTTP context to ensure that any handlers that
264264+// use templ.Components skip rendering <style> elements for classes that are included in the global
265265+// stylesheet. By default, the stylesheet path is /styles/templ.css
266266+func NewCSSMiddleware(next http.Handler, classes ...CSSClass) CSSMiddleware {
267267+ return CSSMiddleware{
268268+ Path: "/styles/templ.css",
269269+ CSSHandler: NewCSSHandler(classes...),
270270+ Next: next,
271271+ }
272272+}
273273+274274+// CSSMiddleware renders a global stylesheet.
275275+type CSSMiddleware struct {
276276+ Path string
277277+ CSSHandler CSSHandler
278278+ Next http.Handler
279279+}
280280+281281+func (cssm CSSMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
282282+ if r.URL.Path == cssm.Path {
283283+ cssm.CSSHandler.ServeHTTP(w, r)
284284+ return
285285+ }
286286+ // Add registered classes to the context.
287287+ ctx, v := getContext(r.Context())
288288+ for _, c := range cssm.CSSHandler.Classes {
289289+ v.addClass(c.ID)
290290+ }
291291+ // Serve the request. Templ components will use the updated context
292292+ // to know to skip rendering <style> elements for any component CSS
293293+ // classes that have been included in the global stylesheet.
294294+ cssm.Next.ServeHTTP(w, r.WithContext(ctx))
295295+}
296296+297297+// NewCSSHandler creates a handler that serves a stylesheet containing the CSS of the
298298+// classes passed in. This is used by the CSSMiddleware to provide global stylesheets
299299+// for templ components.
300300+func NewCSSHandler(classes ...CSSClass) CSSHandler {
301301+ ccssc := make([]ComponentCSSClass, 0, len(classes))
302302+ for _, c := range classes {
303303+ ccss, ok := c.(ComponentCSSClass)
304304+ if !ok {
305305+ continue
306306+ }
307307+ ccssc = append(ccssc, ccss)
308308+ }
309309+ return CSSHandler{
310310+ Classes: ccssc,
311311+ }
312312+}
313313+314314+// CSSHandler is a HTTP handler that serves CSS.
315315+type CSSHandler struct {
316316+ Logger func(err error)
317317+ Classes []ComponentCSSClass
318318+}
319319+320320+func (cssh CSSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
321321+ w.Header().Set("Content-Type", "text/css")
322322+ for _, c := range cssh.Classes {
323323+ _, err := w.Write([]byte(c.Class))
324324+ if err != nil && cssh.Logger != nil {
325325+ cssh.Logger(err)
326326+ }
327327+ }
328328+}
329329+330330+// RenderCSSItems renders the CSS to the writer, if the items haven't already been rendered.
331331+func RenderCSSItems(ctx context.Context, w io.Writer, classes ...any) (err error) {
332332+ if len(classes) == 0 {
333333+ return nil
334334+ }
335335+ _, v := getContext(ctx)
336336+ sb := new(strings.Builder)
337337+ renderCSSItemsToBuilder(sb, v, classes...)
338338+ if sb.Len() == 0 {
339339+ return nil
340340+ }
341341+ if _, err = io.WriteString(w, `<style type="text/css"`); err != nil {
342342+ return err
343343+ }
344344+ if v.nonce != "" {
345345+ if err = writeStrings(w, ` nonce="`, EscapeString(v.nonce), `"`); err != nil {
346346+ return err
347347+ }
348348+ }
349349+ return writeStrings(w, `>`, sb.String(), `</style>`)
350350+}
351351+352352+func renderCSSItemsToBuilder(sb *strings.Builder, v *contextValue, classes ...any) {
353353+ for _, c := range classes {
354354+ switch ccc := c.(type) {
355355+ case ComponentCSSClass:
356356+ if !v.hasClassBeenRendered(ccc.ID) {
357357+ sb.WriteString(string(ccc.Class))
358358+ v.addClass(ccc.ID)
359359+ }
360360+ case KeyValue[ComponentCSSClass, bool]:
361361+ if !ccc.Value {
362362+ continue
363363+ }
364364+ renderCSSItemsToBuilder(sb, v, ccc.Key)
365365+ case KeyValue[CSSClass, bool]:
366366+ if !ccc.Value {
367367+ continue
368368+ }
369369+ renderCSSItemsToBuilder(sb, v, ccc.Key)
370370+ case CSSClasses:
371371+ renderCSSItemsToBuilder(sb, v, ccc...)
372372+ case []CSSClass:
373373+ for _, item := range ccc {
374374+ renderCSSItemsToBuilder(sb, v, item)
375375+ }
376376+ case func() CSSClass:
377377+ renderCSSItemsToBuilder(sb, v, ccc())
378378+ case []string:
379379+ // Skip. These are class names, not CSS classes.
380380+ case string:
381381+ // Skip. This is a class name, not a CSS class.
382382+ case ConstantCSSClass:
383383+ // Skip. This is a class name, not a CSS class.
384384+ case CSSClass:
385385+ // Skip. This is a class name, not a CSS class.
386386+ case map[string]bool:
387387+ // Skip. These are class names, not CSS classes.
388388+ case KeyValue[string, bool]:
389389+ // Skip. These are class names, not CSS classes.
390390+ case []KeyValue[string, bool]:
391391+ // Skip. These are class names, not CSS classes.
392392+ case KeyValue[ConstantCSSClass, bool]:
393393+ // Skip. These are class names, not CSS classes.
394394+ case []KeyValue[ConstantCSSClass, bool]:
395395+ // Skip. These are class names, not CSS classes.
396396+ }
397397+ }
398398+}
399399+400400+// SafeCSS is CSS that has been sanitized.
401401+type SafeCSS string
402402+403403+type SafeCSSProperty string
404404+405405+var safeCSSPropertyType = reflect.TypeOf(SafeCSSProperty(""))
406406+407407+// SanitizeCSS sanitizes CSS properties to ensure that they are safe.
408408+func SanitizeCSS[T ~string](property string, value T) SafeCSS {
409409+ if reflect.TypeOf(value) == safeCSSPropertyType {
410410+ return SafeCSS(safehtml.SanitizeCSSProperty(property) + ":" + string(value) + ";")
411411+ }
412412+ p, v := safehtml.SanitizeCSS(property, string(value))
413413+ return SafeCSS(p + ":" + v + ";")
414414+}
415415+416416+type Attributer interface {
417417+ Items() []KeyValue[string, any]
418418+}
419419+420420+// Attributes is an alias to map[string]any made for spread attributes.
421421+type Attributes map[string]any
422422+423423+var _ Attributer = Attributes{}
424424+425425+// Returns the items of the attributes map in key sorted order.
426426+func (a Attributes) Items() []KeyValue[string, any] {
427427+ var (
428428+ items = make([]KeyValue[string, any], len(a))
429429+ i int
430430+ )
431431+ for k, v := range a {
432432+ items[i] = KeyValue[string, any]{Key: k, Value: v}
433433+ i++
434434+ }
435435+ sort.Slice(items, func(i, j int) bool {
436436+ return items[i].Key < items[j].Key
437437+ })
438438+ return items
439439+}
440440+441441+// OrderedAttributes stores attributes in order of insertion.
442442+type OrderedAttributes []KeyValue[string, any]
443443+444444+var _ Attributer = OrderedAttributes{}
445445+446446+func (a OrderedAttributes) Items() []KeyValue[string, any] {
447447+ return a
448448+}
449449+450450+func writeStrings(w io.Writer, ss ...string) (err error) {
451451+ for _, s := range ss {
452452+ if _, err = io.WriteString(w, s); err != nil {
453453+ return err
454454+ }
455455+ }
456456+ return nil
457457+}
458458+459459+func RenderAttributes(ctx context.Context, w io.Writer, attributes Attributer) (err error) {
460460+ for _, item := range attributes.Items() {
461461+ key := item.Key
462462+ value := item.Value
463463+ switch value := value.(type) {
464464+ case string:
465465+ if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value), `"`); err != nil {
466466+ return err
467467+ }
468468+ case *string:
469469+ if value == nil {
470470+ continue
471471+ }
472472+ if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(*value), `"`); err != nil {
473473+ return err
474474+ }
475475+ case bool:
476476+ if !value {
477477+ continue
478478+ }
479479+ if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
480480+ return err
481481+ }
482482+ case *bool:
483483+ if value == nil || !*value {
484484+ continue
485485+ }
486486+ if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
487487+ return err
488488+ }
489489+ case int, int8, int16, int32, int64,
490490+ uint, uint8, uint16, uint32, uint64, uintptr,
491491+ float32, float64, complex64, complex128:
492492+ if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(fmt.Sprint(value)), `"`); err != nil {
493493+ return err
494494+ }
495495+ case *int, *int8, *int16, *int32, *int64,
496496+ *uint, *uint8, *uint16, *uint32, *uint64, *uintptr,
497497+ *float32, *float64, *complex64, *complex128:
498498+ value = ptrValue(value)
499499+ if value == nil {
500500+ continue
501501+ }
502502+ if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(fmt.Sprint(value)), `"`); err != nil {
503503+ return err
504504+ }
505505+ case KeyValue[string, bool]:
506506+ if !value.Value {
507507+ continue
508508+ }
509509+ if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value.Key), `"`); err != nil {
510510+ return err
511511+ }
512512+ case KeyValue[bool, bool]:
513513+ if !value.Value || !value.Key {
514514+ continue
515515+ }
516516+ if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
517517+ return err
518518+ }
519519+ case func() bool:
520520+ if !value() {
521521+ continue
522522+ }
523523+ if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
524524+ return err
525525+ }
526526+ }
527527+ }
528528+ return nil
529529+}
530530+531531+func ptrValue(v any) any {
532532+ if v == nil {
533533+ return nil
534534+ }
535535+ rv := reflect.ValueOf(v)
536536+ if rv.Kind() != reflect.Ptr {
537537+ return v
538538+ }
539539+ if rv.IsNil() {
540540+ return nil
541541+ }
542542+ return rv.Elem().Interface()
543543+}
544544+545545+// Context.
546546+547547+type contextKeyType int
548548+549549+const contextKey = contextKeyType(0)
550550+551551+type contextValue struct {
552552+ ss map[string]struct{}
553553+ onceHandles map[*OnceHandle]struct{}
554554+ children *Component
555555+ nonce string
556556+}
557557+558558+func (v *contextValue) setHasBeenRendered(h *OnceHandle) {
559559+ if v.onceHandles == nil {
560560+ v.onceHandles = map[*OnceHandle]struct{}{}
561561+ }
562562+ v.onceHandles[h] = struct{}{}
563563+}
564564+565565+func (v *contextValue) getHasBeenRendered(h *OnceHandle) (ok bool) {
566566+ if v.onceHandles == nil {
567567+ v.onceHandles = map[*OnceHandle]struct{}{}
568568+ }
569569+ _, ok = v.onceHandles[h]
570570+ return
571571+}
572572+573573+func (v *contextValue) addScript(s string) {
574574+ if v.ss == nil {
575575+ v.ss = map[string]struct{}{}
576576+ }
577577+ v.ss["script_"+s] = struct{}{}
578578+}
579579+580580+func (v *contextValue) hasScriptBeenRendered(s string) (ok bool) {
581581+ if v.ss == nil {
582582+ v.ss = map[string]struct{}{}
583583+ }
584584+ _, ok = v.ss["script_"+s]
585585+ return
586586+}
587587+588588+func (v *contextValue) addClass(s string) {
589589+ if v.ss == nil {
590590+ v.ss = map[string]struct{}{}
591591+ }
592592+ v.ss["class_"+s] = struct{}{}
593593+}
594594+595595+func (v *contextValue) hasClassBeenRendered(s string) (ok bool) {
596596+ if v.ss == nil {
597597+ v.ss = map[string]struct{}{}
598598+ }
599599+ _, ok = v.ss["class_"+s]
600600+ return
601601+}
602602+603603+// InitializeContext initializes context used to store internal state used during rendering.
604604+func InitializeContext(ctx context.Context) context.Context {
605605+ if _, ok := ctx.Value(contextKey).(*contextValue); ok {
606606+ return ctx
607607+ }
608608+ v := &contextValue{}
609609+ ctx = context.WithValue(ctx, contextKey, v)
610610+ return ctx
611611+}
612612+613613+func getContext(ctx context.Context) (context.Context, *contextValue) {
614614+ v, ok := ctx.Value(contextKey).(*contextValue)
615615+ if !ok {
616616+ ctx = InitializeContext(ctx)
617617+ v = ctx.Value(contextKey).(*contextValue)
618618+ }
619619+ return ctx, v
620620+}
621621+622622+var bufferPool = sync.Pool{
623623+ New: func() any {
624624+ return new(bytes.Buffer)
625625+ },
626626+}
627627+628628+func GetBuffer() *bytes.Buffer {
629629+ return bufferPool.Get().(*bytes.Buffer)
630630+}
631631+632632+func ReleaseBuffer(b *bytes.Buffer) {
633633+ b.Reset()
634634+ bufferPool.Put(b)
635635+}
636636+637637+type ints interface {
638638+ ~int | ~int8 | ~int16 | ~int32 | ~int64
639639+}
640640+641641+type uints interface {
642642+ ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
643643+}
644644+645645+type floats interface {
646646+ ~float32 | ~float64
647647+}
648648+649649+type complexNumbers interface {
650650+ ~complex64 | ~complex128
651651+}
652652+653653+type stringable interface {
654654+ ints | uints | floats | complexNumbers | ~string | ~bool
655655+}
656656+657657+// JoinStringErrs joins an optional list of errors.
658658+func JoinStringErrs[T stringable](s T, errs ...error) (string, error) {
659659+ return fmt.Sprint(s), errors.Join(errs...)
660660+}
661661+662662+// Error returned during template rendering.
663663+type Error struct {
664664+ Err error
665665+ // FileName of the template file.
666666+ FileName string
667667+ // Line index of the error.
668668+ Line int
669669+ // Col index of the error.
670670+ Col int
671671+}
672672+673673+func (e Error) Error() string {
674674+ if e.FileName == "" {
675675+ e.FileName = "templ"
676676+ }
677677+ return fmt.Sprintf("%s: error at line %d, col %d: %v", e.FileName, e.Line, e.Col, e.Err)
678678+}
679679+680680+func (e Error) Unwrap() error {
681681+ return e.Err
682682+}
683683+684684+// Raw renders the input HTML to the output without applying HTML escaping.
685685+//
686686+// Use of this component presents a security risk - the HTML should come from
687687+// a trusted source, because it will be included as-is in the output.
688688+func Raw[T ~string](html T, errs ...error) Component {
689689+ return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
690690+ if err = errors.Join(errs...); err != nil {
691691+ return err
692692+ }
693693+ _, err = io.WriteString(w, string(html))
694694+ return err
695695+ })
696696+}
697697+698698+// FromGoHTML creates a templ Component from a Go html/template template.
699699+func FromGoHTML(t *template.Template, data any) Component {
700700+ return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
701701+ return t.Execute(w, data)
702702+ })
703703+}
704704+705705+// ToGoHTML renders the component to a Go html/template template.HTML string.
706706+func ToGoHTML(ctx context.Context, c Component) (s template.HTML, err error) {
707707+ b := GetBuffer()
708708+ defer ReleaseBuffer(b)
709709+ if err = c.Render(ctx, b); err != nil {
710710+ return
711711+ }
712712+ s = template.HTML(b.String())
713713+ return
714714+}
+62
vendor/github.com/a-h/templ/runtime/buffer.go
···11+package runtime
22+33+import (
44+ "bufio"
55+ "io"
66+ "net/http"
77+)
88+99+// DefaultBufferSize is the default size of buffers. It is set to 4KB by default, which is the
1010+// same as the default buffer size of bufio.Writer.
1111+var DefaultBufferSize = 4 * 1024 // 4KB
1212+1313+// Buffer is a wrapper around bufio.Writer that enables flushing and closing of
1414+// the underlying writer.
1515+type Buffer struct {
1616+ Underlying io.Writer
1717+ b *bufio.Writer
1818+}
1919+2020+// Write the contents of p into the buffer.
2121+func (b *Buffer) Write(p []byte) (n int, err error) {
2222+ return b.b.Write(p)
2323+}
2424+2525+// Flush writes any buffered data to the underlying io.Writer and
2626+// calls the Flush method of the underlying http.Flusher if it implements it.
2727+func (b *Buffer) Flush() error {
2828+ if err := b.b.Flush(); err != nil {
2929+ return err
3030+ }
3131+ if f, ok := b.Underlying.(http.Flusher); ok {
3232+ f.Flush()
3333+ }
3434+ return nil
3535+}
3636+3737+// Close closes the buffer and the underlying io.Writer if it implements io.Closer.
3838+func (b *Buffer) Close() error {
3939+ if c, ok := b.Underlying.(io.Closer); ok {
4040+ return c.Close()
4141+ }
4242+ return nil
4343+}
4444+4545+// Reset sets the underlying io.Writer to w and resets the buffer.
4646+func (b *Buffer) Reset(w io.Writer) {
4747+ if b.b == nil {
4848+ b.b = bufio.NewWriterSize(b, DefaultBufferSize)
4949+ }
5050+ b.Underlying = w
5151+ b.b.Reset(w)
5252+}
5353+5454+// Size returns the size of the underlying buffer in bytes.
5555+func (b *Buffer) Size() int {
5656+ return b.b.Size()
5757+}
5858+5959+// WriteString writes the contents of s into the buffer.
6060+func (b *Buffer) WriteString(s string) (n int, err error) {
6161+ return b.b.WriteString(s)
6262+}
+38
vendor/github.com/a-h/templ/runtime/bufferpool.go
···11+package runtime
22+33+import (
44+ "io"
55+ "sync"
66+)
77+88+var bufferPool = sync.Pool{
99+ New: func() any {
1010+ return new(Buffer)
1111+ },
1212+}
1313+1414+// GetBuffer creates and returns a new buffer if the writer is not already a buffer,
1515+// or returns the existing buffer if it is.
1616+func GetBuffer(w io.Writer) (b *Buffer, existing bool) {
1717+ if w == nil {
1818+ return nil, false
1919+ }
2020+ b, ok := w.(*Buffer)
2121+ if ok {
2222+ return b, true
2323+ }
2424+ b = bufferPool.Get().(*Buffer)
2525+ b.Reset(w)
2626+ return b, false
2727+}
2828+2929+// ReleaseBuffer flushes the buffer and returns it to the pool.
3030+func ReleaseBuffer(w io.Writer) (err error) {
3131+ b, ok := w.(*Buffer)
3232+ if !ok {
3333+ return nil
3434+ }
3535+ err = b.Flush()
3636+ bufferPool.Put(b)
3737+ return err
3838+}
···11+package runtime
22+33+import (
44+ "errors"
55+ "fmt"
66+ "html"
77+ "maps"
88+ "reflect"
99+ "slices"
1010+ "strings"
1111+1212+ "github.com/a-h/templ"
1313+ "github.com/a-h/templ/safehtml"
1414+)
1515+1616+// SanitizeStyleAttributeValues renders a style attribute value.
1717+// The supported types are:
1818+// - string
1919+// - templ.SafeCSS
2020+// - map[string]string
2121+// - map[string]templ.SafeCSSProperty
2222+// - 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.
2323+// - 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.
2424+// - templ.KeyValue[string, bool] - The bool determines whether the value should be included.
2525+// - templ.KeyValue[templ.SafeCSS, bool] - The bool determines whether the value should be included.
2626+// - func() (anyOfTheAboveTypes)
2727+// - func() (anyOfTheAboveTypes, error)
2828+// - []anyOfTheAboveTypes
2929+//
3030+// 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.
3131+// All other types are sanitized before rendering.
3232+//
3333+// If an error is returned by any function, or a non-nil error is included in the input, the error is returned.
3434+func SanitizeStyleAttributeValues(values ...any) (string, error) {
3535+ if err := getJoinedErrorsFromValues(values...); err != nil {
3636+ return "", err
3737+ }
3838+ sb := new(strings.Builder)
3939+ for _, v := range values {
4040+ if v == nil {
4141+ continue
4242+ }
4343+ if err := sanitizeStyleAttributeValue(sb, v); err != nil {
4444+ return "", err
4545+ }
4646+ }
4747+ return sb.String(), nil
4848+}
4949+5050+func sanitizeStyleAttributeValue(sb *strings.Builder, v any) error {
5151+ // Process concrete types.
5252+ switch v := v.(type) {
5353+ case string:
5454+ return processString(sb, v)
5555+5656+ case templ.SafeCSS:
5757+ return processSafeCSS(sb, v)
5858+5959+ case map[string]string:
6060+ return processStringMap(sb, v)
6161+6262+ case map[string]templ.SafeCSSProperty:
6363+ return processSafeCSSPropertyMap(sb, v)
6464+6565+ case templ.KeyValue[string, string]:
6666+ return processStringKV(sb, v)
6767+6868+ case templ.KeyValue[string, bool]:
6969+ if v.Value {
7070+ return processString(sb, v.Key)
7171+ }
7272+ return nil
7373+7474+ case templ.KeyValue[templ.SafeCSS, bool]:
7575+ if v.Value {
7676+ return processSafeCSS(sb, v.Key)
7777+ }
7878+ return nil
7979+ }
8080+8181+ // Fall back to reflection.
8282+8383+ // Handle functions first using reflection.
8484+ if handled, err := handleFuncWithReflection(sb, v); handled {
8585+ return err
8686+ }
8787+8888+ // Handle slices using reflection before concrete types.
8989+ if handled, err := handleSliceWithReflection(sb, v); handled {
9090+ return err
9191+ }
9292+9393+ _, err := sb.WriteString(TemplUnsupportedStyleAttributeValue)
9494+ return err
9595+}
9696+9797+func processSafeCSS(sb *strings.Builder, v templ.SafeCSS) error {
9898+ if v == "" {
9999+ return nil
100100+ }
101101+ sb.WriteString(html.EscapeString(string(v)))
102102+ if !strings.HasSuffix(string(v), ";") {
103103+ sb.WriteRune(';')
104104+ }
105105+ return nil
106106+}
107107+108108+func processString(sb *strings.Builder, v string) error {
109109+ if v == "" {
110110+ return nil
111111+ }
112112+ sanitized := strings.TrimSpace(safehtml.SanitizeStyleValue(v))
113113+ sb.WriteString(html.EscapeString(sanitized))
114114+ if !strings.HasSuffix(sanitized, ";") {
115115+ sb.WriteRune(';')
116116+ }
117117+ return nil
118118+}
119119+120120+var ErrInvalidStyleAttributeFunctionSignature = errors.New("invalid function signature, should be in the form func() (string, error)")
121121+122122+// handleFuncWithReflection handles functions using reflection.
123123+func handleFuncWithReflection(sb *strings.Builder, v any) (bool, error) {
124124+ rv := reflect.ValueOf(v)
125125+ if rv.Kind() != reflect.Func {
126126+ return false, nil
127127+ }
128128+129129+ t := rv.Type()
130130+ if t.NumIn() != 0 || (t.NumOut() != 1 && t.NumOut() != 2) {
131131+ return false, ErrInvalidStyleAttributeFunctionSignature
132132+ }
133133+134134+ // Check the types of the return values
135135+ if t.NumOut() == 2 {
136136+ // Ensure the second return value is of type `error`
137137+ secondReturnType := t.Out(1)
138138+ if !secondReturnType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
139139+ return false, fmt.Errorf("second return value must be of type error, got %v", secondReturnType)
140140+ }
141141+ }
142142+143143+ results := rv.Call(nil)
144144+145145+ if t.NumOut() == 2 {
146146+ // Check if the second return value is an error
147147+ if errVal := results[1].Interface(); errVal != nil {
148148+ if err, ok := errVal.(error); ok && err != nil {
149149+ return true, err
150150+ }
151151+ }
152152+ }
153153+154154+ return true, sanitizeStyleAttributeValue(sb, results[0].Interface())
155155+}
156156+157157+// handleSliceWithReflection handles slices using reflection.
158158+func handleSliceWithReflection(sb *strings.Builder, v any) (bool, error) {
159159+ rv := reflect.ValueOf(v)
160160+ if rv.Kind() != reflect.Slice {
161161+ return false, nil
162162+ }
163163+ for i := range rv.Len() {
164164+ elem := rv.Index(i).Interface()
165165+ if err := sanitizeStyleAttributeValue(sb, elem); err != nil {
166166+ return true, err
167167+ }
168168+ }
169169+ return true, nil
170170+}
171171+172172+// processStringMap processes a map[string]string.
173173+func processStringMap(sb *strings.Builder, m map[string]string) error {
174174+ for _, name := range slices.Sorted(maps.Keys(m)) {
175175+ name, value := safehtml.SanitizeCSS(name, m[name])
176176+ sb.WriteString(html.EscapeString(name))
177177+ sb.WriteRune(':')
178178+ sb.WriteString(html.EscapeString(value))
179179+ sb.WriteRune(';')
180180+ }
181181+ return nil
182182+}
183183+184184+// processSafeCSSPropertyMap processes a map[string]templ.SafeCSSProperty.
185185+func processSafeCSSPropertyMap(sb *strings.Builder, m map[string]templ.SafeCSSProperty) error {
186186+ for _, name := range slices.Sorted(maps.Keys(m)) {
187187+ sb.WriteString(html.EscapeString(safehtml.SanitizeCSSProperty(name)))
188188+ sb.WriteRune(':')
189189+ sb.WriteString(html.EscapeString(string(m[name])))
190190+ sb.WriteRune(';')
191191+ }
192192+ return nil
193193+}
194194+195195+// processStringKV processes a templ.KeyValue[string, string].
196196+func processStringKV(sb *strings.Builder, kv templ.KeyValue[string, string]) error {
197197+ name, value := safehtml.SanitizeCSS(kv.Key, kv.Value)
198198+ sb.WriteString(html.EscapeString(name))
199199+ sb.WriteRune(':')
200200+ sb.WriteString(html.EscapeString(value))
201201+ sb.WriteRune(';')
202202+ return nil
203203+}
204204+205205+// getJoinedErrorsFromValues collects and joins errors from the input values.
206206+func getJoinedErrorsFromValues(values ...any) error {
207207+ var errs []error
208208+ for _, v := range values {
209209+ if err, ok := v.(error); ok {
210210+ errs = append(errs, err)
211211+ }
212212+ }
213213+ return errors.Join(errs...)
214214+}
215215+216216+// TemplUnsupportedStyleAttributeValue is the default value returned for unsupported types.
217217+var TemplUnsupportedStyleAttributeValue = "zTemplUnsupportedStyleAttributeValue:Invalid;"
+181
vendor/github.com/a-h/templ/runtime/watchmode.go
···11+package runtime
22+33+import (
44+ "crypto/sha256"
55+ "encoding/hex"
66+ "errors"
77+ "fmt"
88+ "io"
99+ "os"
1010+ "path/filepath"
1111+ "runtime"
1212+ "strconv"
1313+ "strings"
1414+ "sync"
1515+ "time"
1616+)
1717+1818+var developmentMode = os.Getenv("TEMPL_DEV_MODE") == "true"
1919+2020+var stringLoaderOnce = sync.OnceValue(func() *StringLoader {
2121+ return NewStringLoader(os.Getenv("TEMPL_DEV_MODE_WATCH_ROOT"))
2222+})
2323+2424+// WriteString writes the string to the writer. If development mode is enabled
2525+// s is replaced with the string at the index in the _templ.txt file.
2626+func WriteString(w io.Writer, index int, s string) (err error) {
2727+ if developmentMode {
2828+ _, path, _, _ := runtime.Caller(1)
2929+ if !strings.HasSuffix(path, "_templ.go") {
3030+ return errors.New("templ: attempt to use WriteString from a non templ file")
3131+ }
3232+ s, err = stringLoaderOnce().GetWatchedString(path, index, s)
3333+ if err != nil {
3434+ return fmt.Errorf("templ: failed to get watched string: %w", err)
3535+ }
3636+ }
3737+ _, err = io.WriteString(w, s)
3838+ return err
3939+}
4040+4141+func GetDevModeTextFileName(templFileName string) string {
4242+ if prefix, ok := strings.CutSuffix(templFileName, "_templ.go"); ok {
4343+ templFileName = prefix + ".templ"
4444+ }
4545+ absFileName, err := filepath.Abs(templFileName)
4646+ if err != nil {
4747+ absFileName = templFileName
4848+ }
4949+ absFileName, err = filepath.EvalSymlinks(absFileName)
5050+ if err != nil {
5151+ absFileName = templFileName
5252+ }
5353+ absFileName = normalizePath(absFileName)
5454+5555+ hashedFileName := sha256.Sum256([]byte(absFileName))
5656+ outputFileName := fmt.Sprintf("templ_%s.txt", hex.EncodeToString(hashedFileName[:]))
5757+5858+ root := os.TempDir()
5959+ if os.Getenv("TEMPL_DEV_MODE_ROOT") != "" {
6060+ root = os.Getenv("TEMPL_DEV_MODE_ROOT")
6161+ }
6262+6363+ return filepath.Join(root, outputFileName)
6464+}
6565+6666+// normalizePath converts Windows paths to Unix style paths.
6767+func normalizePath(p string) string {
6868+ p = strings.ReplaceAll(filepath.Clean(p), `\`, `/`)
6969+ parts := strings.SplitN(p, ":", 2)
7070+ if len(parts) == 2 && len(parts[0]) == 1 {
7171+ drive := strings.ToLower(parts[0])
7272+ p = "/" + drive + parts[1]
7373+ }
7474+ return p
7575+}
7676+7777+type watchState struct {
7878+ modTime time.Time
7979+ strings []string
8080+}
8181+8282+type StringLoader struct {
8383+ watchModeRoot string
8484+ watchModeRootErr error
8585+ cache map[string]watchState
8686+ cacheMutex sync.Mutex
8787+}
8888+8989+func NewStringLoader(devModeWatchRootPath string) (sl *StringLoader) {
9090+ sl = &StringLoader{
9191+ cache: make(map[string]watchState),
9292+ }
9393+ if devModeWatchRootPath == "" {
9494+ return sl
9595+ }
9696+ resolvedRoot, err := filepath.EvalSymlinks(devModeWatchRootPath)
9797+ if err != nil {
9898+ sl.watchModeRootErr = fmt.Errorf("templ: failed to eval symlinks for watch mode root %q: %w", devModeWatchRootPath, err)
9999+ return sl
100100+ }
101101+ sl.watchModeRoot = resolvedRoot
102102+ return sl
103103+}
104104+105105+func (sl *StringLoader) GetWatchedString(templFilePath string, index int, defaultValue string) (string, error) {
106106+ if sl.watchModeRootErr != nil {
107107+ return "", sl.watchModeRootErr
108108+ }
109109+ path, err := filepath.EvalSymlinks(templFilePath)
110110+ if err != nil {
111111+ return "", fmt.Errorf("templ: failed to eval symlinks for %q: %w", path, err)
112112+ }
113113+ // If the file is outside the watch mode root, write the string directly.
114114+ // If watch mode root is not set, fall back to the previous behaviour to avoid breaking existing setups.
115115+ if sl.watchModeRoot != "" && !strings.HasPrefix(path, sl.watchModeRoot) {
116116+ return defaultValue, nil
117117+ }
118118+119119+ txtFilePath := GetDevModeTextFileName(path)
120120+ literals, err := sl.getWatchedStrings(txtFilePath)
121121+ if err != nil {
122122+ return "", fmt.Errorf("templ: failed to get watched strings for %q: %w", path, err)
123123+ }
124124+ if index > len(literals) {
125125+ return "", fmt.Errorf("templ: failed to find line %d in %s", index, txtFilePath)
126126+ }
127127+ return strconv.Unquote(`"` + literals[index-1] + `"`)
128128+}
129129+130130+func (sl *StringLoader) getWatchedStrings(txtFilePath string) ([]string, error) {
131131+ sl.cacheMutex.Lock()
132132+ defer sl.cacheMutex.Unlock()
133133+134134+ state, cached := sl.cache[txtFilePath]
135135+ if !cached {
136136+ return sl.cacheStrings(txtFilePath)
137137+ }
138138+139139+ if time.Since(state.modTime) < time.Millisecond*100 {
140140+ return state.strings, nil
141141+ }
142142+143143+ info, err := os.Stat(txtFilePath)
144144+ if err != nil {
145145+ return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
146146+ }
147147+148148+ if !info.ModTime().After(state.modTime) {
149149+ return state.strings, nil
150150+ }
151151+152152+ return sl.cacheStrings(txtFilePath)
153153+}
154154+155155+func (sl *StringLoader) cacheStrings(txtFilePath string) ([]string, error) {
156156+ txtFile, err := os.Open(txtFilePath)
157157+ if err != nil {
158158+ return nil, fmt.Errorf("templ: failed to open %s: %w", txtFilePath, err)
159159+ }
160160+ defer func() {
161161+ _ = txtFile.Close()
162162+ }()
163163+164164+ info, err := txtFile.Stat()
165165+ if err != nil {
166166+ return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
167167+ }
168168+169169+ all, err := io.ReadAll(txtFile)
170170+ if err != nil {
171171+ return nil, fmt.Errorf("templ: failed to read %s: %w", txtFilePath, err)
172172+ }
173173+174174+ literals := strings.Split(string(all), "\n")
175175+ sl.cache[txtFilePath] = watchState{
176176+ modTime: info.ModTime(),
177177+ strings: literals,
178178+ }
179179+180180+ return literals, nil
181181+}
+199
vendor/github.com/a-h/templ/safehtml/style.go
···11+// Adapted from https://raw.githubusercontent.com/google/safehtml/3c4cd5b5d8c9a6c5882fba099979e9f50b65c876/style.go
22+33+// Copyright (c) 2017 The Go Authors. All rights reserved.
44+//
55+// Use of this source code is governed by a BSD-style
66+// license that can be found in the LICENSE file or at
77+// https://developers.google.com/open-source/licenses/bsd
88+99+package safehtml
1010+1111+import (
1212+ "bytes"
1313+ "fmt"
1414+ "net/url"
1515+ "regexp"
1616+ "strings"
1717+)
1818+1919+// SanitizeCSS attempts to sanitize CSS properties.
2020+func SanitizeCSS(property, value string) (string, string) {
2121+ property = SanitizeCSSProperty(property)
2222+ if property == InnocuousPropertyName {
2323+ return InnocuousPropertyName, InnocuousPropertyValue
2424+ }
2525+ return property, SanitizeCSSValue(property, value)
2626+}
2727+2828+func SanitizeCSSValue(property, value string) string {
2929+ if sanitizer, ok := cssPropertyNameToValueSanitizer[property]; ok {
3030+ return sanitizer(value)
3131+ }
3232+ return sanitizeRegular(value)
3333+}
3434+3535+func SanitizeCSSProperty(property string) string {
3636+ if !identifierPattern.MatchString(property) {
3737+ return InnocuousPropertyName
3838+ }
3939+ return strings.ToLower(property)
4040+}
4141+4242+// identifierPattern matches a subset of valid <ident-token> values defined in
4343+// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram. This pattern matches all generic family name
4444+// keywords defined in https://drafts.csswg.org/css-fonts-3/#family-name-value.
4545+var identifierPattern = regexp.MustCompile(`^[-a-zA-Z]+$`)
4646+4747+var cssPropertyNameToValueSanitizer = map[string]func(string) string{
4848+ "background-image": sanitizeBackgroundImage,
4949+ "font-family": sanitizeFontFamily,
5050+ "display": sanitizeEnum,
5151+ "background-color": sanitizeRegular,
5252+ "background-position": sanitizeRegular,
5353+ "background-repeat": sanitizeRegular,
5454+ "background-size": sanitizeRegular,
5555+ "color": sanitizeRegular,
5656+ "height": sanitizeRegular,
5757+ "width": sanitizeRegular,
5858+ "left": sanitizeRegular,
5959+ "right": sanitizeRegular,
6060+ "top": sanitizeRegular,
6161+ "bottom": sanitizeRegular,
6262+ "font-weight": sanitizeRegular,
6363+ "padding": sanitizeRegular,
6464+ "z-index": sanitizeRegular,
6565+}
6666+6767+var validURLPrefixes = []string{
6868+ `url("`,
6969+ `url('`,
7070+ `url(`,
7171+}
7272+7373+var validURLSuffixes = []string{
7474+ `")`,
7575+ `')`,
7676+ `)`,
7777+}
7878+7979+func sanitizeBackgroundImage(v string) string {
8080+ // Check for <> as per https://github.com/google/safehtml/blob/be23134998433fcf0135dda53593fc8f8bf4df7c/style.go#L87C2-L89C3
8181+ if strings.ContainsAny(v, "<>") {
8282+ return InnocuousPropertyValue
8383+ }
8484+ for _, u := range strings.Split(v, ",") {
8585+ u = strings.TrimSpace(u)
8686+ var found bool
8787+ for i, prefix := range validURLPrefixes {
8888+ if strings.HasPrefix(u, prefix) && strings.HasSuffix(u, validURLSuffixes[i]) {
8989+ found = true
9090+ u = strings.TrimPrefix(u, validURLPrefixes[i])
9191+ u = strings.TrimSuffix(u, validURLSuffixes[i])
9292+ break
9393+ }
9494+ }
9595+ if !found || !urlIsSafe(u) {
9696+ return InnocuousPropertyValue
9797+ }
9898+ }
9999+ return v
100100+}
101101+102102+func urlIsSafe(s string) bool {
103103+ u, err := url.Parse(s)
104104+ if err != nil {
105105+ return false
106106+ }
107107+ if u.IsAbs() {
108108+ if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") || strings.EqualFold(u.Scheme, "mailto") {
109109+ return true
110110+ }
111111+ return false
112112+ }
113113+ return true
114114+}
115115+116116+var genericFontFamilyName = regexp.MustCompile(`^[a-zA-Z][- a-zA-Z]+$`)
117117+118118+func sanitizeFontFamily(s string) string {
119119+ for _, f := range strings.Split(s, ",") {
120120+ f = strings.TrimSpace(f)
121121+ if strings.HasPrefix(f, `"`) {
122122+ if !strings.HasSuffix(f, `"`) {
123123+ return InnocuousPropertyValue
124124+ }
125125+ continue
126126+ }
127127+ if !genericFontFamilyName.MatchString(f) {
128128+ return InnocuousPropertyValue
129129+ }
130130+ }
131131+ return s
132132+}
133133+134134+func sanitizeEnum(s string) string {
135135+ if !safeEnumPropertyValuePattern.MatchString(s) {
136136+ return InnocuousPropertyValue
137137+ }
138138+ return s
139139+}
140140+141141+func sanitizeRegular(s string) string {
142142+ if !safeRegularPropertyValuePattern.MatchString(s) {
143143+ return InnocuousPropertyValue
144144+ }
145145+ return s
146146+}
147147+148148+// InnocuousPropertyName is an innocuous property generated by a sanitizer when its input is unsafe.
149149+const InnocuousPropertyName = "zTemplUnsafeCSSPropertyName"
150150+151151+// InnocuousPropertyValue is an innocuous property generated by a sanitizer when its input is unsafe.
152152+const InnocuousPropertyValue = "zTemplUnsafeCSSPropertyValue"
153153+154154+// safeRegularPropertyValuePattern matches strings that are safe to use as property values.
155155+// Specifically, it matches string where every '*' or '/' is followed by end-of-text or a safe rune
156156+// (i.e. alphanumerics or runes in the set [+-.!#%_ \t]). This regex ensures that the following
157157+// are disallowed:
158158+// - "/*" and "*/", which are CSS comment markers.
159159+// - "//", even though this is not a comment marker in the CSS specification. Disallowing
160160+// this string minimizes the chance that browser peculiarities or parsing bugs will allow
161161+// sanitization to be bypassed.
162162+// - '(' and ')', which can be used to call functions.
163163+// - ',', since it can be used to inject extra values into a property.
164164+// - Runes which could be matched on CSS error recovery of a previously malformed token, such as '@'
165165+// and ':'. See http://www.w3.org/TR/css3-syntax/#error-handling.
166166+var safeRegularPropertyValuePattern = regexp.MustCompile(`^(?:[*/]?(?:[0-9a-zA-Z+-.!#%_ \t]|$))*$`)
167167+168168+// safeEnumPropertyValuePattern matches strings that are safe to use as enumerated property values.
169169+// Specifically, it matches strings that contain only alphabetic and '-' runes.
170170+var safeEnumPropertyValuePattern = regexp.MustCompile(`^[a-zA-Z-]*$`)
171171+172172+// SanitizeStyleValue escapes s so that it is safe to put between "" to form a CSS <string-token>.
173173+// See syntax at https://www.w3.org/TR/css-syntax-3/#string-token-diagram.
174174+//
175175+// On top of the escape sequences required in <string-token>, this function also escapes
176176+// control runes to minimize the risk of these runes triggering browser-specific bugs.
177177+// Taken from cssEscapeString in safehtml package.
178178+func SanitizeStyleValue(s string) string {
179179+ var b bytes.Buffer
180180+ b.Grow(len(s))
181181+ for _, c := range s {
182182+ switch {
183183+ case c == '\u0000':
184184+ // Replace the NULL byte according to https://www.w3.org/TR/css-syntax-3/#input-preprocessing.
185185+ // We take this extra precaution in case the user agent fails to handle NULL properly.
186186+ b.WriteString("\uFFFD")
187187+ case c == '<', // Prevents breaking out of a style element with `</style>`. Escape this in case the Style user forgets to.
188188+ c == '"', c == '\\', // Must be CSS-escaped in <string-token>. U+000A line feed is handled in the next case.
189189+ c <= '\u001F', c == '\u007F', // C0 control codes
190190+ c >= '\u0080' && c <= '\u009F', // C1 control codes
191191+ c == '\u2028', c == '\u2029': // Unicode newline characters
192192+ // See CSS escape sequence syntax at https://www.w3.org/TR/css-syntax-3/#escape-diagram.
193193+ fmt.Fprintf(&b, "\\%06X", c)
194194+ default:
195195+ b.WriteRune(c)
196196+ }
197197+ }
198198+ return b.String()
199199+}
+151
vendor/github.com/a-h/templ/scripttemplate.go
···11+package templ
22+33+import (
44+ "context"
55+ "encoding/json"
66+ "fmt"
77+ "html"
88+ "io"
99+ "regexp"
1010+ "strings"
1111+)
1212+1313+// ComponentScript is a templ Script template.
1414+type ComponentScript struct {
1515+ // Name of the script, e.g. print.
1616+ Name string
1717+ // Function to render.
1818+ Function string
1919+ // Call of the function in JavaScript syntax, including parameters, and
2020+ // ensures parameters are HTML escaped; useful for injecting into HTML
2121+ // attributes like onclick, onhover, etc.
2222+ //
2323+ // Given:
2424+ // functionName("some string",12345)
2525+ // It would render:
2626+ // __templ_functionName_sha("some string",12345))
2727+ //
2828+ // This is can be injected into HTML attributes:
2929+ // <button onClick="__templ_functionName_sha("some string",12345))">Click Me</button>
3030+ Call string
3131+ // Call of the function in JavaScript syntax, including parameters. It
3232+ // does not HTML escape parameters; useful for directly calling in script
3333+ // elements.
3434+ //
3535+ // Given:
3636+ // functionName("some string",12345)
3737+ // It would render:
3838+ // __templ_functionName_sha("some string",12345))
3939+ //
4040+ // This is can be used to call the function inside a script tag:
4141+ // <script>__templ_functionName_sha("some string",12345))</script>
4242+ CallInline string
4343+}
4444+4545+var _ Component = ComponentScript{}
4646+4747+func writeScriptHeader(ctx context.Context, w io.Writer) (err error) {
4848+ var nonceAttr string
4949+ if nonce := GetNonce(ctx); nonce != "" {
5050+ nonceAttr = " nonce=\"" + EscapeString(nonce) + "\""
5151+ }
5252+ _, err = fmt.Fprintf(w, `<script%s>`, nonceAttr)
5353+ return err
5454+}
5555+5656+func (c ComponentScript) Render(ctx context.Context, w io.Writer) error {
5757+ err := RenderScriptItems(ctx, w, c)
5858+ if err != nil {
5959+ return err
6060+ }
6161+ if len(c.Call) > 0 {
6262+ if err = writeScriptHeader(ctx, w); err != nil {
6363+ return err
6464+ }
6565+ if _, err = io.WriteString(w, c.CallInline); err != nil {
6666+ return err
6767+ }
6868+ if _, err = io.WriteString(w, `</script>`); err != nil {
6969+ return err
7070+ }
7171+ }
7272+ return nil
7373+}
7474+7575+// RenderScriptItems renders a <script> element, if the script has not already been rendered.
7676+func RenderScriptItems(ctx context.Context, w io.Writer, scripts ...ComponentScript) (err error) {
7777+ if len(scripts) == 0 {
7878+ return nil
7979+ }
8080+ _, v := getContext(ctx)
8181+ sb := new(strings.Builder)
8282+ for _, s := range scripts {
8383+ if !v.hasScriptBeenRendered(s.Name) {
8484+ sb.WriteString(s.Function)
8585+ v.addScript(s.Name)
8686+ }
8787+ }
8888+ if sb.Len() > 0 {
8989+ if err = writeScriptHeader(ctx, w); err != nil {
9090+ return err
9191+ }
9292+ if _, err = io.WriteString(w, sb.String()); err != nil {
9393+ return err
9494+ }
9595+ if _, err = io.WriteString(w, `</script>`); err != nil {
9696+ return err
9797+ }
9898+ }
9999+ return nil
100100+}
101101+102102+// JSExpression represents a JavaScript expression intended for use as an argument for script templates.
103103+// The string value of JSExpression will be inserted directly as JavaScript code in function call arguments.
104104+type JSExpression string
105105+106106+// SafeScript encodes unknown parameters for safety for inside HTML attributes.
107107+func SafeScript(functionName string, params ...any) string {
108108+ if !jsFunctionName.MatchString(functionName) {
109109+ functionName = "__templ_invalid_js_function_name"
110110+ }
111111+ sb := new(strings.Builder)
112112+ sb.WriteString(html.EscapeString(functionName))
113113+ sb.WriteRune('(')
114114+ for i, p := range params {
115115+ sb.WriteString(EscapeString(jsonEncodeParam(p)))
116116+ if i < len(params)-1 {
117117+ sb.WriteRune(',')
118118+ }
119119+ }
120120+ sb.WriteRune(')')
121121+ return sb.String()
122122+}
123123+124124+// SafeScript encodes unknown parameters for safety for inline scripts.
125125+func SafeScriptInline(functionName string, params ...any) string {
126126+ if !jsFunctionName.MatchString(functionName) {
127127+ functionName = "__templ_invalid_js_function_name"
128128+ }
129129+ sb := new(strings.Builder)
130130+ sb.WriteString(functionName)
131131+ sb.WriteRune('(')
132132+ for i, p := range params {
133133+ sb.WriteString(jsonEncodeParam(p))
134134+ if i < len(params)-1 {
135135+ sb.WriteRune(',')
136136+ }
137137+ }
138138+ sb.WriteRune(')')
139139+ return sb.String()
140140+}
141141+142142+func jsonEncodeParam(param any) string {
143143+ if val, ok := param.(JSExpression); ok {
144144+ return string(val)
145145+ }
146146+ enc, _ := json.Marshal(param)
147147+ return string(enc)
148148+}
149149+150150+// isValidJSFunctionName returns true if the given string is a valid JavaScript function name, e.g. console.log, alert, etc.
151151+var jsFunctionName = regexp.MustCompile(`^([$_a-zA-Z][$_a-zA-Z0-9]+\.?)+$`)
vendor/github.com/a-h/templ/templ.png
This is a binary file and will not be displayed.
+31
vendor/github.com/a-h/templ/url.go
···11+package templ
22+33+import (
44+ "errors"
55+ "strings"
66+)
77+88+// FailedSanitizationURL is returned if a URL fails sanitization checks.
99+const FailedSanitizationURL = SafeURL("about:invalid#TemplFailedSanitizationURL")
1010+1111+// URL sanitizes the input string s and returns a SafeURL.
1212+func URL(s string) SafeURL {
1313+ if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
1414+ protocol := s[:i]
1515+ 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") {
1616+ return FailedSanitizationURL
1717+ }
1818+ }
1919+ return SafeURL(s)
2020+}
2121+2222+// SafeURL is a URL that has been sanitized.
2323+type SafeURL string
2424+2525+// JoinURLErrs joins an optional list of errors and returns a sanitized SafeURL.
2626+func JoinURLErrs[T ~string](s T, errs ...error) (SafeURL, error) {
2727+ if safeURL, ok := any(s).(SafeURL); ok {
2828+ return safeURL, errors.Join(errs...)
2929+ }
3030+ return URL(string(s)), errors.Join(errs...)
3131+}
+10
vendor/github.com/a-h/templ/version.go
···11+package templ
22+33+import _ "embed"
44+55+//go:embed .version
66+var version string
77+88+func Version() string {
99+ return "v" + version
1010+}
+105
vendor/github.com/a-h/templ/watchmode.go
···11+package templ
22+33+import (
44+ "errors"
55+ "fmt"
66+ "io"
77+ "os"
88+ "runtime"
99+ "strconv"
1010+ "strings"
1111+ "sync"
1212+ "time"
1313+)
1414+1515+// WriteWatchModeString is used when rendering templates in development mode.
1616+// the generator would have written non-go code to the _templ.txt file, which
1717+// is then read by this function and written to the output.
1818+//
1919+// Deprecated: since templ v0.3.x generated code uses WriteString.
2020+func WriteWatchModeString(w io.Writer, lineNum int) error {
2121+ _, path, _, _ := runtime.Caller(1)
2222+ if !strings.HasSuffix(path, "_templ.go") {
2323+ return errors.New("templ: WriteWatchModeString can only be called from _templ.go")
2424+ }
2525+ txtFilePath := strings.Replace(path, "_templ.go", "_templ.txt", 1)
2626+2727+ literals, err := getWatchedStrings(txtFilePath)
2828+ if err != nil {
2929+ return fmt.Errorf("templ: failed to cache strings: %w", err)
3030+ }
3131+3232+ if lineNum > len(literals) {
3333+ return fmt.Errorf("templ: failed to find line %d in %s", lineNum, txtFilePath)
3434+ }
3535+3636+ s, err := strconv.Unquote(`"` + literals[lineNum-1] + `"`)
3737+ if err != nil {
3838+ return err
3939+ }
4040+ _, err = io.WriteString(w, s)
4141+ return err
4242+}
4343+4444+var (
4545+ watchModeCache = map[string]watchState{}
4646+ watchStateMutex sync.Mutex
4747+)
4848+4949+type watchState struct {
5050+ modTime time.Time
5151+ strings []string
5252+}
5353+5454+func getWatchedStrings(txtFilePath string) ([]string, error) {
5555+ watchStateMutex.Lock()
5656+ defer watchStateMutex.Unlock()
5757+5858+ state, cached := watchModeCache[txtFilePath]
5959+ if !cached {
6060+ return cacheStrings(txtFilePath)
6161+ }
6262+6363+ if time.Since(state.modTime) < time.Millisecond*100 {
6464+ return state.strings, nil
6565+ }
6666+6767+ info, err := os.Stat(txtFilePath)
6868+ if err != nil {
6969+ return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
7070+ }
7171+7272+ if !info.ModTime().After(state.modTime) {
7373+ return state.strings, nil
7474+ }
7575+7676+ return cacheStrings(txtFilePath)
7777+}
7878+7979+func cacheStrings(txtFilePath string) ([]string, error) {
8080+ txtFile, err := os.Open(txtFilePath)
8181+ if err != nil {
8282+ return nil, fmt.Errorf("templ: failed to open %s: %w", txtFilePath, err)
8383+ }
8484+ defer func() {
8585+ _ = txtFile.Close()
8686+ }()
8787+8888+ info, err := txtFile.Stat()
8989+ if err != nil {
9090+ return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
9191+ }
9292+9393+ all, err := io.ReadAll(txtFile)
9494+ if err != nil {
9595+ return nil, fmt.Errorf("templ: failed to read %s: %w", txtFilePath, err)
9696+ }
9797+9898+ literals := strings.Split(string(all), "\n")
9999+ watchModeCache[txtFilePath] = watchState{
100100+ modTime: info.ModTime(),
101101+ strings: literals,
102102+ }
103103+104104+ return literals, nil
105105+}
+5
vendor/modules.txt
···11+# github.com/a-h/templ v0.3.1001
22+## explicit; go 1.23.0
33+github.com/a-h/templ
44+github.com/a-h/templ/runtime
55+github.com/a-h/templ/safehtml
16# github.com/beorn7/perks v1.0.1
27## explicit; go 1.11
38github.com/beorn7/perks/quantile