This commit is contained in:
Alex Yatskov 2024-04-04 20:50:14 -07:00
parent 9f944be1d2
commit 9db9b8025c

176
main.go
View File

@ -2,17 +2,18 @@ package main
import ( import (
_ "embed" _ "embed"
"errors"
"flag" "flag"
"fmt" "fmt"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall" "syscall"
"git.foosoft.net/alex/goldsmith" "git.foosoft.net/alex/goldsmith"
"git.foosoft.net/alex/goldsmith-components/devserver"
"git.foosoft.net/alex/goldsmith-components/filters/operator" "git.foosoft.net/alex/goldsmith-components/filters/operator"
"git.foosoft.net/alex/goldsmith-components/filters/wildcard" "git.foosoft.net/alex/goldsmith-components/filters/wildcard"
"git.foosoft.net/alex/goldsmith-components/plugins/document" "git.foosoft.net/alex/goldsmith-components/plugins/document"
@ -20,7 +21,7 @@ import (
"git.foosoft.net/alex/goldsmith-components/plugins/livejs" "git.foosoft.net/alex/goldsmith-components/plugins/livejs"
"git.foosoft.net/alex/goldsmith-components/plugins/markdown" "git.foosoft.net/alex/goldsmith-components/plugins/markdown"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/toqueteos/webbrowser" "github.com/fsnotify/fsnotify"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
@ -33,13 +34,73 @@ var githubStyle string
//go:embed css/github-fixup.css //go:embed css/github-fixup.css
var githubFixup string var githubFixup string
type builder struct { func watch(dir string, watcher *fsnotify.Watcher) error {
port int watcher.Add(dir)
path string
open bool items, err := os.ReadDir(dir)
if err != nil {
return err
} }
func embedCss(file *goldsmith.File, doc *goquery.Document) error { for _, item := range items {
fullPath := path.Join(dir, item.Name())
if item.IsDir() {
watch(fullPath, watcher)
} else {
watcher.Add(fullPath)
}
}
return nil
}
func builder(dir string, callback func()) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
if err := watch(dir, watcher); err != nil {
return err
}
var terminate bool
signaler := make(chan os.Signal, 1)
signal.Notify(
signaler,
os.Interrupt,
syscall.SIGINT,
syscall.SIGTERM,
)
callback()
for !terminate {
select {
case event := <-watcher.Events:
callback()
if event.Op&fsnotify.Create == fsnotify.Create {
if info, _ := os.Stat(event.Name); info != nil {
if info.IsDir() {
watch(event.Name, watcher)
} else {
watcher.Add(event.Name)
}
}
}
case <-signaler:
log.Println("terminating...")
terminate = true
}
}
return nil
}
func build(sourceDir, targetDir string) {
log.Println("building...")
embedCss := func(file *goldsmith.File, doc *goquery.Document) error {
var styleBuilder strings.Builder var styleBuilder strings.Builder
styleBuilder.WriteString("<style type=\"text/css\">\n") styleBuilder.WriteString("<style type=\"text/css\">\n")
styleBuilder.WriteString(githubStyle) styleBuilder.WriteString(githubStyle)
@ -52,15 +113,6 @@ func embedCss(file *goldsmith.File, doc *goquery.Document) error {
return nil return nil
} }
func (self *builder) Build(contentDir, buildDir, cacheDir string) {
log.Print("building...")
gm := goldmark.New(
goldmark.WithExtensions(extension.GFM, extension.Typographer),
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
goldmark.WithRendererOptions(html.WithUnsafe()),
)
allowedPaths := []string{ allowedPaths := []string{
"**/*.gif", "**/*.gif",
"**/*.html", "**/*.html",
@ -75,8 +127,14 @@ func (self *builder) Build(contentDir, buildDir, cacheDir string) {
"**/.*/**", "**/.*/**",
} }
gm := goldmark.New(
goldmark.WithExtensions(extension.GFM, extension.Typographer),
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
goldmark.WithRendererOptions(html.WithUnsafe()),
)
var gs goldsmith.Goldsmith var gs goldsmith.Goldsmith
errs := gs.Begin(contentDir). errs := gs.Begin(sourceDir).
Clean(true). Clean(true).
FilterPush(wildcard.New(allowedPaths...)). FilterPush(wildcard.New(allowedPaths...)).
FilterPush(operator.Not(wildcard.New(forbiddenPaths...))). FilterPush(operator.Not(wildcard.New(forbiddenPaths...))).
@ -84,28 +142,55 @@ func (self *builder) Build(contentDir, buildDir, cacheDir string) {
Chain(markdown.NewWithGoldmark(gm)). Chain(markdown.NewWithGoldmark(gm)).
Chain(livejs.New()). Chain(livejs.New()).
Chain(document.New(embedCss)). Chain(document.New(embedCss)).
End(buildDir) End(targetDir)
for _, err := range errs { for _, err := range errs {
log.Print(err) log.Println(err)
}
} }
if !self.open { func run(path string) error {
url := fmt.Sprintf("http://127.0.0.1:%d/%s", self.port, self.path) switch strings.ToLower(filepath.Ext(path)) {
log.Printf("opening %s in browser...", url) case ".md", ".markdown":
webbrowser.Open(url) break
self.open = true default:
return errors.New("unexpected file type")
} }
if info, err := os.Stat(path); err != nil {
return err
} else if info.IsDir() {
return errors.New("unexpected directory")
}
targetDir, err := os.MkdirTemp("", "mdv-*")
if err != nil {
return err
}
defer os.RemoveAll(targetDir)
sourceDir := filepath.Dir(path)
builder(sourceDir, func() {
build(sourceDir, targetDir)
})
// if !self.open {
// url := fmt.Sprintf("http://127.0.0.1:%d/%s", self.port, self.path)
// log.Printf("opening %s in browser...", url)
// webbrowser.Open(url)
// self.open = true
// }
return nil
} }
func main() { func main() {
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage %s [options] [path]\n\n", filepath.Base(os.Args[0])) fmt.Fprintf(os.Stderr, "Usage %s [options] <path>\n\n", filepath.Base(os.Args[0]))
fmt.Fprintln(os.Stderr, "Parameters:") fmt.Fprintln(os.Stderr, "Parameters:")
flag.PrintDefaults() flag.PrintDefaults()
} }
port := flag.Int("port", 8080, "port")
flag.Parse() flag.Parse()
if flag.NArg() != 1 { if flag.NArg() != 1 {
@ -113,45 +198,8 @@ func main() {
os.Exit(2) os.Exit(2)
} }
path := flag.Arg(0) if err := run(flag.Arg(0)); err != nil {
info, err := os.Stat(path)
if err != nil {
log.Fatal(err) log.Fatal(err)
} }
var contentName string
contentDir := path
if !info.IsDir() {
contentName = filepath.Base(path)
contentExt := filepath.Ext(contentName)
switch contentExt {
case ".md", ".markdown":
contentName = strings.TrimSuffix(contentName, contentExt)
contentName += ".html"
}
contentDir = filepath.Dir(path)
}
buildDir, err := os.MkdirTemp("", "mvd-*")
if err != nil {
log.Fatal(err)
}
defer func() {
log.Println("cleaning up...")
if err := os.RemoveAll(buildDir); err != nil {
log.Fatal(err)
}
}()
go func() {
b := &builder{port: *port, path: contentName}
devserver.DevServe(b, *port, contentDir, buildDir, "")
}()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
} }