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 add --profile --rootfolder ") } 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) } }