package web
import (
"fmt"
"html/template"
"net/url"
"strings"
"time"
)
func templateFuncs() template.FuncMap {
return template.FuncMap{
"repoURL": repoURL,
"manifestURL": manifestURL,
"pull": pullAddress,
"size": formatSize,
"timestamp": timestamp,
"age": displayAge,
"join": strings.Join,
}
}
// sanitizeURLPath escapes each path segment separately, preserving slashes in
// names such as group/repository. Appends a trailing slash.
func sanitizeURLPath(path string) string {
parts := strings.Split(path, "/")
for i := range parts {
parts[i] = url.PathEscape(parts[i])
}
return strings.Join(parts, "/") + "/"
}
func repoURL(name string) string {
return "/r/" + sanitizeURLPath(name)
}
func manifestURL(repository, digest, tag string) string {
path := repoURL(repository) + "m/" + sanitizeURLPath(digest)
if tag != "" {
path += "?" + url.Values{"tag": {tag}}.Encode()
}
return path
}
func pullAddress(host, repository, tag, digest string) string {
address := strings.TrimSuffix(host, "/") + "/" + repository
if tag != "" {
address += ":" + tag
}
if digest != "" {
address += "@" + digest
}
return address
}
func formatSize(value int64) string {
if value >= 1024*1024 {
return fmt.Sprintf("%.1fMB", float64(value)/(1024*1024))
}
if value >= 1024 {
return fmt.Sprintf("%.1fKB", float64(value)/1024)
}
return fmt.Sprintf("%dB", value)
}
func timestamp(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Format(time.DateTime + " MST")
}
func displayAge(updated time.Time, frozen string) string {
if frozen != "" {
return frozen
}
if updated.IsZero() {
return "unknown"
}
elapsed := time.Since(updated)
if elapsed < time.Minute {
return "just now"
}
for _, unit := range []struct {
duration time.Duration
label string
}{
{365 * 24 * time.Hour, "year"},
{30 * 24 * time.Hour, "month"},
{24 * time.Hour, "day"},
{time.Hour, "hour"},
{time.Minute, "minute"},
} {
if count := int(elapsed / unit.duration); count > 0 {
suffix := "s"
if count == 1 {
suffix = ""
}
return fmt.Sprintf("%d %s%s ago", count, unit.label, suffix)
}
}
return "just now"
}