diff --git a/templates/funcs.go b/templates/funcs.go new file mode 100644 index 0000000..7cc5864 --- /dev/null +++ b/templates/funcs.go @@ -0,0 +1,152 @@ +package templates + +import ( + "encoding/json" + "fmt" + "html/template" + "slices" + "strconv" + "strings" + "time" +) + +func dict(values ...any) (map[string]any, error) { + if len(values)%2 != 0 { + return nil, fmt.Errorf("dict expects even number of values, got %d", len(values)) + } + out := make(map[string]any, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].(string) + if !ok { + return nil, fmt.Errorf("dict key at index %d is not a string", i) + } + out[key] = values[i+1] + } + return out, nil +} + +func jsonAttr(v any) (template.HTMLAttr, error) { + b, err := json.Marshal(v) + if err != nil { + return "", fmt.Errorf("json marshal: %w", err) + } + return template.HTMLAttr(b), nil +} + +func genresParams(genres []int) string { + if len(genres) == 0 { + return "" + } + var b strings.Builder + for i, g := range genres { + if i > 0 { + b.WriteByte('&') + } + b.WriteString("genres=") + b.WriteString(strconv.Itoa(g)) + } + return b.String() +} + +func hasGenre(id int, genres []int) bool { + return slices.Contains(genres, id) +} + +func div(a, b float64) float64 { + if b == 0 { + return 0 + } + return a / b +} + +func ceilDiv(a, b int) int { + if b == 0 { + return 0 + } + return (a + b - 1) / b +} + +func idiv(a, b int) int { + if b == 0 { + return 0 + } + return a / b +} + +func atoi(v any) int { + s, ok := v.(string) + if !ok { + return 0 + } + n, err := strconv.Atoi(s) + if err != nil { + return 0 + } + return n +} + +func seq(v any) []int { + count := 0 + switch n := v.(type) { + case int: + count = n + case int64: + if n > int64(^uint(0)>>1) { + count = 0 + } else { + count = int(n) + } + } + if count <= 0 { + return []int{} + } + res := make([]int, count) + for i := 0; i < count; i++ { + res[i] = i + } + return res +} + +func toInt(v any) int { + switch n := v.(type) { + case int: + return n + case int64: + if n > int64(^uint(0)>>1) { + return 0 + } + return int(n) + case float64: + if n > float64(int(^uint(0)>>1)) { + return 0 + } + return int(n) + case string: + i, err := strconv.Atoi(n) + if err != nil { + return 0 + } + return i + default: + return 0 + } +} + +func percent(current, total float64) float64 { + if total == 0 { + return 0 + } + return (current / total) * 100 +} + +func formatDate(dateStr string) string { + t, err := time.Parse(time.RFC3339, dateStr) + if err != nil { + t, err = time.Parse("2006-01-02T15:04:05+00:00", dateStr) + if err != nil { + return dateStr + } + } + return t.Format("Jan 2, 2006") +} + diff --git a/templates/renderer.go b/templates/renderer.go index ed4d78e..7373e38 100644 --- a/templates/renderer.go +++ b/templates/renderer.go @@ -1,7 +1,6 @@ package templates import ( - "encoding/json" "errors" "fmt" "html/template" @@ -9,10 +8,7 @@ import ( "io/fs" "net/http" "path" - "slices" - "strconv" "strings" - "time" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/render" @@ -144,144 +140,3 @@ func (r *Renderer) ExecuteFragment(w io.Writer, name string, block string, data } return tmpl.ExecuteTemplate(w, block, data) } - -func dict(values ...any) (map[string]any, error) { - if len(values)%2 != 0 { - return nil, fmt.Errorf("dict expects even number of values, got %d", len(values)) - } - out := make(map[string]any, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].(string) - if !ok { - return nil, fmt.Errorf("dict key at index %d is not a string", i) - } - out[key] = values[i+1] - } - return out, nil -} - -func jsonAttr(v any) (template.HTMLAttr, error) { - b, err := json.Marshal(v) - if err != nil { - return "", fmt.Errorf("json marshal: %w", err) - } - return template.HTMLAttr(b), nil -} - -func genresParams(genres []int) string { - if len(genres) == 0 { - return "" - } - var b strings.Builder - for i, g := range genres { - if i > 0 { - b.WriteByte('&') - } - b.WriteString("genres=") - b.WriteString(strconv.Itoa(g)) - } - return b.String() -} - -func hasGenre(id int, genres []int) bool { - return slices.Contains(genres, id) -} - -func div(a, b float64) float64 { - if b == 0 { - return 0 - } - return a / b -} - -func ceilDiv(a, b int) int { - if b == 0 { - return 0 - } - return (a + b - 1) / b -} - -func idiv(a, b int) int { - if b == 0 { - return 0 - } - return a / b -} - -func atoi(v any) int { - s, ok := v.(string) - if !ok { - return 0 - } - n, err := strconv.Atoi(s) - if err != nil { - return 0 - } - return n -} - -func seq(v any) []int { - count := 0 - switch n := v.(type) { - case int: - count = n - case int64: - if n > int64(^uint(0)>>1) { - count = 0 - } else { - count = int(n) - } - } - if count <= 0 { - return []int{} - } - res := make([]int, count) - for i := 0; i < count; i++ { - res[i] = i - } - return res -} - -func toInt(v any) int { - switch n := v.(type) { - case int: - return n - case int64: - if n > int64(^uint(0)>>1) { - return 0 - } - return int(n) - case float64: - if n > float64(int(^uint(0)>>1)) { - return 0 - } - return int(n) - case string: - i, err := strconv.Atoi(n) - if err != nil { - return 0 - } - return i - default: - return 0 - } -} - -func percent(current, total float64) float64 { - if total == 0 { - return 0 - } - return (current / total) * 100 -} - -func formatDate(dateStr string) string { - t, err := time.Parse(time.RFC3339, dateStr) - if err != nil { - t, err = time.Parse("2006-01-02T15:04:05+00:00", dateStr) - if err != nil { - return dateStr - } - } - return t.Format("Jan 2, 2006") -} -