Compare commits

...

9 Commits

Author SHA1 Message Date
Maxim Lebedev 11255805b1
🔧 Updated env config
continuous-integration/drone/push Build is failing Details
2023-03-15 06:20:52 +06:00
Maxim Lebedev dd29a48db3
⬆️ Upgraded caarlos0/env package 2023-03-15 06:16:52 +06:00
Maxim Lebedev 1ee4956afd
🔧 Created stub systemd service 2023-03-15 05:26:45 +06:00
Maxim Lebedev 28a1498f76
👷 Created Drone CI config 2023-03-15 05:26:15 +06:00
Maxim Lebedev d0a00f0331
🔊 Added running log 2023-03-15 05:21:06 +06:00
Maxim Lebedev 71ee3e4da4
💄 Added revision info into templates 2023-03-15 05:21:06 +06:00
Maxim Lebedev 9678e99140
🧑‍💻 Created Makefile 2023-03-15 04:36:16 +06:00
Maxim Lebedev 26ea6b7939
✏️ Created stub README.md 2023-03-15 04:10:22 +06:00
Maxim Lebedev 884e390b79
Fixed broken tests 2023-03-15 04:10:05 +06:00
29 changed files with 788 additions and 274 deletions

28
Makefile Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/make -f
SHELL = /bin/sh
#### Start of system configuration section. ####
srcdir = .
GO ?= go
GOFLAGS ?= -buildvcs=true
EXECUTABLE ?= hub
#### End of system configuration section. ####
.PHONY: all
all: main.go
$(GO) build -v $(GOFLAGS) -o $(EXECUTABLE)
.PHONY: clean
clean: ## Delete all files in the current directory that are normally created by building the program
$(GO) clean
.PHONY: check
check: ## Perform self-tests
$(GO) test -v -cover -failfast -short -shuffle=on $(GOFLAGS) $(srcdir)/...
.PHONY: help
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

49
build/ci/drone.yml Normal file
View File

@ -0,0 +1,49 @@
---
kind: "pipeline"
type: "docker"
name: "default"
environment:
CGO_ENABLED: 0
steps:
- name: "test"
image: "golang:alpine"
volumes:
- name: "modules"
path: "/go/pkg/mod"
commands:
- "make check"
- name: "build"
image: "golang:alpine"
volumes:
- name: "modules"
path: "/go/pkg/mod"
commands:
- "make"
depends_on:
- "test"
- name: "delivery"
image: "drillster/drone-rsync"
settings:
hosts:
from_secret: "SSH_HOST_IP"
key:
from_secret: "SSH_PRIVATE_KEY"
source: "./hub"
target: "/etc/hub/"
prescript:
- "systemctl stop websub"
script:
- "systemctl start websub"
depends_on:
- build
when:
branch:
- master
volumes:
- name: modules
temp: {}

View File

@ -39,29 +39,30 @@ func init() {
} }
var messageKeyToIndex = map[string]int{ var messageKeyToIndex = map[string]int{
"%d subscribers": 4, "%d subscribers": 5,
"%s logo": 0, "%s logo": 1,
"Dead simple WebSub hub": 1, "Dead simple WebSub hub": 2,
"How to publish and consume?": 2, "How to publish and consume?": 3,
"What the spec?": 3, "What the spec?": 4,
"version": 0,
} }
var enIndex = []uint32{ // 6 elements var enIndex = []uint32{ // 7 elements
0x00000000, 0x0000000b, 0x00000022, 0x0000003e, 0x00000000, 0x00000008, 0x00000013, 0x0000002a,
0x0000004d, 0x0000005f, 0x00000046, 0x00000055, 0x00000067,
} // Size: 48 bytes } // Size: 52 bytes
const enData string = "" + // Size: 95 bytes const enData string = "" + // Size: 103 bytes
"\x02%[1]s logo\x02Dead simple WebSub hub\x02How to publish and consume?" + "\x02version\x02%[1]s logo\x02Dead simple WebSub hub\x02How to publish an" +
"\x02What the spec?\x02%[1]d subscribers" "d consume?\x02What the spec?\x02%[1]d subscribers"
var ruIndex = []uint32{ // 6 elements var ruIndex = []uint32{ // 7 elements
0x00000000, 0x00000015, 0x00000038, 0x0000006d, 0x00000000, 0x0000000d, 0x00000022, 0x00000045,
0x00000083, 0x000000a0, 0x0000007a, 0x00000090, 0x000000ad,
} // Size: 48 bytes } // Size: 52 bytes
const ruData string = "" + // Size: 160 bytes const ruData string = "" + // Size: 173 bytes
"\x02логотип %[1]s\x02Простейший хаб WebSub\x02Как публиковать и принимат" + "\x02версия\x02логотип %[1]s\x02Простейший хаб WebSub\x02Как публиковать " +
"ь?\x02В чём спека?\x02%[1]d подписчиков" "и принимать?\x02В чём спека?\x02%[1]d подписчиков"
// Total table size 351 bytes (0KiB); checksum: D963980B // Total table size 380 bytes (0KiB); checksum: 7D8C2E8B

2
docs/README.md Normal file
View File

