Use templ instead of Go template for HTML sources

Also add Nix.
This commit is contained in:
Hoang Nguyen 2023-11-20 00:00:00 +07:00
parent 1397196e4e
commit 2ace4b61bf
Signed by: folliehiyuki
GPG Key ID: B0567C20730E9B11
39 changed files with 2339 additions and 189 deletions

View File

@ -8,5 +8,5 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[{Makefile,*.go}]
[{*.templ,*.go}]
indent_style = tab

8
.gitignore vendored
View File

@ -1 +1,7 @@
/out
# Nix
/result
# Built artifacts
*_templ.go
/src/**/*.html
/out/

View File

@ -5,34 +5,43 @@ default:
- docker
- linux
workflow:
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
stages:
- build
- deploy
build:generate_html:
workflow:
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
SRC_DIR: out
build:site:
stage: build
image: golang:1.21-alpine
before_script:
- echo "https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
- apk add --no-cache minify templ
- mkdir "$SRC_DIR"
- cp -r src/* "$SRC_DIR"/
script:
- templ generate
- go run ./*.go -gen "$SRC_DIR"
- minify -r "$SRC_DIR" -o .
artifacts:
expire_in: 1 hour
paths:
- out/
image: golang:1.20-alpine
script:
- apk --no-cache add minify make
- make
deploy:cloudflare_pages:
stage: deploy
image:
name: node:20-alpine
name: node:21-alpine
entrypoint: ['']
script: |
npx wrangler pages deploy \
--project-name=folliehiyuki-cdn \
--branch="$CI_COMMIT_REF_NAME" \
out/
"$SRC_DIR"/
dependencies:
- build:generate_html
- build:site

39
.golangci.toml Normal file
View File

@ -0,0 +1,39 @@
[linters]
disable-all = true
enable = [
"errcheck",
"goconst",
"gofumpt",
"gosec",
"gosimple",
"govet",
"ineffassign",
"lll",
"misspell",
"nakedret",
"revive",
"staticcheck",
"typecheck",
"unconvert",
"unused"
]
[linters-settings]
[linters-settings.errcheck]
check-type-assertions = true
[linters-settings.gofumpt]
extra-rules = true
[linters-settings.govet]
check-shadowing = true
[issues]
fix = false
[output]
sort-results = true
[run]
skip-dirs-use-default = true
skip-files = [ ".*_templ\\.go$" ]
go = "1.20"
timeout = "15m"

20
LICENSE
View File

@ -1,7 +1,19 @@
Copyright © 2023 Hoang Nguyen
Copyright © 2023 Hoang Nguyen <folliekazetani@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,28 +0,0 @@
.DEFAULT_GOAL := bundle
OUTPUT_DIR ?= out
.PHONY: clean
clean:
@rm -rf $(OUTPUT_DIR)
.PHONY: gen
gen:
@mkdir -p $(OUTPUT_DIR)
@cp -rf src/* $(OUTPUT_DIR)
@find ./$(OUTPUT_DIR) -mindepth 1 -type d -print -exec go run ./tools/update_html.go -root=$(OUTPUT_DIR) {} \;
@go run ./tools/update_html.go -root=$(OUTPUT_DIR) -home=true "$(OUTPUT_DIR)"
.PHONY: minify
minify:
@minify -r ./$(OUTPUT_DIR) -o .
.PHONY: serve
serve: gen minify
@podman run --rm -it \
-p 8080:80 \
-v ./$(OUTPUT_DIR):/usr/share/nginx/html:ro \
docker.io/library/nginx:stable-alpine
.PHONY: bundle
bundle: clean gen minify

144
flake.lock Normal file
View File

@ -0,0 +1,144 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1694102001,
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1700204040,
"narHash": "sha256-xSVcS5HBYnD3LTer7Y2K8ZQCDCXMa3QUD1MzRjHzuhI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c757e9bd77b16ca2e03c89bf8bc9ecb28e0c06ad",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": "nixpkgs",
"templ-src": "templ-src"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"templ-src": {
"inputs": {
"gitignore": [
"gitignore"
],
"nixpkgs": [
"nixpkgs"
],
"xc": "xc"
},
"locked": {
"lastModified": 1700298481,
"narHash": "sha256-eFDvyf1EGfHpFQn7XLG9CRba4BleYRlTM64Oyty6rpA=",
"owner": "a-h",
"repo": "templ",
"rev": "6c411018048a7aa35a275caddc5c3c1222283293",
"type": "github"
},
"original": {
"owner": "a-h",
"repo": "templ",
"type": "github"
}
},
"xc": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"templ-src",
"nixpkgs"
]
},
"locked": {
"lastModified": 1696495449,
"narHash": "sha256-dthZiJ2FX/eIC0l1mdfefJZXDTVLfwp7L7Arq5rsCWA=",
"owner": "joerdav",
"repo": "xc",
"rev": "c8baab14d679fb276f11c576607010283be21220",
"type": "github"
},
"original": {
"owner": "joerdav",
"repo": "xc",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

103
flake.nix Normal file
View File

@ -0,0 +1,103 @@
{
description = "FollieHiyuki's stupid CDN";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
templ-src = {
url = "github:a-h/templ";
inputs.nixpkgs.follows = "nixpkgs";
inputs.gitignore.follows = "gitignore";
};
gitignore = {
url = "github:hercules-ci/gitignore.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, templ-src, flake-utils, gitignore, ... }:
flake-utils.lib.eachSystem [
"x86_64-linux"
"x86_64-darwin"
"aarch64-linux"
"aarch64-darwin"
]
(system:
let
inherit (nixpkgs) lib;
pkgs = nixpkgs.legacyPackages."${system}" // templ-src.packages."${system}";
buildInputs = with pkgs; [ go minify templ ];
shellHook = ''
export GOTOOLCHAIN=local
export CGO_ENABLED=0
'';
tasks = with pkgs; {
gen = {
runtimeInputs = [ templ ];
script = "templ generate";
};
preview = {
runtimeInputs = [ go ];
script = "go run ./*.go -preview result/";
};
publish = {
runtimeInputs = [ nodePackages.wrangler ];
# NOTE: need to login beforehand, or export CLOUDFLARE_API_TOKEN first
script = ''
wrangler pages deploy \
--project-name=folliehiyuki-cdn \
--branch=main \
result/
'';
};
};
in
{
apps = (lib.attrsets.mapAttrs
(k: v: flake-utils.lib.mkApp {
drv = pkgs.writeShellApplication {
name = k;
runtimeInputs = v.runtimeInputs;
text = v.script;
};
})
tasks);
packages = rec {
default = bundle;
bundle = with pkgs; stdenv.mkDerivation {
inherit buildInputs;
name = "cdn.folliehiyuki.com";
src = lib.cleanSource (gitignore.lib.gitignoreSource ./.);
configurePhase = ''
${shellHook}
export HOME="$TMPDIR"
export GOCACHE="$PWD/go-cache"
export GOTMPDIR="$PWD"
export GOMODCACHE="$PWD/go"
templ generate
'';
buildPhase = ''
go run *.go -gen src
minify -r ./src -o .
'';
installPhase = ''
mkdir -p "$out"
cp -r ./src/* "$out"/
'';
};
};
devShells.default = with pkgs; mkShell {
inherit shellHook;
nativeBuildInputs = buildInputs ++ [ nodePackages.wrangler ];
};
}
);
}

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module cdn
go 1.21.3
require (
github.com/a-h/templ v0.2.476
golang.org/x/sync v0.5.0
)

6
go.sum Normal file
View File

@ -0,0 +1,6 @@
github.com/a-h/templ v0.2.476 h1:+H4hP4CwK4kfJwXsE6kHeFWMGtcVOVoOm/I64uzARBk=
github.com/a-h/templ v0.2.476/go.mod h1:zQ95mSyadNTGHv6k5Fm+wQU8zkBMMbHCHg7eAvUZKNM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=

118
main.go Normal file
View File

@ -0,0 +1,118 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"time"
"golang.org/x/sync/errgroup"
)
var (
ctx = context.Background()
previewPort = "8080"
// Specify the starting directory, for consistency
_, f, _, _ = runtime.Caller(0)
)
// genListingPages generates listing pages recursively inside provided directory
func genListingPages(path string) error {
entries, err := os.ReadDir(path)
if err != nil {
return err
}
indexFile, err := os.Create(filepath.Join(path, "index.html"))
if err != nil {
return err
}
if err := listingPage(path, entries).Render(ctx, indexFile); err != nil {
return err
}
// Goroutines are cheap, so just spawn them needlessly \^-^/
errGroup := new(errgroup.Group)
for _, entry := range entries {
entry := entry
if entry.IsDir() {
errGroup.Go(func() error {
return genListingPages(filepath.Join(path, entry.Name()))
})
}
}
return errGroup.Wait()
}
// preview Previews the website at specified root directory on localhost:<port>
func preview(root, port string) error {
http.Handle("/", http.FileServer(
http.Dir(
filepath.Join(filepath.Dir(f), root),
),
))
fmt.Println("Serving the preview webpage at http://localhost:" + port)
srv := &http.Server{
Addr: ":" + port,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
return srv.ListenAndServe()
}
// generate generates index.html files for the root directory
func generate(root string) error {
// NOTE: it also generate one for the root directory. We'll override this file later
if err := genListingPages(root); err != nil {
return err
}
// Render the home page to override the written listing page at root
homePage, err := os.Create(filepath.Join(root, "index.html"))
if err != nil {
return err
}
return indexPage().Render(ctx, homePage)
}
func main() {
genFlag := flag.Bool("gen", false, "Generate index files")
previewFlag := flag.Bool("preview", false, "Preview the built website")
flag.Usage = func() {
fmt.Printf(`Usage: go run *.go [OPTIONS] DIR
Arguments:
DIR
Path to the directory containing the source files
Options:
`)
flag.PrintDefaults()
}
flag.Parse()
// The root directory argument is required
if len(flag.Args()) != 1 {
log.Fatal("[ERROR] exactly 1 argument DIR is required")
}
rootDir := flag.Arg(0)
if *genFlag {
if err := generate(rootDir); err != nil {
log.Fatalf("[ERROR] %v", err)
}
}
// Only preview after things are generated
if *previewFlag {
if err := preview(rootDir, previewPort); err != nil {
log.Fatalf("[ERROR] %v", err)
}
}
}

86
pages.templ Normal file
View File

@ -0,0 +1,86 @@
package main
import "io/fs"
var (
siteName = "FollieCDN"
siteURL = "https://cdn.folliehiyuki.com/"
)
templ pageTemplate(path string) {
<html lang="en-us">
<head>
<title>
if path != "" {
{ path + " | " }
}
{ siteName }
</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
if path != "" {
<meta name="description" content={ path + " | " + siteName }/>
} else {
<meta name="description" content={ siteName }/>
}
<link rel="icon" type="image/x-icon" href="/favicon.ico"/>
<link rel="canonical" href={ siteURL + path }/>
<link rel="stylesheet" href="/styles/normalize.css" integrity="sha256-Atknw9eu6T9FV6v//wFp8skKdJ5JcAqANQ1bPykR4go=" crossorigin="anonymous"/>
<link rel="stylesheet" href="/styles/self.css" integrity="sha256-KReR+W3bFJ+Eu2X+F0lR+bULlbmaLS8SXUhIQJ/p/p4=" crossorigin="anonymous"/>
<link rel="stylesheet" href="/fonts/iosevka/iosevka-aile.css" integrity="sha256-20HRMpRlRW2+dk9R7asoOl5/z8Xyc2BbjeLZsButUww=" crossorigin="anonymous"/>
<link rel="stylesheet" href="/fonts/font-awesome/solid.css" integrity="sha256-xuw8YIIudFiLKxnOSDufxt0N2CFAJQXsK2lnTUPbofE=" crossorigin="anonymous"/>
<link rel="preload" href="/fonts/iosevka/iosevka-aile-bold.woff2" as="font" type="font/woff2" integrity="sha256-Hu4sqJ4m0rjWqqKYxhGKw1Lqz/VpftqKe0CeHACkSto=" crossorigin="anonymous"/>
<link rel="preload" href="/fonts/iosevka/iosevka-aile-bolditalic.woff2" as="font" type="font/woff2" integrity="sha256-fRJP2kQFy+xRyveNgFHkYWfHUG/DSkoUrXdfx7RGJMk=" crossorigin="anonymous"/>
<link rel="preload" href="/fonts/iosevka/iosevka-aile-italic.woff2" as="font" type="font/woff2" integrity="sha256-gH5Tq4ZWU0MJJQjXsdC00d++xrM842dN8pCVRmBnf0k=" crossorigin="anonymous"/>
<link rel="preload" href="/fonts/iosevka/iosevka-aile-regular.woff2" as="font" type="font/woff2" integrity="sha256-y6fQ+gvBFZWjeNgxiWQrYYgni3T5H+TLAwHCsSFSjKE=" crossorigin="anonymous"/>
</head>
<body>
<main>
{ children... }
</main>
</body>
</html>
}
templ indexPage() {
@pageTemplate("") {
<h1>FollieHiyuki's personal web assets</h1>
<p>
Hi! Welcome to my <em>"Content Delivery Network"</em> <s>freeloading</s> running on Cloudflare. The files served here are used to power my websites, including my
<a href="https://www.folliehiyuki.com" target="_blank" rel="author noopener external">personal blog</a> and
<a href="https://docs.folliehiyuki.com" target="_blank" rel="noopener external">handbook</a>. The source code is avaliable to see on
<a href="https://gitlab.com/FollieHiyuki/cdn" target="_blank" rel="noreferrer nofollow external">GitLab</a>.
</p>
<h2>Packages</h2>
<p>
<span class="fa-solid">&#xf031</span> <a href="/fonts/">fonts</a> - Various 3rd-party fonts I use everywhere
<br/>
<span class="fa-solid">&#xf53f</span> <a href="/styles/">styles</a> - Some minimal, opinionated CSS settings
<br/>
</p>
}
}
templ listingPage(path string, entries []fs.DirEntry) {
@pageTemplate(path) {
<h1>{ path }</h1>
<p>
<span class="fa-solid">&#xf07b</span> <a href="../">{ "../" }</a>
<br/>
for _, entry := range entries {
// Obviously we need to discard the index file from the UI
if entry.Name() != "index.html" {
<span class="fa-solid">
if entry.IsDir() {
&#xf07b
} else {
&#xf15c&nbsp;
}
</span>
<a href={ templ.URL("./" + entry.Name()) }>{ entry.Name() }</a>
<br/>
}
}
</p>
}
}

View File

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>{{ if not .IsHomePage }}{{ .Path }} | {{ end }}FollieCDN</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{ if not .IsHomePage }}{{ .Path }} | {{ end }}FollieCDN">
<link rel="stylesheet" href="/css/normalize.css" integrity="sha256-Atknw9eu6T9FV6v//wFp8skKdJ5JcAqANQ1bPykR4go=" crossorigin="anonymous">
<link rel="stylesheet" href="/css/self.css" integrity="sha256-KReR+W3bFJ+Eu2X+F0lR+bULlbmaLS8SXUhIQJ/p/p4=" crossorigin="anonymous">
<link rel="stylesheet" href="/fonts/iosevka/iosevka-aile.css" integrity="sha256-20HRMpRlRW2+dk9R7asoOl5/z8Xyc2BbjeLZsButUww=" crossorigin="anonymous">
<link rel="stylesheet" href="/fonts/font-awesome/solid.css" integrity="sha256-xuw8YIIudFiLKxnOSDufxt0N2CFAJQXsK2lnTUPbofE=" crossorigin="anonymous">
<link rel="canonical" href="https://cdn.folliehiyuki.com{{ if not .IsHomePage }}/{{ .Path }}{{ end }}">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="preload" href="/fonts/iosevka/iosevka-aile-bold.woff2" as="font" type="font/woff2" integrity="sha256-Hu4sqJ4m0rjWqqKYxhGKw1Lqz/VpftqKe0CeHACkSto=" crossorigin="anonymous">
<link rel="preload" href="/fonts/iosevka/iosevka-aile-bolditalic.woff2" as="font" type="font/woff2" integrity="sha256-fRJP2kQFy+xRyveNgFHkYWfHUG/DSkoUrXdfx7RGJMk=" crossorigin="anonymous">
<link rel="preload" href="/fonts/iosevka/iosevka-aile-italic.woff2" as="font" type="font/woff2" integrity="sha256-gH5Tq4ZWU0MJJQjXsdC00d++xrM842dN8pCVRmBnf0k=" crossorigin="anonymous">
<link rel="preload" href="/fonts/iosevka/iosevka-aile-regular.woff2" as="font" type="font/woff2" integrity="sha256-y6fQ+gvBFZWjeNgxiWQrYYgni3T5H+TLAwHCsSFSjKE=" crossorigin="anonymous">
</head>
<body>
<main>
<h1>
{{ if .IsHomePage }}
FollieHiyuki's personal web assets
{{ else }}
{{ .Path }}
{{ end }}
</h1>
{{ if .IsHomePage }}
<p>
Hi! Welcome to my <em>"Content Delivery Network"</em> <s>freeloading</s> running on Cloudflare. The files served here are used to power my websites, including my
<a href="https://www.folliehiyuki.com" target="_blank" rel="author noopener external">personal blog</a> and
<a href="https://docs.folliehiyuki.com" target="_blank" rel="noopener external">handbook</a>. The source code is avaliable to see on
<a href="https://gitlab.com/FollieHiyuki/cdn" target="_blank" rel="noreferrer nofollow external">GitLab</a>.
</p>
<h2>Packages</h2>
<p>
<span class="fa-solid">&#xf031</span> <a href="./fonts/">fonts</a> - Various 3rd-party fonts I use everywhere<br>
<span class="fa-solid">&#xf53f</span> <a href="./css/">css</a> - Some minimal, opinionated CSS settings<br>
</p>
{{ else }}
<p>
<span class="fa-solid">&#xf07b</span> <a href="../">../</a><br>
{{ range .Entries }}
<span class="fa-solid">{{ if .IsDir }}&#xf07b{{ else }}&#xf15c&nbsp;{{ end }}</span>
<a href="./{{ .Name }}">{{ .Name }}</a><br>
{{ end }}
</p>
{{ end }}
</main>
</body>
</html>

View File

@ -1,88 +0,0 @@
package main
import (
_ "embed"
"flag"
"html/template"
"log"
"os"
"path"
"regexp"
)
// A struct based on fs.DirEntry
type Entry struct {
Name string
IsDir bool
}
//go:embed index.html.tmpl
var indexTemplate string
// Return the list of entries inside a specified directory
func getEntries(dirPath string) ([]Entry, error) {
dirEntries, err := os.ReadDir(dirPath)
if err != nil {
return nil, err
}
entryList := []Entry{}
for _, entry := range dirEntries {
// Of course we want to ignore index.html file in the directory listing
if entry.Name() != "index.html" {
entryName := entry.Name()
if entry.IsDir() {
entryName = entryName + "/"
}
entryList = append(entryList, Entry{Name: entryName, IsDir: entry.IsDir()})
}
}
return entryList, nil
}
// Output an index.html file listing entries to a specified destination
func renderIndexHTML(entries []Entry, root string, dest string, isHomePage bool) error {
// Get rid of ./out/ prefix from find(1) result
re := regexp.MustCompile(`^\.\/` + root + `\/`)
dirPath := re.ReplaceAllString(dest, "")
tmpl, err := template.New(dirPath).Parse(indexTemplate)
if err != nil {
return err
}
data := struct {
Path string
Entries []Entry
IsHomePage bool
}{Path: dirPath, Entries: entries, IsHomePage: isHomePage}
destFile, err := os.Create(path.Join(dest, "index.html"))
if err != nil {
return err
}
return tmpl.Execute(destFile, data)
}
func main() {
rootDir := flag.String("root", "out", "Directory indicating the root of the website")
isHomePage := flag.Bool("home", false, "Whether the directory is the top-level website's index")
flag.Parse()
dirPath := flag.Arg(0)
if dirPath == "" {
log.Fatalln("Missing argument: directory to output the index.html file")
}
entries, err := getEntries(dirPath)
if err != nil {
log.Fatalln(err)
}
err = renderIndexHTML(entries, *rootDir, dirPath, *isHomePage)
if err != nil {
log.Fatalln(err)
}
}

3
vendor/github.com/a-h/templ/.dockerignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
.git
Dockerfile
.dockerignore

25
vendor/github.com/a-h/templ/.gitignore generated vendored Normal file
View File

@ -0,0 +1,25 @@
# Output.
cmd/templ/templ
# Logs.
cmd/templ/lspcmd/*log.txt
# Go code coverage.
coverage.out
coverage
# Mac filesystem jank.
.DS_Store
# Docusaurus.
docs/build/
docs/resources/_gen/
node_modules/
dist/
# Nix artifacts.
result
# Editors
## nvim
.null-ls*

36
vendor/github.com/a-h/templ/.goreleaser.yaml generated vendored Normal file
View File

@ -0,0 +1,36 @@
builds:
- env:
- CGO_ENABLED=0
dir: cmd/templ
mod_timestamp: '{{ .CommitTimestamp }}'
flags:
- -trimpath
ldflags:
- -s -w
goos:
- linux
- windows
- darwin
signs:
- artifacts: checksum
archives:
- format: tar.gz
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

1
vendor/github.com/a-h/templ/.version generated vendored Normal file
View File

@ -0,0 +1 @@
0.2.476

128
vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md generated vendored Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
adrianhesketh@hushail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

244
vendor/github.com/a-h/templ/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,244 @@
# Contributing to templ
## Vision
Enable Go developers to build strongly typed, component-based HTML user interfaces with first-class developer tooling, and a short learning curve.
## Come up with a design and share it
Before starting work on any major pull requests or code changes, start a discussion at https://github.com/a-h/templ/discussions or raise an issue.
We don't want you to spend time on a PR or feature that ultimately doesn't get merged because it doesn't fit with the project goals, or the design doesn't work for some reason.
For issues, it really helps if you provide a reproduction repo, or can create a failing unit test to describe the behaviour.
In designs, we need to consider:
* Backwards compatibility - Not changing the public API between releases, introducing gradual deprecation - don't break people's code.
* Correctness over time - How can we reduce the risk of defects both now, and in future releases?
* Threat model - How could each change be used to inject vulnerabilities into web pages?
* Go version - We target the oldest supported version of Go as per https://go.dev/doc/devel/release
* Automatic migration - If we need to force through a change.
* Compile time vs runtime errors - Prefer compile time.
* Documentation - New features are only useful if people can understand the new feature, what would the documentation look like?
* Examples - How will we demonstrate the feature?
## Project structure
templ is structured into a few areas:
### Parser `./parser`
The parser directory currently contains both v1 and v2 parsers.
The v1 parser is not maintained, it's only used to migrate v1 code over to the v2 syntax.
The parser is responsible for parsing templ files into an object model. The types that make up the object model are in `types.go`. Automatic formatting of the types is tested in `types_test.go`.
A templ file is parsed into the `TemplateFile` struct object model.
```go
type TemplateFile struct {
// Header contains comments or whitespace at the top of the file.
Header []GoExpression
// Package expression.
Package Package
// Nodes in the file.
Nodes []TemplateFileNode
}
```
Parsers are individually tested using two types of unit test.
One test covers the successful parsing of text into an object. For example, the `HTMLCommentParser` test checks for successful patterns.
```go
func TestHTMLCommentParser(t *testing.T) {
var tests = []struct {
name string
input string
expected HTMLComment
}{
{
name: "comment - single line",
input: `<!-- single line comment -->`,
expected: HTMLComment{
Contents: " single line comment ",
},
},
{
name: "comment - no whitespace",
input: `<!--no whitespace between sequence open and close-->`,
expected: HTMLComment{
Contents: "no whitespace between sequence open and close",
},
},
{
name: "comment - multiline",
input: `<!-- multiline
comment
-->`,
expected: HTMLComment{
Contents: ` multiline
comment
`,
},
},
{
name: "comment - with tag",
input: `<!-- <p class="test">tag</p> -->`,
expected: HTMLComment{
Contents: ` <p class="test">tag</p> `,
},
},
{
name: "comments can contain tags",
input: `<!-- <div> hello world </div> -->`,
expected: HTMLComment{
Contents: ` <div> hello world </div> `,
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
input := parse.NewInput(tt.input)
result, ok, err := htmlComment.Parse(input)
if err != nil {
t.Fatalf("parser error: %v", err)
}
if !ok {
t.Fatalf("failed to parse at %d", input.Index())
}
if diff := cmp.Diff(tt.expected, result); diff != "" {
t.Errorf(diff)
}
})
}
}
```
Alongside each success test, is a similar test to check that invalid syntax is detected.
```go
func TestHTMLCommentParserErrors(t *testing.T) {
var tests = []struct {
name string
input string
expected error
}{
{
name: "unclosed HTML comment",
input: `<!-- unclosed HTML comment`,
expected: parse.Error("expected end comment literal '-->' not found",
parse.Position{
Index: 26,
Line: 0,
Col: 26,
}),
},
{
name: "comment in comment",
input: `<!-- <-- other --> -->`,
expected: parse.Error("comment contains invalid sequence '--'", parse.Position{
Index: 8,
Line: 0,
Col: 8,
}),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
input := parse.NewInput(tt.input)
_, _, err := htmlComment.Parse(input)
if diff := cmp.Diff(tt.expected, err); diff != "" {
t.Error(diff)
}
})
}
}
```
### Generator
The generator takes the object model and writes out Go code that produces the expected output. Any changes to Go code output by templ are made in this area.
Testing of the generator is carried out by creating a templ file, and a matching expected output file.
For example, `./generator/test-a-href` contains a templ file of:
```templ
package testahref
templ render() {
<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
<a href={ templ.URL("javascript:alert('should be sanitized')") }>Sanitized</a>
<a href={ templ.SafeURL("javascript:alert('should not be sanitized')") }>Unsanitized</a>
}
```
It also contains an expected output file.
```html
<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
<a href="about:invalid#TemplFailedSanitizationURL">Sanitized</a>
<a href="javascript:alert(&#39;should not be sanitized&#39;)">Unsanitized</a>
```
These tests contribute towards the code coverage metrics by building an instrumented test CLI program. See the `test-cover` task in the `README.md` file.
### CLI
The command line interface for templ is used to generate Go code from templ files, format templ files, and run the LSP.
The code for this is at `./cmd/templ`.
Testing of the templ command line is done with unit tests to check the argument parsing.
The `templ generate` command is tested by generating templ files in the project, and testing that the expected output HTML is present.
### Runtime
The runtime is used by generated code, and by template authors, to serve template content over HTTP, and to carry out various operations.
It is in the root directory of the project at `./runtime.go`. The runtime is unit tested, as well as being tested as part of the `generate` tests.
### LSP
The LSP is structured within the command line interface, and proxies commands through to the `gopls` LSP.
### Docs
The docs are a Docusaurus project at `./docs`.
## Coding
### Build tasks
templ uses the `xc` task runner - https://github.com/joerdav/xc
If you run `xc` you can get see a list of the development tasks that can be run, or you can read the `README.md` file and see the `Tasks` section.
The most useful tasks for local development are:
* `install-snapshot` - this builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path.
* `test` - this regenerates all templates, and runs the unit tests.
* `fmt` - run the `gofmt` tool to format all Go code.
* `lint` - run the same linting as run in the CI process.
* `docs-run` - run the Docusaurus documentation site.
### Commit messages
The project using https://www.conventionalcommits.org/en/v1.0.0/
Examples:
* `feat: support Go comments in templates, fixes #234"`
### Coding style
* Reduce nesting - i.e. prefer early returns over an `else` block, as per https://danp.net/posts/reducing-go-nesting/ or https://go.dev/doc/effective_go#if
* Use line breaks to separate "paragraphs" of code - don't use line breaks in between lines, or at the start/end of functions etc.
* Use the `fmt` and `lint` build tasks to format and lint your code before submitting a PR.

21
vendor/github.com/a-h/templ/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Adrian Hesketh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

143
vendor/github.com/a-h/templ/README.md generated vendored Normal file
View File

@ -0,0 +1,143 @@
![templ](https://github.com/a-h/templ/raw/main/templ.png)
## A HTML templating language for Go that has great developer tooling.
![templ](ide-demo.gif)
## Documentation
See user documentation at https://templ.guide
<p align="center">
<a href="https://pkg.go.dev/github.com/a-h/templ"><img src="https://pkg.go.dev/badge/github.com/a-h/templ.svg" alt="Go Reference" /></a>
<a href="https://xcfile.dev"><img src="https://xcfile.dev/badge.svg" alt="xc compatible" /></a>
<a href="https://raw.githack.com/wiki/a-h/templ/coverage.html"><img src="https://github.com/a-h/templ/wiki/coverage.svg" alt="Go Coverage" /></a>
<a href="https://goreportcard.com/report/github.com/a-h/templ"><img src="https://goreportcard.com/badge/github.com/a-h/templ" alt="Go Report Card" /></a<
</p>
## Tasks
### build
Build a local version.
```sh
go run ./get-version > .version
cd cmd/templ
go build
```
### install-snapshot
Build and install to ~/bin
```sh
rm cmd/templ/lspcmd/*.txt || true
go run ./get-version > .version
cd cmd/templ && go build -o ~/bin/templ
```
### build-snapshot
Use goreleaser to build the command line binary using goreleaser.
```sh
goreleaser build --snapshot --clean
```
### generate
Run templ generate using local version.
```sh
go run ./cmd/templ generate -include-version=false
```
### test
Run Go tests.
```sh
go run ./get-version > .version
go run ./cmd/templ generate -include-version=false
go test ./...
```
### test-cover
Run Go tests.
```sh
# Create test profile directories.
mkdir -p coverage/fmt
mkdir -p coverage/generate
mkdir -p coverage/unit
# Build the test binary.
go build -cover -o ./coverage/templ-cover ./cmd/templ
# Run the covered generate command.
GOCOVERDIR=coverage/fmt ./coverage/templ-cover fmt .
GOCOVERDIR=coverage/generate ./coverage/templ-cover generate -include-version=false
# Run the unit tests.
go test -cover ./... -args -test.gocoverdir="$PWD/coverage/unit"
# Display the combined percentage.
go tool covdata percent -i=./coverage/fmt,./coverage/generate,./coverage/unit
# Generate a text coverage profile for tooling to use.
go tool covdata textfmt -i=./coverage/fmt,./coverage/generate,./coverage/unit -o coverage.out
# Print total
go tool cover -func coverage.out | grep total
```
### benchmark
Run benchmarks.
```sh
go run ./cmd/templ generate -include-version=false && go test ./... -bench=. -benchmem
```
### fmt
Format all Go and templ code.
```sh
gofmt -s -w .
go run ./cmd/templ fmt .
```
### lint
```sh
golangci-lint run --verbose
```
### release
Create production build with goreleaser.
```sh
if [ "${GITHUB_TOKEN}" == "" ]; then echo "No github token, run:"; echo "export GITHUB_TOKEN=`pass github.com/goreleaser_access_token`"; exit 1; fi
./push-tag.sh
goreleaser --clean
```
### docs-run
Run the development server.
Directory: docs
```sh
npm run start
```
### docs-build
Build production docs site.
Directory: docs
```sh
npm run build
```

9
vendor/github.com/a-h/templ/SECURITY.md generated vendored Normal file
View File

@ -0,0 +1,9 @@
# Security Policy
## Supported Versions
The latest version of templ is supported.
## Reporting a Vulnerability
Use the "Security" tab in Github and fill out the "Report a vulnerability" form.

85
vendor/github.com/a-h/templ/flake.lock generated vendored Normal file
View File

@ -0,0 +1,85 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1694102001,
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1694422566,
"narHash": "sha256-lHJ+A9esOz9vln/3CJG23FV6Wd2OoOFbDeEs4cMGMqc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3a2786eea085f040a66ecde1bc3ddc7099f6dbeb",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"gitignore": "gitignore",
"nixpkgs": "nixpkgs",
"xc": "xc"
}
},
"xc": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1696495449,
"narHash": "sha256-dthZiJ2FX/eIC0l1mdfefJZXDTVLfwp7L7Arq5rsCWA=",
"owner": "joerdav",
"repo": "xc",
"rev": "c8baab14d679fb276f11c576607010283be21220",
"type": "github"
},
"original": {
"owner": "joerdav",
"repo": "xc",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

85
vendor/github.com/a-h/templ/flake.nix generated vendored Normal file
View File

@ -0,0 +1,85 @@
{
description = "templ";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
gitignore = {
url = "github:hercules-ci/gitignore.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
xc = {
url = "github:joerdav/xc";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, gitignore, xc }:
let
# Systems supported
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
"aarch64-linux" # 64-bit ARM Linux
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
# Helper to provide system-specific attributes
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
inherit system;
pkgs = import nixpkgs { inherit system; };
});
in
rec {
packages = forAllSystems ({ pkgs, ... }: rec {
default = templ;
templ = pkgs.buildGo121Module {
name = "templ";
src = gitignore.lib.gitignoreSource ./.;
subPackages = [ "cmd/templ" ];
vendorSha256 = "sha256-hbXKWWwrlv0w3SxMgPtDBpluvrbjDRGiJ/9QnRKlwCE=";
CGO_ENALBED = 0;
flags = [
"-trimpath"
];
ldflags = [
"-s"
"-w"
"-extldflags -static"
];
};
templ-docs = pkgs.buildNpmPackage {
name = "templ-docs";
src = gitignore.lib.gitignoreSource ./docs;
npmDepsHash = "sha256-i6clvSyHtQEGl2C/wcCXonl1W/Kxq7WPTYH46AhUvDM=";
installPhase = ''
mkdir -p $out/share
cp -r build/ $out/share/docs
'';
};
});
# `nix develop` provides a shell containing required tools for development
devShell = forAllSystems ({ system, pkgs }:
pkgs.mkShell {
buildInputs = with pkgs; [
(golangci-lint.override { buildGoModule = buildGo121Module; })
go_1_21
goreleaser
nodejs
xc.packages.${system}.xc
];
});
# Allows users to install the package on their system in an easy way
overlays.default = final: prev:
forAllSystems ({ system, ... }: {
templ = packages.${system}.templ;
templ-docs = packages.${system}.templ-docs;
});
};
}

BIN
vendor/github.com/a-h/templ/ide-demo.gif generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

5
vendor/github.com/a-h/templ/push-tag.sh generated vendored Normal file
View File

@ -0,0 +1,5 @@
#!/bin/sh
export VERSION=`cat .version`
echo Adding git tag with version v${VERSION};
git tag v${VERSION};
git push origin v${VERSION};

645
vendor/github.com/a-h/templ/runtime.go generated vendored Normal file
View File

@ -0,0 +1,645 @@
package templ
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"sort"
"strings"
"sync"
"github.com/a-h/templ/safehtml"
)
// Types exposed by all components.
// Component is the interface that all templates implement.
type Component interface {
// Render the template.
Render(ctx context.Context, w io.Writer) error
}
// ComponentFunc converts a function that matches the Component interface's
// Render method into a Component.
type ComponentFunc func(ctx context.Context, w io.Writer) error
// Render the template.
func (cf ComponentFunc) Render(ctx context.Context, w io.Writer) error {
return cf(ctx, w)
}
func WithChildren(ctx context.Context, children Component) context.Context {
ctx, v := getContext(ctx)
v.children = &children
return ctx
}
func ClearChildren(ctx context.Context) context.Context {
_, v := getContext(ctx)
v.children = nil
return ctx
}
// NopComponent is a component that doesn't render anything.
var NopComponent = ComponentFunc(func(ctx context.Context, w io.Writer) error { return nil })
// GetChildren from the context.
func GetChildren(ctx context.Context) Component {
_, v := getContext(ctx)
if v.children == nil {
return NopComponent
}
return *v.children
}
// ComponentHandler is a http.Handler that renders components.
type ComponentHandler struct {
Component Component
Status int
ContentType string
ErrorHandler func(r *http.Request, err error) http.Handler
}
const componentHandlerErrorMessage = "templ: failed to render template"
// ServeHTTP implements the http.Handler interface.
func (ch ComponentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if ch.Status != 0 {
w.WriteHeader(ch.Status)
}
w.Header().Add("Content-Type", ch.ContentType)
err := ch.Component.Render(r.Context(), w)
if err != nil {
if ch.ErrorHandler != nil {
ch.ErrorHandler(r, err).ServeHTTP(w, r)
return
}
http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
}
}
// Handler creates a http.Handler that renders the template.
func Handler(c Component, options ...func(*ComponentHandler)) *ComponentHandler {
ch := &ComponentHandler{
Component: c,
ContentType: "text/html",
}
for _, o := range options {
o(ch)
}
return ch
}
// WithStatus sets the HTTP status code returned by the ComponentHandler.
func WithStatus(status int) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.Status = status
}
}
// WithConentType sets the Content-Type header returned by the ComponentHandler.
func WithContentType(contentType string) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.ContentType = contentType
}
}
// WithErrorHandler sets the error handler used if rendering fails.
func WithErrorHandler(eh func(r *http.Request, err error) http.Handler) func(*ComponentHandler) {
return func(ch *ComponentHandler) {
ch.ErrorHandler = eh
}
}
// EscapeString escapes HTML text within templates.
func EscapeString(s string) string {
return html.EscapeString(s)
}
// Bool attribute value.
func Bool(value bool) bool {
return value
}
// Classes for CSS.
// Supported types are string, ConstantCSSClass, ComponentCSSClass, map[string]bool.
func Classes(classes ...any) CSSClasses {
return CSSClasses(classes)
}
// CSSClasses is a slice of CSS classes.
type CSSClasses []any
// String returns the names of all CSS classes.
func (classes CSSClasses) String() string {
if len(classes) == 0 {
return ""
}
cp := newCSSProcessor()
for _, v := range classes {
cp.Add(v)
}
return cp.String()
}
func newCSSProcessor() *cssProcessor {
return &cssProcessor{
classNameToEnabled: make(map[string]bool),
}
}
type cssProcessor struct {
classNameToEnabled map[string]bool
orderedNames []string
}
func (cp *cssProcessor) Add(item any) {
switch c := item.(type) {
case []string:
for _, className := range c {
cp.AddClassName(className, true)
}
case string:
cp.AddClassName(c, true)
case ConstantCSSClass:
cp.AddClassName(c.ClassName(), true)
case ComponentCSSClass:
cp.AddClassName(c.ClassName(), true)
case map[string]bool:
// In Go, map keys are iterated in a randomized order.
// So the keys in the map must be sorted to produce consistent output.
keys := make([]string, len(c))
var i int
for key := range c {
keys[i] = key
i++
}
sort.Strings(keys)
for _, className := range keys {
cp.AddClassName(className, c[className])
}
case []KeyValue[string, bool]:
for _, kv := range c {
cp.AddClassName(kv.Key, kv.Value)
}
case KeyValue[string, bool]:
cp.AddClassName(c.Key, c.Value)
case []KeyValue[CSSClass, bool]:
for _, kv := range c {
cp.AddClassName(kv.Key.ClassName(), kv.Value)
}
case KeyValue[CSSClass, bool]:
cp.AddClassName(c.Key.ClassName(), c.Value)
case CSSClasses:
for _, item := range c {
cp.Add(item)
}
case func() CSSClass:
cp.AddClassName(c().ClassName(), true)
default:
cp.AddClassName(unknownTypeClassName, true)
}
}
func (cp *cssProcessor) AddClassName(className string, enabled bool) {
cp.classNameToEnabled[className] = enabled
cp.orderedNames = append(cp.orderedNames, className)
}
func (cp *cssProcessor) String() string {
// Order the outputs according to how they were input, and remove disabled names.
rendered := make(map[string]any, len(cp.classNameToEnabled))
var names []string
for _, name := range cp.orderedNames {
if enabled := cp.classNameToEnabled[name]; !enabled {
continue
}
if _, hasBeenRendered := rendered[name]; hasBeenRendered {
continue
}
names = append(names, name)
rendered[name] = struct{}{}
}
return strings.Join(names, " ")
}
// KeyValue is a key and value pair.
type KeyValue[TKey comparable, TValue any] struct {
Key TKey `json:"name"`
Value TValue `json:"value"`
}
// KV creates a new key/value pair from the input key and value.
func KV[TKey comparable, TValue any](key TKey, value TValue) KeyValue[TKey, TValue] {
return KeyValue[TKey, TValue]{
Key: key,
Value: value,
}
}
const unknownTypeClassName = "--templ-css-class-unknown-type"
// Class returns a CSS class name.
// Deprecated: use a string instead.
func Class(name string) CSSClass {
return SafeClass(name)
}
// SafeClass bypasses CSS class name validation.
// Deprecated: use a string instead.
func SafeClass(name string) CSSClass {
return ConstantCSSClass(name)
}
// CSSClass provides a class name.
type CSSClass interface {
ClassName() string
}
// ConstantCSSClass is a string constant of a CSS class name.
// Deprecated: use a string instead.
type ConstantCSSClass string
// ClassName of the CSS class.
func (css ConstantCSSClass) ClassName() string {
return string(css)
}
// ComponentCSSClass is a templ.CSS
type ComponentCSSClass struct {
// ID of the class, will be autogenerated.
ID string
// Definition of the CSS.
Class SafeCSS
}
// ClassName of the CSS class.
func (css ComponentCSSClass) ClassName() string {
return css.ID
}
// CSSID calculates an ID.
func CSSID(name string, css string) string {
sum := sha256.Sum256([]byte(css))
hp := hex.EncodeToString(sum[:])[0:4]
return fmt.Sprintf("%s_%s", name, hp)
}
// NewCSSMiddleware creates HTTP middleware that renders a global stylesheet of ComponentCSSClass
// CSS if the request path matches, or updates the HTTP context to ensure that any handlers that
// use templ.Components skip rendering <style> elements for classes that are included in the global
// stylesheet. By default, the stylesheet path is /styles/templ.css
func NewCSSMiddleware(next http.Handler, classes ...CSSClass) CSSMiddleware {
return CSSMiddleware{
Path: "/styles/templ.css",
CSSHandler: NewCSSHandler(classes...),
Next: next,
}
}
// CSSMiddleware renders a global stylesheet.
type CSSMiddleware struct {
Path string
CSSHandler CSSHandler
Next http.Handler
}
func (cssm CSSMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == cssm.Path {
cssm.CSSHandler.ServeHTTP(w, r)
return
}
// Add registered classes to the context.
ctx, v := getContext(r.Context())
for _, c := range cssm.CSSHandler.Classes {
v.addClass(c.ID)
}
// Serve the request. Templ components will use the updated context
// to know to skip rendering <style> elements for any component CSS
// classes that have been included in the global stylesheet.
cssm.Next.ServeHTTP(w, r.WithContext(ctx))
}
// NewCSSHandler creates a handler that serves a stylesheet containing the CSS of the
// classes passed in. This is used by the CSSMiddleware to provide global stylesheets
// for templ components.
func NewCSSHandler(classes ...CSSClass) CSSHandler {
ccssc := make([]ComponentCSSClass, 0, len(classes))
for _, c := range classes {
ccss, ok := c.(ComponentCSSClass)
if !ok {
continue
}
ccssc = append(ccssc, ccss)
}
return CSSHandler{
Classes: ccssc,
}
}
// CSSHandler is a HTTP handler that serves CSS.
type CSSHandler struct {
Logger func(err error)
Classes []ComponentCSSClass
}
func (cssh CSSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/css")
for _, c := range cssh.Classes {
_, err := w.Write([]byte(c.Class))
if err != nil && cssh.Logger != nil {
cssh.Logger(err)
}
}
}
// RenderCSSItems renders the CSS to the writer, if the items haven't already been rendered.
func RenderCSSItems(ctx context.Context, w io.Writer, classes ...any) (err error) {
if len(classes) == 0 {
return nil
}
_, v := getContext(ctx)
sb := new(strings.Builder)
renderCSSItemsToBuilder(sb, v, classes...)
if sb.Len() > 0 {
if _, err = io.WriteString(w, `<style type="text/css">`); err != nil {
return err
}
if _, err = io.WriteString(w, sb.String()); err != nil {
return err
}
if _, err = io.WriteString(w, `</style>`); err != nil {
return err
}
}
return nil
}
func renderCSSItemsToBuilder(sb *strings.Builder, v *contextValue, classes ...any) {
for _, c := range classes {
switch ccc := c.(type) {
case ComponentCSSClass:
if !v.hasClassBeenRendered(ccc.ID) {
sb.WriteString(string(ccc.Class))
v.addClass(ccc.ID)
}
case KeyValue[ComponentCSSClass, bool]:
if !ccc.Value {
continue
}
renderCSSItemsToBuilder(sb, v, ccc.Key)
case KeyValue[CSSClass, bool]:
if !ccc.Value {
continue
}
renderCSSItemsToBuilder(sb, v, ccc.Key)
case CSSClasses:
renderCSSItemsToBuilder(sb, v, ccc...)
case func() CSSClass:
renderCSSItemsToBuilder(sb, v, ccc())
case []string:
// Skip. These are class names, not CSS classes.
case string:
// Skip. This is a class name, not a CSS class.
case ConstantCSSClass:
// Skip. This is a class name, not a CSS class.
case CSSClass:
// Skip. This is a class name, not a CSS class.
case map[string]bool:
// Skip. These are class names, not CSS classes.
case KeyValue[string, bool]:
// Skip. These are class names, not CSS classes.
case []KeyValue[string, bool]:
// Skip. These are class names, not CSS classes.
case KeyValue[ConstantCSSClass, bool]:
// Skip. These are class names, not CSS classes.
case []KeyValue[ConstantCSSClass, bool]:
// Skip. These are class names, not CSS classes.
}
}
}
// SafeCSS is CSS that has been sanitized.
type SafeCSS string
// SanitizeCSS sanitizes CSS properties to ensure that they are safe.
func SanitizeCSS(property, value string) SafeCSS {
p, v := safehtml.SanitizeCSS(property, value)
return SafeCSS(p + ":" + v + ";")
}
// Hyperlink sanitization.
// FailedSanitizationURL is returned if a URL fails sanitization checks.
const FailedSanitizationURL = SafeURL("about:invalid#TemplFailedSanitizationURL")
// URL sanitizes the input string s and returns a SafeURL.
func URL(s string) SafeURL {
if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
protocol := s[:i]
if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
return FailedSanitizationURL
}
}
return SafeURL(s)
}
// SafeURL is a URL that has been sanitized.
type SafeURL string
// Script handling.
func safeEncodeScriptParams(escapeHTML bool, params []any) []string {
encodedParams := make([]string, len(params))
for i := 0; i < len(encodedParams); i++ {
enc, _ := json.Marshal(params[i])
if !escapeHTML {
encodedParams[i] = string(enc)
continue
}
encodedParams[i] = EscapeString(string(enc))
}
return encodedParams
}
// SafeScript encodes unknown parameters for safety for inside HTML attributes.
func SafeScript(functionName string, params ...any) string {
encodedParams := safeEncodeScriptParams(true, params)
sb := new(strings.Builder)
sb.WriteString(functionName)
sb.WriteRune('(')
sb.WriteString(strings.Join(encodedParams, ","))
sb.WriteRune(')')
return sb.String()
}
// SafeScript encodes unknown parameters for safety for inline scripts.
func SafeScriptInline(functionName string, params ...any) string {
encodedParams := safeEncodeScriptParams(false, params)
sb := new(strings.Builder)
sb.WriteString(functionName)
sb.WriteRune('(')
sb.WriteString(strings.Join(encodedParams, ","))
sb.WriteRune(')')
return sb.String()
}
type contextKeyType int
const contextKey = contextKeyType(0)
type contextValue struct {
ss map[string]struct{}
children *Component
}
func (v *contextValue) addScript(s string) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
v.ss["script_"+s] = struct{}{}
}
func (v *contextValue) hasScriptBeenRendered(s string) (ok bool) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
_, ok = v.ss["script_"+s]
return
}
func (v *contextValue) addClass(s string) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
v.ss["class_"+s] = struct{}{}
}
func (v *contextValue) hasClassBeenRendered(s string) (ok bool) {
if v.ss == nil {
v.ss = map[string]struct{}{}
}
_, ok = v.ss["class_"+s]
return
}
// InitializeContext initializes context used to store internal state used during rendering.
func InitializeContext(ctx context.Context) context.Context {
if _, ok := ctx.Value(contextKey).(*contextValue); ok {
return ctx
}
v := &contextValue{}
ctx = context.WithValue(ctx, contextKey, v)
return ctx
}
func getContext(ctx context.Context) (context.Context, *contextValue) {
v, ok := ctx.Value(contextKey).(*contextValue)
if !ok {
ctx = InitializeContext(ctx)
v = ctx.Value(contextKey).(*contextValue)
}
return ctx, v
}
// ComponentScript is a templ Script template.
type ComponentScript struct {
// Name of the script, e.g. print.
Name string
// Function to render.
Function string
// Call of the function in JavaScript syntax, including parameters, and
// ensures parameters are HTML escaped; useful for injecting into HTML
// attributes like onclick, onhover, etc.
//
// Given:
// functionName("some string",12345)
// It would render:
// __templ_functionName_sha(&#34;some string&#34;,12345))
//
// This is can be injected into HTML attributes:
// <button onClick="__templ_functionName_sha(&#34;some string&#34;,12345))">Click Me</button>
Call string
// Call of the function in JavaScript syntax, including parameters. It
// does not HTML escape parameters; useful for directly calling in script
// elements.
//
// Given:
// functionName("some string",12345)
// It would render:
// __templ_functionName_sha("some string",12345))
//
// This is can be used to call the function inside a script tag:
// <script>__templ_functionName_sha("some string",12345))</script>
CallInline string
}
var _ Component = ComponentScript{}
func (c ComponentScript) Render(ctx context.Context, w io.Writer) error {
err := RenderScriptItems(ctx, w, c)
if err != nil {
return err
}
if len(c.Call) > 0 {
if _, err = io.WriteString(w, `<script type="text/javascript">`); err != nil {
return err
}
if _, err = io.WriteString(w, c.CallInline); err != nil {
return err
}
if _, err = io.WriteString(w, `</script>`); err != nil {
return err
}
}
return nil
}
// RenderScriptItems renders a <script> element, if the script has not already been rendered.
func RenderScriptItems(ctx context.Context, w io.Writer, scripts ...ComponentScript) (err error) {
if len(scripts) == 0 {
return nil
}
_, v := getContext(ctx)
sb := new(strings.Builder)
for _, s := range scripts {
if !v.hasScriptBeenRendered(s.Name) {
sb.WriteString(s.Function)
v.addScript(s.Name)
}
}
if sb.Len() > 0 {
if _, err = io.WriteString(w, `<script type="text/javascript">`); err != nil {
return err
}
if _, err = io.WriteString(w, sb.String()); err != nil {
return err
}
if _, err = io.WriteString(w, `</script>`); err != nil {
return err
}
}
return nil
}
var bufferPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func GetBuffer() *bytes.Buffer {
return bufferPool.Get().(*bytes.Buffer)
}
func ReleaseBuffer(b *bytes.Buffer) {
b.Reset()
bufferPool.Put(b)
}

139
vendor/github.com/a-h/templ/safehtml/style.go generated vendored Normal file
View File

@ -0,0 +1,139 @@
// Adapted from https://raw.githubusercontent.com/google/safehtml/3c4cd5b5d8c9a6c5882fba099979e9f50b65c876/style.go
// Copyright (c) 2017 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
package safehtml
import (
"net/url"
"regexp"
"strings"
)
// SanitizeCSS attempts to sanitize CSS properties.
func SanitizeCSS(property, value string) (string, string) {
if !identifierPattern.MatchString(property) {
return InnocuousPropertyName, InnocuousPropertyValue
}
property = strings.ToLower(property)
if sanitizer, ok := cssPropertyNameToValueSanitizer[property]; ok {
return property, sanitizer(value)
}
return property, sanitizeRegular(value)
}
// identifierPattern matches a subset of valid <ident-token> values defined in
// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram. This pattern matches all generic family name
// keywords defined in https://drafts.csswg.org/css-fonts-3/#family-name-value.
var identifierPattern = regexp.MustCompile(`^[-a-zA-Z]+$`)
var cssPropertyNameToValueSanitizer = map[string]func(string) string{
"background-image": sanitizeBackgroundImage,
"font-family": sanitizeFontFamily,
"display": sanitizeEnum,
"background-color": sanitizeRegular,
"background-position": sanitizeRegular,
"background-repeat": sanitizeRegular,
"background-size": sanitizeRegular,
"color": sanitizeRegular,
"height": sanitizeRegular,
"width": sanitizeRegular,
"left": sanitizeRegular,
"right": sanitizeRegular,
"top": sanitizeRegular,
"bottom": sanitizeRegular,
"font-weight": sanitizeRegular,
"padding": sanitizeRegular,
"z-index": sanitizeRegular,
}
func sanitizeBackgroundImage(v string) string {
for _, u := range strings.Split(v, ",") {
u = strings.TrimSpace(u)
if !strings.HasPrefix(u, `url("`) {
return InnocuousPropertyValue
}
if !strings.HasSuffix(u, `")`) {
return InnocuousPropertyValue
}
u := u[5 : len(u)-2]
if !urlIsSafe(u) {
return InnocuousPropertyValue
}
}
return v
}
func urlIsSafe(s string) bool {
u, err := url.Parse(s)
if err != nil {
return false
}
if u.IsAbs() {
if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") || strings.EqualFold(u.Scheme, "mailto") {
return true
}
return false
}
return true
}
var genericFontFamilyName = regexp.MustCompile(`^[a-zA-Z][- a-zA-Z]+$`)
func sanitizeFontFamily(s string) string {
for _, f := range strings.Split(s, ",") {
f = strings.TrimSpace(f)
if strings.HasPrefix(f, `"`) {
if !strings.HasSuffix(f, `"`) {
return InnocuousPropertyValue
}
continue
}
if !genericFontFamilyName.MatchString(f) {
return InnocuousPropertyValue
}
}
return s
}
func sanitizeEnum(s string) string {
if !safeEnumPropertyValuePattern.MatchString(s) {
return InnocuousPropertyValue
}
return s
}
func sanitizeRegular(s string) string {
if !safeRegularPropertyValuePattern.MatchString(s) {
return InnocuousPropertyValue
}
return s
}
// InnocuousPropertyName is an innocuous property generated by a sanitizer when its input is unsafe.
const InnocuousPropertyName = "zTemplUnsafeCSSPropertyName"
// InnocuousPropertyValue is an innocuous property generated by a sanitizer when its input is unsafe.
const InnocuousPropertyValue = "zTemplUnsafeCSSPropertyValue"
// safeRegularPropertyValuePattern matches strings that are safe to use as property values.
// Specifically, it matches string where every '*' or '/' is followed by end-of-text or a safe rune
// (i.e. alphanumerics or runes in the set [+-.!#%_ \t]). This regex ensures that the following
// are disallowed:
// - "/*" and "*/", which are CSS comment markers.
// - "//", even though this is not a comment marker in the CSS specification. Disallowing
// this string minimizes the chance that browser peculiarities or parsing bugs will allow
// sanitization to be bypassed.
// - '(' and ')', which can be used to call functions.
// - ',', since it can be used to inject extra values into a property.
// - Runes which could be matched on CSS error recovery of a previously malformed token, such as '@'
// and ':'. See http://www.w3.org/TR/css3-syntax/#error-handling.
var safeRegularPropertyValuePattern = regexp.MustCompile(`^(?:[*/]?(?:[0-9a-zA-Z+-.!#%_ \t]|$))*$`)
// safeEnumPropertyValuePattern matches strings that are safe to use as enumerated property values.
// Specifically, it matches strings that contain only alphabetic and '-' runes.
var safeEnumPropertyValuePattern = regexp.MustCompile(`^[a-zA-Z-]*$`)

