mirror of
https://github.com/facundoolano/jorge.git
synced 2024-12-26 21:58:51 +01:00
Introduce config mod and struct (#2)
* outline config mod and struct * replace site interfaces * load config from config.yml * adapt commands to load and pass config * fix test * fix tests * use symlinks for static assets * implement absolute url filter * doc comment * fix tests * remove outdated TODO comments * Add go build actions workflow (#3) * Add go build actions workflow * set go to 1.22
This commit is contained in:
parent
859327d4bd
commit
a4279aae0d
11 changed files with 265 additions and 135 deletions
|
@ -2,17 +2,11 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
|
"github.com/facundoolano/blorg/config"
|
||||||
"github.com/facundoolano/blorg/site"
|
"github.com/facundoolano/blorg/site"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SRC_DIR = "src"
|
|
||||||
const TARGET_DIR = "target"
|
|
||||||
const LAYOUTS_DIR = "layouts"
|
|
||||||
const INCLUDES_DIR = "includes"
|
|
||||||
const DATA_DIR = "data"
|
|
||||||
|
|
||||||
func Init() error {
|
func Init() error {
|
||||||
// get working directory
|
// get working directory
|
||||||
// default to .
|
// default to .
|
||||||
|
@ -34,15 +28,15 @@ func New() error {
|
||||||
|
|
||||||
// Read the files in src/ render them and copy the result to target/
|
// Read the files in src/ render them and copy the result to target/
|
||||||
func Build(root string) error {
|
func Build(root string) error {
|
||||||
src := filepath.Join(root, SRC_DIR)
|
config, err := config.Load(root)
|
||||||
target := filepath.Join(root, TARGET_DIR)
|
|
||||||
layouts := filepath.Join(root, LAYOUTS_DIR)
|
|
||||||
data := filepath.Join(root, DATA_DIR)
|
|
||||||
|
|
||||||
site, err := site.Load(src, layouts, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return site.Build(src, target, true, false)
|
site, err := site.Load(*config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.Build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,24 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/facundoolano/blorg/config"
|
||||||
"github.com/facundoolano/blorg/site"
|
"github.com/facundoolano/blorg/site"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate and serve the site, rebuilding when the source files change.
|
// Generate and serve the site, rebuilding when the source files change.
|
||||||
func Serve() error {
|
func Serve(rootDir string) error {
|
||||||
|
config, err := config.LoadDevServer(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := rebuild(); err != nil {
|
if err := rebuild(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch for changes in src and layouts, and trigger a rebuild
|
// watch for changes in src and layouts, and trigger a rebuild
|
||||||
watcher, err := setupWatcher()
|
watcher, err := setupWatcher(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -35,13 +40,14 @@ func Serve() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rebuild() error {
|
func rebuild(config *config.Config) error {
|
||||||
site, err := site.Load(SRC_DIR, LAYOUTS_DIR, DATA_DIR)
|
|
||||||
|
site, err := site.Load(*config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := site.Build(SRC_DIR, TARGET_DIR, false, true); err != nil {
|
if err := site.Build(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +72,7 @@ func (d HTMLDir) Open(name string) (http.File, error) {
|
||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupWatcher() (*fsnotify.Watcher, error) {
|
func setupWatcher(config *config.Config) (*fsnotify.Watcher, error) {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -96,12 +102,12 @@ func setupWatcher() (*fsnotify.Watcher, error) {
|
||||||
|
|
||||||
// since new nested directories could be triggering this change, and we need to watch those too
|
// since new nested directories could be triggering this change, and we need to watch those too
|
||||||
// and since re-watching files is a noop, I just re-add the entire src everytime there's a change
|
// and since re-watching files is a noop, I just re-add the entire src everytime there's a change
|
||||||
if err := addAll(watcher); err != nil {
|
if err := addAll(watcher, config); err != nil {
|
||||||
fmt.Println("error:", err)
|
fmt.Println("error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rebuild(); err != nil {
|
if err := rebuild(config); err != nil {
|
||||||
fmt.Println("error:", err)
|
fmt.Println("error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -115,19 +121,19 @@ func setupWatcher() (*fsnotify.Watcher, error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = addAll(watcher)
|
err = addAll(watcher, config)
|
||||||
|
|
||||||
return watcher, err
|
return watcher, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the layouts and all source directories to the given watcher
|
// Add the layouts and all source directories to the given watcher
|
||||||
func addAll(watcher *fsnotify.Watcher) error {
|
func addAll(watcher *fsnotify.Watcher, config *config.Config) error {
|
||||||
err := watcher.Add(LAYOUTS_DIR)
|
err := watcher.Add(config.LayoutsDir)
|
||||||
err = watcher.Add(DATA_DIR)
|
err = watcher.Add(config.DataDir)
|
||||||
err = watcher.Add(INCLUDES_DIR)
|
err = watcher.Add(config.IncludesDir)
|
||||||
// fsnotify watches all files within a dir, but non recursively
|
// fsnotify watches all files within a dir, but non recursively
|
||||||
// this walks through the src dir and adds watches for each found directory
|
// this walks through the src dir and adds watches for each found directory
|
||||||
filepath.WalkDir(SRC_DIR, func(path string, entry fs.DirEntry, err error) error {
|
filepath.WalkDir(config.SrcDir, func(path string, entry fs.DirEntry, err error) error {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
watcher.Add(path)
|
watcher.Add(path)
|
||||||
}
|
}
|
||||||
|
|
116
config/config.go
Normal file
116
config/config.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The properties that are depended upon in the source code are declared explicitly in the config struct.
|
||||||
|
// The constructors will set default values for most.
|
||||||
|
// Depending on the command, different defaults will be used (serve is assumed to be a "dev" environment
|
||||||
|
// while build is assumed to be prod)
|
||||||
|
// Some defaults could be overridden by cli flags (eg disable live reload on serve).
|
||||||
|
// The user can override some of those via config yaml.
|
||||||
|
// The non declared values found in config yaml will just be passed as site.config values
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
RootDir string
|
||||||
|
SrcDir string
|
||||||
|
TargetDir string
|
||||||
|
LayoutsDir string
|
||||||
|
IncludesDir string
|
||||||
|
DataDir string
|
||||||
|
|
||||||
|
SiteUrl string
|
||||||
|
SlugFormat string
|
||||||
|
|
||||||
|
Minify bool
|
||||||
|
LiveReload bool
|
||||||
|
LinkStatic bool
|
||||||
|
|
||||||
|
ServerHost string
|
||||||
|
ServerPort int
|
||||||
|
|
||||||
|
pageDefaults map[string]interface{}
|
||||||
|
|
||||||
|
// the user provided overrides, as found in config.yml
|
||||||
|
// these will passed as found as template context
|
||||||
|
overrides map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(rootDir string) (*Config, error) {
|
||||||
|
// TODO allow to disable minify
|
||||||
|
|
||||||
|
config := &Config{
|
||||||
|
RootDir: rootDir,
|
||||||
|
SrcDir: filepath.Join(rootDir, "src"),
|
||||||
|
TargetDir: filepath.Join(rootDir, "target"),
|
||||||
|
LayoutsDir: filepath.Join(rootDir, "layouts"),
|
||||||
|
IncludesDir: filepath.Join(rootDir, "includes"),
|
||||||
|
DataDir: filepath.Join(rootDir, "data"),
|
||||||
|
SlugFormat: ":title",
|
||||||
|
Minify: true,
|
||||||
|
LiveReload: false,
|
||||||
|
LinkStatic: false,
|
||||||
|
pageDefaults: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// load overrides from config.yml
|
||||||
|
configPath := filepath.Join(rootDir, "config.yml")
|
||||||
|
yamlContent, err := os.ReadFile(configPath)
|
||||||
|
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
// config file is not mandatory
|
||||||
|
return config, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(yamlContent, &config.overrides)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set user-provided overrides of declared config keys
|
||||||
|
if url, found := config.overrides["url"]; found {
|
||||||
|
config.SiteUrl = url.(string)
|
||||||
|
}
|
||||||
|
if slug, found := config.overrides["url"]; found {
|
||||||
|
config.SlugFormat = slug.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadDevServer(rootDir string) (*Config, error) {
|
||||||
|
// TODO revisit is this Load vs LoadDevServer is the best way to handle both modes
|
||||||
|
// TODO some of the options need to be overridable: host, port, live reload at least
|
||||||
|
|
||||||
|
config, err := Load(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup serve command specific overrides (these could be eventually tweaked with flags)
|
||||||
|
config.ServerHost = "localhost"
|
||||||
|
config.ServerPort = 4001
|
||||||
|
config.Minify = false
|
||||||
|
config.LiveReload = true
|
||||||
|
config.LinkStatic = true
|
||||||
|
config.SiteUrl = fmt.Sprintf("http://%s:%d", config.ServerHost, config.ServerPort)
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) AsContext() map[string]interface{} {
|
||||||
|
context := map[string]interface{}{
|
||||||
|
"url": config.SiteUrl,
|
||||||
|
}
|
||||||
|
maps.Copy(context, config.overrides)
|
||||||
|
return context
|
||||||
|
}
|
9
go.mod
9
go.mod
|
@ -3,18 +3,17 @@ module github.com/facundoolano/blorg
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/elliotchance/orderedmap/v2 v2.2.0
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/niklasfasching/go-org v1.7.0
|
github.com/niklasfasching/go-org v1.7.0
|
||||||
github.com/osteele/liquid v1.3.2
|
github.com/osteele/liquid v1.3.2
|
||||||
github.com/yuin/goldmark v1.7.0
|
github.com/yuin/goldmark v1.7.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
|
|
||||||
github.com/osteele/tuesday v1.0.3 // indirect
|
github.com/osteele/tuesday v1.0.3 // indirect
|
||||||
github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
|
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.4.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
9
go.sum
9
go.sum
|
@ -12,11 +12,8 @@ github.com/osteele/tuesday v1.0.3 h1:SrCmo6sWwSgnvs1bivmXLvD7Ko9+aJvvkmDjB5G4FTU
|
||||||
github.com/osteele/tuesday v1.0.3/go.mod h1:pREKpE+L03UFuR+hiznj3q7j3qB1rUZ4XfKejwWFF2M=
|
github.com/osteele/tuesday v1.0.3/go.mod h1:pREKpE+L03UFuR+hiznj3q7j3qB1rUZ4XfKejwWFF2M=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4 h1:qk1XyC6UGfPa51PGmsTQJavyhfMLScqw97pEV3sFClI=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4/go.mod h1:X6iKjXCleSyo/LZzKZ9zDF/ZB2L9gC36I5gLMf32w3M=
|
|
||||||
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
||||||
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||||
|
@ -31,5 +28,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
6
main.go
6
main.go
|
@ -43,7 +43,11 @@ func run(args []string) error {
|
||||||
newCmd.Parse(os.Args[2:])
|
newCmd.Parse(os.Args[2:])
|
||||||
return commands.New()
|
return commands.New()
|
||||||
case "serve":
|
case "serve":
|
||||||
return commands.Serve()
|
rootDir := "."
|
||||||
|
if len(os.Args) > 2 {
|
||||||
|
rootDir = os.Args[2]
|
||||||
|
}
|
||||||
|
return commands.Serve(rootDir)
|
||||||
default:
|
default:
|
||||||
// TODO print usage
|
// TODO print usage
|
||||||
return errors.New("unknown subcommand")
|
return errors.New("unknown subcommand")
|
||||||
|
|
87
site/site.go
87
site/site.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/facundoolano/blorg/config"
|
||||||
"github.com/facundoolano/blorg/templates"
|
"github.com/facundoolano/blorg/templates"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
@ -19,7 +20,7 @@ import (
|
||||||
const FILE_RW_MODE = 0777
|
const FILE_RW_MODE = 0777
|
||||||
|
|
||||||
type Site struct {
|
type Site struct {
|
||||||
config map[string]string // may need to make this interface{} if config gets sophisticated
|
Config config.Config
|
||||||
layouts map[string]templates.Template
|
layouts map[string]templates.Template
|
||||||
posts []map[string]interface{}
|
posts []map[string]interface{}
|
||||||
pages []map[string]interface{}
|
pages []map[string]interface{}
|
||||||
|
@ -30,34 +31,33 @@ type Site struct {
|
||||||
templates map[string]*templates.Template
|
templates map[string]*templates.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(srcDir string, layoutsDir string, dataDir string) (*Site, error) {
|
func Load(config config.Config) (*Site, error) {
|
||||||
// TODO load config from config.yml
|
|
||||||
site := Site{
|
site := Site{
|
||||||
layouts: make(map[string]templates.Template),
|
layouts: make(map[string]templates.Template),
|
||||||
templates: make(map[string]*templates.Template),
|
templates: make(map[string]*templates.Template),
|
||||||
config: make(map[string]string),
|
Config: config,
|
||||||
tags: make(map[string][]map[string]interface{}),
|
tags: make(map[string][]map[string]interface{}),
|
||||||
data: make(map[string]interface{}),
|
data: make(map[string]interface{}),
|
||||||
templateEngine: templates.NewEngine(),
|
templateEngine: templates.NewEngine(config.SiteUrl),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := site.loadDataFiles(dataDir); err != nil {
|
if err := site.loadDataFiles(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := site.loadLayouts(layoutsDir); err != nil {
|
if err := site.loadLayouts(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := site.loadTemplates(srcDir); err != nil {
|
if err := site.loadTemplates(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &site, nil
|
return &site, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (site *Site) loadLayouts(layoutsDir string) error {
|
func (site *Site) loadLayouts() error {
|
||||||
files, err := os.ReadDir(layoutsDir)
|
files, err := os.ReadDir(site.Config.LayoutsDir)
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
|
@ -68,7 +68,7 @@ func (site *Site) loadLayouts(layoutsDir string) error {
|
||||||
for _, entry := range files {
|
for _, entry := range files {
|
||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
filename := entry.Name()
|
filename := entry.Name()
|
||||||
path := filepath.Join(layoutsDir, filename)
|
path := filepath.Join(site.Config.LayoutsDir, filename)
|
||||||
templ, err := templates.Parse(site.templateEngine, path)
|
templ, err := templates.Parse(site.templateEngine, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -82,8 +82,8 @@ func (site *Site) loadLayouts(layoutsDir string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (site *Site) loadDataFiles(dataDir string) error {
|
func (site *Site) loadDataFiles() error {
|
||||||
files, err := os.ReadDir(dataDir)
|
files, err := os.ReadDir(site.Config.DataDir)
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
|
@ -94,7 +94,7 @@ func (site *Site) loadDataFiles(dataDir string) error {
|
||||||
for _, entry := range files {
|
for _, entry := range files {
|
||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
filename := entry.Name()
|
filename := entry.Name()
|
||||||
path := filepath.Join(dataDir, filename)
|
path := filepath.Join(site.Config.DataDir, filename)
|
||||||
|
|
||||||
yamlContent, err := os.ReadFile(path)
|
yamlContent, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -114,15 +114,15 @@ func (site *Site) loadDataFiles(dataDir string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (site *Site) loadTemplates(srcDir string) error {
|
func (site *Site) loadTemplates() error {
|
||||||
_, err := os.ReadDir(srcDir)
|
_, err := os.ReadDir(site.Config.SrcDir)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return fmt.Errorf("missing %s directory", srcDir)
|
return fmt.Errorf("missing %s directory", site.Config.SrcDir)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("couldn't read %s", srcDir)
|
return fmt.Errorf("couldn't read %s", site.Config.SrcDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = filepath.WalkDir(srcDir, func(path string, entry fs.DirEntry, err error) error {
|
err = filepath.WalkDir(site.Config.SrcDir, func(path string, entry fs.DirEntry, err error) error {
|
||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
templ, err := templates.Parse(site.templateEngine, path)
|
templ, err := templates.Parse(site.templateEngine, path)
|
||||||
// if something fails or this is not a template, skip
|
// if something fails or this is not a template, skip
|
||||||
|
@ -131,10 +131,10 @@ func (site *Site) loadTemplates(srcDir string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set site related (?) metadata. Not sure if this should go elsewhere
|
// set site related (?) metadata. Not sure if this should go elsewhere
|
||||||
relPath, _ := filepath.Rel(srcDir, path)
|
relPath, _ := filepath.Rel(site.Config.SrcDir, path)
|
||||||
relPath = strings.TrimSuffix(relPath, filepath.Ext(relPath)) + templ.Ext()
|
relPath = strings.TrimSuffix(relPath, filepath.Ext(relPath)) + templ.Ext()
|
||||||
templ.Metadata["path"] = relPath
|
templ.Metadata["path"] = relPath
|
||||||
templ.Metadata["url"] = "/" + strings.TrimSuffix(relPath, ".html")
|
templ.Metadata["url"] = "/" + strings.TrimSuffix(strings.TrimSuffix(relPath, "index.html"), ".html")
|
||||||
templ.Metadata["dir"] = "/" + filepath.Dir(relPath)
|
templ.Metadata["dir"] = "/" + filepath.Dir(relPath)
|
||||||
|
|
||||||
// posts are templates that can be chronologically sorted --that have a date.
|
// posts are templates that can be chronologically sorted --that have a date.
|
||||||
|
@ -182,19 +182,18 @@ func (site *Site) loadTemplates(srcDir string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO consider making minify and reload site.config values
|
func (site *Site) Build() error {
|
||||||
func (site *Site) Build(srcDir string, targetDir string, minify bool, htmlReload bool) error {
|
|
||||||
// clear previous target contents
|
// clear previous target contents
|
||||||
os.RemoveAll(targetDir)
|
os.RemoveAll(site.Config.TargetDir)
|
||||||
os.Mkdir(srcDir, FILE_RW_MODE)
|
os.Mkdir(site.Config.SrcDir, FILE_RW_MODE)
|
||||||
|
|
||||||
// walk the source directory, creating directories and files at the target dir
|
// walk the source directory, creating directories and files at the target dir
|
||||||
return filepath.WalkDir(srcDir, func(path string, entry fs.DirEntry, err error) error {
|
return filepath.WalkDir(site.Config.SrcDir, func(path string, entry fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
subpath, _ := filepath.Rel(srcDir, path)
|
subpath, _ := filepath.Rel(site.Config.SrcDir, path)
|
||||||
targetPath := filepath.Join(targetDir, subpath)
|
targetPath := filepath.Join(site.Config.TargetDir, subpath)
|
||||||
|
|
||||||
// if it's a directory, just create the same at the target
|
// if it's a directory, just create the same at the target
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
|
@ -202,7 +201,22 @@ func (site *Site) Build(srcDir string, targetDir string, minify bool, htmlReload
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentReader io.Reader
|
var contentReader io.Reader
|
||||||
if templ, found := site.templates[path]; found {
|
templ, found := site.templates[path]
|
||||||
|
if !found {
|
||||||
|
// if no template found at location, treat the file as static write its contents to target
|
||||||
|
if site.Config.LinkStatic {
|
||||||
|
// dev optimization: link static files instead of copying them
|
||||||
|
abs, _ := filepath.Abs(path)
|
||||||
|
return os.Symlink(abs, targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFile, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
contentReader = srcFile
|
||||||
|
} else {
|
||||||
content, err := site.render(templ)
|
content, err := site.render(templ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -210,25 +224,16 @@ func (site *Site) Build(srcDir string, targetDir string, minify bool, htmlReload
|
||||||
|
|
||||||
targetPath = strings.TrimSuffix(targetPath, filepath.Ext(targetPath)) + templ.Ext()
|
targetPath = strings.TrimSuffix(targetPath, filepath.Ext(targetPath)) + templ.Ext()
|
||||||
contentReader = bytes.NewReader(content)
|
contentReader = bytes.NewReader(content)
|
||||||
} else {
|
|
||||||
// if no template found at location, treat the file as static
|
|
||||||
// write its contents to target
|
|
||||||
srcFile, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer srcFile.Close()
|
|
||||||
contentReader = srcFile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
targetExt := filepath.Ext(targetPath)
|
targetExt := filepath.Ext(targetPath)
|
||||||
// if live reload is enabled, inject the reload snippet to html files
|
// if live reload is enabled, inject the reload snippet to html files
|
||||||
if htmlReload && targetExt == ".html" {
|
if site.Config.LiveReload && targetExt == ".html" {
|
||||||
// TODO inject live reload snippet
|
// TODO inject live reload snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
// if enabled, minify web files
|
// if enabled, minify web files
|
||||||
if minify && (targetExt == ".html" || targetExt == ".css" || targetExt == ".js") {
|
if site.Config.Minify && (targetExt == ".html" || targetExt == ".css" || targetExt == ".js") {
|
||||||
// TODO minify output
|
// TODO minify output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +246,7 @@ func (site *Site) Build(srcDir string, targetDir string, minify bool, htmlReload
|
||||||
func (site Site) render(templ *templates.Template) ([]byte, error) {
|
func (site Site) render(templ *templates.Template) ([]byte, error) {
|
||||||
ctx := map[string]interface{}{
|
ctx := map[string]interface{}{
|
||||||
"site": map[string]interface{}{
|
"site": map[string]interface{}{
|
||||||
"config": site.config,
|
"config": site.Config.AsContext(),
|
||||||
"posts": site.posts,
|
"posts": site.posts,
|
||||||
"tags": site.tags,
|
"tags": site.tags,
|
||||||
"pages": site.pages,
|
"pages": site.pages,
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package site
|
package site
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/facundoolano/blorg/config"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadAndRenderTemplates(t *testing.T) {
|
func TestLoadAndRenderTemplates(t *testing.T) {
|
||||||
root, layouts, src, data := newProject()
|
config := newProject()
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(config.RootDir)
|
||||||
|
|
||||||
// add two layouts
|
// add two layouts
|
||||||
content := `---
|
content := `---
|
||||||
|
@ -19,7 +20,7 @@ func TestLoadAndRenderTemplates(t *testing.T) {
|
||||||
{{content}}
|
{{content}}
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
file := newFile(layouts, "base.html", content)
|
file := newFile(config.LayoutsDir, "base.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
content = `---
|
content = `---
|
||||||
|
@ -28,7 +29,7 @@ layout: base
|
||||||
<h1>{{page.title}}</h1>
|
<h1>{{page.title}}</h1>
|
||||||
<h2>{{page.subtitle}}</h2>
|
<h2>{{page.subtitle}}</h2>
|
||||||
{{content}}`
|
{{content}}`
|
||||||
file = newFile(layouts, "post.html", content)
|
file = newFile(config.LayoutsDir, "post.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
// add two posts
|
// add two posts
|
||||||
|
@ -39,7 +40,7 @@ subtitle: my first post
|
||||||
date: 2024-01-01
|
date: 2024-01-01
|
||||||
---
|
---
|
||||||
<p>Hello world!</p>`
|
<p>Hello world!</p>`
|
||||||
file = newFile(src, "hello.html", content)
|
file = newFile(config.SrcDir, "hello.html", content)
|
||||||
helloPath := file.Name()
|
helloPath := file.Name()
|
||||||
defer os.Remove(helloPath)
|
defer os.Remove(helloPath)
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ subtitle: my last post
|
||||||
date: 2024-02-01
|
date: 2024-02-01
|
||||||
---
|
---
|
||||||
<p>goodbye world!</p>`
|
<p>goodbye world!</p>`
|
||||||
file = newFile(src, "goodbye.html", content)
|
file = newFile(config.SrcDir, "goodbye.html", content)
|
||||||
goodbyePath := file.Name()
|
goodbyePath := file.Name()
|
||||||
defer os.Remove(goodbyePath)
|
defer os.Remove(goodbyePath)
|
||||||
|
|
||||||
|
@ -60,15 +61,15 @@ layout: base
|
||||||
title: about
|
title: about
|
||||||
---
|
---
|
||||||
<p>about this site</p>`
|
<p>about this site</p>`
|
||||||
file = newFile(src, "about.html", content)
|
file = newFile(config.SrcDir, "about.html", content)
|
||||||
aboutPath := file.Name()
|
aboutPath := file.Name()
|
||||||
defer os.Remove(aboutPath)
|
defer os.Remove(aboutPath)
|
||||||
|
|
||||||
// add a static file (no front matter)
|
// add a static file (no front matter)
|
||||||
content = `go away!`
|
content = `go away!`
|
||||||
file = newFile(src, "robots.txt", content)
|
file = newFile(config.SrcDir, "robots.txt", content)
|
||||||
|
|
||||||
site, err := Load(src, layouts, data)
|
site, err := Load(*config)
|
||||||
|
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
|
|
||||||
|
@ -115,15 +116,15 @@ title: about
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderArchive(t *testing.T) {
|
func TestRenderArchive(t *testing.T) {
|
||||||
root, layouts, src, data := newProject()
|
config := newProject()
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(config.RootDir)
|
||||||
|
|
||||||
content := `---
|
content := `---
|
||||||
title: hello world!
|
title: hello world!
|
||||||
date: 2024-01-01
|
date: 2024-01-01
|
||||||
---
|
---
|
||||||
<p>Hello world!</p>`
|
<p>Hello world!</p>`
|
||||||
file := newFile(src, "hello.html", content)
|
file := newFile(config.SrcDir, "hello.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
content = `---
|
content = `---
|
||||||
|
@ -131,7 +132,7 @@ title: goodbye!
|
||||||
date: 2024-02-01
|
date: 2024-02-01
|
||||||
---
|
---
|
||||||
<p>goodbye world!</p>`
|
<p>goodbye world!</p>`
|
||||||
file = newFile(src, "goodbye.html", content)
|
file = newFile(config.SrcDir, "goodbye.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
content = `---
|
content = `---
|
||||||
|
@ -139,7 +140,7 @@ title: an oldie!
|
||||||
date: 2023-01-01
|
date: 2023-01-01
|
||||||
---
|
---
|
||||||
<p>oldie</p>`
|
<p>oldie</p>`
|
||||||
file = newFile(src, "an-oldie.html", content)
|
file = newFile(config.SrcDir, "an-oldie.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
// add a page (no date)
|
// add a page (no date)
|
||||||
|
@ -149,10 +150,10 @@ date: 2023-01-01
|
||||||
<li>{{ post.date | date: "%Y-%m-%d" }} <a href="{{ post.url }}">{{post.title}}</a></li>{%endfor%}
|
<li>{{ post.date | date: "%Y-%m-%d" }} <a href="{{ post.url }}">{{post.title}}</a></li>{%endfor%}
|
||||||
</ul>`
|
</ul>`
|
||||||
|
|
||||||
file = newFile(src, "about.html", content)
|
file = newFile(config.SrcDir, "about.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
site, err := Load(src, layouts, data)
|
site, err := Load(*config)
|
||||||
output, err := site.render(site.templates[file.Name()])
|
output, err := site.render(site.templates[file.Name()])
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
assertEqual(t, string(output), `<ul>
|
assertEqual(t, string(output), `<ul>
|
||||||
|
@ -163,8 +164,8 @@ date: 2023-01-01
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderTags(t *testing.T) {
|
func TestRenderTags(t *testing.T) {
|
||||||
root, layouts, src, data := newProject()
|
config := newProject()
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(config.RootDir)
|
||||||
|
|
||||||
content := `---
|
content := `---
|
||||||
title: hello world!
|
title: hello world!
|
||||||
|
@ -172,7 +173,7 @@ date: 2024-01-01
|
||||||
tags: [web, software]
|
tags: [web, software]
|
||||||
---
|
---
|
||||||
<p>Hello world!</p>`
|
<p>Hello world!</p>`
|
||||||
file := newFile(src, "hello.html", content)
|
file := newFile(config.SrcDir, "hello.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
content = `---
|
content = `---
|
||||||
|
@ -181,7 +182,7 @@ date: 2024-02-01
|
||||||
tags: [web]
|
tags: [web]
|
||||||
---
|
---
|
||||||
<p>goodbye world!</p>`
|
<p>goodbye world!</p>`
|
||||||
file = newFile(src, "goodbye.html", content)
|
file = newFile(config.SrcDir, "goodbye.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
content = `---
|
content = `---
|
||||||
|
@ -190,7 +191,7 @@ date: 2023-01-01
|
||||||
tags: [software]
|
tags: [software]
|
||||||
---
|
---
|
||||||
<p>oldie</p>`
|
<p>oldie</p>`
|
||||||
file = newFile(src, "an-oldie.html", content)
|
file = newFile(config.SrcDir, "an-oldie.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
// add a page (no date)
|
// add a page (no date)
|
||||||
|
@ -202,10 +203,10 @@ tags: [software]
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
`
|
`
|
||||||
|
|
||||||
file = newFile(src, "about.html", content)
|
file = newFile(config.SrcDir, "about.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
site, err := Load(src, layouts, data)
|
site, err := Load(*config)
|
||||||
output, err := site.render(site.templates[file.Name()])
|
output, err := site.render(site.templates[file.Name()])
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
assertEqual(t, string(output), `<h1>software</h1>
|
assertEqual(t, string(output), `<h1>software</h1>
|
||||||
|
@ -222,28 +223,28 @@ hello world!
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderPagesInDir(t *testing.T) {
|
func TestRenderPagesInDir(t *testing.T) {
|
||||||
root, layouts, src, data := newProject()
|
config := newProject()
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(config.RootDir)
|
||||||
|
|
||||||
content := `---
|
content := `---
|
||||||
title: "1. hello world!"
|
title: "1. hello world!"
|
||||||
---
|
---
|
||||||
<p>Hello world!</p>`
|
<p>Hello world!</p>`
|
||||||
file := newFile(src, "01-hello.html", content)
|
file := newFile(config.SrcDir, "01-hello.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
content = `---
|
content = `---
|
||||||
title: "3. goodbye!"
|
title: "3. goodbye!"
|
||||||
---
|
---
|
||||||
<p>goodbye world!</p>`
|
<p>goodbye world!</p>`
|
||||||
file = newFile(src, "03-goodbye.html", content)
|
file = newFile(config.SrcDir, "03-goodbye.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
content = `---
|
content = `---
|
||||||
title: "2. an oldie!"
|
title: "2. an oldie!"
|
||||||
---
|
---
|
||||||
<p>oldie</p>`
|
<p>oldie</p>`
|
||||||
file = newFile(src, "02-an-oldie.html", content)
|
file = newFile(config.SrcDir, "02-an-oldie.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
// add a page (no date)
|
// add a page (no date)
|
||||||
|
@ -253,10 +254,10 @@ title: "2. an oldie!"
|
||||||
<li><a href="{{ page.url }}">{{page.title}}</a></li>{%endfor%}
|
<li><a href="{{ page.url }}">{{page.title}}</a></li>{%endfor%}
|
||||||
</ul>`
|
</ul>`
|
||||||
|
|
||||||
file = newFile(src, "index.html", content)
|
file = newFile(config.SrcDir, "index.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
site, err := Load(src, layouts, data)
|
site, err := Load(*config)
|
||||||
output, err := site.render(site.templates[file.Name()])
|
output, err := site.render(site.templates[file.Name()])
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
assertEqual(t, string(output), `<ul>
|
assertEqual(t, string(output), `<ul>
|
||||||
|
@ -271,8 +272,8 @@ func TestRenderArchiveWithExcerpts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderDataFile(t *testing.T) {
|
func TestRenderDataFile(t *testing.T) {
|
||||||
root, layouts, src, data := newProject()
|
config := newProject()
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(config.RootDir)
|
||||||
|
|
||||||
content := `
|
content := `
|
||||||
- name: feedi
|
- name: feedi
|
||||||
|
@ -280,7 +281,7 @@ func TestRenderDataFile(t *testing.T) {
|
||||||
- name: blorg
|
- name: blorg
|
||||||
url: https://github.com/facundoolano/blorg
|
url: https://github.com/facundoolano/blorg
|
||||||
`
|
`
|
||||||
file := newFile(data, "projects.yml", content)
|
file := newFile(config.DataDir, "projects.yml", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
// add a page (no date)
|
// add a page (no date)
|
||||||
|
@ -290,10 +291,10 @@ func TestRenderDataFile(t *testing.T) {
|
||||||
<li><a href="{{ project.url }}">{{project.name}}</a></li>{%endfor%}
|
<li><a href="{{ project.url }}">{{project.name}}</a></li>{%endfor%}
|
||||||
</ul>`
|
</ul>`
|
||||||
|
|
||||||
file = newFile(src, "projects.html", content)
|
file = newFile(config.SrcDir, "projects.html", content)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
site, err := Load(src, layouts, data)
|
site, err := Load(*config)
|
||||||
output, err := site.render(site.templates[file.Name()])
|
output, err := site.render(site.templates[file.Name()])
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
assertEqual(t, string(output), `<ul>
|
assertEqual(t, string(output), `<ul>
|
||||||
|
@ -304,7 +305,7 @@ func TestRenderDataFile(t *testing.T) {
|
||||||
|
|
||||||
// ------ HELPERS --------
|
// ------ HELPERS --------
|
||||||
|
|
||||||
func newProject() (string, string, string, string) {
|
func newProject() *config.Config {
|
||||||
projectDir, _ := os.MkdirTemp("", "root")
|
projectDir, _ := os.MkdirTemp("", "root")
|
||||||
layoutsDir := filepath.Join(projectDir, "layouts")
|
layoutsDir := filepath.Join(projectDir, "layouts")
|
||||||
srcDir := filepath.Join(projectDir, "src")
|
srcDir := filepath.Join(projectDir, "src")
|
||||||
|
@ -313,7 +314,9 @@ func newProject() (string, string, string, string) {
|
||||||
os.Mkdir(srcDir, 0777)
|
os.Mkdir(srcDir, 0777)
|
||||||
os.Mkdir(dataDir, 0777)
|
os.Mkdir(dataDir, 0777)
|
||||||
|
|
||||||
return projectDir, layoutsDir, srcDir, dataDir
|
config, _ := config.Load(projectDir)
|
||||||
|
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFile(dir string, filename string, contents string) *os.File {
|
func newFile(dir string, filename string, contents string) *os.File {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package templates
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
@ -18,7 +20,7 @@ import (
|
||||||
// a lot of the filters and tags available at jekyll aren't default liquid manually adding them here
|
// a lot of the filters and tags available at jekyll aren't default liquid manually adding them here
|
||||||
// copied from https://github.com/osteele/gojekyll/blob/f1794a874890bfb601cae767a0cce15d672e9058/filters/filters.go
|
// copied from https://github.com/osteele/gojekyll/blob/f1794a874890bfb601cae767a0cce15d672e9058/filters/filters.go
|
||||||
// MIT License: https://github.com/osteele/gojekyll/blob/f1794a874890bfb601cae767a0cce15d672e9058/LICENSE
|
// MIT License: https://github.com/osteele/gojekyll/blob/f1794a874890bfb601cae767a0cce15d672e9058/LICENSE
|
||||||
func loadJekyllFilters(e *liquid.Engine) {
|
func loadJekyllFilters(e *liquid.Engine, siteUrl string) {
|
||||||
e.RegisterFilter("filter", filter)
|
e.RegisterFilter("filter", filter)
|
||||||
e.RegisterFilter("group_by", groupByFilter)
|
e.RegisterFilter("group_by", groupByFilter)
|
||||||
e.RegisterFilter("group_by_exp", groupByExpFilter)
|
e.RegisterFilter("group_by_exp", groupByExpFilter)
|
||||||
|
@ -32,15 +34,17 @@ func loadJekyllFilters(e *liquid.Engine) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := goldmark.Convert([]byte(s), &buf)
|
err := goldmark.Convert([]byte(s), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
})
|
})
|
||||||
|
|
||||||
e.RegisterFilter("absolute_url", func(s string) string {
|
e.RegisterFilter("absolute_url", func(path string) string {
|
||||||
// FIXME implement after adding a config struct, using the url
|
url, err := url.JoinPath(siteUrl, path)
|
||||||
// return utils.URLJoin(c.AbsoluteURL, c.BaseURL, s)
|
if err != nil {
|
||||||
return s
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return url
|
||||||
})
|
})
|
||||||
|
|
||||||
e.RegisterFilter("date_to_rfc822", func(date time.Time) string {
|
e.RegisterFilter("date_to_rfc822", func(date time.Time) string {
|
||||||
|
|
|
@ -25,9 +25,11 @@ type Template struct {
|
||||||
liquidTemplate liquid.Template
|
liquidTemplate liquid.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEngine() *Engine {
|
// Create a new template engine, with custom liquid filters.
|
||||||
|
// The `siteUrl` is necessary to provide context for the absolute_url filter.
|
||||||
|
func NewEngine(siteUrl string) *Engine {
|
||||||
e := liquid.NewEngine()
|
e := liquid.NewEngine()
|
||||||
loadJekyllFilters(e)
|
loadJekyllFilters(e, siteUrl)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ tags: ["software", "web"]
|
||||||
file := newFile("test*.html", input)
|
file := newFile("test*.html", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
templ, err := Parse(NewEngine(), file.Name())
|
templ, err := Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
|
|
||||||
assertEqual(t, templ.Metadata["title"], "my new post")
|
assertEqual(t, templ.Metadata["title"], "my new post")
|
||||||
|
@ -42,7 +42,7 @@ subtitle: a blog post
|
||||||
file := newFile("test*.html", input)
|
file := newFile("test*.html", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
_, err := Parse(NewEngine(), file.Name())
|
_, err := Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
|
|
||||||
// not first thing in file, leaving as is
|
// not first thing in file, leaving as is
|
||||||
|
@ -57,7 +57,7 @@ tags: ["software", "web"]
|
||||||
file = newFile("test*.html", input)
|
file = newFile("test*.html", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
_, err = Parse(NewEngine(), file.Name())
|
_, err = Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ tags: ["software", "web"]
|
||||||
`
|
`
|
||||||
file := newFile("test*.html", input)
|
file := newFile("test*.html", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
_, err := Parse(NewEngine(), file.Name())
|
_, err := Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
|
|
||||||
assertEqual(t, err.Error(), "front matter not closed")
|
assertEqual(t, err.Error(), "front matter not closed")
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ tags: ["software", "web"]
|
||||||
|
|
||||||
file = newFile("test*.html", input)
|
file = newFile("test*.html", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
_, err = Parse(NewEngine(), file.Name())
|
_, err = Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
assert(t, strings.Contains(err.Error(), "invalid yaml"))
|
assert(t, strings.Contains(err.Error(), "invalid yaml"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ tags: ["software", "web"]
|
||||||
file := newFile("test*.html", input)
|
file := newFile("test*.html", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
templ, err := Parse(NewEngine(), file.Name())
|
templ, err := Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
ctx := map[string]interface{}{"page": templ.Metadata}
|
ctx := map[string]interface{}{"page": templ.Metadata}
|
||||||
content, err := templ.Render(ctx)
|
content, err := templ.Render(ctx)
|
||||||
|
@ -130,7 +130,7 @@ tags: ["software", "web"]
|
||||||
file := newFile("test*.org", input)
|
file := newFile("test*.org", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
templ, err := Parse(NewEngine(), file.Name())
|
templ, err := Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
|
|
||||||
content, err := templ.Render(nil)
|
content, err := templ.Render(nil)
|
||||||
|
@ -172,7 +172,7 @@ tags: ["software", "web"]
|
||||||
file := newFile("test*.md", input)
|
file := newFile("test*.md", input)
|
||||||
defer os.Remove(file.Name())
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
templ, err := Parse(NewEngine(), file.Name())
|
templ, err := Parse(NewEngine("https://olano.dev"), file.Name())
|
||||||
assertEqual(t, err, nil)
|
assertEqual(t, err, nil)
|
||||||
|
|
||||||
content, err := templ.Render(nil)
|
content, err := templ.Render(nil)
|
||||||
|
|
Loading…
Reference in a new issue