@ -0,0 +1,2 @@
# WebSub [![Build Status](https://drone.toby3d.me/api/badges/toby3d/hub/status.svg)](https://drone.toby3d.me/toby3d/hub)
> Personal WebSub hub

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.20
require ( require (
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/caarlos0/env/v6 v6.10.1 github.com/caarlos0/env/v7 v7.1.0
github.com/go-logfmt/logfmt v0.6.0 github.com/go-logfmt/logfmt v0.6.0
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/valyala/quicktemplate v1.7.0 github.com/valyala/quicktemplate v1.7.0

4
go.sum
View File

@ -2,8 +2,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=

15
init/hub.service Normal file
View File

@ -0,0 +1,15 @@
[Unit]
Description=WebSub Hub
After=syslog.target
After=network.target
[Service]
EnvironmentFile=/etc/hub/env
RestartSec=2s
Type=simple
WorkingDirectory=/etc/hub/
ExecStart=/etc/hub/hub
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -7,7 +7,7 @@ import (
type Config struct { type Config struct {
BaseURL *url.URL `env:"BASE_URL" envDefault:"http://localhost:3000/"` BaseURL *url.URL `env:"BASE_URL" envDefault:"http://localhost:3000/"`
Bind string `end:"BIND" envDefault:":3000"` Bind string `end:"BIND,required" envDefault:":3000"`
Name string `env:"NAME" envDefault:"WebSub"` Name string `env:"NAME" envDefault:"WebSub"`
} }

View File

@ -48,7 +48,7 @@ func TestHandler_ServeHTTP_Subscribe(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
delivery.NewHandler(delivery.NewHandlerParams{ delivery.NewHandler(delivery.NewHandlerParams{
Hub: hub, Hub: hub,
Subscriptions: subscriptionucase.NewSubscriptionUseCase(subscriptions, topics), Subscriptions: subscriptionucase.NewSubscriptionUseCase(subscriptions, topics, srv.Client()),
Topics: topicucase.NewTopicUseCase(topics, srv.Client()), Topics: topicucase.NewTopicUseCase(topics, srv.Client()),
Matcher: language.NewMatcher([]language.Tag{language.English}), Matcher: language.NewMatcher([]language.Tag{language.English}),
Name: "WebSub", Name: "WebSub",
@ -94,7 +94,7 @@ func TestHandler_ServeHTTP_Unsubscribe(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
delivery.NewHandler(delivery.NewHandlerParams{ delivery.NewHandler(delivery.NewHandlerParams{
Hub: hub, Hub: hub,
Subscriptions: subscriptionucase.NewSubscriptionUseCase(subscriptions, topics), Subscriptions: subscriptionucase.NewSubscriptionUseCase(subscriptions, topics, srv.Client()),
Topics: topicucase.NewTopicUseCase(topics, srv.Client()), Topics: topicucase.NewTopicUseCase(topics, srv.Client()),
Matcher: language.NewMatcher([]language.Tag{language.English}), Matcher: language.NewMatcher([]language.Tag{language.English}),
Name: "WebSub", Name: "WebSub",

View File

@ -2,8 +2,13 @@ package usecase_test
import ( import (
"context" "context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing" "testing"
"source.toby3d.me/toby3d/hub/internal/common"
"source.toby3d.me/toby3d/hub/internal/domain" "source.toby3d.me/toby3d/hub/internal/domain"
subscriptionmemoryrepo "source.toby3d.me/toby3d/hub/internal/subscription/repository/memory" subscriptionmemoryrepo "source.toby3d.me/toby3d/hub/internal/subscription/repository/memory"
"source.toby3d.me/toby3d/hub/internal/subscription/usecase" "source.toby3d.me/toby3d/hub/internal/subscription/usecase"
@ -13,11 +18,24 @@ import (
func TestSubscriptionUseCase_Subscribe(t *testing.T) { func TestSubscriptionUseCase_Subscribe(t *testing.T) {
t.Parallel() t.Parallel()
subscription := domain.TestSubscription(t, "https://example.com/") topic := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(common.HeaderContentType, common.MIMETextPlainCharsetUTF8)
fmt.Fprint(w, "hello, world")
}))
t.Cleanup(topic.Close)
callback := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(common.HeaderContentType, common.MIMETextPlainCharsetUTF8)
fmt.Fprint(w, "hello, world")
}))
t.Cleanup(callback.Close)
subscription := domain.TestSubscription(t, callback.URL)
subscription.Topic, _ = url.Parse(topic.URL + "/")
topics := topicmemoryrepo.NewMemoryTopicRepository() topics := topicmemoryrepo.NewMemoryTopicRepository()
subscriptions := subscriptionmemoryrepo.NewMemorySubscriptionRepository() subscriptions := subscriptionmemoryrepo.NewMemorySubscriptionRepository()
ucase := usecase.NewSubscriptionUseCase(subscriptions, topics) ucase := usecase.NewSubscriptionUseCase(subscriptions, topics, callback.Client())
ok, err := ucase.Subscribe(context.Background(), *subscription) ok, err := ucase.Subscribe(context.Background(), *subscription)
if err != nil { if err != nil {
@ -49,6 +67,12 @@ func TestSubscriptionUseCase_Subscribe(t *testing.T) {
func TestSubscriptionUseCase_Unsubscribe(t *testing.T) { func TestSubscriptionUseCase_Unsubscribe(t *testing.T) {
t.Parallel() t.Parallel()
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(common.HeaderContentType, common.MIMETextPlainCharsetUTF8)
fmt.Fprint(w, "hello, world")
}))
t.Cleanup(srv.Close)
subscription := domain.TestSubscription(t, "https://example.com/") subscription := domain.TestSubscription(t, "https://example.com/")
topics := topicmemoryrepo.NewMemoryTopicRepository() topics := topicmemoryrepo.NewMemoryTopicRepository()
subscriptions := subscriptionmemoryrepo.NewMemorySubscriptionRepository() subscriptions := subscriptionmemoryrepo.NewMemorySubscriptionRepository()
@ -57,7 +81,7 @@ func TestSubscriptionUseCase_Unsubscribe(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
ok, err := usecase.NewSubscriptionUseCase(subscriptions, topics). ok, err := usecase.NewSubscriptionUseCase(subscriptions, topics, srv.Client()).
Unsubscribe(context.Background(), *subscription) Unsubscribe(context.Background(), *subscription)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -1,6 +1,13 @@
{ {
"language": "en", "language": "en",
"messages": [ "messages": [
{
"id": "version",
"message": "version",
"translation": "version",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{ {
"id": "{Name} logo", "id": "{Name} logo",
"message": "{Name} logo", "message": "{Name} logo",

View File

@ -1,6 +1,11 @@
{ {
"language": "ru", "language": "ru",
"messages": [ "messages": [
{
"id": "version",
"message": "version",
"translation": "версия"
},
{ {
"id": "{Name} logo", "id": "{Name} logo",
"message": "{Name} logo", "message": "{Name} logo",

View File

@ -1,6 +1,11 @@
{ {
"language": "ru", "language": "ru",
"messages": [ "messages": [
{
"id": "version",
"message": "version",
"translation": "версия"
},
{ {
"id": "{Name} logo", "id": "{Name} logo",
"message": "{Name} logo", "message": "{Name} logo",

View File

@ -14,7 +14,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
"golang.org/x/text/feature/plural" "golang.org/x/text/feature/plural"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
@ -56,7 +56,11 @@ func main() {
ctx := context.Background() ctx := context.Background()
config := new(domain.Config) config := new(domain.Config)
if err := env.Parse(config, env.Options{Prefix: "HUB_"}); err != nil { if err := env.Parse(config, env.Options{
Prefix: "HUB_",
TagName: "env",
UseFieldNameByDefault: true,
}); err != nil {
logger.Fatalln(err) logger.Fatalln(err)
} }
@ -100,6 +104,7 @@ func main() {
go hubService.ListenAndServe(ctx) go hubService.ListenAndServe(ctx)
logger.Printf("started %s on %s: %s", config.Name, config.Bind, config.BaseURL.String())
if err = server.ListenAndServe(); err != nil { if err = server.ListenAndServe(); err != nil {
logger.Fatalln(err) logger.Fatalln(err)
} }

View File

@ -1,8 +1,8 @@
# env # env
[![Build Status](https://img.shields.io/github/workflow/status/caarlos0/env/build?style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build) [![Build Status](https://img.shields.io/github/actions/workflow/status/caarlos0/env/build.yml?branch=main&style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build)
[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env) [![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env)
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v6) [![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v7)
A simple and zero-dependencies library to parse environment variables into structs. A simple and zero-dependencies library to parse environment variables into structs.
@ -11,7 +11,7 @@ A simple and zero-dependencies library to parse environment variables into struc
Get the module with: Get the module with:
```sh ```sh
go get github.com/caarlos0/env/v6 go get github.com/caarlos0/env/v7
``` ```
The usage looks like this: The usage looks like this:
@ -23,7 +23,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type config struct { type config struct {
@ -53,7 +53,14 @@ $ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go
{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s} {Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s}
``` ```
⚠️⚠️⚠️ **Attention:** _unexported fields_ will be **ignored**. ## Caveats
> **Warning**
>
> **This is important!**
- _Unexported fields_ are **ignored**
## Supported types and defaults ## Supported types and defaults
@ -80,11 +87,15 @@ Complete list:
- `encoding.TextUnmarshaler` - `encoding.TextUnmarshaler`
- `url.URL` - `url.URL`
Pointers, slices and slices of pointers of those types are also supported. Pointers, slices and slices of pointers, and maps of those types are also
supported.
You can also use/define a [custom parser func](#custom-parser-funcs) for any You can also use/define a [custom parser func](#custom-parser-funcs) for any
other type you want. other type you want.
You can also use custom keys and values in your maps, as long as you provide a
parser function for them.
If you set the `envDefault` tag for something, this value will be used in the If you set the `envDefault` tag for something, this value will be used in the
case of absence of it in the environment. case of absence of it in the environment.
@ -107,7 +118,7 @@ also accepts a `map[reflect.Type]env.ParserFunc`.
If you add a custom parser for, say `Foo`, it will also be used to parse If you add a custom parser for, say `Foo`, it will also be used to parse
`*Foo` and `[]Foo` types. `*Foo` and `[]Foo` types.
Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v6) Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v7)
for more info. for more info.
### A note about `TextUnmarshaler` and `time.Time` ### A note about `TextUnmarshaler` and `time.Time`
@ -148,7 +159,7 @@ type config struct {
## Not Empty fields ## Not Empty fields
While `required` demands the environment variable to be check, it doesn't check its value. While `required` demands the environment variable to be set, it doesn't check its value.
If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`). If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
Example: Example:
@ -185,7 +196,7 @@ package main
import ( import (
"fmt" "fmt"
"time" "time"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type config struct { type config struct {
@ -217,6 +228,46 @@ $ SECRET=/tmp/secret \
## Options ## Options
### Use field names as environment variables by default
If you don't want to set the `env` tag on every field, you can use the
`UseFieldNameByDefault` option.
It will use the field name as environment variable name.
Here's an example:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string // will use $USERNAME
Password string // will use $PASSWORD
UserFullName string // will use $USER_FULL_NAME
}
func main() {
cfg := &Config{}
opts := &env.Options{UseFieldNameByDefault: true}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
### Environment ### Environment
By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values` By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
@ -232,7 +283,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -251,7 +302,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -268,7 +319,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -285,7 +336,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -302,7 +353,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -318,7 +369,7 @@ type ComplexConfig struct {
func main() { func main() {
cfg := ComplexConfig{} cfg := ComplexConfig{}
if err := Parse(&cfg, Options{ if err := Parse(&cfg, Options{
Prefix: "T_", Prefix: "T_",
Environment: map[string]string{ Environment: map[string]string{
"T_FOO_HOME": "/foo", "T_FOO_HOME": "/foo",
@ -336,7 +387,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -352,7 +403,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -374,7 +425,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -391,7 +442,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -409,7 +460,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -425,7 +476,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -447,6 +498,54 @@ func main() {
} }
``` ```
## Error handling
You can handle the errors the library throws like so:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string `env:"USERNAME" envDefault:"admin"`
Password string `env:"PASSWORD"`
}
func main() {
var cfg Config
err := env.Parse(&cfg)
if e, ok := err.(*env.AggregateError); ok {
for _, er := range e.Errors {
switch v := er.(type) {
case env.ParseError:
// handle it
case env.NotStructPtrError:
// handle it
case env.NoParserError:
// handle it
case env.NoSupportedTagOptionError:
// handle it
default:
fmt.Printf("Unknown error type %v", v)
}
}
}
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
}
```
> **Info**
>
> If you want to check if an specific error is in the chain, you can also use
> `errors.Is()`.
## Stargazers over time ## Stargazers over time
[![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env) [![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)

View File

@ -2,7 +2,6 @@ package env
import ( import (
"encoding" "encoding"
"errors"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
@ -10,14 +9,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
) )
// nolint: gochecknoglobals // nolint: gochecknoglobals
var ( var (
// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
// Struct to Parse.
ErrNotAStructPtr = errors.New("env: expected a pointer to a Struct")
defaultBuiltInParsers = map[reflect.Kind]ParserFunc{ defaultBuiltInParsers = map[reflect.Kind]ParserFunc{
reflect.Bool: func(v string) (interface{}, error) { reflect.Bool: func(v string) (interface{}, error) {
return strconv.ParseBool(v) return strconv.ParseBool(v)
@ -79,14 +75,14 @@ func defaultTypeParsers() map[reflect.Type]ParserFunc {
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) { reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
u, err := url.Parse(v) u, err := url.Parse(v)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse URL: %v", err) return nil, newParseValueError("unable to parse URL", err)
} }
return *u, nil return *u, nil
}, },
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) { reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
s, err := time.ParseDuration(v) s, err := time.ParseDuration(v)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse duration: %v", err) return nil, newParseValueError("unable to parse duration", err)
} }
return s, err return s, err
}, },
@ -107,15 +103,20 @@ type Options struct {
// TagName specifies another tagname to use rather than the default env. // TagName specifies another tagname to use rather than the default env.
TagName string TagName string
// RequiredIfNoDef automatically sets all env as required if they do not declare 'envDefault' // RequiredIfNoDef automatically sets all env as required if they do not
// declare 'envDefault'.
RequiredIfNoDef bool RequiredIfNoDef bool
// OnSet allows to run a function when a value is set // OnSet allows to run a function when a value is set.
OnSet OnSetFn OnSet OnSetFn
// Prefix define a prefix for each key // Prefix define a prefix for each key.
Prefix string Prefix string
// UseFieldNameByDefault defines whether or not env should use the field
// name by default if the `env` key is missing.
UseFieldNameByDefault bool
// Sets to true if we have already configured once. // Sets to true if we have already configured once.
configured bool configured bool
} }
@ -150,6 +151,7 @@ func configure(opts []Options) []Options {
if item.Prefix != "" { if item.Prefix != "" {
opt.Prefix = item.Prefix opt.Prefix = item.Prefix
} }
opt.UseFieldNameByDefault = item.UseFieldNameByDefault
opt.RequiredIfNoDef = item.RequiredIfNoDef opt.RequiredIfNoDef = item.RequiredIfNoDef
} }
@ -183,11 +185,11 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
ptrRef := reflect.ValueOf(v) ptrRef := reflect.ValueOf(v)
if ptrRef.Kind() != reflect.Ptr { if ptrRef.Kind() != reflect.Ptr {
return ErrNotAStructPtr return newAggregateError(NotStructPtrError{})
} }
ref := ptrRef.Elem() ref := ptrRef.Elem()
if ref.Kind() != reflect.Struct { if ref.Kind() != reflect.Struct {
return ErrNotAStructPtr return newAggregateError(NotStructPtrError{})
} }
parsers := defaultTypeParsers() parsers := defaultTypeParsers()
for k, v := range funcMap { for k, v := range funcMap {
@ -200,22 +202,22 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error { func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error {
refType := ref.Type() refType := ref.Type()
var agrErr aggregateError var agrErr AggregateError
for i := 0; i < refType.NumField(); i++ { for i := 0; i < refType.NumField(); i++ {
refField := ref.Field(i) refField := ref.Field(i)
refTypeField := refType.Field(i) refTypeField := refType.Field(i)
if err := doParseField(refField, refTypeField, funcMap, opts); err != nil { if err := doParseField(refField, refTypeField, funcMap, opts); err != nil {
if val, ok := err.(aggregateError); ok { if val, ok := err.(AggregateError); ok {
agrErr.errors = append(agrErr.errors, val.errors...) agrErr.Errors = append(agrErr.Errors, val.Errors...)
} else { } else {
agrErr.errors = append(agrErr.errors, err) agrErr.Errors = append(agrErr.Errors, err)
} }
} }
} }
if len(agrErr.errors) == 0 { if len(agrErr.Errors) == 0 {
return nil return nil
} }
@ -226,7 +228,7 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
if !refField.CanSet() { if !refField.CanSet() {
return nil return nil
} }
if reflect.Ptr == refField.Kind() && refField.Elem().Kind() == reflect.Struct { if reflect.Ptr == refField.Kind() && !refField.IsNil() {
return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...) return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...)
} }
if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" { if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" {
@ -248,6 +250,19 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
return nil return nil
} }
const underscore rune = '_'
func toEnvName(input string) string {
var output []rune
for i, c := range input {
if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c {
output = append(output, underscore)
}
output = append(output, unicode.ToUpper(c))
}
return string(output)
}
func get(field reflect.StructField, opts []Options) (val string, err error) { func get(field reflect.StructField, opts []Options) (val string, err error) {
var exists bool var exists bool
var isDefault bool var isDefault bool
@ -258,6 +273,9 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
required := opts[0].RequiredIfNoDef required := opts[0].RequiredIfNoDef
prefix := opts[0].Prefix prefix := opts[0].Prefix
ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts))) ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts)))
if ownKey == "" && opts[0].UseFieldNameByDefault {
ownKey = toEnvName(field.Name)
}
key := prefix + ownKey key := prefix + ownKey
for _, tag := range tags { for _, tag := range tags {
switch tag { switch tag {
@ -272,7 +290,7 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
case "notEmpty": case "notEmpty":
notEmpty = true notEmpty = true
default: default:
return "", fmt.Errorf("tag option %q not supported", tag) return "", newNoSupportedTagOptionError(tag)
} }
} }
expand := strings.EqualFold(field.Tag.Get("envExpand"), "true") expand := strings.EqualFold(field.Tag.Get("envExpand"), "true")
@ -288,18 +306,18 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
} }
if required && !exists && len(ownKey) > 0 { if required && !exists && len(ownKey) > 0 {
return "", fmt.Errorf(`required environment variable %q is not set`, key) return "", newEnvVarIsNotSet(key)
} }
if notEmpty && val == "" { if notEmpty && val == "" {
return "", fmt.Errorf("environment variable %q should not be empty", key) return "", newEmptyEnvVarError(key)
} }
if loadFile && val != "" { if loadFile && val != "" {
filename := val filename := val
val, err = getFromFile(filename) val, err = getFromFile(filename)
if err != nil { if err != nil {
return "", fmt.Errorf(`could not load content of file "%s" from variable %s: %v`, filename, key, err) return "", newLoadFileContentError(filename, key, err)
} }
} }
@ -325,6 +343,8 @@ func getOr(key, defaultValue string, defExists bool, envs map[string]string) (st
switch { switch {
case (!exists || key == "") && defExists: case (!exists || key == "") && defExists:
return defaultValue, true, true return defaultValue, true, true
case exists && value == "" && defExists:
return defaultValue, true, true
case !exists: case !exists:
return "", false, false return "", false, false
} }
@ -369,8 +389,11 @@ func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[
return nil return nil
} }
if field.Kind() == reflect.Slice { switch field.Kind() {
case reflect.Slice:
return handleSlice(field, value, sf, funcMap) return handleSlice(field, value, sf, funcMap)
case reflect.Map:
return handleMap(field, value, sf, funcMap)
} }
return newNoParserError(sf) return newNoParserError(sf)
@ -417,6 +440,54 @@ func handleSlice(field reflect.Value, value string, sf reflect.StructField, func
return nil return nil
} }
func handleMap(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
keyType := sf.Type.Key()
keyParserFunc, ok := funcMap[keyType]
if !ok {
keyParserFunc, ok = defaultBuiltInParsers[keyType.Kind()]
if !ok {
return newNoParserError(sf)
}
}
elemType := sf.Type.Elem()
elemParserFunc, ok := funcMap[elemType]
if !ok {
elemParserFunc, ok = defaultBuiltInParsers[elemType.Kind()]
if !ok {
return newNoParserError(sf)
}
}
separator := sf.Tag.Get("envSeparator")
if separator == "" {
separator = ","
}
result := reflect.MakeMap(sf.Type)
for _, part := range strings.Split(value, separator) {
pairs := strings.Split(part, ":")
if len(pairs) != 2 {
return newParseError(sf, fmt.Errorf(`%q should be in "key:value" format`, part))
}
key, err := keyParserFunc(pairs[0])
if err != nil {
return newParseError(sf, err)
}
elem, err := elemParserFunc(pairs[1])
if err != nil {
return newParseError(sf, err)
}
result.SetMapIndex(reflect.ValueOf(key).Convert(keyType), reflect.ValueOf(elem).Convert(elemType))
}
field.Set(result)
return nil
}
func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler { func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler {
if reflect.Ptr == field.Kind() { if reflect.Ptr == field.Kind() {
if field.IsNil() { if field.IsNil() {
@ -459,26 +530,6 @@ func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.Struct
return nil return nil
} }
func newParseError(sf reflect.StructField, err error) error {
return parseError{
sf: sf,
err: err,
}
}
type parseError struct {
sf reflect.StructField
err error
}
func (e parseError) Error() string {
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.sf.Name, e.sf.Type, e.err)
}
func newNoParserError(sf reflect.StructField) error {
return fmt.Errorf(`no parser found for field "%s" of type "%s"`, sf.Name, sf.Type)
}
func optsWithPrefix(field reflect.StructField, opts []Options) []Options { func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
subOpts := make([]Options, len(opts)) subOpts := make([]Options, len(opts))
copy(subOpts, opts) copy(subOpts, opts)
@ -487,18 +538,3 @@ func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
} }
return subOpts return subOpts
} }
type aggregateError struct {
errors []error
}
func (e aggregateError) Error() string {
var sb strings.Builder
sb.WriteString("env:")
for _, err := range e.errors {
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
}
return strings.TrimRight(sb.String(), ";")
}

164
vendor/github.com/caarlos0/env/v7/error.go generated vendored Normal file
View File

@ -0,0 +1,164 @@
package env
import (
"fmt"
"reflect"
"strings"
)
// An aggregated error wrapper to combine gathered errors. This allows either to display all errors or convert them individually
// List of the available errors
// ParseError
// NotStructPtrError
// NoParserError
// NoSupportedTagOptionError
// EnvVarIsNotSetError
// EmptyEnvVarError
// LoadFileContentError
// ParseValueError
type AggregateError struct {
Errors []error
}
func newAggregateError(initErr error) error {
return AggregateError{
[]error{
initErr,
},
}
}
func (e AggregateError) Error() string {
var sb strings.Builder
sb.WriteString("env:")
for _, err := range e.Errors {
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
}
return strings.TrimRight(sb.String(), ";")
}
// Is conforms with errors.Is.
func (e AggregateError) Is(err error) bool {
for _, ie := range e.Errors {
if reflect.TypeOf(ie) == reflect.TypeOf(err) {
return true
}
}
return false
}
// The error occurs when it's impossible to convert the value for given type.
type ParseError struct {
Name string
Type reflect.Type
Err error
}
func newParseError(sf reflect.StructField, err error) error {
return ParseError{sf.Name, sf.Type, err}
}
func (e ParseError) Error() string {
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.Name, e.Type, e.Err)
}
// The error occurs when pass something that is not a pointer to a Struct to Parse
type NotStructPtrError struct{}
func (e NotStructPtrError) Error() string {
return "expected a pointer to a Struct"
}
// This error occurs when there is no parser provided for given type
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
type NoParserError struct {
Name string
Type reflect.Type
}
func newNoParserError(sf reflect.StructField) error {
return NoParserError{sf.Name, sf.Type}
}
func (e NoParserError) Error() string {
return fmt.Sprintf(`no parser found for field "%s" of type "%s"`, e.Name, e.Type)
}
// This error occurs when the given tag is not supported
// In-built supported tags: "", "file", "required", "unset", "notEmpty", "envDefault", "envExpand", "envSeparator"
// How to create a custom tag: https://github.com/caarlos0/env#changing-default-tag-name
type NoSupportedTagOptionError struct {
Tag string
}
func newNoSupportedTagOptionError(tag string) error {
return NoSupportedTagOptionError{tag}
}
func (e NoSupportedTagOptionError) Error() string {
return fmt.Sprintf("tag option %q not supported", e.Tag)
}
// This error occurs when the required variable is not set
// Read about required fields: https://github.com/caarlos0/env#required-fields
type EnvVarIsNotSetError struct {
Key string
}
func newEnvVarIsNotSet(key string) error {
return EnvVarIsNotSetError{key}
}
func (e EnvVarIsNotSetError) Error() string {
return fmt.Sprintf(`required environment variable %q is not set`, e.Key)
}
// This error occurs when the variable which must be not empty is existing but has an empty value
// Read about not empty fields: https://github.com/caarlos0/env#not-empty-fields
type EmptyEnvVarError struct {
Key string
}
func newEmptyEnvVarError(key string) error {
return EmptyEnvVarError{key}
}
func (e EmptyEnvVarError) Error() string {
return fmt.Sprintf("environment variable %q should not be empty", e.Key)
}
// This error occurs when it's impossible to load the value from the file
// Read about From file feature: https://github.com/caarlos0/env#from-file
type LoadFileContentError struct {
Filename string
Key string
Err error
}
func newLoadFileContentError(filename, key string, err error) error {
return LoadFileContentError{filename, key, err}
}
func (e LoadFileContentError) Error() string {
return fmt.Sprintf(`could not load content of file "%s" from variable %s: %v`, e.Filename, e.Key, e.Err)
}
// This error occurs when it's impossible to convert value using given parser
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
type ParseValueError struct {
Msg string
Err error
}
func newParseValueError(message string, err error) error {
return ParseValueError{message, err}
}
func (e ParseValueError) Error() string {
return fmt.Sprintf("%s: %v", e.Msg, e.Err)
}

4
vendor/modules.txt vendored
View File

@ -1,9 +1,9 @@
# github.com/DATA-DOG/go-sqlmock v1.5.0 # github.com/DATA-DOG/go-sqlmock v1.5.0
## explicit ## explicit
github.com/DATA-DOG/go-sqlmock github.com/DATA-DOG/go-sqlmock
# github.com/caarlos0/env/v6 v6.10.1 # github.com/caarlos0/env/v7 v7.1.0
## explicit; go 1.17 ## explicit; go 1.17
github.com/caarlos0/env/v6 github.com/caarlos0/env/v7
# github.com/dustin/go-humanize v1.0.1 # github.com/dustin/go-humanize v1.0.1
## explicit; go 1.16 ## explicit; go 1.16
github.com/dustin/go-humanize github.com/dustin/go-humanize

View File

@ -1,4 +1,6 @@
{% import ( {% import (
"runtime/debug"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
) %} ) %}
@ -81,6 +83,53 @@ func NewBaseOf(lang language.Tag, name string) *BaseOf {
<body class="page__body cover"> <body class="page__body cover">
{%= p.body() %} {%= p.body() %}
{% code
var path, vcsRevision string
if bi, ok := debug.ReadBuildInfo(); ok {
path = bi.Path
for i := range bi.Settings {
if bi.Settings[i].Key != "vcs.revision" {
continue
}
vcsRevision = bi.Settings[i].Value
}
}
%}
<footer class="[ body__footer ][ center ][ text-align_center ]">
<p>
Made with
<span role="img"
aria-label="love">
❤️
</span>
to
<a rel="external"
href="https://{%s path %}">
open source
</a>
by
<a rel="author"
hreflang="en"
href="https://toby3d.me/">
toby3d
</a>
</p>
{% if vcsRevision != "" %}
<small>
{%= p.t("version") %}
<a href="https://{%s path %}/commit/{%s vcsRevision %}"
target="_blank">
{%s vcsRevision[:7] -%}
</a>
</small>
{% endif %}
</footer>
</body> </body>
</html> </html>
{% endfunc %} {% endfunc %}

View File

@ -6,59 +6,61 @@ package template
//line web/template/baseof.qtpl:1 //line web/template/baseof.qtpl:1
import ( import (
"runtime/debug"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
) )
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
type Page interface { type Page interface {
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
body() string body() string
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
streambody(qw422016 *qt422016.Writer) streambody(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
writebody(qq422016 qtio422016.Writer) writebody(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
head() string head() string
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
streamhead(qw422016 *qt422016.Writer) streamhead(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
writehead(qq422016 qtio422016.Writer) writehead(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
lang() string lang() string
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
streamlang(qw422016 *qt422016.Writer) streamlang(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
writelang(qq422016 qtio422016.Writer) writelang(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
t(format message.Reference, args ...any) string t(format message.Reference, args ...any) string
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
streamt(qw422016 *qt422016.Writer, format message.Reference, args ...any) streamt(qw422016 *qt422016.Writer, format message.Reference, args ...any)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
writet(qq422016 qtio422016.Writer, format message.Reference, args ...any) writet(qq422016 qtio422016.Writer, format message.Reference, args ...any)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
title() string title() string
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
streamtitle(qw422016 *qt422016.Writer) streamtitle(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
writetitle(qq422016 qtio422016.Writer) writetitle(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:6 //line web/template/baseof.qtpl:8
} }
//line web/template/baseof.qtpl:15 //line web/template/baseof.qtpl:17
type BaseOf struct { type BaseOf struct {
language language.Tag language language.Tag
printer *message.Printer printer *message.Printer
@ -73,228 +75,273 @@ func NewBaseOf(lang language.Tag, name string) *BaseOf {
} }
} }
//line web/template/baseof.qtpl:31 //line web/template/baseof.qtpl:33
func (p *BaseOf) streamlang(qw422016 *qt422016.Writer) { func (p *BaseOf) streamlang(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:31 //line web/template/baseof.qtpl:33
qw422016.N().S(` `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:32 //line web/template/baseof.qtpl:34
qw422016.E().S(p.language.String()) qw422016.E().S(p.language.String())
//line web/template/baseof.qtpl:32 //line web/template/baseof.qtpl:34
qw422016.N().S(` `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
} }
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
func (p *BaseOf) writelang(qq422016 qtio422016.Writer) { func (p *BaseOf) writelang(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
p.streamlang(qw422016) p.streamlang(qw422016)
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
} }
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
func (p *BaseOf) lang() string { func (p *BaseOf) lang() string {
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
p.writelang(qb422016) p.writelang(qb422016)
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
return qs422016 return qs422016
//line web/template/baseof.qtpl:33 //line web/template/baseof.qtpl:35
} }
//line web/template/baseof.qtpl:35 //line web/template/baseof.qtpl:37
func (p *BaseOf) streamtitle(qw422016 *qt422016.Writer) { func (p *BaseOf) streamtitle(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:35 //line web/template/baseof.qtpl:37
qw422016.N().S(` `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:36 //line web/template/baseof.qtpl:38
qw422016.E().S(p.name) qw422016.E().S(p.name)
//line web/template/baseof.qtpl:36 //line web/template/baseof.qtpl:38
qw422016.N().S(` `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
} }
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
func (p *BaseOf) writetitle(qq422016 qtio422016.Writer) { func (p *BaseOf) writetitle(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
p.streamtitle(qw422016) p.streamtitle(qw422016)
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
} }
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
func (p *BaseOf) title() string { func (p *BaseOf) title() string {
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
p.writetitle(qb422016) p.writetitle(qb422016)
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
return qs422016 return qs422016
//line web/template/baseof.qtpl:37 //line web/template/baseof.qtpl:39
} }
//line web/template/baseof.qtpl:39 //line web/template/baseof.qtpl:41
func (p *BaseOf) streamhead(qw422016 *qt422016.Writer) { func (p *BaseOf) streamhead(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:39 //line web/template/baseof.qtpl:41
qw422016.N().S(` `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:40 //line web/template/baseof.qtpl:42
qw422016.N().S(` <link rel="icon" href="/static/favicon.ico" sizes="any"> <link rel="icon" href="/static/icon.svg" type="image/svg+xml"> <link rel="apple-touch-icon" href="/static/apple-touch-icon.png"> <link rel="manifest" href="/static/manifest.webmanifest"> `) qw422016.N().S(` <link rel="manifest" href="/static/manifest.webmanifest"> <link rel="icon" href="/static/favicon.ico" sizes="any"> <link rel="icon" href="/static/icon.svg" type="image/svg+xml"> <link rel="apple-touch-icon" href="/static/apple-touch-icon.png"> `)
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
} }
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
func (p *BaseOf) writehead(qq422016 qtio422016.Writer) { func (p *BaseOf) writehead(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
p.streamhead(qw422016) p.streamhead(qw422016)
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
} }
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
func (p *BaseOf) head() string { func (p *BaseOf) head() string {
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
p.writehead(qb422016) p.writehead(qb422016)
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
return qs422016 return qs422016
//line web/template/baseof.qtpl:54 //line web/template/baseof.qtpl:56
} }
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
func (p *BaseOf) streambody(qw422016 *qt422016.Writer) { func (p *BaseOf) streambody(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
} }
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
func (p *BaseOf) writebody(qq422016 qtio422016.Writer) { func (p *BaseOf) writebody(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
p.streambody(qw422016) p.streambody(qw422016)
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
} }
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
func (p *BaseOf) body() string { func (p *BaseOf) body() string {
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
p.writebody(qb422016) p.writebody(qb422016)
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
return qs422016 return qs422016
//line web/template/baseof.qtpl:56 //line web/template/baseof.qtpl:58
} }
//line web/template/baseof.qtpl:58 //line web/template/baseof.qtpl:60
func (p *BaseOf) streamt(qw422016 *qt422016.Writer, format message.Reference, args ...any) { func (p *BaseOf) streamt(qw422016 *qt422016.Writer, format message.Reference, args ...any) {
//line web/template/baseof.qtpl:58 //line web/template/baseof.qtpl:60
qw422016.N().S(` `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:59 //line web/template/baseof.qtpl:61
qw422016.E().S(p.printer.Sprintf(format, args...)) qw422016.E().S(p.printer.Sprintf(format, args...))
//line web/template/baseof.qtpl:59 //line web/template/baseof.qtpl:61
qw422016.N().S(` `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
} }
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
func (p *BaseOf) writet(qq422016 qtio422016.Writer, format message.Reference, args ...any) { func (p *BaseOf) writet(qq422016 qtio422016.Writer, format message.Reference, args ...any) {
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
p.streamt(qw422016, format, args...) p.streamt(qw422016, format, args...)
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
} }
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
func (p *BaseOf) t(format message.Reference, args ...any) string { func (p *BaseOf) t(format message.Reference, args ...any) string {
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
p.writet(qb422016, format, args...) p.writet(qb422016, format, args...)
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
return qs422016 return qs422016
//line web/template/baseof.qtpl:60 //line web/template/baseof.qtpl:62
} }
//line web/template/baseof.qtpl:62 //line web/template/baseof.qtpl:64
func StreamTemplate(qw422016 *qt422016.Writer, p Page) { func StreamTemplate(qw422016 *qt422016.Writer, p Page) {
//line web/template/baseof.qtpl:62 //line web/template/baseof.qtpl:64
qw422016.N().S(` <!DOCTYPE html> <html class="page" lang="`) qw422016.N().S(` <!DOCTYPE html> <html class="page" lang="`)
//line web/template/baseof.qtpl:65 //line web/template/baseof.qtpl:67
p.streamlang(qw422016) p.streamlang(qw422016)
//line web/template/baseof.qtpl:65 //line web/template/baseof.qtpl:67
qw422016.N().S(`" dir="ltr"> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>`) qw422016.N().S(`" dir="ltr"> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>`)
//line web/template/baseof.qtpl:72 //line web/template/baseof.qtpl:74
p.streamtitle(qw422016) p.streamtitle(qw422016)
//line web/template/baseof.qtpl:72 //line web/template/baseof.qtpl:74
qw422016.N().S(`</title> <link rel="preload stylesheet" as="style" href="/static/style.css" type="text/css"> `) qw422016.N().S(`</title> <link rel="preload stylesheet" as="style" href="/static/style.css" type="text/css"> `)
//line web/template/baseof.qtpl:79 //line web/template/baseof.qtpl:81
p.streamhead(qw422016) p.streamhead(qw422016)
//line web/template/baseof.qtpl:79 //line web/template/baseof.qtpl:81
qw422016.N().S(` </head> <body class="page__body cover"> `) qw422016.N().S(` </head> <body class="page__body cover"> `)
//line web/template/baseof.qtpl:83 //line web/template/baseof.qtpl:85
p.streambody(qw422016) p.streambody(qw422016)
//line web/template/baseof.qtpl:83 //line web/template/baseof.qtpl:85
qw422016.N().S(` </body> </html> `) qw422016.N().S(` `)
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:88
var path, vcsRevision string
if bi, ok := debug.ReadBuildInfo(); ok {
path = bi.Path
for i := range bi.Settings {
if bi.Settings[i].Key != "vcs.revision" {
continue
}
vcsRevision = bi.Settings[i].Value
}
}
//line web/template/baseof.qtpl:101
qw422016.N().S(` <footer class="[ body__footer ][ center ][ text-align_center ]"> <p> Made with <span role="img" aria-label="love"> ❤️ </span> to <a rel="external" href="https://`)
//line web/template/baseof.qtpl:112
qw422016.E().S(path)
//line web/template/baseof.qtpl:112
qw422016.N().S(`"> open source </a> by <a rel="author" hreflang="en" href="https://toby3d.me/"> toby3d </a> </p> `)
//line web/template/baseof.qtpl:123
if vcsRevision != "" {
//line web/template/baseof.qtpl:123
qw422016.N().S(` <small> `)
//line web/template/baseof.qtpl:125
p.streamt(qw422016, "version")
//line web/template/baseof.qtpl:125
qw422016.N().S(` <a href="https://`)
//line web/template/baseof.qtpl:126
qw422016.E().S(path)
//line web/template/baseof.qtpl:126
qw422016.N().S(`/commit/`)
//line web/template/baseof.qtpl:126
qw422016.E().S(vcsRevision)
//line web/template/baseof.qtpl:126
qw422016.N().S(`" target="_blank"> `)
//line web/template/baseof.qtpl:128
qw422016.E().S(vcsRevision[:7])
//line web/template/baseof.qtpl:128
qw422016.N().S(`</a> </small> `)
//line web/template/baseof.qtpl:131
}
//line web/template/baseof.qtpl:131
qw422016.N().S(` </footer> </body> </html> `)
//line web/template/baseof.qtpl:135
} }
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
func WriteTemplate(qq422016 qtio422016.Writer, p Page) { func WriteTemplate(qq422016 qtio422016.Writer, p Page) {
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
StreamTemplate(qw422016, p) StreamTemplate(qw422016, p)
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
} }
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
func Template(p Page) string { func Template(p Page) string {
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
WriteTemplate(qb422016, p) WriteTemplate(qb422016, p)
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
return qs422016 return qs422016
//line web/template/baseof.qtpl:86 //line web/template/baseof.qtpl:135
} }

View File

@ -51,27 +51,5 @@
</li> </li>
</ul> </ul>
</main> </main>
<footer class="[ body__footer ][ center ][ text-align_center ]">
<p>
Made with
<span role="img"
aria-label="love">
❤️
</span>
to
<a rel="external"
href="https://source.toby3d.me/toby3d/hub">
open source
</a>
by
<a rel="author"
hreflang="en"
href="https://toby3d.me/">
toby3d
</a>
</p>
<small>v1.0.0</small>
</footer>
{% endfunc %} {% endfunc %}
{% endcollapsespace %} {% endcollapsespace %}

View File

@ -45,32 +45,32 @@ func (p *Home) streambody(qw422016 *qt422016.Writer) {
//line web/template/home.qtpl:49 //line web/template/home.qtpl:49
p.streamt(qw422016, "What the spec?") p.streamt(qw422016, "What the spec?")
//line web/template/home.qtpl:49 //line web/template/home.qtpl:49
qw422016.N().S(` </a> </li> </ul> </main> <footer class="[ body__footer ][ center ][ text-align_center ]"> <p> Made with <span role="img" aria-label="love"> ❤️ </span> to <a rel="external" href="https://source.toby3d.me/toby3d/hub"> open source </a> by <a rel="author" hreflang="en" href="https://toby3d.me/"> toby3d </a> </p> <small>v1.0.0</small> </footer> `) qw422016.N().S(` </a> </li> </ul> </main> `)
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
} }
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
func (p *Home) writebody(qq422016 qtio422016.Writer) { func (p *Home) writebody(qq422016 qtio422016.Writer) {
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
p.streambody(qw422016) p.streambody(qw422016)
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
} }
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
func (p *Home) body() string { func (p *Home) body() string {
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
p.writebody(qb422016) p.writebody(qb422016)
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
return qs422016 return qs422016
//line web/template/home.qtpl:76 //line web/template/home.qtpl:54
} }