Skip to content

Commit

Permalink
templates: make public and admin pages use slightly complexer default…
Browse files Browse the repository at this point in the history
… logic

the admin pages now rely on DEFAULT_DIR and DEFAULT_ADMIN_DIR instead of only
DEFAULT_DIR
  • Loading branch information
Wessie committed Apr 26, 2024
1 parent 0a9a3bc commit b98c7b8
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 55 deletions.
139 changes: 88 additions & 51 deletions templates/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,14 @@ func (tb ThemeBundle) Assets() fs.FS {
type loadState struct {
fs fs.FS

baseTemplates []string
defaultPartials []string
defaultForms []string
defaultBundle map[string]*TemplateBundle
baseTemplates []string
defaults loadStateDefault
}

type loadStateDefault struct {
partials []string
forms []string
bundle map[string]*TemplateBundle
}

func LoadThemes(fsys fs.FS) (Themes, error) {
Expand All @@ -316,75 +320,108 @@ func LoadThemes(fsys fs.FS) (Themes, error) {
var err error

state.fs = fsys

// first, we're looking for .tmpl files in the main template directory
// these are included in all other templates as a base
state.baseTemplates, err = readDirFilterString(fsys, ".", isTemplate)
if err != nil {
return nil, errors.E(op, err)
}

// find our default directory
state.defaultBundle, err = state.loadSubDir(DEFAULT_DIR)
// then we're going to look for directories that don't start with a dot
subdirs, err := readDirFilterString(fsys, ".", func(de fs.DirEntry) bool {
return !strings.HasPrefix(de.Name(), ".") && de.IsDir()
})
if err != nil {
return nil, errors.E(op, err)
}
// sanity check that we have atleast 1 bundle
if len(state.defaultBundle) == 0 {
return nil, errors.E(op, "default bundle empty")
}

// grab the partials from any template bundle
for _, v := range state.defaultBundle {
state.defaultPartials = v.partials
state.defaultForms = v.forms
break
// now each directory will be a separate theme in the final result, but we
// have 'public' themes and 'admin' themes so split those apart
var publicDirs, adminDirs []string
for _, dir := range subdirs {
if strings.HasPrefix(dir, ADMIN_PREFIX) {
adminDirs = append(adminDirs, dir)
} else {
publicDirs = append(publicDirs, dir)
}
}

// get the assets directory fs
assets, err := fs.Sub(fsys, path.Join(DEFAULT_DIR, ASSETS_DIR))
if err != nil && !errors.IsE(err, os.ErrNotExist) {
fmt.Println(publicDirs, adminDirs)
// now setup the themes we're going to end up returning later
var themes = make(Themes)

// fill it with the public themes
err = state.loadThemes(themes, DEFAULT_DIR, publicDirs)
if err != nil {
return nil, errors.E(op, err)
}

// read the rest of the directories
subdirs, err := readDirFilterString(fsys, ".", func(e fs.DirEntry) bool {
isExcluded := strings.HasPrefix(e.Name(), ".")
return !isExcluded && e.IsDir()
})
// and the admin themes
err = state.loadThemes(themes, DEFAULT_ADMIN_DIR, adminDirs)
if err != nil {
return nil, errors.E(op, err)
}

// add our default directory that we loaded above as a ThemeBundle
themes := Themes{
DEFAULT_DIR: ThemeBundle{DEFAULT_DIR, state.defaultBundle, assets},
return themes, nil
}

// noExt removes the extension of s as returned by filepath.Ext
func noExt(s string) string {
return strings.TrimSuffix(filepath.Base(s), filepath.Ext(s))
}

func (ls loadState) loadThemes(themes Themes, defaultDir string, dirs []string) error {
const op errors.Op = "templates/loadState.loadThemes"
var defaults loadStateDefault
var err error

// load the default theme
defaults.bundle, err = ls.loadSubDir(defaultDir)
if errors.IsE(err, os.ErrNotExist) {
return errors.E(op, err, errors.Info("default theme does not exist"))
}
// then read the rest of the themes
for _, subdir := range subdirs {
if subdir == DEFAULT_DIR { // skip the default dir since we already loaded it earlier
if err != nil {
return errors.E(op, err)
}
// grab the partials and forms for quicker access
for _, v := range defaults.bundle {
defaults.forms = v.forms
defaults.partials = v.partials
break
}
// set the default in the loadState so it can be used by the other themes
ls.defaults = defaults

// and we need the assets directory for the construction of the
// ThemeBundle
assetsFs, err := fs.Sub(ls.fs, path.Join(defaultDir, ASSETS_DIR))
if err != nil && !errors.IsE(err, os.ErrNotExist) {
return errors.E(op, err)
}

// construct the bundle for the default
themes[defaultDir] = ThemeBundle{defaultDir, defaults.bundle, assetsFs}

// and now we have to do it for all the leftover directories
for _, dir := range dirs {
if dir == defaultDir {
// skip the default, since we already loaded it above
continue
}
bundles, err := state.loadSubDir(subdir)

bundle, err := ls.loadSubDir(dir)
if err != nil {
return nil, errors.E(op, err)
return errors.E(op, err)
}

assets, err := fs.Sub(fsys, path.Join(subdir, ASSETS_DIR))
assetsFs, err := fs.Sub(ls.fs, path.Join(dir, ASSETS_DIR))
if err != nil && !errors.IsE(err, os.ErrNotExist) {
return nil, errors.E(op, err)
return errors.E(op, err)
}

themes[subdir] = ThemeBundle{
name: subdir,
pages: bundles,
assets: assets,
}
themes[dir] = ThemeBundle{dir, bundle, assetsFs}
}

return themes, nil
}

// noExt removes the extension of s as returned by filepath.Ext
func noExt(s string) string {
return strings.TrimSuffix(filepath.Base(s), filepath.Ext(s))
return nil
}

// loadSubDir searches a subdirectory of the FS used in the creation of the loader.
Expand All @@ -398,8 +435,8 @@ func (ls loadState) loadSubDir(dir string) (map[string]*TemplateBundle, error) {
var bundle = TemplateBundle{
fs: ls.fs,
base: ls.baseTemplates,
defaultPartials: ls.defaultPartials,
defaultForms: ls.defaultForms,
defaultPartials: ls.defaults.partials,
defaultForms: ls.defaults.forms,
}

// read the forms subdirectory
Expand Down Expand Up @@ -444,7 +481,7 @@ func (ls loadState) loadSubDir(dir string) (map[string]*TemplateBundle, error) {
// create a bundle for each page in this directory
pageBundle := bundle

defaultPage := ls.defaultBundle[noExt(name)]
defaultPage := ls.defaults.bundle[noExt(name)]
if defaultPage != nil {
pageBundle.defaultPage = defaultPage.page
}
Expand All @@ -454,14 +491,14 @@ func (ls loadState) loadSubDir(dir string) (map[string]*TemplateBundle, error) {
}

// if there are no defaults to handle, we're done
if ls.defaultBundle == nil {
if ls.defaults.bundle == nil {
return bundles, nil
}

// otherwise check for missing pages, these are pages defined
// in the default theme but not in this current theme. Copy over
// the default pages if they're missing.
for name, page := range ls.defaultBundle {
for name, page := range ls.defaults.bundle {
if _, ok := bundles[name]; ok {
continue
}
Expand Down
39 changes: 35 additions & 4 deletions templates/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ func txtarFSFromBytes(b []byte) fstest.MapFS {
return txtarFS(txtar.Parse(b))
}

func txtarFSFromString(b string) fstest.MapFS {
return txtarFSFromBytes([]byte(b))
}

func TestLoadThemes(t *testing.T) {
type args struct {
fsys fs.FS
Expand Down Expand Up @@ -115,15 +119,17 @@ func FuzzLoadThemes(f *testing.F) {

func TestExecuteTemplate(t *testing.T) {
type args struct {
fsys fs.FS
fsys fs.FS
theme string
}
tests := []struct {
name string
args args
wantErr bool
shouldExec bool
}{
{"empty", args{txtarFSFromBytes([]byte(`
{"empty", args{
fsys: txtarFSFromString(`
-- base.tmpl --
{{ define "base" }}
base
Expand All @@ -136,7 +142,31 @@ base
-- default-light/partials/empty.tmpl --
{{ define "empty_part" }}
empty
{{ end }}`))}, false, true},
{{ end }}
-- admin-light/default.tmpl --
null
`),
theme: "default-light",
}, false, true},
{"admin", args{
fsys: txtarFSFromString(`
-- base.tmpl --
{{ define "base" }}
admin-base
{{ template "admin" }}
{{ template "admin_partial" }}
{{ end }}
-- default-light/default.tmpl --
{{ define "empty" }}{{ end }}
-- admin-light/default.tmpl --
{{ define "admin" }}{{ end }}
-- admin-light/partials/admin.tmpl --
{{ define "admin_partial" }}{{ end }}
-- admin-dark/default.tmpl --
{{ define "admin" }}{{ end }}
`),
theme: "admin-dark",
}, false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -145,9 +175,10 @@ empty
t.Errorf("FromFS() error = %v, wantErr %v", err, tt.wantErr)
return
}

if tt.shouldExec {
exec := got.Executor()
err = exec.ExecuteTemplate(context.Background(), "default", "default", "base", io.Discard, nil)
err = exec.ExecuteTemplate(context.Background(), tt.args.theme, "default", "base", io.Discard, nil)
if err != nil {
t.Errorf("template did not execute: %v", err)
return
Expand Down

0 comments on commit b98c7b8

Please sign in to comment.