diff --git a/main.go b/main.go
index 2183de6..2835683 100644
--- a/main.go
+++ b/main.go
@@ -2,17 +2,18 @@ package main
import (
_ "embed"
+ "errors"
"flag"
"fmt"
"log"
"os"
"os/signal"
+ "path"
"path/filepath"
"strings"
"syscall"
"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/wildcard"
"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/markdown"
"github.com/PuerkitoBio/goquery"
- "github.com/toqueteos/webbrowser"
+ "github.com/fsnotify/fsnotify"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
@@ -33,34 +34,85 @@ var githubStyle string
//go:embed css/github-fixup.css
var githubFixup string
-type builder struct {
- port int
- path string
- open bool
-}
+func watch(dir string, watcher *fsnotify.Watcher) error {
+ watcher.Add(dir)
-func embedCss(file *goldsmith.File, doc *goquery.Document) error {
- var styleBuilder strings.Builder
- styleBuilder.WriteString("")
+ items, err := os.ReadDir(dir)
+ if err != nil {
+ return err
+ }
- doc.Find("body").AddClass("markdown-body")
- doc.Find("head").SetHtml(styleBuilder.String())
+ for _, item := range items {
+ fullPath := path.Join(dir, item.Name())
+ if item.IsDir() {
+ watch(fullPath, watcher)
+ } else {
+ watcher.Add(fullPath)
+ }
+ }
return nil
}
-func (self *builder) Build(contentDir, buildDir, cacheDir string) {
- log.Print("building...")
+func builder(dir string, callback func()) error {
+ watcher, err := fsnotify.NewWatcher()
+ if err != nil {
+ return err
+ }
- gm := goldmark.New(
- goldmark.WithExtensions(extension.GFM, extension.Typographer),
- goldmark.WithParserOptions(parser.WithAutoHeadingID()),
- goldmark.WithRendererOptions(html.WithUnsafe()),
+ 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
+ styleBuilder.WriteString("")
+
+ doc.Find("body").AddClass("markdown-body")
+ doc.Find("head").SetHtml(styleBuilder.String())
+
+ return nil
+ }
+
allowedPaths := []string{
"**/*.gif",
"**/*.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
- errs := gs.Begin(contentDir).
+ errs := gs.Begin(sourceDir).
Clean(true).
FilterPush(wildcard.New(allowedPaths...)).
FilterPush(operator.Not(wildcard.New(forbiddenPaths...))).
@@ -84,28 +142,55 @@ func (self *builder) Build(contentDir, buildDir, cacheDir string) {
Chain(markdown.NewWithGoldmark(gm)).
Chain(livejs.New()).
Chain(document.New(embedCss)).
- End(buildDir)
+ End(targetDir)
for _, err := range errs {
- log.Print(err)
+ log.Println(err)
+ }
+}
+
+func run(path string) error {
+ switch strings.ToLower(filepath.Ext(path)) {
+ case ".md", ".markdown":
+ break
+ default:
+ return errors.New("unexpected file type")
}
- 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
+ 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() {
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] \n\n", filepath.Base(os.Args[0]))
fmt.Fprintln(os.Stderr, "Parameters:")
flag.PrintDefaults()
}
- port := flag.Int("port", 8080, "port")
flag.Parse()
if flag.NArg() != 1 {
@@ -113,45 +198,8 @@ func main() {
os.Exit(2)
}
- path := flag.Arg(0)
- info, err := os.Stat(path)
- if err != nil {
+ if err := run(flag.Arg(0)); err != nil {
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
}