From bd2a62b40d99ffd412ddb4c25967b5ed67456105 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 18:39:11 +0600 Subject: [PATCH 1/5] :label: Created template Context domain --- internal/domain/context.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 internal/domain/context.go diff --git a/internal/domain/context.go b/internal/domain/context.go new file mode 100644 index 0000000..0e5ccba --- /dev/null +++ b/internal/domain/context.go @@ -0,0 +1,7 @@ +package domain + +// Context represent a all-in-one theme template context. +type Context struct { + Site *Site + *Page +} From 47c88e26f082fa44337694e7223e7271aa4e5ace Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 18:39:54 +0600 Subject: [PATCH 2/5] :recycle: Refactored template context usage --- internal/cmd/home/home.go | 37 ++++++++---------------- internal/templateutil/templateutil.go | 16 ++++------ internal/templateutil/urls/urls.go | 21 ++++---------- internal/theme/repository.go | 2 +- internal/theme/repository/fs/fs_theme.go | 12 ++++---- internal/theme/usecase.go | 14 ++++++--- internal/theme/usecase/theme_ucase.go | 20 +++++++++---- 7 files changed, 53 insertions(+), 69 deletions(-) diff --git a/internal/cmd/home/home.go b/internal/cmd/home/home.go index fa91a4c..f3c93ce 100644 --- a/internal/cmd/home/home.go +++ b/internal/cmd/home/home.go @@ -28,42 +28,32 @@ import ( siteucase "source.toby3d.me/toby3d/home/internal/site/usecase" staticfsrepo "source.toby3d.me/toby3d/home/internal/static/repository/fs" staticucase "source.toby3d.me/toby3d/home/internal/static/usecase" - "source.toby3d.me/toby3d/home/internal/templateutil" themefsrepo "source.toby3d.me/toby3d/home/internal/theme/repository/fs" themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase" "source.toby3d.me/toby3d/home/internal/urlutil" ) -type ( - App struct { - server *http.Server - } - - // TODO(toby3d): move this into separated package. - Context struct { - Site *domain.Site - Page *domain.Page - } -) +type App struct { + server *http.Server +} func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { themeDir := os.DirFS(config.ThemeDir) + + partialsDir, err := fs.Sub(themeDir, "partials") + if err != nil { + return nil, fmt.Errorf("cannot substitute into partials subdirectory: %w", err) + } contentDir := os.DirFS(config.ContentDir) resources := resourcefsrepo.NewFileServerResourceRepository(contentDir) sites := sitefsrepo.NewFileSystemSiteRepository(contentDir) siter := siteucase.NewSiteUseCase(sites, resources) - - funcMap, err := templateutil.New(themeDir, siter) - if err != nil { - return nil, fmt.Errorf("cannot setup template.FuncMap for templates: %w", err) - } - staticDir := os.DirFS(config.StaticDir) statics := staticfsrepo.NewFileServerStaticRepository(staticDir) staticer := staticucase.NewStaticUseCase(statics) resourcer := resourceucase.NewResourceUseCase(resources) - themes := themefsrepo.NewFileSystemThemeRepository(themeDir, funcMap) - themer := themeucase.NewThemeUseCase(themes) + themes := themefsrepo.NewFileSystemThemeRepository(themeDir) + themer := themeucase.NewThemeUseCase(partialsDir, themes) pages := pagefsrepo.NewFileSystemPageRepository(contentDir) pager := pageucase.NewPageUseCase(pages, resources) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -175,7 +165,7 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", ")) - tpl, err := themer.Do(r.Context()) + pageTemplate, err := themer.Do(r.Context(), s, p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -183,10 +173,7 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { } w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8) - if err = tpl.Execute(w, &Context{ - Site: s, - Page: p, - }); err != nil { + if err = pageTemplate(w); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) diff --git a/internal/templateutil/templateutil.go b/internal/templateutil/templateutil.go index 3477c22..6f3c1ab 100644 --- a/internal/templateutil/templateutil.go +++ b/internal/templateutil/templateutil.go @@ -1,11 +1,10 @@ package templateutil import ( - "fmt" "html/template" "io/fs" - "source.toby3d.me/toby3d/home/internal/site" + "source.toby3d.me/toby3d/home/internal/domain" "source.toby3d.me/toby3d/home/internal/templateutil/collections" "source.toby3d.me/toby3d/home/internal/templateutil/partials" "source.toby3d.me/toby3d/home/internal/templateutil/safe" @@ -20,12 +19,7 @@ type Function struct { Name string } -func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) { - partialDir, err := fs.Sub(dir, "partials") - if err != nil { - return nil, fmt.Errorf("cannot substitute into partials subdirectory: %w", err) - } - +func New(partialsDir fs.FS, site *domain.Site) template.FuncMap { funcMap := make(template.FuncMap) funcs := make([]Function, 0) stringsNamespace := strings.New() @@ -45,7 +39,7 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) { "safeHTML": safeNamespace.HTML, }, }) - partialsNamespace := partials.New(partialDir, funcMap) + partialsNamespace := partials.New(partialsDir, funcMap) funcs = append(funcs, Function{ Name: "partials", Handler: func(v ...any) any { return partialsNamespace }, @@ -53,7 +47,7 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) { "partial": partialsNamespace.Include, }, }) - urlsNamespace := urls.New(siter) + urlsNamespace := urls.New(site) funcs = append(funcs, Function{ Name: "urls", Handler: func(v ...any) any { return urlsNamespace }, @@ -89,5 +83,5 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) { } } - return funcMap, nil + return funcMap } diff --git a/internal/templateutil/urls/urls.go b/internal/templateutil/urls/urls.go index e674ab7..4d49b54 100644 --- a/internal/templateutil/urls/urls.go +++ b/internal/templateutil/urls/urls.go @@ -1,35 +1,24 @@ package urls import ( - "context" - "errors" - "fmt" "net/url" "path" "source.toby3d.me/toby3d/home/internal/domain" - "source.toby3d.me/toby3d/home/internal/site" ) type Namespace struct { - siter site.UseCase + site *domain.Site } -var ErrAbsURL error = errors.New("unsupported input type for AbsURL") - -func New(siter site.UseCase) *Namespace { +func New(site *domain.Site) *Namespace { return &Namespace{ - siter: siter, + site: site, } } -func (ns *Namespace) AbsURL(p string) (string, error) { - site, err := ns.siter.Do(context.Background(), domain.LanguageUnd) - if err != nil { - return "", fmt.Errorf("cannot fetch site root for AbsURL processing: %w", err) - } - - return site.BaseURL.JoinPath(p).String(), nil +func (ns *Namespace) AbsURL(p string) string { + return ns.site.BaseURL.JoinPath(p).String() } func (ns *Namespace) RelURL(p string) string { diff --git a/internal/theme/repository.go b/internal/theme/repository.go index b961d85..34c715d 100644 --- a/internal/theme/repository.go +++ b/internal/theme/repository.go @@ -7,5 +7,5 @@ import ( type Repository interface { // TODO(toby3d): use Page context to find it's specific template. - Get(ctx context.Context) (*template.Template, error) + Get(ctx context.Context, funcMap template.FuncMap) (*template.Template, error) } diff --git a/internal/theme/repository/fs/fs_theme.go b/internal/theme/repository/fs/fs_theme.go index 9535275..bc96285 100644 --- a/internal/theme/repository/fs/fs_theme.go +++ b/internal/theme/repository/fs/fs_theme.go @@ -10,19 +10,17 @@ import ( ) type fileSystemThemeRepository struct { - dir fs.FS - funcMap template.FuncMap + dir fs.FS } -func NewFileSystemThemeRepository(dir fs.FS, funcMap template.FuncMap) theme.Repository { +func NewFileSystemThemeRepository(dir fs.FS) theme.Repository { return &fileSystemThemeRepository{ - dir: dir, - funcMap: funcMap, + dir: dir, } } -func (repo *fileSystemThemeRepository) Get(ctx context.Context) (*template.Template, error) { - tpl, err := template.New("").Funcs(repo.funcMap).ParseFS(repo.dir, "*.html") +func (repo *fileSystemThemeRepository) Get(ctx context.Context, funcMap template.FuncMap) (*template.Template, error) { + tpl, err := template.New("").Funcs(funcMap).ParseFS(repo.dir, "*.html") if err != nil { return nil, fmt.Errorf("cannot find baseof template: %w", err) } diff --git a/internal/theme/usecase.go b/internal/theme/usecase.go index 99c646a..993388a 100644 --- a/internal/theme/usecase.go +++ b/internal/theme/usecase.go @@ -2,9 +2,15 @@ package theme import ( "context" - "html/template" + "io" + + "source.toby3d.me/toby3d/home/internal/domain" ) -type UseCase interface { - Do(ctx context.Context) (*template.Template, error) -} +type ( + Writer func(w io.Writer) error + + UseCase interface { + Do(ctx context.Context, site *domain.Site, page *domain.Page) (Writer, error) + } +) diff --git a/internal/theme/usecase/theme_ucase.go b/internal/theme/usecase/theme_ucase.go index ded3ce4..a89ede4 100644 --- a/internal/theme/usecase/theme_ucase.go +++ b/internal/theme/usecase/theme_ucase.go @@ -3,26 +3,36 @@ package usecase import ( "context" "fmt" - "html/template" + "io" + "io/fs" + "source.toby3d.me/toby3d/home/internal/domain" + "source.toby3d.me/toby3d/home/internal/templateutil" "source.toby3d.me/toby3d/home/internal/theme" ) type themeUseCase struct { + dir fs.FS themes theme.Repository } -func NewThemeUseCase(themes theme.Repository) theme.UseCase { +func NewThemeUseCase(dir fs.FS, themes theme.Repository) theme.UseCase { return &themeUseCase{ + dir: dir, themes: themes, } } -func (ucase *themeUseCase) Do(ctx context.Context) (*template.Template, error) { - out, err := ucase.themes.Get(ctx) +func (ucase *themeUseCase) Do(ctx context.Context, site *domain.Site, page *domain.Page) (theme.Writer, error) { + out, err := ucase.themes.Get(ctx, templateutil.New(ucase.dir, site)) if err != nil { return nil, fmt.Errorf("cannot find theme: %w", err) } - return out.Lookup("baseof"), nil + return func(w io.Writer) error { + return out.Lookup("baseof.html").Execute(w, &domain.Context{ + Site: site, + Page: page, + }) + }, nil } From 0ea82fe25da1bbfa4413b95c183a431273803c8e Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 18:40:27 +0600 Subject: [PATCH 3/5] :memo: Updated README due changes in templates lookup --- docs/README.en.md | 4 +--- docs/README.ru.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/README.en.md b/docs/README.en.md index 47420f4..81b0181 100644 --- a/docs/README.en.md +++ b/docs/README.en.md @@ -82,11 +82,10 @@ 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: +Templates are required to render page content in browsers and must be placed in `$HOME_THEME_DIR`. At least one template `baseof.html` that will be called for each page is required, for example: ```html -{{ define "baseof" }} @@ -102,7 +101,6 @@ Templates are required to render page content in browsers and must be placed in {{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }} -{{ 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](../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. diff --git a/docs/README.ru.md b/docs/README.ru.md index f00c607..e373363 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -83,11 +83,10 @@ This is a sample page content for demo. * В `$HOME_STATIC_DIR` как публично доступная статика, без каких-либо ограничений "как есть", по тому же пути в котором файл расположен в директории; * В `$HOME_CONTENT_DIR` рядом с нужной страницей: файл будет доступен в коллекциях `.Site.Resources` и `.Page.Resources` и ограничен настройками доступа родительской страницы; -Для рендера содержимого страниц в браузерах необходимы шаблоны, которые должны размещаться в `$HOME_THEME_DIR`. Для работы необходим хотя бы один шаблон с блоком `baseof` который будет вызываться для каждой страницы, например: +Для рендера содержимого страниц в браузерах необходимы шаблоны, которые должны размещаться в `$HOME_THEME_DIR`. Для работы необходим хотя бы один шаблон `baseof.html` который будет вызываться для каждой страницы, например: ```html -{{ define "baseof" }} @@ -103,7 +102,6 @@ This is a sample page content for demo. {{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }} -{{ end }} ``` Шаблонизация работает по [инструкциям стандартного шаблонизатора Go](https://pkg.go.dev/html/template) с небольшими дополнениями в виде [дополнительных утилит](../internal/templateutil/templateutil.go). В `$HOME_THEME_DIR` разрешена любая комбинация и иеархия шаблонов и их взаимосвязей до тех пор, пока существует `baseof` в качестве единого родительского блока. From fa1faa3bc30eb32dec42282d8a4e8b884e3dc9df Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 19:02:13 +0600 Subject: [PATCH 4/5] :sparkles: Added language specific URL transformers --- internal/templateutil/templateutil.go | 6 ++++-- internal/templateutil/urls/urls.go | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/templateutil/templateutil.go b/internal/templateutil/templateutil.go index 6f3c1ab..3929786 100644 --- a/internal/templateutil/templateutil.go +++ b/internal/templateutil/templateutil.go @@ -52,8 +52,10 @@ func New(partialsDir fs.FS, site *domain.Site) template.FuncMap { Name: "urls", Handler: func(v ...any) any { return urlsNamespace }, Methods: template.FuncMap{ - "absURL": urlsNamespace.AbsURL, - "relURL": urlsNamespace.RelURL, + "absLangURL": urlsNamespace.AbsLangURL, + "absURL": urlsNamespace.AbsURL, + "relLangURL": urlsNamespace.RelLangURL, + "relURL": urlsNamespace.RelURL, }, }) transformNamespace := transform.New() diff --git a/internal/templateutil/urls/urls.go b/internal/templateutil/urls/urls.go index 4d49b54..17b67d5 100644 --- a/internal/templateutil/urls/urls.go +++ b/internal/templateutil/urls/urls.go @@ -28,3 +28,11 @@ func (ns *Namespace) RelURL(p string) string { func (ns *Namespace) Parse(rawUrl string) (*url.URL, error) { return url.Parse(rawUrl) } + +func (ns *Namespace) RelLangURL(p string) string { + return path.Join(ns.site.BaseURL.Path, ns.site.LanguagePrefix(), p) +} + +func (ns *Namespace) AbsLangURL(p string) string { + return ns.site.BaseURL.JoinPath(ns.site.LanguagePrefix(), p).String() +} From bd4a14b38bb64a83617a7a58c34a2320036a540f Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 19:02:25 +0600 Subject: [PATCH 5/5] :art: Format of the code --- internal/cmd/home/home.go | 4 ++-- internal/templateutil/templateutil.go | 4 ++-- internal/theme/usecase/theme_ucase.go | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/cmd/home/home.go b/internal/cmd/home/home.go index f3c93ce..d98c446 100644 --- a/internal/cmd/home/home.go +++ b/internal/cmd/home/home.go @@ -165,7 +165,7 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", ")) - pageTemplate, err := themer.Do(r.Context(), s, p) + template, err := themer.Do(r.Context(), s, p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -173,7 +173,7 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { } w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8) - if err = pageTemplate(w); err != nil { + if err = template(w); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) diff --git a/internal/templateutil/templateutil.go b/internal/templateutil/templateutil.go index 3929786..e3e3745 100644 --- a/internal/templateutil/templateutil.go +++ b/internal/templateutil/templateutil.go @@ -71,9 +71,9 @@ func New(partialsDir fs.FS, site *domain.Site) template.FuncMap { Name: "collections", Handler: func(v ...any) any { return collectionsNamespace }, Methods: template.FuncMap{ - "slice": collectionsNamespace.Slice, - "seq": collectionsNamespace.Seq, "index": collectionsNamespace.Index, + "seq": collectionsNamespace.Seq, + "slice": collectionsNamespace.Slice, }, }) diff --git a/internal/theme/usecase/theme_ucase.go b/internal/theme/usecase/theme_ucase.go index a89ede4..66f0f12 100644 --- a/internal/theme/usecase/theme_ucase.go +++ b/internal/theme/usecase/theme_ucase.go @@ -12,19 +12,19 @@ import ( ) type themeUseCase struct { - dir fs.FS - themes theme.Repository + partials fs.FS + themes theme.Repository } -func NewThemeUseCase(dir fs.FS, themes theme.Repository) theme.UseCase { +func NewThemeUseCase(partials fs.FS, themes theme.Repository) theme.UseCase { return &themeUseCase{ - dir: dir, - themes: themes, + partials: partials, + themes: themes, } } func (ucase *themeUseCase) Do(ctx context.Context, site *domain.Site, page *domain.Page) (theme.Writer, error) { - out, err := ucase.themes.Get(ctx, templateutil.New(ucase.dir, site)) + out, err := ucase.themes.Get(ctx, templateutil.New(ucase.partials, site)) if err != nil { return nil, fmt.Errorf("cannot find theme: %w", err) }