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{
"%d subscribers": 4,
"%s logo": 0,
"Dead simple WebSub hub": 1,
"How to publish and consume?": 2,
"What the spec?": 3,
"%d subscribers": 5,
"%s logo": 1,
"Dead simple WebSub hub": 2,
"How to publish and consume?": 3,
"What the spec?": 4,
"version": 0,
}
var enIndex = []uint32{ // 6 elements
0x00000000, 0x0000000b, 0x00000022, 0x0000003e,
0x0000004d, 0x0000005f,
} // Size: 48 bytes
var enIndex = []uint32{ // 7 elements
0x00000000, 0x00000008, 0x00000013, 0x0000002a,
0x00000046, 0x00000055, 0x00000067,
} // Size: 52 bytes
const enData string = "" + // Size: 95 bytes
"\x02%[1]s logo\x02Dead simple WebSub hub\x02How to publish and consume?" +
"\x02What the spec?\x02%[1]d subscribers"
const enData string = "" + // Size: 103 bytes
"\x02version\x02%[1]s logo\x02Dead simple WebSub hub\x02How to publish an" +
"d consume?\x02What the spec?\x02%[1]d subscribers"
var ruIndex = []uint32{ // 6 elements
0x00000000, 0x00000015, 0x00000038, 0x0000006d,
0x00000083, 0x000000a0,
} // Size: 48 bytes
var ruIndex = []uint32{ // 7 elements
0x00000000, 0x0000000d, 0x00000022, 0x00000045,
0x0000007a, 0x00000090, 0x000000ad,
} // Size: 52 bytes
const ruData string = "" + // Size: 160 bytes
"\x02логотип %[1]s\x02Простейший хаб WebSub\x02Как публиковать и принимат" +
"ь?\x02В чём спека?\x02%[1]d подписчиков"
const ruData string = "" + // Size: 173 bytes
"\x02версия\x02логотип %[1]s\x02Простейший хаб WebSub\x02Как публиковать " +
"и принимать?\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 (
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/jmoiron/sqlx v1.3.5
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/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
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/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
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/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
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 {
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"`
}

View File

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

View File

