Initial release
This commit is contained in:
+147
@@ -0,0 +1,147 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user