mirror of
https://github.com/Luzifer/go-latestver.git
synced 2024-11-08 15:10:04 +00:00
Remove dependency to badge-gen instance
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
3073908498
commit
60d8172559
11 changed files with 225 additions and 20 deletions
31
api.go
31
api.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -14,8 +13,9 @@ import (
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/Luzifer/go-latestver/internal/badge"
|
||||||
"github.com/Luzifer/go-latestver/internal/config"
|
"github.com/Luzifer/go-latestver/internal/config"
|
||||||
"github.com/Luzifer/go-latestver/internal/database"
|
"github.com/Luzifer/go-latestver/internal/database"
|
||||||
"github.com/Luzifer/go-latestver/internal/fetcher"
|
"github.com/Luzifer/go-latestver/internal/fetcher"
|
||||||
|
@ -58,7 +58,7 @@ func catalogEntryToAPICatalogEntry(ce database.CatalogEntry) (apiCatalogEntry, e
|
||||||
return apiCatalogEntry{CatalogEntry: ce, CatalogMeta: *cm}, nil
|
return apiCatalogEntry{CatalogEntry: ce, CatalogMeta: *cm}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBadgeRedirect(w http.ResponseWriter, r *http.Request) {
|
func handleBadge(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
compare = r.FormValue("compare")
|
compare = r.FormValue("compare")
|
||||||
vars = mux.Vars(r)
|
vars = mux.Vars(r)
|
||||||
|
@ -82,14 +82,11 @@ func handleBadgeRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
color = "red"
|
color = "red"
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := url.Parse(cfg.BadgeGenInstance)
|
svg := badge.Create(ce.Key(), cm.CurrentVersion, color)
|
||||||
if err != nil {
|
w.Header().Add("Content-Type", "image/svg+xml")
|
||||||
http.Error(w, "Misconfigured BadgeGenInstance", http.StatusInternalServerError)
|
if _, err = w.Write(svg); err != nil {
|
||||||
return
|
logrus.WithError(err).Error("writing SVG response")
|
||||||
}
|
}
|
||||||
|
|
||||||
target.Path = path.Join(target.Path, "static", ce.Key(), cm.CurrentVersion, color)
|
|
||||||
http.Redirect(w, r, target.String(), http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCatalogGet(w http.ResponseWriter, r *http.Request) {
|
func handleCatalogGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -106,14 +103,14 @@ func handleCatalogGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ae, err := catalogEntryToAPICatalogEntry(ce)
|
ae, err := catalogEntryToAPICatalogEntry(ce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Unable to fetch catalog data")
|
logrus.WithError(err).Error("Unable to fetch catalog data")
|
||||||
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
|
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if err = json.NewEncoder(w).Encode(ae); err != nil {
|
if err = json.NewEncoder(w).Encode(ae); err != nil {
|
||||||
log.WithError(err).Error("Unable to encode catalog entry")
|
logrus.WithError(err).Error("Unable to encode catalog entry")
|
||||||
http.Error(w, "Unable to encode catalog meta", http.StatusInternalServerError)
|
http.Error(w, "Unable to encode catalog meta", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -133,7 +130,7 @@ func handleCatalogGetVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
cm, err := storage.Catalog.GetMeta(&ce)
|
cm, err := storage.Catalog.GetMeta(&ce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Unable to fetch catalog meta")
|
logrus.WithError(err).Error("Unable to fetch catalog meta")
|
||||||
http.Error(w, "Unable to fetch catalog meta", http.StatusInternalServerError)
|
http.Error(w, "Unable to fetch catalog meta", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -150,7 +147,7 @@ func handleCatalogList(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
|
||||||
ae, err := catalogEntryToAPICatalogEntry(ce)
|
ae, err := catalogEntryToAPICatalogEntry(ce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Unable to fetch catalog data")
|
logrus.WithError(err).Error("Unable to fetch catalog data")
|
||||||
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
|
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -162,7 +159,7 @@ func handleCatalogList(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if err := json.NewEncoder(w).Encode(out); err != nil {
|
if err := json.NewEncoder(w).Encode(out); err != nil {
|
||||||
log.WithError(err).Error("Unable to encode catalog entry list")
|
logrus.WithError(err).Error("Unable to encode catalog entry list")
|
||||||
http.Error(w, "Unable to encode catalog meta", http.StatusInternalServerError)
|
http.Error(w, "Unable to encode catalog meta", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -184,7 +181,7 @@ func handleLog(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if err = json.NewEncoder(w).Encode(logs); err != nil {
|
if err = json.NewEncoder(w).Encode(logs); err != nil {
|
||||||
log.WithError(err).Error("Unable to encode logs")
|
logrus.WithError(err).Error("Unable to encode logs")
|
||||||
http.Error(w, "Unable to encode logs", http.StatusInternalServerError)
|
http.Error(w, "Unable to encode logs", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -229,7 +226,7 @@ func handleLogFeed(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
|
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
|
||||||
if err = feed.WriteRss(w); err != nil {
|
if err = feed.WriteRss(w); err != nil {
|
||||||
log.WithError(err).Error("Unable to render RSS")
|
logrus.WithError(err).Error("Unable to render RSS")
|
||||||
http.Error(w, "Unable to render RSS", http.StatusInternalServerError)
|
http.Error(w, "Unable to render RSS", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -10,11 +10,15 @@ require (
|
||||||
github.com/antchfx/xpath v1.2.5
|
github.com/antchfx/xpath v1.2.5
|
||||||
github.com/blang/semver/v4 v4.0.0
|
github.com/blang/semver/v4 v4.0.0
|
||||||
github.com/go-git/go-git/v5 v5.11.0
|
github.com/go-git/go-git/v5 v5.11.0
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
github.com/gorilla/feeds v1.1.2
|
github.com/gorilla/feeds v1.1.2
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/tdewolff/minify/v2 v2.20.19
|
||||||
|
golang.org/x/image v0.15.0
|
||||||
golang.org/x/net v0.22.0
|
golang.org/x/net v0.22.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/mysql v1.5.5
|
gorm.io/driver/mysql v1.5.5
|
||||||
|
@ -96,6 +100,7 @@ require (
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.0 // indirect
|
github.com/prometheus/client_model v0.6.0 // indirect
|
||||||
github.com/prometheus/common v0.50.0 // indirect
|
github.com/prometheus/common v0.50.0 // indirect
|
||||||
|
@ -104,6 +109,7 @@ require (
|
||||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||||
github.com/spf13/cobra v1.8.0 // indirect
|
github.com/spf13/cobra v1.8.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/tdewolff/parse/v2 v2.7.12 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xlab/treeprint v1.2.0 // indirect
|
github.com/xlab/treeprint v1.2.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
|
|
14
go.sum
14
go.sum
|
@ -140,6 +140,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
@ -303,8 +305,9 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
@ -314,6 +317,13 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
|
||||||
|
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||||
|
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
|
||||||
|
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
||||||
|
@ -351,6 +361,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
|
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||||
|
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
|
BIN
internal/badge/DejaVuSans.ttf
Normal file
BIN
internal/badge/DejaVuSans.ttf
Normal file
Binary file not shown.
6
internal/badge/assets.go
Normal file
6
internal/badge/assets.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package badge
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed badge.svg.tpl DejaVuSans.ttf
|
||||||
|
var assets embed.FS
|
73
internal/badge/badge.go
Normal file
73
internal/badge/badge.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Package badge contains a SVG-badge generator creating a badge from
|
||||||
|
// title, text and color
|
||||||
|
package badge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/tdewolff/minify/v2"
|
||||||
|
"github.com/tdewolff/minify/v2/svg"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
xSpacing = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
colorNameBlue = "blue"
|
||||||
|
colorNameBrightGreen = "brightgreen"
|
||||||
|
colorNameGray = "gray"
|
||||||
|
colorNameGreen = "green"
|
||||||
|
colorNameLightGray = "lightgray"
|
||||||
|
colorNameOrange = "orange"
|
||||||
|
colorNameRed = "red"
|
||||||
|
colorNameYellow = "yellow"
|
||||||
|
colorNameYellowGreen = "yellowgreen"
|
||||||
|
)
|
||||||
|
|
||||||
|
var colorList = map[string]string{
|
||||||
|
colorNameBlue: "007ec6",
|
||||||
|
colorNameBrightGreen: "4c1",
|
||||||
|
colorNameGray: "555",
|
||||||
|
colorNameGreen: "97CA00",
|
||||||
|
colorNameLightGray: "9f9f9f",
|
||||||
|
colorNameOrange: "fe7d37",
|
||||||
|
colorNameRed: "e05d44",
|
||||||
|
colorNameYellow: "dfb317",
|
||||||
|
colorNameYellowGreen: "a4a61d",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create renders the badge and returns the SVG in minified but
|
||||||
|
// uncompressed form
|
||||||
|
func Create(title, text, color string) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
titleW, _ := calculateTextWidth(title)
|
||||||
|
textW, _ := calculateTextWidth(text)
|
||||||
|
|
||||||
|
width := titleW + textW + 4*xSpacing //nolint:gomnd
|
||||||
|
|
||||||
|
t, _ := assets.ReadFile("badge.svg.tpl")
|
||||||
|
tpl, _ := template.New("svg").Parse(string(t))
|
||||||
|
|
||||||
|
if c, ok := colorList[color]; ok {
|
||||||
|
color = c
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = tpl.Execute(&buf, map[string]any{
|
||||||
|
"Width": width,
|
||||||
|
"TitleWidth": titleW + 2*xSpacing,
|
||||||
|
"Title": title,
|
||||||
|
"Text": text,
|
||||||
|
"TitleAnchor": titleW/2 + xSpacing,
|
||||||
|
"TextAnchor": titleW + textW/2 + 3*xSpacing,
|
||||||
|
"Color": color,
|
||||||
|
})
|
||||||
|
|
||||||
|
m := minify.New()
|
||||||
|
m.AddFunc("image/svg+xml", svg.Minify)
|
||||||
|
|
||||||
|
out, _ := m.Bytes("image/svg+xml", buf.Bytes())
|
||||||
|
return out
|
||||||
|
}
|
20
internal/badge/badge.svg.tpl
Normal file
20
internal/badge/badge.svg.tpl
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ .Width }}" height="20">
|
||||||
|
<linearGradient id="b" x2="0" y2="100%">
|
||||||
|
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||||
|
<stop offset="1" stop-opacity=".1" />
|
||||||
|
</linearGradient>
|
||||||
|
<mask id="a">
|
||||||
|
<rect width="{{ .Width }}" height="20" rx="3" fill="#fff" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#a)">
|
||||||
|
<path fill="#555" d="M0 0 h{{ .TitleWidth }} v20 H0 z" />
|
||||||
|
<path fill="#{{ .Color }}" d="M{{ .TitleWidth }} 0 H{{ .Width }} v20 H{{ .TitleWidth }} z" />
|
||||||
|
<path fill="url(#b)" d="M0 0 h{{ .Width }} v20 H0 z" />
|
||||||
|
</g>
|
||||||
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||||
|
<text x="{{ .TitleAnchor }}" y="15" fill="#010101" fill-opacity=".3">{{ .Title }}</text>
|
||||||
|
<text x="{{ .TitleAnchor }}" y="14" >{{ .Title }}</text>
|
||||||
|
<text x="{{ .TextAnchor }}" y="15" fill="#010101" fill-opacity=".3">{{ .Text }}</text>
|
||||||
|
<text x="{{ .TextAnchor }}" y="14" >{{ .Text }}</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
24
internal/badge/badge_test.go
Normal file
24
internal/badge/badge_test.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package badge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseBadgeTemplate(t *testing.T) {
|
||||||
|
raw, err := assets.ReadFile("badge.svg.tpl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = template.New("svg").Parse(string(raw))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderBadge(t *testing.T) {
|
||||||
|
badge := Create("golang", "test", "green")
|
||||||
|
assert.Equal(t,
|
||||||
|
[]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="90" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="90" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h53v20H0z"/><path fill="#97ca00" d="M53 0H90v20H53z"/><path fill="url(#b)" d="M0 0h90v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="26" y="15" fill="#010101" fill-opacity=".3">golang</text><text x="26" y="14">golang</text><text x="71" y="15" fill="#010101" fill-opacity=".3">test</text><text x="71" y="14">test</text></g></svg>`),
|
||||||
|
badge)
|
||||||
|
}
|
41
internal/badge/font.go
Normal file
41
internal/badge/font.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package badge
|
||||||
|
|
||||||
|
// Some of this code (namely the code for computing the
|
||||||
|
// width of a string in a given font) was copied from
|
||||||
|
// code.google.com/p/freetype-go/freetype/ which includes
|
||||||
|
// the following copyright notice:
|
||||||
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fontSize = 11
|
||||||
|
)
|
||||||
|
|
||||||
|
func calculateTextWidth(text string) (int, error) {
|
||||||
|
binFont, _ := assets.ReadFile("DejaVuSans.ttf")
|
||||||
|
font, err := truetype.Parse(binFont)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "parsing truetype font")
|
||||||
|
}
|
||||||
|
|
||||||
|
scale := fontSize / float64(font.FUnitsPerEm())
|
||||||
|
|
||||||
|
width := 0
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rune := range text {
|
||||||
|
fUnitsPerEm := fixed.Int26_6(font.FUnitsPerEm())
|
||||||
|
index := font.Index(rune)
|
||||||
|
if hasPrev {
|
||||||
|
width += int(font.Kern(fUnitsPerEm, prev, index))
|
||||||
|
}
|
||||||
|
width += int(font.HMetric(fUnitsPerEm, index).AdvanceWidth)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(float64(width) * scale), nil
|
||||||
|
}
|
27
internal/badge/font_test.go
Normal file
27
internal/badge/font_test.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package badge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmbeddedFontHash(t *testing.T) {
|
||||||
|
// Check the embedded font did not change
|
||||||
|
font, err := assets.ReadFile("DejaVuSans.ttf")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t,
|
||||||
|
"3fdf69cabf06049ea70a00b5919340e2ce1e6d02b0cc3c4b44fb6801bd1e0d22",
|
||||||
|
fmt.Sprintf("%x", sha256.Sum256(font)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringLength(t *testing.T) {
|
||||||
|
// As the font is embedded into the source the length calculation should not change
|
||||||
|
w, err := calculateTextWidth("Test 123 öäüß … !@#%&")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 138, w)
|
||||||
|
}
|
3
main.go
3
main.go
|
@ -20,7 +20,6 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg = struct {
|
cfg = struct {
|
||||||
BadgeGenInstance string `flag:"badge-gen-instance" default:"https://badges.fyi/" description:"Where to find the badge-gen instance to use badges from"`
|
|
||||||
BaseURL string `flag:"base-url" default:"https://example.com/" description:"Base-URL the application is reachable at"`
|
BaseURL string `flag:"base-url" default:"https://example.com/" description:"Base-URL the application is reachable at"`
|
||||||
Config string `flag:"config,c" default:"config.yaml" description:"Configuration file with catalog entries"`
|
Config string `flag:"config,c" default:"config.yaml" description:"Configuration file with catalog entries"`
|
||||||
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
|
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
|
||||||
|
@ -108,7 +107,7 @@ func main() {
|
||||||
router.HandleFunc("/v1/catalog/{name}/{tag}/version", handleCatalogGetVersion).Methods(http.MethodGet)
|
router.HandleFunc("/v1/catalog/{name}/{tag}/version", handleCatalogGetVersion).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/v1/log", handleLog).Methods(http.MethodGet)
|
router.HandleFunc("/v1/log", handleLog).Methods(http.MethodGet)
|
||||||
|
|
||||||
router.HandleFunc("/{name}/{tag}.svg", handleBadgeRedirect).Methods(http.MethodGet).Name("catalog-entry-badge")
|
router.HandleFunc("/{name}/{tag}.svg", handleBadge).Methods(http.MethodGet).Name("catalog-entry-badge")
|
||||||
router.HandleFunc("/{name}/{tag}/log.rss", handleLogFeed).Methods(http.MethodGet).Name("catalog-entry-rss")
|
router.HandleFunc("/{name}/{tag}/log.rss", handleLogFeed).Methods(http.MethodGet).Name("catalog-entry-rss")
|
||||||
router.HandleFunc("/log.rss", handleLogFeed).Methods(http.MethodGet).Name("log-rss")
|
router.HandleFunc("/log.rss", handleLogFeed).Methods(http.MethodGet).Name("log-rss")
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue