1
0
Fork 0
mirror of https://github.com/Luzifer/badge-gen.git synced 2024-11-08 13:20:02 +00:00

Refactor app, add support for service hooks

This commit is contained in:
Knut Ahlers 2016-06-28 19:35:48 +02:00
parent 1f7edccdc3
commit ced8de83f9
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
3 changed files with 139 additions and 91 deletions

132
app.go Normal file
View file

@ -0,0 +1,132 @@
package main
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"net/http"
"strings"
"text/template"
"github.com/Luzifer/rconfig"
"github.com/gorilla/mux"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/svg"
)
const (
xSpacing = 8
defaultColor = "4c1"
)
var (
cfg = struct {
Port int64 `env:"PORT"`
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
}{}
serviceHandlers = map[string]serviceHandlerFunction{}
)
type serviceHandlerFunction func(params []string) (title, text, color string, err error)
func registerServiceHandler(service string, f serviceHandlerFunction) error {
if _, ok := serviceHandlers[service]; ok {
return errors.New("Duplicate service handler")
}
serviceHandlers[service] = f
return nil
}
func main() {
rconfig.Parse(&cfg)
if cfg.Port != 0 {
cfg.Listen = fmt.Sprintf(":%d", cfg.Port)
}
r := mux.NewRouter()
r.HandleFunc("/v1/badge", generateBadge).Methods("GET")
r.HandleFunc("/{service}/{parameters:.*}", generateServiceBadge).Methods("GET")
http.ListenAndServe(cfg.Listen, r)
}
func generateServiceBadge(res http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := vars["service"]
params := strings.Split(vars["parameters"], "/")
handler, ok := serviceHandlers[service]
if !ok {
http.Error(res, "Service not found: "+service, http.StatusNotFound)
return
}
title, text, color, err := handler(params)
if err != nil {
http.Error(res, "Error while executing service: "+err.Error(), http.StatusInternalServerError)
return
}
renderBadgeToResponse(res, title, text, color)
}
func generateBadge(res http.ResponseWriter, r *http.Request) {
title := r.URL.Query().Get("title")
text := r.URL.Query().Get("text")
color := r.URL.Query().Get("color")
if title == "" || text == "" {
http.Error(res, "You must specify parameters 'title' and 'text'.", http.StatusInternalServerError)
return
}
if color == "" {
color = defaultColor
}
renderBadgeToResponse(res, title, text, color)
}
func renderBadgeToResponse(res http.ResponseWriter, title, text, color string) {
badge, eTag := createBadge(title, text, color)
res.Header().Add("Content-Type", "image/svg+xml")
res.Header().Add("Cache-Control", "public, max-age=31536000")
res.Header().Add("ETag", eTag)
m := minify.New()
m.AddFunc("image/svg+xml", svg.Minify)
badge, _ = m.Bytes("image/svg+xml", badge)
res.Write(badge)
}
func createBadge(title, text, color string) ([]byte, string) {
var buf bytes.Buffer
titleW, _ := calculateTextWidth(title)
textW, _ := calculateTextWidth(text)
width := titleW + textW + 4*xSpacing
t, _ := Asset("assets/badgeTemplate.tpl")
tpl, _ := template.New("svg").Parse(string(t))
tpl.Execute(&buf, map[string]interface{}{
"Width": width,
"TitleWidth": titleW + 2*xSpacing,
"Title": title,
"Text": text,
"TitleAnchor": titleW/2 + xSpacing,
"TextAnchor": titleW + textW/2 + 3*xSpacing,
"Color": color,
})
return buf.Bytes(), generateETag(buf.Bytes())
}
func generateETag(in []byte) string {
return fmt.Sprintf("%x", sha1.Sum(in))
}

10
font.go
View file

@ -5,7 +5,10 @@
// Copyright 2010 The Freetype-Go Authors. All rights reserved. // Copyright 2010 The Freetype-Go Authors. All rights reserved.
package main package main
import "code.google.com/p/freetype-go/freetype/truetype" import (
"github.com/golang/freetype/truetype"
"golang.org/x/image/math/fixed"
)
const ( const (
fontSize = 11 fontSize = 11
@ -23,11 +26,12 @@ func calculateTextWidth(text string) (int, error) {
width := 0 width := 0
prev, hasPrev := truetype.Index(0), false prev, hasPrev := truetype.Index(0), false
for _, rune := range text { for _, rune := range text {
fUnitsPerEm := fixed.Int26_6(font.FUnitsPerEm())
index := font.Index(rune) index := font.Index(rune)
if hasPrev { if hasPrev {
width += int(font.Kerning(font.FUnitsPerEm(), prev, index)) width += int(font.Kern(fUnitsPerEm, prev, index))
} }
width += int(font.HMetric(font.FUnitsPerEm(), index).AdvanceWidth) width += int(font.HMetric(fUnitsPerEm, index).AdvanceWidth)
prev, hasPrev = index, true prev, hasPrev = index, true
} }

88
main.go
View file

@ -1,88 +0,0 @@
package main
import (
"bufio"
"bytes"
"fmt"
"net/http"
"os"
"github.com/alecthomas/template"
"github.com/gorilla/mux"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/svg"
)
const (
xSpacing = 8
)
func main() {
port := fmt.Sprintf(":%s", os.Getenv("PORT"))
if port == ":" {
port = ":3000"
}
http.Handle("/", generateMux())
http.ListenAndServe(port, nil)
}
func generateMux() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/v1/badge", generateBadge).Methods("GET")
return r
}
func generateBadge(res http.ResponseWriter, r *http.Request) {
title := r.URL.Query().Get("title")
text := r.URL.Query().Get("text")
color := r.URL.Query().Get("color")
if title == "" || text == "" {
http.Error(res, "You must specify parameters 'title' and 'text'.", http.StatusInternalServerError)
return
}
if color == "" {
color = "4c1"
}
badge := createBadge(title, text, color)
res.Header().Add("Content-Type", "image/svg+xml")
res.Header().Add("Cache-Control", "public, max-age=31536000")
m := minify.New()
m.AddFunc("image/svg+xml", svg.Minify)
badge, _ = minify.Bytes(m, "image/svg+xml", badge)
res.Write(badge)
}
func createBadge(title, text, color string) []byte {
var buf bytes.Buffer
bufw := bufio.NewWriter(&buf)
titleW, _ := calculateTextWidth(title)
textW, _ := calculateTextWidth(text)
width := titleW + textW + 4*xSpacing
t, _ := Asset("assets/badgeTemplate.tpl")
tpl, _ := template.New("svg").Parse(string(t))
tpl.Execute(bufw, map[string]interface{}{
"Width": width,
"TitleWidth": titleW + 2*xSpacing,
"Title": title,
"Text": text,
"TitleAnchor": titleW/2 + xSpacing,
"TextAnchor": titleW + textW/2 + 3*xSpacing,
"Color": color,
})
bufw.Flush()
return buf.Bytes()
}