Skip to content

Commit

Permalink
adds html template support to web package (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
jritsema authored Jun 10, 2023
1 parent b49cc3f commit 4f7e3c7
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 13 deletions.
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A kitchen sink of Go tools that I've found useful. Uses only the standard librar

### contents

- [super lightweight http server framework](web)
- [super lightweight http server library](web)
- [exponential backoff retry](retry.go)
- [working with JSON](json.go)
- [working with the file system](fs.go)
Expand Down Expand Up @@ -53,27 +53,48 @@ func main() {
if err != nil {
fmt.Println("error executing command: %w", err)
}

var data interface{}
err = gotoolbox.HttpGetJSON("https://api.example.com/data.json", &data)
err = gotoolbox.HttpPostJSON("https://api.example.com/data.json", data, http.StatusOK)
}
```

#### web framework
#### web package

```go
package main

import "github.com/jritsema/gotoolbox/web"
import (
"embed"
"html/template"
"net/http"
"github.com/jritsema/gotoolbox/web"
)

var (
//go:embed all:templates/*
templateFS embed.FS
html *template.Template
)

type Data struct {
Hello string `json:"hello"`
}

func hello(r *http.Request) *web.Response {
func index(r *http.Request) *web.Response {
return HTML(http.StatusOK, html, "index.html", Data{Hello: "world"}, nil)
}

func api(r *http.Request) *web.Response {
return web.DataJSON(http.StatusOK, Data{Hello: "world"}, nil)
}

func main() {
html, _ = web.TemplateParseFSRecursive(templateFS, ".html", true, nil)
mux := http.NewServeMux()
mux.Handle("/hello", web.Action(hello))
mux.Handle("/api", web.Action(api))
mux.Handle("/", web.Action(index))
http.ListenAndServe(":8080", mux)
}
```
Expand Down
90 changes: 82 additions & 8 deletions web/main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
//Package web is a super minimalistic web/http server framework
// Package web is a super minimalistic web/http server library
package web

import (
"bytes"
"encoding/json"
"html/template"
"io"
"io/fs"
"log"
"net/http"
"os"
"strings"
)

// Headers is a map of string to string where the key is the key for the header
Expand All @@ -24,7 +29,7 @@ type Response struct {
Headers Headers
}

//Write writes a response to an http.ResponseWriter
// Write writes a response to an http.ResponseWriter
func (response *Response) Write(rw http.ResponseWriter) {
if response != nil {
if response.ContentType != "" {
Expand All @@ -45,7 +50,7 @@ func (response *Response) Write(rw http.ResponseWriter) {
}

// Action represents a simplified http action
// implements http.Handler
// that implements http.Handler
type Action func(r *http.Request) *Response

// Hyperlink represents a hyperlink
Expand All @@ -60,7 +65,7 @@ func (a Action) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
response.Write(rw)
}

//Error returns an error response
// Error returns an error response
func Error(status int, err error, headers Headers) *Response {
return &Response{
Status: status,
Expand All @@ -73,7 +78,7 @@ type errorResponse struct {
Error string `json:"error"`
}

//ErrorJSON returns an error in json format
// ErrorJSON returns an error in json format
func ErrorJSON(status int, err error, headers Headers) *Response {
errResp := errorResponse{
Error: err.Error(),
Expand All @@ -92,7 +97,7 @@ func ErrorJSON(status int, err error, headers Headers) *Response {
}
}

//Data returns a data response
// Data returns a data response
func Data(status int, content []byte, headers Headers) *Response {
return &Response{
Status: status,
Expand All @@ -101,7 +106,7 @@ func Data(status int, content []byte, headers Headers) *Response {
}
}

//DataJSON returns a data response in json format
// DataJSON returns a data response in json format
func DataJSON(status int, v interface{}, headers Headers) *Response {

b, err := json.MarshalIndent(v, "", " ")
Expand All @@ -117,7 +122,76 @@ func DataJSON(status int, v interface{}, headers Headers) *Response {
}
}

//Empty returns an empty http response
// Empty returns an empty http response
func Empty(status int) *Response {
return Data(status, []byte(""), nil)
}

// HTML renders an html template to a web response
func HTML(status int, t *template.Template, template string, data interface{}, headers Headers) *Response {

//render template to buffer
var buf bytes.Buffer
if err := t.ExecuteTemplate(&buf, template, data); err != nil {
log.Println(err)
return Empty(http.StatusInternalServerError)
}
return &Response{
Status: status,
ContentType: "text/html",
Content: &buf,
Headers: headers,
}
}

// TemplateParseFSRecursive recursively parses all templates in the FS with the given extension.
// File paths are used as template names to support duplicate file names.
// Use nonRootTemplateNames to exclude root directory from template names
// (e.g. index.html instead of templates/index.html)
func TemplateParseFSRecursive(
templates fs.FS,
ext string,
nonRootTemplateNames bool,
funcMap template.FuncMap) (*template.Template, error) {

root := template.New("")
err := fs.WalkDir(templates, "templates", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() && strings.HasSuffix(path, ext) {
if err != nil {
return err
}
b, err := fs.ReadFile(templates, path)
if err != nil {
return err
}
name := ""
if nonRootTemplateNames {
//name the template based on the file path (excluding the root)
parts := strings.Split(path, string(os.PathSeparator))
name = strings.Join(parts[1:], string(os.PathSeparator))
}
t := root.New(name).Funcs(funcMap)
_, err = t.Parse(string(b))
if err != nil {
return err
}
}
return nil
})
return root, err
}

// PathLast returns the last segment in the path
// and the number of path segments
func PathLast(r *http.Request) (string, int) {

//remove trailing /
path := r.URL.Path
if path[len(path)-1] == '/' {
path = path[0 : len(path)-1]
}

parts := strings.Split(path, "/")
l := len(parts) - 1
return parts[l], l
}

0 comments on commit 4f7e3c7

Please sign in to comment.