mirror of https://github.com/zschaffer/jenga.git
Merge branch 'dev-server-testing'
This commit is contained in:
commit
66ecf73651
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"mysqlOptions": {
|
||||
"authProtocol": "default"
|
||||
},
|
||||
"previewLimit": 50,
|
||||
"server": "localhost",
|
||||
"port": 3306,
|
||||
"askForPassword": true,
|
||||
"driver": "MySQL",
|
||||
"name": "snippetbox",
|
||||
"database": "snippetbox",
|
||||
"username": "root"
|
||||
}
|
||||
]
|
||||
}
|
13
README.md
13
README.md
|
@ -1,12 +1,11 @@
|
|||
<img align="right" width="250px" src="https://github.com/zschaffer/jenga/blob/a8cbfd372c352d78b7ac91d7b6e439d379d995cb/jenga.png">
|
||||
|
||||
# Jenga
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/zschaffer/jenga.svg)](https://pkg.go.dev/github.com/zschaffer/jenga)
|
||||
![build](https://github.com/zschaffer/jenga/actions/workflows/go.yml/badge.svg)
|
||||
![build](https://github.com/zschaffer/jenga/actions/workflows/go.yml/badge.svg)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/zschaffer/jenga)](https://goreportcard.com/report/github.com/zschaffer/jenga)
|
||||
|
||||
|
||||
|
||||
A tool for building static single page blogs in [Go](https://golang.org/).
|
||||
|
||||
## Details
|
||||
|
@ -37,13 +36,13 @@ go install
|
|||
|
||||
Jenga has some basic setting up in order to get going; sort of like the real game!
|
||||
|
||||
### Set up your own `template.html` or copy it from the releases tab
|
||||
### Set up your own `jenga.tmpl` or copy it from the releases tab
|
||||
|
||||
Jenga uses Go's [`html/template`](https://pkg.go.dev/html/template) library for template construction. Read their [doc's](https://pkg.go.dev/html/template) for more information on how to manipulate your data. The basic thing required in your `template.html` is a `{{.}}` block to render the data converted from your `.md` files.
|
||||
Jenga uses Go's [`html/template`](https://pkg.go.dev/html/template) library for template construction. Read their [doc's](https://pkg.go.dev/html/template) for more information on how to manipulate your data. The basic thing required in your `jenga.tmpl` is a `{{.}}` block to render the data converted from your `.md` files.
|
||||
|
||||
The included template.html file looks something like this:
|
||||
The included `jenga.tmpl` file looks something like this:
|
||||
|
||||
```html
|
||||
```tmpl
|
||||
<body>
|
||||
<!-- Wrap everything in a div -->
|
||||
<div>
|
||||
|
|
32
builder.go
32
builder.go
|
@ -34,10 +34,25 @@ func readFile(filePath string) (template.HTML, error) {
|
|||
|
||||
// build reads all the files in the inputFilePaths slice, then passes them to writeOutputFile to build an index.html
|
||||
func (b *builder) build() error {
|
||||
fmt.Println()
|
||||
inputData, err := getInputData(b.inputFilePaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get input data: %w", err)
|
||||
}
|
||||
|
||||
if err := writeOutputFile(inputData, b.outputDirPath, b.template); err != nil {
|
||||
return fmt.Errorf("failed to write to output file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInputData(inputFilePaths []string) ([]template.HTML, error) {
|
||||
fmt.Println("\033[0;34m[1/2]\033[0m converting source files to HTML")
|
||||
var inputData []template.HTML
|
||||
temporaryMap := make(map[string]template.HTML)
|
||||
g := new(errgroup.Group)
|
||||
for _, inputFilePath := range b.inputFilePaths {
|
||||
|
||||
for _, inputFilePath := range inputFilePaths {
|
||||
path := inputFilePath
|
||||
g.Go(func() error {
|
||||
data, err := readFile(path)
|
||||
|
@ -49,34 +64,27 @@ func (b *builder) build() error {
|
|||
}
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
} else {
|
||||
for _, inputFilePath := range b.inputFilePaths {
|
||||
for _, inputFilePath := range inputFilePaths {
|
||||
inputData = append([]template.HTML{temporaryMap[inputFilePath]}, inputData...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := writeOutputFile(inputData, b.outputDirPath, b.template); err != nil {
|
||||
return fmt.Errorf("failed to write to output file: %w", err)
|
||||
}
|
||||
return nil
|
||||
return inputData, nil
|
||||
}
|
||||
|
||||
// writeOutputFile creates an index.html file at outputDirPath using a template filled with inputData
|
||||
func writeOutputFile(inputData []template.HTML, outputDirPath string, t *template.Template) error {
|
||||
fmt.Println("\033[0;34m[2/2]\033[0m generating index.html")
|
||||
filePath := filepath.Join(outputDirPath, "index.html")
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file %+s: %v", filePath, err)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
writer := bufio.NewWriter(file)
|
||||
|
||||
defer writer.Flush()
|
||||
|
||||
if err := t.Execute(writer, inputData); err != nil {
|
||||
return fmt.Errorf("error executing template: %w", err)
|
||||
}
|
||||
|
|
84
cli.go
84
cli.go
|
@ -1,28 +1,46 @@
|
|||
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
|
||||
OutputDirPath string
|
||||
TemplatePath string
|
||||
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 {
|
||||
configPath := flag.String("config", "./jenga.toml", "/path/to/jenga.toml")
|
||||
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()
|
||||
|
||||
fmt.Printf("~ using config at %q\n", *configPath)
|
||||
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 {
|
||||
|
@ -39,18 +57,54 @@ func run() error {
|
|||
return fmt.Errorf("failed to get template (%q) %w", cfg.TemplatePath, err)
|
||||
}
|
||||
|
||||
g := &builder{
|
||||
b := &builder{
|
||||
inputFilePaths: inputFilePaths,
|
||||
outputDirPath: cfg.OutputDirPath,
|
||||
template: template,
|
||||
}
|
||||
|
||||
fmt.Printf("~ using template from %q\n", cfg.TemplatePath)
|
||||
fmt.Printf("~ using source files from %q\n", cfg.InputDirPath)
|
||||
fmt.Printf("~ building 'index.html' in %q\n", cfg.OutputDirPath)
|
||||
// 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 err := g.build(); err != nil {
|
||||
return fmt.Errorf("failed to build files %w", err)
|
||||
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
|
||||
|
@ -66,7 +120,7 @@ func getTemplate(templatePath string) (*template.Template, error) {
|
|||
return t, nil
|
||||
}
|
||||
|
||||
// getInputFilePaths returns file paths to every .md in input directory
|
||||
// getInputFilePaths returns file paths to every .md in input directory in reverse alphabetical order
|
||||
func getInputFilePaths(inputDirPath string) ([]string, error) {
|
||||
var inputFilePaths []string
|
||||
|
||||
|
@ -77,11 +131,15 @@ func getInputFilePaths(inputDirPath string) ([]string, error) {
|
|||
|
||||
defer inputDir.Close()
|
||||
|
||||
inputFiles, err := inputDir.Readdir(-1)
|
||||
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()))
|
||||
|
|
56
cli_test.go
56
cli_test.go
|
@ -1,22 +1,69 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
func TestParseInput(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func TestGetTemplate(t *testing.T) {
|
||||
got, err := getTemplate("./testdata/jenga.tmpl")
|
||||
if err != nil {
|
||||
t.Errorf("failed to get template: %v", err)
|
||||
}
|
||||
|
||||
want, _ := template.ParseFiles("./testdata/jenga.tmpl")
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetInputFilePaths(t *testing.T) {
|
||||
t.Run("correctly return file paths to markdown files in input directory in reverse order", func(t *testing.T) {
|
||||
got, err := getInputFilePaths("./testdata/src")
|
||||
if err != nil {
|
||||
t.Errorf("failed to return file paths: %v", err)
|
||||
}
|
||||
|
||||
want := []string{
|
||||
"testdata/src/test3.md",
|
||||
"testdata/src/test2.md",
|
||||
"testdata/src/test1.md",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("return empty string array and err when passed path with no markdown files", func(t *testing.T) {
|
||||
_, err := getInputFilePaths("./testdata/empty")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("error is empty, want: inputDirPath is empty")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("return error when passed a broken path", func(t *testing.T) {
|
||||
_, err := getInputFilePaths("./testdata/path/does/not/exist")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("error is empty, want: failed to open")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetConfig(t *testing.T) {
|
||||
|
||||
t.Run("correctly parse passed config", func(t *testing.T) {
|
||||
|
||||
got, err := getConfig("./testdata/jenga.toml")
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse config: %v", err)
|
||||
|
@ -25,7 +72,7 @@ func TestGetConfig(t *testing.T) {
|
|||
want := &config{
|
||||
InputDirPath: "./src",
|
||||
OutputDirPath: "./build",
|
||||
TemplatePath: "./template.html",
|
||||
TemplatePath: "./jenga.tmpl",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
|
@ -39,6 +86,5 @@ func TestGetConfig(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Errorf("should return error when passed a bad path")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
|
6
go.mod
6
go.mod
|
@ -5,5 +5,11 @@ go 1.18
|
|||
require (
|
||||
github.com/BurntSushi/toml v1.2.0
|
||||
github.com/gomarkdown/markdown v0.0.0-20220731190611-dcdaee8e7a53
|
||||
github.com/rjeczalik/notify v0.9.2
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bluele/adblock v0.0.0-20150928111208-97547036a6ec // indirect
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect
|
||||
)
|
||||
|
|
7
go.sum
7
go.sum
|
@ -1,6 +1,13 @@
|
|||
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
|
||||
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/bluele/adblock v0.0.0-20150928111208-97547036a6ec h1:bDC3KtBzO9PhiPl+SYkMnwgzMc4YjfQDnz1nmMX4Tlo=
|
||||
github.com/bluele/adblock v0.0.0-20150928111208-97547036a6ec/go.mod h1:KpJbk0iUmfxhUj4NXBLYjg6E5RCApkovVJ+SPY/hcaA=
|
||||
github.com/gomarkdown/markdown v0.0.0-20220731190611-dcdaee8e7a53 h1:JguE3sS3yLzLiCTCnsmzVFuTvTMDJALbzCgurwY5G/0=
|
||||
github.com/gomarkdown/markdown v0.0.0-20220731190611-dcdaee8e7a53/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8=
|
||||
github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
@ -2,4 +2,4 @@ InputDirPath = "../blog/src"
|
|||
|
||||
OutputDirPath = "../blog/build"
|
||||
|
||||
TemplatePath = "../blog/template.html"
|
||||
TemplatePath = "../blog/jenga.tmpl"
|
3
main.go
3
main.go
|
@ -13,7 +13,6 @@ in the current directory
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
|
@ -21,6 +20,4 @@ func main() {
|
|||
if err := run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("finished building!")
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@ inputDirPath = "./src"
|
|||
|
||||
outputDirPath = "./build"
|
||||
|
||||
templatePath = "./template.html"
|
||||
templatePath = "./jenga.tmpl"
|
Loading…
Reference in New Issue