131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
package export
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"slidewalk/internal/parser"
|
|
"slidewalk/internal/render"
|
|
"slidewalk/web"
|
|
)
|
|
|
|
// exportedAssets are the static assets (relative to web.Assets' "assets"
|
|
// root) copied into every export. The dev-only live-reload client script is
|
|
// deliberately not part of this list.
|
|
var exportedAssets = []string{
|
|
"vendor/style.css",
|
|
"vendor/chroma.css",
|
|
"vendor/nav.js",
|
|
"vendor/mermaid.min.js",
|
|
}
|
|
|
|
// Export renders the slide deck in dir and writes it, along with its
|
|
// required assets, into outDir. outName is the filename of the exported
|
|
// HTML document, e.g. "index.html".
|
|
func Export(dir, outDir, outName string) error {
|
|
slides, err := parser.ParseDir(dir)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing slides in %s: %w", dir, err)
|
|
}
|
|
|
|
if err := os.MkdirAll(outDir, 0o755); err != nil {
|
|
return fmt.Errorf("creating output directory %s: %w", outDir, err)
|
|
}
|
|
|
|
if err := writePage(outDir, outName, slides); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, name := range exportedAssets {
|
|
dest := filepath.Join(outDir, filepath.FromSlash(name))
|
|
if err := copyAsset(name, dest); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := copyUserAssets(dir, outDir); err != nil {
|
|
return fmt.Errorf("copying assets from %s: %w", dir, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// copyUserAssets copies every file in dir other than the slide ".md" files
|
|
// themselves (e.g. images referenced via Markdown like "")
|
|
// into outDir, preserving relative paths and subdirectory structure.
|
|
// Dotfiles and dot-directories (".git", ".DS_Store", ...) are skipped.
|
|
func copyUserAssets(dir, outDir string) error {
|
|
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.HasPrefix(d.Name(), ".") {
|
|
if d.IsDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
if strings.EqualFold(filepath.Ext(d.Name()), ".md") {
|
|
return nil
|
|
}
|
|
|
|
rel, err := filepath.Rel(dir, path)
|
|
if err != nil {
|
|
return fmt.Errorf("computing relative path for %s: %w", path, err)
|
|
}
|
|
return copyFile(path, filepath.Join(outDir, rel))
|
|
})
|
|
}
|
|
|
|
// copyFile copies the file at src to dest, creating any missing parent
|
|
// directories.
|
|
func copyFile(src, dest string) error {
|
|
data, err := os.ReadFile(src)
|
|
if err != nil {
|
|
return fmt.Errorf("reading %s: %w", src, err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
|
return fmt.Errorf("creating directory for %s: %w", dest, err)
|
|
}
|
|
if err := os.WriteFile(dest, data, 0o644); err != nil {
|
|
return fmt.Errorf("writing %s: %w", dest, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writePage(outDir, outName string, slides []parser.Slide) error {
|
|
htmlPath := filepath.Join(outDir, outName)
|
|
f, err := os.Create(htmlPath)
|
|
if err != nil {
|
|
return fmt.Errorf("creating %s: %w", htmlPath, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
page := render.Page{Title: filepath.Base(filepath.Clean(outDir)), Slides: slides}
|
|
if err := render.Render(f, page); err != nil {
|
|
return fmt.Errorf("rendering page to %s: %w", htmlPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// copyAsset copies the embedded asset at "assets/<name>" to dest.
|
|
func copyAsset(name, dest string) error {
|
|
data, err := fs.ReadFile(web.Assets, path.Join("assets", name))
|
|
if err != nil {
|
|
return fmt.Errorf("reading embedded asset %s: %w", name, err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
|
return fmt.Errorf("creating directory for %s: %w", dest, err)
|
|
}
|
|
if err := os.WriteFile(dest, data, 0o644); err != nil {
|
|
return fmt.Errorf("writing %s: %w", dest, err)
|
|
}
|
|
return nil
|
|
}
|