@ -2,8 +2,13 @@ package usecase_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"source.toby3d.me/toby3d/hub/internal/common"
"source.toby3d.me/toby3d/hub/internal/domain"
subscriptionmemoryrepo "source.toby3d.me/toby3d/hub/internal/subscription/repository/memory"
"source.toby3d.me/toby3d/hub/internal/subscription/usecase"
@ -13,11 +18,24 @@ import (
func TestSubscriptionUseCase_Subscribe(t *testing.T) {
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()
subscriptions := subscriptionmemoryrepo.NewMemorySubscriptionRepository()
ucase := usecase.NewSubscriptionUseCase(subscriptions, topics)
ucase := usecase.NewSubscriptionUseCase(subscriptions, topics, callback.Client())
ok, err := ucase.Subscribe(context.Background(), *subscription)
if err != nil {
@ -49,6 +67,12 @@ func TestSubscriptionUseCase_Subscribe(t *testing.T) {
func TestSubscriptionUseCase_Unsubscribe(t *testing.T) {
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/")
topics := topicmemoryrepo.NewMemoryTopicRepository()
subscriptions := subscriptionmemoryrepo.NewMemorySubscriptionRepository()
@ -57,7 +81,7 @@ func TestSubscriptionUseCase_Unsubscribe(t *testing.T) {
t.Fatal(err)
}
ok, err := usecase.NewSubscriptionUseCase(subscriptions, topics).
ok, err := usecase.NewSubscriptionUseCase(subscriptions, topics, srv.Client()).
Unsubscribe(context.Background(), *subscription)
if err != nil {
t.Fatal(err)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
# 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)
[![](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.
@ -11,7 +11,7 @@ A simple and zero-dependencies library to parse environment variables into struc
Get the module with:
```sh
go get github.com/caarlos0/env/v6
go get github.com/caarlos0/env/v7
```
The usage looks like this:
@ -23,7 +23,7 @@ import (
"fmt"
"time"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
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}
```
⚠️⚠️⚠️ **Attention:** _unexported fields_ will be **ignored**.
## Caveats
> **Warning**
>
> **This is important!**
- _Unexported fields_ are **ignored**
## Supported types and defaults
@ -80,11 +87,15 @@ Complete list:
- `encoding.TextUnmarshaler`
- `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
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
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
`*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.
### A note about `TextUnmarshaler` and `time.Time`
@ -148,7 +159,7 @@ type config struct {
## 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"`).
Example:
@ -185,7 +196,7 @@ package main
import (
"fmt"
"time"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type config struct {
@ -217,6 +228,46 @@ $ SECRET=/tmp/secret \
## 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
By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
@ -232,7 +283,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -251,7 +302,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -268,7 +319,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -285,7 +336,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -302,7 +353,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -318,7 +369,7 @@ type ComplexConfig struct {
func main() {
cfg := ComplexConfig{}
if err := Parse(&cfg, Options{
if err := Parse(&cfg, Options{
Prefix: "T_",
Environment: map[string]string{
"T_FOO_HOME": "/foo",
@ -336,7 +387,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -352,7 +403,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -374,7 +425,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -391,7 +442,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -409,7 +460,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -425,7 +476,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
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](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)

View File

@ -2,7 +2,6 @@ package env
import (
"encoding"
"errors"
"fmt"
"net/url"
"os"
@ -10,14 +9,11 @@ import (
"strconv"
"strings"
"time"
"unicode"
)
// nolint: gochecknoglobals
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{
reflect.Bool: func(v string) (interface{}, error) {
return strconv.ParseBool(v)
@ -79,14 +75,14 @@ func defaultTypeParsers() map[reflect.Type]ParserFunc {
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
u, err := url.Parse(v)
if err != nil {
return nil, fmt.Errorf("unable to parse URL: %v", err)
return nil, newParseValueError("unable to parse URL", err)
}
return *u, nil
},
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
s, err := time.ParseDuration(v)
if err != nil {
return nil, fmt.Errorf("unable to parse duration: %v", err)
return nil, newParseValueError("unable to parse duration", err)
}
return s, err
},
@ -107,15 +103,20 @@ type Options struct {
// TagName specifies another tagname to use rather than the default env.
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
// OnSet allows to run a function when a value is set
// OnSet allows to run a function when a value is set.
OnSet OnSetFn
// Prefix define a prefix for each key
// Prefix define a prefix for each key.
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.
configured bool
}
@ -150,6 +151,7 @@ func configure(opts []Options) []Options {
if item.Prefix != "" {
opt.Prefix = item.Prefix
}
opt.UseFieldNameByDefault = item.UseFieldNameByDefault
opt.RequiredIfNoDef = item.RequiredIfNoDef
}
@ -183,11 +185,11 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
ptrRef := reflect.ValueOf(v)
if ptrRef.Kind() != reflect.Ptr {
return ErrNotAStructPtr
return newAggregateError(NotStructPtrError{})
}
ref := ptrRef.Elem()
if ref.Kind() != reflect.Struct {
return ErrNotAStructPtr
return newAggregateError(NotStructPtrError{})
}
parsers := defaultTypeParsers()
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 {
refType := ref.Type()
var agrErr aggregateError
var agrErr AggregateError
for i := 0; i < refType.NumField(); i++ {
refField := ref.Field(i)
refTypeField := refType.Field(i)
if err := doParseField(refField, refTypeField, funcMap, opts); err != nil {
if val, ok := err.(aggregateError); ok {
agrErr.errors = append(agrErr.errors, val.errors...)
if val, ok := err.(AggregateError); ok {
agrErr.Errors = append(agrErr.Errors, val.Errors...)
} 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
}
@ -226,7 +228,7 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
if !refField.CanSet() {
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)...)
}
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
}
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) {
var exists bool
var isDefault bool
@ -258,6 +273,9 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
required := opts[0].RequiredIfNoDef
prefix := opts[0].Prefix
ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts)))
if ownKey == "" && opts[0].UseFieldNameByDefault {
ownKey = toEnvName(field.Name)
}
key := prefix + ownKey
for _, tag := range tags {
switch tag {
@ -272,7 +290,7 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
case "notEmpty":
notEmpty = true
default:
return "", fmt.Errorf("tag option %q not supported", tag)
return "", newNoSupportedTagOptionError(tag)
}
}
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 {
return "", fmt.Errorf(`required environment variable %q is not set`, key)
return "", newEnvVarIsNotSet(key)
}
if notEmpty && val == "" {
return "", fmt.Errorf("environment variable %q should not be empty", key)
return "", newEmptyEnvVarError(key)
}
if loadFile && val != "" {
filename := val
val, err = getFromFile(filename)
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 {
case (!exists || key == "") && defExists:
return defaultValue, true, true
case exists && value == "" && defExists:
return defaultValue, true, true
case !exists:
return "", false, false
}
@ -369,8 +389,11 @@ func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[
return nil
}
if field.Kind() == reflect.Slice {
switch field.Kind() {
case reflect.Slice:
return handleSlice(field, value, sf, funcMap)
case reflect.Map:
return handleMap(field, value, sf, funcMap)
}
return newNoParserError(sf)
@ -417,6 +440,54 @@ func handleSlice(field reflect.Value, value string, sf reflect.StructField, func
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 {
if reflect.Ptr == field.Kind() {
if field.IsNil() {
@ -459,26 +530,6 @@ func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.Struct
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 {
subOpts := make([]Options, len(opts))
copy(subOpts, opts)
@ -487,18 +538,3 @@ func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
}
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
## explicit
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
github.com/caarlos0/env/v6
github.com/caarlos0/env/v7
# github.com/dustin/go-humanize v1.0.1
## explicit; go 1.16
github.com/dustin/go-humanize

View File

@ -1,4 +1,6 @@
{% import (
"runtime/debug"
"golang.org/x/text/language"
"golang.org/x/text/message"
) %}
@ -81,6 +83,53 @@ func NewBaseOf(lang language.Tag, name string) *BaseOf {
<body class="page__body cover">
{%= 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>
</html>
{% endfunc %}

View File

@ -6,59 +6,61 @@ package template
//line web/template/baseof.qtpl:1
import (
"runtime/debug"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
type Page interface {
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
body() string
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
streambody(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
writebody(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
head() string
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
streamhead(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
writehead(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
lang() string
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
streamlang(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
writelang(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
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)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
writet(qq422016 qtio422016.Writer, format message.Reference, args ...any)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
title() string
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
streamtitle(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:6
//line web/template/baseof.qtpl:8
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 {
language language.Tag
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) {
//line web/template/baseof.qtpl:31
//line web/template/baseof.qtpl:33
qw422016.N().S(` `)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:34
qw422016.E().S(p.language.String())
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:34
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) {
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
p.streamlang(qw422016)
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
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 {
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
p.writelang(qb422016)
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:33
//line web/template/baseof.qtpl:35
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) {
//line web/template/baseof.qtpl:35
//line web/template/baseof.qtpl:37
qw422016.N().S(` `)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:38
qw422016.E().S(p.name)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:38
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) {
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
p.streamtitle(qw422016)
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
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 {
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
p.writetitle(qb422016)
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:37
//line web/template/baseof.qtpl:39
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) {
//line web/template/baseof.qtpl:39
//line web/template/baseof.qtpl:41
qw422016.N().S(` `)
//line web/template/baseof.qtpl:40
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"> `)
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:42
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:56
}
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
func (p *BaseOf) writehead(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
p.streamhead(qw422016)
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
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 {
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
p.writehead(qb422016)
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:54
//line web/template/baseof.qtpl:56
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) {
//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) {
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
p.streambody(qw422016)
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
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 {
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
p.writebody(qb422016)
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:56
//line web/template/baseof.qtpl:58
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) {
//line web/template/baseof.qtpl:58
//line web/template/baseof.qtpl:60
qw422016.N().S(` `)
//line web/template/baseof.qtpl:59
//line web/template/baseof.qtpl:61
qw422016.E().S(p.printer.Sprintf(format, args...))
//line web/template/baseof.qtpl:59
//line web/template/baseof.qtpl:61
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) {
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
p.streamt(qw422016, format, args...)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
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 {
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
p.writet(qb422016, format, args...)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:62
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) {
//line web/template/baseof.qtpl:62
//line web/template/baseof.qtpl:64
qw422016.N().S(` <!DOCTYPE html> <html class="page" lang="`)
//line web/template/baseof.qtpl:65
//line web/template/baseof.qtpl:67
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>`)
//line web/template/baseof.qtpl:72
//line web/template/baseof.qtpl:74
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"> `)
//line web/template/baseof.qtpl:79
//line web/template/baseof.qtpl:81
p.streamhead(qw422016)
//line web/template/baseof.qtpl:79
//line web/template/baseof.qtpl:81
qw422016.N().S(` </head> <body class="page__body cover"> `)
//line web/template/baseof.qtpl:83
//line web/template/baseof.qtpl:85
p.streambody(qw422016)
//line web/template/baseof.qtpl:83
qw422016.N().S(` </body> </html> `)
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:85
qw422016.N().S(` `)
//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) {
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
StreamTemplate(qw422016, p)
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
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 {
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
WriteTemplate(qb422016, p)
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
return qs422016
//line web/template/baseof.qtpl:86
//line web/template/baseof.qtpl:135
}

View File

@ -51,27 +51,5 @@
</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>
{% endfunc %}
{% endcollapsespace %}

View File

@ -45,32 +45,32 @@ func (p *Home) streambody(qw422016 *qt422016.Writer) {
//line web/template/home.qtpl:49
p.streamt(qw422016, "What the spec?")
//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> `)
//line web/template/home.qtpl:76
qw422016.N().S(` </a> </li> </ul> </main> `)
//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) {
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
p.streambody(qw422016)
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
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 {
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
p.writebody(qb422016)
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
qs422016 := string(qb422016.B)
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
return qs422016
//line web/template/home.qtpl:76
//line web/template/home.qtpl:54
}