Compare commits

...

25 Commits

Author SHA1 Message Date
Maxim Lebedev 94c8850e80
📝 Updated README
/ docker (push) Successful in 1m11s Details
2023-11-13 07:41:40 +06:00
Maxim Lebedev aff4976a40
Merge branch 'feature/static' into develop 2023-11-13 07:25:55 +06:00
Maxim Lebedev 90a91c332d
✏️ Updated doc comment about static serving 2023-11-13 07:21:45 +06:00
Maxim Lebedev 6fd916058d
♻️ Safe strip slash prefix for static path 2023-11-13 07:17:41 +06:00
Maxim Lebedev f8a6580444
🐳 Expose default static directory 2023-11-13 07:15:59 +06:00
Maxim Lebedev 495d49dada
🏗️ Serve static from HOME_STATIC_DIR first 2023-11-13 07:15:39 +06:00
Maxim Lebedev 43126b3d92
🔧 Added static directory in config 2023-11-13 07:12:50 +06:00
Maxim Lebedev 92b983f913
Merge branch 'feature/language' into develop 2023-11-13 06:47:47 +06:00
Maxim Lebedev 32f3b803e6
📝 Added comment about potentially insecure static routing 2023-11-13 06:37:11 +06:00
Maxim Lebedev 6eafdd4b86
🗃️ Support DefaultLanguage property in FileSystem Site repo 2023-11-13 06:20:34 +06:00
Maxim Lebedev ae9f1cae4a
🏷️ Added DefaultLanguage property for Site domain 2023-11-13 06:19:12 +06:00
Maxim Lebedev 516758b74a
🔥 Removed unused property in Site and Page FileSystem repositories 2023-11-13 05:58:18 +06:00
Maxim Lebedev d762f33b19
Fixed FileSystem Site repository tests 2023-11-13 05:56:20 +06:00
Maxim Lebedev fe8a7d36de
👔 Return all supported languages in Site use case 2023-11-13 05:55:52 +06:00
Maxim Lebedev 7087380d15
🏷️ Added Languages property for Site and methods depends of it 2023-11-13 05:55:06 +06:00
Maxim Lebedev 2ad44a03bc
♻️ Simplify Page translations searching by using Resource.File 2023-11-13 05:34:21 +06:00
Maxim Lebedev f3eca3dc73
Fixed FileSystem repositoryes tests of Site and Page 2023-11-13 05:26:59 +06:00
Maxim Lebedev c9db0f0ce0
🏗️ Updated Language domain usage in main 2023-11-13 05:19:53 +06:00
Maxim Lebedev 2c23c74118
🍱 Regenerated quicktemplate templates due domains changes 2023-11-13 05:19:23 +06:00
Maxim Lebedev d8673188ce
♻️ Replaced Language property in Site module 2023-11-13 05:18:56 +06:00
Maxim Lebedev 6046187817
♻️ Replaced Language property in Page module 2023-11-13 05:18:31 +06:00
Maxim Lebedev 9cdb470ada
🏷️ Replaced Language property in File domain 2023-11-13 05:13:31 +06:00
Maxim Lebedev c83a86119e
♻️ Refactored Language domain initialization 2023-11-13 05:12:37 +06:00
Maxim Lebedev 66dadfe9c3
📌 Vendored go modules 2023-11-13 04:33:00 +06:00
Maxim Lebedev 104814a633
🏷️ Created Language domain with tests 2023-11-13 04:32:34 +06:00
33 changed files with 54517 additions and 340 deletions

View File

@ -23,7 +23,7 @@ WORKDIR /
COPY --from=builder /app/home /home
VOLUME ["/content", "/theme"]
VOLUME ["/content", "/theme", "/static"]
EXPOSE 3000

View File

