mdview/main.go
2024-04-09 10:39:06 -07:00

221 lines
4.3 KiB
Go

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/filters/operator"
"git.foosoft.net/alex/goldsmith/filters/wildcard"
"git.foosoft.net/alex/goldsmith/plugins/document"
"git.foosoft.net/alex/goldsmith/plugins/frontmatter"
"git.foosoft.net/alex/goldsmith/plugins/livejs"
"git.foosoft.net/alex/goldsmith/plugins/markdown"
"github.com/PuerkitoBio/goquery"
"github.com/fsnotify/fsnotify"
"github.com/skratchdot/open-golang/open"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
)
//go:embed css/github-markdown.css
var githubStyle string
//go:embed css/github-fixup.css
var githubFixup string
func watch(dir string, watcher *fsnotify.Watcher) error {
watcher.Add(dir)
items, err := os.ReadDir(dir)
if err != nil {
return err
}
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(bool) error) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
if err := watch(dir, watcher); err != nil {
return err
}
if err := callback(true); err != nil {
return err
}
signaler := make(chan os.Signal, 1)
signal.Notify(
signaler,
os.Interrupt,
syscall.SIGINT,
syscall.SIGTERM,
)
for {
select {
case event := <-watcher.Events:
if err := callback(false); err != nil {
return err
}
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:
return nil
}
}
}
func build(sourceDir, targetDir string) error {
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{
"**/*.gif",
"**/*.html",
"**/*.jpeg",
"**/*.jpg",
"**/*.md",
"**/*.png",
"**/*.svg",
}
forbiddenPaths := []string{
"**/.*/**",
}
gm := goldmark.New(
goldmark.WithExtensions(extension.GFM, extension.Typographer),
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
goldmark.WithRendererOptions(html.WithUnsafe()),
)
gs := goldsmith.Goldsmith{Clean: true}
errs := gs.Begin(sourceDir).
FilterPush(wildcard.New(allowedPaths...)).
FilterPush(operator.Not(wildcard.New(forbiddenPaths...))).
Chain(frontmatter.New()).
Chain(markdown.NewWithGoldmark(gm)).
Chain(livejs.New()).
Chain(document.New(embedCss)).
End(targetDir)
if len(errs) > 0 {
return errs[0]
}
return nil
}
func run(path string) error {
switch strings.ToLower(filepath.Ext(path)) {
case ".md", ".markdown":
break
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(first bool) error {
if err := build(sourceDir, targetDir); err != nil {
return err
}
if first {
var (
mdName = filepath.Base(path)
mdExt = filepath.Ext(mdName)
htmlPath = filepath.Join(targetDir, mdName[:len(mdName)-len(mdExt)]+".html")
)
if _, err := os.Stat(htmlPath); err != nil {
return err
}
if err := open.Start(htmlPath); err != nil {
return err
}
}
return nil
})
return nil
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage %s [options] <path>\n\n", filepath.Base(os.Args[0]))
fmt.Fprintln(os.Stderr, "Parameters:")
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}
if err := run(flag.Arg(0)); err != nil {
log.Fatal(err)
}
}