package app import ( "fmt" "strings" ) func parseVPNLocationsOutput(raw string) ([]vpnLocationItem, error) { stdout := stripANSI(raw) type parsedLocation struct { iso string label string name string } var parsed []parsedLocation for _, ln := range strings.Split(stdout, "\n") { line := strings.TrimSpace(ln) if line == "" { continue } if strings.HasPrefix(line, "ISO ") { continue } if strings.HasPrefix(line, "VPN ") || strings.HasPrefix(line, "You can connect") { continue } parts := strings.Fields(line) if len(parts) < 4 { continue } iso := strings.ToUpper(strings.TrimSpace(parts[0])) ping := strings.TrimSpace(parts[len(parts)-1]) if len(iso) != 2 { continue } if !isDigitsOnly(ping) { continue } name := strings.Join(parts[1:len(parts)-1], " ") label := fmt.Sprintf("%s %s (%s ms)", iso, name, ping) parsed = append(parsed, parsedLocation{ iso: iso, label: label, name: name, }) } if len(parsed) == 0 { trimmed := strings.TrimSpace(stdout) if trimmed == "" { return nil, fmt.Errorf("empty list-locations output") } return nil, fmt.Errorf("no locations parsed from output") } namesByISO := map[string][][]string{} for _, it := range parsed { namesByISO[it.iso] = append(namesByISO[it.iso], strings.Fields(it.name)) } commonPrefixByISO := map[string][]string{} for iso, names := range namesByISO { if len(names) < 2 { continue } pfx := commonPrefixTokens(names) if len(pfx) > 0 { commonPrefixByISO[iso] = pfx } } var out []vpnLocationItem for _, it := range parsed { target := strings.ToUpper(strings.TrimSpace(it.iso)) if pfx := commonPrefixByISO[it.iso]; len(pfx) > 0 { tokens := strings.Fields(it.name) if len(tokens) > len(pfx) { city := strings.TrimSpace(strings.Join(tokens[len(pfx):], " ")) if city != "" { target = city } } } item, ok := normalizeVPNLocationItem(vpnLocationItem{ Label: it.label, ISO: it.iso, Target: target, }) if !ok { continue } out = append(out, item) } return out, nil } func commonPrefixTokens(items [][]string) []string { if len(items) == 0 { return nil } prefix := append([]string{}, items[0]...) for i := 1; i < len(items); i++ { row := items[i] n := len(prefix) if len(row) < n { n = len(row) } j := 0 for j < n { if !strings.EqualFold(prefix[j], row[j]) { break } j++ } prefix = prefix[:j] if len(prefix) == 0 { return nil } } return prefix } func isDigitsOnly(s string) bool { if s == "" { return false } for _, ch := range s { if ch < '0' || ch > '9' { return false } } return true }