BIN
vendor/github.com/a-h/templ/templ.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

6
vendor/github.com/a-h/templ/version.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
package templ
import _ "embed"
//go:embed .version
var Version string

27
vendor/golang.org/x/sync/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
vendor/golang.org/x/sync/PATENTS generated vendored Normal file
View File

@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

132
vendor/golang.org/x/sync/errgroup/errgroup.go generated vendored Normal file
View File

@ -0,0 +1,132 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errgroup provides synchronization, error propagation, and Context
// cancelation for groups of goroutines working on subtasks of a common task.
package errgroup
import (
"context"
"fmt"
"sync"
)
type token struct{}
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group is valid, has no limit on the number of active goroutines,
// and does not cancel on error.
type Group struct {
cancel func(error)
wg sync.WaitGroup
sem chan token
errOnce sync.Once
err error
}
func (g *Group) done() {
if g.sem != nil {
<-g.sem
}
g.wg.Done()
}
// WithContext returns a new Group and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := withCancelCause(ctx)
return &Group{cancel: cancel}, ctx
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel(g.err)
}
return g.err
}
// Go calls the given function in a new goroutine.
// It blocks until the new goroutine can be added without the number of
// active goroutines in the group exceeding the configured limit.
//
// The first call to return a non-nil error cancels the group's context, if the
// group was created by calling WithContext. The error will be returned by Wait.
func (g *Group) Go(f func() error) {
if g.sem != nil {
g.sem <- token{}
}
g.wg.Add(1)
go func() {
defer g.done()
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel(g.err)
}
})
}
}()
}
// TryGo calls the given function in a new goroutine only if the number of
// active goroutines in the group is currently below the configured limit.
//
// The return value reports whether the goroutine was started.
func (g *Group) TryGo(f func() error) bool {
if g.sem != nil {
select {
case g.sem <- token{}:
// Note: this allows barging iff channels in general allow barging.
default:
return false
}
}
g.wg.Add(1)
go func() {
defer g.done()
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel(g.err)
}
})
}
}()
return true
}
// SetLimit limits the number of active goroutines in this group to at most n.
// A negative value indicates no limit.
//
// Any subsequent call to the Go method will block until it can add an active
// goroutine without exceeding the configured limit.
//
// The limit must not be modified while any goroutines in the group are active.
func (g *Group) SetLimit(n int) {
if n < 0 {
g.sem = nil
return
}
if len(g.sem) != 0 {
panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem)))
}
g.sem = make(chan token, n)
}

13
vendor/golang.org/x/sync/errgroup/go120.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.20
package errgroup
import "context"
func withCancelCause(parent context.Context) (context.Context, func(error)) {
return context.WithCancelCause(parent)
}

14
vendor/golang.org/x/sync/errgroup/pre_go120.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.20
package errgroup
import "context"
func withCancelCause(parent context.Context) (context.Context, func(error)) {
ctx, cancel := context.WithCancel(parent)
return ctx, func(error) { cancel() }
}

7
vendor/modules.txt vendored Normal file
View File

@ -0,0 +1,7 @@
# github.com/a-h/templ v0.2.476
## explicit; go 1.20
github.com/a-h/templ
github.com/a-h/templ/safehtml
# golang.org/x/sync v0.5.0
## explicit; go 1.18
golang.org/x/sync/errgroup