WIP
This commit is contained in:
parent
9f944be1d2
commit
9db9b8025c
186
main.go
186
main.go
@ -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,34 +34,85 @@ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func embedCss(file *goldsmith.File, doc *goquery.Document) error {
|
items, err := os.ReadDir(dir)
|
||||||
var styleBuilder strings.Builder
|
if err != nil {
|
||||||
styleBuilder.WriteString("<style type=\"text/css\">\n")
|
return err
|
||||||
styleBuilder.WriteString(githubStyle)
|
}
|
||||||
styleBuilder.WriteString(githubFixup)
|
|
||||||
styleBuilder.WriteString("</style>")
|
|
||||||
|
|
||||||
doc.Find("body").AddClass("markdown-body")
|
for _, item := range items {
|
||||||
doc.Find("head").SetHtml(styleBuilder.String())
|
fullPath := path.Join(dir, item.Name())
|
||||||
|
if item.IsDir() {
|
||||||
|
watch(fullPath, watcher)
|
||||||
|
} else {
|
||||||
|
watcher.Add(fullPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *builder) Build(contentDir, buildDir, cacheDir string) {
|
func builder(dir string, callback func()) error {
|
||||||
log.Print("building...")
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
gm := goldmark.New(
|
if err := watch(dir, watcher); err != nil {
|
||||||
goldmark.WithExtensions(extension.GFM, extension.Typographer),
|
return err
|
||||||
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
|
}
|
||||||
goldmark.WithRendererOptions(html.WithUnsafe()),
|
|
||||||
|
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("<style type=\"text/css\">\n")
|
||||||
|
styleBuilder.WriteString(githubStyle)
|
||||||
|
styleBuilder.WriteString(githubFixup)
|
||||||
|
styleBuilder.WriteString("</style>")
|
||||||
|
|
||||||
|
doc.Find("body").AddClass("markdown-body")
|
||||||
|
doc.Find("head").SetHtml(styleBuilder.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if info, err := os.Stat(path); err != nil {
|
||||||
url := fmt.Sprintf("http://127.0.0.1:%d/%s", self.port, self.path)
|
return err
|
||||||
log.Printf("opening %s in browser...", url)
|
} else if info.IsDir() {
|
||||||
webbrowser.Open(url)
|
return errors.New("unexpected directory")
|
||||||
self.open = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user