package main import ( "log" "fmt" "bytes" "strings" "io/fs" "io" "path/filepath" "os" "text/template" "zedshaw.games/ssgod/config" "github.com/yuin/goldmark" ) 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 RenderTemplate(out io.Writer, embed string, variables any) (error) { layout_path := config.Settings.Layout layout_main, err := os.ReadFile(layout_path) if err != nil { return Fail(err, "can't read your layout file: %s", layout_path) } tmpl := template.New(layout_path) callbacks := template.FuncMap{ "embed": func() string { return embed }, } tmpl.Funcs(callbacks) tmpl, err = tmpl.Parse(string(layout_main)) if err != nil { return Fail(err, "can't parse %s", layout_path) } err = tmpl.Execute(out, variables) return err } func RenderMarkdown(path string, target_path string, page_id string) error { log.Printf("MARKDOWN: %s -> %s", path, target_path) out, err := os.OpenFile(target_path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) defer out.Close() if err != nil { return Fail(err, "writing file %s", target_path) } input_data, err := os.ReadFile(path) var md_out bytes.Buffer err = goldmark.Convert(input_data, &md_out) if err != nil { return Fail(err, "failed converting markdown %s", path) } err = RenderTemplate(out, md_out.String(), map[string]string{"PageId": page_id}) if err != nil { return Fail(err, "failed to render template %s->%s", path, target_path) } return err; } func RenderHTML(source_path string, target_path string, page_id string) error { log.Printf("RENDER: %s -> %s", source_path, target_path) out, err := os.OpenFile(target_path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) defer out.Close() html_content, err := os.ReadFile(source_path) if err != nil { return Fail(err, "cannot open html input %s", source_path) } err = RenderTemplate(out, string(html_content), map[string]string{"PageId": page_id}) if err != nil { return Fail(err, "writing file %s", target_path) } return err; } func MkdirPath(target_path string) error { target_dir := filepath.Dir(target_path) _, err := os.Stat(target_dir) if os.IsNotExist(err) { log.Println("MAKING: ", target_dir) err = os.MkdirAll(target_dir, 0750) if err != nil { return Fail(err, "making path to %s", target_dir); } } return nil; } func SplitPathExt(path string) (string, string, bool) { 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) source_name, found := strings.CutSuffix(source_name, ext) return source_name, ext, found } func RePrefixPath(path string, new_prefix string) string { split_path := strings.Split(path, string(os.PathSeparator))[1:] prefixed_path := append([]string{new_prefix}, split_path...) return filepath.Join(prefixed_path...) } func ProcessDirEntry(path string, d fs.DirEntry, err error) error { settings := config.Settings if !d.IsDir() { if err != nil { return Fail(err, "path: %s", path); } source_name, ext, found := SplitPathExt(path) if found && path != settings.Layout { target_path := RePrefixPath(path, settings.Target) err = MkdirPath(target_path) if err != nil { return Fail(err, "making target path: %s", target_path) } // generate a data-testid for all pages based on template name page_id := strings.ReplaceAll(source_name, "/", "-") + "-page" if ext == ".html" { err = RenderHTML(path, target_path, page_id) if err != nil { return Fail(err, "failed to render %s", path) } } else if ext == ".md" { // need to strip the .md and replace with .html html_name, _ := strings.CutSuffix(target_path, ext) html_name = fmt.Sprintf("%s.html", html_name) RenderMarkdown(path, html_name, page_id) if err != nil { return Fail(err, "failed to render markdown %s", path) } } } } return nil } func RenderPages() { err := filepath.WalkDir(config.Settings.Views, func (path string, d fs.DirEntry, err error) error { return ProcessDirEntry(path, d, err) }) if err != nil { log.Fatalf("can't walk content") } } func main() { config.Load("ssgod.toml") RenderPages() }