package main
import (
"html/template"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"time"
"github.com/cdelorme/go-log"
"github.com/cdelorme/go-maps"
"github.com/cdelorme/go-option"
)
// check that a path exists
// does not care if it is a directory
// will not say whether user has rw access, but
// will throw an error if the user cannot read the parent directory
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// if within a git repo, gets git version as a short-hash
// otherwise falls back to a unix timestamp
func version() string {
version := strconv.FormatInt(time.Now().Unix(), 10)
out, err := exec.Command("sh", "-c", "git rev-parse --short HEAD").Output()
if err == nil {
version = strings.Trim(string(out), "\n")
}
return version
}
// remove the extension from a given filename
func basename(name string) string {
return filepath.Base(strings.TrimSuffix(name, filepath.Ext(name)))
}
func main() {
// get current directory
cwd, _ := os.Getwd()
// prepare staticmd with dependencies & defaults
staticmd := Staticmd{
Logger: log.Logger{Level: log.Error},
Subdirectories: make(map[string][]string),
Indexes: make(map[string][]string),
Version: version(),
Input: cwd,
Output: filepath.Join(cwd, "public/"),
}
// optimize concurrent processing
staticmd.MaxParallelism = runtime.NumCPU()
runtime.GOMAXPROCS(staticmd.MaxParallelism)
// prepare cli options
appOptions := option.App{Description: "command line tool for generating deliverable static content"}
appOptions.Flag("template", "path to the template file", "--template", "-t")
appOptions.Flag("input", "path to the markdown files", "--input", "-i")
appOptions.Flag("output", "path to place generated content", "--output", "-o")
appOptions.Flag("book", "combine all content into a single file", "--book", "-b")
appOptions.Flag("relative", "use relative paths instead of absolute paths", "--relative", "-r")
appOptions.Flag("debug", "verbose debug output", "--debug", "-d")
appOptions.Flag("profile", "produce profile output to supplied path", "--profile", "-p")
appOptions.Example("-t template.tmpl -i . -b")
appOptions.Example("-t template.tmpl -i src/ -o out/ -r")
flags := appOptions.Parse()
// apply flags
t, _ := maps.String(&flags, "", "template")
if tmpl, err := template.ParseFiles(t); err != nil {
staticmd.Logger.Error("Failed to open template: %s", err)
} else {
staticmd.Template = *tmpl
}
staticmd.Input, _ = maps.String(&flags, staticmd.Input, "input")
staticmd.Output, _ = maps.String(&flags, staticmd.Output, "output")
staticmd.Book, _ = maps.Bool(&flags, staticmd.Book, "book")
staticmd.Relative, _ = maps.Bool(&flags, staticmd.Relative, "relative")
// sanitize input & output
staticmd.Input, _ = filepath.Abs(staticmd.Input)
staticmd.Output, _ = filepath.Abs(staticmd.Output)
// optionally enable debugging
if debug, _ := maps.Bool(&flags, false, "debug"); debug {
staticmd.Logger.Level = log.Debug
}
// optionally enable profiling
if profile, _ := maps.String(&flags, "", "profile"); profile != "" {
f, _ := os.Create(profile)
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
// sanitize & validate properties
staticmd.Input = filepath.Clean(staticmd.Input)
staticmd.Output = filepath.Clean(staticmd.Output)
// print debug status
staticmd.Logger.Debug("Staticmd State: %+v", staticmd)
if err := filepath.Walk(staticmd.Input, staticmd.Walk); err != nil {
staticmd.Logger.Error("failed to walk directory: %s", err)
}
staticmd.Logger.Debug("Pages: %+v", staticmd.Pages)
// build
if staticmd.Book {
staticmd.Single()
} else {
staticmd.Multi()
}
}