feat(format-servers): add json format option
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrFormatNotRecognized = errors.New("format is not recognized")
|
|
||||||
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||||
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
|
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
|
||||||
)
|
)
|
||||||
@@ -43,7 +42,7 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
providersToFormat[provider] = new(bool)
|
providersToFormat[provider] = new(bool)
|
||||||
}
|
}
|
||||||
flagSet := flag.NewFlagSet("format-servers", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("format-servers", flag.ExitOnError)
|
||||||
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown' or 'json'")
|
||||||
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||||
titleCaser := cases.Title(language.English)
|
titleCaser := cases.Title(language.English)
|
||||||
for _, provider := range allProviderFlags {
|
for _, provider := range allProviderFlags {
|
||||||
@@ -53,9 +52,7 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if format != "markdown" {
|
// Note the format is validated by storage.Format
|
||||||
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify only one provider is set to be formatted.
|
// Verify only one provider is set to be formatted.
|
||||||
var providers []string
|
var providers []string
|
||||||
@@ -87,7 +84,10 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
return fmt.Errorf("creating servers storage: %w", err)
|
return fmt.Errorf("creating servers storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
formatted := storage.FormatToMarkdown(providerToFormat)
|
formatted, err := storage.Format(providerToFormat, format)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("formatting servers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
output = filepath.Clean(output)
|
output = filepath.Clean(output)
|
||||||
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -90,8 +91,11 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
|
|||||||
return "| " + strings.Join(fields, " | ") + " |"
|
return "| " + strings.Join(fields, " | ") + " |"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Servers) ToMarkdown(vpnProvider string) (markdown string) {
|
func (s *Servers) toMarkdown(vpnProvider string) (formatted string, err error) {
|
||||||
headers := getMarkdownHeaders(vpnProvider)
|
headers, err := getMarkdownHeaders(vpnProvider)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting markdown headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
legend := markdownTableHeading(headers...)
|
legend := markdownTableHeading(headers...)
|
||||||
|
|
||||||
@@ -100,63 +104,67 @@ func (s *Servers) ToMarkdown(vpnProvider string) (markdown string) {
|
|||||||
entries[i] = server.ToMarkdown(headers...)
|
entries[i] = server.ToMarkdown(headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
markdown = legend + "\n" +
|
formatted = legend + "\n" +
|
||||||
strings.Join(entries, "\n") + "\n"
|
strings.Join(entries, "\n") + "\n"
|
||||||
return markdown
|
return formatted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMarkdownHeaders(vpnProvider string) (headers []string) {
|
var (
|
||||||
|
ErrMarkdownHeadersNotDefined = errors.New("markdown headers not defined")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getMarkdownHeaders(vpnProvider string) (headers []string, err error) {
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Airvpn:
|
case providers.Airvpn:
|
||||||
return []string{regionHeader, countryHeader, cityHeader, vpnHeader,
|
return []string{regionHeader, countryHeader, cityHeader, vpnHeader,
|
||||||
udpHeader, tcpHeader, hostnameHeader, nameHeader}
|
udpHeader, tcpHeader, hostnameHeader, nameHeader}, nil
|
||||||
case providers.Cyberghost:
|
case providers.Cyberghost:
|
||||||
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Expressvpn:
|
case providers.Expressvpn:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Fastestvpn:
|
case providers.Fastestvpn:
|
||||||
return []string{countryHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.HideMyAss:
|
case providers.HideMyAss:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Ipvanish:
|
case providers.Ipvanish:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Ivpn:
|
case providers.Ivpn:
|
||||||
return []string{countryHeader, cityHeader, ispHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, ispHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Mullvad:
|
case providers.Mullvad:
|
||||||
return []string{countryHeader, cityHeader, ispHeader, ownedHeader, hostnameHeader, vpnHeader}
|
return []string{countryHeader, cityHeader, ispHeader, ownedHeader, hostnameHeader, vpnHeader}, nil
|
||||||
case providers.Nordvpn:
|
case providers.Nordvpn:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, vpnHeader, categoriesHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, vpnHeader, categoriesHeader}, nil
|
||||||
case providers.Perfectprivacy:
|
case providers.Perfectprivacy:
|
||||||
return []string{cityHeader, tcpHeader, udpHeader}
|
return []string{cityHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Privado:
|
case providers.Privado:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}, nil
|
||||||
case providers.PrivateInternetAccess:
|
case providers.PrivateInternetAccess:
|
||||||
return []string{regionHeader, hostnameHeader, nameHeader, tcpHeader, udpHeader, portForwardHeader}
|
return []string{regionHeader, hostnameHeader, nameHeader, tcpHeader, udpHeader, portForwardHeader}, nil
|
||||||
case providers.Privatevpn:
|
case providers.Privatevpn:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader}, nil
|
||||||
case providers.Protonvpn:
|
case providers.Protonvpn:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, vpnHeader,
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, vpnHeader,
|
||||||
freeHeader, portForwardHeader, secureHeader, torHeader}
|
freeHeader, portForwardHeader, secureHeader, torHeader}, nil
|
||||||
case providers.Purevpn:
|
case providers.Purevpn:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.SlickVPN:
|
case providers.SlickVPN:
|
||||||
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader}
|
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader}, nil
|
||||||
case providers.Surfshark:
|
case providers.Surfshark:
|
||||||
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader,
|
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader,
|
||||||
vpnHeader, multiHopHeader, tcpHeader, udpHeader}
|
vpnHeader, multiHopHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Torguard:
|
case providers.Torguard:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.VPNSecure:
|
case providers.VPNSecure:
|
||||||
return []string{regionHeader, cityHeader, hostnameHeader, premiumHeader}
|
return []string{regionHeader, cityHeader, hostnameHeader, premiumHeader}, nil
|
||||||
case providers.VPNUnlimited:
|
case providers.VPNUnlimited:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader, freeHeader, streamHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader, freeHeader, streamHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Vyprvpn:
|
case providers.Vyprvpn:
|
||||||
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Wevpn:
|
case providers.Wevpn:
|
||||||
return []string{cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{cityHeader, hostnameHeader, tcpHeader, udpHeader}, nil
|
||||||
case providers.Windscribe:
|
case providers.Windscribe:
|
||||||
return []string{regionHeader, cityHeader, hostnameHeader, vpnHeader}
|
return []string{regionHeader, cityHeader, hostnameHeader, vpnHeader}, nil
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil, fmt.Errorf("%w: for %s", ErrMarkdownHeadersNotDefined, vpnProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,15 @@ func Test_Servers_ToMarkdown(t *testing.T) {
|
|||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
provider string
|
provider string
|
||||||
servers Servers
|
servers Servers
|
||||||
expectedMarkdown string
|
formatted string
|
||||||
|
errWrapped error
|
||||||
|
errMessage string
|
||||||
}{
|
}{
|
||||||
|
"unsupported_provider": {
|
||||||
|
provider: "unsupported",
|
||||||
|
errWrapped: ErrMarkdownHeadersNotDefined,
|
||||||
|
errMessage: "getting markdown headers: markdown headers not defined: for unsupported",
|
||||||
|
},
|
||||||
providers.Cyberghost: {
|
providers.Cyberghost: {
|
||||||
provider: providers.Cyberghost,
|
provider: providers.Cyberghost,
|
||||||
servers: Servers{
|
servers: Servers{
|
||||||
@@ -24,7 +31,7 @@ func Test_Servers_ToMarkdown(t *testing.T) {
|
|||||||
{Country: "b", TCP: true, Hostname: "xb"},
|
{Country: "b", TCP: true, Hostname: "xb"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
|
formatted: "| Country | Hostname | TCP | UDP |\n" +
|
||||||
"| --- | --- | --- | --- |\n" +
|
"| --- | --- | --- | --- |\n" +
|
||||||
"| a | `xa` | ❌ | ✅ |\n" +
|
"| a | `xa` | ❌ | ✅ |\n" +
|
||||||
"| b | `xb` | ✅ | ❌ |\n",
|
"| b | `xb` | ✅ | ❌ |\n",
|
||||||
@@ -37,7 +44,7 @@ func Test_Servers_ToMarkdown(t *testing.T) {
|
|||||||
{Country: "b", Hostname: "xb", VPN: vpn.OpenVPN, UDP: true},
|
{Country: "b", Hostname: "xb", VPN: vpn.OpenVPN, UDP: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedMarkdown: "| Country | Hostname | VPN | TCP | UDP |\n" +
|
formatted: "| Country | Hostname | VPN | TCP | UDP |\n" +
|
||||||
"| --- | --- | --- | --- | --- |\n" +
|
"| --- | --- | --- | --- | --- |\n" +
|
||||||
"| a | `xa` | openvpn | ✅ | ❌ |\n" +
|
"| a | `xa` | openvpn | ✅ | ❌ |\n" +
|
||||||
"| b | `xb` | openvpn | ❌ | ✅ |\n",
|
"| b | `xb` | openvpn | ❌ | ✅ |\n",
|
||||||
@@ -49,9 +56,13 @@ func Test_Servers_ToMarkdown(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
markdown := testCase.servers.ToMarkdown(testCase.provider)
|
markdown, err := testCase.servers.toMarkdown(testCase.provider)
|
||||||
|
|
||||||
assert.Equal(t, testCase.expectedMarkdown, markdown)
|
assert.Equal(t, testCase.formatted, markdown)
|
||||||
|
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||||
|
if testCase.errWrapped != nil {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package models
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -156,3 +157,29 @@ type Servers struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Servers []Server `json:"servers,omitempty"`
|
Servers []Server `json:"servers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrServersFormatNotSupported = errors.New("servers format not supported")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Servers) Format(vpnProvider, format string) (formatted string, err error) {
|
||||||
|
switch format {
|
||||||
|
case "markdown":
|
||||||
|
return s.toMarkdown(vpnProvider)
|
||||||
|
case "json":
|
||||||
|
return s.toJSON()
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("%w: %s", ErrServersFormatNotSupported, format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Servers) toJSON() (formatted string, err error) {
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(s.Servers)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("encoding servers: %w", err)
|
||||||
|
}
|
||||||
|
return buffer.String(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,19 +46,18 @@ func (s *Storage) GetServersCount(provider string) (count int) {
|
|||||||
return len(serversObject.Servers)
|
return len(serversObject.Servers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatToMarkdown Markdown formats the servers for the provider given
|
// Format formats the servers for the provider using the format given
|
||||||
// and returns the resulting string.
|
// and returns the resulting string.
|
||||||
func (s *Storage) FormatToMarkdown(provider string) (formatted string) {
|
func (s *Storage) Format(provider, format string) (formatted string, err error) {
|
||||||
if provider == providers.Custom {
|
if provider == providers.Custom {
|
||||||
return ""
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mergedMutex.RLock()
|
s.mergedMutex.RLock()
|
||||||
defer s.mergedMutex.RUnlock()
|
defer s.mergedMutex.RUnlock()
|
||||||
|
|
||||||
serversObject := s.getMergedServersObject(provider)
|
serversObject := s.getMergedServersObject(provider)
|
||||||
formatted = serversObject.ToMarkdown(provider)
|
return serversObject.Format(provider, format)
|
||||||
return formatted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServersCount returns the number of servers for the provider given.
|
// GetServersCount returns the number of servers for the provider given.
|
||||||
|
|||||||
Reference in New Issue
Block a user