mirror of https://github.com/zschaffer/jenga.git
175 lines
4.3 KiB
Go
175 lines
4.3 KiB
Go
package main
|
|
|
|
// TODO: add documentation to all functions
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/rjeczalik/notify"
|
|
)
|
|
|
|
// config represents a toml file used to configure jenga
|
|
type config struct {
|
|
InputDirPath string // path to a directory of markdown files to be consumed by jenga
|
|
OutputDirPath string // path to output directory of html files
|
|
TemplatePath string // path to go-styled template.html
|
|
}
|
|
|
|
const AppVersion = "v0.1.1"
|
|
|
|
func run() error {
|
|
version := flag.Bool("v", false, "prints current jenga version")
|
|
dev := flag.Bool("dev", false, "watchs source folder and rebuilds on changes")
|
|
configPath := flag.String("config", "./jenga.toml", "path to jenga.toml config")
|
|
flag.Parse()
|
|
|
|
args := flag.Args()
|
|
if len(args) != 0 {
|
|
fmt.Printf("unknown arguments: %v\n", args)
|
|
fmt.Println("use jenga -h for accepted arguments")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if *version {
|
|
fmt.Println(AppVersion)
|
|
os.Exit(0)
|
|
}
|
|
|
|
cfg, err := getConfig(*configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get config (%q) %w", *configPath, err)
|
|
}
|
|
|
|
inputFilePaths, err := getInputFilePaths(cfg.InputDirPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get input file paths (%q) %w", cfg.InputDirPath, err)
|
|
}
|
|
|
|
template, err := getTemplate(cfg.TemplatePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get template (%q) %w", cfg.TemplatePath, err)
|
|
}
|
|
|
|
b := &builder{
|
|
inputFilePaths: inputFilePaths,
|
|
outputDirPath: cfg.OutputDirPath,
|
|
template: template,
|
|
}
|
|
|
|
// Print out Jenga status
|
|
fmt.Printf("\033[0;34mconfig\033[0m = %q\n", *configPath)
|
|
fmt.Printf("\033[0;34mtemplate\033[0m = %q\n", cfg.TemplatePath)
|
|
fmt.Printf("\033[0;34minput\033[0m = %q\n", cfg.InputDirPath)
|
|
fmt.Printf("\033[0;34moutput\033[0m = %q\n", cfg.OutputDirPath)
|
|
|
|
if *dev {
|
|
if err := watch(b, cfg); err != nil {
|
|
return fmt.Errorf("failed to watch files: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := b.build(); err != nil {
|
|
return fmt.Errorf("failed to build files: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func watch(b *builder, cfg *config) error {
|
|
c := make(chan notify.EventInfo, 1)
|
|
if err := notify.Watch(cfg.InputDirPath, c, notify.Write, notify.Remove); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer notify.Stop(c)
|
|
|
|
srv := http.FileServer(http.Dir(cfg.OutputDirPath))
|
|
go http.ListenAndServe(":3000", srv)
|
|
|
|
fmt.Println()
|
|
fmt.Println("listening @ \033[0;34mhttp://localhost:3000\033[0m")
|
|
fmt.Println()
|
|
fmt.Println("watching \033[0;34minput\033[0m for changes...")
|
|
|
|
if err := b.build(); err != nil {
|
|
return fmt.Errorf("failed to build files: %w", err)
|
|
}
|
|
|
|
for range c {
|
|
if err := b.build(); err != nil {
|
|
return fmt.Errorf("failed to build files: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getTemplate returns a pointer to a parsed Template from templatePath
|
|
func getTemplate(templatePath string) (*template.Template, error) {
|
|
t, err := template.ParseFiles(templatePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse template (%q) %w", templatePath, err)
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// getInputFilePaths returns file paths to every .md in input directory in reverse alphabetical order
|
|
func getInputFilePaths(inputDirPath string) ([]string, error) {
|
|
var inputFilePaths []string
|
|
|
|
inputDir, err := os.Open(inputDirPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open %q %w", inputDirPath, err)
|
|
}
|
|
|
|
defer inputDir.Close()
|
|
|
|
inputFiles, err := inputDir.ReadDir(-1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read inputDir %w", err)
|
|
}
|
|
|
|
if len(inputFiles) == 0 {
|
|
return nil, errors.New("inputDirPath is empty")
|
|
}
|
|
|
|
for _, file := range inputFiles {
|
|
if !file.IsDir() && file.Name()[0] != '.' {
|
|
inputFilePaths = append(inputFilePaths, filepath.Join(inputDirPath, file.Name()))
|
|
}
|
|
}
|
|
|
|
return inputFilePaths, nil
|
|
}
|
|
|
|
// getConfig decodes a toml file at variable path, returning a config struct
|
|
func getConfig(path string) (*config, error) {
|
|
cfg := config{}
|
|
|
|
_, err := toml.DecodeFile(path, &cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode toml config %w", err)
|
|
}
|
|
|
|
if cfg.InputDirPath == "" {
|
|
return nil, errors.New("failed to find InputDirPath in config")
|
|
}
|
|
|
|
if cfg.OutputDirPath == "" {
|
|
return nil, errors.New("failed to find OutputDirPath in config")
|
|
}
|
|
|
|
if cfg.TemplatePath == "" {
|
|
return nil, errors.New("failed to find TemplatePath in config")
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|