Files
arrman/cmd/helpers.go
T
2026-03-12 22:13:57 +00:00

148 lines
3.1 KiB
Go

package cmd
import (
"fmt"
"sort"
"strings"
"unicode"
)
func parseAddArgs(args []string) (term, profile, rootfolder string, err error) {
var termParts []string
for i := 0; i < len(args); i++ {
switch args[i] {
case "--profile":
if i+1 >= len(args) {
return "", "", "", fmt.Errorf("--profile requires a value")
}
i++
profile = args[i]
case "--rootfolder":
if i+1 >= len(args) {
return "", "", "", fmt.Errorf("--rootfolder requires a value")
}
i++
rootfolder = args[i]
default:
termParts = append(termParts, args[i])
}
}
term = strings.Join(termParts, " ")
if term == "" {
return "", "", "", fmt.Errorf("usage: arrman <tv|film> add <term> --profile <id> --rootfolder <path>")
}
if profile == "" || rootfolder == "" {
return "", "", "", fmt.Errorf("both --profile and --rootfolder are required")
}
return term, profile, rootfolder, nil
}
func isAllDigits(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if !unicode.IsDigit(r) {
return false
}
}
return true
}
// levenshtein computes the edit distance between two strings.
func levenshtein(a, b string) int {
a = strings.ToLower(a)
b = strings.ToLower(b)
if a == b {
return 0
}
la, lb := len(a), len(b)
if la == 0 {
return lb
}
if lb == 0 {
return la
}
prev := make([]int, lb+1)
curr := make([]int, lb+1)
for j := range prev {
prev[j] = j
}
for i := 1; i <= la; i++ {
curr[0] = i
for j := 1; j <= lb; j++ {
cost := 1
if a[i-1] == b[j-1] {
cost = 0
}
curr[j] = min(curr[j-1]+1, min(prev[j]+1, prev[j-1]+cost))
}
prev, curr = curr, prev
}
return prev[lb]
}
// similarity returns a score between 0.0 and 1.0 indicating how similar two strings are.
func similarity(a, b string) float64 {
maxLen := len(a)
if len(b) > maxLen {
maxLen = len(b)
}
if maxLen == 0 {
return 1.0
}
return 1.0 - float64(levenshtein(a, b))/float64(maxLen)
}
// fuzzyThreshold is the minimum similarity score to consider a match.
const fuzzyThreshold = 0.6
// bestMatch holds a title and its similarity score.
type bestMatch struct {
Index int
Title string
Similarity float64
}
// rankMatches sorts candidates by similarity to the query and returns them.
func rankMatches(query string, titles []string) []bestMatch {
query = strings.ToLower(query)
matches := make([]bestMatch, len(titles))
for i, t := range titles {
// Boost score if the title contains the query as a substring
sim := similarity(query, strings.ToLower(t))
if strings.Contains(strings.ToLower(t), query) {
// Substring match gets a boost
boost := float64(len(query)) / float64(len(t))
if boost > sim {
sim = boost
}
if sim < 0.7 {
sim = 0.7
}
}
matches[i] = bestMatch{Index: i, Title: t, Similarity: sim}
}
sort.Slice(matches, func(i, j int) bool {
return matches[i].Similarity > matches[j].Similarity
})
return matches
}
func formatBytes(b int64) string {
const (
gb = 1024 * 1024 * 1024
mb = 1024 * 1024
)
switch {
case b >= gb:
return fmt.Sprintf("%.1f GB", float64(b)/float64(gb))
case b >= mb:
return fmt.Sprintf("%.1f MB", float64(b)/float64(mb))
default:
return fmt.Sprintf("%d B", b)
}
}