137 lines
2.6 KiB
Go
137 lines
2.6 KiB
Go
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
|
|
}
|