mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
Refactor: comprehensive cleanup and modularization
- Extracted common JSON parsing helpers into internal/jsonutil - Removed duplicated helper functions from provider packages - Removed dead code in internal/app/app.go and downloader.go - Replaced deprecated strings.Title with jsonutil.TitleCase - Added graceful shutdown with signal handling in main.go - Split monolithic cmd/rip/main.go into args.go, helpers.go, lastfm.go, search.go
This commit is contained in:
131
internal/jsonutil/jsonutil.go
Normal file
131
internal/jsonutil/jsonutil.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Package jsonutil provides shared helpers for working with untyped JSON
|
||||
// values (map[string]any) that come from API responses across all providers.
|
||||
package jsonutil
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringFromAny converts a dynamic JSON value to a string.
|
||||
// Numeric types are formatted without trailing zeroes.
|
||||
func StringFromAny(v any) string {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return t
|
||||
case int:
|
||||
return strconv.Itoa(t)
|
||||
case int64:
|
||||
return strconv.FormatInt(t, 10)
|
||||
case float64:
|
||||
return strconv.FormatFloat(t, 'f', -1, 64)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// IntFromAny converts a dynamic JSON value to an int.
|
||||
// Handles int, int64, float64, and string types.
|
||||
func IntFromAny(v any) int {
|
||||
switch t := v.(type) {
|
||||
case int:
|
||||
return t
|
||||
case int32:
|
||||
return int(t)
|
||||
case int64:
|
||||
return int(t)
|
||||
case float64:
|
||||
return int(t)
|
||||
case string:
|
||||
i, _ := strconv.Atoi(strings.TrimSpace(t))
|
||||
return i
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// FloatFromAny converts a dynamic JSON value to a float64.
|
||||
func FloatFromAny(v any) float64 {
|
||||
switch t := v.(type) {
|
||||
case float64:
|
||||
return t
|
||||
case int:
|
||||
return float64(t)
|
||||
case int64:
|
||||
return float64(t)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// BoolFromAny converts a dynamic JSON value to a bool.
|
||||
// Supports bool, string ("true"/"1"/"yes"), and numeric types.
|
||||
func BoolFromAny(v any) bool {
|
||||
switch t := v.(type) {
|
||||
case bool:
|
||||
return t
|
||||
case string:
|
||||
l := strings.ToLower(strings.TrimSpace(t))
|
||||
return l == "1" || l == "true" || l == "yes"
|
||||
case int:
|
||||
return t != 0
|
||||
case int64:
|
||||
return t != 0
|
||||
case float64:
|
||||
return t != 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// FirstNonEmpty returns the first string in items that is non-empty after trimming.
|
||||
func FirstNonEmpty(items ...string) string {
|
||||
for _, item := range items {
|
||||
if strings.TrimSpace(item) != "" {
|
||||
return strings.TrimSpace(item)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// NestedMap returns the value at m[key] as a map[string]any.
|
||||
// Returns an empty map if the key is missing or the value is not a map.
|
||||
func NestedMap(m map[string]any, key string) map[string]any {
|
||||
v, ok := m[key].(map[string]any)
|
||||
if !ok {
|
||||
return map[string]any{}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// NestedAny traverses a chain of map keys and returns the final value.
|
||||
func NestedAny(v map[string]any, keys ...string) any {
|
||||
cur := any(v)
|
||||
for _, key := range keys {
|
||||
m, ok := cur.(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cur = m[key]
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
// NestedString traverses a chain of map keys and returns the final value as a string.
|
||||
func NestedString(v map[string]any, keys ...string) string {
|
||||
return StringFromAny(NestedAny(v, keys...))
|
||||
}
|
||||
|
||||
// TitleCase capitalises the first rune of s. This is a simple ASCII replacement
|
||||
// for the deprecated strings.Title function, suitable for source names like
|
||||
// "qobuz" → "Qobuz".
|
||||
func TitleCase(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r := []rune(s)
|
||||
if r[0] >= 'a' && r[0] <= 'z' {
|
||||
r[0] -= 'a' - 'A'
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
Reference in New Issue
Block a user