148 lines
3.1 KiB
Go
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)
|
|
}
|
|
}
|