diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c9db0f --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# ---> Vim +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +backup +*.exe +*.dll +coverage +coverage/* +.venv +*.gz +public diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..df1aa25 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +GO_IS_STUPID_EXE= + +ifeq '$(OS)' 'Windows_NT' + GO_IS_STUPID_EXE=.exe +endif + +build: + go build . + +test: + go test zedshaw.games/ssgod/tests -c -o runtests$(GO_IS_STUPID_EXE) + ./runtests$(GO_IS_STUPID_EXE) + +docs: + go tool pkgsite --open + +coverage: + go build -cover -o webapp + mkdir -p .coverage + echo "GOCOVERDIR=.coverage ./webapp" + +cover_report: + go tool covdata textfmt -i=.coverage -o coverage.txt + go tool cover -func=coverage.txt + go tool cover -html=coverage.txt -o coverage.html + open coverage.html diff --git a/README.md b/README.md index 52882cd..4dd23ae 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,14 @@ SSG is a Static Site Generator that is only a Static Site Generator. No resumes ## Usage ```bash -# assumes a ssgod.tomly -$ beerus +# init your config +$ ssgod init + +# creates a ssgod.tomly +$ ssgod # explicit ssgod -$ beerus --config ssgod.toml +$ ssgod --config ssgod.toml ``` * Probably just an extension mapping: .md -> .html, .html -> .html diff --git a/config/loader.go b/config/loader.go new file mode 100644 index 0000000..11dd381 --- /dev/null +++ b/config/loader.go @@ -0,0 +1,27 @@ +package config + +import ( + "log" + "github.com/BurntSushi/toml" +) + +type config struct { + Views string `toml:"views"` + Layouts string `toml:"layouts"` +} + +var Settings config + +func Load(path string) { + metadata, err := toml.DecodeFile(path, &Settings) + + if err != nil { + log.Fatalf("error loading config.toml: %v", err) + } + + bad_keys := metadata.Undecoded() + + if len(bad_keys) > 0 { + log.Fatalf("unknown configuration keys: %v", bad_keys); + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..be0f622 --- /dev/null +++ b/go.mod @@ -0,0 +1,29 @@ +module zedshaw.games/ssgod + +go 1.24.2 + +require ( + github.com/BurntSushi/toml v1.5.0 + github.com/gofiber/fiber/v2 v2.52.9 + github.com/gofiber/template/html/v2 v2.1.3 + github.com/stretchr/testify v1.10.0 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gofiber/template v1.8.3 // indirect + github.com/gofiber/utils v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f9ac82c --- /dev/null +++ b/go.sum @@ -0,0 +1,45 @@ +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= +github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= +github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= +github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o= +github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE= +github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= +github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..73a1dad --- /dev/null +++ b/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "log" + "fmt" + "strings" + "io/fs" + "path/filepath" + "os" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/template/html/v2" + "zedshaw.games/ssgod/config" +) + +func Fail(err error, format string, v ...any) error { + err_format := fmt.Sprintf("ERROR: %v; %s", err, format) + log.Printf(err_format, v...) + return err +} + +func RenderPages(pages_path string, target string, layout string) { + engine := html.New(pages_path, ".html") + engine.Load() + + err := filepath.WalkDir(pages_path, + func(path string, d fs.DirEntry, err error) error { + if !d.IsDir() { + if err != nil { return Fail(err, "path: %s", path); } + + dir := filepath.Dir(path) + err = os.MkdirAll(dir, 0750) + if err != nil { + return Fail(err, "making dir %s", dir); + } + + split_path := strings.Split(path, string(os.PathSeparator))[1:] + source_name := strings.Join(split_path, "/") // Render wants / even on windows + ext := filepath.Ext(source_name) + template_name, found := strings.CutSuffix(source_name, ext) + + if found && ext == ".html" && template_name != layout { + prefixed_path := append([]string{target}, split_path...) + + target_path := filepath.Join(prefixed_path...) + _, err := os.Stat(target_path) + + if os.IsNotExist(err) { + target_dir := filepath.Dir(target_path) + log.Println("MAKING: ", target_dir) + os.MkdirAll(target_dir, 0750) + } + + // TODO: compare time stamps and skip if not newer + + out, err := os.OpenFile(target_path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { return Fail(err, "writing file %s", target_path) } + + // generate a data-testid for all pages based on template name + page_id := strings.ReplaceAll(template_name, "/", "-") + "-page" + err = engine.Render(out, template_name, fiber.Map{"PageId": page_id}, layout) + if err != nil { return Fail(err, "failed to render %s", path) } + + log.Printf("RENDER: %s -> %s", template_name, target_path) + out.Close() + } + } + + return nil + }) + + if err != nil { log.Fatalf("can't walk content") } +} + +func main() { + config.Load("ssgod.toml") + log.Printf("views=%s, layouts=%s", config.Settings.Views, config.Settings.Layouts) +} diff --git a/ssgod.toml b/ssgod.toml new file mode 100644 index 0000000..8384591 --- /dev/null +++ b/ssgod.toml @@ -0,0 +1,2 @@ +views = "./views" +layouts = "layouts/main" diff --git a/tests/base_test.go b/tests/base_test.go new file mode 100644 index 0000000..8199ec6 --- /dev/null +++ b/tests/base_test.go @@ -0,0 +1,12 @@ +package tests + +import ( + "testing" + "github.com/stretchr/testify/require" +) + + +func TestBasic(t *testing.T) { + assert := require.New(t) + assert.Equal(100, 100) +}