adds build script
This commit is contained in:
221
cmd/build/main.go
Normal file
221
cmd/build/main.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"smartbar/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expose page size to templates via CSS variables inline on <body>.
|
||||||
|
// Use HTMLAttr so it renders as an attribute safely.
|
||||||
|
func pageSizeStyleAttr(widthIn, heightIn float64) template.HTMLAttr {
|
||||||
|
return template.HTMLAttr(
|
||||||
|
`style="--page-w: ` + floatToIn(widthIn) + `; --page-h: ` + floatToIn(heightIn) + `;"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func floatToIn(v float64) string {
|
||||||
|
return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.4fin", v), "0"), ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageData struct {
|
||||||
|
Title string
|
||||||
|
Content template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
type IndexPage struct {
|
||||||
|
Name string
|
||||||
|
Href string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrintPage struct {
|
||||||
|
Content template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
type TwoUpSheet struct {
|
||||||
|
Left PrintPage
|
||||||
|
Right *PrintPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustReadFile(path string) string {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(path string, content []byte) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, content, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Required flags
|
||||||
|
var pagesDir string
|
||||||
|
var outDir string
|
||||||
|
var templatesDir string
|
||||||
|
var orderPath string
|
||||||
|
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||||
|
flagSet.StringVar(&pagesDir, "pages", "", "path to pages directory (required)")
|
||||||
|
flagSet.StringVar(&outDir, "out", "", "path to output directory (required)")
|
||||||
|
flagSet.StringVar(&templatesDir, "templates", "", "path to templates directory (required)")
|
||||||
|
flagSet.StringVar(&orderPath, "order", "pages.yaml", "path to YAML file listing page order (optional)")
|
||||||
|
_ = flagSet.Parse(os.Args[1:])
|
||||||
|
if pagesDir == "" || outDir == "" || templatesDir == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: --pages, --out, and --templates are required")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
baseTplPath := filepath.Join(templatesDir, "base.gohtml")
|
||||||
|
|
||||||
|
baseTpl := template.Must(template.New("base").Parse(mustReadFile(baseTplPath)))
|
||||||
|
|
||||||
|
var pageFiles []string
|
||||||
|
if data, err := os.ReadFile(orderPath); err == nil {
|
||||||
|
ordered, perr := parseYAMLListOfStrings(string(data))
|
||||||
|
if perr != nil {
|
||||||
|
panic(perr)
|
||||||
|
}
|
||||||
|
for _, rel := range ordered {
|
||||||
|
abs := filepath.Join(pagesDir, rel)
|
||||||
|
if _, statErr := os.Stat(abs); statErr == nil {
|
||||||
|
pageFiles = append(pageFiles, abs)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: listed page not found: %s\n", rel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(pageFiles) == 0 {
|
||||||
|
filepath.WalkDir(pagesDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(d.Name(), ".html") {
|
||||||
|
pageFiles = append(pageFiles, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
sort.Strings(pageFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build each page
|
||||||
|
for _, pagePath := range pageFiles {
|
||||||
|
raw := mustReadFile(pagePath)
|
||||||
|
// Trim outer whitespace but keep inner spacing
|
||||||
|
content := strings.TrimSpace(raw)
|
||||||
|
|
||||||
|
// Title based on filename
|
||||||
|
base := filepath.Base(pagePath)
|
||||||
|
title := strings.TrimSuffix(base, filepath.Ext(base))
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
if err := baseTpl.Execute(&out, map[string]any{
|
||||||
|
"Title": title,
|
||||||
|
"Content": template.HTML(content),
|
||||||
|
"PageSizeAttr": pageSizeStyleAttr(config.PageWidthIn, config.PageHeightIn),
|
||||||
|
}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rel, _ := filepath.Rel(pagesDir, pagePath)
|
||||||
|
outPath := filepath.Join(outDir, rel)
|
||||||
|
if err := writeFile(outPath, out.Bytes()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build index.html via template
|
||||||
|
indexPath := filepath.Join(outDir, "index.html")
|
||||||
|
indexTplPath := filepath.Join(templatesDir, "index.gohtml")
|
||||||
|
indexTpl := template.Must(template.New("index").Parse(mustReadFile(indexTplPath)))
|
||||||
|
|
||||||
|
var pages []IndexPage
|
||||||
|
for _, pagePath := range pageFiles {
|
||||||
|
rel, _ := filepath.Rel(pagesDir, pagePath)
|
||||||
|
pages = append(pages, IndexPage{
|
||||||
|
Name: filepath.Base(pagePath),
|
||||||
|
// Index is written to outDir, and the compiled pages are also in outDir.
|
||||||
|
// So iframe src should be relative to index location, not prefixed with outDir again.
|
||||||
|
Href: filepath.ToSlash(rel),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var outIndex bytes.Buffer
|
||||||
|
if err := indexTpl.Execute(&outIndex, map[string]any{"Pages": pages}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := writeFile(indexPath, outIndex.Bytes()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build print.html containing all pages one after another for PDF
|
||||||
|
printTplPath := filepath.Join(templatesDir, "print.gohtml")
|
||||||
|
printTpl := template.Must(template.New("print").Parse(mustReadFile(printTplPath)))
|
||||||
|
var printPages []PrintPage
|
||||||
|
for _, pagePath := range pageFiles {
|
||||||
|
content := strings.TrimSpace(mustReadFile(pagePath))
|
||||||
|
printPages = append(printPages, PrintPage{Content: template.HTML(content)})
|
||||||
|
}
|
||||||
|
var outPrint bytes.Buffer
|
||||||
|
if err := printTpl.Execute(&outPrint, map[string]any{"Pages": printPages, "PageSizeAttr": pageSizeStyleAttr(config.PageWidthIn, config.PageHeightIn)}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := writeFile(filepath.Join(outDir, "print.html"), outPrint.Bytes()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build 2-up print HTML (letter pages with two half-letter pages side by side)
|
||||||
|
var sheets []TwoUpSheet
|
||||||
|
for i := 0; i < len(printPages); i += 2 {
|
||||||
|
left := printPages[i]
|
||||||
|
var right *PrintPage
|
||||||
|
if i+1 < len(printPages) {
|
||||||
|
r := printPages[i+1]
|
||||||
|
right = &r
|
||||||
|
}
|
||||||
|
sheets = append(sheets, TwoUpSheet{Left: left, Right: right})
|
||||||
|
}
|
||||||
|
print2TplPath := filepath.Join(templatesDir, "print_2up.gohtml")
|
||||||
|
print2Tpl := template.Must(template.New("print2up").Parse(mustReadFile(print2TplPath)))
|
||||||
|
var outPrint2 bytes.Buffer
|
||||||
|
if err := print2Tpl.Execute(&outPrint2, map[string]any{"Sheets": sheets}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := writeFile(filepath.Join(outDir, "print_2up.html"), outPrint2.Bytes()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseYAMLListOfStrings expects a YAML document that is a top-level sequence of strings.
|
||||||
|
// We avoid adding a dependency to keep the builder lightweight; this is minimal and strict.
|
||||||
|
func parseYAMLListOfStrings(src string) ([]string, error) {
|
||||||
|
var out []string
|
||||||
|
for _, line := range strings.Split(src, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "-") {
|
||||||
|
item := strings.TrimSpace(strings.TrimPrefix(line, "-"))
|
||||||
|
if item != "" {
|
||||||
|
out = append(out, item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For simplicity, only support list form
|
||||||
|
return nil, fmt.Errorf("invalid yaml line (expect '- item'): %s", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user