@ -14,6 +14,7 @@ Four options are available:
* `HOME_CONTENT_DIR` -- the name or path of the directory that contains pages and static files, the default is `content/`;
* `HOME_HOST` -- IP address where the engine will run, default is `0.0.0.0.0`;
* `HOME_THEME_DIR` -- the name or path of the directory that contains the template files, default is `theme/`;
* `HOME_STATIC_DIR` -- name or path of the directory containing publicly accessible files from any part of the site "as is", default is `static/`;
* `HOME_PORT` -- the port on which the engine will work, by default is `3000`;
Configuration of the site and its pages is fully provided through FrontMatter in Markdown files located in `$HOME_CONTENT_DIR`.
@ -41,9 +42,14 @@ backlink: "https://mstdn.io/@toby3d"
Pages are published instantly through the creation of Markdown files. Index files of the home page and sections are called `index.md` or `index.lang.md` where `lang` is a two-letter designation of the localization language. The engine supports working with any number of localizations of the same pages: `index.ru.md`, `index.en.md`, `index.jp.md` and so on.
Individual pages can be named anything, the name will be used as a slug from the URL, e.g.:
* `$HOME_CONTENT_DIR/folder/file.md`: `/folder/file`, `/folder/file/`, `/folder/file/index.html` and `/folder/file.html`;
* `$HOME_CONTENT_DIR/now/index.md`: `/now`, `/now/`, `/now/index.html` and `/now.html`;
* `$HOME_CONTENT_DIR/2023/11/09.md`: `/2023/11/09`, `/2023/11/09/`, `/2023/11/09/index.html` and `/2023/11/09.html`;
* `$HOME_CONTENT_DIR/folder/file.md`:
* `/folder/file`
* `/folder/file/`
* `/folder/file.html`
* `$HOME_CONTENT_DIR/now/index.md`:
* `/now`
* `/now/`
* `/now/index.html`
* and so on;
The rules on localizations also apply here: `folder/file.ru.md` b `folder/file.en.md`, `now.jp.md`, `2023/11/09.de.md` and so on.
@ -53,6 +59,7 @@ The content of the published pages also conforms to the FrontMatter format:
---
# Known properties:
title: "Hello, World!"
description: "Demo page for demo"
# Any other key-value sets will be treated as secondary parameters and accessed through the `.Page.Params`:
style:
@ -67,7 +74,9 @@ contact: "hey@toby3d.me"
This is a sample page content for demo.
```
Static files like images, videos, `robots.txt`, styles, scripts and anything else are placed in `$HOME_CONTENT_DIR` as they are: in the root if they should be available globally via `.Site.Files` and for home pages, as well as in the necessary directories with pages to access them via `.Page.Files`. The format and filenames are not restricted in any way.
Any static files can be hosted and published in two ways:
* In `$HOME_STATIC_DIR` as publicly available static, "as is" without any restrictions, in the same path in which the file is located in the directory;
* In `$HOME_CONTENT_DIR` next to the specific page: the file will be available in the `.Site.Resources` and `.Page.Resources` collections and restricted by the access settings of the parent page;
Templates are required to render page content in browsers and must be placed in `$HOME_THEME_DIR`. At least one template with a `baseof` block that will be called for each page is required, for example:
```html
@ -75,7 +84,9 @@ Templates are required to render page content in browsers and must be placed in
{{ define "baseof" }}
<!DOCTYPE html>
<html lang="{{ .Site.Language }}">
<html lang="{{ or .Page.Language.Lang .Site.Language.Lang }}"
dir="{{ or .Page.Language.Dir .Site.Language.Dir }}">
<head>
<meta charset="UTF-8">
<meta name="viewport"
@ -84,12 +95,12 @@ Templates are required to render page content in browsers and must be placed in
<title>{{ .Site.Title }}</title>
</head>
<body>
{{ block "body" . }}{{ .Page.Content }}{{ end }}
{{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }}
</body>
</html>
{{ end }}
```
Templating works according to [the instructions of the standard Go templating engine](https://pkg.go.dev/html/template) with minor additions in the form of additional utilities. In `$HOME_THEME_DIR`, any combination and hierarchy of templates and their relationships is allowed as long as there is a `baseof` as a single parent block.
Templating works according to [the instructions of the standard Go templating engine](https://pkg.go.dev/html/template) with minor additions in the form of [additional utilities](../internal/templateutil/templateutil.go). In `$HOME_THEME_DIR`, any combination and hierarchy of templates and their relationships is allowed as long as there is a `baseof` as a single parent block.
Two structures are thrown into each template as contexts: `.Site` with global options from `$HOME_CONTENT_DIR/index.md` and the currently viewed page `.Page` with its options found in `$HOME_CONTENT_DIR`. The data structures in these contexts can be found in the [domains](../internal/domain) package, they are used in the templates as is.

View File

@ -14,6 +14,7 @@
* `HOME_CONTENT_DIR` -- имя или путь директории в которой содержатся страницы и статика, по-умолчанию `content/`;
* `HOME_HOST` -- IP-адрес на котором будет работать движок, по-умолчанию `0.0.0.0`;
* `HOME_THEME_DIR` -- имя или путь директории в которой содержатся файлы шаблонов, по-умолчанию `theme/`;
* `HOME_STATIC_DIR` -- имя или путь директории в которой содержатся публично доступные файлы из любой части сайта "как есть", по-умолчанию `static/`;
* `HOME_PORT` -- порт на котором будет работать движок, по-умолчанию `3000`;
Конфигурация сайта и его страниц полностью обеспечивается через FrontMatter в Markdown-файлах находящиеся в `$HOME_CONTENT_DIR`.
@ -41,9 +42,14 @@ backlink: "https://mstdn.io/@toby3d"
Страницы публикуются мгновенно через создание Markdown-файлов. Индексные файлы главной страницы и директорий называются `index.md` или `index.lang.md` где `lang` это двубуквенное обозначение языка локализации. Движок поддерживает работу с любым числом локализаций одних и тех же страниц: `index.ru.md`, `index.en.md`, `index.jp.md` и так далее.
Именованные страницы могут называться как угодно, имя будет использоваться в качестве slug из URL, например:
* `$HOME_CONTENT_DIR/folder/file.md`: `/folder/file`, `/folder/file/`, `/folder/file/index.html` и `/folder/file.html`;
* `$HOME_CONTENT_DIR/now/index.md`: `/now`, `/now/`, `/now/index.html` и `/now.html`;
* `$HOME_CONTENT_DIR/2023/11/09.md`: `/2023/11/09`, `/2023/11/09/`, `/2023/11/09/index.html` и `/2023/11/09.html`;
* `$HOME_CONTENT_DIR/folder/file.md`:
* `/folder/file`
* `/folder/file/`
* `/folder/file.html`
* `$HOME_CONTENT_DIR/now/index.md`:
* `/now`
* `/now/`
* `/now/index.html`
* и так далее;
Правила по локализациям здесь также применимы: `folder/file.ru.md` b `folder/file.en.md`, `now.jp.md`, `2023/11/09.de.md` и так далее.
@ -53,6 +59,7 @@ backlink: "https://mstdn.io/@toby3d"
---
# Известные параметры:
title: "Hello, World!"
description: "Demo page for demo"
# Любые другие наборы ключей-значений будут восприниматься как второстепенные параметры и доступные через `.Page.Params`:
style:
@ -67,7 +74,9 @@ contact: "hey@toby3d.me"
This is a sample page content for demo.
```
Статические файлы вроде изображений, видео, `robots.txt`, стилей, скриптов и чего-угодно ещё размещаются в `$HOME_CONTENT_DIR` как есть: в корень если они должны быть доступны глобально через `.Site.Files` и для домашних страниц, а также в нужных директориях со страницами для доступа к ним через `.Page.Files`. Формат и имена файлов ничем не ограничены.
Любые статические файлы могут быть размещены и опубликованы двумя способами:
* В `$HOME_STATIC_DIR` как публично доступная статика, без каких-либо ограничений "как есть", по тому же пути в котором файл расположен в директории;
* В `$HOME_CONTENT_DIR` рядом с нужной страницей: файл будет доступен в коллекциях `.Site.Resources` и `.Page.Resources` и ограничен настройками доступа родительской страницы;
Для рендера содержимого страниц в браузерах необходимы шаблоны, которые должны размещаться в `$HOME_THEME_DIR`. Для работы необходим хотя бы один шаблон с блоком `baseof` который будет вызываться для каждой страницы, например:
```html
@ -75,7 +84,9 @@ This is a sample page content for demo.
{{ define "baseof" }}
<!DOCTYPE html>
<html lang="{{ .Site.Language }}">
<html lang="{{ or .Page.Language.Lang .Site.Language.Lang }}"
dir="{{ or .Page.Language.Dir .Site.Language.Dir }}">
<head>
<meta charset="UTF-8">
<meta name="viewport"
@ -84,12 +95,12 @@ This is a sample page content for demo.
<title>{{ .Site.Title }}</title>
</head>
<body>
{{ block "body" . }}{{ .Page.Content }}{{ end }}
{{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }}
</body>
</html>
{{ end }}
```
Шаблонизация работает по [инструкциям стандартного шаблонизатора Go](https://pkg.go.dev/html/template) с небольшими дополнениями в виде дополнительных утилит. В `$HOME_THEME_DIR` разрешена любая комбинация и иеархия шаблонов и их взаимосвязей до тех пор, пока существует `baseof` в качестве единого родительского блока.
Шаблонизация работает по [инструкциям стандартного шаблонизатора Go](https://pkg.go.dev/html/template) с небольшими дополнениями в виде [дополнительных утилит](../internal/templateutil/templateutil.go). В `$HOME_THEME_DIR` разрешена любая комбинация и иеархия шаблонов и их взаимосвязей до тех пор, пока существует `baseof` в качестве единого родительского блока.
В качестве контекста в каждый шаблон пробрасываются две структуры: `.Site` с глобальными опциям из `$HOME_CONTENT_DIR/index.md` и текущая просматриваемая страница `.Page` с её опциями найденная в `$HOME_CONTENT_DIR`. Структуры данных в этих контекстах можно посмотреть в пакете [domains](../internal/domain), они используются в шаблонах как есть.

View File

@ -11,6 +11,7 @@ type Config struct {
ContentDir string `env:"CONTENT_DIR" envDefault:"content/"`
Host string `env:"HOST" envDefault:"0.0.0.0"`
ThemeDir string `env:"THEME_DIR" envDefault:"theme/"`
StaticDir string `env:"STATIC_DIR" envDefault:"static/"`
Port uint16 `env:"PORT" envDefault:"3000"`
}
@ -21,6 +22,7 @@ func TestConfig(tb testing.TB) *Config {
ContentDir: "testdata/content/",
Host: "0.0.0.0",
ThemeDir: "testdata/theme/",
StaticDir: "testdata/static/",
Port: 3000,
}
}

View File

@ -4,12 +4,10 @@ import (
"crypto/md5"
"path/filepath"
"strings"
"golang.org/x/text/language"
)
type File struct {
language language.Tag
Language Language
baseFileName string
contentBaseName string
dir string
@ -23,7 +21,7 @@ type File struct {
func NewFile(path string) File {
out := File{
language: language.Tag{},
Language: LanguageUnd,
baseFileName: "",
contentBaseName: "",
dir: filepath.Dir(path) + "/",
@ -38,7 +36,7 @@ func NewFile(path string) File {
out.baseFileName = strings.TrimSuffix(out.logicalName, filepath.Ext(out.logicalName))
parts := strings.Split(out.baseFileName, ".")
out.language = language.Make(parts[len(parts)-1])
out.Language = NewLanguage(parts[len(parts)-1])
out.translationBaseName = strings.Join(parts[:len(parts)-1], ".")
out.contentBaseName = out.translationBaseName
@ -79,12 +77,6 @@ func (f File) Filename() string {
return f.filename
}
// Language returns language.Tag of current file based on his suffix before
// extention.
func (f File) Language() language.Tag {
return f.language
}
func (f File) LogicalName() string {
return f.logicalName
}

View File

@ -4,8 +4,6 @@ import (
"path/filepath"
"testing"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
)
@ -106,7 +104,7 @@ func TestFile_Ext(t *testing.T) {
func TestFile_Language(t *testing.T) {
t.Parallel()
var expect language.Tag = language.English
var expect domain.Language = domain.NewLanguage("en")
for name, input := range map[string]string{
"regular": testRegularFile,
@ -118,7 +116,7 @@ func TestFile_Language(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
if actual := domain.NewFile(input).Language(); actual != expect {
if actual := domain.NewFile(input).Language; actual != expect {
t.Errorf("Language() = '%s', want '%s'", actual, expect)
}
})

View File

@ -0,0 +1,68 @@
package domain
import (
"strings"
"golang.org/x/text/language"
"golang.org/x/text/language/display"
)
type Language struct {
code string
lang string
name string
dir string
}
var LanguageUnd Language = Language{}
func NewLanguage(raw string) Language {
tag, err := language.BCP47.Parse(raw)
if err != nil || tag == language.Und {
return LanguageUnd
}
out := Language{
code: tag.String(),
dir: "ltr",
name: strings.ToLower(display.Self.Name(tag)),
}
switch tag {
case language.Arabic, language.Persian, language.Hebrew, language.Urdu:
out.dir = "rtl"
}
base, _ := tag.Base()
out.lang = base.String()
return out
}
func (l Language) Lang() string {
return l.lang
}
func (l Language) Code() string {
return l.code
}
func (l Language) Dir() string {
return l.dir
}
func (l Language) Name() string {
return l.name
}
func (l Language) String() string {
if l.code == "" {
return l.code
}
return "und"
}
func (l Language) GoString() string {
return "domain.Language(" + l.String() + ")"
}

View File

@ -0,0 +1,105 @@
package domain_test
import (
"testing"
"source.toby3d.me/toby3d/home/internal/domain"
)
func TestLanguage_Lang(t *testing.T) {
t.Parallel()
for name, tc := range map[string]struct {
input string
expect string
}{
"2letter": {"en", "en"},
"3letter": {"eng", "en"},
"region": {"en-US", "en"},
"und": {"", ""},
} {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()
if actual := domain.NewLanguage(tc.input).Lang(); actual != tc.expect {
t.Errorf("Lang() = %s, want %s", actual, tc.expect)
}
})
}
}
func TestLanguage_Code(t *testing.T) {
t.Parallel()
for name, tc := range map[string]struct {
input string
expect string
}{
"2letter": {"en", "en"},
"3letter": {"eng", "en"},
"region": {"en-US", "en-US"},
"und": {"", ""},
} {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()
if actual := domain.NewLanguage(tc.input).Code(); actual != tc.expect {
t.Errorf("Code() = %s, want %s", actual, tc.expect)
}
})
}
}
func TestLanguage_Dir(t *testing.T) {
t.Parallel()
for name, tc := range map[string]struct {
input string
expect string
}{
"2letter": {"en", "ltr"},
"rtl": {"ur", "rtl"},
"3letter": {"eng", "ltr"},
"region": {"en-US", "ltr"},
"und": {"", ""},
} {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()
if actual := domain.NewLanguage(tc.input).Dir(); actual != tc.expect {
t.Errorf("Dir() = %s, want %s", actual, tc.expect)
}
})
}
}
func TestLanguage_Name(t *testing.T) {
t.Parallel()
for name, tc := range map[string]struct {
input string
expect string
}{
"localized": {"ru", "русский"},
"2letter": {"en", "english"},
"3letter": {"eng", "english"},
"region": {"en-US", "american english"},
"und": {"", ""},
} {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()
if actual := domain.NewLanguage(tc.input).Name(); actual != tc.expect {
t.Errorf("Name() = %s, want %s", actual, tc.expect)
}
})
}
}

View File

@ -1,9 +1,7 @@
package domain
import "golang.org/x/text/language"
type Page struct {
Language language.Tag
Language Language
Params map[string]any
File File
Description string

View File

@ -16,6 +16,8 @@ import (
)
type Resource struct {
File File
modTime time.Time
reader io.ReadSeeker
params map[string]any // TODO(toby3d): set from Page configuration
@ -30,6 +32,7 @@ type Resource struct {
func NewResource(modTime time.Time, content []byte, key string) *Resource {
mediaType, _, _ := mime.ParseMediaType(mime.TypeByExtension(path.Ext(key)))
out := &Resource{
File: NewFile(key),
modTime: modTime,
key: key,
name: key, // TODO(toby3d): set from Page configuration

View File

@ -2,17 +2,30 @@ package domain
import (
"net/url"
"path"
"time"
"golang.org/x/text/language"
)
type Site struct {
Language language.Tag
BaseURL *url.URL
Params map[string]any
TimeZone *time.Location
File File
Title string
Resources Resources
DefaultLanguage Language
Language Language
Languages []Language
BaseURL *url.URL
Params map[string]any
TimeZone *time.Location
File File
Title string
Resources Resources
}
func (s Site) LanguagePrefix() string {
if s.Language != LanguageUnd {
return path.Join("/", s.Language.lang, "/")
}
return ""
}
func (s Site) IsMultiLingual() bool {
return 1 < len(s.Languages)
}

View File

@ -3,11 +3,9 @@ package page
import (
"context"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
)
type Repository interface {
Get(ctx context.Context, lang language.Tag, path string) (*domain.Page, error)
Get(ctx context.Context, lang domain.Language, path string) (*domain.Page, error)
}

View File

@ -6,7 +6,6 @@ import (
"io/fs"
"github.com/adrg/frontmatter"
"golang.org/x/text/language"
"gopkg.in/yaml.v3"
"source.toby3d.me/toby3d/home/internal/domain"
@ -22,8 +21,7 @@ type (
}
fileSystemPageRepository struct {
dir fs.FS
rootPath string
dir fs.FS
}
)
@ -31,17 +29,16 @@ var FrontMatterFormats = []*frontmatter.Format{
frontmatter.NewFormat(`---`, `---`, yaml.Unmarshal),
}
func NewFileSystemPageRepository(rootDir fs.FS) page.Repository {
func NewFileSystemPageRepository(dir fs.FS) page.Repository {
return &fileSystemPageRepository{
dir: rootDir,
dir: dir,
}
}
func (repo *fileSystemPageRepository) Get(ctx context.Context, lang language.Tag, p string) (*domain.Page, error) {
func (repo *fileSystemPageRepository) Get(ctx context.Context, lang domain.Language, p string) (*domain.Page, error) {
ext := ".md"
if lang != language.Und {
base, _ := lang.Base()
ext = "." + base.String() + ext
if lang != domain.LanguageUnd {
ext = "." + lang.Lang() + ext
}
target := p + ext

View File

@ -8,7 +8,6 @@ import (
"testing/fstest"
"github.com/google/go-cmp/cmp"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
repository "source.toby3d.me/toby3d/home/internal/page/repository/fs"
@ -34,41 +33,46 @@ func TestGet(t *testing.T) {
"index": {
input: path.Join("index"),
expect: &domain.Page{
Content: []byte("index.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Content: []byte("index.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Translations: make([]*domain.Page, 0),
},
},
"file": {
input: path.Join("file"),
expect: &domain.Page{
Content: []byte("file.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Content: []byte("file.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Translations: make([]*domain.Page, 0),
},
},
"folder": {
input: path.Join("folder", "index"),
expect: &domain.Page{
Content: []byte("folder/index.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Content: []byte("folder/index.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Translations: make([]*domain.Page, 0),
},
},
"both-file": {
input: path.Join("both"),
expect: &domain.Page{
Content: []byte("both.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Content: []byte("both.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Translations: make([]*domain.Page, 0),
},
},
"both-folder": {
input: path.Join("both", "index"),
expect: &domain.Page{
Content: []byte("both/index.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Content: []byte("both/index.md"),
Params: make(map[string]any),
Resources: make([]*domain.Resource, 0),
Translations: make([]*domain.Page, 0),
},
},
} {
@ -77,12 +81,19 @@ func TestGet(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
out, err := repo.Get(context.Background(), language.Und, tc.input)
out, err := repo.Get(context.Background(), domain.LanguageUnd, tc.input)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(out, tc.expect, cmp.AllowUnexported(language.Und)); diff != "" {
if diff := cmp.Diff(out, tc.expect, cmp.FilterPath(func(p cmp.Path) bool {
switch p.String() {
default:
return false
case "File", "Language":
return true
}
}, cmp.Ignore())); diff != "" {
t.Error(diff)
}
})

View File

@ -4,13 +4,11 @@ import (
"context"
"errors"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
)
type UseCase interface {
Do(ctx context.Context, lang language.Tag, path string) (*domain.Page, error)
Do(ctx context.Context, lang domain.Language, path string) (*domain.Page, error)
}
var ErrNotExist error = errors.New("page not exists")

View File

@ -4,9 +4,6 @@ import (
"context"
"fmt"
"path"
"strings"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/page"
@ -26,7 +23,7 @@ func NewPageUseCase(pages page.Repository, statics static.Repository) page.UseCa
}
}
func (ucase *pageUseCase) Do(ctx context.Context, lang language.Tag, p string) (*domain.Page, error) {
func (ucase *pageUseCase) Do(ctx context.Context, lang domain.Language, p string) (*domain.Page, error) {
ext := path.Ext(p)
if ext == ".html" {
p = p[:len(p)-len(ext)]
@ -55,22 +52,11 @@ func (ucase *pageUseCase) Do(ctx context.Context, lang language.Tag, p string) (
}
for _, res := range out.Resources.GetType(domain.ResourceTypePage) {
// TODO(toby3d): simplify this, it's awful
resName := path.Base(res.Key())
resExt := path.Ext(resName)
resParts := strings.Split(resName[:len(resName)-len(resExt)], ".")
translationBaseName := strings.Join(resParts[:len(resParts)-1], ".")
if translationBaseName != out.File.TranslationBaseName() {
if res.File.TranslationBaseName() != out.File.TranslationBaseName() {
continue
}
resLang := language.Make(resParts[len(resParts)-1])
if resLang == language.Und {
continue
}
translation, err := ucase.pages.Get(ctx, resLang, targets[i])
translation, err := ucase.pages.Get(ctx, res.File.Language, targets[i])
if err != nil {
continue
}
@ -78,11 +64,6 @@ func (ucase *pageUseCase) Do(ctx context.Context, lang language.Tag, p string) (
out.Translations = append(out.Translations, translation)
}
translations := make([]string, 0)
for i := range out.Translations {
translations = append(translations, out.Translations[i].Language.String())
}
return out, nil
}

View File

@ -7,8 +7,8 @@ import (
"testing/fstest"
"github.com/google/go-cmp/cmp"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
pagefsrepo "source.toby3d.me/toby3d/home/internal/page/repository/fs"
"source.toby3d.me/toby3d/home/internal/page/usecase"
"source.toby3d.me/toby3d/home/internal/static"
@ -49,7 +49,7 @@ func TestDo(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
actual, err := ucase.Do(context.Background(), language.Und, tc.input)
actual, err := ucase.Do(context.Background(), domain.LanguageUnd, tc.input)
if err != nil {
t.Fatal(err)
}

View File

@ -3,11 +3,9 @@ package site
import (
"context"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
)
type Repository interface {
Get(ctx context.Context, lang language.Tag) (*domain.Site, error)
Get(ctx context.Context, lang domain.Language) (*domain.Site, error)
}

View File

@ -8,7 +8,6 @@ import (
"time"
"github.com/adrg/frontmatter"
"golang.org/x/text/language"
"gopkg.in/yaml.v3"
"source.toby3d.me/toby3d/home/internal/domain"
@ -17,11 +16,12 @@ import (
type (
Site struct {
Title string `yaml:"title"`
TimeZone TimeZone `yaml:"timeZone"`
BaseURL URL `yaml:"baseUrl"`
Params map[string]any `yaml:",inline"`
Content []byte `yaml:"-"`
Title string `yaml:"title"`
TimeZone TimeZone `yaml:"timeZone"`
DefaultLanguage Language `yaml:"defaultLanguage"`
BaseURL URL `yaml:"baseUrl"`
Params map[string]any `yaml:",inline"`
Content []byte `yaml:"-"`
}
TimeZone struct {
@ -32,9 +32,12 @@ type (
*url.URL `yaml:"-"`
}
Language struct {
domain.Language `yaml:"-"`
}
fileSystemSiteRepository struct {
dir fs.FS
rootPath string
dir fs.FS
}
)
@ -48,11 +51,10 @@ func NewFileSystemSiteRepository(rootDir fs.FS) site.Repository {
}
}
func (repo *fileSystemSiteRepository) Get(ctx context.Context, lang language.Tag) (*domain.Site, error) {
func (repo *fileSystemSiteRepository) Get(ctx context.Context, lang domain.Language) (*domain.Site, error) {
ext := ".md"
if lang != language.Und {
base, _ := lang.Base()
ext = "." + base.String() + ext
if lang != domain.LanguageUnd {
ext = "." + lang.Lang() + ext
}
target := "index" + ext
@ -69,12 +71,14 @@ func (repo *fileSystemSiteRepository) Get(ctx context.Context, lang language.Tag
}
return &domain.Site{
File: domain.NewFile(target),
Language: lang,
Title: data.Title,
BaseURL: data.BaseURL.URL,
TimeZone: data.TimeZone.Location,
Params: data.Params,
File: domain.NewFile(target),
DefaultLanguage: data.DefaultLanguage.Language,
Language: lang,
Title: data.Title,
BaseURL: data.BaseURL.URL,
TimeZone: data.TimeZone.Location,
Params: data.Params,
Languages: make([]domain.Language, 0),
}, nil
}
@ -126,6 +130,24 @@ func (u URL) MarshalYAML() (any, error) {
return u.URL.String(), nil
}
func (l *Language) UnmarshalYAML(value *yaml.Node) error {
if value.IsZero() {
return nil
}
l.Language = domain.NewLanguage(value.Value)
return nil
}
func (l Language) MarshalYAML() (any, error) {
if l.Language == domain.LanguageUnd {
return nil, nil
}
return l.Language.Lang(), nil
}
func NewSite() *Site {
return &Site{
Params: make(map[string]any),

View File

@ -6,7 +6,6 @@ import (
"testing/fstest"
"github.com/google/go-cmp/cmp"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
repository "source.toby3d.me/toby3d/home/internal/site/repository/fs"
@ -15,6 +14,8 @@ import (
func TestGet(t *testing.T) {
t.Parallel()
testLangEn := domain.NewLanguage("en")
testLangRu := domain.NewLanguage("ru")
testData := fstest.MapFS{
"index.ru.md": &fstest.MapFile{
Data: []byte("---\ntitle: пример\n---\nпривет, мир!\n"),
@ -27,23 +28,25 @@ func TestGet(t *testing.T) {
repo := repository.NewFileSystemSiteRepository(testData)
for name, tc := range map[string]struct {
input language.Tag
expect *domain.Site
input domain.Language
}{
"english": {
input: language.English,
input: testLangEn,
expect: &domain.Site{
Language: language.English,
Title: "example",
Params: make(map[string]any),
Language: testLangEn,
Title: "example",
Params: make(map[string]any),
Languages: make([]domain.Language, 0),
},
},
"russian": {
input: language.Russian,
input: testLangRu,
expect: &domain.Site{
Language: language.Russian,
Title: "пример",
Params: make(map[string]any),
Language: testLangRu,
Title: "пример",
Params: make(map[string]any),
Languages: make([]domain.Language, 0),
},
},
} {
@ -57,7 +60,14 @@ func TestGet(t *testing.T) {
t.Fatal(err)
}
if diff := cmp.Diff(out, tc.expect, cmp.AllowUnexported(language.Und)); diff != "" {
if diff := cmp.Diff(out, tc.expect, cmp.FilterPath(func(p cmp.Path) bool {
switch p.String() {
default:
return false
case "File", "Language", "DefaultLanguage":
return true
}
}, cmp.Ignore())); diff != "" {
t.Error(diff)
}
})

View File

@ -3,11 +3,9 @@ package site
import (
"context"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
)
type UseCase interface {
Do(ctx context.Context, lang language.Tag) (*domain.Site, error)
Do(ctx context.Context, lang domain.Language) (*domain.Site, error)
}

View File

@ -4,8 +4,6 @@ import (
"context"
"fmt"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/site"
"source.toby3d.me/toby3d/home/internal/static"
@ -23,13 +21,17 @@ func NewSiteUseCase(sites site.Repository, statics static.Repository) site.UseCa
}
}
func (ucase *siteUseCase) Do(ctx context.Context, lang language.Tag) (*domain.Site, error) {
out, err := ucase.sites.Get(ctx, language.Und)
func (ucase *siteUseCase) Do(ctx context.Context, lang domain.Language) (*domain.Site, error) {
out, err := ucase.sites.Get(ctx, domain.LanguageUnd)
if err != nil {
return nil, fmt.Errorf("cannot find base site data: %w", err)
}
out.Resources, _, _ = ucase.statics.Fetch(ctx, "")
if out.Resources, _, err = ucase.statics.Fetch(ctx, ""); err == nil {
for _, res := range out.Resources.Match("index.*.md") {
out.Languages = append(out.Languages, res.File.Language)
}
}
sub, err := ucase.sites.Get(ctx, lang)
if err != nil {

View File

@ -7,8 +7,7 @@ import (
"net/url"
"path"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/site"
)
@ -25,7 +24,7 @@ func New(siter site.UseCase) *Namespace {
}
func (ns *Namespace) AbsURL(p string) (string, error) {
site, err := ns.siter.Do(context.Background(), language.Und)
site, err := ns.siter.Do(context.Background(), domain.LanguageUnd)
if err != nil {
return "", fmt.Errorf("cannot fetch site root for AbsURL processing: %w", err)
}

41
main.go
View File

@ -63,20 +63,23 @@ var cpuProfilePath, memProfilePath string
func NewApp(ctx context.Context, config *domain.Config) (*App, error) {
themeDir := os.DirFS(config.ThemeDir)
contentDir := os.DirFS(config.ContentDir)
statics := staticfsrepo.NewFileServerStaticRepository(contentDir)
resources := staticfsrepo.NewFileServerStaticRepository(contentDir)
sites := sitefsrepo.NewFileSystemSiteRepository(contentDir)
siter := siteucase.NewSiteUseCase(sites, statics)
siter := siteucase.NewSiteUseCase(sites, resources)
funcMap, err := templateutil.New(themeDir, siter)
if err != nil {
logger.Fatalln("cannot setup template.FuncMap for templates: %w", err)
}
staticer := staticucase.NewStaticUseCase(statics)
staticDir := os.DirFS(config.StaticDir)
statics := staticfsrepo.NewFileServerStaticRepository(staticDir)
// TODO(toby3d): use exists static use case or split that on static and resource modules?
resourcer := staticucase.NewStaticUseCase(resources)
themes := themefsrepo.NewFileSystemThemeRepository(themeDir, funcMap)
themer := themeucase.NewThemeUseCase(themes)
pages := pagefsrepo.NewFileSystemPageRepository(contentDir)
pager := pageucase.NewPageUseCase(pages, statics)
pager := pageucase.NewPageUseCase(pages, resources)
matcher := language.NewMatcher(message.DefaultCatalog.Languages())
@ -98,26 +101,18 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) {
return
}
lang, err := language.Parse(head)
if err != nil || lang == language.Und {
res, err := staticer.Do(r.Context(), r.URL.Path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeContent(w, r, res.Name(), domain.ResourceModTime(res), res)
// TODO(toby3d): use exists static use case or split that on static and resource modules?
// INFO(toby3d): any static file is public and unprotected by design, so it's safe to search it
// first before deep down to any page or it's resource which might be secured by middleware or
// something else.
static, err := statics.Get(r.Context(), strings.TrimPrefix(r.URL.Path, "/"))
if err == nil {
http.ServeContent(w, r, static.Name(), domain.ResourceModTime(static), static)
return
}
lang := domain.NewLanguage(head)
r.URL.Path = tail
s, err := siter.Do(r.Context(), lang)
@ -135,7 +130,7 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) {
return
}
res, err := staticer.Do(r.Context(), r.URL.Path)
res, err := resourcer.Do(r.Context(), r.URL.Path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
http.Error(w, err.Error(), http.StatusNotFound)
@ -155,8 +150,7 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) {
contentLanguage := make([]string, len(p.Translations))
for i := range p.Translations {
base, _ := p.Translations[i].Language.Base()
contentLanguage[i] = base.String()
contentLanguage[i] = p.Translations[i].Language.Code()
}
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
@ -220,6 +214,7 @@ func main() {
for _, dir := range []*string{
&config.ContentDir,
&config.ThemeDir,
&config.StaticDir,
} {
if *dir, err = filepath.Abs(filepath.Clean(*dir)); err != nil {
logger.Fatalf("cannot format '%s' into absolute path: %s", *dir, err)

92
vendor/golang.org/x/text/language/display/dict.go generated vendored Normal file
View File

@ -0,0 +1,92 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package display
// This file contains sets of data for specific languages. Users can use these
// to create smaller collections of supported languages and reduce total table
// size.
// The variable names defined here correspond to those in package language.
var (
Afrikaans *Dictionary = &af // af
Amharic *Dictionary = &am // am
Arabic *Dictionary = &ar // ar
ModernStandardArabic *Dictionary = Arabic // ar-001
Azerbaijani *Dictionary = &az // az
Bulgarian *Dictionary = &bg // bg
Bengali *Dictionary = &bn // bn
Catalan *Dictionary = &ca // ca
Czech *Dictionary = &cs // cs
Danish *Dictionary = &da // da
German *Dictionary = &de // de
Greek *Dictionary = &el // el
English *Dictionary = &en // en
AmericanEnglish *Dictionary = English // en-US
BritishEnglish *Dictionary = English // en-GB
Spanish *Dictionary = &es // es
EuropeanSpanish *Dictionary = Spanish // es-ES
LatinAmericanSpanish *Dictionary = Spanish // es-419
Estonian *Dictionary = &et // et
Persian *Dictionary = &fa // fa
Finnish *Dictionary = &fi // fi
Filipino *Dictionary = &fil // fil
French *Dictionary = &fr // fr
Gujarati *Dictionary = &gu // gu
Hebrew *Dictionary = &he // he
Hindi *Dictionary = &hi // hi
Croatian *Dictionary = &hr // hr
Hungarian *Dictionary = &hu // hu
Armenian *Dictionary = &hy // hy
Indonesian *Dictionary = &id // id
Icelandic *Dictionary = &is // is
Italian *Dictionary = &it // it
Japanese *Dictionary = &ja // ja
Georgian *Dictionary = &ka // ka
Kazakh *Dictionary = &kk // kk
Khmer *Dictionary = &km // km
Kannada *Dictionary = &kn // kn
Korean *Dictionary = &ko // ko
Kirghiz *Dictionary = &ky // ky
Lao *Dictionary = &lo // lo
Lithuanian *Dictionary = &lt // lt
Latvian *Dictionary = &lv // lv
Macedonian *Dictionary = &mk // mk
Malayalam *Dictionary = &ml // ml
Mongolian *Dictionary = &mn // mn
Marathi *Dictionary = &mr // mr
Malay *Dictionary = &ms // ms
Burmese *Dictionary = &my // my
Nepali *Dictionary = &ne // ne
Dutch *Dictionary = &nl // nl
Norwegian *Dictionary = &no // no
Punjabi *Dictionary = &pa // pa
Polish *Dictionary = &pl // pl
Portuguese *Dictionary = &pt // pt
BrazilianPortuguese *Dictionary = Portuguese // pt-BR
EuropeanPortuguese *Dictionary = &ptPT // pt-PT
Romanian *Dictionary = &ro // ro
Russian *Dictionary = &ru // ru
Sinhala *Dictionary = &si // si
Slovak *Dictionary = &sk // sk
Slovenian *Dictionary = &sl // sl
Albanian *Dictionary = &sq // sq
Serbian *Dictionary = &sr // sr
SerbianLatin *Dictionary = &srLatn // sr
Swedish *Dictionary = &sv // sv
Swahili *Dictionary = &sw // sw
Tamil *Dictionary = &ta // ta
Telugu *Dictionary = &te // te
Thai *Dictionary = &th // th
Turkish *Dictionary = &tr // tr
Ukrainian *Dictionary = &uk // uk
Urdu *Dictionary = &ur // ur
Uzbek *Dictionary = &uz // uz
Vietnamese *Dictionary = &vi // vi
Chinese *Dictionary = &zh // zh
SimplifiedChinese *Dictionary = Chinese // zh-Hans
TraditionalChinese *Dictionary = &zhHant // zh-Hant
Zulu *Dictionary = &zu // zu
)

420
vendor/golang.org/x/text/language/display/display.go generated vendored Normal file
View File

@ -0,0 +1,420 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run maketables.go -output tables.go
// Package display provides display names for languages, scripts and regions in
// a requested language.
//
// The data is based on CLDR's localeDisplayNames. It includes the names of the
// draft level "contributed" or "approved". The resulting tables are quite
// large. The display package is designed so that users can reduce the linked-in
// table sizes by cherry picking the languages one wishes to support. There is a
// Dictionary defined for a selected set of common languages for this purpose.
package display // import "golang.org/x/text/language/display"
import (
"fmt"
"strings"
"golang.org/x/text/internal/format"
"golang.org/x/text/language"
)
/*
TODO:
All fairly low priority at the moment:
- Include alternative and variants as an option (using func options).
- Option for returning the empty string for undefined values.
- Support variants, currencies, time zones, option names and other data
provided in CLDR.
- Do various optimizations:
- Reduce size of offset tables.
- Consider compressing infrequently used languages and decompress on demand.
*/
// A Formatter formats a tag in the current language. It is used in conjunction
// with the message package.
type Formatter struct {
lookup func(tag int, x interface{}) string
x interface{}
}
// Format implements "golang.org/x/text/internal/format".Formatter.
func (f Formatter) Format(state format.State, verb rune) {
// TODO: there are a lot of inefficiencies in this code. Fix it when we
// language.Tag has embedded compact tags.
t := state.Language()
_, index, _ := matcher.Match(t)
str := f.lookup(index, f.x)
if str == "" {
// TODO: use language-specific punctuation.
// TODO: use codePattern instead of language?
if unknown := f.lookup(index, language.Und); unknown != "" {
fmt.Fprintf(state, "%v (%v)", unknown, f.x)
} else {
fmt.Fprintf(state, "[language: %v]", f.x)
}
} else {
state.Write([]byte(str))
}
}
// Language returns a Formatter that renders the name for lang in the
// current language. x may be a language.Base or a language.Tag.
// It renders lang in the default language if no translation for the current
// language is supported.
func Language(lang interface{}) Formatter {
return Formatter{langFunc, lang}
}
// Region returns a Formatter that renders the name for region in the current
// language. region may be a language.Region or a language.Tag.
// It renders region in the default language if no translation for the current
// language is supported.
func Region(region interface{}) Formatter {
return Formatter{regionFunc, region}
}
// Script returns a Formatter that renders the name for script in the current
// language. script may be a language.Script or a language.Tag.
// It renders script in the default language if no translation for the current
// language is supported.
func Script(script interface{}) Formatter {
return Formatter{scriptFunc, script}
}
// Tag returns a Formatter that renders the name for tag in the current
// language. tag may be a language.Tag.
// It renders tag in the default language if no translation for the current
// language is supported.
func Tag(tag interface{}) Formatter {
return Formatter{tagFunc, tag}
}
// A Namer is used to get the name for a given value, such as a Tag, Language,
// Script or Region.
type Namer interface {
// Name returns a display string for the given value. A Namer returns an
// empty string for values it does not support. A Namer may support naming
// an unspecified value. For example, when getting the name for a region for
// a tag that does not have a defined Region, it may return the name for an
// unknown region. It is up to the user to filter calls to Name for values
// for which one does not want to have a name string.
Name(x interface{}) string
}
var (
// Supported lists the languages for which names are defined.
Supported language.Coverage
// The set of all possible values for which names are defined. Note that not
// all Namer implementations will cover all the values of a given type.
// A Namer will return the empty string for unsupported values.
Values language.Coverage
matcher language.Matcher
)
func init() {
tags := make([]language.Tag, numSupported)
s := supported
for i := range tags {
p := strings.IndexByte(s, '|')
tags[i] = language.Raw.Make(s[:p])
s = s[p+1:]
}
matcher = language.NewMatcher(tags)
Supported = language.NewCoverage(tags)
Values = language.NewCoverage(langTagSet.Tags, supportedScripts, supportedRegions)
}
// Languages returns a Namer for naming languages. It returns nil if there is no
// data for the given tag. The type passed to Name must be either language.Base
// or language.Tag. Note that the result may differ between passing a tag or its
// base language. For example, for English, passing "nl-BE" would return Flemish
// whereas passing "nl" returns "Dutch".
func Languages(t language.Tag) Namer {
if _, index, conf := matcher.Match(t); conf != language.No {
return languageNamer(index)
}
return nil
}
type languageNamer int
func langFunc(i int, x interface{}) string {
return nameLanguage(languageNamer(i), x)
}
func (n languageNamer) name(i int) string {
return lookup(langHeaders[:], int(n), i)
}
// Name implements the Namer interface for language names.
func (n languageNamer) Name(x interface{}) string {
return nameLanguage(n, x)
}
// nonEmptyIndex walks up the parent chain until a non-empty header is found.
// It returns -1 if no index could be found.
func nonEmptyIndex(h []header, index int) int {
for ; index != -1 && h[index].data == ""; index = int(parents[index]) {
}
return index
}
// Scripts returns a Namer for naming scripts. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Script or a language.Tag. It will not attempt to infer a script for
// tags with an unspecified script.
func Scripts(t language.Tag) Namer {
if _, index, conf := matcher.Match(t); conf != language.No {
if index = nonEmptyIndex(scriptHeaders[:], index); index != -1 {
return scriptNamer(index)
}
}
return nil
}
type scriptNamer int
func scriptFunc(i int, x interface{}) string {
return nameScript(scriptNamer(i), x)
}
func (n scriptNamer) name(i int) string {
return lookup(scriptHeaders[:], int(n), i)
}
// Name implements the Namer interface for script names.
func (n scriptNamer) Name(x interface{}) string {
return nameScript(n, x)
}
// Regions returns a Namer for naming regions. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Region or a language.Tag. It will not attempt to infer a region for
// tags with an unspecified region.
func Regions(t language.Tag) Namer {
if _, index, conf := matcher.Match(t); conf != language.No {
if index = nonEmptyIndex(regionHeaders[:], index); index != -1 {
return regionNamer(index)
}
}
return nil
}
type regionNamer int
func regionFunc(i int, x interface{}) string {
return nameRegion(regionNamer(i), x)
}
func (n regionNamer) name(i int) string {
return lookup(regionHeaders[:], int(n), i)
}
// Name implements the Namer interface for region names.
func (n regionNamer) Name(x interface{}) string {
return nameRegion(n, x)
}
// Tags returns a Namer for giving a full description of a tag. The names of
// scripts and regions that are not already implied by the language name will
// in appended within parentheses. It returns nil if there is not data for the
// given tag. The type passed to Name must be a tag.
func Tags(t language.Tag) Namer {
if _, index, conf := matcher.Match(t); conf != language.No {
return tagNamer(index)
}
return nil
}
type tagNamer int
func tagFunc(i int, x interface{}) string {
return nameTag(languageNamer(i), scriptNamer(i), regionNamer(i), x)
}
// Name implements the Namer interface for tag names.
func (n tagNamer) Name(x interface{}) string {
return nameTag(languageNamer(n), scriptNamer(n), regionNamer(n), x)
}
// lookup finds the name for an entry in a global table, traversing the
// inheritance hierarchy if needed.
func lookup(table []header, dict, want int) string {
for dict != -1 {
if s := table[dict].name(want); s != "" {
return s
}
dict = int(parents[dict])
}
return ""
}
// A Dictionary holds a collection of Namers for a single language. One can
// reduce the amount of data linked in to a binary by only referencing
// Dictionaries for the languages one needs to support instead of using the
// generic Namer factories.
type Dictionary struct {
parent *Dictionary
lang header
script header
region header
}
// Tags returns a Namer for giving a full description of a tag. The names of
// scripts and regions that are not already implied by the language name will
// in appended within parentheses. It returns nil if there is not data for the
// given tag. The type passed to Name must be a tag.
func (d *Dictionary) Tags() Namer {
return dictTags{d}
}
type dictTags struct {
d *Dictionary
}
// Name implements the Namer interface for tag names.
func (n dictTags) Name(x interface{}) string {
return nameTag(dictLanguages{n.d}, dictScripts{n.d}, dictRegions{n.d}, x)
}
// Languages returns a Namer for naming languages. It returns nil if there is no
// data for the given tag. The type passed to Name must be either language.Base
// or language.Tag. Note that the result may differ between passing a tag or its
// base language. For example, for English, passing "nl-BE" would return Flemish
// whereas passing "nl" returns "Dutch".
func (d *Dictionary) Languages() Namer {
return dictLanguages{d}
}
type dictLanguages struct {
d *Dictionary
}
func (n dictLanguages) name(i int) string {
for d := n.d; d != nil; d = d.parent {
if s := d.lang.name(i); s != "" {
return s
}
}
return ""
}
// Name implements the Namer interface for language names.
func (n dictLanguages) Name(x interface{}) string {
return nameLanguage(n, x)
}
// Scripts returns a Namer for naming scripts. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Script or a language.Tag. It will not attempt to infer a script for
// tags with an unspecified script.
func (d *Dictionary) Scripts() Namer {
return dictScripts{d}
}
type dictScripts struct {
d *Dictionary
}
func (n dictScripts) name(i int) string {
for d := n.d; d != nil; d = d.parent {
if s := d.script.name(i); s != "" {
return s
}
}
return ""
}
// Name implements the Namer interface for script names.
func (n dictScripts) Name(x interface{}) string {
return nameScript(n, x)
}
// Regions returns a Namer for naming regions. It returns nil if there is no
// data for the given tag. The type passed to Name must be either a
// language.Region or a language.Tag. It will not attempt to infer a region for
// tags with an unspecified region.
func (d *Dictionary) Regions() Namer {
return dictRegions{d}
}
type dictRegions struct {
d *Dictionary
}
func (n dictRegions) name(i int) string {
for d := n.d; d != nil; d = d.parent {
if s := d.region.name(i); s != "" {
return s
}
}
return ""
}
// Name implements the Namer interface for region names.
func (n dictRegions) Name(x interface{}) string {
return nameRegion(n, x)
}
// A SelfNamer implements a Namer that returns the name of language in this same
// language. It provides a very compact mechanism to provide a comprehensive
// list of languages to users in their native language.
type SelfNamer struct {
// Supported defines the values supported by this Namer.
Supported language.Coverage
}
var (
// Self is a shared instance of a SelfNamer.
Self *SelfNamer = &self
self = SelfNamer{language.NewCoverage(selfTagSet.Tags)}
)
// Name returns the name of a given language tag in the language identified by
// this tag. It supports both the language.Base and language.Tag types.
func (n SelfNamer) Name(x interface{}) string {
t, _ := language.All.Compose(x)
base, scr, reg := t.Raw()
baseScript := language.Script{}
if (scr == language.Script{} && reg != language.Region{}) {
// For looking up in the self dictionary, we need to select the
// maximized script. This is even the case if the script isn't
// specified.
s1, _ := t.Script()
if baseScript = getScript(base); baseScript != s1 {
scr = s1
}
}
i, scr, reg := selfTagSet.index(base, scr, reg)
if i == -1 {
return ""
}
// Only return the display name if the script matches the expected script.
if (scr != language.Script{}) {
if (baseScript == language.Script{}) {
baseScript = getScript(base)
}
if baseScript != scr {
return ""
}
}
return selfHeaders[0].name(i)
}
// getScript returns the maximized script for a base language.
func getScript(b language.Base) language.Script {
tag, _ := language.Raw.Compose(b)
scr, _ := tag.Script()
return scr
}

253
vendor/golang.org/x/text/language/display/lookup.go generated vendored Normal file
View File

@ -0,0 +1,253 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package display
// This file contains common lookup code that is shared between the various
// implementations of Namer and Dictionaries.
import (
"fmt"
"sort"
"strings"
"golang.org/x/text/language"
)
type namer interface {
// name gets the string for the given index. It should walk the
// inheritance chain if a value is not present in the base index.
name(idx int) string
}
func nameLanguage(n namer, x interface{}) string {
t, _ := language.All.Compose(x)
for {
i, _, _ := langTagSet.index(t.Raw())
if s := n.name(i); s != "" {
return s
}
if t = t.Parent(); t == language.Und {
return ""
}
}
}
func nameScript(n namer, x interface{}) string {
t, _ := language.DeprecatedScript.Compose(x)
_, s, _ := t.Raw()
return n.name(scriptIndex.index(s.String()))
}
func nameRegion(n namer, x interface{}) string {
t, _ := language.DeprecatedRegion.Compose(x)
_, _, r := t.Raw()
return n.name(regionIndex.index(r.String()))
}
func nameTag(langN, scrN, regN namer, x interface{}) string {
t, ok := x.(language.Tag)
if !ok {
return ""
}
const form = language.All &^ language.SuppressScript
if c, err := form.Canonicalize(t); err == nil {
t = c
}
_, sRaw, rRaw := t.Raw()
i, scr, reg := langTagSet.index(t.Raw())
for i != -1 {
if str := langN.name(i); str != "" {
if hasS, hasR := (scr != language.Script{}), (reg != language.Region{}); hasS || hasR {
ss, sr := "", ""
if hasS {
ss = scrN.name(scriptIndex.index(scr.String()))
}
if hasR {
sr = regN.name(regionIndex.index(reg.String()))
}
// TODO: use patterns in CLDR or at least confirm they are the
// same for all languages.
if ss != "" && sr != "" {
return fmt.Sprintf("%s (%s, %s)", str, ss, sr)
}
if ss != "" || sr != "" {
return fmt.Sprintf("%s (%s%s)", str, ss, sr)
}
}
return str
}
scr, reg = sRaw, rRaw
if t = t.Parent(); t == language.Und {
return ""
}
i, _, _ = langTagSet.index(t.Raw())
}
return ""
}
// header contains the data and indexes for a single namer.
// data contains a series of strings concatenated into one. index contains the
// offsets for a string in data. For example, consider a header that defines
// strings for the languages de, el, en, fi, and nl:
//
// header{
// data: "GermanGreekEnglishDutch",
// index: []uint16{0, 6, 11, 18, 18, 23},
// }
//
// For a language with index i, the string is defined by
// data[index[i]:index[i+1]]. So the number of elements in index is always one
// greater than the number of languages for which header defines a value.
// A string for a language may be empty, which means the name is undefined. In
// the above example, the name for fi (Finnish) is undefined.
type header struct {
data string
index []uint16
}
// name looks up the name for a tag in the dictionary, given its index.
func (h *header) name(i int) string {
if 0 <= i && i < len(h.index)-1 {
return h.data[h.index[i]:h.index[i+1]]
}
return ""
}
// tagSet is used to find the index of a language in a set of tags.
type tagSet struct {
single tagIndex
long []string
}
var (
langTagSet = tagSet{
single: langIndex,
long: langTagsLong,
}
// selfTagSet is used for indexing the language strings in their own
// language.
selfTagSet = tagSet{
single: selfIndex,
long: selfTagsLong,
}
zzzz = language.MustParseScript("Zzzz")
zz = language.MustParseRegion("ZZ")
)
// index returns the index of the tag for the given base, script and region or
// its parent if the tag is not available. If the match is for a parent entry,
// the excess script and region are returned.
func (ts *tagSet) index(base language.Base, scr language.Script, reg language.Region) (int, language.Script, language.Region) {
lang := base.String()
index := -1
if (scr != language.Script{} || reg != language.Region{}) {
if scr == zzzz {
scr = language.Script{}
}
if reg == zz {
reg = language.Region{}
}
i := sort.SearchStrings(ts.long, lang)
// All entries have either a script or a region and not both.
scrStr, regStr := scr.String(), reg.String()
for ; i < len(ts.long) && strings.HasPrefix(ts.long[i], lang); i++ {
if s := ts.long[i][len(lang)+1:]; s == scrStr {
scr = language.Script{}
index = i + ts.single.len()
break
} else if s == regStr {
reg = language.Region{}
index = i + ts.single.len()
break
}
}
}
if index == -1 {
index = ts.single.index(lang)
}
return index, scr, reg
}
func (ts *tagSet) Tags() []language.Tag {
tags := make([]language.Tag, 0, ts.single.len()+len(ts.long))
ts.single.keys(func(s string) {
tags = append(tags, language.Raw.MustParse(s))
})
for _, s := range ts.long {
tags = append(tags, language.Raw.MustParse(s))
}
return tags
}
func supportedScripts() []language.Script {
scr := make([]language.Script, 0, scriptIndex.len())
scriptIndex.keys(func(s string) {
scr = append(scr, language.MustParseScript(s))
})
return scr
}
func supportedRegions() []language.Region {
reg := make([]language.Region, 0, regionIndex.len())
regionIndex.keys(func(s string) {
reg = append(reg, language.MustParseRegion(s))
})
return reg
}
// tagIndex holds a concatenated lists of subtags of length 2 to 4, one string
// for each length, which can be used in combination with binary search to get
// the index associated with a tag.
// For example, a tagIndex{
//
// "arenesfrruzh", // 6 2-byte tags.
// "barwae", // 2 3-byte tags.
// "",
//
// }
// would mean that the 2-byte tag "fr" had an index of 3, and the 3-byte tag
// "wae" had an index of 7.
type tagIndex [3]string
func (t *tagIndex) index(s string) int {
sz := len(s)
if sz < 2 || 4 < sz {
return -1
}
a := t[sz-2]
index := sort.Search(len(a)/sz, func(i int) bool {
p := i * sz
return a[p:p+sz] >= s
})
p := index * sz
if end := p + sz; end > len(a) || a[p:end] != s {
return -1
}
// Add the number of tags for smaller sizes.
for i := 0; i < sz-2; i++ {
index += len(t[i]) / (i + 2)
}
return index
}
// len returns the number of tags that are contained in the tagIndex.
func (t *tagIndex) len() (n int) {
for i, s := range t {
n += len(s) / (i + 2)
}
return n
}
// keys calls f for each tag.
func (t *tagIndex) keys(f func(key string)) {
for i, s := range *t {
for ; s != ""; s = s[i+2:] {
f(s[:i+2])
}
}
}

53114
vendor/golang.org/x/text/language/display/tables.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1
vendor/modules.txt vendored
View File

@ -55,6 +55,7 @@ golang.org/x/text/internal/number
golang.org/x/text/internal/stringset
golang.org/x/text/internal/tag
golang.org/x/text/language
golang.org/x/text/language/display
golang.org/x/text/message
golang.org/x/text/message/catalog
# gopkg.in/yaml.v2 v2.3.0

View File

@ -10,6 +10,7 @@
T(format message.Reference, v ...any)
Title()
Lang()
Dir()
} %}
{% code
@ -21,7 +22,7 @@ type BaseOf struct {
func NewBaseOf(site *domain.Site) BaseOf {
return BaseOf{
site: site,
printer: message.NewPrinter(site.Language),
printer: message.NewPrinter(language.Make(site.Language.Code())),
}
}
%}
@ -38,14 +39,16 @@ func NewBaseOf(site *domain.Site) BaseOf {
{% func (b BaseOf) Body() %}{% endfunc %}
{% func (b BaseOf) Lang() %}
{% if b.site.Language != language.Und %}
{%s b.site.Language.String() %}
{% endif %}
{%s b.site.Language.Lang() %}
{% endfunc %}
{% func (b BaseOf) Dir() %}
{%s b.site.Language.Dir() %}
{% endfunc %}
{% func Template(p Pager) %}
<!DOCTYPE html>
<html lang="{%= p.Lang() %}">
<html lang="{%= p.Lang() %}" dir="{%= p.Dir() %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@ -51,10 +51,16 @@ type Pager interface {
StreamLang(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:8
WriteLang(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:8
Dir() string
//line web/template/baseof.qtpl:8
StreamDir(qw422016 *qt422016.Writer)
//line web/template/baseof.qtpl:8
WriteDir(qq422016 qtio422016.Writer)
//line web/template/baseof.qtpl:8
}
//line web/template/baseof.qtpl:16
//line web/template/baseof.qtpl:17
type BaseOf struct {
printer *message.Printer
site *domain.Site
@ -63,185 +69,218 @@ type BaseOf struct {
func NewBaseOf(site *domain.Site) BaseOf {
return BaseOf{
site: site,
printer: message.NewPrinter(site.Language),
printer: message.NewPrinter(language.Make(site.Language.Code())),
}
}
//line web/template/baseof.qtpl:30
func (b BaseOf) StreamTitle(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:31
func (b BaseOf) StreamTitle(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:32
qw422016.E().S(b.site.Title)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
}
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
func (b BaseOf) WriteTitle(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
b.StreamTitle(qw422016)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
}
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
func (b BaseOf) Title() string {
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
b.WriteTitle(qb422016)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
return qs422016
//line web/template/baseof.qtpl:32
//line web/template/baseof.qtpl:33
}
//line web/template/baseof.qtpl:34
func (b BaseOf) StreamT(qw422016 *qt422016.Writer, key message.Reference, a ...any) {
//line web/template/baseof.qtpl:35
func (b BaseOf) StreamT(qw422016 *qt422016.Writer, key message.Reference, a ...any) {
//line web/template/baseof.qtpl:36
qw422016.E().S(b.printer.Sprintf(key, a...))
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
}
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
func (b BaseOf) WriteT(qq422016 qtio422016.Writer, key message.Reference, a ...any) {
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
b.StreamT(qw422016, key, a...)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
}
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
func (b BaseOf) T(key message.Reference, a ...any) string {
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
b.WriteT(qb422016, key, a...)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
return qs422016
//line web/template/baseof.qtpl:36
//line web/template/baseof.qtpl:37
}
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
func (b BaseOf) StreamBody(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
}
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
func (b BaseOf) WriteBody(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
b.StreamBody(qw422016)
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
}
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
func (b BaseOf) Body() string {
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
b.WriteBody(qb422016)
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
return qs422016
//line web/template/baseof.qtpl:38
//line web/template/baseof.qtpl:39
}
//line web/template/baseof.qtpl:40
func (b BaseOf) StreamLang(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:41
if b.site.Language != language.Und {
func (b BaseOf) StreamLang(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:42
qw422016.E().S(b.site.Language.String())
qw422016.E().S(b.site.Language.Lang())
//line web/template/baseof.qtpl:43
}
//line web/template/baseof.qtpl:44
}
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
func (b BaseOf) WriteLang(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
b.StreamLang(qw422016)
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
}
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
func (b BaseOf) Lang() string {
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
b.WriteLang(qb422016)
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
return qs422016
//line web/template/baseof.qtpl:44
//line web/template/baseof.qtpl:43
}
//line web/template/baseof.qtpl:45
func (b BaseOf) StreamDir(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:46
func StreamTemplate(qw422016 *qt422016.Writer, p Pager) {
//line web/template/baseof.qtpl:46
qw422016.N().S(`<!DOCTYPE html><html lang="`)
//line web/template/baseof.qtpl:48
p.StreamLang(qw422016)
//line web/template/baseof.qtpl:48
qw422016.N().S(`"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>`)
//line web/template/baseof.qtpl:53
p.StreamTitle(qw422016)
//line web/template/baseof.qtpl:53
qw422016.N().S(`</title></head><body>`)
//line web/template/baseof.qtpl:57
p.StreamBody(qw422016)
//line web/template/baseof.qtpl:57
qw422016.N().S(`</body></html>`)
//line web/template/baseof.qtpl:60
qw422016.E().S(b.site.Language.Dir())
//line web/template/baseof.qtpl:47
}
//line web/template/baseof.qtpl:60
func WriteTemplate(qq422016 qtio422016.Writer, p Pager) {
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
func (b BaseOf) WriteDir(qq422016 qtio422016.Writer) {
//line web/template/baseof.qtpl:47
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:60
StreamTemplate(qw422016, p)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
b.StreamDir(qw422016)
//line web/template/baseof.qtpl:47
qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
}
//line web/template/baseof.qtpl:60
func Template(p Pager) string {
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
func (b BaseOf) Dir() string {
//line web/template/baseof.qtpl:47
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:60
WriteTemplate(qb422016, p)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
b.WriteDir(qb422016)
//line web/template/baseof.qtpl:47
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
return qs422016
//line web/template/baseof.qtpl:60
//line web/template/baseof.qtpl:47
}
//line web/template/baseof.qtpl:49
func StreamTemplate(qw422016 *qt422016.Writer, p Pager) {
//line web/template/baseof.qtpl:49
qw422016.N().S(`<!DOCTYPE html><html lang="`)
//line web/template/baseof.qtpl:51
p.StreamLang(qw422016)
//line web/template/baseof.qtpl:51
qw422016.N().S(`" dir="`)
//line web/template/baseof.qtpl:51
p.StreamDir(qw422016)
//line web/template/baseof.qtpl:51
qw422016.N().S(`"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>`)
//line web/template/baseof.qtpl:56
p.StreamTitle(qw422016)
//line web/template/baseof.qtpl:56
qw422016.N().S(`</title></head><body>`)
//line web/template/baseof.qtpl:60
p.StreamBody(qw422016)
//line web/template/baseof.qtpl:60
qw422016.N().S(`</body></html>`)
//line web/template/baseof.qtpl:63
}
//line web/template/baseof.qtpl:63
func WriteTemplate(qq422016 qtio422016.Writer, p Pager) {
//line web/template/baseof.qtpl:63
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/baseof.qtpl:63
StreamTemplate(qw422016, p)
//line web/template/baseof.qtpl:63
qt422016.ReleaseWriter(qw422016)
//line web/template/baseof.qtpl:63
}
//line web/template/baseof.qtpl:63
func Template(p Pager) string {
//line web/template/baseof.qtpl:63
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/baseof.qtpl:63
WriteTemplate(qb422016, p)
//line web/template/baseof.qtpl:63
qs422016 := string(qb422016.B)
//line web/template/baseof.qtpl:63
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/baseof.qtpl:63
return qs422016
//line web/template/baseof.qtpl:63
}

View File

@ -1,6 +1,4 @@
{% import (
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
) %}
@ -28,8 +26,16 @@ func NewPage(base BaseOf, page *domain.Page) Page {
{% endfunc %}
{% func (p Page) Lang() %}
{% if p.page.Language != language.Und %}
{%s p.page.Language.String() %}
{% if p.page.Language != domain.LanguageUnd %}
{%s p.page.Language.Lang() %}
{% else %}
{%= p.BaseOf.Lang() %}
{% endif %}
{% endfunc %}
{% func (p Page) Dir() %}
{% if p.page.Language != domain.LanguageUnd %}
{%s p.page.Language.Dir() %}
{% else %}
{%= p.BaseOf.Lang() %}
{% endif %}

View File

@ -6,25 +6,23 @@ package template
//line web/template/page.qtpl:1
import (
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/domain"
)
//line web/template/page.qtpl:7
//line web/template/page.qtpl:5
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line web/template/page.qtpl:7
//line web/template/page.qtpl:5
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line web/template/page.qtpl:8
//line web/template/page.qtpl:6
type Page struct {
BaseOf
page *domain.Page
@ -37,121 +35,162 @@ func NewPage(base BaseOf, page *domain.Page) Page {
}
}
//line web/template/page.qtpl:22
//line web/template/page.qtpl:20
func (p Page) StreamTitle(qw422016 *qt422016.Writer) {
//line web/template/page.qtpl:23
//line web/template/page.qtpl:21
if p.page.Title != "" {
//line web/template/page.qtpl:24
//line web/template/page.qtpl:22
qw422016.E().S(p.page.Title)
//line web/template/page.qtpl:25
//line web/template/page.qtpl:23
} else {
//line web/template/page.qtpl:26
//line web/template/page.qtpl:24
p.BaseOf.StreamTitle(qw422016)
//line web/template/page.qtpl:27
//line web/template/page.qtpl:25
}
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
}
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
func (p Page) WriteTitle(qq422016 qtio422016.Writer) {
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
p.StreamTitle(qw422016)
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
qt422016.ReleaseWriter(qw422016)
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
}
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
func (p Page) Title() string {
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
p.WriteTitle(qb422016)
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
qs422016 := string(qb422016.B)
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
return qs422016
//line web/template/page.qtpl:28
//line web/template/page.qtpl:26
}
//line web/template/page.qtpl:30
//line web/template/page.qtpl:28
func (p Page) StreamLang(qw422016 *qt422016.Writer) {
//line web/template/page.qtpl:29
if p.page.Language != domain.LanguageUnd {
//line web/template/page.qtpl:30
qw422016.E().S(p.page.Language.Lang())
//line web/template/page.qtpl:31
if p.page.Language != language.Und {
//line web/template/page.qtpl:32
qw422016.E().S(p.page.Language.String())
//line web/template/page.qtpl:33
} else {
//line web/template/page.qtpl:34
//line web/template/page.qtpl:32
p.BaseOf.StreamLang(qw422016)
//line web/template/page.qtpl:35
//line web/template/page.qtpl:33
}
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
}
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
func (p Page) WriteLang(qq422016 qtio422016.Writer) {
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
p.StreamLang(qw422016)
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
qt422016.ReleaseWriter(qw422016)
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
}
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
func (p Page) Lang() string {
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
p.WriteLang(qb422016)
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
qs422016 := string(qb422016.B)
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/page.qtpl:36
//line web/template/page.qtpl:34
return qs422016
//line web/template/page.qtpl:34
}
//line web/template/page.qtpl:36
func (p Page) StreamDir(qw422016 *qt422016.Writer) {
//line web/template/page.qtpl:37
if p.page.Language != domain.LanguageUnd {
//line web/template/page.qtpl:38
qw422016.E().S(p.page.Language.Dir())
//line web/template/page.qtpl:39
} else {
//line web/template/page.qtpl:40
p.BaseOf.StreamLang(qw422016)
//line web/template/page.qtpl:41
}
//line web/template/page.qtpl:42
}
//line web/template/page.qtpl:38
func (p Page) StreamBody(qw422016 *qt422016.Writer) {
//line web/template/page.qtpl:38
qw422016.N().S(`<p>`)
//line web/template/page.qtpl:39
qw422016.N().Z(p.page.Content)
//line web/template/page.qtpl:39
qw422016.N().S(`</p>`)
//line web/template/page.qtpl:40
}
//line web/template/page.qtpl:40
func (p Page) WriteBody(qq422016 qtio422016.Writer) {
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
func (p Page) WriteDir(qq422016 qtio422016.Writer) {
//line web/template/page.qtpl:42
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/page.qtpl:40
p.StreamBody(qw422016)
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
p.StreamDir(qw422016)
//line web/template/page.qtpl:42
qt422016.ReleaseWriter(qw422016)
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
}
//line web/template/page.qtpl:40
func (p Page) Body() string {
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
func (p Page) Dir() string {
//line web/template/page.qtpl:42
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/page.qtpl:40
p.WriteBody(qb422016)
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
p.WriteDir(qb422016)
//line web/template/page.qtpl:42
qs422016 := string(qb422016.B)
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
return qs422016
//line web/template/page.qtpl:40
//line web/template/page.qtpl:42
}
//line web/template/page.qtpl:44
func (p Page) StreamBody(qw422016 *qt422016.Writer) {
//line web/template/page.qtpl:44
qw422016.N().S(`<p>`)
//line web/template/page.qtpl:45
qw422016.N().Z(p.page.Content)
//line web/template/page.qtpl:45
qw422016.N().S(`</p>`)
//line web/template/page.qtpl:46
}
//line web/template/page.qtpl:46
func (p Page) WriteBody(qq422016 qtio422016.Writer) {
//line web/template/page.qtpl:46
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/page.qtpl:46
p.StreamBody(qw422016)
//line web/template/page.qtpl:46
qt422016.ReleaseWriter(qw422016)
//line web/template/page.qtpl:46
}
//line web/template/page.qtpl:46
func (p Page) Body() string {
//line web/template/page.qtpl:46
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/page.qtpl:46
p.WriteBody(qb422016)
//line web/template/page.qtpl:46
qs422016 := string(qb422016.B)
//line web/template/page.qtpl:46
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/page.qtpl:46
return qs422016
//line web/template/page.qtpl:46
}