package main
import (
"log"
"fmt"
"bytes"
"strings"
"io/fs"
"io"
"path/filepath"
"os"
"flag"
"text/template"
"zedshaw.games/ssgod/config"
"github.com/yuin/goldmark"
"github.com/fsnotify/fsnotify"
)
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 WatchDir ( ) {
watcher , err := fsnotify . NewWatcher ( )
if err != nil { log . Fatal ( err ) }
defer watcher . Close ( )
go func ( ) {
for {
select {
case event , ok := <- watcher . Events :
if ! ok { return }
log . Println ( "event: " , event )
if event . Has ( fsnotify . Write ) {
log . Println ( "modified file: " , event . Name )
RenderPages ( )
}
case err , ok := <- watcher . Errors :
if ! ok { return }
log . Println ( "error: " , err )
}
}
} ( )
err = watcher . Add ( config . Settings . Views )
if err != nil { log . Fatal ( err ) }
<- make ( chan struct { } )
}
func main ( ) {
config . Load ( "ssgod.toml" )
watch := flag . Bool ( "watch" , false , "Watch the views directory for changes" )
flag . Parse ( )
if * watch {
WatchDir ( )
} else {
RenderPages ( ) ;
}
}