diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9247b67e6f..3f7ac75802 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,11 +6,11 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: 1.21.x + go-version: 1.23.x - uses: actions/checkout@v4 - name: Install Requirements run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.60.3 make dep chmod +x ./ci_scripts/create-ip-aliases.sh ./ci_scripts/create-ip-aliases.sh @@ -25,11 +25,11 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: 1.21.x + go-version: 1.23.x - uses: actions/checkout@v4 - name: Install Requirements run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.60.3 make dep chmod +x ./ci_scripts/create-ip-aliases.sh ./ci_scripts/create-ip-aliases.sh @@ -44,12 +44,12 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: 1.21.x + go-version: 1.23.x - uses: actions/checkout@v4 - name: Install Requirements run: | choco install make - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 make dep - name: Testing run: | diff --git a/.golangci.yml b/.golangci.yml index a34efe3125..60bac5731a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -45,7 +45,7 @@ run: # output configuration options output: # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" - format: colored-line-number + formats: colored-line-number # print lines of code with issue, default is true print-issued-lines: true @@ -66,8 +66,7 @@ linters-settings: check-blank: true govet: # report about shadowed variables - check-shadowing: true - + shadow: true # Obtain type information from installed (to $GOPATH/pkg) package files: # golangci-lint will execute `go install -i` and `go test -i` for analyzed packages # before analyzing them. @@ -160,7 +159,6 @@ linters: - ineffassign - typecheck - gosec - - megacheck - misspell - nakedret enable-all: false diff --git a/Makefile b/Makefile index e95a5d7855..42830c67cc 100644 --- a/Makefile +++ b/Makefile @@ -158,7 +158,10 @@ install-static: ## Install `skywire-visor`, `skywire-cli`, `setup-node` lint: ## Run linters. Use make install-linters first golangci-lint --version - ${OPTS} golangci-lint run -c .golangci.yml ./... + ${OPTS} golangci-lint run -c .golangci.yml skywire.go #break down the linter run over smaller sections of the source code + ${OPTS} golangci-lint run -c .golangci.yml ./cmd/... + ${OPTS} golangci-lint run -c .golangci.yml ./pkg/... + ${OPTS} golangci-lint run -c .golangci.yml ./... lint-windows: ## Run linters. Use make install-linters-windows first powershell 'golangci-lint --version' diff --git a/README.md b/README.md index bde281b946..f5cbf795c1 100644 --- a/README.md +++ b/README.md @@ -549,3 +549,14 @@ yay --mflags " -p git.PKGBUILD " -S skywire 6. [ ̶I̶s̶s̶u̶e̶ ̶a̶ ̶p̶e̶r̶s̶o̶n̶a̶l̶ ̶G̶i̶t̶H̶u̶b̶ ̶a̶c̶c̶e̶s̶s̶ ̶t̶o̶k̶e̶n̶.̶](https://github.com/settings/tokens) 7. ̶R̶u̶n̶ ̶`̶G̶I̶T̶H̶U̶B̶_̶T̶O̶K̶E̶N̶=̶y̶o̶u̶r̶_̶t̶o̶k̶e̶n̶ ̶m̶a̶k̶e̶ ̶g̶i̶t̶h̶u̶b̶-̶r̶e̶l̶e̶a̶s̶e̶`̶ 8. [Check the created GitHub release.](https://github.com/skycoin/skywire/releases/) + + +## Dependency Graph + +made with [goda](https://github.com/loov/goda) + +``` +goda graph github.com/skycoin/skywire/... | dot -Tsvg -o docs/skywire-goda-graph.svg +``` + +![Dependency Graph](docs/skywire-goda-graph.svg "github.com/skycoin/skywire Dependency Graph") diff --git a/cmd/apps/skychat/commands/skychat.go b/cmd/apps/skychat/commands/skychat.go index b9d3c8dba0..d5ecf21abd 100644 --- a/cmd/apps/skychat/commands/skychat.go +++ b/cmd/apps/skychat/commands/skychat.go @@ -67,7 +67,7 @@ var RootCmd = &cobra.Command{ DisableSuggestions: true, DisableFlagsInUseLine: true, Version: buildinfo.Version(), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { appCl = app.NewClient(nil) defer appCl.Close() @@ -277,8 +277,10 @@ func sseHandler(w http.ResponseWriter, req *http.Request) { if !ok { return } - _, _ = fmt.Fprintf(w, "data: %s\n\n", msg) - f.Flush() + _, err := fmt.Fprintf(w, "data: %s\n\n", msg) + if err == nil { + f.Flush() + } case <-req.Context().Done(): fmt.Print("SSE connection were closed.") diff --git a/cmd/apps/skysocks-client/commands/skysocks-client.go b/cmd/apps/skysocks-client/commands/skysocks-client.go index 3c5332a00d..e844cc0332 100644 --- a/cmd/apps/skysocks-client/commands/skysocks-client.go +++ b/cmd/apps/skysocks-client/commands/skysocks-client.go @@ -60,7 +60,7 @@ var RootCmd = &cobra.Command{ DisableSuggestions: true, DisableFlagsInUseLine: true, Version: buildinfo.Version(), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { appCl := app.NewClient(nil) defer appCl.Close() diff --git a/cmd/apps/skysocks/commands/skysocks.go b/cmd/apps/skysocks/commands/skysocks.go index 223af2b02b..05a77e0351 100644 --- a/cmd/apps/skysocks/commands/skysocks.go +++ b/cmd/apps/skysocks/commands/skysocks.go @@ -44,7 +44,7 @@ var RootCmd = &cobra.Command{ DisableSuggestions: true, DisableFlagsInUseLine: true, Version: buildinfo.Version(), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { appCl := app.NewClient(nil) defer appCl.Close() diff --git a/cmd/apps/vpn-client/commands/vpn-client.go b/cmd/apps/vpn-client/commands/vpn-client.go index a93374dc96..ff1100d63b 100644 --- a/cmd/apps/vpn-client/commands/vpn-client.go +++ b/cmd/apps/vpn-client/commands/vpn-client.go @@ -55,7 +55,7 @@ var RootCmd = &cobra.Command{ DisableSuggestions: true, DisableFlagsInUseLine: true, Version: buildinfo.Version(), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { var directIPsCh, nonDirectIPsCh = make(chan net.IP, 100), make(chan net.IP, 100) defer close(directIPsCh) diff --git a/cmd/apps/vpn-server/commands/vpn-server.go b/cmd/apps/vpn-server/commands/vpn-server.go index 685a51a6c7..a9b9adad70 100644 --- a/cmd/apps/vpn-server/commands/vpn-server.go +++ b/cmd/apps/vpn-server/commands/vpn-server.go @@ -56,7 +56,7 @@ var RootCmd = &cobra.Command{ DisableSuggestions: true, DisableFlagsInUseLine: true, Version: buildinfo.Version(), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { appCl := app.NewClient(nil) defer appCl.Close() diff --git a/cmd/skywire-cli/commands/config/dmsghttp.go b/cmd/skywire-cli/commands/config/dmsghttp.go deleted file mode 100644 index 4bd97f3f5e..0000000000 --- a/cmd/skywire-cli/commands/config/dmsghttp.go +++ /dev/null @@ -1,97 +0,0 @@ -// Package cliconfig cmd/skywire-cli/commands/config/dmsghttp.go -package cliconfig - -import ( - "context" - "encoding/json" - "io" - "net/http" - "os" - - "github.com/spf13/cobra" - - "github.com/skycoin/skywire-utilities/pkg/cmdutil" - "github.com/skycoin/skywire-utilities/pkg/httputil" - "github.com/skycoin/skywire-utilities/pkg/logging" - "github.com/skycoin/skywire-utilities/pkg/skyenv" -) - -var dmsghttpPath string - -func init() { - updateCmd.AddCommand(dmsghttpCmd) - dmsghttpCmd.Flags().SortFlags = false - //TODO: fix path for non linux package defaults - dmsghttpCmd.Flags().StringVarP(&dmsghttpPath, "path", "p", "/opt/skywire/dmsghttp-config.json", "path of dmsghttp-config file, default is for pkg installation") -} - -var dmsghttpCmd = &cobra.Command{ - Use: "dmsghttp", - Short: "update dmsghttp-config.json file from config bootstrap service", - Run: func(cmd *cobra.Command, args []string) { //nolint - log := logging.MustGetLogger("dmsghttp_updater") - - ctx, cancel := cmdutil.SignalContext(context.Background(), log) - defer cancel() - go func() { - <-ctx.Done() - cancel() - os.Exit(1) - }() - dmsghttpConf, err := fetchDmsghttpConf() - if err != nil { - log.WithError(err).Error("Cannot fetching updated dmsghttp-config data") - } - - file, err := json.MarshalIndent(dmsghttpConf, "", " ") - if err != nil { - log.WithError(err).Error("Error accurs during marshal content to json file") - } - - err = os.WriteFile(dmsghttpPath, file, 0600) - if err != nil { - log.WithError(err).Errorf("Cannot save new dmsghttp-config.json file at %s", dmsghttpPath) - } - }, -} - -type dmsghttpConf struct { //nolint - Test httputil.DMSGHTTPConf `json:"test"` - Prod httputil.DMSGHTTPConf `json:"prod"` -} - -func fetchDmsghttpConf() (dmsghttpConf, error) { - var newConf dmsghttpConf - var prodConf httputil.DMSGHTTPConf - prodResp, err := http.Get(skyenv.ServiceConfAddr + "/dmsghttp") - if err != nil { - return newConf, err - } - defer prodResp.Body.Close() //nolint - body, err := io.ReadAll(prodResp.Body) - if err != nil { - return newConf, err - } - err = json.Unmarshal(body, &prodConf) - if err != nil { - return newConf, err - } - newConf.Prod = prodConf - - var testConf httputil.DMSGHTTPConf - testResp, err := http.Get(skyenv.TestServiceConfAddr + "/dmsghttp") - if err != nil { - return newConf, err - } - defer testResp.Body.Close() //nolint - body, err = io.ReadAll(testResp.Body) - if err != nil { - return newConf, err - } - err = json.Unmarshal(body, &testConf) - if err != nil { - return newConf, err - } - newConf.Test = testConf - return newConf, nil -} diff --git a/cmd/skywire-cli/commands/config/gen.go b/cmd/skywire-cli/commands/config/gen.go index 4eeb8436ba..c1967b7915 100644 --- a/cmd/skywire-cli/commands/config/gen.go +++ b/cmd/skywire-cli/commands/config/gen.go @@ -22,7 +22,6 @@ import ( "github.com/skycoin/skywire-utilities/pkg/buildinfo" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/netutil" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/pkg/app/appserver" "github.com/skycoin/skywire/pkg/dmsgc" "github.com/skycoin/skywire/pkg/routing" @@ -36,7 +35,7 @@ var checkPKCmd = &cobra.Command{ Use: "check-pk ", Short: "check a skywire public key", Args: cobra.ExactArgs(1), // Require exactly one argument - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { if len(args) == 0 { return } @@ -53,7 +52,7 @@ var checkPKCmd = &cobra.Command{ var genKeysCmd = &cobra.Command{ Use: "gen-keys", Short: "generate public / secret keypair", - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { pk, sk := cipher.GenerateKeyPair() fmt.Println(pk) fmt.Println(sk) @@ -72,7 +71,7 @@ func init() { genConfigCmd.Flags().SortFlags = false RootCmd.AddCommand(genConfigCmd, genKeysCmd, checkPKCmd) - genConfigCmd.Flags().StringVarP(&serviceConfURL, "url", "a", scriptExecArray(fmt.Sprintf("${SVCCONFADDR[@]-%s}", utilenv.ServiceConfAddr)), "services conf url\n\r") + genConfigCmd.Flags().StringVarP(&serviceConfURL, "url", "a", scriptExecArray(fmt.Sprintf("${SVCCONFADDR[@]-%s}", serviceConfURL)), "services conf url\n\r") gHiddenFlags = append(gHiddenFlags, "url") genConfigCmd.Flags().StringVar(&logLevel, "loglvl", scriptExecString("${LOGLVL:-info}"), "level of logging in config\033[0m") gHiddenFlags = append(gHiddenFlags, "loglvl") @@ -159,7 +158,7 @@ func init() { } genConfigCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r") gHiddenFlags = append(gHiddenFlags, "sk") - genConfigCmd.Flags().BoolVarP(&isTestEnv, "testenv", "t", scriptExecBool("${TESTENV:-false}"), "use test deployment "+testConf+"\033[0m") + genConfigCmd.Flags().BoolVarP(&isTestEnv, "testenv", "t", scriptExecBool("${TESTENV:-false}"), "use test deployment\033[0m") gHiddenFlags = append(gHiddenFlags, "testenv") genConfigCmd.Flags().BoolVarP(&isVpnServerEnable, "servevpn", "v", scriptExecBool("${VPNSERVER:-false}"), "enable vpn server\033[0m") gHiddenFlags = append(gHiddenFlags, "servevpn") @@ -302,7 +301,7 @@ var genConfigCmd = &cobra.Command{ } //use test deployment if isTestEnv { - serviceConfURL = utilenv.TestServiceConfAddr + serviceConfURL = testServiceConfURL } var err error if isDmsgHTTP { @@ -380,7 +379,7 @@ var genConfigCmd = &cobra.Command{ } } }, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { log := logger wasStdout := isStdout @@ -391,13 +390,19 @@ var genConfigCmd = &cobra.Command{ isStdout = false } - if !noFetch { + //determine best protocol + if isBestProtocol && netutil.LocalProtocol() { + disablePublicAutoConn = true + isDmsgHTTP = true + } + + if !noFetch && !isDmsgHTTP { // create an http client to fetch the services client := http.Client{ Timeout: time.Second * 15, // Timeout after 15 seconds } if serviceConfURL == "" { - serviceConfURL = utilenv.ServiceConfAddr + serviceConfURL = "http://" } if !isStdout { log.Infof("Fetching service endpoints from %s", serviceConfURL) @@ -516,11 +521,6 @@ var genConfigCmd = &cobra.Command{ } } - //determine best protocol - if isBestProtocol && netutil.LocalProtocol() { - disablePublicAutoConn = true - isDmsgHTTP = true - } //generate the common config containing public & secret keys u := buildinfo.Version() x := u @@ -546,8 +546,6 @@ var genConfigCmd = &cobra.Command{ conf.Common.SK = sk conf.Common.PK = pk - dnsServer := utilenv.DNSServer - if services.DNSServer != "" { dnsServer = services.DNSServer } @@ -581,12 +579,8 @@ var genConfigCmd = &cobra.Command{ // If nothing was fetched if services.SurveyWhitelist == nil { // By default - if !noDefaults { - // set the hardcoded defaults from skywire-utilities for the service public keys - if err := surveyWlPKs.Set(utilenv.SurveyWhitelistPKs); err != nil { - log.Fatalf("Failed to unmarshal survey whitelist public keys: %v", err) - } - } + log.Error("Services were not fetched from default conf service URL") + } //if the flag is not empty if surveyWhitelistPKs != "" { @@ -597,109 +591,29 @@ var genConfigCmd = &cobra.Command{ } services.SurveyWhitelist = append(services.SurveyWhitelist, surveyWlPKs...) - if !isTestEnv { - if services.DmsgDiscovery == "" { - services.DmsgDiscovery = utilenv.DmsgDiscAddr - } - if services.DmsgDiscovery == "" { - services.DmsgDiscovery = utilenv.DmsgDiscAddr - } - if services.TransportDiscovery == "" { - services.TransportDiscovery = utilenv.TpDiscAddr - } - if services.AddressResolver == "" { - services.AddressResolver = utilenv.AddressResolverAddr - } - if services.RouteFinder == "" { - services.RouteFinder = utilenv.RouteFinderAddr - } - if services.UptimeTracker == "" { - services.UptimeTracker = utilenv.UptimeTrackerAddr - } - if services.ServiceDiscovery == "" { - services.ServiceDiscovery = utilenv.ServiceDiscAddr - } - if services.StunServers == nil { - services.StunServers = utilenv.GetStunServers() - } - if services.DNSServer == "" { - services.DNSServer = utilenv.DNSServer - } - if routeSetupNodes != "" { - if err := routeSetupPKs.Set(routeSetupNodes); err != nil { - log.Fatalf("bad key set for route setup node flag: %v", err) - } - } - if services.RouteSetupNodes == nil { - if !noDefaults { - if err := routeSetupPKs.Set(utilenv.RouteSetupPKs); err != nil { - log.Fatalf("Failed to unmarshal route setup-node public keys: %v", err) - } - } - } - services.RouteSetupNodes = append(services.RouteSetupNodes, routeSetupPKs...) - if transportSetupPKs != "" { - if err := tpSetupPKs.Set(transportSetupPKs); err != nil { - log.Fatalf("bad key set for transport setup node flag: %v", err) - } - } - if services.TransportSetupPKs == nil { - if !noDefaults { - if err := tpSetupPKs.Set(utilenv.TPSetupPKs); err != nil { - log.Fatalf("Failed to unmarshal transport setup-node public keys: %v", err) - } - } - } - services.TransportSetupPKs = append(services.TransportSetupPKs, tpSetupPKs...) - } else { - if services.DmsgDiscovery == "" { - services.DmsgDiscovery = utilenv.TestDmsgDiscAddr - } - if services.TransportDiscovery == "" { - services.TransportDiscovery = utilenv.TestTpDiscAddr - } - if services.AddressResolver == "" { - services.AddressResolver = utilenv.TestAddressResolverAddr - } - if services.RouteFinder == "" { - services.RouteFinder = utilenv.TestRouteFinderAddr - } - if services.UptimeTracker == "" { - services.UptimeTracker = utilenv.TestUptimeTrackerAddr - } - if services.ServiceDiscovery == "" { - services.ServiceDiscovery = utilenv.TestServiceDiscAddr - } - if services.StunServers == nil { - services.StunServers = utilenv.GetStunServers() - } - if services.DNSServer == "" { - services.DNSServer = utilenv.DNSServer - } - if routeSetupNodes != "" { - if err := routeSetupPKs.Set(routeSetupNodes); err != nil { - log.Fatalf("bad key set for route setup node flag: %v", err) - } - } - if services.RouteSetupNodes == nil { - if err := routeSetupPKs.Set(utilenv.TestRouteSetupPKs); err != nil { - log.Fatalf("Failed to unmarshal route setup-node public keys: %v", err) - } - } - services.RouteSetupNodes = append(services.RouteSetupNodes, routeSetupPKs...) - if transportSetupPKs != "" { - if err := tpSetupPKs.Set(transportSetupPKs); err != nil { - log.Fatalf("bad key set for transport setup node flag: %v", err) - } + if services.DmsgDiscovery == "" { + log.Fatalf("Dmsg Discovery not set") + } + if services.TransportDiscovery == "" { + log.Fatalf("Transport Discovery not set") + } + if routeSetupNodes != "" { + if err := routeSetupPKs.Set(routeSetupNodes); err != nil { + log.Fatalf("bad key set for route setup node flag: %v", err) } - if services.TransportSetupPKs == nil { - if !noDefaults { - if err := tpSetupPKs.Set(utilenv.TestTPSetupPKs); err != nil { - log.Fatalf("Failed to unmarshal transport setup-node public keys: %v", err) - } - } + } + services.RouteSetupNodes = append(services.RouteSetupNodes, routeSetupPKs...) + if services.RouteSetupNodes == nil { + log.Fatalf("Route Setup node not set") + } + if transportSetupPKs != "" { + if err := tpSetupPKs.Set(transportSetupPKs); err != nil { + log.Fatalf("bad key set for transport setup node flag: %v", err) } - services.TransportSetupPKs = append(services.TransportSetupPKs, tpSetupPKs...) + } + services.TransportSetupPKs = append(services.TransportSetupPKs, tpSetupPKs...) + if services.TransportSetupPKs == nil { + log.Fatalf("Route Setup node not set") } conf.Dmsg = &dmsgc.DmsgConfig{ diff --git a/cmd/skywire-cli/commands/config/root.go b/cmd/skywire-cli/commands/config/root.go index d08fc195e9..dc3dc00c98 100644 --- a/cmd/skywire-cli/commands/config/root.go +++ b/cmd/skywire-cli/commands/config/root.go @@ -12,7 +12,6 @@ import ( "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/logging" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/pkg/visor/visorconfig" ) @@ -55,7 +54,9 @@ var ( selectedOS string disableApps string isBestProtocol bool - serviceConfURL string + serviceConfURL = "http://conf.skywire.skycoin.com" + testServiceConfURL = "http://conf.skywire.dev" + dnsServer = "1.1.1.1" services visorconfig.Services servicesConfig servicesConf isForce bool @@ -64,8 +65,6 @@ var ( isOutUnset bool ver string isRoot = visorconfig.IsRoot() - svcConf = strings.ReplaceAll(utilenv.ServiceConfAddr, "http://", "") //visorconfig.DefaultServiceConfAddr - testConf = strings.ReplaceAll(utilenv.TestServiceConfAddr, "http://", "") //visorconfig.DefaultServiceConfAddr gHiddenFlags []string uHiddenFlags []string binPath string diff --git a/cmd/skywire-cli/commands/config/services.go b/cmd/skywire-cli/commands/config/services.go index 96c96dafc9..75dc63b080 100644 --- a/cmd/skywire-cli/commands/config/services.go +++ b/cmd/skywire-cli/commands/config/services.go @@ -12,7 +12,6 @@ import ( "github.com/skycoin/skywire-utilities/pkg/cmdutil" "github.com/skycoin/skywire-utilities/pkg/logging" - "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/pkg/visor/visorconfig" ) @@ -26,7 +25,7 @@ func init() { var servicesCmd = &cobra.Command{ Use: "svc", Short: "update services-config.json file from config bootstrap service", - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { log := logging.MustGetLogger("services_updater") ctx, cancel := cmdutil.SignalContext(context.Background(), log) @@ -57,7 +56,7 @@ var servicesCmd = &cobra.Command{ func fetchServicesConf() (servicesConf, error) { var newConf servicesConf var prodConf visorconfig.Services - prodResp, err := http.Get(skyenv.ServiceConfAddr) + prodResp, err := http.Get(serviceConfURL) //nolint if err != nil { return newConf, err } @@ -73,7 +72,7 @@ func fetchServicesConf() (servicesConf, error) { newConf.Prod = prodConf var testConf visorconfig.Services - testResp, err := http.Get(skyenv.TestServiceConfAddr) + testResp, err := http.Get(testServiceConfURL) //nolint if err != nil { return newConf, err } diff --git a/cmd/skywire-cli/commands/config/update.go b/cmd/skywire-cli/commands/config/update.go index fdbfcc24d0..c243b297fb 100644 --- a/cmd/skywire-cli/commands/config/update.go +++ b/cmd/skywire-cli/commands/config/update.go @@ -12,6 +12,7 @@ import ( coinCipher "github.com/skycoin/skycoin/src/cipher" "github.com/spf13/cobra" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/logging" "github.com/skycoin/skywire/pkg/dmsgc" @@ -31,8 +32,8 @@ func init() { updateCmd.Flags().SortFlags = false updateCmd.Flags().BoolVarP(&isUpdateEndpoints, "endpoints", "a", false, "update server endpoints") updateCmd.Flags().StringVar(&logLevel, "log-level", "", "level of logging in config") - updateCmd.Flags().StringVarP(&serviceConfURL, "url", "b", "", "service config URL: "+svcConf) - updateCmd.Flags().BoolVarP(&isTestEnv, "testenv", "t", false, "use test deployment: "+testConf) + updateCmd.Flags().StringVarP(&serviceConfURL, "url", "b", skywire.ProdConf.Conf, "service config URL") + updateCmd.Flags().BoolVarP(&isTestEnv, "testenv", "t", false, "use test deployment") updateCmd.Flags().StringVar(&setPublicAutoconnect, "public-autoconn", "", "change public autoconnect configuration") updateCmd.Flags().IntVar(&minHops, "set-minhop", -1, "change min hops value") updateCmd.PersistentFlags().StringVarP(&input, "input", "i", "", "path of input config file.") @@ -91,9 +92,9 @@ var updateCmd = &cobra.Command{ PreRun: func(_ *cobra.Command, _ []string) { if isUpdateEndpoints && (serviceConfURL == "") { if !isTestEnv { - serviceConfURL = svcConf + serviceConfURL = skywire.ProdConf.Conf } else { - serviceConfURL = testConf + serviceConfURL = skywire.TestConf.Conf } } setDefaults() @@ -106,7 +107,7 @@ var updateCmd = &cobra.Command{ conf = initUpdate() if isUpdateEndpoints { if isTestEnv { - serviceConfURL = testConf + serviceConfURL = skywire.TestConf.Conf } mLog := logging.NewMasterLogger() mLog.SetLevel(logrus.InfoLevel) diff --git a/cmd/skywire-cli/commands/log/log.go b/cmd/skywire-cli/commands/log/log.go index 087898cf6a..d5f7c376c9 100644 --- a/cmd/skywire-cli/commands/log/log.go +++ b/cmd/skywire-cli/commands/log/log.go @@ -22,13 +22,22 @@ import ( "github.com/skycoin/dmsg/pkg/dmsghttp" "github.com/spf13/cobra" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/cmdutil" "github.com/skycoin/skywire-utilities/pkg/logging" - "github.com/skycoin/skywire-utilities/pkg/skyenv" ) func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + dmsgDiscURL = services.DmsgDiscovery + utURL = services.UptimeTracker + } + } + logCmd.Flags().SortFlags = false logCmd.Flags().BoolVarP(&logOnly, "log", "l", false, "fetch only transport logs") logCmd.Flags().BoolVarP(&surveyOnly, "survey", "v", false, "fetch only surveys") @@ -42,8 +51,8 @@ func init() { logCmd.Flags().BoolVar(&allVisors, "all", false, "consider all visors ; no version filtering") logCmd.Flags().IntVar(&batchSize, "batchSize", 50, "number of visor in each batch") logCmd.Flags().Int64Var(&maxFileSize, "maxfilesize", 1024, "maximum file size allowed to download during collecting logs, in KB") - logCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", skyenv.DmsgDiscAddr, "dmsg discovery url\n") - logCmd.Flags().StringVarP(&utAddr, "ut", "u", skyenv.UptimeTrackerAddr, "uptime tracker url\n") + logCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsgDiscURL, "dmsg discovery url\n") + logCmd.Flags().StringVarP(&utAddr, "ut", "u", utURL, "uptime tracker url\n") if os.Getenv("DMSGCURL_SK") != "" { sk.Set(os.Getenv("DMSGCURL_SK")) //nolint } @@ -54,7 +63,7 @@ var logCmd = &cobra.Command{ Use: "log", Short: "survey & transport log collection", Long: "Fetch health, survey, and transport logging from visors which are online in the uptime tracker\nhttp://ut.skywire.skycoin.com/uptimes?v=v2\nhttp://ut.skywire.skycoin.com/uptimes?v=v2&visors=;;", - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { log := logging.MustGetLogger("log-collecting") fver, err := version.NewVersion("v1.3.17") if err != nil { diff --git a/cmd/skywire-cli/commands/log/root.go b/cmd/skywire-cli/commands/log/root.go index 0056d54cfb..ef5f37d68f 100644 --- a/cmd/skywire-cli/commands/log/root.go +++ b/cmd/skywire-cli/commands/log/root.go @@ -6,6 +6,8 @@ import ( ) var ( + dmsgDiscURL string + utURL string pubKey string duration int minv string diff --git a/cmd/skywire-cli/commands/mdisc/root.go b/cmd/skywire-cli/commands/mdisc/root.go index 6d60a85757..cd910dd146 100644 --- a/cmd/skywire-cli/commands/mdisc/root.go +++ b/cmd/skywire-cli/commands/mdisc/root.go @@ -4,6 +4,7 @@ package climdisc import ( "bytes" "context" + "encoding/json" "fmt" "net/http" "os" @@ -16,9 +17,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/logging" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/cmd/skywire-cli/internal" ) @@ -27,6 +28,7 @@ var ( cacheFilesAge int mdURL string isStats bool + dmsgDiscURL string ) // var allEntries bool @@ -34,6 +36,13 @@ var masterLogger = logging.NewMasterLogger() var packageLogger = masterLogger.PackageLogger("mdisc:disc") func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + dmsgDiscURL = services.DmsgDiscovery + } + } RootCmd.AddCommand( entryCmd, availableServersCmd, @@ -41,9 +50,9 @@ func init() { RootCmd.Flags().StringVar(&cacheFileDMSGD, "cfu", os.TempDir()+"/dmsgd.json", "DMSGD cache file location.") RootCmd.Flags().IntVarP(&cacheFilesAge, "cfa", "m", 5, "update cache file if older than n minutes") RootCmd.Flags().BoolVarP(&isStats, "stats", "s", false, "count the number of results") - entryCmd.Flags().StringVar(&mdURL, "url", utilenv.DmsgDiscAddr, "specify alternative DMSG discovery url") - RootCmd.Flags().StringVar(&mdURL, "url", utilenv.DmsgDiscAddr, "specify alternative DMSG discovery url") - availableServersCmd.Flags().StringVar(&mdURL, "url", utilenv.DmsgDiscAddr, "specify alternative DMSG discovery url") + entryCmd.Flags().StringVar(&mdURL, "url", dmsgDiscURL, "specify alternative DMSG discovery url") + RootCmd.Flags().StringVar(&mdURL, "url", dmsgDiscURL, "specify alternative DMSG discovery url") + availableServersCmd.Flags().StringVar(&mdURL, "url", dmsgDiscURL, "specify alternative DMSG discovery url") } // RootCmd is the command that contains sub-commands which interacts with DMSG services. @@ -52,7 +61,7 @@ var RootCmd = &cobra.Command{ Short: "Query DMSG Discovery", Long: `Query DMSG Discovery list entries in dmsg discovery`, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { dmsgclientkeys := internal.GetData(cacheFileDMSGD, mdURL+"/dmsg-discovery/entries", cacheFilesAge) if isStats { stats, _ := script.Echo(dmsgclientkeys).JQ(".[]").CountLines() //nolint diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go index 69feb29381..c09996809a 100644 --- a/cmd/skywire-cli/commands/proxy/proxy.go +++ b/cmd/skywire-cli/commands/proxy/proxy.go @@ -4,6 +4,7 @@ package skysocksc import ( "bytes" "context" + "encoding/json" "fmt" "os" "strings" @@ -15,9 +16,9 @@ import ( "github.com/spf13/cobra" "github.com/tidwall/pretty" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/buildinfo" "github.com/skycoin/skywire-utilities/pkg/cmdutil" - "github.com/skycoin/skywire-utilities/pkg/skyenv" clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc" "github.com/skycoin/skywire/cmd/skywire-cli/internal" "github.com/skycoin/skywire/pkg/app/appserver" @@ -172,7 +173,7 @@ var startCmd = &cobra.Command{ var stopCmd = &cobra.Command{ Use: "stop", Short: "stop the " + serviceType + " client", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { internal.PrintFatalError(cmd.Flags(), fmt.Errorf("unable to create RPC client: %w", err)) @@ -196,7 +197,7 @@ var stopCmd = &cobra.Command{ var statusCmd = &cobra.Command{ Use: "status", Short: serviceType + " client status", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { //TODO: check status of multiple clients rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { @@ -216,7 +217,10 @@ var statusCmd = &cobra.Command{ AppPort routing.Port `json:"app_port"` } var jsonAppStatus []appState - fmt.Fprintf(w, "---- All Proxy List -----------------------------------------------------\n\n") + _, err = fmt.Fprintf(w, "---- All Proxy List -----------------------------------------------------\n\n") + if err != nil { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("error on fmt.Fprintf: %w", err)) + } for _, state := range states { for _, v := range state.AppConfig.Args { if v == binaryName { @@ -249,7 +253,10 @@ var statusCmd = &cobra.Command{ } } } - fmt.Fprintf(w, "-------------------------------------------------------------------------\n") + _, err = fmt.Fprintf(w, "-------------------------------------------------------------------------\n") + if err != nil { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("error on fmt.Fprintf: %w", err)) + } internal.Catch(cmd.Flags(), w.Flush()) internal.PrintOutput(cmd.Flags(), jsonAppStatus, b.String()) }, @@ -258,12 +265,20 @@ var statusCmd = &cobra.Command{ var isLabel bool func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + svcDiscURL = services.DmsgDiscovery + utURL = services.UptimeTracker + } + } if version == "unknown" { version = "" //nolint } version = strings.Split(version, "-")[0] - listCmd.Flags().StringVarP(&utURL, "uturl", "w", skyenv.UptimeTrackerAddr, "uptime tracker url") - listCmd.Flags().StringVarP(&sdURL, "sdurl", "a", skyenv.ServiceDiscAddr, "service discovery url") + listCmd.Flags().StringVarP(&utURL, "uturl", "w", utURL, "uptime tracker url") + listCmd.Flags().StringVarP(&sdURL, "sdurl", "a", svcDiscURL, "service discovery url") listCmd.Flags().BoolVarP(&rawData, "raw", "r", false, "print raw data") listCmd.Flags().BoolVarP(&noFilterOnline, "noton", "o", false, "do not filter by online status in UT") listCmd.Flags().StringVar(&cacheFileSD, "cfs", os.TempDir()+"/proxysd.json", "SD cache file location") @@ -280,8 +295,8 @@ func init() { var listCmd = &cobra.Command{ Use: "list", Short: "List servers", - Long: fmt.Sprintf("List %v servers from service discovery\n%v/api/services?type=%v\n%v/api/services?type=%v&country=US\n\nSet cache file location to \"\" to avoid using cache files", serviceType, skyenv.ServiceDiscAddr, serviceType, skyenv.ServiceDiscAddr, serviceType), - Run: func(cmd *cobra.Command, args []string) { + Long: fmt.Sprintf("List %v servers from service discovery\n%v/api/services?type=%v\n%v/api/services?type=%v&country=US\n\nSet cache file location to \"\" to avoid using cache files", serviceType, svcDiscURL, serviceType, svcDiscURL, serviceType), + Run: func(_ *cobra.Command, _ []string) { sds := internal.GetData(cacheFileSD, sdURL+"/api/services?type="+serviceType, cacheFilesAge) if rawData { script.Echo(string(pretty.Color(pretty.Pretty([]byte(sds)), nil))).Stdout() //nolint diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go index 0caa1ee242..49646a83b9 100644 --- a/cmd/skywire-cli/commands/proxy/root.go +++ b/cmd/skywire-cli/commands/proxy/root.go @@ -32,6 +32,7 @@ var ( addr string startingTimeout int httpAddr string + svcDiscURL string ) // RootCmd contains commands that interact with the skywire-visor diff --git a/cmd/skywire-cli/commands/rewards/calc.go b/cmd/skywire-cli/commands/rewards/calc.go index 5c3964033e..c83cddb266 100644 --- a/cmd/skywire-cli/commands/rewards/calc.go +++ b/cmd/skywire-cli/commands/rewards/calc.go @@ -93,7 +93,7 @@ var RootCmd = &cobra.Command{ Long: ` Collect surveys: skywire-cli log Fetch uptimes: skywire-cli ut > ut.txt`, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { var err error if log == nil { log = logging.MustGetLogger("rewards") @@ -406,7 +406,7 @@ func init() { var testCmd = &cobra.Command{ Use: "svc", Short: "verify services in survey", - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { var err error if log == nil { log = logging.MustGetLogger("rewards") diff --git a/cmd/skywire-cli/commands/rewards/ui.go b/cmd/skywire-cli/commands/rewards/ui.go index 5fdd7686e4..db23b006aa 100644 --- a/cmd/skywire-cli/commands/rewards/ui.go +++ b/cmd/skywire-cli/commands/rewards/ui.go @@ -6,6 +6,7 @@ import ( "context" "encoding/base64" "encoding/csv" + "encoding/json" "fmt" htmpl "html/template" "io" @@ -29,26 +30,33 @@ import ( dmsg "github.com/skycoin/dmsg/pkg/dmsg" "github.com/spf13/cobra" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/cmdutil" "github.com/skycoin/skywire-utilities/pkg/logging" - "github.com/skycoin/skywire-utilities/pkg/skyenv" ) func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + dmsgDiscURL = services.DmsgDiscovery + } + } RootCmd.CompletionOptions.DisableDefaultCmd = true RootCmd.AddCommand( uiCmd, ) uiCmd.Flags().UintVarP(&webPort, "port", "p", scriptExecUint("${WEBPORT:-80}"), "port to serve") - uiCmd.Flags().UintVarP(&dmsgPort, "dport", "d", scriptExecUint("${DMSGPORT:-80}"), "dmsg port to serve") + uiCmd.Flags().Uint16VarP(&dmsgPort, "dport", "d", scriptExecUint16("${DMSGPORT:-80}"), "dmsg port to serve") uiCmd.Flags().IntVarP(&dmsgSess, "dsess", "e", scriptExecInt("${DMSGSESSIONS:-1}"), "dmsg sessions") msg := "add whitelist keys, comma separated to permit POST of reward transaction to be broadcast" if scriptExecArray("${REWARDPKS[@]}") != "" { msg += "\n\r" } uiCmd.Flags().StringVarP(&wl, "wl", "w", scriptExecArray("${REWARDPKS[@]}"), msg) - uiCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", "", "dmsg discovery url default:\n"+skyenv.DmsgDiscAddr) + uiCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", "", "dmsg discovery url default:\n"+dmsgDiscURL) uiCmd.Flags().StringVarP(&ensureOnlineURL, "ensure-online", "O", scriptExecString("${ENSUREONLINE}"), "Exit when the specified URL cannot be fetched;\ni.e. https://fiber.skywire.dev") if os.Getenv("DMSGHTTP_SK") != "" { sk.Set(os.Getenv("DMSGHTTP_SK")) //nolint @@ -87,12 +95,13 @@ var ( runTime time.Duration sk cipher.SecKey dmsgDisc string - dmsgPort uint + dmsgPort uint16 dmsgSess int wl string wlkeys []cipher.PubKey webPort uint ensureOnlineURL string + dmsgDiscURL string ) var skyenvfile = os.Getenv("SKYENV") @@ -240,10 +249,10 @@ var tmpl *htmpl.Template func server() { + log := logging.MustGetLogger("dmsghttp") if dmsgDisc == "" { - dmsgDisc = skyenv.DmsgDiscAddr + log.Fatal("Dmsg Discovery URL not specified") } - log := logging.MustGetLogger("dmsghttp") ctx, cancel := cmdutil.SignalContext(context.Background(), log) defer cancel() @@ -1497,6 +1506,47 @@ func scriptExecUint(s string) uint { return uint(0) } +func scriptExecUint16(s string) uint16 { + if runtime.GOOS == "windows" { + var variable string + if strings.Contains(s, ":-") { + parts := strings.SplitN(s, ":-", 2) + variable = parts[0] + "}" + } else { + variable = s + } + out, err := script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; echo %s"`, skyenvfile, variable)).String() + if err == nil { + if (out == "") || (out == variable) { + return 0 + } + i, err := strconv.Atoi(strings.TrimSpace(strings.TrimRight(out, "\n"))) + if err == nil { + if i >= 0 && i <= 65535 { + return uint16(i) //nolint + } + return 0 + } + return 0 + } + return 0 + } + z, err := script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; printf "%s"'`, skyenvfile, s)).String() + if err == nil { + if z == "" { + return 0 + } + i, err := strconv.Atoi(z) + if err == nil { + if i >= 0 && i <= 65535 { + return uint16(i) //nolint + } + return 0 + } + } + return uint16(0) +} + func whitelistAuth(whitelistedPKs []cipher.PubKey) gin.HandlerFunc { return func(c *gin.Context) { // Get the remote PK. diff --git a/cmd/skywire-cli/commands/root.go b/cmd/skywire-cli/commands/root.go index dee47484c0..5a04199f87 100644 --- a/cmd/skywire-cli/commands/root.go +++ b/cmd/skywire-cli/commands/root.go @@ -84,7 +84,7 @@ var treeCmd = &cobra.Command{ SilenceUsage: true, DisableSuggestions: true, DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { // You can use a LeveledList here, for easy generation. leveledList := pterm.LeveledList{} leveledList = append(leveledList, pterm.LeveledListItem{Level: 0, Text: RootCmd.Use}) @@ -140,7 +140,7 @@ var docCmd = &cobra.Command{ SilenceUsage: true, DisableSuggestions: true, DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { fmt.Printf("\n# %s\n", "skywire-cli documentation") fmt.Printf("\n%s\n", "skywire command line interface") diff --git a/cmd/skywire-cli/commands/route/route.go b/cmd/skywire-cli/commands/route/route.go index fa19fb0423..c988b265a3 100644 --- a/cmd/skywire-cli/commands/route/route.go +++ b/cmd/skywire-cli/commands/route/route.go @@ -3,6 +3,7 @@ package cliroute import ( "bytes" + "encoding/json" "errors" "fmt" "io" @@ -17,8 +18,8 @@ import ( "github.com/spf13/pflag" "golang.org/x/net/context" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc" "github.com/skycoin/skywire/cmd/skywire-cli/internal" "github.com/skycoin/skywire/pkg/routefinder/rfclient" @@ -28,17 +29,27 @@ import ( "github.com/skycoin/skywire/pkg/visor/visorconfig" ) -var frAddr string -var frMinHops, frMaxHops uint16 -var timeout time.Duration -var skywireconfig string +var ( + frAddr string + frMinHops, frMaxHops uint16 + timeout time.Duration + skywireconfig string + rfURL string +) func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + rfURL = services.RouteFinder + } + } findCmd.Flags().SortFlags = false findCmd.Flags().Uint16VarP(&frMinHops, "min", "n", 1, "minimum hops") findCmd.Flags().Uint16VarP(&frMaxHops, "max", "x", 1000, "maximum hops") findCmd.Flags().DurationVarP(&timeout, "timeout", "t", 10*time.Second, "request timeout") - findCmd.Flags().StringVarP(&frAddr, "addr", "a", "", "route finder service address\n"+utilenv.RouteFinderAddr) + findCmd.Flags().StringVarP(&frAddr, "addr", "a", rfURL, "route finder service address") } // RootCmd is the command that queries the route finder. @@ -58,7 +69,7 @@ Assumes the local visor public key as an argument if only one argument is given` } //set the routefinder address. It's not used as the default value to fix the display of the help command if frAddr == "" { - frAddr = utilenv.RouteFinderAddr + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Route Finder URL not specified")) } //assume the local public key as the first argument if only 1 argument is given ; resize args array to 2 and move the first argument to the second one if len(args) == 1 { diff --git a/cmd/skywire-cli/commands/survey/root.go b/cmd/skywire-cli/commands/survey/root.go index 813f20e819..35edafd95b 100644 --- a/cmd/skywire-cli/commands/survey/root.go +++ b/cmd/skywire-cli/commands/survey/root.go @@ -12,10 +12,10 @@ import ( "github.com/skycoin/dmsg/pkg/dmsg" "github.com/spf13/cobra" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/cmdutil" "github.com/skycoin/skywire-utilities/pkg/logging" - "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/cmd/skywire-cli/internal" "github.com/skycoin/skywire/pkg/visor/visorconfig" ) @@ -33,12 +33,20 @@ var ( pkgconfigexists bool userconfigexists bool conf *visorconfig.V1 + dmsgDiscURL string ) func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + dmsgDiscURL = services.DmsgDiscovery + } + } surveyCmd.Flags().SortFlags = false surveyCmd.Flags().StringVarP(&confPath, "config", "c", "", "optionl config file to use (i.e.: "+visorconfig.ConfigName+")") - surveyCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", skyenv.DmsgDiscAddr, "value of dmsg discovery") + surveyCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsgDiscURL, "value of dmsg discovery") // surveyCmd.Flags().StringVarP(&confArg, "confarg", "C", "", "supply config as argument") // surveyCmd.Flags().BoolVarP(&stdin, "stdin", "n", false, "read config from stdin") if _, err := os.Stat(visorconfig.SkywirePath + "/" + visorconfig.ConfigJSON); err == nil { @@ -63,7 +71,7 @@ var surveyCmd = &cobra.Command{ DisableFlagsInUseLine: true, Short: "system survey", Long: "print the system survey", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { if pkg { confPath = visorconfig.SkywirePath + "/" + visorconfig.ConfigJSON } diff --git a/cmd/skywire-cli/commands/tp/tp.go b/cmd/skywire-cli/commands/tp/tp.go index 19ffa8dda9..4d49b55625 100644 --- a/cmd/skywire-cli/commands/tp/tp.go +++ b/cmd/skywire-cli/commands/tp/tp.go @@ -3,6 +3,7 @@ package clitp import ( "bytes" + "encoding/json" "errors" "fmt" "os" @@ -20,9 +21,9 @@ import ( "github.com/spf13/pflag" "github.com/tidwall/pretty" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/logging" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc" "github.com/skycoin/skywire/cmd/skywire-cli/internal" "github.com/skycoin/skywire/pkg/transport" @@ -37,9 +38,21 @@ var ( logger = logging.MustGetLogger("skywire-cli") removeAll bool tpTypes bool + tpDiscURL string + sdURL string + utURL string ) func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + tpDiscURL = services.TransportDiscovery + sdURL = services.ServiceDiscovery + utURL = services.UptimeTracker + } + } tpCmd.Flags().SortFlags = false addTpCmd.Flags().SortFlags = false rmTpCmd.Flags().SortFlags = false @@ -109,7 +122,6 @@ var tpCmd = &cobra.Command{ var ( sortedEdgeKeys []string - utURL string tpdURL string rootNode string lastNode string @@ -126,7 +138,6 @@ var ( transportType string timeout time.Duration rpk string - sdURL string cacheFileSD string cacheFilesAge int forceAttempt bool @@ -138,7 +149,7 @@ func init() { addTpCmd.Flags().StringVarP(&rpk, "rpk", "r", "", "remote public key.") addTpCmd.Flags().StringVarP(&transportType, "type", "t", "", "type of transport to add.") addTpCmd.Flags().DurationVarP(&timeout, "timeout", "o", 0, "if specified, sets an operation timeout") - addTpCmd.Flags().StringVarP(&sdURL, "sdurl", "a", utilenv.ServiceDiscAddr, "service discovery url") + addTpCmd.Flags().StringVarP(&sdURL, "sdurl", "a", sdURL, "service discovery url") //TODO // listCmd.Flags().BoolVarP(&queryHealth, "health", "q", false, "check /health of remote visor over dmsg before creating transport") addTpCmd.Flags().BoolVarP(&forceAttempt, "force", "f", false, "attempt transport creation without check of SD") // or visor /health over dmsg @@ -240,7 +251,7 @@ var rmTpCmd = &cobra.Command{ Short: "Remove transport(s) by id", Long: "\n Remove transport(s) by id", DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { rpcClient, err := clirpc.Client(cmd.Flags()) if removeAll { internal.Catch(cmd.Flags(), rpcClient.RemoveAllTransports()) @@ -410,8 +421,8 @@ func init() { treeCmd.Flags().StringVarP(&rootNode, "source", "k", "", "root node ; defaults to visor with most transports") treeCmd.Flags().StringVarP(&lastNode, "dest", "d", "", "map route between source and dest") - treeCmd.Flags().StringVarP(&tpdURL, "tpdurl", "a", utilenv.TpDiscAddr, "transport discovery url") - treeCmd.Flags().StringVarP(&utURL, "uturl", "w", utilenv.UptimeTrackerAddr, "uptime tracker url") + treeCmd.Flags().StringVarP(&tpdURL, "tpdurl", "a", tpDiscURL, "transport discovery url") + treeCmd.Flags().StringVarP(&utURL, "uturl", "w", utURL, "uptime tracker url") treeCmd.Flags().BoolVarP(&rawData, "raw", "r", false, "print raw json data") treeCmd.Flags().BoolVarP(&refinedData, "pretty", "p", false, "print pretty json data") treeCmd.Flags().BoolVarP(&noFilterOnline, "noton", "o", false, "do not filter by online status in UT") @@ -427,8 +438,8 @@ func init() { var treeCmd = &cobra.Command{ Use: "tree", Short: "tree map of transports on the skywire network", - Long: fmt.Sprintf("display a tree representation of transports from TPD\n\n%v/all-transports\n\nSet cache file location to \"\" to avoid using cache files", utilenv.TpDiscAddr), - Run: func(cmd *cobra.Command, args []string) { + Long: fmt.Sprintf("display a tree representation of transports from TPD\n\n%v/all-transports\n\nSet cache file location to \"\" to avoid using cache files", tpDiscURL), + Run: func(cmd *cobra.Command, _ []string) { if rootNode != "" { err := rootnode.Set(rootNode) if err != nil { diff --git a/cmd/skywire-cli/commands/ut/root.go b/cmd/skywire-cli/commands/ut/root.go index 402074ce8c..ec5e9751ec 100644 --- a/cmd/skywire-cli/commands/ut/root.go +++ b/cmd/skywire-cli/commands/ut/root.go @@ -2,13 +2,14 @@ package cliut import ( + "encoding/json" "fmt" "os" "github.com/bitfield/script" "github.com/spf13/cobra" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" + "github.com/skycoin/skywire" "github.com/skycoin/skywire/cmd/skywire-cli/internal" ) @@ -28,6 +29,13 @@ var ( var minUT int func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + utURL = services.UptimeTracker + } + } utCmd.Flags().StringVarP(&pk, "pk", "k", "", "check uptime for the specified key") utCmd.Flags().BoolVarP(&online, "on", "o", false, "list currently online visors") utCmd.Flags().BoolVarP(&isStats, "stats", "s", false, "count the number of results") @@ -35,13 +43,13 @@ func init() { utCmd.Flags().IntVarP(&minUT, "min", "n", 75, "list visors meeting minimum uptime") utCmd.Flags().StringVar(&cacheFileUT, "cfu", os.TempDir()+"/ut.json", "UT cache file location.") utCmd.Flags().IntVarP(&cacheFilesAge, "cfa", "m", 5, "update cache files if older than n minutes") - utCmd.Flags().StringVarP(&utURL, "url", "u", utilenv.UptimeTrackerAddr, "specify alternative uptime tracker url") + utCmd.Flags().StringVarP(&utURL, "url", "u", utURL, "specify alternative uptime tracker url") } var utCmd = &cobra.Command{ Use: "ut", Short: "query uptime tracker", - Long: fmt.Sprintf("query uptime tracker\n\n%v/uptimes?v=v2\n\nCheck local visor daily uptime percent with:\n skywire-cli ut -k $(skywire-cli visor pk)n\nSet cache file location to \"\" to avoid using cache files", utilenv.UptimeTrackerAddr), + Long: fmt.Sprintf("query uptime tracker\n\n%v/uptimes?v=v2\n\nCheck local visor daily uptime percent with:\n skywire-cli ut -k $(skywire-cli visor pk)n\nSet cache file location to \"\" to avoid using cache files", utURL), Run: func(cmd *cobra.Command, _ []string) { uts := internal.GetData(cacheFileUT, utURL+"/uptimes?v=v2", cacheFilesAge) if online { diff --git a/cmd/skywire-cli/commands/visor/app.go b/cmd/skywire-cli/commands/visor/app.go index 4cea5061a9..7468fbea05 100644 --- a/cmd/skywire-cli/commands/visor/app.go +++ b/cmd/skywire-cli/commands/visor/app.go @@ -142,7 +142,7 @@ var registerAppCmd = &cobra.Command{ Use: "register", Short: "Register app", Long: "\n Register app", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { os.Exit(1) @@ -175,7 +175,7 @@ var deregisterAppCmd = &cobra.Command{ Use: "deregister", Short: "Deregister app", Long: "\n Deregister app", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { os.Exit(1) diff --git a/cmd/skywire-cli/commands/visor/ping.go b/cmd/skywire-cli/commands/visor/ping.go index 464077bf20..4cb83a4a68 100644 --- a/cmd/skywire-cli/commands/visor/ping.go +++ b/cmd/skywire-cli/commands/visor/ping.go @@ -58,7 +58,7 @@ var testCmd = &cobra.Command{ Use: "test", Short: "Test the visor with public visors on network", Long: "\n Creates a route with public visors as a hop and returns latency on the conn", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { pingConfig := visor.PingConfig{Tries: tries, PcktSize: pcktSize, PubVisCount: pubVisCount} rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { diff --git a/cmd/skywire-cli/commands/visor/process.go b/cmd/skywire-cli/commands/visor/process.go index 006e9705af..32aa05f1c3 100644 --- a/cmd/skywire-cli/commands/visor/process.go +++ b/cmd/skywire-cli/commands/visor/process.go @@ -37,7 +37,7 @@ func init() { var startCmd = &cobra.Command{ Use: "start", Short: "start visor", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { var output string var err error if !sourcerun { @@ -71,7 +71,7 @@ var reloadCmd = &cobra.Command{ Use: "reload", Short: "reload visor", Hidden: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { os.Exit(1) @@ -97,7 +97,7 @@ var shutdownCmd = &cobra.Command{ Use: "halt", Short: "Stop a running visor", Long: "\n Stop a running visor", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { rpcClient, err := clirpc.Client(cmd.Flags()) if err != nil { os.Exit(1) diff --git a/cmd/skywire-cli/commands/vpn/vvpn.go b/cmd/skywire-cli/commands/vpn/vvpn.go index eee5127d2a..0de39a9b37 100644 --- a/cmd/skywire-cli/commands/vpn/vvpn.go +++ b/cmd/skywire-cli/commands/vpn/vvpn.go @@ -4,6 +4,7 @@ package clivpn import ( "bytes" "context" + "encoding/json" "fmt" "os" "strings" @@ -15,8 +16,8 @@ import ( "github.com/spf13/cobra" "github.com/tidwall/pretty" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cmdutil" - "github.com/skycoin/skywire-utilities/pkg/skyenv" clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc" "github.com/skycoin/skywire/cmd/skywire-cli/internal" "github.com/skycoin/skywire/pkg/app/appserver" @@ -165,12 +166,20 @@ var statusCmd = &cobra.Command{ var isLabel bool func init() { + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + sdURL = services.ServiceDiscovery + utURL = services.UptimeTracker + } + } if version == "unknown" { version = "" } version = strings.Split(version, "-")[0] - listCmd.Flags().StringVarP(&utURL, "uturl", "w", skyenv.UptimeTrackerAddr, "uptime tracker url") - listCmd.Flags().StringVarP(&sdURL, "sdurl", "a", skyenv.ServiceDiscAddr, "service discovery url") + listCmd.Flags().StringVarP(&utURL, "uturl", "w", utURL, "uptime tracker url") + listCmd.Flags().StringVarP(&sdURL, "sdurl", "a", sdURL, "service discovery url") listCmd.Flags().BoolVarP(&rawData, "raw", "r", false, "print raw data") listCmd.Flags().BoolVarP(&noFilterOnline, "noton", "o", false, "do not filter by online status in UT") listCmd.Flags().StringVar(&cacheFileSD, "cfs", os.TempDir()+"/vpnsd.json", "SD cache file location") @@ -187,8 +196,8 @@ func init() { var listCmd = &cobra.Command{ Use: "list", Short: "List servers", - Long: fmt.Sprintf("List %v servers from service discovery\n%v/api/services?type=%v\n%v/api/services?type=%v&country=US\n\nSet cache file location to \"\" to avoid using cache files", serviceType, skyenv.ServiceDiscAddr, serviceType, skyenv.ServiceDiscAddr, serviceType), - Run: func(cmd *cobra.Command, args []string) { + Long: fmt.Sprintf("List %v servers from service discovery\n%v/api/services?type=%v\n%v/api/services?type=%v&country=US\n\nSet cache file location to \"\" to avoid using cache files", serviceType, sdURL, serviceType, sdURL, serviceType), + Run: func(_ *cobra.Command, _ []string) { sds := internal.GetData(cacheFileSD, sdURL+"/api/services?type="+serviceType, cacheFilesAge) if rawData { script.Echo(string(pretty.Color(pretty.Pretty([]byte(sds)), nil))).Stdout() //nolint diff --git a/cmd/skywire/commands/root.go b/cmd/skywire/commands/root.go new file mode 100644 index 0000000000..a63d30c5ee --- /dev/null +++ b/cmd/skywire/commands/root.go @@ -0,0 +1,262 @@ +// Package commands cmd/skywire/commands/root.go +package commands + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "github.com/bitfield/script" + "github.com/pterm/pterm" + "github.com/pterm/pterm/putils" + dmsg "github.com/skycoin/dmsg/cmd/dmsg/commands" + sd "github.com/skycoin/skycoin-service-discovery/cmd/service-discovery/commands" + "github.com/spf13/cobra" + + services "github.com/skycoin/skywire-services/cmd/skywire-services/commands" + "github.com/skycoin/skywire-utilities/pkg/buildinfo" + sc "github.com/skycoin/skywire/cmd/apps/skychat/commands" + ssc "github.com/skycoin/skywire/cmd/apps/skysocks-client/commands" + ss "github.com/skycoin/skywire/cmd/apps/skysocks/commands" + vpnc "github.com/skycoin/skywire/cmd/apps/vpn-client/commands" + vpns "github.com/skycoin/skywire/cmd/apps/vpn-server/commands" + sn "github.com/skycoin/skywire/cmd/setup-node/commands" + scli "github.com/skycoin/skywire/cmd/skywire-cli/commands" + "github.com/skycoin/skywire/pkg/visor" +) + +func init() { + + appsCmd.AddCommand( + vpns.RootCmd, + vpnc.RootCmd, + ssc.RootCmd, + ss.RootCmd, + sc.RootCmd, + ) + services.RootCmd.AddCommand( + sd.RootCmd, + sn.RootCmd, + ) + RootCmd.AddCommand( + visor.RootCmd, + scli.RootCmd, + services.RootCmd, + dmsg.RootCmd, + appsCmd, + treeCmd, + docCmd, + ) + visor.RootCmd.Long = ` + ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ ┬ ┬┬┌─┐┌─┐┬─┐ + └─┐├┴┐└┬┘││││├┬┘├┤───└┐┌┘│└─┐│ │├┬┘ + └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ └┘ ┴└─┘└─┘┴└─` + dmsg.RootCmd.Use = "dmsg" + services.RootCmd.Use = "svc" + sd.RootCmd.Use = "sd" + sn.RootCmd.Use = "sn" + scli.RootCmd.Use = "cli" + visor.RootCmd.Use = "visor" + vpns.RootCmd.Use = "vpn-server" + vpnc.RootCmd.Use = "vpn-client" + ssc.RootCmd.Use = "skysocks-client" + ss.RootCmd.Use = "skysocks" + sc.RootCmd.Use = "skychat" +} + +// RootCmd contains literally every 'command' from four repos here +var RootCmd = &cobra.Command{ + Use: func() string { + return strings.Split(filepath.Base(strings.ReplaceAll(strings.ReplaceAll(fmt.Sprintf("%v", os.Args), "[", ""), "]", "")), " ")[0] + }(), + Long: ` + ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ + └─┐├┴┐└┬┘││││├┬┘├┤ + └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘`, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, + Version: buildinfo.Version(), +} + +var appsCmd = &cobra.Command{ + Use: "app", + Short: "skywire native applications", + Long: ` + ┌─┐┌─┐┌─┐┌─┐ + ├─┤├─┘├─┘└─┐ + ┴ ┴┴ ┴ └─┘`, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, +} + +var treeCmd = &cobra.Command{ + Use: "tree", + Short: "subcommand tree", + Long: `subcommand tree`, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, + Run: func(_ *cobra.Command, _ []string) { + // You can use a LeveledList here, for easy generation. + leveledList := pterm.LeveledList{} + leveledList = append(leveledList, pterm.LeveledListItem{Level: 0, Text: RootCmd.Use}) + for _, j := range RootCmd.Commands() { + use := strings.Split(j.Use, " ") + leveledList = append(leveledList, pterm.LeveledListItem{Level: 1, Text: use[0]}) + for _, k := range j.Commands() { + use := strings.Split(k.Use, " ") + leveledList = append(leveledList, pterm.LeveledListItem{Level: 2, Text: use[0]}) + for _, l := range k.Commands() { + use := strings.Split(l.Use, " ") + leveledList = append(leveledList, pterm.LeveledListItem{Level: 3, Text: use[0]}) + for _, m := range l.Commands() { + use := strings.Split(m.Use, " ") + leveledList = append(leveledList, pterm.LeveledListItem{Level: 4, Text: use[0]}) + for _, n := range m.Commands() { + use := strings.Split(n.Use, " ") + leveledList = append(leveledList, pterm.LeveledListItem{Level: 5, Text: use[0]}) + for _, o := range n.Commands() { + use := strings.Split(o.Use, " ") + leveledList = append(leveledList, pterm.LeveledListItem{Level: 6, Text: use[0]}) + for _, p := range o.Commands() { + use := strings.Split(p.Use, " ") + leveledList = append(leveledList, pterm.LeveledListItem{Level: 7, Text: use[0]}) + } + } + } + } + } + } + } + // Generate tree from LeveledList. + r := putils.TreeFromLeveledList(leveledList) + + // Render TreePrinter + err := pterm.DefaultTree.WithRoot(r).Render() + if err != nil { + log.Fatal("render subcommand tree: ", err) + } + }, +} + +// for toc generation use: https://github.com/ekalinin/github-markdown-toc.go +var docCmd = &cobra.Command{ + Use: "doc", + Short: "generate markdown docs", + Long: `generate markdown docs + + UNHIDEFLAGS=1 go run cmd/skywire/skywire.go doc + + UNHIDEFLAGS=1 go run cmd/skywire/skywire.go doc > cmd/skywire/README1.md + + generate toc: + + cat cmd/skywire/README1.md | gh-md-toc`, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, + Run: func(_ *cobra.Command, _ []string) { + fmt.Printf("\n# %s\n", "skywire documentation") + fmt.Printf("\n## %s\n", "subcommand tree") + fmt.Printf("\n%s\n", "A tree representation of the skywire subcommands") + fmt.Printf("\n```\n") + _, err := script.Exec(os.Args[0] + " tree").Stdout() //nolint + if err != nil { + fmt.Println(err.Error()) + } + fmt.Printf("\n```\n") + + var use string + for _, j := range RootCmd.Commands() { + use = strings.Split(j.Use, " ")[0] + fmt.Printf("\n### %s\n", use) + fmt.Printf("\n```\n") + j.Help() //nolint + fmt.Printf("\n```\n") + if j.Name() == "cli" { + fmt.Printf("\n%s\n", "skywire command line interface") + fmt.Printf("\n## %s\n", RootCmd.Use) + fmt.Printf("\n```\n") + RootCmd.Help() //nolint + fmt.Printf("\n```\n") + fmt.Printf("\n## %s\n", "global flags") + fmt.Printf("\n%s\n", "The skywire-cli interacts with the running visor via rpc calls. By default the rpc server is available on localhost:3435. The rpc address and port the visor is using may be changed in the config file, once generated.") + + fmt.Printf("\n%s\n", "It is not recommended to expose the rpc server on the local network. Exposing the rpc allows unsecured access to the machine over the local network") + fmt.Printf("\n```\n") + fmt.Printf("\n%s\n", "Global Flags:") + fmt.Printf("\n%s\n", " --rpc string RPC server address (default \"localhost:3435\")") + fmt.Printf("\n%s\n", " --json bool print output as json") + fmt.Printf("\n```\n") + } + for _, k := range j.Commands() { + use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + fmt.Printf("\n#### %s\n", use) + fmt.Printf("\n```\n") + k.Help() //nolint + fmt.Printf("\n```\n") + if k.Name() == "survey" { + fmt.Printf("\n```\n") + _, err = script.Exec("sudo " + os.Args[0] + ` survey`).Stdout() //nolint + if err != nil { + fmt.Println(err.Error()) + } + fmt.Printf("\n```\n") + } + for _, l := range k.Commands() { + use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] + fmt.Printf("\n##### %s\n", use) + fmt.Printf("\n```\n") + l.Help() //nolint + fmt.Printf("\n```\n") + if l.Name() == "gen" { + fmt.Printf("\n##### Example for package / msi\n") + fmt.Printf("\n```\n") + fmt.Printf("$ skywire cli config gen -bpirxn\n") + _, err = script.Exec(os.Args[0] + ` cli config gen -bpirxn`).Stdout() //nolint + if err != nil { + fmt.Println(err.Error()) + } + fmt.Printf("\n```\n") + } + for _, m := range l.Commands() { + use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] + " " + strings.Split(m.Use, " ")[0] + fmt.Printf("\n###### %s\n", use) + fmt.Printf("\n```\n") + m.Help() //nolint + fmt.Printf("\n```\n") + for _, n := range m.Commands() { + use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] + " " + strings.Split(m.Use, " ")[0] + " " + strings.Split(n.Use, " ")[0] + fmt.Printf("\n###### %s\n", use) + fmt.Printf("\n```\n") + m.Help() //nolint + fmt.Printf("\n```\n") + for _, o := range n.Commands() { + use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] + " " + strings.Split(m.Use, " ")[0] + " " + strings.Split(n.Use, " ")[0] + " " + strings.Split(o.Use, " ")[0] + fmt.Printf("\n###### %s\n", use) + fmt.Printf("\n```\n") + m.Help() //nolint + fmt.Printf("\n```\n") + } + } + } + } + } + } + }, +} + +// Execute executes root CLI command. +func Execute() { + if err := RootCmd.Execute(); err != nil { + log.Fatal("Failed to execute command: ", err) + } +} diff --git a/cmd/skywire/skywire.go b/cmd/skywire/skywire.go index 08466e37dd..66e854a02e 100644 --- a/cmd/skywire/skywire.go +++ b/cmd/skywire/skywire.go @@ -5,383 +5,23 @@ skywire package main import ( - "fmt" - "log" - "os" - "path/filepath" - "strings" - - "github.com/bitfield/script" cc "github.com/ivanpirog/coloredcobra" - "github.com/pterm/pterm" - "github.com/pterm/pterm/putils" - dmsgdisc "github.com/skycoin/dmsg/cmd/dmsg-discovery/commands" - dmsgserver "github.com/skycoin/dmsg/cmd/dmsg-server/commands" - dmsgsocks "github.com/skycoin/dmsg/cmd/dmsg-socks5/commands" - dmsgcurl "github.com/skycoin/dmsg/cmd/dmsgcurl/commands" - dmsghttp "github.com/skycoin/dmsg/cmd/dmsghttp/commands" - dmsgptycli "github.com/skycoin/dmsg/cmd/dmsgpty-cli/commands" - dmsgptyhost "github.com/skycoin/dmsg/cmd/dmsgpty-host/commands" - dmsgptyui "github.com/skycoin/dmsg/cmd/dmsgpty-ui/commands" - dmsgweb "github.com/skycoin/dmsg/cmd/dmsgweb/commands" - sd "github.com/skycoin/skycoin-service-discovery/cmd/service-discovery/commands" "github.com/spf13/cobra" - ar "github.com/skycoin/skywire-services/cmd/address-resolver/commands" - confbs "github.com/skycoin/skywire-services/cmd/config-bootstrapper/commands" - dmsgmon "github.com/skycoin/skywire-services/cmd/dmsg-monitor/commands" - kg "github.com/skycoin/skywire-services/cmd/keys-gen/commands" - lc "github.com/skycoin/skywire-services/cmd/liveness-checker/commands" - nwmon "github.com/skycoin/skywire-services/cmd/network-monitor/commands" - nv "github.com/skycoin/skywire-services/cmd/node-visualizer/commands" - pvmon "github.com/skycoin/skywire-services/cmd/public-visor-monitor/commands" - rf "github.com/skycoin/skywire-services/cmd/route-finder/commands" - ssmon "github.com/skycoin/skywire-services/cmd/skysocks-monitor/commands" - se "github.com/skycoin/skywire-services/cmd/sw-env/commands" - tpd "github.com/skycoin/skywire-services/cmd/transport-discovery/commands" - tps "github.com/skycoin/skywire-services/cmd/transport-setup/commands" - vpnmon "github.com/skycoin/skywire-services/cmd/vpn-monitor/commands" - "github.com/skycoin/skywire-utilities/pkg/buildinfo" - sc "github.com/skycoin/skywire/cmd/apps/skychat/commands" - ssc "github.com/skycoin/skywire/cmd/apps/skysocks-client/commands" - ss "github.com/skycoin/skywire/cmd/apps/skysocks/commands" - vpnc "github.com/skycoin/skywire/cmd/apps/vpn-client/commands" - vpns "github.com/skycoin/skywire/cmd/apps/vpn-server/commands" - sn "github.com/skycoin/skywire/cmd/setup-node/commands" - scli "github.com/skycoin/skywire/cmd/skywire-cli/commands" - "github.com/skycoin/skywire/pkg/visor" + "github.com/skycoin/skywire/cmd/skywire/commands" ) func init() { - dmsgptyCmd.AddCommand( - dmsgptycli.RootCmd, - dmsgptyhost.RootCmd, - dmsgptyui.RootCmd, - ) - dmsgCmd.AddCommand( - dmsgptyCmd, - dmsgdisc.RootCmd, - dmsgserver.RootCmd, - dmsghttp.RootCmd, - dmsgcurl.RootCmd, - dmsgweb.RootCmd, - dmsgsocks.RootCmd, - dmsgmon.RootCmd, - ) - svcCmd.AddCommand( - sn.RootCmd, - tpd.RootCmd, - tps.RootCmd, - ar.RootCmd, - rf.RootCmd, - confbs.RootCmd, - kg.RootCmd, - lc.RootCmd, - nv.RootCmd, - se.RootCmd, - sd.RootCmd, - nwmon.RootCmd, - pvmon.RootCmd, - ssmon.RootCmd, - vpnmon.RootCmd, - ) - appsCmd.AddCommand( - vpns.RootCmd, - vpnc.RootCmd, - ssc.RootCmd, - ss.RootCmd, - sc.RootCmd, - ) - RootCmd.AddCommand( - visor.RootCmd, - scli.RootCmd, - svcCmd, - dmsgCmd, - appsCmd, - treeCmd, - docCmd, - ) - visor.RootCmd.Long = ` - ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ ┬ ┬┬┌─┐┌─┐┬─┐ - └─┐├┴┐└┬┘││││├┬┘├┤───└┐┌┘│└─┐│ │├┬┘ - └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ └┘ ┴└─┘└─┘┴└─` - dmsgcurl.RootCmd.Use = "curl" - dmsgweb.RootCmd.Use = "web" - dmsgptycli.RootCmd.Use = "cli" - dmsgptyhost.RootCmd.Use = "host" - dmsgptyui.RootCmd.Use = "ui" - dmsgdisc.RootCmd.Use = "disc" - dmsgserver.RootCmd.Use = "server" - dmsghttp.RootCmd.Use = "http" - dmsgcurl.RootCmd.Use = "curl" - dmsgweb.RootCmd.Use = "web" - dmsgsocks.RootCmd.Use = "socks" - dmsgmon.RootCmd.Use = "mon" - tpd.RootCmd.Use = "tpd" - tps.RootCmd.Use = "tps" - ar.RootCmd.Use = "ar" - rf.RootCmd.Use = "rf" - confbs.RootCmd.Use = "cb" - kg.RootCmd.Use = "kg" - lc.RootCmd.Use = "lc" - nv.RootCmd.Use = "nv" - vpnmon.RootCmd.Use = "vpnm" - pvmon.RootCmd.Use = "pvm" - ssmon.RootCmd.Use = "ssm" - nwmon.RootCmd.Use = "nwmon" - se.RootCmd.Use = "se" - sd.RootCmd.Use = "sd" - sn.RootCmd.Use = "sn" - scli.RootCmd.Use = "cli" - visor.RootCmd.Use = "visor" - vpns.RootCmd.Use = "vpn-server" - vpnc.RootCmd.Use = "vpn-client" - ssc.RootCmd.Use = "skysocks-client" - ss.RootCmd.Use = "skysocks" - sc.RootCmd.Use = "skychat" - var helpflag bool - RootCmd.SetUsageTemplate(help) - RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for "+RootCmd.Use) - RootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) - RootCmd.PersistentFlags().MarkHidden("help") //nolint - RootCmd.CompletionOptions.DisableDefaultCmd = true - RootCmd.SetUsageTemplate(help) - -} - -// RootCmd contains literally every 'command' from four repos here -var RootCmd = &cobra.Command{ - Use: func() string { - return strings.Split(filepath.Base(strings.ReplaceAll(strings.ReplaceAll(fmt.Sprintf("%v", os.Args), "[", ""), "]", "")), " ")[0] - }(), - Long: ` - ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ - └─┐├┴┐└┬┘││││├┬┘├┤ - └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘`, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, - Version: buildinfo.Version(), -} - -// RootCmd contains all subcommands -var svcCmd = &cobra.Command{ - Use: "svc", - Short: "Skywire services", - Long: ` - ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ ┌─┐┌─┐┬─┐┬ ┬┬┌─┐┌─┐┌─┐ - └─┐├┴┐└┬┘││││├┬┘├┤───└─┐├┤ ├┬┘└┐┌┘││ ├┤ └─┐ - └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ └─┘└─┘┴└─ └┘ ┴└─┘└─┘└─┘`, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, - Version: buildinfo.Version(), -} - -// RootCmd contains all binaries which may be separately compiled as subcommands -var dmsgCmd = &cobra.Command{ - Use: "dmsg", - Short: "Dmsg services & utilities", - Long: ` - ┌┬┐┌┬┐┌─┐┌─┐ - │││││└─┐│ ┬ - ─┴┘┴ ┴└─┘└─┘ `, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, -} - -var dmsgptyCmd = &cobra.Command{ - Use: "pty", - Short: "Dmsg pseudoterminal (pty)", - Long: ` - ┌─┐┌┬┐┬ ┬ - ├─┘ │ └┬┘ - ┴ ┴ ┴ `, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, -} -var appsCmd = &cobra.Command{ - Use: "app", - Short: "skywire native applications", - Long: ` - ┌─┐┌─┐┌─┐┌─┐ - ├─┤├─┘├─┘└─┐ - ┴ ┴┴ ┴ └─┘`, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, -} - -var treeCmd = &cobra.Command{ - Use: "tree", - Short: "subcommand tree", - Long: `subcommand tree`, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - // You can use a LeveledList here, for easy generation. - leveledList := pterm.LeveledList{} - leveledList = append(leveledList, pterm.LeveledListItem{Level: 0, Text: RootCmd.Use}) - for _, j := range RootCmd.Commands() { - use := strings.Split(j.Use, " ") - leveledList = append(leveledList, pterm.LeveledListItem{Level: 1, Text: use[0]}) - for _, k := range j.Commands() { - use := strings.Split(k.Use, " ") - leveledList = append(leveledList, pterm.LeveledListItem{Level: 2, Text: use[0]}) - for _, l := range k.Commands() { - use := strings.Split(l.Use, " ") - leveledList = append(leveledList, pterm.LeveledListItem{Level: 3, Text: use[0]}) - for _, m := range l.Commands() { - use := strings.Split(m.Use, " ") - leveledList = append(leveledList, pterm.LeveledListItem{Level: 4, Text: use[0]}) - for _, n := range m.Commands() { - use := strings.Split(n.Use, " ") - leveledList = append(leveledList, pterm.LeveledListItem{Level: 5, Text: use[0]}) - for _, o := range n.Commands() { - use := strings.Split(o.Use, " ") - leveledList = append(leveledList, pterm.LeveledListItem{Level: 6, Text: use[0]}) - for _, p := range o.Commands() { - use := strings.Split(p.Use, " ") - leveledList = append(leveledList, pterm.LeveledListItem{Level: 7, Text: use[0]}) - } - } - } - } - } - } - } - // Generate tree from LeveledList. - r := putils.TreeFromLeveledList(leveledList) - - // Render TreePrinter - err := pterm.DefaultTree.WithRoot(r).Render() - if err != nil { - log.Fatal("render subcommand tree: ", err) - } - }, -} - -// for toc generation use: https://github.com/ekalinin/github-markdown-toc.go -var docCmd = &cobra.Command{ - Use: "doc", - Short: "generate markdown docs", - Long: `generate markdown docs - - UNHIDEFLAGS=1 go run cmd/skywire/skywire.go doc - - UNHIDEFLAGS=1 go run cmd/skywire/skywire.go doc > cmd/skywire/README1.md - - generate toc: - - cat cmd/skywire/README1.md | gh-md-toc`, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("\n# %s\n", "skywire documentation") - fmt.Printf("\n## %s\n", "subcommand tree") - fmt.Printf("\n%s\n", "A tree representation of the skywire subcommands") - fmt.Printf("\n```\n") - _, err := script.Exec(os.Args[0] + " tree").Stdout() //nolint - if err != nil { - fmt.Println(err.Error()) - } - fmt.Printf("\n```\n") - - var use string - for _, j := range RootCmd.Commands() { - use = strings.Split(j.Use, " ")[0] - fmt.Printf("\n### %s\n", use) - fmt.Printf("\n```\n") - j.Help() //nolint - fmt.Printf("\n```\n") - if j.Name() == "cli" { - fmt.Printf("\n%s\n", "skywire command line interface") - fmt.Printf("\n## %s\n", RootCmd.Use) - fmt.Printf("\n```\n") - RootCmd.Help() //nolint - fmt.Printf("\n```\n") - fmt.Printf("\n## %s\n", "global flags") - fmt.Printf("\n%s\n", "The skywire-cli interacts with the running visor via rpc calls. By default the rpc server is available on localhost:3435. The rpc address and port the visor is using may be changed in the config file, once generated.") - - fmt.Printf("\n%s\n", "It is not recommended to expose the rpc server on the local network. Exposing the rpc allows unsecured access to the machine over the local network") - fmt.Printf("\n```\n") - fmt.Printf("\n%s\n", "Global Flags:") - fmt.Printf("\n%s\n", " --rpc string RPC server address (default \"localhost:3435\")") - fmt.Printf("\n%s\n", " --json bool print output as json") - fmt.Printf("\n```\n") - } - for _, k := range j.Commands() { - use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] - fmt.Printf("\n#### %s\n", use) - fmt.Printf("\n```\n") - k.Help() //nolint - fmt.Printf("\n```\n") - if k.Name() == "survey" { - fmt.Printf("\n```\n") - _, err = script.Exec("sudo " + os.Args[0] + ` survey`).Stdout() //nolint - if err != nil { - fmt.Println(err.Error()) - } - fmt.Printf("\n```\n") - } - for _, l := range k.Commands() { - use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] - fmt.Printf("\n##### %s\n", use) - fmt.Printf("\n```\n") - l.Help() //nolint - fmt.Printf("\n```\n") - if l.Name() == "gen" { - fmt.Printf("\n##### Example for package / msi\n") - fmt.Printf("\n```\n") - fmt.Printf("$ skywire cli config gen -bpirxn\n") - _, err = script.Exec(os.Args[0] + ` cli config gen -bpirxn`).Stdout() //nolint - if err != nil { - fmt.Println(err.Error()) - } - fmt.Printf("\n```\n") - } - for _, m := range l.Commands() { - use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] + " " + strings.Split(m.Use, " ")[0] - fmt.Printf("\n###### %s\n", use) - fmt.Printf("\n```\n") - m.Help() //nolint - fmt.Printf("\n```\n") - for _, n := range m.Commands() { - use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] + " " + strings.Split(m.Use, " ")[0] + " " + strings.Split(n.Use, " ")[0] - fmt.Printf("\n###### %s\n", use) - fmt.Printf("\n```\n") - m.Help() //nolint - fmt.Printf("\n```\n") - for _, o := range n.Commands() { - use = strings.Split(j.Use, " ")[0] + " " + strings.Split(k.Use, " ")[0] + " " + strings.Split(l.Use, " ")[0] + " " + strings.Split(m.Use, " ")[0] + " " + strings.Split(n.Use, " ")[0] + " " + strings.Split(o.Use, " ")[0] - fmt.Printf("\n###### %s\n", use) - fmt.Printf("\n```\n") - m.Help() //nolint - fmt.Printf("\n```\n") - } - } - } - } - } - } - }, + commands.RootCmd.SetUsageTemplate(help) + commands.RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help menu") + commands.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) + commands.RootCmd.PersistentFlags().MarkHidden("help") //nolint } func main() { cc.Init(&cc.Config{ - RootCmd: RootCmd, + RootCmd: commands.RootCmd, Headings: cc.HiBlue + cc.Bold, Commands: cc.HiBlue + cc.Bold, CmdShortDescr: cc.HiBlue, @@ -392,14 +32,12 @@ func main() { NoExtraNewlines: true, NoBottomNewline: true, }) - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - } + commands.Execute() } -const help = "{{if gt (len .Aliases) 0}}" + +const help = "{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" + "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}" + - "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " + + "Available Commands:{{range .Commands}} {{if and (ne .Name \"completion\") .IsAvailableCommand}}\r\n " + "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" + "Flags:\r\n" + "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" + diff --git a/docs/skywire-goda-graph.svg b/docs/skywire-goda-graph.svg new file mode 100644 index 0000000000..589c645b58 --- /dev/null +++ b/docs/skywire-goda-graph.svg @@ -0,0 +1,2417 @@ + + + + + + +G + + + +github.com/skycoin/skywire + + +github.com/skycoin/skywire +83 / 2.8KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat + + +github.com/skycoin/skywire/cmd/apps/skychat +38 / 1.5KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat/commands + + +github.com/skycoin/skywire/cmd/apps/skychat/commands +288 / 7.9KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat:e->github.com/skycoin/skywire/cmd/apps/skychat/commands + + + + + +github.com/skycoin/skywire/pkg/app + + +github.com/skycoin/skywire/pkg/app +336 / 9.3KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat/commands:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/pkg/app/appnet + + +github.com/skycoin/skywire/pkg/app/appnet +841 / 22.7KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat/commands:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/pkg/app/appserver + + +github.com/skycoin/skywire/pkg/app/appserver +1920 / 54.8KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat/commands:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/pkg/routing + + +github.com/skycoin/skywire/pkg/routing +1019 / 30.8KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat/commands:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig + + +github.com/skycoin/skywire/pkg/visor/visorconfig +1404 / 47.7KB + + + + + +github.com/skycoin/skywire/cmd/apps/skychat/commands:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks + + +github.com/skycoin/skywire/cmd/apps/skysocks +38 / 1.5KB + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands +106 / 3.2KB + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks:e->github.com/skycoin/skywire/cmd/apps/skysocks/commands + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client + + +github.com/skycoin/skywire/cmd/apps/skysocks-client +38 / 1.5KB + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands +179 / 5.6KB + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client:e->github.com/skycoin/skywire/cmd/apps/skysocks-client/commands + + + + + +github.com/skycoin/skywire/internal/skysocks + + +github.com/skycoin/skywire/internal/skysocks +294 / 7.2KB + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands:e->github.com/skycoin/skywire/internal/skysocks + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks-client/commands:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands:e->github.com/skycoin/skywire/internal/skysocks + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/cmd/apps/skysocks/commands:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client + + +github.com/skycoin/skywire/cmd/apps/vpn-client +38 / 1.5KB + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands +210 / 6.5KB + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client:e->github.com/skycoin/skywire/cmd/apps/vpn-client/commands + + + + + +github.com/skycoin/skywire/internal/vpn + + +github.com/skycoin/skywire/internal/vpn +1884 / 59.5KB + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands:e->github.com/skycoin/skywire/internal/vpn + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/pkg/app/appevent + + +github.com/skycoin/skywire/pkg/app/appevent +474 / 13.7KB + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands:e->github.com/skycoin/skywire/pkg/app/appevent + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-client/commands:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server + + +github.com/skycoin/skywire/cmd/apps/vpn-server +38 / 1.5KB + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands +144 / 4.3KB + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server:e->github.com/skycoin/skywire/cmd/apps/vpn-server/commands + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands:e->github.com/skycoin/skywire/internal/vpn + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/skyenv + + +github.com/skycoin/skywire/pkg/skyenv +128 / 6.2KB + + + + + +github.com/skycoin/skywire/cmd/apps/vpn-server/commands:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/cmd/setup-node + + +github.com/skycoin/skywire/cmd/setup-node +39 / 1.5KB + + + + + +github.com/skycoin/skywire/cmd/setup-node/commands + + +github.com/skycoin/skywire/cmd/setup-node/commands +98 / 3.1KB + + + + + +github.com/skycoin/skywire/cmd/setup-node:e->github.com/skycoin/skywire/cmd/setup-node/commands + + + + + +github.com/skycoin/skywire/pkg/router + + +github.com/skycoin/skywire/pkg/router +2985 / 95.9KB + + + + + +github.com/skycoin/skywire/cmd/setup-node/commands:e->github.com/skycoin/skywire/pkg/router + + + + + +github.com/skycoin/skywire/pkg/router/setupmetrics + + +github.com/skycoin/skywire/pkg/router/setupmetrics +76 / 2.8KB + + + + + +github.com/skycoin/skywire/cmd/setup-node/commands:e->github.com/skycoin/skywire/pkg/router/setupmetrics + + + + + +github.com/skycoin/skywire/cmd/skywire + + +github.com/skycoin/skywire/cmd/skywire +368 / 13.1KB + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/cmd/apps/skychat/commands + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/cmd/apps/skysocks-client/commands + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/cmd/apps/skysocks/commands + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/cmd/apps/vpn-client/commands + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/cmd/apps/vpn-server/commands + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/cmd/setup-node/commands + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands + + +github.com/skycoin/skywire/cmd/skywire-cli/commands +199 / 7.7KB + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/cmd/skywire-cli/commands + + + + + +github.com/skycoin/skywire/pkg/visor + + +github.com/skycoin/skywire/pkg/visor +7829 / 241.1KB + + + + + +github.com/skycoin/skywire/cmd/skywire:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli + + +github.com/skycoin/skywire/cmd/skywire-cli +38 / 1.4KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli:e->github.com/skycoin/skywire/cmd/skywire-cli/commands + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/completion + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/completion +38 / 1.0KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/completion + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config +1801 / 63.9KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/config + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/dmsgpty + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/dmsgpty +163 / 5.1KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/dmsgpty + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/log + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/log +637 / 22.7KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/log + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/mdisc + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/mdisc +124 / 4.5KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/mdisc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy +378 / 13.5KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/reward + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/reward +181 / 6.1KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/reward + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards +2105 / 376.7KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route +448 / 16.8KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/route + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyfwd + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyfwd +74 / 2.3KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/skyfwd + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyrev + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyrev +88 / 2.9KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/skyrev + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/survey + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/survey +154 / 5.5KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/survey + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp +704 / 25.2KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/tp + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/ut + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/ut +74 / 3.0KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/ut + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor +834 / 25.4KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn +374 / 12.6KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/internal + + +github.com/skycoin/skywire/cmd/skywire-cli/internal +133 / 4.0KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/pkg/dmsgc + + +github.com/skycoin/skywire/pkg/dmsgc +44 / 1.8KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config:e->github.com/skycoin/skywire/pkg/dmsgc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/transport/network + + +github.com/skycoin/skywire/pkg/transport/network +1091 / 33.7KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/config:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/dmsgpty:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/dmsgpty:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/dmsgpty:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/log:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/mdisc:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/mdisc:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc +26 / 0.8KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/servicedisc + + +github.com/skycoin/skywire/pkg/servicedisc +660 / 19.1KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy:e->github.com/skycoin/skywire/pkg/servicedisc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/reward:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/reward:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/reward:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards/tgbot + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards/tgbot +101 / 3.4KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rewards/tgbot + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/pkg/routefinder/rfclient + + +github.com/skycoin/skywire/pkg/routefinder/rfclient +174 / 5.0KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire/pkg/routefinder/rfclient + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire/pkg/router + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/route:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyfwd:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyfwd:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyrev:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/skyrev:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/survey:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/survey:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/survey:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/pkg/transport + + +github.com/skycoin/skywire/pkg/transport +1604 / 47.9KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp:e->github.com/skycoin/skywire/pkg/transport + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/tp:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/ut:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/ut:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/pkg/app/appcommon + + +github.com/skycoin/skywire/pkg/app/appcommon +597 / 15.8KB + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/visor:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire/cmd/skywire-cli/commands/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire/cmd/skywire-cli/internal + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire/pkg/servicedisc + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/cmd/skywire-visor + + +github.com/skycoin/skywire/cmd/skywire-visor +41 / 1.5KB + + + + + +github.com/skycoin/skywire/cmd/skywire-visor:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/example/example-client-app + + +github.com/skycoin/skywire/example/example-client-app +131 / 3.7KB + + + + + +github.com/skycoin/skywire/example/example-client-app:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/example/example-client-app:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/example/example-client-app:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/example/example-client-app:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/example/example-client-app:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/example/example-server-app + + +github.com/skycoin/skywire/example/example-server-app +96 / 2.7KB + + + + + +github.com/skycoin/skywire/example/example-server-app:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/example/example-server-app:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/example/example-server-app:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/example/http-server + + +github.com/skycoin/skywire/example/http-server +74 / 1.9KB + + + + + +github.com/skycoin/skywire/example/http-server/html + + +github.com/skycoin/skywire/example/http-server/html +29 / 626B + + + + + +github.com/skycoin/skywire/example/http-server:e->github.com/skycoin/skywire/example/http-server/html + + + + + +github.com/skycoin/skywire/example/http-server:e->github.com/skycoin/skywire/pkg/visor + + + + + +github.com/skycoin/skywire/internal/httpauth + + +github.com/skycoin/skywire/internal/httpauth +261 / 7.9KB + + + + + +github.com/skycoin/skywire/internal/packetfilter + + +github.com/skycoin/skywire/internal/packetfilter +69 / 2.1KB + + + + + +github.com/skycoin/skywire/internal/skysocks:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/internal/skysocks:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/internal/skysocks:e->github.com/skycoin/skywire/pkg/router + + + + + +github.com/skycoin/skywire/internal/skysocks:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/internal/testhelpers + + +github.com/skycoin/skywire/internal/testhelpers +9 / 290B + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/app + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/routefinder/rfclient + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/router + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/util/osutil + + +github.com/skycoin/skywire/pkg/util/osutil +149 / 4.3KB + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/util/osutil + + + + + +github.com/skycoin/skywire/internal/vpn:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/pkg/app:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/pkg/app:e->github.com/skycoin/skywire/pkg/app/appevent + + + + + +github.com/skycoin/skywire/pkg/app:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/pkg/app:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/pkg/app/idmanager + + +github.com/skycoin/skywire/pkg/app/idmanager +224 / 5.4KB + + + + + +github.com/skycoin/skywire/pkg/app:e->github.com/skycoin/skywire/pkg/app/idmanager + + + + + +github.com/skycoin/skywire/pkg/app:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/app/appcommon:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/app/appdisc + + +github.com/skycoin/skywire/pkg/app/appdisc +128 / 3.8KB + + + + + +github.com/skycoin/skywire/pkg/app/appdisc:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/pkg/app/appdisc:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/pkg/app/appdisc:e->github.com/skycoin/skywire/pkg/servicedisc + + + + + +github.com/skycoin/skywire/pkg/app/appdisc:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/app/appevent:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/pkg/app/appnet:e->github.com/skycoin/skywire/pkg/router + + + + + +github.com/skycoin/skywire/pkg/app/appnet:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/app/appnet:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/app/appdisc + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/app/appevent + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/app/idmanager + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/util/rpcutil + + +github.com/skycoin/skywire/pkg/util/rpcutil +30 / 0.8KB + + + + + +github.com/skycoin/skywire/pkg/app/appserver:e->github.com/skycoin/skywire/pkg/util/rpcutil + + + + + +github.com/skycoin/skywire/pkg/app/launcher + + +github.com/skycoin/skywire/pkg/app/launcher +398 / 11.7KB + + + + + +github.com/skycoin/skywire/pkg/app/launcher:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/pkg/app/launcher:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/pkg/app/launcher:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/pkg/app/launcher:e->github.com/skycoin/skywire/pkg/router + + + + + +github.com/skycoin/skywire/pkg/util/pathutil + + +github.com/skycoin/skywire/pkg/util/pathutil +197 / 6.6KB + + + + + +github.com/skycoin/skywire/pkg/app/launcher:e->github.com/skycoin/skywire/pkg/util/pathutil + + + + + +github.com/skycoin/skywire/pkg/dmsgc:e->github.com/skycoin/skywire/pkg/app/appevent + + + + + +github.com/skycoin/skywire/pkg/routefinder/rfclient:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/dmsgc + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/routefinder/rfclient + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/router/setupmetrics + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/transport + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/pkg/util/deadline + + +github.com/skycoin/skywire/pkg/util/deadline +72 / 1.8KB + + + + + +github.com/skycoin/skywire/pkg/router:e->github.com/skycoin/skywire/pkg/util/deadline + + + + + +github.com/skycoin/skywire/pkg/router/setupmetrics:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/servicedisc:e->github.com/skycoin/skywire/internal/httpauth + + + + + +github.com/skycoin/skywire/pkg/servicedisc:e->github.com/skycoin/skywire/pkg/transport + + + + + +github.com/skycoin/skywire/pkg/servicedisc:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/pkg/transport:e->github.com/skycoin/skywire/pkg/app/appevent + + + + + +github.com/skycoin/skywire/pkg/transport:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/transport:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/transport:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/pkg/transport/network/addrresolver + + +github.com/skycoin/skywire/pkg/transport/network/addrresolver +564 / 16.9KB + + + + + +github.com/skycoin/skywire/pkg/transport:e->github.com/skycoin/skywire/pkg/transport/network/addrresolver + + + + + +github.com/skycoin/skywire/pkg/transport/deprecated + + +github.com/skycoin/skywire/pkg/transport/deprecated +14 / 555B + + + + + +github.com/skycoin/skywire/pkg/transport/network:e->github.com/skycoin/skywire/internal/packetfilter + + + + + +github.com/skycoin/skywire/pkg/transport/network:e->github.com/skycoin/skywire/pkg/app/appevent + + + + + +github.com/skycoin/skywire/pkg/transport/network:e->github.com/skycoin/skywire/pkg/transport/network/addrresolver + + + + + +github.com/skycoin/skywire/pkg/transport/network/handshake + + +github.com/skycoin/skywire/pkg/transport/network/handshake +220 / 6.1KB + + + + + +github.com/skycoin/skywire/pkg/transport/network:e->github.com/skycoin/skywire/pkg/transport/network/handshake + + + + + +github.com/skycoin/skywire/pkg/transport/network/porter + + +github.com/skycoin/skywire/pkg/transport/network/porter +69 / 1.6KB + + + + + +github.com/skycoin/skywire/pkg/transport/network:e->github.com/skycoin/skywire/pkg/transport/network/porter + + + + + +github.com/skycoin/skywire/pkg/transport/network/stcp + + +github.com/skycoin/skywire/pkg/transport/network/stcp +80 / 2.2KB + + + + + +github.com/skycoin/skywire/pkg/transport/network:e->github.com/skycoin/skywire/pkg/transport/network/stcp + + + + + +github.com/skycoin/skywire/pkg/transport/network/addrresolver:e->github.com/skycoin/skywire/internal/httpauth + + + + + +github.com/skycoin/skywire/pkg/transport/network/addrresolver:e->github.com/skycoin/skywire/internal/packetfilter + + + + + +github.com/skycoin/skywire/pkg/transport/setup + + +github.com/skycoin/skywire/pkg/transport/setup +167 / 5.2KB + + + + + +github.com/skycoin/skywire/pkg/transport/setup:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/transport/setup:e->github.com/skycoin/skywire/pkg/transport + + + + + +github.com/skycoin/skywire/pkg/transport/setup:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/pkg/transport/tpdclient + + +github.com/skycoin/skywire/pkg/transport/tpdclient +142 / 4.8KB + + + + + +github.com/skycoin/skywire/pkg/transport/tpdclient:e->github.com/skycoin/skywire/internal/httpauth + + + + + +github.com/skycoin/skywire/pkg/transport/tpdclient:e->github.com/skycoin/skywire/pkg/transport + + + + + +github.com/skycoin/skywire/pkg/utclient + + +github.com/skycoin/skywire/pkg/utclient +158 / 4.5KB + + + + + +github.com/skycoin/skywire/pkg/utclient:e->github.com/skycoin/skywire/internal/httpauth + + + + + +github.com/skycoin/skywire/pkg/util/cipherutil + + +github.com/skycoin/skywire/pkg/util/cipherutil +20 / 528B + + + + + +github.com/skycoin/skywire/pkg/util/rename + + +github.com/skycoin/skywire/pkg/util/rename +60 / 1.7KB + + + + + +github.com/skycoin/skywire/pkg/util/pathutil:e->github.com/skycoin/skywire/pkg/util/rename + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/internal/vpn + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/app/appcommon + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/app/appdisc + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/app/appevent + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/app/appnet + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/app/launcher + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/dmsgc + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/routefinder/rfclient + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/router + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/servicedisc + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/transport + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/transport/network/addrresolver + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/transport/network/stcp + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/transport/setup + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/transport/tpdclient + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/utclient + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/util/cipherutil + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/util/osutil + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/util/pathutil + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/util/rpcutil + + + + + +github.com/skycoin/skywire/pkg/visor/dmsgtracker + + +github.com/skycoin/skywire/pkg/visor/dmsgtracker +265 / 6.9KB + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/visor/dmsgtracker + + + + + +github.com/skycoin/skywire/pkg/visor/logserver + + +github.com/skycoin/skywire/pkg/visor/logserver +219 / 6.5KB + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/visor/logserver + + + + + +github.com/skycoin/skywire/pkg/visor/logstore + + +github.com/skycoin/skywire/pkg/visor/logstore +77 / 2.4KB + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/visor/logstore + + + + + +github.com/skycoin/skywire/pkg/visor/rewardconfig + + +github.com/skycoin/skywire/pkg/visor/rewardconfig +37 / 1.1KB + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/visor/rewardconfig + + + + + +github.com/skycoin/skywire/pkg/visor/usermanager + + +github.com/skycoin/skywire/pkg/visor/usermanager +552 / 15.8KB + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/visor/usermanager + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/pkg/visor/visorinit + + +github.com/skycoin/skywire/pkg/visor/visorinit +130 / 3.6KB + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/pkg/visor/visorinit + + + + + +github.com/skycoin/skywire/static/icons + + +github.com/skycoin/skywire/static/icons +8 / 143B + + + + + +github.com/skycoin/skywire/pkg/visor:e->github.com/skycoin/skywire/static/icons + + + + + +github.com/skycoin/skywire/pkg/visor/dmsgtracker:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/pkg/visor/logserver:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/pkg/visor/usermanager:e->github.com/skycoin/skywire/pkg/visor/visorconfig + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/app/appserver + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/app/launcher + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/dmsgc + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/routing + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/skyenv + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/transport + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/transport/network + + + + + +github.com/skycoin/skywire/pkg/visor/visorconfig:e->github.com/skycoin/skywire/pkg/util/pathutil + + + + + diff --git a/go.mod b/go.mod index 5b867b8bdd..1ca5081f1f 100644 --- a/go.mod +++ b/go.mod @@ -30,10 +30,10 @@ require ( github.com/pterm/pterm v0.12.66 github.com/robert-nix/ansihtml v1.0.1 github.com/sirupsen/logrus v1.9.3 - github.com/skycoin/dmsg v1.3.25 + github.com/skycoin/dmsg v1.3.26-0.20240924024628-ed5195787d40 github.com/skycoin/skycoin v0.27.1 - github.com/skycoin/skycoin-service-discovery v1.3.25 - github.com/skycoin/skywire-services v1.3.25 + github.com/skycoin/skycoin-service-discovery v1.3.26-0.20240923193126-2175869a0af5 + github.com/skycoin/skywire-services v1.3.26-0.20240924025250-cb6c2e8fe08f github.com/skycoin/skywire-utilities v1.3.25 github.com/skycoin/systray v1.10.0 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 @@ -80,11 +80,12 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-chi/cors v1.2.1 // indirect + github.com/go-chi/httprate v0.9.0 // indirect + github.com/go-echarts/go-echarts/v2 v2.3.3 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.15.1 // indirect - github.com/go-redis/redis v6.15.9+incompatible // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -169,10 +170,10 @@ require ( replace github.com/xxxserxxx/gotop/v4 => github.com/ersonp/gotop/v4 v4.2.1 // Uncomment for tests with local sources of other skywire repos -// replace github.com/skycoin/dmsg => ../dmsg -// replace github.com/skycoin/skywire-services => ../skywire-services -// replace github.com/skycoin/skycoin-service-discovery => ../skycoin-service-discovery -// replace github.com/skycoin/skywire-utilities => ../skywire-utilities +//replace github.com/skycoin/dmsg => ../dmsg +//replace github.com/skycoin/skywire-services => ../skywire-services +//replace github.com/skycoin/skycoin-service-discovery => ../skycoin-service-discovery +//replace github.com/skycoin/skywire-utilities => ../skywire-utilities // Uncomment to update other skywire deps to specific commit hash. // run `go mod tidy ; go mod vendor` @@ -180,5 +181,7 @@ replace github.com/xxxserxxx/gotop/v4 => github.com/ersonp/gotop/v4 v4.2.1 // then, re-comment the line before saving. // replace github.com/skycoin/dmsg => github.com/skycoin/dmsg // replace github.com/skycoin/skywire-services => github.com/skycoin/skywire-services +//replace github.com/skycoin/skywire-services => github.com/skycoin/skywire-services + // replace github.com/skycoin/skycoin-service-discovery => github.com/skycoin/skycoin-service-discovery // replace github.com/skycoin/skywire-utilities => github.com/skycoin/skywire-utilities diff --git a/go.sum b/go.sum index 3b8cd6e2f7..a344729dca 100644 --- a/go.sum +++ b/go.sum @@ -227,6 +227,10 @@ github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-chi/httprate v0.9.0 h1:21A+4WDMDA5FyWcg7mNrhj63aNT8CGh+Z1alOE/piU8= +github.com/go-chi/httprate v0.9.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A= +github.com/go-echarts/go-echarts/v2 v2.3.3 h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg= +github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -682,20 +686,16 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skycoin/dmsg v1.3.25 h1:Gs4aRhLq/ZCo5I0vN3nTv/N5/MC6sunWCuS50Tct6qI= -github.com/skycoin/dmsg v1.3.25/go.mod h1:3pyc9MmDJQYP0spTAWKLMctz4+ZKjMZgXtdMmXpYolw= +github.com/skycoin/dmsg v1.3.26-0.20240924024628-ed5195787d40 h1:GKUDgXHWahO45dDuivC36h6rRVfg6R02d8oDhQp2t74= +github.com/skycoin/dmsg v1.3.26-0.20240924024628-ed5195787d40/go.mod h1:qfF8cAig3JHLAojmfYEWn8ttV4aHsiitkZeD1TH8Wrg= github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 h1:1Nc5EBY6pjfw1kwW0duwyG+7WliWz5u9kgk1h5MnLuA= github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:UXghlricA7J3aRD/k7p/zBObQfmBawwCxIVPVjz2Q3o= github.com/skycoin/skycoin v0.27.1 h1:HatxsRwVSPaV4qxH6290xPBmkH/HgiuAoY2qC+e8C9I= github.com/skycoin/skycoin v0.27.1/go.mod h1:78nHjQzd8KG0jJJVL/j0xMmrihXi70ti63fh8vXScJw= -github.com/skycoin/skycoin-service-discovery v0.0.0-20240627171358-7f59a951fa29 h1:Y7EJVrs41zHGLHvqTH69Lx2Rc39s+umxIlZ439osBeA= -github.com/skycoin/skycoin-service-discovery v0.0.0-20240627171358-7f59a951fa29/go.mod h1:v2r2hqtBsF9rb1wBoJWn21YCiGm0EmGgnhrwD4c54yY= -github.com/skycoin/skycoin-service-discovery v1.3.25 h1:vnB4Is0/+rYuE9pH3+pBdnTbKQYQV3h9BvzxHZi7GB8= -github.com/skycoin/skycoin-service-discovery v1.3.25/go.mod h1:D/4UQpOMdv8In2IY3P7+pLJUgJDqrpq7vpjX7dQEPng= -github.com/skycoin/skywire-services v0.0.0-20240627171623-87edf49d9025 h1:48nukMj8f8VsUlNuGfJP/49wqfzqfJshoKXkjKf4Qyc= -github.com/skycoin/skywire-services v0.0.0-20240627171623-87edf49d9025/go.mod h1:007npHSDcpTeQiG7e3h36W5IMl2+YT8N6nUB0pwtSYs= -github.com/skycoin/skywire-services v1.3.25 h1:LzUsNx7CNBTodkhkyl6fmJ1pbHY7WALiS4mNw8PKPe0= -github.com/skycoin/skywire-services v1.3.25/go.mod h1:gRucqzsrQmzyPYBfYLJYFgi3+o98j+04oVFY0jRLScs= +github.com/skycoin/skycoin-service-discovery v1.3.26-0.20240923193126-2175869a0af5 h1:YuUEIh0ZxpprE6cHgTks3nsA9W0h6MAeTiTYzvRvlyY= +github.com/skycoin/skycoin-service-discovery v1.3.26-0.20240923193126-2175869a0af5/go.mod h1:vfFU+qDJkxdf2AgPPem/WbKRxm2dNsWy1d+HJ3mluSM= +github.com/skycoin/skywire-services v1.3.26-0.20240924025250-cb6c2e8fe08f h1:Jyd+js9PUSGb8RcKgOhZ/wqpvkpLIV4dU6fPcQNBDvw= +github.com/skycoin/skywire-services v1.3.26-0.20240924025250-cb6c2e8fe08f/go.mod h1:4SS2gwkGUZU9vjYUVpS74Roo+tWhug+uTRVu+fjbgqs= github.com/skycoin/skywire-utilities v1.3.25 h1:mk8dUonFdhVopFF3d9wbOyXXoiuAO+mN1y+ve6SzgX4= github.com/skycoin/skywire-utilities v1.3.25/go.mod h1:yFKWpL1bDRPKU3uK+cTF4PnYUMe+eyIj5N2bk4sF5Cw= github.com/skycoin/systray v1.10.0 h1:fQZJHMylpVvfmOOTLvUssfyHVDoC8Idx6Ba2BlLEuGg= diff --git a/internal/skysocks/server_test.go b/internal/skysocks/server_test.go index 809a8175ff..1306d81030 100644 --- a/internal/skysocks/server_test.go +++ b/internal/skysocks/server_test.go @@ -70,7 +70,7 @@ func TestProxy(t *testing.T) { proxyDial, err := proxy.SOCKS5("tcp", ":10080", nil, proxy.Direct) require.NoError(t, err) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := fmt.Fprintln(w, "Hello, client") require.NoError(t, err) })) diff --git a/pkg/app/appcommon/hello.go b/pkg/app/appcommon/hello.go index 807d73ecf1..a648d15d6e 100644 --- a/pkg/app/appcommon/hello.go +++ b/pkg/app/appcommon/hello.go @@ -63,7 +63,7 @@ func WriteHello(w io.Writer, hello Hello) error { raw := make([]byte, 2+len(helloRaw)) size := len(helloRaw) - binary.BigEndian.PutUint16(raw[:2], uint16(size)) + binary.BigEndian.PutUint16(raw[:2], uint16(size)) //nolint if n := copy(raw[2:], helloRaw); n != size { panic("hello write does not add up") } diff --git a/pkg/app/appdisc/factory.go b/pkg/app/appdisc/factory.go index 9ac8548f3a..9b193610eb 100644 --- a/pkg/app/appdisc/factory.go +++ b/pkg/app/appdisc/factory.go @@ -2,13 +2,14 @@ package appdisc import ( + "encoding/json" "net/http" "github.com/sirupsen/logrus" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/logging" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/pkg/app/appcommon" "github.com/skycoin/skywire/pkg/servicedisc" "github.com/skycoin/skywire/pkg/skyenv" @@ -31,7 +32,15 @@ func (f *Factory) setDefaults() { f.Log = logging.MustGetLogger("appdisc") } if f.ServiceDisc == "" { - f.ServiceDisc = utilenv.ServiceDiscAddr + var envServices skywire.EnvServices + var services skywire.Services + var sdURL string + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + sdURL = services.ServiceDiscovery + } + } + f.ServiceDisc = sdURL } } diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index cd6d0d0b08..96b6f9d210 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -439,7 +439,8 @@ func (p *Proc) ConnectionsSummary() []ConnectionSummary { } var summaries []ConnectionSummary - rpcGW.cm.DoRange(func(id uint16, v interface{}) bool { + // rpcGW.cm.DoRange(func(id uint16, v interface{}) bool { + rpcGW.cm.DoRange(func(_ uint16, v interface{}) bool { if v == nil { summaries = append(summaries, ConnectionSummary{}) return true diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index 357dde3bb5..b79e6278ff 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -221,7 +221,7 @@ func (m *procManager) Start(conf appcommon.ProcConfig) (appcommon.ProcID, error) return 0, err } delete(m.errors, conf.AppName) - return appcommon.ProcID(proc.cmd.Process.Pid), nil + return appcommon.ProcID(proc.cmd.Process.Pid), nil //nolint } // Register registers a proc for an external app. diff --git a/pkg/app/idmanager/manager_test.go b/pkg/app/idmanager/manager_test.go index c570754a5f..e69f32ca47 100644 --- a/pkg/app/idmanager/manager_test.go +++ b/pkg/app/idmanager/manager_test.go @@ -426,7 +426,7 @@ func TestManager_DoRange(t *testing.T) { } for i, v := range vals { - _, err := m.Add(uint16(i), v) + _, err := m.Add(uint16(i), v) //nolint require.NoError(t, err) } diff --git a/pkg/router/network_stats.go b/pkg/router/network_stats.go index f954ec2a1b..c151e3ef12 100644 --- a/pkg/router/network_stats.go +++ b/pkg/router/network_stats.go @@ -32,7 +32,7 @@ func (s *networkStats) SetLatency(latency uint32) { func (s *networkStats) Latency() time.Duration { latencyMs := atomic.LoadUint32(&s.latency) // the latency is store in uint32 of millisecond but time.Duration takes nanosecond - return time.Duration(latencyMs * uint32(time.Millisecond.Nanoseconds())) + return time.Duration(latencyMs * uint32(time.Millisecond.Nanoseconds())) //nolint } func (s *networkStats) SetUploadSpeed(speed uint32) { diff --git a/pkg/router/route_group.go b/pkg/router/route_group.go index 9b8293e9b0..ca694c4a02 100644 --- a/pkg/router/route_group.go +++ b/pkg/router/route_group.go @@ -449,7 +449,7 @@ func (rg *RouteGroup) sendPing() error { throughput := rg.networkStats.RemoteThroughput() timestamp := time.Now().UTC().UnixNano() / int64(time.Millisecond) - rg.networkStats.SetDownloadSpeed(uint32(throughput)) + rg.networkStats.SetDownloadSpeed(uint32(throughput)) //nolint packet := routing.MakePingPacket(rule.NextRouteID(), timestamp, throughput) @@ -697,7 +697,7 @@ func (rg *RouteGroup) handleErrorPacket(packet routing.Packet) error { return nil } - rg.SetError(fmt.Errorf(string(packet.Payload()))) + rg.SetError(fmt.Errorf("%v", string(packet.Payload()))) return nil } @@ -723,9 +723,9 @@ func (rg *RouteGroup) handlePingPacket(packet routing.Packet) error { rg.logger.WithField("func", "RouteGroup.handlePingPacket").Tracef("Throughput is around %d", throughput) - rg.networkStats.SetUploadSpeed(uint32(throughput)) + rg.networkStats.SetUploadSpeed(uint32(throughput)) //nolint - return rg.sendPong(int64(timestamp)) + return rg.sendPong(int64(timestamp)) //nolint } func (rg *RouteGroup) handlePongPacket(packet routing.Packet) error { @@ -734,13 +734,13 @@ func (rg *RouteGroup) handlePongPacket(packet routing.Packet) error { sentAtMs := binary.BigEndian.Uint64(payload) ms := sentAtMs % 1000 - sentAt := time.Unix(int64(sentAtMs/1000), int64(ms)*int64(time.Millisecond)).UTC() + sentAt := time.Unix(int64(sentAtMs/1000), int64(ms)*int64(time.Millisecond)).UTC() //nolint latency := time.Now().UTC().Sub(sentAt).Milliseconds() rg.logger.WithField("func", "RouteGroup.handlePongPacket").Tracef("Latency is around %d ms", latency) - rg.networkStats.SetLatency(uint32(latency)) + rg.networkStats.SetLatency(uint32(latency)) //nolint return nil } diff --git a/pkg/router/router.go b/pkg/router/router.go index c18f28b22f..45a99a36b7 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -990,11 +990,11 @@ func (r *router) forwardPacket(ctx context.Context, packet routing.Packet, rule case routing.ClosePacket: p = routing.MakeClosePacket(rule.NextRouteID(), routing.CloseCode(packet.Payload()[0])) case routing.PingPacket: - timestamp := int64(binary.BigEndian.Uint64(packet[routing.PacketPayloadOffset:])) - throughput := int64(binary.BigEndian.Uint64(packet[routing.PacketPayloadOffset+8:])) + timestamp := int64(binary.BigEndian.Uint64(packet[routing.PacketPayloadOffset:])) //nolint + throughput := int64(binary.BigEndian.Uint64(packet[routing.PacketPayloadOffset+8:])) //nolint p = routing.MakePingPacket(rule.NextRouteID(), timestamp, throughput) case routing.PongPacket: - timestamp := int64(binary.BigEndian.Uint64(packet[routing.PacketPayloadOffset:])) + timestamp := int64(binary.BigEndian.Uint64(packet[routing.PacketPayloadOffset:])) //nolint p = routing.MakePongPacket(rule.NextRouteID(), timestamp) case routing.ErrorPacket: var err error @@ -1328,7 +1328,8 @@ func (r *router) removeRouteGroupOfRule(rule routing.Rule) { } func (r *router) checkIfTransportAvailable() (ok bool) { - r.tm.WalkTransports(func(tp *transport.ManagedTransport) bool { + // r.tm.WalkTransports(func(tp *transport.ManagedTransport) bool { + r.tm.WalkTransports(func(_ *transport.ManagedTransport) bool { ok = true return ok }) diff --git a/pkg/router/rpc_gateway_test.go b/pkg/router/rpc_gateway_test.go index 7752d4ff13..a0d14c8bf7 100644 --- a/pkg/router/rpc_gateway_test.go +++ b/pkg/router/rpc_gateway_test.go @@ -123,10 +123,10 @@ func TestRPCGateway_ReserveIDs(t *testing.T) { gateway := NewRPCGateway(r, mlog) - var gotIds []routing.RouteID - err := gateway.ReserveIDs(uint8(n), &gotIds) + var gotIDs []routing.RouteID + err := gateway.ReserveIDs(uint8(n), &gotIDs) //nolint require.NoError(t, err) - require.Equal(t, ids, gotIds) + require.Equal(t, ids, gotIDs) }) t.Run("fail reserving keys", func(t *testing.T) { @@ -140,9 +140,9 @@ func TestRPCGateway_ReserveIDs(t *testing.T) { Msg: testhelpers.Err.Error(), } - var gotIds []routing.RouteID - err := gateway.ReserveIDs(uint8(n), &gotIds) + var gotIDs []routing.RouteID + err := gateway.ReserveIDs(uint8(n), &gotIDs) //nolint require.Equal(t, wantErr, err) - require.Nil(t, gotIds) + require.Nil(t, gotIDs) }) } diff --git a/pkg/router/setupmetrics/victoria_metrics.go b/pkg/router/setupmetrics/victoria_metrics.go index 32f32e9aca..6a2b592929 100644 --- a/pkg/router/setupmetrics/victoria_metrics.go +++ b/pkg/router/setupmetrics/victoria_metrics.go @@ -43,7 +43,7 @@ func (m *VictoriaMetrics) RecordRequest() func(rules *routing.EdgeRules, err *er start := time.Now() m.activeRequests.Inc() - return func(rules *routing.EdgeRules, err *error) { + return func(_ *routing.EdgeRules, err *error) { if *err == nil { m.reqDurationsSuccesses.UpdateDuration(start) } else { diff --git a/pkg/routing/packet.go b/pkg/routing/packet.go index d75502815c..5fbd602a65 100644 --- a/pkg/routing/packet.go +++ b/pkg/routing/packet.go @@ -105,7 +105,7 @@ func MakeDataPacket(id RouteID, payload []byte) (Packet, error) { packet[PacketTypeOffset] = byte(DataPacket) binary.BigEndian.PutUint32(packet[PacketRouteIDOffset:], uint32(id)) - binary.BigEndian.PutUint16(packet[PacketPayloadSizeOffset:], uint16(len(payload))) + binary.BigEndian.PutUint16(packet[PacketPayloadSizeOffset:], uint16(len(payload))) //nolint copy(packet[PacketPayloadOffset:], payload) return packet, nil @@ -187,7 +187,7 @@ func MakeErrorPacket(id RouteID, errPayload []byte) (Packet, error) { packet[PacketTypeOffset] = byte(ErrorPacket) binary.BigEndian.PutUint32(packet[PacketRouteIDOffset:], uint32(id)) - binary.BigEndian.PutUint16(packet[PacketPayloadSizeOffset:], uint16(len(errPayload))) + binary.BigEndian.PutUint16(packet[PacketPayloadSizeOffset:], uint16(len(errPayload))) //nolint copy(packet[PacketPayloadOffset:], errPayload) return packet, nil diff --git a/pkg/routing/rule.go b/pkg/routing/rule.go index 1d0b091fc8..6aa22cfec8 100644 --- a/pkg/routing/rule.go +++ b/pkg/routing/rule.go @@ -65,7 +65,7 @@ func (r Rule) assertLen(l int) { // KeepAlive returns rule's keep-alive timeout. func (r Rule) KeepAlive() time.Duration { r.assertLen(RuleHeaderSize) - return time.Duration(binary.BigEndian.Uint64(r[0:8])) + return time.Duration(binary.BigEndian.Uint64(r[0:8])) //nolint } // setKeepAlive sets rule's keep-alive timeout. diff --git a/pkg/routing/table.go b/pkg/routing/table.go index bc85fe8695..954986abb1 100644 --- a/pkg/routing/table.go +++ b/pkg/routing/table.go @@ -92,7 +92,7 @@ func (mt *memTable) reserveKeysImpl(n int) (first, last RouteID, err error) { } first = mt.nextID + 1 - mt.nextID += RouteID(n) + mt.nextID += RouteID(n) //nolint last = mt.nextID return first, last, nil diff --git a/pkg/servicedisc/types.go b/pkg/servicedisc/types.go index b3d44ec58e..9e9b2c5b40 100644 --- a/pkg/servicedisc/types.go +++ b/pkg/servicedisc/types.go @@ -82,7 +82,7 @@ func (a *SWAddr) UnmarshalText(text []byte) error { if err != nil { return err } - binary.BigEndian.PutUint16(a[len(a)-2:], uint16(port)) + binary.BigEndian.PutUint16(a[len(a)-2:], uint16(port)) //nolint return nil } diff --git a/pkg/transport/handshake.go b/pkg/transport/handshake.go index ab3636584f..a773da678a 100644 --- a/pkg/transport/handshake.go +++ b/pkg/transport/handshake.go @@ -96,7 +96,7 @@ func (hs SettlementHS) Do(ctx context.Context, dc DiscoveryClient, transport net // The handshake logic only REGISTERS the transport, and does not update the status of the transport. func MakeSettlementHS(init bool, log *logging.Logger) SettlementHS { // initiating logic. - initHS := func(ctx context.Context, dc DiscoveryClient, transport network.Transport, sk cipher.SecKey) (err error) { + initHS := func(ctx context.Context, dc DiscoveryClient, transport network.Transport, sk cipher.SecKey) (err error) { //nolint entry := makeEntryFromTransport(transport) // create signed entry and send it to responding visor. diff --git a/pkg/transport/network/addrresolver/client_test.go b/pkg/transport/network/addrresolver/client_test.go index c80473a20d..7f1b1655e4 100644 --- a/pkg/transport/network/addrresolver/client_test.go +++ b/pkg/transport/network/addrresolver/client_test.go @@ -74,7 +74,7 @@ func TestBind(t *testing.T) { testPubKey, testSecKey := cipher.GenerateKeyPair() urlCh := make(chan string, 1) - srv := httptest.NewServer(authHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(authHandler(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { urlCh <- r.URL.String() }))) @@ -95,7 +95,8 @@ func authHandler(next http.Handler) http.Handler { r := chi.NewRouter() r.Handle("/security/nonces/{pk}", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { + // func(w http.ResponseWriter, r *http.Request) { + func(w http.ResponseWriter, _ *http.Request) { if err := json.NewEncoder(w).Encode(&httpauth.NextNonceResponse{Edge: testPubKey, NextNonce: 1}); err != nil { log.WithError(err).Error("Failed to encode nonce response") } diff --git a/pkg/transport/network/handshake/handshake.go b/pkg/transport/network/handshake/handshake.go index 125c6e325b..46fccfc77b 100644 --- a/pkg/transport/network/handshake/handshake.go +++ b/pkg/transport/network/handshake/handshake.go @@ -64,7 +64,7 @@ type Handshake func(conn net.Conn, deadline time.Time) (lAddr, rAddr dmsg.Addr, // InitiatorHandshake creates the handshake logic on the initiator's side. func InitiatorHandshake(lSK cipher.SecKey, localAddr, remoteAddr dmsg.Addr) Handshake { - return handshakeMiddleware(func(conn net.Conn, deadline time.Time) (lAddr, rAddr dmsg.Addr, err error) { + return handshakeMiddleware(func(conn net.Conn, _ time.Time) (lAddr, rAddr dmsg.Addr, err error) { if err = writeFrame0(conn); err != nil { return dmsg.Addr{}, dmsg.Addr{}, err } @@ -113,7 +113,8 @@ func MakeF2PortChecker(portChecker func(port uint16) error) CheckF2 { // ResponderHandshake creates the handshake logic on the responder's side. func ResponderHandshake(checkF2 CheckF2) Handshake { - return handshakeMiddleware(func(conn net.Conn, deadline time.Time) (lAddr, rAddr dmsg.Addr, err error) { + // return handshakeMiddleware(func(conn net.Conn, deadline time.Time) (lAddr, rAddr dmsg.Addr, err error) { + return handshakeMiddleware(func(conn net.Conn, _ time.Time) (lAddr, rAddr dmsg.Addr, err error) { if err = readFrame0(conn); err != nil { return dmsg.Addr{}, dmsg.Addr{}, err } diff --git a/pkg/transport/tpdclient/client_test.go b/pkg/transport/tpdclient/client_test.go index 230317215b..3f75143a1c 100644 --- a/pkg/transport/tpdclient/client_test.go +++ b/pkg/transport/tpdclient/client_test.go @@ -101,7 +101,7 @@ func TestRegisterTransportResponses(t *testing.T) { }{ { "StatusCreated", - func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) }, + func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusCreated) }, func(err error) { require.NoError(t, err) }, }, // TODO(evaninjin): Not sure why this is failing and why this is expected behavior. @@ -112,12 +112,14 @@ func TestRegisterTransportResponses(t *testing.T) { //}, { "StatusInternalServerError", - func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, + // func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, + func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, func(err error) { require.Error(t, err) }, }, { "JSONError", - func(w http.ResponseWriter, r *http.Request) { + // func(w http.ResponseWriter, r *http.Request) { + func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) require.NoError(t, json.NewEncoder(w).Encode(JSONError{Error: "boom"})) }, @@ -129,7 +131,8 @@ func TestRegisterTransportResponses(t *testing.T) { }, { "NonJSONError", - func(w http.ResponseWriter, r *http.Request) { + // func(w http.ResponseWriter, r *http.Request) { + func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, err := fmt.Fprintf(w, "boom") require.NoError(t, err) @@ -142,7 +145,8 @@ func TestRegisterTransportResponses(t *testing.T) { }, { "Request", - func(w http.ResponseWriter, r *http.Request) { + // func(w http.ResponseWriter, r *http.Request) { + func(_ http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "/transports/", r.URL.String()) }, @@ -229,7 +233,8 @@ func authHandler(t *testing.T, next http.Handler) http.Handler { r := chi.NewRouter() r.Handle("/security/nonces/{pk}", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { + // func(w http.ResponseWriter, r *http.Request) { + func(w http.ResponseWriter, _ *http.Request) { require.NoError(t, json.NewEncoder(w).Encode(&httpauth.NextNonceResponse{Edge: testPubKey, NextNonce: 1})) }, )) diff --git a/pkg/utclient/client_test.go b/pkg/utclient/client_test.go index 3fcc4c94b2..626a4415ae 100644 --- a/pkg/utclient/client_test.go +++ b/pkg/utclient/client_test.go @@ -67,7 +67,8 @@ func TestClientAuth(t *testing.T) { func TestUpdateVisorUptime(t *testing.T) { urlCh := make(chan string, 1) - srv := httptest.NewServer(authHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // srv := httptest.NewServer(authHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(authHandler(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { urlCh <- r.URL.String() }))) @@ -86,7 +87,8 @@ func authHandler(next http.Handler) http.Handler { r := chi.NewRouter() log := logging.MustGetLogger("utclient") r.Handle("/security/nonces/{pk}", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { + // func(w http.ResponseWriter, r *http.Request) { + func(w http.ResponseWriter, _ *http.Request) { if err := json.NewEncoder(w).Encode(&httpauth.NextNonceResponse{Edge: testPubKey, NextNonce: 1}); err != nil { log.WithError(err).Error("Failed to encode nonce response") } diff --git a/pkg/visor/hypervisor.go b/pkg/visor/hypervisor.go index 8ebbfebdea..182b4e5010 100644 --- a/pkg/visor/hypervisor.go +++ b/pkg/visor/hypervisor.go @@ -179,7 +179,7 @@ func (hv *Hypervisor) AddMockData(config MockConfig) error { hv.remoteVisors[pk] = Conn{ Addr: dmsg.Addr{ PK: pk, - Port: uint16(i), + Port: uint16(i), //nolint }, API: client, } @@ -1111,7 +1111,7 @@ func (hv *Hypervisor) deleteRoutes() http.HandlerFunc { } continue } - routeID := routing.RouteID(ridUint64) + routeID := routing.RouteID(ridUint64) //nolint contains := false for _, rule := range rules { if rule.KeyRouteID() == routeID { @@ -1502,7 +1502,7 @@ func ridFromParam(r *http.Request, key string) (routing.RouteID, error) { return 0, errors.New("invalid route ID provided") } - return routing.RouteID(rid), nil + return routing.RouteID(rid), nil //nolint } func strSliceFromQuery(r *http.Request, key string, defaultVal []string) []string { diff --git a/pkg/visor/init.go b/pkg/visor/init.go index 4f341c1b88..c793712ade 100644 --- a/pkg/visor/init.go +++ b/pkg/visor/init.go @@ -30,10 +30,10 @@ import ( "github.com/skycoin/dmsg/pkg/dmsghttp" "github.com/skycoin/dmsg/pkg/dmsgpty" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/logging" "github.com/skycoin/skywire-utilities/pkg/netutil" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/internal/vpn" "github.com/skycoin/skywire/pkg/app/appdisc" "github.com/skycoin/skywire/pkg/app/appevent" @@ -640,7 +640,7 @@ func initSkywireForwardConn(ctx context.Context, v *Visor, log *logging.Logger) log.Debug("Accepting sky forwarding conn...") conn, err := l.Accept() if err != nil { - if !errors.Is(appnet.ErrClosedConn, err) { + if !errors.Is(err, appnet.ErrClosedConn) { log.WithError(err).Error("Failed to accept conn") } return @@ -1516,8 +1516,14 @@ func initPublicAutoconnect(ctx context.Context, v *Visor, log *logging.Logger) e return nil } serviceDisc := v.conf.Launcher.ServiceDisc - if serviceDisc == "" { - serviceDisc = utilenv.ServiceDiscAddr + if serviceDisc == "" { //it might be intentionally blank ; consider revising. + var envServices skywire.EnvServices + var services skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if err := json.Unmarshal(envServices.Prod, &services); err == nil { + serviceDisc = services.ServiceDiscovery + } + } } // todo: refactor updatedisc: split connecting to services in updatedisc and diff --git a/pkg/visor/visorconfig/config.go b/pkg/visor/visorconfig/config.go index d456596052..b598410362 100644 --- a/pkg/visor/visorconfig/config.go +++ b/pkg/visor/visorconfig/config.go @@ -11,9 +11,9 @@ import ( "github.com/skycoin/dmsg/pkg/dmsgpty" coinCipher "github.com/skycoin/skycoin/src/cipher" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/logging" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/pkg/app/appserver" "github.com/skycoin/skywire/pkg/dmsgc" "github.com/skycoin/skywire/pkg/routing" @@ -25,34 +25,20 @@ import ( // This is used as default values if no config is given, or for missing *required* fields. // This function always returns the latest config version. func MakeBaseConfig(common *Common, testEnv bool, dmsgHTTP bool, services *Services, dmsgHTTPServersList *DmsgHTTPServers) *V1 { + //check if any services were passed if services == nil { - //fall back on defaults + var envServices skywire.EnvServices + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err != nil { + return nil + } if !testEnv { - services = &Services{ - DmsgDiscovery: utilenv.DmsgDiscAddr, - TransportDiscovery: utilenv.TpDiscAddr, - AddressResolver: utilenv.AddressResolverAddr, - RouteFinder: utilenv.RouteFinderAddr, - RouteSetupNodes: MustPKs(utilenv.RouteSetupPKs), - TransportSetupPKs: MustPKs(utilenv.TPSetupPKs), - UptimeTracker: utilenv.UptimeTrackerAddr, - ServiceDiscovery: utilenv.ServiceDiscAddr, - StunServers: utilenv.GetStunServers(), - DNSServer: utilenv.DNSServer, + if err := json.Unmarshal(envServices.Prod, &services); err != nil { + return nil } } else { - services = &Services{ - DmsgDiscovery: utilenv.TestDmsgDiscAddr, - TransportDiscovery: utilenv.TestTpDiscAddr, - AddressResolver: utilenv.TestAddressResolverAddr, - RouteFinder: utilenv.TestRouteFinderAddr, - RouteSetupNodes: MustPKs(utilenv.TestRouteSetupPKs), - TransportSetupPKs: MustPKs(utilenv.TestTPSetupPKs), - UptimeTracker: utilenv.TestUptimeTrackerAddr, - ServiceDiscovery: utilenv.TestServiceDiscAddr, - StunServers: utilenv.GetStunServers(), - DNSServer: utilenv.DNSServer, + if err := json.Unmarshal(envServices.Test, &services); err != nil { + return nil } } } @@ -61,14 +47,14 @@ func MakeBaseConfig(common *Common, testEnv bool, dmsgHTTP bool, services *Servi conf.Common = common } conf.Dmsg = &dmsgc.DmsgConfig{ - Discovery: services.DmsgDiscovery, //utilenv.DmsgDiscAddr, + Discovery: services.DmsgDiscovery, SessionsCount: 1, Servers: []*disc.Entry{}, ConnectedServersType: "all", } conf.Transport = &Transport{ - Discovery: services.TransportDiscovery, //utilenv.TpDiscAddr, - AddressResolver: services.AddressResolver, //utilenv.AddressResolverAddr, + Discovery: services.TransportDiscovery, + AddressResolver: services.AddressResolver, PublicAutoconnect: PublicAutoconnect, LogStore: &LogStore{ Type: FileLogStore, @@ -79,25 +65,25 @@ func MakeBaseConfig(common *Common, testEnv bool, dmsgHTTP bool, services *Servi StcprPort: 0, } conf.Routing = &Routing{ - RouteFinder: services.RouteFinder, //utilenv.RouteFinderAddr, - RouteSetupNodes: services.RouteSetupNodes, //[]cipher.PubKey{utilenv.MustPK(utilenv.SetupPK)}, + RouteFinder: services.RouteFinder, + RouteSetupNodes: services.RouteSetupNodes, RouteFinderTimeout: DefaultTimeout, } conf.Launcher = &Launcher{ - ServiceDisc: services.ServiceDiscovery, //utilenv.ServiceDiscAddr, + ServiceDisc: services.ServiceDiscovery, Apps: nil, ServerAddr: AppSrvAddr, BinPath: AppBinPath, DisplayNodeIP: false, } conf.UptimeTracker = &UptimeTracker{ - Addr: services.UptimeTracker, //utilenv.UptimeTrackerAddr, + Addr: services.UptimeTracker, } conf.CLIAddr = RPCAddr conf.LogLevel = LogLevel conf.LocalPath = LocalPath conf.DmsgHTTPServerPath = LocalPath + "/" + Custom - conf.StunServers = services.StunServers //utilenv.GetStunServers() + conf.StunServers = services.StunServers conf.ShutdownTimeout = DefaultTimeout conf.Dmsgpty = &Dmsgpty{ @@ -148,9 +134,22 @@ func MakeDefaultConfig(log *logging.MasterLogger, sk *cipher.SecKey, usrEnv bool if err != nil { return nil, err } + dnsServer := "" var dmsgHTTPServersList *DmsgHTTPServers - - dnsServer := utilenv.DNSServer + var envServices skywire.EnvServices + var svcs skywire.Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err != nil { + return nil, nil + } + if !testEnv { + if err := json.Unmarshal(envServices.Prod, &svcs); err != nil { + dnsServer = svcs.DNSServer + } + } else { + if err := json.Unmarshal(envServices.Test, &svcs); err != nil { + dnsServer = svcs.DNSServer + } + } if services != nil { if services.DNSServer != "" { dnsServer = services.DNSServer diff --git a/pkg/visor/visorconfig/hypervisorconfig.go b/pkg/visor/visorconfig/hypervisorconfig.go index 99b284ea37..aa2476cda5 100644 --- a/pkg/visor/visorconfig/hypervisorconfig.go +++ b/pkg/visor/visorconfig/hypervisorconfig.go @@ -11,8 +11,8 @@ import ( "path/filepath" "time" + "github.com/skycoin/skywire" "github.com/skycoin/skywire-utilities/pkg/cipher" - utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv" "github.com/skycoin/skywire/pkg/util/pathutil" ) @@ -112,24 +112,34 @@ func (c *HypervisorConfig) FillDefaults(testEnv bool) { } if c.DmsgDiscovery == "" { - if testEnv { - c.DmsgDiscovery = utilenv.TestDmsgDiscAddr - } else { - c.DmsgDiscovery = utilenv.DmsgDiscAddr + var envServices EnvServices + var services Services + if err := json.Unmarshal([]byte(skywire.ServicesJSON), &envServices); err == nil { + if testEnv { + if err := json.Unmarshal(envServices.Test, &services); err != nil { + return + } + } else { + if err := json.Unmarshal(envServices.Prod, &services); err != nil { + return + } + } + + c.DmsgDiscovery = services.DmsgDiscovery } - } - if c.DmsgPort == 0 { - c.DmsgPort = DmsgHypervisorPort - } - if c.HTTPAddr == "" { - c.HTTPAddr = httpAddr - } - c.Cookies.FillDefaults() - c.EnableAuth = EnableAuth - c.EnableTLS = EnableTLS - c.TLSCertFile = TLSCert - c.TLSKeyFile = TLSKey + if c.DmsgPort == 0 { + c.DmsgPort = DmsgHypervisorPort + } + if c.HTTPAddr == "" { + c.HTTPAddr = httpAddr + } + c.Cookies.FillDefaults() + c.EnableAuth = EnableAuth + c.EnableTLS = EnableTLS + c.TLSCertFile = TLSCert + c.TLSKeyFile = TLSKey + } } // Parse parses the file in path, and decodes to the config. diff --git a/pkg/visor/visorconfig/services.go b/pkg/visor/visorconfig/services.go index 44ff133a83..c3f3c4c8f3 100644 --- a/pkg/visor/visorconfig/services.go +++ b/pkg/visor/visorconfig/services.go @@ -52,7 +52,13 @@ func Fetch(mLog *logging.MasterLogger, serviceConf string, stdout bool) (service return services } -// Services are subdomains and IP addresses of the skywire services +// EnvServices is the struct for the outer JSON +type EnvServices struct { + Test json.RawMessage `json:"test"` + Prod json.RawMessage `json:"prod"` +} + +// Services is subdomains and IP addresses of the skywire services type Services struct { DmsgDiscovery string `json:"dmsg_discovery,omitempty"` TransportDiscovery string `json:"transport_discovery,omitempty"` diff --git a/pkg/visor/visorconfig/v1.go b/pkg/visor/visorconfig/v1.go index b74fd5bd7a..3f19d26fe2 100644 --- a/pkg/visor/visorconfig/v1.go +++ b/pkg/visor/visorconfig/v1.go @@ -290,13 +290,13 @@ func (v1 *V1) AddAppConfig(launch *launcher.AppLauncher, appName, binaryName str for { min := 10 max := 99 - randomNumber = rand.Intn(max-min+1) + min //nolint - if _, ok := busyPorts[routing.Port(randomNumber)]; !ok { + randomNumber = rand.Intn(max-min+1) + min //nolint + if _, ok := busyPorts[routing.Port(randomNumber)]; !ok { //nolint break } } - conf.Apps = append(conf.Apps, appserver.AppConfig{Name: appName, Binary: binaryName, Port: routing.Port(randomNumber)}) + conf.Apps = append(conf.Apps, appserver.AppConfig{Name: appName, Binary: binaryName, Port: routing.Port(randomNumber)}) //nolint launch.ResetConfig(launcher.AppLauncherConfig{ VisorPK: v1.PK, diff --git a/skywire.go b/skywire.go index ac53ec55aa..ee99b8062b 100644 --- a/skywire.go +++ b/skywire.go @@ -63,7 +63,6 @@ var Test Services // TestConf is the service configuration address / URL for the skywire test deployment var TestConf Conf -// initialize the embedded files into variables func init() { var js interface{} err := json.Unmarshal([]byte(ServicesJSON), &js) diff --git a/vendor/github.com/go-chi/httprate/LICENSE b/vendor/github.com/go-chi/httprate/LICENSE new file mode 100644 index 0000000000..0bb58ba519 --- /dev/null +++ b/vendor/github.com/go-chi/httprate/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka). + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/go-chi/httprate/README.md b/vendor/github.com/go-chi/httprate/README.md new file mode 100644 index 0000000000..118f393dab --- /dev/null +++ b/vendor/github.com/go-chi/httprate/README.md @@ -0,0 +1,92 @@ +# httprate + +![](https://github.com/go-chi/httprate/workflows/build/badge.svg?branch=master) + +net/http request rate limiter based on the Sliding Window Counter pattern inspired by +CloudFlare https://blog.cloudflare.com/counting-things-a-lot-of-different-things/. + +The sliding window counter pattern is accurate, smooths traffic and offers a simple counter +design to share a rate-limit among a cluster of servers. For example, if you'd like +to use redis to coordinate a rate-limit across a group of microservices you just need +to implement the httprate.LimitCounter interface to support an atomic increment +and get. + + +## Example + +```go +package main + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/httprate" +) + +func main() { + r := chi.NewRouter() + r.Use(middleware.Logger) + + // Enable httprate request limiter of 100 requests per minute. + // + // In the code example below, rate-limiting is bound to the request IP address + // via the LimitByIP middleware handler. + // + // To have a single rate-limiter for all requests, use httprate.LimitAll(..). + // + // Please see _example/main.go for other more, or read the library code. + r.Use(httprate.LimitByIP(100, 1*time.Minute)) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(".")) + }) + + http.ListenAndServe(":3333", r) +} +``` + +## Common use cases + +### Rate limit by IP and URL path (aka endpoint) +```go + r.Use(httprate.Limit( + 10, // requests + 10*time.Second, // per duration + httprate.WithKeyFuncs(httprate.KeyByIP, httprate.KeyByEndpoint), + )) +``` + +### Rate limit by arbitrary keys +```go + r.Use(httprate.Limit( + 100, // requests + 1*time.Minute, // per duration + // an oversimplified example of rate limiting by a custom header + httprate.WithKeyFuncs(func(r *http.Request) (string, error) { + return r.Header.Get("X-Access-Token"), nil + }), + )) +``` + +### Send specific response for rate limited requests + +```go + r.Use(httprate.Limit( + 10, // requests + 1*time.Second, // per duration + httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "some specific response here", http.StatusTooManyRequests) + }), + )) +``` + +## Related packages + +Redis backend for httprate: https://github.com/go-chi/httprate-redis + + +## LICENSE + +MIT diff --git a/vendor/github.com/go-chi/httprate/context.go b/vendor/github.com/go-chi/httprate/context.go new file mode 100644 index 0000000000..138110d5b3 --- /dev/null +++ b/vendor/github.com/go-chi/httprate/context.go @@ -0,0 +1,32 @@ +package httprate + +import "context" + +type ctxKey int + +const ( + incrementKey ctxKey = iota + requestLimitKey +) + +func WithIncrement(ctx context.Context, value int) context.Context { + return context.WithValue(ctx, incrementKey, value) +} + +func getIncrement(ctx context.Context) int { + if value, ok := ctx.Value(incrementKey).(int); ok { + return value + } + return 1 +} + +func WithRequestLimit(ctx context.Context, value int) context.Context { + return context.WithValue(ctx, requestLimitKey, value) +} + +func getRequestLimit(ctx context.Context) int { + if value, ok := ctx.Value(requestLimitKey).(int); ok { + return value + } + return 0 +} diff --git a/vendor/github.com/go-chi/httprate/httprate.go b/vendor/github.com/go-chi/httprate/httprate.go new file mode 100644 index 0000000000..1bf204dee8 --- /dev/null +++ b/vendor/github.com/go-chi/httprate/httprate.go @@ -0,0 +1,141 @@ +package httprate + +import ( + "net" + "net/http" + "strings" + "time" +) + +func Limit(requestLimit int, windowLength time.Duration, options ...Option) func(next http.Handler) http.Handler { + return NewRateLimiter(requestLimit, windowLength, options...).Handler +} + +type KeyFunc func(r *http.Request) (string, error) +type Option func(rl *rateLimiter) + +func LimitAll(requestLimit int, windowLength time.Duration) func(next http.Handler) http.Handler { + return Limit(requestLimit, windowLength) +} + +func LimitByIP(requestLimit int, windowLength time.Duration) func(next http.Handler) http.Handler { + return Limit(requestLimit, windowLength, WithKeyFuncs(KeyByIP)) +} + +func LimitByRealIP(requestLimit int, windowLength time.Duration) func(next http.Handler) http.Handler { + return Limit(requestLimit, windowLength, WithKeyFuncs(KeyByRealIP)) +} + +func KeyByIP(r *http.Request) (string, error) { + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + ip = r.RemoteAddr + } + return canonicalizeIP(ip), nil +} + +func KeyByRealIP(r *http.Request) (string, error) { + var ip string + + if tcip := r.Header.Get("True-Client-IP"); tcip != "" { + ip = tcip + } else if xrip := r.Header.Get("X-Real-IP"); xrip != "" { + ip = xrip + } else if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + i := strings.Index(xff, ", ") + if i == -1 { + i = len(xff) + } + ip = xff[:i] + } else { + var err error + ip, _, err = net.SplitHostPort(r.RemoteAddr) + if err != nil { + ip = r.RemoteAddr + } + } + + return canonicalizeIP(ip), nil +} + +func KeyByEndpoint(r *http.Request) (string, error) { + return r.URL.Path, nil +} + +func WithKeyFuncs(keyFuncs ...KeyFunc) Option { + return func(rl *rateLimiter) { + if len(keyFuncs) > 0 { + rl.keyFn = composedKeyFunc(keyFuncs...) + } + } +} + +func WithKeyByIP() Option { + return WithKeyFuncs(KeyByIP) +} + +func WithKeyByRealIP() Option { + return WithKeyFuncs(KeyByRealIP) +} + +func WithLimitHandler(h http.HandlerFunc) Option { + return func(rl *rateLimiter) { + rl.onRequestLimit = h + } +} + +func WithLimitCounter(c LimitCounter) Option { + return func(rl *rateLimiter) { + rl.limitCounter = c + } +} + +func WithNoop() Option { + return func(rl *rateLimiter) {} +} + +func composedKeyFunc(keyFuncs ...KeyFunc) KeyFunc { + return func(r *http.Request) (string, error) { + var key strings.Builder + for i := 0; i < len(keyFuncs); i++ { + k, err := keyFuncs[i](r) + if err != nil { + return "", err + } + key.WriteString(k) + key.WriteRune(':') + } + return key.String(), nil + } +} + +// canonicalizeIP returns a form of ip suitable for comparison to other IPs. +// For IPv4 addresses, this is simply the whole string. +// For IPv6 addresses, this is the /64 prefix. +func canonicalizeIP(ip string) string { + isIPv6 := false + // This is how net.ParseIP decides if an address is IPv6 + // https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/ip.go;l=704 + for i := 0; !isIPv6 && i < len(ip); i++ { + switch ip[i] { + case '.': + // IPv4 + return ip + case ':': + // IPv6 + isIPv6 = true + break + } + } + if !isIPv6 { + // Not an IP address at all + return ip + } + + ipv6 := net.ParseIP(ip) + if ipv6 == nil { + return ip + } + + return ipv6.Mask(net.CIDRMask(64, 128)).String() +} diff --git a/vendor/github.com/go-chi/httprate/limiter.go b/vendor/github.com/go-chi/httprate/limiter.go new file mode 100644 index 0000000000..1ec1fcf563 --- /dev/null +++ b/vendor/github.com/go-chi/httprate/limiter.go @@ -0,0 +1,226 @@ +package httprate + +import ( + "fmt" + "math" + "net/http" + "sync" + "time" + + "github.com/cespare/xxhash/v2" +) + +type LimitCounter interface { + Config(requestLimit int, windowLength time.Duration) + Increment(key string, currentWindow time.Time) error + IncrementBy(key string, currentWindow time.Time, amount int) error + Get(key string, currentWindow, previousWindow time.Time) (int, int, error) +} + +func NewRateLimiter(requestLimit int, windowLength time.Duration, options ...Option) *rateLimiter { + return newRateLimiter(requestLimit, windowLength, options...) +} + +func newRateLimiter(requestLimit int, windowLength time.Duration, options ...Option) *rateLimiter { + rl := &rateLimiter{ + requestLimit: requestLimit, + windowLength: windowLength, + } + + for _, opt := range options { + opt(rl) + } + + if rl.keyFn == nil { + rl.keyFn = func(r *http.Request) (string, error) { + return "*", nil + } + } + + if rl.limitCounter == nil { + rl.limitCounter = &localCounter{ + counters: make(map[uint64]*count), + windowLength: windowLength, + } + } + rl.limitCounter.Config(requestLimit, windowLength) + + if rl.onRequestLimit == nil { + rl.onRequestLimit = func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests) + } + } + + return rl +} + +type rateLimiter struct { + requestLimit int + windowLength time.Duration + keyFn KeyFunc + limitCounter LimitCounter + onRequestLimit http.HandlerFunc + mu sync.Mutex +} + +func (l *rateLimiter) Counter() LimitCounter { + return l.limitCounter +} + +func (l *rateLimiter) Status(key string) (bool, float64, error) { + return l.calculateRate(key, l.requestLimit) +} + +func (l *rateLimiter) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + key, err := l.keyFn(r) + if err != nil { + http.Error(w, err.Error(), http.StatusPreconditionRequired) + return + } + + currentWindow := time.Now().UTC().Truncate(l.windowLength) + ctx := r.Context() + + limit := l.requestLimit + if val := getRequestLimit(ctx); val > 0 { + limit = val + } + w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", limit)) + w.Header().Set("X-RateLimit-Remaining", fmt.Sprintf("%d", 0)) + w.Header().Set("X-RateLimit-Reset", fmt.Sprintf("%d", currentWindow.Add(l.windowLength).Unix())) + + l.mu.Lock() + _, rate, err := l.calculateRate(key, limit) + if err != nil { + l.mu.Unlock() + http.Error(w, err.Error(), http.StatusPreconditionRequired) + return + } + nrate := int(math.Round(rate)) + + if limit > nrate { + w.Header().Set("X-RateLimit-Remaining", fmt.Sprintf("%d", limit-nrate)) + } + + if nrate >= limit { + l.mu.Unlock() + w.Header().Set("Retry-After", fmt.Sprintf("%d", int(l.windowLength.Seconds()))) // RFC 6585 + l.onRequestLimit(w, r) + return + } + + err = l.limitCounter.IncrementBy(key, currentWindow, getIncrement(r.Context())) + if err != nil { + l.mu.Unlock() + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + l.mu.Unlock() + + next.ServeHTTP(w, r) + }) +} + +func (l *rateLimiter) calculateRate(key string, requestLimit int) (bool, float64, error) { + t := time.Now().UTC() + currentWindow := t.Truncate(l.windowLength) + previousWindow := currentWindow.Add(-l.windowLength) + + currCount, prevCount, err := l.limitCounter.Get(key, currentWindow, previousWindow) + if err != nil { + return false, 0, err + } + + diff := t.Sub(currentWindow) + rate := float64(prevCount)*(float64(l.windowLength)-float64(diff))/float64(l.windowLength) + float64(currCount) + if rate > float64(requestLimit) { + return false, rate, nil + } + + return true, rate, nil +} + +type localCounter struct { + counters map[uint64]*count + windowLength time.Duration + lastEvict time.Time + mu sync.Mutex +} + +var _ LimitCounter = &localCounter{} + +type count struct { + value int + updatedAt time.Time +} + +func (c *localCounter) Config(requestLimit int, windowLength time.Duration) { + c.mu.Lock() + defer c.mu.Unlock() + c.windowLength = windowLength +} + +func (c *localCounter) Increment(key string, currentWindow time.Time) error { + return c.IncrementBy(key, currentWindow, 1) +} + +func (c *localCounter) IncrementBy(key string, currentWindow time.Time, amount int) error { + c.evict() + + c.mu.Lock() + defer c.mu.Unlock() + + hkey := LimitCounterKey(key, currentWindow) + + v, ok := c.counters[hkey] + if !ok { + v = &count{} + c.counters[hkey] = v + } + v.value += amount + v.updatedAt = time.Now() + + return nil +} + +func (c *localCounter) Get(key string, currentWindow, previousWindow time.Time) (int, int, error) { + c.mu.Lock() + defer c.mu.Unlock() + + curr, ok := c.counters[LimitCounterKey(key, currentWindow)] + if !ok { + curr = &count{value: 0, updatedAt: time.Now()} + } + prev, ok := c.counters[LimitCounterKey(key, previousWindow)] + if !ok { + prev = &count{value: 0, updatedAt: time.Now()} + } + + return curr.value, prev.value, nil +} + +func (c *localCounter) evict() { + c.mu.Lock() + defer c.mu.Unlock() + + d := c.windowLength * 3 + + if time.Since(c.lastEvict) < d { + return + } + c.lastEvict = time.Now() + + for k, v := range c.counters { + if time.Since(v.updatedAt) >= d { + delete(c.counters, k) + } + } +} + +func LimitCounterKey(key string, window time.Time) uint64 { + h := xxhash.New() + h.WriteString(key) + h.WriteString(fmt.Sprintf("%d", window.Unix())) + return h.Sum64() +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/LICENSE b/vendor/github.com/go-echarts/go-echarts/v2/LICENSE new file mode 100644 index 0000000000..17f9843e8d --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019~now chenjiandongx + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/go-echarts/go-echarts/v2/actions/global.go b/vendor/github.com/go-echarts/go-echarts/v2/actions/global.go new file mode 100644 index 0000000000..5bf5a564c3 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/actions/global.go @@ -0,0 +1,29 @@ +package actions + +import ( + "math/rand" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// Type kind of dispatch action +type Type string + +// Areas means select-boxes. Multi-boxes can be specified. +// If Areas is empty, all of the select-boxes will be deleted. +// The first area. +type Areas struct { + + //BrushType Optional: 'polygon', 'rect', 'lineX', 'lineY' + BrushType string `json:"brushType,omitempty"` + + // CoordRange Only for "coordinate system area", define the area with the + // coordinates. + CoordRange []string `json:"coordRange,omitempty"` + + // XAxisIndex Assigns which of the xAxisIndex can use Area selecting. + XAxisIndex interface{} `json:"xAxisIndex,omitempty"` +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/bar.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/bar.go new file mode 100644 index 0000000000..27f73fe431 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/bar.go @@ -0,0 +1,63 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Bar represents a bar chart. +type Bar struct { + RectChart + + isXYReversal bool +} + +// Type returns the chart type. +func (*Bar) Type() string { return types.ChartBar } + +// NewBar creates a new bar chart instance. +func NewBar() *Bar { + c := &Bar{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// EnablePolarType enables the polar bar. +func (c *Bar) EnablePolarType() *Bar { + c.hasXYAxis = false + c.hasPolar = true + return c +} + +// SetXAxis sets the X axis. +func (c *Bar) SetXAxis(x interface{}) *Bar { + c.xAxisData = x + return c +} + +// AddSeries adds the new series. +func (c *Bar) AddSeries(name string, data []opts.BarData, options ...SeriesOpts) *Bar { + series := SingleSeries{Name: name, Type: types.ChartBar, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// XYReversal checks if X axis and Y axis are reversed. +func (c *Bar) XYReversal() *Bar { + c.isXYReversal = true + return c +} + +// Validate validates the given configuration. +func (c *Bar) Validate() { + c.XAxisList[0].Data = c.xAxisData + if c.isXYReversal { + c.YAxisList[0].Data = c.xAxisData + c.XAxisList[0].Data = nil + } + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/bar3d.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/bar3d.go new file mode 100644 index 0000000000..a01423486f --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/bar3d.go @@ -0,0 +1,30 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Bar3D represents a 3D bar chart. +type Bar3D struct { + Chart3D +} + +// Type returns the chart type. +func (*Bar3D) Type() string { return types.ChartBar3D } + +// NewBar3D creates a new 3D bar chart. +func NewBar3D() *Bar3D { + c := &Bar3D{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.initChart3D() + return c +} + +// AddSeries adds the new series. +func (c *Bar3D) AddSeries(name string, data []opts.Chart3DData, options ...SeriesOpts) *Bar3D { + c.addSeries(types.ChartBar3D, name, data, options...) + return c +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/base.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/base.go new file mode 100644 index 0000000000..5f888f4d79 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/base.go @@ -0,0 +1,435 @@ +package charts + +import ( + "bytes" + "encoding/json" + "html/template" + + "github.com/go-echarts/go-echarts/v2/actions" + "github.com/go-echarts/go-echarts/v2/datasets" + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" +) + +// GlobalOpts sets the Global options for charts. +type GlobalOpts func(bc *BaseConfiguration) + +// GlobalActions sets the Global actions for charts +type GlobalActions func(ba *BaseActions) + +// BaseConfiguration represents an option set needed by all chart types. +type BaseConfiguration struct { + opts.Legend `json:"legend"` + opts.Tooltip `json:"tooltip"` + opts.Toolbox `json:"toolbox"` + opts.Title `json:"title"` + opts.Polar `json:"polar"` + opts.AngleAxis `json:"angleAxis"` + opts.RadiusAxis `json:"radiusAxis"` + opts.Brush `json:"brush"` + *opts.AxisPointer `json:"axisPointer"` + Calendar []*opts.Calendar `json:"calendar"` + + render.Renderer `json:"-"` + opts.Initialization `json:"-"` + opts.Assets `json:"-"` + opts.RadarComponent `json:"-"` + opts.GeoComponent `json:"-"` + opts.ParallelComponent `json:"-"` + opts.JSFunctions `json:"-"` + opts.SingleAxis `json:"-"` + + MultiSeries + XYAxis + + opts.XAxis3D + opts.YAxis3D + opts.ZAxis3D + opts.Grid3D + opts.Grid + + legends []string + // Colors is the color list of palette. + // If no color is set in series, the colors would be adopted sequentially and circularly + // from this list as the colors of series. + Colors []string + appendColor []string // append customize color to the Colors(reverse order) + + // Animation whether enable the animation, default true + Animation bool `json:"animation" default:"true"` + + // Array of datasets, managed by AddDataset() + DatasetList []opts.Dataset `json:"dataset,omitempty"` + + DataZoomList []opts.DataZoom `json:"datazoom,omitempty"` + VisualMapList []opts.VisualMap `json:"visualmap,omitempty"` + + // ParallelAxisList represents the component list which is the coordinate axis for parallel coordinate. + ParallelAxisList []opts.ParallelAxis + + has3DAxis bool + hasXYAxis bool + hasGeo bool + hasRadar bool + hasParallel bool + hasSingleAxis bool + hasPolar bool + hasBrush bool + + GridList []opts.Grid `json:"grid,omitempty"` +} + +// BaseActions represents a dispatchAction set needed by all chart types. +type BaseActions struct { + actions.Type `json:"type,omitempty"` + actions.Areas `json:"areas,omitempty"` +} + +// JSON wraps all the options to a map so that it could be used in the base template +// +// Get data in bytes +// bs, _ : = json.Marshal(bar.JSON()) +func (bc *BaseConfiguration) JSON() map[string]interface{} { + return bc.json() +} + +// JSONNotEscaped works like method JSON, but it returns a marshaled object whose characters will not be escaped in the template +func (bc *BaseConfiguration) JSONNotEscaped() template.HTML { + obj := bc.json() + buff := bytes.NewBufferString("") + enc := json.NewEncoder(buff) + enc.SetEscapeHTML(false) + enc.Encode(obj) + + return template.HTML(buff.String()) +} + +// JSONNotEscapedAction works like method JSON, but it returns a marshaled object whose characters will not be escaped in the template +func (ba *BaseActions) JSONNotEscapedAction() template.HTML { + obj := ba.json() + buff := bytes.NewBufferString("") + enc := json.NewEncoder(buff) + enc.SetEscapeHTML(false) + enc.Encode(obj) + + return template.HTML(buff.String()) +} + +func (bc *BaseConfiguration) json() map[string]interface{} { + obj := map[string]interface{}{ + "title": bc.Title, + "legend": bc.Legend, + "animation": bc.Animation, + "tooltip": bc.Tooltip, + "series": bc.MultiSeries, + } + // if only one item, use it directly instead of an Array + if len(bc.DatasetList) == 1 { + obj["dataset"] = bc.DatasetList[0] + } else if len(bc.DatasetList) > 1 { + obj["dataset"] = bc.DatasetList + + } + if bc.AxisPointer != nil { + obj["axisPointer"] = bc.AxisPointer + } + + if bc.hasPolar { + obj["polar"] = bc.Polar + obj["angleAxis"] = bc.AngleAxis + obj["radiusAxis"] = bc.RadiusAxis + } + + if bc.hasGeo { + obj["geo"] = bc.GeoComponent + } + + if bc.hasRadar { + obj["radar"] = bc.RadarComponent + } + + if bc.hasParallel { + obj["parallel"] = bc.ParallelComponent + obj["parallelAxis"] = bc.ParallelAxisList + } + + if bc.hasSingleAxis { + obj["singleAxis"] = bc.SingleAxis + } + + if bc.Toolbox.Show { + obj["toolbox"] = bc.Toolbox + } + + if len(bc.DataZoomList) > 0 { + obj["dataZoom"] = bc.DataZoomList + } + + if len(bc.VisualMapList) > 0 { + obj["visualMap"] = bc.VisualMapList + } + + if bc.hasXYAxis { + obj["xAxis"] = bc.XAxisList + obj["yAxis"] = bc.YAxisList + } + + if bc.has3DAxis { + obj["xAxis3D"] = bc.XAxis3D + obj["yAxis3D"] = bc.YAxis3D + obj["zAxis3D"] = bc.ZAxis3D + obj["grid3D"] = bc.Grid3D + } + + if bc.Theme == "white" { + obj["color"] = bc.Colors + } + + if bc.BackgroundColor != "" { + obj["backgroundColor"] = bc.BackgroundColor + } + + if len(bc.GridList) > 0 { + obj["grid"] = bc.GridList + } + + if bc.hasBrush { + obj["brush"] = bc.Brush + } + + if bc.Calendar != nil { + obj["calendar"] = bc.Calendar + } + + return obj +} + +// GetAssets returns the Assets options. +func (bc *BaseConfiguration) GetAssets() opts.Assets { + return bc.Assets +} + +// AddDataset adds a Dataset to this chart +func (bc *BaseConfiguration) AddDataset(dataset ...opts.Dataset) { + bc.DatasetList = append(bc.DatasetList, dataset...) +} + +// FillDefaultValues fill default values for chart options. +func (bc *BaseConfiguration) FillDefaultValues() { + opts.SetDefaultValue(bc) +} + +func (bc *BaseConfiguration) initBaseConfiguration() { + bc.initSeriesColors() + bc.InitAssets() + bc.initXYAxis() + bc.Initialization.Validate() + bc.FillDefaultValues() +} + +func (bc *BaseConfiguration) initSeriesColors() { + bc.Colors = []string{ + "#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de", + "#3ba272", "#fc8452", "#9a60b4", "#ea7ccc", + } +} + +func (bc *BaseConfiguration) insertSeriesColors(colors []string) { + reversed := reverseSlice(colors) + for i := 0; i < len(reversed); i++ { + bc.Colors = append(bc.Colors, "") + copy(bc.Colors[1:], bc.Colors[0:]) + bc.Colors[0] = reversed[i] + } +} + +func (bc *BaseConfiguration) setBaseGlobalOptions(opts ...GlobalOpts) { + for _, opt := range opts { + opt(bc) + } +} + +func (ba *BaseActions) setBaseGlobalActions(opts ...GlobalActions) { + for _, opt := range opts { + opt(ba) + } +} + +func (ba *BaseActions) json() map[string]interface{} { + obj := map[string]interface{}{ + "type": ba.Type, + "areas": ba.Areas, + } + return obj +} + +// WithAreas sets the areas of the action +func WithAreas(act actions.Areas) GlobalActions { + return func(ba *BaseActions) { + ba.Areas = act + } +} + +// WithType sets the type of the action +func WithType(act actions.Type) GlobalActions { + return func(ba *BaseActions) { + ba.Type = act + } +} + +// WithAngleAxisOps sets the angle of the axis. +func WithAngleAxisOps(opt opts.AngleAxis) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.AngleAxis = opt + } +} + +// WithRadiusAxisOps sets the radius of the axis. +func WithRadiusAxisOps(opt opts.RadiusAxis) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.RadiusAxis = opt + } +} + +// WithBrush sets the Brush. +func WithBrush(opt opts.Brush) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.hasBrush = true + bc.Brush = opt + } +} + +// WithPolarOps sets the polar. +func WithPolarOps(opt opts.Polar) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Polar = opt + } +} + +// WithTitleOpts sets the title. +func WithTitleOpts(opt opts.Title) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Title = opt + } +} + +// WithAnimation enable or disable the animation. +func WithAnimation() GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Animation = false + } +} + +// WithToolboxOpts sets the toolbox. +func WithToolboxOpts(opt opts.Toolbox) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Toolbox = opt + } +} + +// WithSingleAxisOpts sets the single axis. +func WithSingleAxisOpts(opt opts.SingleAxis) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.SingleAxis = opt + } +} + +// WithTooltipOpts sets the tooltip. +func WithTooltipOpts(opt opts.Tooltip) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Tooltip = opt + } +} + +// WithLegendOpts sets the legend. +func WithLegendOpts(opt opts.Legend) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Legend = opt + } +} + +// WithInitializationOpts sets the initialization. +func WithInitializationOpts(opt opts.Initialization) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Initialization = opt + if bc.Initialization.Theme != "" && + bc.Initialization.Theme != "white" && + bc.Initialization.Theme != "dark" { + bc.JSAssets.Add("themes/" + opt.Theme + ".js") + } + bc.Initialization.Validate() + } +} + +// WithDataZoomOpts sets the list of the zoom data. +func WithDataZoomOpts(opt ...opts.DataZoom) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.DataZoomList = append(bc.DataZoomList, opt...) + } +} + +// WithVisualMapOpts sets the List of the visual map. +func WithVisualMapOpts(opt ...opts.VisualMap) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.VisualMapList = append(bc.VisualMapList, opt...) + } +} + +// WithRadarComponentOpts sets the component of the radar. +func WithRadarComponentOpts(opt opts.RadarComponent) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.RadarComponent = opt + } +} + +// WithGeoComponentOpts sets the geo component. +func WithGeoComponentOpts(opt opts.GeoComponent) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.GeoComponent = opt + bc.JSAssets.Add("maps/" + datasets.MapFileNames[opt.Map] + ".js") + } + +} + +// WithParallelComponentOpts sets the parallel component. +func WithParallelComponentOpts(opt opts.ParallelComponent) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.ParallelComponent = opt + } +} + +// WithParallelAxisList sets the list of the parallel axis. +func WithParallelAxisList(opt []opts.ParallelAxis) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.ParallelAxisList = opt + } +} + +// WithColorsOpts sets the color. +func WithColorsOpts(opt opts.Colors) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.insertSeriesColors(opt) + } +} + +// reverseSlice reverses the string slice. +func reverseSlice(s []string) []string { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + return s +} + +// WithGridOpts sets the List of the grid. +func WithGridOpts(opt ...opts.Grid) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.GridList = append(bc.GridList, opt...) + } +} + +// WithAxisPointerOpts sets the axis pointer. +func WithAxisPointerOpts(opt *opts.AxisPointer) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.AxisPointer = opt + } +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/boxplot.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/boxplot.go new file mode 100644 index 0000000000..fef35e1f5b --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/boxplot.go @@ -0,0 +1,44 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// BoxPlot represents a boxplot chart. +type BoxPlot struct { + RectChart +} + +// Type returns the chart type. +func (*BoxPlot) Type() string { return types.ChartBoxPlot } + +// NewBoxPlot creates a new boxplot chart. +func NewBoxPlot() *BoxPlot { + c := &BoxPlot{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// SetXAxis adds the X axis. +func (c *BoxPlot) SetXAxis(x interface{}) *BoxPlot { + c.xAxisData = x + return c +} + +// AddSeries adds the new series. +func (c *BoxPlot) AddSeries(name string, data []opts.BoxPlotData, options ...SeriesOpts) *BoxPlot { + series := SingleSeries{Name: name, Type: types.ChartBoxPlot, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// Validate validates the given configuration. +func (c *BoxPlot) Validate() { + c.XAxisList[0].Data = c.xAxisData + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/chart3d.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/chart3d.go new file mode 100644 index 0000000000..48d0e4910a --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/chart3d.go @@ -0,0 +1,67 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Chart3D is a chart in 3D coordinates. +type Chart3D struct { + BaseConfiguration +} + +// WithXAxis3DOpts sets the X axis of the Chart3D instance. +func WithXAxis3DOpts(opt opts.XAxis3D) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.XAxis3D = opt + } +} + +// WithYAxis3DOpts sets the Y axis of the Chart3D instance. +func WithYAxis3DOpts(opt opts.YAxis3D) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.YAxis3D = opt + } +} + +// WithZAxis3DOpts sets the Z axis of the Chart3D instance. +func WithZAxis3DOpts(opt opts.ZAxis3D) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.ZAxis3D = opt + } +} + +// WithGrid3DOpts sets the grid of the Chart3D instance. +func WithGrid3DOpts(opt opts.Grid3D) GlobalOpts { + return func(bc *BaseConfiguration) { + bc.Grid3D = opt + } +} + +func (c *Chart3D) initChart3D() { + c.JSAssets.Add(opts.CompatibleEchartsJS) + c.JSAssets.Add("echarts-gl.min.js") + c.has3DAxis = true +} + +func (c *Chart3D) addSeries(chartType, name string, data []opts.Chart3DData, options ...SeriesOpts) { + series := SingleSeries{ + Name: name, + Type: chartType, + Data: data, + CoordSystem: types.ChartCartesian3D, + } + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) +} + +// SetGlobalOptions sets options for the Chart3D instance. +func (c *Chart3D) SetGlobalOptions(options ...GlobalOpts) *Chart3D { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// Validate validates the given configuration. +func (c *Chart3D) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/custom.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/custom.go new file mode 100644 index 0000000000..71d344d231 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/custom.go @@ -0,0 +1,45 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Custom represents a custom chart. +type Custom struct { + RectChart +} + +// Type returns the chart type. +func (*Custom) Type() string { return types.ChartCustom } + +// NewCustom creates a new Custom chart. +func NewCustom() *Custom { + c := &Custom{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// SetXAxis adds the X axis. +func (c *Custom) SetXAxis(x interface{}) *Custom { + c.xAxisData = x + return c +} + +// AddSeries adds the new series. +func (c *Custom) AddSeries(name string, data []opts.CustomData, options ...SeriesOpts) *Custom { + series := SingleSeries{Name: name, Type: types.ChartCustom, Data: data} + series.InitSeriesDefaultOpts(c.BaseConfiguration) + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// Validate validates the given configuration. +func (c *Custom) Validate() { + c.XAxisList[0].Data = c.xAxisData + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/effectscatter.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/effectscatter.go new file mode 100644 index 0000000000..42251ca2a0 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/effectscatter.go @@ -0,0 +1,44 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// EffectScatter represents an effect scatter chart. +type EffectScatter struct { + RectChart +} + +// Type returns the chart type. +func (*EffectScatter) Type() string { return types.ChartEffectScatter } + +// NewEffectScatter creates a new effect scatter chart. +func NewEffectScatter() *EffectScatter { + c := &EffectScatter{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// SetXAxis adds the X axis. +func (c *EffectScatter) SetXAxis(x interface{}) *EffectScatter { + c.xAxisData = x + return c +} + +// AddSeries adds the Y axis. +func (c *EffectScatter) AddSeries(name string, data []opts.EffectScatterData, options ...SeriesOpts) *EffectScatter { + series := SingleSeries{Name: name, Type: types.ChartEffectScatter, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// Validate validates the given configuration. +func (c *EffectScatter) Validate() { + c.XAxisList[0].Data = c.xAxisData + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/funnel.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/funnel.go new file mode 100644 index 0000000000..a6d0610011 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/funnel.go @@ -0,0 +1,49 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Funnel represents a funnel chart. +type Funnel struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Funnel) Type() string { return types.ChartFunnel } + +// NewFunnel creates a new funnel chart. +func NewFunnel() *Funnel { + c := &Funnel{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// AddSeries adds new data sets. +func (c *Funnel) AddSeries(name string, data []opts.FunnelData, options ...SeriesOpts) *Funnel { + series := SingleSeries{Name: name, Type: types.ChartFunnel, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Funnel instance. +func (c *Funnel) SetGlobalOptions(options ...GlobalOpts) *Funnel { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Gauge instance. +func (c *Funnel) SetDispatchActions(actions ...GlobalActions) *Funnel { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Funnel) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/gauge.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/gauge.go new file mode 100644 index 0000000000..46e07570f7 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/gauge.go @@ -0,0 +1,49 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Gauge represents a gauge chart. +type Gauge struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Gauge) Type() string { return types.ChartGauge } + +// NewGauge creates a new gauge chart. +func NewGauge() *Gauge { + c := &Gauge{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// AddSeries adds new data sets. +func (c *Gauge) AddSeries(name string, data []opts.GaugeData, options ...SeriesOpts) *Gauge { + series := SingleSeries{Name: name, Type: types.ChartGauge, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Gauge instance. +func (c *Gauge) SetGlobalOptions(options ...GlobalOpts) *Gauge { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Gauge instance. +func (c *Gauge) SetDispatchActions(actions ...GlobalActions) *Gauge { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Gauge) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/geo.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/geo.go new file mode 100644 index 0000000000..b4eff64f63 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/geo.go @@ -0,0 +1,75 @@ +package charts + +import ( + "log" + + "github.com/go-echarts/go-echarts/v2/datasets" + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Geo represents a geo chart. +type Geo struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Geo) Type() string { return types.ChartGeo } + +var geoFormatter = `function (params) { + return params.name + ' : ' + params.value[2]; +}` + +// NewGeo creates a new geo chart. +func NewGeo() *Geo { + c := &Geo{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasGeo = true + return c +} + +// AddSeries adds new data sets. +// geoType options: +// * types.ChartScatter +// * types.ChartEffectScatter +// * types.ChartHeatMap +func (c *Geo) AddSeries(name, geoType string, data []opts.GeoData, options ...SeriesOpts) *Geo { + series := SingleSeries{Name: name, Type: geoType, Data: data, CoordSystem: types.ChartGeo} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +func (c *Geo) extendValue(region string, v float32) []float32 { + res := make([]float32, 0) + tv := datasets.Coordinates[region] + if tv == [2]float32{0, 0} { + log.Printf("goecharts: No coordinate is specified for %s\n", region) + } else { + res = append(tv[:], v) + } + return res +} + +// SetGlobalOptions sets options for the Geo instance. +func (c *Geo) SetGlobalOptions(options ...GlobalOpts) *Geo { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Geo instance. +func (c *Geo) SetDispatchActions(actions ...GlobalActions) *Geo { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Geo) Validate() { + if c.Tooltip.Formatter == "" { + c.Tooltip.Formatter = opts.FuncOpts(geoFormatter) + } + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/graph.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/graph.go new file mode 100644 index 0000000000..b23975d77a --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/graph.go @@ -0,0 +1,55 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Graph represents a graph chart. +type Graph struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Graph) Type() string { return types.ChartGraph } + +// NewGraph creates a new graph chart. +func NewGraph() *Graph { + chart := new(Graph) + chart.initBaseConfiguration() + chart.Renderer = render.NewChartRender(chart, chart.Validate) + return chart +} + +// AddSeries adds the new series. +func (c *Graph) AddSeries(name string, nodes []opts.GraphNode, links []opts.GraphLink, options ...SeriesOpts) *Graph { + series := SingleSeries{Name: name, Type: types.ChartGraph, Links: links, Data: nodes} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Graph instance. +func (c *Graph) SetGlobalOptions(options ...GlobalOpts) *Graph { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Graph instance. +func (c *Graph) SetDispatchActions(actions ...GlobalActions) *Graph { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Graph) Validate() { + // If there is no layout setting, default layout is set to "force". + for i := 0; i < len(c.MultiSeries); i++ { + if c.MultiSeries[i].Layout == "" { + c.MultiSeries[i].Layout = "force" + } + } + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/heatmap.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/heatmap.go new file mode 100644 index 0000000000..8dc737f56b --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/heatmap.go @@ -0,0 +1,50 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// HeatMap represents a heatmap chart. +type HeatMap struct { + RectChart +} + +// Type returns the chart type. +func (*HeatMap) Type() string { return types.ChartHeatMap } + +// NewHeatMap creates a new heatmap chart. +func NewHeatMap() *HeatMap { + c := &HeatMap{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// SetXAxis adds the X axis. +func (c *HeatMap) SetXAxis(x interface{}) *HeatMap { + c.xAxisData = x + return c +} + +// AddSeries adds the new series. +func (c *HeatMap) AddSeries(name string, data []opts.HeatMapData, options ...SeriesOpts) *HeatMap { + series := SingleSeries{Name: name, Type: types.ChartHeatMap, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// AddCalendar adds the calendar configuration to the chart. +func (c *HeatMap) AddCalendar(calendar ...*opts.Calendar) *HeatMap { + c.Calendar = append(c.Calendar, calendar...) + c.hasXYAxis = false + return c +} + +// Validate validates the given configuration. +func (c *HeatMap) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/kline.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/kline.go new file mode 100644 index 0000000000..6b4515eeba --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/kline.go @@ -0,0 +1,44 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Kline represents a kline chart. +type Kline struct { + RectChart +} + +// Type returns the chart type. +func (*Kline) Type() string { return types.ChartKline } + +// NewKLine creates a new kline chart. +func NewKLine() *Kline { + c := &Kline{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// SetXAxis adds the X axis. +func (c *Kline) SetXAxis(xAxis interface{}) *Kline { + c.xAxisData = xAxis + return c +} + +// AddSeries adds the new series. +func (c *Kline) AddSeries(name string, data []opts.KlineData, options ...SeriesOpts) *Kline { + series := SingleSeries{Name: name, Type: types.ChartKline, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// Validate validates the given configuration. +func (c *Kline) Validate() { + c.XAxisList[0].Data = c.xAxisData + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/line.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/line.go new file mode 100644 index 0000000000..036dc877c6 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/line.go @@ -0,0 +1,45 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Line represents a line chart. +type Line struct { + RectChart +} + +// Type returns the chart type. +func (*Line) Type() string { return types.ChartLine } + +// NewLine creates a new line chart. +func NewLine() *Line { + c := &Line{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// SetXAxis adds the X axis. +func (c *Line) SetXAxis(x interface{}) *Line { + c.xAxisData = x + return c +} + +// AddSeries adds the new series. +func (c *Line) AddSeries(name string, data []opts.LineData, options ...SeriesOpts) *Line { + series := SingleSeries{Name: name, Type: types.ChartLine, Data: data} + series.InitSeriesDefaultOpts(c.BaseConfiguration) + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// Validate validates the given configuration. +func (c *Line) Validate() { + c.XAxisList[0].Data = c.xAxisData + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/line3d.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/line3d.go new file mode 100644 index 0000000000..4591a80648 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/line3d.go @@ -0,0 +1,30 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Line3D represents a 3D line chart. +type Line3D struct { + Chart3D +} + +// Type returns the chart type. +func (*Line3D) Type() string { return types.ChartLine3D } + +// NewLine3D creates a new 3D line chart. +func NewLine3D() *Line3D { + c := &Line3D{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.initChart3D() + return c +} + +// AddSeries adds the new series. +func (c *Line3D) AddSeries(name string, data []opts.Chart3DData, options ...SeriesOpts) *Line3D { + c.addSeries(types.ChartLine3D, name, data, options...) + return c +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/liquid.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/liquid.go new file mode 100644 index 0000000000..ca5e62d470 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/liquid.go @@ -0,0 +1,51 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Liquid represents a liquid chart. +type Liquid struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Liquid) Type() string { return types.ChartLiquid } + +// NewLiquid creates a new liquid chart. +func NewLiquid() *Liquid { + c := &Liquid{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.JSAssets.Add(opts.CompatibleEchartsJS) + c.JSAssets.Add("echarts-liquidfill.min.js") + return c +} + +// AddSeries adds new data sets. +func (c *Liquid) AddSeries(name string, data []opts.LiquidData, options ...SeriesOpts) *Liquid { + series := SingleSeries{Name: name, Type: types.ChartLiquid, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Liquid instance. +func (c *Liquid) SetGlobalOptions(options ...GlobalOpts) *Liquid { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Liquid instance. +func (c *Liquid) SetDispatchActions(actions ...GlobalActions) *Liquid { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Liquid) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/map.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/map.go new file mode 100644 index 0000000000..72de6b9757 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/map.go @@ -0,0 +1,58 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/datasets" + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Map represents a map chart. +type Map struct { + BaseConfiguration + BaseActions + + mapType string +} + +// Type returns the chart type. +func (*Map) Type() string { return types.ChartMap } + +// NewMap creates a new map chart. +func NewMap() *Map { + c := &Map{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// RegisterMapType registers the given mapType. +func (c *Map) RegisterMapType(mapType string) { + c.mapType = mapType + c.JSAssets.Add("maps/" + datasets.MapFileNames[mapType] + ".js") +} + +// AddSeries adds new data sets. +func (c *Map) AddSeries(name string, data []opts.MapData, options ...SeriesOpts) *Map { + series := SingleSeries{Name: name, Type: types.ChartMap, MapType: c.mapType, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Map instance. +func (c *Map) SetGlobalOptions(options ...GlobalOpts) *Map { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Radar instance. +func (c *Map) SetDispatchActions(actions ...GlobalActions) *Map { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Map) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/parallel.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/parallel.go new file mode 100644 index 0000000000..a2b77e8ddb --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/parallel.go @@ -0,0 +1,50 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Parallel represents a parallel axis. +type Parallel struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Parallel) Type() string { return types.ChartParallel } + +// NewParallel creates a new parallel instance. +func NewParallel() *Parallel { + c := &Parallel{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasParallel = true + return c +} + +// AddSeries adds new data sets. +func (c *Parallel) AddSeries(name string, data []opts.ParallelData, options ...SeriesOpts) *Parallel { + series := SingleSeries{Name: name, Type: types.ChartParallel, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Parallel instance. +func (c *Parallel) SetGlobalOptions(options ...GlobalOpts) *Parallel { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Radar instance. +func (c *Parallel) SetDispatchActions(actions ...GlobalActions) *Parallel { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Parallel) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/pie.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/pie.go new file mode 100644 index 0000000000..072e98855c --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/pie.go @@ -0,0 +1,49 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Pie represents a pie chart. +type Pie struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Pie) Type() string { return types.ChartPie } + +// NewPie creates a new pie chart. +func NewPie() *Pie { + c := &Pie{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// AddSeries adds new data sets. +func (c *Pie) AddSeries(name string, data []opts.PieData, options ...SeriesOpts) *Pie { + series := SingleSeries{Name: name, Type: types.ChartPie, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Pie instance. +func (c *Pie) SetGlobalOptions(options ...GlobalOpts) *Pie { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Pie instance. +func (c *Pie) SetDispatchActions(actions ...GlobalActions) *Pie { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Pie) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/radar.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/radar.go new file mode 100644 index 0000000000..2cc3f9f520 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/radar.go @@ -0,0 +1,55 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Radar represents a radar chart. +type Radar struct { + BaseConfiguration + BaseActions + + // SymbolKeepAspect is whether to keep aspect for symbols in the form of path://. + SymbolKeepAspect bool +} + +// Type returns the chart type. +func (*Radar) Type() string { return types.ChartRadar } + +// NewRadar creates a new radar chart. +func NewRadar() *Radar { + c := &Radar{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasRadar = true + return c +} + +// AddSeries adds new data sets. +func (c *Radar) AddSeries(name string, data []opts.RadarData, options ...SeriesOpts) *Radar { + series := SingleSeries{Name: name, Type: types.ChartRadar, Data: data, SymbolKeepAspect: c.SymbolKeepAspect} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + c.legends = append(c.legends, name) + return c +} + +// SetGlobalOptions sets options for the Radar instance. +func (c *Radar) SetGlobalOptions(options ...GlobalOpts) *Radar { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Radar instance. +func (c *Radar) SetDispatchActions(actions ...GlobalActions) *Radar { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Radar) Validate() { + c.Legend.Data = c.legends + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/rectangle.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/rectangle.go new file mode 100644 index 0000000000..2ba65395ee --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/rectangle.go @@ -0,0 +1,111 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" +) + +type Overlaper interface { + overlap() MultiSeries +} + +// XYAxis represent the X and Y axis in the rectangular coordinates. +type XYAxis struct { + XAxisList []opts.XAxis `json:"xaxis"` + YAxisList []opts.YAxis `json:"yaxis"` +} + +func (xy *XYAxis) initXYAxis() { + xy.XAxisList = append(xy.XAxisList, opts.XAxis{}) + xy.YAxisList = append(xy.YAxisList, opts.YAxis{}) +} + +// ExtendXAxis adds new X axes. +func (xy *XYAxis) ExtendXAxis(xAxis ...opts.XAxis) { + xy.XAxisList = append(xy.XAxisList, xAxis...) +} + +// ExtendYAxis adds new Y axes. +func (xy *XYAxis) ExtendYAxis(yAxis ...opts.YAxis) { + xy.YAxisList = append(xy.YAxisList, yAxis...) +} + +// WithXAxisOpts sets the X axis. +func WithXAxisOpts(opt opts.XAxis, index ...int) GlobalOpts { + return func(bc *BaseConfiguration) { + if len(index) == 0 { + index = []int{0} + } + for i := 0; i < len(index); i++ { + bc.XYAxis.XAxisList[index[i]] = opt + } + } +} + +// WithYAxisOpts sets the Y axis. +func WithYAxisOpts(opt opts.YAxis, index ...int) GlobalOpts { + return func(bc *BaseConfiguration) { + if len(index) == 0 { + index = []int{0} + } + for i := 0; i < len(index); i++ { + bc.XYAxis.YAxisList[index[i]] = opt + } + } +} + +// RectConfiguration contains options for the rectangular coordinates. +type RectConfiguration struct { + BaseConfiguration + BaseActions +} + +func (rect *RectConfiguration) setRectGlobalOptions(options ...GlobalOpts) { + rect.BaseConfiguration.setBaseGlobalOptions(options...) +} + +func (rect *RectConfiguration) setRectGlobalActions(options ...GlobalActions) { + rect.BaseActions.setBaseGlobalActions(options...) +} + +// RectChart is a chart in RectChart coordinate. +type RectChart struct { + RectConfiguration + + xAxisData interface{} +} + +func (rc *RectChart) overlap() MultiSeries { + return rc.MultiSeries +} + +// SetGlobalOptions sets options for the RectChart instance. +func (rc *RectChart) SetGlobalOptions(options ...GlobalOpts) *RectChart { + rc.RectConfiguration.setRectGlobalOptions(options...) + return rc +} + +// SetDispatchActions sets actions for the RectChart instance. +func (rc *RectChart) SetDispatchActions(options ...GlobalActions) *RectChart { + rc.RectConfiguration.setRectGlobalActions(options...) + return rc +} + +// Overlap composes multiple charts into one single canvas. +// It is only suited for some of the charts which are in rectangular coordinate. +// Supported charts: Bar/BoxPlot/Line/Scatter/EffectScatter/Kline/HeatMap/Custom +func (rc *RectChart) Overlap(a ...Overlaper) { + for i := 0; i < len(a); i++ { + rc.MultiSeries = append(rc.MultiSeries, a[i].overlap()...) + } +} + +// Validate validates the given configuration. +func (rc *RectChart) Validate() { + // Make sure that the data of X axis won't be cleaned for XAxisOpts + rc.XAxisList[0].Data = rc.xAxisData + // Make sure that the labels of Y axis show correctly + for i := 0; i < len(rc.YAxisList); i++ { + rc.YAxisList[i].AxisLabel.Show = true + } + rc.Assets.Validate(rc.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/sankey.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/sankey.go new file mode 100644 index 0000000000..9f806f588a --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/sankey.go @@ -0,0 +1,49 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Sankey represents a sankey chart. +type Sankey struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Sankey) Type() string { return types.ChartSankey } + +// NewSankey creates a new sankey chart. +func NewSankey() *Sankey { + c := &Sankey{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// AddSeries adds new data sets. +func (c *Sankey) AddSeries(name string, nodes []opts.SankeyNode, links []opts.SankeyLink, options ...SeriesOpts) *Sankey { + series := SingleSeries{Name: name, Type: types.ChartSankey, Data: nodes, Links: links} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Sankey instance. +func (c *Sankey) SetGlobalOptions(options ...GlobalOpts) *Sankey { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Sankey instance. +func (c *Sankey) SetDispatchActions(actions ...GlobalActions) *Sankey { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Sankey) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/scatter.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/scatter.go new file mode 100644 index 0000000000..31a043a9fc --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/scatter.go @@ -0,0 +1,44 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Scatter represents a scatter chart. +type Scatter struct { + RectChart +} + +// Type returns the chart type. +func (*Scatter) Type() string { return types.ChartScatter } + +// NewScatter creates a new scatter chart. +func NewScatter() *Scatter { + c := &Scatter{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasXYAxis = true + return c +} + +// SetXAxis adds the X axis. +func (c *Scatter) SetXAxis(x interface{}) *Scatter { + c.xAxisData = x + return c +} + +// AddSeries adds the new series. +func (c *Scatter) AddSeries(name string, data []opts.ScatterData, options ...SeriesOpts) *Scatter { + series := SingleSeries{Name: name, Type: types.ChartScatter, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// Validate validates the given configuration. +func (c *Scatter) Validate() { + c.XAxisList[0].Data = c.xAxisData + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/scatter3d.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/scatter3d.go new file mode 100644 index 0000000000..974f347591 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/scatter3d.go @@ -0,0 +1,30 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Scatter3D represents a 3D scatter chart. +type Scatter3D struct { + Chart3D +} + +// Type returns the chart type. +func (*Scatter3D) Type() string { return types.ChartScatter3D } + +// NewScatter3D creates a new 3D scatter chart. +func NewScatter3D() *Scatter3D { + c := &Scatter3D{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.initChart3D() + return c +} + +// AddSeries adds the new series. +func (c *Scatter3D) AddSeries(name string, data []opts.Chart3DData, options ...SeriesOpts) *Scatter3D { + c.addSeries(types.ChartScatter3D, name, data, options...) + return c +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/series.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/series.go new file mode 100644 index 0000000000..17a09a6a3f --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/series.go @@ -0,0 +1,576 @@ +package charts + +import "github.com/go-echarts/go-echarts/v2/opts" + +type SingleSeries struct { + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + + // Rectangular charts + Stack string `json:"stack,omitempty"` + XAxisIndex int `json:"xAxisIndex,omitempty"` + YAxisIndex int `json:"yAxisIndex,omitempty"` + + // Bar + BarGap string `json:"barGap,omitempty"` + BarCategoryGap string `json:"barCategoryGap,omitempty"` + ShowBackground bool `json:"showBackground,omitempty"` + RoundCap bool `json:"roundCap,omitempty"` + + // Bar3D + Shading string `json:"shading,omitempty"` + + // Graph + Links interface{} `json:"links,omitempty"` + Layout string `json:"layout,omitempty"` + Force interface{} `json:"force,omitempty"` + Categories interface{} `json:"categories,omitempty"` + Roam bool `json:"roam,omitempty"` + EdgeSymbol interface{} `json:"edgeSymbol,omitempty"` + EdgeSymbolSize interface{} `json:"edgeSymbolSize,omitempty"` + EdgeLabel interface{} `json:"edgeLabel,omitempty"` + Draggable bool `json:"draggable,omitempty"` + FocusNodeAdjacency bool `json:"focusNodeAdjacency,omitempty"` + SymbolKeepAspect bool `json:"symbolKeepAspect,omitempty"` + + // KLine + BarWidth string `json:"barWidth,omitempty"` + BarMinWidth string `json:"barMinWidth,omitempty"` + BarMaxWidth string `json:"barMaxWidth,omitempty"` + + // Line + Step interface{} `json:"step,omitempty"` + Smooth bool `json:"smooth"` + ConnectNulls bool `json:"connectNulls"` + ShowSymbol bool `json:"showSymbol"` + Symbol string `json:"symbol,omitempty"` + Color string `json:"color,omitempty"` + + // Liquid + IsLiquidOutline bool `json:"outline,omitempty"` + IsWaveAnimation bool `json:"waveAnimation"` + + // Map + MapType string `json:"map,omitempty"` + CoordSystem string `json:"coordinateSystem,omitempty"` + + // Pie + RoseType interface{} `json:"roseType,omitempty"` + Center interface{} `json:"center,omitempty"` + Radius interface{} `json:"radius,omitempty"` + + // Scatter + SymbolSize interface{} `json:"symbolSize,omitempty"` + + // Tree + Orient string `json:"orient,omitempty"` + ExpandAndCollapse bool `json:"expandAndCollapse,omitempty"` + InitialTreeDepth int `json:"initialTreeDepth,omitempty"` + Leaves interface{} `json:"leaves,omitempty"` + Left string `json:"left,omitempty"` + Right string `json:"right,omitempty"` + Top string `json:"top,omitempty"` + Bottom string `json:"bottom,omitempty"` + + // TreeMap + LeafDepth int `json:"leafDepth,omitempty"` + Levels interface{} `json:"levels,omitempty"` + UpperLabel interface{} `json:"upperLabel,omitempty"` + + // WordCloud + Shape string `json:"shape,omitempty"` + SizeRange []float32 `json:"sizeRange,omitempty"` + RotationRange []float32 `json:"rotationRange,omitempty"` + + // Sunburst + NodeClick string `json:"nodeClick,omitempty"` + Sort string `json:"sort,omitempty"` + RenderLabelForZeroData bool `json:"renderLabelForZeroData"` + SelectedMode bool `json:"selectedMode"` + Animation bool `json:"animation" default:"true"` + AnimationThreshold int `json:"animationThreshold,omitempty"` + AnimationDuration int `json:"animationDuration,omitempty"` + AnimationEasing string `json:"animationEasing,omitempty"` + AnimationDelay int `json:"animationDelay,omitempty"` + AnimationDurationUpdate int `json:"animationDurationUpdate,omitempty"` + AnimationEasingUpdate string `json:"animationEasingUpdate,omitempty"` + AnimationDelayUpdate int `json:"animationDelayUpdate,omitempty"` + + // Custom + RenderItem string `json:"renderItem,omitempty"` + + // series data + Data interface{} `json:"data,omitempty"` + DatasetIndex int `json:"datasetIndex,omitempty"` + + // series options + *opts.Encode `json:"encode,omitempty"` + *opts.ItemStyle `json:"itemStyle,omitempty"` + *opts.Label `json:"label,omitempty"` + *opts.LabelLine `json:"labelLine,omitempty"` + *opts.Emphasis `json:"emphasis,omitempty"` + *opts.MarkLines `json:"markLine,omitempty"` + *opts.MarkAreas `json:"markArea,omitempty"` + *opts.MarkPoints `json:"markPoint,omitempty"` + *opts.RippleEffect `json:"rippleEffect,omitempty"` + *opts.LineStyle `json:"lineStyle,omitempty"` + *opts.AreaStyle `json:"areaStyle,omitempty"` + *opts.TextStyle `json:"textStyle,omitempty"` + *opts.CircularStyle `json:"circular,omitempty"` + + // Calendar + CalendarIndex int `json:"calendarIndex,omitempty"` +} + +type SeriesOpts func(s *SingleSeries) + +func WithCoordinateSystem(cs string) SeriesOpts { + return func(s *SingleSeries) { + s.CoordSystem = cs + } +} + +func WithCalendarIndex(index int) SeriesOpts { + return func(s *SingleSeries) { + s.CalendarIndex = index + } +} + +func WithSeriesAnimation(enable bool) SeriesOpts { + return func(s *SingleSeries) { + s.Animation = enable + } +} + +// WithLabelOpts sets the label. +func WithLabelOpts(opt opts.Label) SeriesOpts { + return func(s *SingleSeries) { + s.Label = &opt + } +} + +// WithEmphasisOpts sets the emphasis. +func WithEmphasisOpts(opt opts.Emphasis) SeriesOpts { + return func(s *SingleSeries) { + s.Emphasis = &opt + } +} + +// WithAreaStyleOpts sets the area style. +func WithAreaStyleOpts(opt opts.AreaStyle) SeriesOpts { + return func(s *SingleSeries) { + s.AreaStyle = &opt + } +} + +// WithItemStyleOpts sets the item style. +func WithItemStyleOpts(opt opts.ItemStyle) SeriesOpts { + return func(s *SingleSeries) { + s.ItemStyle = &opt + } +} + +// WithRippleEffectOpts sets the ripple effect. +func WithRippleEffectOpts(opt opts.RippleEffect) SeriesOpts { + return func(s *SingleSeries) { + s.RippleEffect = &opt + } +} + +// WithLineStyleOpts sets the line style. +func WithLineStyleOpts(opt opts.LineStyle) SeriesOpts { + return func(s *SingleSeries) { + s.LineStyle = &opt + } +} + +// With CircularStyle Opts +func WithCircularStyleOpts(opt opts.CircularStyle) SeriesOpts { + return func(s *SingleSeries) { + s.CircularStyle = &opt + } +} + +/* Chart Options */ + +// WithBarChartOpts sets the BarChart option. +func WithBarChartOpts(opt opts.BarChart) SeriesOpts { + return func(s *SingleSeries) { + s.Stack = opt.Stack + s.BarGap = opt.BarGap + s.BarCategoryGap = opt.BarCategoryGap + s.XAxisIndex = opt.XAxisIndex + s.YAxisIndex = opt.YAxisIndex + s.ShowBackground = opt.ShowBackground + s.RoundCap = opt.RoundCap + s.CoordSystem = opt.CoordSystem + } +} + +// WithSunburstOpts sets the SunburstChart option. +func WithSunburstOpts(opt opts.SunburstChart) SeriesOpts { + return func(s *SingleSeries) { + s.NodeClick = opt.NodeClick + s.Sort = opt.Sort + s.RenderLabelForZeroData = opt.RenderLabelForZeroData + s.SelectedMode = opt.SelectedMode + s.Animation = opt.Animation + s.AnimationThreshold = opt.AnimationThreshold + s.AnimationDuration = opt.AnimationDuration + s.AnimationEasing = opt.AnimationEasing + s.AnimationDelay = opt.AnimationDelay + s.AnimationDurationUpdate = opt.AnimationDurationUpdate + s.AnimationEasingUpdate = opt.AnimationEasingUpdate + s.AnimationDelayUpdate = opt.AnimationDelayUpdate + } +} + +// WithGraphChartOpts sets the GraphChart option. +func WithGraphChartOpts(opt opts.GraphChart) SeriesOpts { + return func(s *SingleSeries) { + s.Layout = opt.Layout + s.Force = opt.Force + s.Roam = opt.Roam + s.EdgeSymbol = opt.EdgeSymbol + s.EdgeSymbolSize = opt.EdgeSymbolSize + s.Draggable = opt.Draggable + s.FocusNodeAdjacency = opt.FocusNodeAdjacency + s.Categories = opt.Categories + s.EdgeLabel = opt.EdgeLabel + s.SymbolKeepAspect = opt.SymbolKeepAspect + } +} + +// WithHeatMapChartOpts sets the HeatMapChart option. +func WithHeatMapChartOpts(opt opts.HeatMapChart) SeriesOpts { + return func(s *SingleSeries) { + s.XAxisIndex = opt.XAxisIndex + s.YAxisIndex = opt.YAxisIndex + } +} + +// WithLineChartOpts sets the LineChart option. +func WithLineChartOpts(opt opts.LineChart) SeriesOpts { + return func(s *SingleSeries) { + s.YAxisIndex = opt.YAxisIndex + s.Stack = opt.Stack + s.Smooth = opt.Smooth + s.ShowSymbol = opt.ShowSymbol + s.Symbol = opt.Symbol + s.SymbolSize = opt.SymbolSize + s.Step = opt.Step + s.XAxisIndex = opt.XAxisIndex + s.YAxisIndex = opt.YAxisIndex + s.ConnectNulls = opt.ConnectNulls + s.Color = opt.Color + s.SymbolKeepAspect = opt.SymbolKeepAspect + } +} + +// WithLineChartOpts sets the LineChart option. +func WithKlineChartOpts(opt opts.KlineChart) SeriesOpts { + return func(s *SingleSeries) { + s.BarWidth = opt.BarWidth + s.BarMinWidth = opt.BarMinWidth + s.BarMaxWidth = opt.BarMaxWidth + } +} + +// WithPieChartOpts sets the PieChart option. +func WithPieChartOpts(opt opts.PieChart) SeriesOpts { + return func(s *SingleSeries) { + s.RoseType = opt.RoseType + s.Center = opt.Center + s.Radius = opt.Radius + } +} + +// WithScatterChartOpts sets the ScatterChart option. +func WithScatterChartOpts(opt opts.ScatterChart) SeriesOpts { + return func(s *SingleSeries) { + s.XAxisIndex = opt.XAxisIndex + s.YAxisIndex = opt.YAxisIndex + s.SymbolKeepAspect = opt.SymbolKeepAspect + } +} + +// WithLiquidChartOpts sets the LiquidChart option. +func WithLiquidChartOpts(opt opts.LiquidChart) SeriesOpts { + return func(s *SingleSeries) { + s.Shape = opt.Shape + s.IsLiquidOutline = opt.IsShowOutline + s.IsWaveAnimation = opt.IsWaveAnimation + } +} + +// WithBar3DChartOpts sets the Bar3DChart option. +func WithBar3DChartOpts(opt opts.Bar3DChart) SeriesOpts { + return func(s *SingleSeries) { + s.Shading = opt.Shading + } +} + +// WithTreeOpts sets the TreeChart option. +func WithTreeOpts(opt opts.TreeChart) SeriesOpts { + return func(s *SingleSeries) { + s.Layout = opt.Layout + s.Orient = opt.Orient + s.ExpandAndCollapse = opt.ExpandAndCollapse + s.InitialTreeDepth = opt.InitialTreeDepth + s.Roam = opt.Roam + s.Label = opt.Label + s.Leaves = opt.Leaves + s.Right = opt.Right + s.Left = opt.Left + s.Top = opt.Top + s.Bottom = opt.Bottom + s.SymbolKeepAspect = opt.SymbolKeepAspect + } +} + +// WithTreeMapOpts sets the TreeMapChart options. +func WithTreeMapOpts(opt opts.TreeMapChart) SeriesOpts { + return func(s *SingleSeries) { + s.Animation = opt.Animation + s.LeafDepth = opt.LeafDepth + s.Roam = opt.Roam + s.Levels = opt.Levels + s.UpperLabel = opt.UpperLabel + s.Right = opt.Right + s.Left = opt.Left + s.Top = opt.Top + s.Bottom = opt.Bottom + } +} + +// WithWorldCloudChartOpts sets the WorldCloudChart option. +func WithWorldCloudChartOpts(opt opts.WordCloudChart) SeriesOpts { + return func(s *SingleSeries) { + s.Shape = opt.Shape + s.SizeRange = opt.SizeRange + s.RotationRange = opt.RotationRange + } +} + +// WithMarkLineNameTypeItemOpts sets the type of the MarkLine. +func WithMarkLineNameTypeItemOpts(opt ...opts.MarkLineNameTypeItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkLines == nil { + s.MarkLines = &opts.MarkLines{} + } + for _, o := range opt { + s.MarkLines.Data = append(s.MarkLines.Data, o) + } + } +} + +// WithMarkLineStyleOpts sets the style of the MarkLine. +func WithMarkLineStyleOpts(opt opts.MarkLineStyle) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkLines == nil { + s.MarkLines = &opts.MarkLines{} + } + + s.MarkLines.MarkLineStyle = opt + } +} + +// WithMarkLineNameCoordItemOpts sets the coordinates of the MarkLine. +func WithMarkLineNameCoordItemOpts(opt ...opts.MarkLineNameCoordItem) SeriesOpts { + type MLNameCoord struct { + Name string `json:"name,omitempty"` + Coord []interface{} `json:"coord"` + } + return func(s *SingleSeries) { + if s.MarkLines == nil { + s.MarkLines = &opts.MarkLines{} + } + for _, o := range opt { + s.MarkLines.Data = append( + s.MarkLines.Data, + []MLNameCoord{{Name: o.Name, Coord: o.Coordinate0}, {Coord: o.Coordinate1}}, + ) + } + } +} + +// WithMarkLineNameXAxisItemOpts sets the X axis of the MarkLine. +func WithMarkLineNameXAxisItemOpts(opt ...opts.MarkLineNameXAxisItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkLines == nil { + s.MarkLines = &opts.MarkLines{} + } + for _, o := range opt { + s.MarkLines.Data = append(s.MarkLines.Data, o) + } + } +} + +// WithMarkLineNameYAxisItemOpts sets the Y axis of the MarkLine. +func WithMarkLineNameYAxisItemOpts(opt ...opts.MarkLineNameYAxisItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkLines == nil { + s.MarkLines = &opts.MarkLines{} + } + for _, o := range opt { + s.MarkLines.Data = append(s.MarkLines.Data, o) + } + } +} + +// WithMarkAreaNameTypeItemOpts sets the type of the MarkArea. +func WithMarkAreaNameTypeItemOpts(opt ...opts.MarkAreaNameTypeItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkAreas == nil { + s.MarkAreas = &opts.MarkAreas{} + } + for _, o := range opt { + s.MarkAreas.Data = append(s.MarkAreas.Data, o) + } + } +} + +// WithMarkAreaStyleOpts sets the style of the MarkArea. +func WithMarkAreaStyleOpts(opt opts.MarkAreaStyle) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkAreas == nil { + s.MarkAreas = &opts.MarkAreas{} + } + + s.MarkAreas.MarkAreaStyle = opt + } +} + +// WithMarkAreaNameCoordItemOpts sets the coordinates of the MarkLine. +func WithMarkAreaNameCoordItemOpts(opt ...opts.MarkAreaNameCoordItem) SeriesOpts { + type MANameCoord struct { + Name string `json:"name,omitempty"` + ItemStyle *opts.ItemStyle `json:"itemStyle"` + Coord []interface{} `json:"coord"` + } + return func(s *SingleSeries) { + if s.MarkAreas == nil { + s.MarkAreas = &opts.MarkAreas{} + } + for _, o := range opt { + s.MarkAreas.Data = append( + s.MarkAreas.Data, + []MANameCoord{ + {Name: o.Name, ItemStyle: o.ItemStyle, Coord: o.Coordinate0}, + {Coord: o.Coordinate1}, + }, + ) + } + } +} + +// WithMarkAreaNameXAxisItemOpts sets the X axis of the MarkLine. +func WithMarkAreaNameXAxisItemOpts(opt ...opts.MarkAreaNameXAxisItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkAreas == nil { + s.MarkAreas = &opts.MarkAreas{} + } + for _, o := range opt { + s.MarkAreas.Data = append(s.MarkAreas.Data, o) + } + } +} + +// WithMarkAreaNameYAxisItemOpts sets the Y axis of the MarkLine. +func WithMarkAreaNameYAxisItemOpts(opt ...opts.MarkAreaNameYAxisItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkAreas == nil { + s.MarkAreas = &opts.MarkAreas{} + } + for _, o := range opt { + s.MarkAreas.Data = append(s.MarkAreas.Data, o) + } + } +} + +// WithMarkPointNameTypeItemOpts sets the type of the MarkPoint. +func WithMarkPointNameTypeItemOpts(opt ...opts.MarkPointNameTypeItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkPoints == nil { + s.MarkPoints = &opts.MarkPoints{} + } + for _, o := range opt { + s.MarkPoints.Data = append(s.MarkPoints.Data, o) + } + } +} + +// WithMarkPointStyleOpts sets the style of the MarkPoint. +func WithMarkPointStyleOpts(opt opts.MarkPointStyle) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkPoints == nil { + s.MarkPoints = &opts.MarkPoints{} + } + + s.MarkPoints.MarkPointStyle = opt + } +} + +// WithMarkPointNameCoordItemOpts sets the coordinated of the MarkPoint. +func WithMarkPointNameCoordItemOpts(opt ...opts.MarkPointNameCoordItem) SeriesOpts { + return func(s *SingleSeries) { + if s.MarkPoints == nil { + s.MarkPoints = &opts.MarkPoints{} + } + for _, o := range opt { + s.MarkPoints.Data = append(s.MarkPoints.Data, o) + } + } +} + +func (s *SingleSeries) InitSeriesDefaultOpts(c BaseConfiguration) { + opts.SetDefaultValue(s) + // some special inherited options from BaseConfiguration + s.Animation = c.Animation +} + +func (s *SingleSeries) ConfigureSeriesOpts(options ...SeriesOpts) { + for _, opt := range options { + opt(s) + } +} + +// MultiSeries represents multiple series. +type MultiSeries []SingleSeries + +// SetSeriesOptions sets options for all the series. +// Previous options will be overwrote every time hence setting them on the `AddSeries` if you want +// to customize each series individually +// +// here -> ↓ <- +// +// func (c *Bar) AddSeries(name string, data []opts.BarData, options ...SeriesOpts) +func (ms *MultiSeries) SetSeriesOptions(opts ...SeriesOpts) { + s := *ms + for i := 0; i < len(s); i++ { + s[i].ConfigureSeriesOpts(opts...) + } +} + +// WithEncodeOpts Set encodes for dataSets +func WithEncodeOpts(opt opts.Encode) SeriesOpts { + return func(s *SingleSeries) { + s.Encode = &opt + } +} + +// WithDatasetIndex sets the datasetIndex option. +func WithDatasetIndex(index int) SeriesOpts { + return func(s *SingleSeries) { + s.DatasetIndex = index + } +} + +// WithCustomChartOpts sets the CustomChart option. +func WithCustomChartOpts(opt opts.CustomChart) SeriesOpts { + return func(s *SingleSeries) { + s.XAxisIndex = opt.XAxisIndex + s.YAxisIndex = opt.YAxisIndex + s.RenderItem = opt.RenderItem + } +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/sunburst.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/sunburst.go new file mode 100644 index 0000000000..e882c0aff7 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/sunburst.go @@ -0,0 +1,49 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Sunburst represents a sunburst chart. +type Sunburst struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Sunburst) Type() string { return types.ChartSunburst } + +// NewSunburst creates a new sunburst chart instance. +func NewSunburst() *Sunburst { + c := &Sunburst{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// AddSeries adds new data sets. +func (c *Sunburst) AddSeries(name string, data []opts.SunBurstData, options ...SeriesOpts) *Sunburst { + series := SingleSeries{Name: name, Type: types.ChartSunburst, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Pie instance. +func (c *Sunburst) SetGlobalOptions(options ...GlobalOpts) *Sunburst { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Sunburst instance. +func (c *Sunburst) SetDispatchActions(actions ...GlobalActions) *Sunburst { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Sunburst) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/surface3d.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/surface3d.go new file mode 100644 index 0000000000..b7cfda3e42 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/surface3d.go @@ -0,0 +1,30 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Surface3D represents a 3D surface chart. +type Surface3D struct { + Chart3D +} + +// Type returns the chart type. +func (*Surface3D) Type() string { return types.ChartSurface3D } + +// NewSurface3D creates a new 3d surface chart. +func NewSurface3D() *Surface3D { + c := &Surface3D{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.initChart3D() + return c +} + +// AddSeries adds the new series. +func (c *Surface3D) AddSeries(name string, data []opts.Chart3DData, options ...SeriesOpts) *Surface3D { + c.addSeries(types.ChartScatter3D, name, data, options...) + return c +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/themeriver.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/themeriver.go new file mode 100644 index 0000000000..52be1bbdb8 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/themeriver.go @@ -0,0 +1,54 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// ThemeRiver represents a theme river chart. +type ThemeRiver struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*ThemeRiver) Type() string { return types.ChartThemeRiver } + +// NewThemeRiver creates a new theme river chart. +func NewThemeRiver() *ThemeRiver { + c := &ThemeRiver{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.hasSingleAxis = true + return c +} + +// AddSeries adds new data sets. +func (c *ThemeRiver) AddSeries(name string, data []opts.ThemeRiverData, options ...SeriesOpts) *ThemeRiver { + cd := make([][3]interface{}, len(data)) + for i := 0; i < len(data); i++ { + cd[i] = data[i].ToList() + } + series := SingleSeries{Name: name, Type: types.ChartThemeRiver, Data: cd} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the ThemeRiver instance. +func (c *ThemeRiver) SetGlobalOptions(options ...GlobalOpts) *ThemeRiver { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the ThemeRiver instance. +func (c *ThemeRiver) SetDispatchActions(actions ...GlobalActions) *ThemeRiver { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *ThemeRiver) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/tree.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/tree.go new file mode 100644 index 0000000000..2ad93c5927 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/tree.go @@ -0,0 +1,49 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// Tree represents a Tree chart. +type Tree struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*Tree) Type() string { return types.ChartTree } + +// NewTree creates a new Tree chart instance. +func NewTree() *Tree { + c := &Tree{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// AddSeries adds new data sets. +func (c *Tree) AddSeries(name string, data []opts.TreeData, options ...SeriesOpts) *Tree { + series := SingleSeries{Name: name, Type: types.ChartTree, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the Tree instance. +func (c *Tree) SetGlobalOptions(options ...GlobalOpts) *Tree { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the Tree instance. +func (c *Tree) SetDispatchActions(actions ...GlobalActions) *Tree { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *Tree) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/treemap.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/treemap.go new file mode 100644 index 0000000000..4db1800529 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/treemap.go @@ -0,0 +1,49 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// TreeMap represents a TreeMap chart. +type TreeMap struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*TreeMap) Type() string { return types.ChartTreeMap } + +// NewTreeMap creates a new TreeMap chart instance. +func NewTreeMap() *TreeMap { + c := &TreeMap{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + return c +} + +// AddSeries adds new data sets. +func (c *TreeMap) AddSeries(name string, data []opts.TreeMapNode, options ...SeriesOpts) *TreeMap { + series := SingleSeries{Name: name, Type: types.ChartTreeMap, Data: data} + series.ConfigureSeriesOpts(options...) + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the TreeMap instance. +func (c *TreeMap) SetGlobalOptions(options ...GlobalOpts) *TreeMap { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the TreeMap instance. +func (c *TreeMap) SetDispatchActions(actions ...GlobalActions) *TreeMap { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *TreeMap) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/charts/wordcloud.go b/vendor/github.com/go-echarts/go-echarts/v2/charts/wordcloud.go new file mode 100644 index 0000000000..b9e94fa136 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/charts/wordcloud.go @@ -0,0 +1,67 @@ +package charts + +import ( + "github.com/go-echarts/go-echarts/v2/opts" + "github.com/go-echarts/go-echarts/v2/render" + "github.com/go-echarts/go-echarts/v2/types" +) + +// WordCloud represents a word cloud chart. +type WordCloud struct { + BaseConfiguration + BaseActions +} + +// Type returns the chart type. +func (*WordCloud) Type() string { return types.ChartWordCloud } + +var wcTextColor = `function () { + return 'rgb(' + [ + Math.round(Math.random() * 160), + Math.round(Math.random() * 160), + Math.round(Math.random() * 160)].join(',') + ')'; +}` + +// NewWordCloud creates a new word cloud chart. +func NewWordCloud() *WordCloud { + c := &WordCloud{} + c.initBaseConfiguration() + c.Renderer = render.NewChartRender(c, c.Validate) + c.JSAssets.Add(opts.CompatibleEchartsJS) + c.JSAssets.Add("echarts-wordcloud.min.js") + return c +} + +// AddSeries adds new data sets. +func (c *WordCloud) AddSeries(name string, data []opts.WordCloudData, options ...SeriesOpts) *WordCloud { + series := SingleSeries{Name: name, Type: types.ChartWordCloud, Data: data} + series.ConfigureSeriesOpts(options...) + + // set default random color for WordCloud chart + if series.TextStyle == nil { + series.TextStyle = &opts.TextStyle{Normal: &opts.TextStyle{}} + } + if series.TextStyle.Normal.Color == "" { + series.TextStyle.Normal.Color = opts.FuncOpts(wcTextColor) + } + + c.MultiSeries = append(c.MultiSeries, series) + return c +} + +// SetGlobalOptions sets options for the WordCloud instance. +func (c *WordCloud) SetGlobalOptions(options ...GlobalOpts) *WordCloud { + c.BaseConfiguration.setBaseGlobalOptions(options...) + return c +} + +// SetDispatchActions sets actions for the WordCloud instance. +func (c *WordCloud) SetDispatchActions(actions ...GlobalActions) *WordCloud { + c.BaseActions.setBaseGlobalActions(actions...) + return c +} + +// Validate validates the given configuration. +func (c *WordCloud) Validate() { + c.Assets.Validate(c.AssetsHost) +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/datasets/coordinates.go b/vendor/github.com/go-echarts/go-echarts/v2/datasets/coordinates.go new file mode 100644 index 0000000000..47f0ff6045 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/datasets/coordinates.go @@ -0,0 +1,3755 @@ +package datasets + +var Coordinates = map[string][2]float32{ + "阿城": {126.58, 45.32}, + "阿克苏": {80.19, 41.09}, + "阿勒泰": {88.12, 47.5}, + "阿图什": {76.08, 39.42}, + "安达": {125.18, 46.24}, + "安国": {115.2, 38.24}, + "安康": {109.01, 32.41}, + "安陆": {113.41, 31.15}, + "安庆": {117.02, 30.31}, + "安丘": {119.12, 36.25}, + "安顺": {105.55, 26.14}, + "安阳": {114.35, 36.1}, + "鞍山": {122.85, 41.12}, + "巴中": {106.43, 31.51}, + "霸州": {116.24, 39.06}, + "白城": {122.5, 45.38}, + "白山": {126.26, 41.56}, + "白银": {104.12, 36.33}, + "百色": {106.36, 23.54}, + "蚌埠": {117.21, 32.56}, + "包头": {110, 40.58}, + "宝鸡": {107.15, 34.38}, + "保定": {115.48, 38.85}, + "保山": {99.1, 25.08}, + "北海": {109.12, 21.49}, + "北流": {110.21, 22.42}, + "北票": {120.47, 41.48}, + "本溪": {123.73, 41.3}, + "毕节": {105.18, 27.18}, + "滨州": {118.03, 37.36}, + "亳州": {115.47, 33.52}, + "博乐": {82.08, 44.57}, + "沧州": {116.83, 38.33}, + "昌吉": {87.18, 44.02}, + "昌邑": {119.24, 39.52}, + "常德": {111.69, 29.05}, + "常熟": {120.74, 31.64}, + "常州": {119.95, 31.79}, + "巢湖": {117.52, 31.36}, + "朝阳": {120.27, 41.34}, + "潮阳": {116.36, 23.16}, + "潮州": {116.63, 23.68}, + "郴州": {113.02, 25.46}, + "成都": {104.06, 30.67}, + "承德": {117.93, 40.97}, + "澄海": {116.46, 23.28}, + "赤峰": {118.87, 42.28}, + "赤水": {105.42, 28.34}, + "崇州": {103.4, 30.39}, + "滁州": {118.18, 32.18}, + "楚雄": {101.32, 25.01}, + "慈溪": {121.15, 30.11}, + "从化": {113.33, 23.33}, + "达川": {107.29, 31.14}, + "大安": {124.18, 45.3}, + "大理": {100.13, 25.34}, + "大连": {121.62, 38.92}, + "大庆": {125.03, 46.58}, + "大石桥": {122.31, 40.37}, + "大同": {113.3, 40.12}, + "大冶": {114.58, 30.06}, + "丹东": {124.37, 40.13}, + "丹江口": {108.3, 32.33}, + "丹阳": {119.32, 32}, + "儋州": {109.34, 19.31}, + "当阳": {111.47, 30.5}, + "德惠": {125.42, 44.32}, + "德令哈": {97.23, 37.22}, + "德兴": {117.35, 28.57}, + "德阳": {104.37, 31.13}, + "德州": {116.29, 37.45}, + "登封": {113.02, 34.27}, + "邓州": {112.05, 32.42}, + "定州": {115, 38.3}, + "东川": {103.12, 26.06}, + "东港": {124.08, 39.53}, + "东莞": {113.75, 23.04}, + "东胜": {109.59, 39.48}, + "东台": {120.19, 32.51}, + "东阳": {120.14, 29.16}, + "东营": {118.49, 37.46}, + "都江堰": {103.37, 31.01}, + "都匀": {107.31, 26.15}, + "敦化": {128.13, 43.22}, + "敦煌": {94.41, 40.08}, + "峨眉山": {103.29, 29.36}, + "额尔古纳": {120.11, 50.13}, + "鄂尔多斯": {109.781327, 39.608266}, + "鄂州": {114.52, 30.23}, + "恩平": {112.19, 22.12}, + "恩施": {109.29, 30.16}, + "二连浩特": {111.58, 43.38}, + "番禺": {113.22, 22.57}, + "防城港": {108.2, 21.37}, + "肥城": {116.46, 36.14}, + "丰城": {115.48, 28.12}, + "丰南": {118.06, 39.34}, + "丰镇": {113.09, 40.27}, + "凤城": {124.02, 40.28}, + "奉化": {121.24, 29.39}, + "佛山": {113.11, 23.05}, + "涪陵": {107.22, 29.42}, + "福安": {119.39, 27.06}, + "福清": {119.23, 25.42}, + "福州": {119.3, 26.08}, + "抚顺": {123.97, 41.97}, + "阜康": {87.58, 44.09}, + "阜新": {121.39, 42.01}, + "阜阳": {115.48, 32.54}, + "富锦": {132.02, 47.15}, + "富阳": {119.95, 30.07}, + "盖州": {122.21, 40.24}, + "赣州": {114.56, 28.52}, + "高安": {115.22, 28.25}, + "高碑店": {115.51, 39.2}, + "高密": {119.44, 36.22}, + "高明": {112.5, 22.53}, + "高平": {112.55, 35.48}, + "高要": {112.26, 23.02}, + "高邮": {119.27, 32.47}, + "高州": {110.5, 21.54}, + "格尔木": {94.55, 36.26}, + "个旧": {103.09, 23.21}, + "根河": {121.29, 50.48}, + "公主岭": {124.49, 43.31}, + "巩义": {112.58, 34.46}, + "古交": {112.09, 37.54}, + "广汉": {104.15, 30.58}, + "广水": {113.48, 31.37}, + "广元": {105.51, 32.28}, + "广州": {113.23, 23.16}, + "贵池": {117.28, 30.39}, + "贵港": {109.36, 23.06}, + "贵阳": {106.71, 26.57}, + "桂林": {110.28, 25.29}, + "桂平": {110.04, 23.22}, + "哈尔滨": {126.63, 45.75}, + "哈密": {93.28, 42.5}, + "海城": {122.43, 40.51}, + "海口": {110.35, 20.02}, + "海拉尔": {119.39, 49.12}, + "海林": {129.21, 44.35}, + "海伦": {126.57, 47.28}, + "海门": {121.15, 31.89}, + "海宁": {120.42, 30.32}, + "邯郸": {114.47, 36.6}, + "韩城": {110.27, 35.28}, + "汉中": {107.01, 33.04}, + "杭州": {120.19, 30.26}, + "蒿城": {114.5, 38.02}, + "合川": {106.15, 30.02}, + "合肥": {117.27, 31.86}, + "合山": {108.52, 23.47}, + "和龙": {129, 42.32}, + "和田": {79.55, 37.09}, + "河池": {108.03, 24.42}, + "河间": {116.05, 38.26}, + "河津": {110.41, 35.35}, + "河源": {114.68, 23.73}, + "菏泽": {115.480656, 35.23375}, + "鹤壁": {114.11, 35.54}, + "鹤岗": {130.16, 47.2}, + "鹤山": {112.57, 22.46}, + "黑河": {127.29, 50.14}, + "衡水": {115.72, 37.72}, + "衡阳": {112.37, 26.53}, + "洪湖": {113.27, 29.48}, + "洪江": {109.59, 27.07}, + "侯马": {111.21, 35.37}, + "呼和浩特": {111.65, 40.82}, + "湖州": {120.1, 30.86}, + "葫芦岛": {120.836932, 40.711052}, + "花都": {113.12, 23.23}, + "华阴": {110.05, 34.34}, + "华蓥": {106.44, 30.26}, + "化州": {110.37, 21.39}, + "桦甸": {126.44, 42.58}, + "怀化": {109.58, 27.33}, + "淮安": {119.15, 33.5}, + "淮北": {116.47, 33.57}, + "淮南": {116.58, 32.37}, + "淮阴": {119.02, 33.36}, + "黄骅": {117.21, 38.21}, + "黄山": {118.18, 29.43}, + "黄石": {115.06, 30.12}, + "黄州": {114.52, 30.27}, + "珲春": {130.22, 42.52}, + "辉县": {113.47, 35.27}, + "惠阳": {114.28, 22.48}, + "惠州": {114.4, 23.09}, + "霍林郭勒": {119.38, 45.32}, + "霍州": {111.42, 36.34}, + "鸡西": {130.57, 45.17}, + "吉安": {114.58, 27.07}, + "吉首": {109.43, 28.18}, + "即墨": {120.45, 36.38}, + "集安": {126.11, 41.08}, + "集宁": {113.06, 41.02}, + "济南": {117, 36.65}, + "济宁": {116.59, 35.38}, + "济源": {112.35, 35.04}, + "冀州": {115.33, 37.34}, + "佳木斯": {130.22, 46.47}, + "嘉兴": {120.76, 30.77}, + "嘉峪关": {98.289152, 39.77313}, + "简阳": {104.32, 30.24}, + "建德": {119.16, 29.29}, + "建瓯": {118.2, 27.03}, + "建阳": {118.07, 27.21}, + "江都": {119.32, 32.26}, + "江津": {106.16, 29.18}, + "江门": {113.06, 22.61}, + "江山": {118.37, 28.45}, + "江阴": {120.26, 31.91}, + "江油": {104.42, 31.48}, + "姜堰": {120.08, 32.34}, + "胶南": {119.97, 35.88}, + "胶州": {120.03336, 36.264622}, + "焦作": {113.21, 35.24}, + "蛟河": {127.21, 43.42}, + "揭阳": {116.35, 23.55}, + "介休": {111.55, 37.02}, + "界首": {115.21, 33.15}, + "金昌": {102.188043, 38.520089}, + "金华": {119.64, 29.12}, + "金坛": {119.56, 31.74}, + "津市": {111.52, 29.38}, + "锦州": {121.15, 41.13}, + "晋城": {112.51, 35.3}, + "晋江": {118.35, 24.49}, + "晋州": {115.02, 38.02}, + "荆门": {112.12, 31.02}, + "荆沙": {112.16, 30.18}, + "荆州": {112.239741, 30.335165}, + "井冈山": {114.1, 26.34}, + "景德镇": {117.13, 29.17}, + "景洪": {100.48, 22.01}, + "靖江": {120.17, 32.02}, + "九江": {115.97, 29.71}, + "九台": {125.51, 44.09}, + "酒泉": {98.31, 39.44}, + "句容": {119.16, 31.95}, + "喀什": {75.59, 39.3}, + "开封": {114.35, 34.79}, + "开平": {112.4, 22.22}, + "开原": {124.02, 42.32}, + "开远": {103.13, 23.43}, + "凯里": {107.58, 26.35}, + "克拉玛依": {84.77, 45.59}, + "库尔勒": {86.06, 41.68}, + "奎屯": {84.56, 44.27}, + "昆明": {102.73, 25.04}, + "昆山": {120.95, 31.39}, + "廓坊": {116.42, 39.31}, + "拉萨": {91.11, 29.97}, + "莱芜": {117.67, 36.19}, + "莱西": {120.53, 36.86}, + "莱阳": {120.42, 36.58}, + "莱州": {119.942327, 37.177017}, + "兰溪": {119.28, 29.12}, + "兰州": {103.73, 36.03}, + "阆中": {105.58, 31.36}, + "廊坊": {116.7, 39.53}, + "老河口": {111.4, 32.23}, + "乐昌": {113.21, 25.09}, + "乐陵": {117.12, 37.44}, + "乐平": {117.08, 28.58}, + "乐清": {120.58, 28.08}, + "乐山": {103.44, 29.36}, + "雷州": {110.04, 20.54}, + "耒阳": {112.51, 26.24}, + "冷水江": {111.26, 27.42}, + "冷水滩": {111.35, 26.26}, + "醴陵": {113.3, 27.4}, + "丽水": {119.92, 28.45}, + "利川": {108.56, 30.18}, + "溧阳": {119.48, 31.43}, + "连云港": {119.16, 34.59}, + "连州": {112.23, 24.48}, + "涟源": {111.41, 27.41}, + "廉江": {110.17, 21.37}, + "辽阳": {123.12, 41.16}, + "辽源": {125.09, 42.54}, + "聊城": {115.97, 36.45}, + "林州": {113.49, 36.03}, + "临安": {119.72, 30.23}, + "临川": {116.21, 27.59}, + "临汾": {111.5, 36.08}, + "临海": {121.08, 28.51}, + "临河": {107.22, 40.46}, + "临江": {126.53, 41.49}, + "临清": {115.42, 36.51}, + "临夏": {103.12, 35.37}, + "临湘": {113.27, 29.29}, + "临沂": {118.35, 35.05}, + "赁祥": {106.44, 22.07}, + "灵宝": {110.52, 34.31}, + "凌海": {121.21, 41.1}, + "凌源": {119.22, 41.14}, + "浏阳": {113.37, 28.09}, + "柳州": {109.4, 24.33}, + "六安": {116.28, 31.44}, + "六盘水": {104.5, 26.35}, + "龙海": {117.48, 24.26}, + "龙井": {129.26, 42.46}, + "龙口": {120.21, 37.39}, + "龙泉": {119.08, 28.04}, + "龙岩": {117.01, 25.06}, + "娄底": {111.59, 27.44}, + "泸州": {105.39, 28.91}, + "鹿泉": {114.19, 38.04}, + "潞城": {113.14, 36.21}, + "罗定": {111.33, 22.46}, + "洛阳": {112.44, 34.7}, + "漯河": {114.02, 33.33}, + "麻城": {115.01, 31.1}, + "马鞍山": {118.48, 31.56}, + "满洲里": {117.23, 49.35}, + "茂名": {110.88, 21.68}, + "梅河口": {125.4, 42.32}, + "梅州": {116.1, 24.55}, + "汨罗": {113.03, 28.49}, + "密山": {131.5, 45.32}, + "绵阳": {104.73, 31.48}, + "明光": {117.58, 32.47}, + "牡丹江": {129.58, 44.6}, + "南安": {118.23, 24.57}, + "南昌": {115.89, 28.68}, + "南充": {106.110698, 30.837793}, + "南川": {107.05, 29.1}, + "南宫": {115.23, 37.22}, + "南海": {113.09, 23.01}, + "南京": {118.78, 32.04}, + "南宁": {108.33, 22.84}, + "南平": {118.1, 26.38}, + "南通": {121.05, 32.08}, + "南阳": {112.32, 33}, + "讷河": {124.51, 48.29}, + "内江": {105.02, 29.36}, + "宁安": {129.28, 44.21}, + "宁波": {121.56, 29.86}, + "宁德": {119.31, 26.39}, + "攀枝花": {101.718637, 26.582347}, + "盘锦": {122.070714, 41.119997}, + "彭州": {103.57, 30.59}, + "蓬莱": {120.75, 37.8}, + "邳州": {117.59, 34.19}, + "平顶山": {113.29, 33.75}, + "平度": {119.97, 36.77}, + "平湖": {121.01, 30.42}, + "平凉": {106.4, 35.32}, + "萍乡": {113.5, 27.37}, + "泊头": {116.34, 38.04}, + "莆田": {119.01, 24.26}, + "濮阳": {115.01, 35.44}, + "浦圻": {113.51, 29.42}, + "普兰店": {121.58, 39.23}, + "普宁": {116.1, 23.18}, + "七台河": {130.49, 45.48}, + "齐齐哈尔": {123.97, 47.33}, + "启乐": {121.39, 31.48}, + "潜江": {112.53, 30.26}, + "钦州": {108.37, 21.57}, + "秦皇岛": {119.57, 39.95}, + "沁阳": {112.57, 35.05}, + "青岛": {120.33, 36.07}, + "青铜峡": {105.59, 37.56}, + "青州": {118.28, 36.42}, + "清远": {113.01, 23.7}, + "清镇": {106.27, 26.33}, + "邛崃": {103.28, 30.26}, + "琼海": {110.28, 19.14}, + "琼山": {110.21, 19.59}, + "曲阜": {116.58, 35.36}, + "曲靖": {103.79, 25.51}, + "衢州": {118.88, 28.97}, + "泉州": {118.58, 24.93}, + "任丘": {116.07, 38.42}, + "日喀则": {88.51, 29.16}, + "日照": {119.46, 35.42}, + "荣成": {122.41, 37.16}, + "如皋": {120.33, 32.23}, + "汝州": {112.5, 34.09}, + "乳山": {121.52, 36.89}, + "瑞安": {120.38, 27.48}, + "瑞昌": {115.38, 29.4}, + "瑞金": {116.01, 25.53}, + "瑞丽": {97.5, 24}, + "三河": {117.04, 39.58}, + "三门峡": {111.19, 34.76}, + "三明": {117.36, 26.13}, + "三水": {112.52, 23.1}, + "三亚": {109.511909, 18.252847}, + "沙河": {114.3, 36.51}, + "厦门": {118.1, 24.46}, + "汕头": {116.69, 23.39}, + "汕尾": {115.375279, 22.786211}, + "商丘": {115.38, 34.26}, + "商州": {109.57, 33.52}, + "上饶": {117.58, 25.27}, + "上虞": {120.52, 30.01}, + "尚志": {127.55, 45.14}, + "韶关": {113.62, 24.84}, + "韶山": {112.29, 27.54}, + "邵武": {117.29, 27.2}, + "邵阳": {111.28, 27.14}, + "绍兴": {120.58, 30.01}, + "深圳": {114.07, 22.62}, + "深州": {115.32, 38.01}, + "沈阳": {123.38, 41.8}, + "十堰": {110.47, 32.4}, + "石河子": {86, 44.18}, + "石家庄": {114.48, 38.03}, + "石狮": {118.38, 24.44}, + "石首": {112.24, 29.43}, + "石嘴山": {106.39, 39.04}, + "寿光": {118.73, 36.86}, + "舒兰": {126.57, 44.24}, + "双城": {126.15, 45.22}, + "双鸭山": {131.11, 46.38}, + "顺德": {113.15, 22.5}, + "朔州": {112.26, 39.19}, + "思茅": {100.58, 22.48}, + "四会": {112.41, 23.21}, + "四平": {124.22, 43.1}, + "松原": {124.49, 45.11}, + "苏州": {120.62, 31.32}, + "宿迁": {118.3, 33.96}, + "宿州": {116.58, 33.38}, + "绥芬河": {131.11, 44.25}, + "绥化": {126.59, 46.38}, + "随州": {113.22, 31.42}, + "遂宁": {105.33, 30.31}, + "塔城": {82.59, 46.46}, + "台北": {121.3, 25.03}, + "台山": {112.48, 22.15}, + "台州": {121.420757, 28.656386}, + "太仓": {121.1, 31.45}, + "太原": {112.53, 37.87}, + "泰安": {117.13, 36.18}, + "泰兴": {120.01, 32.1}, + "泰州": {119.9, 32.49}, + "唐山": {118.02, 39.63}, + "洮南": {122.47, 45.2}, + "滕州": {117.09, 35.06}, + "天门": {113.1, 30.39}, + "天水": {105.42, 34.37}, + "天长": {118.59, 32.41}, + "铁法": {123.32, 42.28}, + "铁力": {128.01, 46.59}, + "铁岭": {123.51, 42.18}, + "通化": {125.56, 41.43}, + "通辽": {122.16, 43.37}, + "通什": {109.31, 18.46}, + "通州": {121.03, 32.05}, + "同江": {132.3, 47.39}, + "桐乡": {120.32, 30.38}, + "铜川": {109.11, 35.09}, + "铜陵": {117.48, 30.56}, + "铜仁": {109.12, 27.43}, + "图们": {129.51, 42.57}, + "吐鲁番": {89.11, 42.54}, + "瓦房店": {121.979603, 39.627114}, + "畹町": {98.04, 24.06}, + "万县": {108.21, 30.5}, + "万源": {108.03, 32.03}, + "威海": {122.1, 37.5}, + "潍坊": {119.1, 36.62}, + "卫辉": {114.03, 35.24}, + "渭南": {109.5, 34.52}, + "温岭": {121.21, 28.22}, + "温州": {120.65, 28.01}, + "文登": {122.05, 37.2}, + "乌海": {106.48, 39.4}, + "乌兰浩特": {122.03, 46.03}, + "乌鲁木齐": {87.68, 43.77}, + "无锡": {120.29, 31.59}, + "吴川": {110.47, 21.26}, + "吴江": {120.63, 31.16}, + "吴忠": {106.11, 37.59}, + "芜湖": {118.38, 31.33}, + "梧州": {111.2, 23.29}, + "五常": {127.11, 44.55}, + "五大连池": {126.07, 48.38}, + "武安": {114.11, 36.42}, + "武冈": {110.37, 26.43}, + "武汉": {114.31, 30.52}, + "武威": {102.39, 37.56}, + "武穴": {115.33, 29.51}, + "武夷山": {118.02, 27.46}, + "舞钢": {113.3, 33.17}, + "西安": {108.95, 34.27}, + "西昌": {102.16, 27.54}, + "西峰": {107.4, 35.45}, + "西宁": {101.74, 36.56}, + "锡林浩特": {116.03, 43.57}, + "仙桃": {113.27, 30.22}, + "咸宁": {114.17, 29.53}, + "咸阳": {108.72, 34.36}, + "湘潭": {112.91, 27.87}, + "湘乡": {112.31, 27.44}, + "襄樊": {112.08, 32.02}, + "项城": {114.54, 33.26}, + "萧山": {120.16, 30.09}, + "孝感": {113.54, 30.56}, + "孝义": {111.48, 37.08}, + "忻州": {112.43, 38.24}, + "辛集": {115.12, 37.54}, + "新会": {113.01, 22.32}, + "新乐": {114.41, 38.2}, + "新密": {113.22, 34.31}, + "新民": {122.49, 41.59}, + "新泰": {117.45, 35.54}, + "新乡": {113.52, 35.18}, + "新沂": {118.2, 34.22}, + "新余": {114.56, 27.48}, + "新郑": {113.43, 34.24}, + "信阳": {114.04, 32.07}, + "邢台": {114.48, 37.05}, + "荥阳": {113.21, 34.46}, + "兴城": {120.41, 40.37}, + "兴化": {119.5, 32.56}, + "兴宁": {115.43, 24.09}, + "兴平": {108.29, 34.18}, + "兴义": {104.53, 25.05}, + "徐州": {117.2, 34.26}, + "许昌": {113.49, 34.01}, + "宣威": {104.06, 26.13}, + "宣州": {118.44, 30.57}, + "牙克石": {120.4, 49.17}, + "雅安": {102.59, 29.59}, + "烟台": {121.39, 37.52}, + "延安": {109.47, 36.6}, + "延吉": {129.3, 42.54}, + "盐城": {120.13, 33.38}, + "盐在": {120.08, 33.22}, + "兖州": {116.49, 35.32}, + "偃师": {112.47, 34.43}, + "扬中": {119.49, 32.14}, + "扬州": {119.42, 32.39}, + "阳春": {111.48, 22.1}, + "阳江": {111.95, 21.85}, + "阳泉": {113.57, 37.85}, + "伊春": {128.56, 47.42}, + "伊宁": {81.2, 43.55}, + "仪征": {119.1, 32.16}, + "宜宾": {104.56, 29.77}, + "宜昌": {111.3, 30.7}, + "宜城": {112.15, 31.42}, + "宜春": {114.23, 27.47}, + "宜兴": {119.82, 31.36}, + "宜州": {108.4, 24.28}, + "义马": {111.55, 34.43}, + "义乌": {120.06, 29.32}, + "益阳": {112.2, 28.36}, + "银川": {106.27, 38.47}, + "应城": {113.33, 30.57}, + "英德": {113.22, 24.1}, + "鹰潭": {117.03, 28.14}, + "营口": {122.18, 40.65}, + "永安": {117.23, 25.58}, + "永川": {105.53, 29.23}, + "永济": {110.27, 34.52}, + "永康": {120.01, 29.54}, + "永州": {111.37, 26.13}, + "余杭": {120.18, 30.26}, + "余姚": {121.1, 30.02}, + "愉树": {126.32, 44.49}, + "榆次": {112.43, 37.41}, + "榆林": {109.47, 38.18}, + "禹城": {116.39, 36.56}, + "禹州": {113.28, 34.09}, + "玉林": {110.09, 22.38}, + "玉门": {97.35, 39.49}, + "玉溪": {102.52, 24.35}, + "沅江": {112.22, 28.5}, + "原平": {112.42, 38.43}, + "岳阳": {113.09, 29.37}, + "云浮": {112.02, 22.93}, + "运城": {110.59, 35.02}, + "枣阳": {112.44, 32.07}, + "枣庄": {117.57, 34.86}, + "增城": {113.49, 23.18}, + "扎兰屯": {122.47, 48}, + "湛江": {110.359377, 21.270708}, + "张家港": {120.555821, 31.875428}, + "张家界": {110.479191, 29.117096}, + "张家口": {114.87, 40.82}, + "张掖": {100.26, 38.56}, + "章丘": {117.53, 36.72}, + "漳平": {117.24, 25.17}, + "漳州": {117.39, 24.31}, + "樟树": {115.32, 28.03}, + "长春": {125.35, 43.88}, + "长葛": {113.47, 34.12}, + "长乐": {119.31, 25.58}, + "长沙": {113, 28.21}, + "长治": {113.08, 36.18}, + "招远": {120.38, 37.35}, + "昭通": {103.42, 27.2}, + "肇东": {125.58, 46.04}, + "肇庆": {112.44, 23.05}, + "镇江": {119.44, 32.2}, + "郑州": {113.65, 34.76}, + "枝城": {111.27, 30.23}, + "中山": {113.38, 22.52}, + "钟祥": {112.34, 31.1}, + "舟山": {122.207216, 29.985295}, + "周口": {114.38, 33.37}, + "株洲": {113.16, 27.83}, + "珠海": {113.52, 22.3}, + "诸城": {119.24, 35.59}, + "诸暨": {120.23, 29.71}, + "驻马店": {114.01, 32.58}, + "庄河": {122.58, 39.41}, + "涿州": {115.59, 39.29}, + "资兴": {113.13, 25.58}, + "资阳": {104.38, 30.09}, + "淄博": {118.05, 36.78}, + "自贡": {104.778442, 29.33903}, + "邹城": {116.58, 35.24}, + "遵化": {117.58, 40.11}, + "遵义": {106.9, 27.7}, + "晋中": {112.75, 37.68}, + "吕梁": {111.13, 37.52}, + "呼伦贝尔": {119.77, 49.22}, + "巴彦淖尔": {107.42, 40.75}, + "抚州": {116.35, 28}, + "襄阳": {112.2, 32.08}, + "黄冈": {114.87, 30.45}, + "红河": {103.4, 23.37}, + "文山": {104.25, 23.37}, + "商洛": {109.93, 33.87}, + "定西": {104.62, 35.58}, + "陇南": {104.92, 33.4}, + "北京市": {116.4, 39.9}, + "天安门": {116.38, 39.9}, + "东城区": {116.42, 39.93}, + "西城区": {116.37, 39.92}, + "崇文区": {116.43, 39.88}, + "宣武区": {116.35, 39.87}, + "丰台区": {116.28, 39.85}, + "石景山区": {116.22, 39.9}, + "海淀区": {116.3, 39.95}, + "门头沟区": {116.1, 39.93}, + "房山区": {116.13, 39.75}, + "通州区": {116.65, 39.92}, + "顺义区": {116.65, 40.13}, + "昌平区": {116.23, 40.22}, + "大兴区": {116.33, 39.73}, + "怀柔区": {116.63, 40.32}, + "平谷区": {117.12, 40.13}, + "密云县": {116.83, 40.37}, + "延庆县": {115.97, 40.45}, + "天津市": {117.2, 39.12}, + "河西区": {117.22, 39.12}, + "南开区": {117.15, 39.13}, + "河北区": {117.18, 39.15}, + "红桥区": {117.15, 39.17}, + "塘沽区": {117.65, 39.02}, + "汉沽区": {117.8, 39.25}, + "大港区": {117.45, 38.83}, + "东丽区": {117.3, 39.08}, + "西青区": {117, 39.13}, + "津南区": {117.38, 38.98}, + "北辰区": {117.13, 39.22}, + "武清区": {117.03, 39.38}, + "宝坻区": {117.3, 39.72}, + "滨海新区": {117.68, 39.03}, + "宁河县": {117.82, 39.33}, + "静海县": {116.92, 38.93}, + "蓟县": {117.4, 40.05}, + "石家庄市": {114.52, 38.05}, + "井陉矿区": {114.05, 38.08}, + "裕华区": {114.52, 38.02}, + "井陉县": {114.13, 38.03}, + "正定县": {114.57, 38.15}, + "栾城县": {114.65, 37.88}, + "行唐县": {114.55, 38.43}, + "灵寿县": {114.37, 38.3}, + "高邑县": {114.6, 37.6}, + "深泽县": {115.2, 38.18}, + "赞皇县": {114.38, 37.67}, + "无极县": {114.97, 38.18}, + "平山县": {114.2, 38.25}, + "元氏县": {114.52, 37.75}, + "赵县": {114.77, 37.75}, + "辛集市": {115.22, 37.92}, + "藁城市": {114.83, 38.03}, + "晋州市": {115.03, 38.03}, + "新乐市": {114.68, 38.35}, + "鹿泉市": {114.3, 38.08}, + "唐山市": {118.2, 39.63}, + "路南区": {118.17, 39.63}, + "路北区": {118.22, 39.63}, + "古冶区": {118.42, 39.73}, + "开平区": {118.27, 39.68}, + "丰南区": {118.1, 39.57}, + "丰润区": {118.17, 39.83}, + "滦县": {118.7, 39.75}, + "滦南县": {118.68, 39.5}, + "乐亭县": {118.9, 39.42}, + "迁西县": {118.32, 40.15}, + "玉田县": {117.73, 39.88}, + "唐海县": {118.45, 39.27}, + "遵化市": {117.95, 40.18}, + "迁安市": {118.7, 40.02}, + "秦皇岛市": {119.6, 39.93}, + "海港区": {119.6, 39.93}, + "山海关区": {119.77, 40}, + "北戴河区": {119.48, 39.83}, + "青龙满族自治县": {118.95, 40.4}, + "昌黎县": {119.17, 39.7}, + "抚宁县": {119.23, 39.88}, + "卢龙县": {118.87, 39.88}, + "邯郸市": {114.48, 36.62}, + "邯山区": {114.48, 36.6}, + "丛台区": {114.48, 36.63}, + "复兴区": {114.45, 36.63}, + "峰峰矿区": {114.2, 36.42}, + "邯郸县": {114.53, 36.6}, + "临漳县": {114.62, 36.35}, + "成安县": {114.68, 36.43}, + "大名县": {115.15, 36.28}, + "涉县": {113.67, 36.57}, + "磁县": {114.37, 36.35}, + "肥乡县": {114.8, 36.55}, + "永年县": {114.48, 36.78}, + "邱县": {115.17, 36.82}, + "鸡泽县": {114.87, 36.92}, + "广平县": {114.93, 36.48}, + "馆陶县": {115.3, 36.53}, + "魏县": {114.93, 36.37}, + "曲周县": {114.95, 36.78}, + "武安市": {114.2, 36.7}, + "邢台市": {114.48, 37.07}, + "邢台县": {114.5, 37.08}, + "临城县": {114.5, 37.43}, + "内丘县": {114.52, 37.3}, + "柏乡县": {114.68, 37.5}, + "隆尧县": {114.77, 37.35}, + "任县": {114.68, 37.13}, + "南和县": {114.68, 37}, + "宁晋县": {114.92, 37.62}, + "巨鹿县": {115.03, 37.22}, + "新河县": {115.25, 37.53}, + "广宗县": {115.15, 37.07}, + "平乡县": {115.03, 37.07}, + "威县": {115.25, 36.98}, + "清河县": {115.67, 37.07}, + "临西县": {115.5, 36.85}, + "南宫市": {115.38, 37.35}, + "沙河市": {114.5, 36.85}, + "保定市": {115.47, 38.87}, + "北市区": {115.48, 38.87}, + "南市区": {115.5, 38.85}, + "满城县": {115.32, 38.95}, + "清苑县": {115.48, 38.77}, + "涞水县": {115.72, 39.4}, + "阜平县": {114.18, 38.85}, + "徐水县": {115.65, 39.02}, + "定兴县": {115.77, 39.27}, + "唐县": {114.98, 38.75}, + "高阳县": {115.78, 38.68}, + "容城县": {115.87, 39.05}, + "涞源县": {114.68, 39.35}, + "望都县": {115.15, 38.72}, + "安新县": {115.93, 38.92}, + "易县": {115.5, 39.35}, + "曲阳县": {114.7, 38.62}, + "蠡县": {115.57, 38.48}, + "顺平县": {115.13, 38.83}, + "博野县": {115.47, 38.45}, + "雄县": {116.1, 38.98}, + "涿州市": {115.97, 39.48}, + "定州市": {114.97, 38.52}, + "安国市": {115.32, 38.42}, + "高碑店市": {115.85, 39.33}, + "张家口市": {114.88, 40.82}, + "宣化区": {115.05, 40.6}, + "下花园区": {115.27, 40.48}, + "宣化县": {115.02, 40.55}, + "张北县": {114.7, 41.15}, + "康保县": {114.62, 41.85}, + "沽源县": {115.7, 41.67}, + "尚义县": {113.97, 41.08}, + "蔚县": {114.57, 39.85}, + "阳原县": {114.17, 40.12}, + "怀安县": {114.42, 40.67}, + "万全县": {114.72, 40.75}, + "怀来县": {115.52, 40.4}, + "涿鹿县": {115.22, 40.38}, + "赤城县": {115.83, 40.92}, + "崇礼县": {115.27, 40.97}, + "承德市": {117.93, 40.97}, + "双滦区": {117.78, 40.95}, + "鹰手营子矿区": {117.65, 40.55}, + "承德县": {118.17, 40.77}, + "兴隆县": {117.52, 40.43}, + "平泉县": {118.68, 41}, + "滦平县": {117.33, 40.93}, + "隆化县": {117.72, 41.32}, + "丰宁满族自治县": {116.65, 41.2}, + "宽城满族自治县": {118.48, 40.6}, + "围场满族蒙古族自治县": {117.75, 41.93}, + "沧州市": {116.83, 38.3}, + "运河区": {116.85, 38.32}, + "沧县": {116.87, 38.3}, + "青县": {116.82, 38.58}, + "东光县": {116.53, 37.88}, + "海兴县": {117.48, 38.13}, + "盐山县": {117.22, 38.05}, + "肃宁县": {115.83, 38.43}, + "南皮县": {116.7, 38.03}, + "吴桥县": {116.38, 37.62}, + "献县": {116.12, 38.18}, + "孟村回族自治县": {117.1, 38.07}, + "泊头市": {116.57, 38.07}, + "任丘市": {116.1, 38.72}, + "黄骅市": {117.35, 38.37}, + "河间市": {116.08, 38.43}, + "廊坊市": {116.7, 39.52}, + "安次区": {116.68, 39.52}, + "广阳区": {116.72, 39.53}, + "固安县": {116.3, 39.43}, + "永清县": {116.5, 39.32}, + "香河县": {117, 39.77}, + "大城县": {116.63, 38.7}, + "文安县": {116.47, 38.87}, + "大厂回族自治县": {116.98, 39.88}, + "霸州市": {116.4, 39.1}, + "三河市": {117.07, 39.98}, + "衡水市": {115.68, 37.73}, + "桃城区": {115.68, 37.73}, + "枣强县": {115.72, 37.52}, + "武邑县": {115.88, 37.82}, + "武强县": {115.98, 38.03}, + "饶阳县": {115.73, 38.23}, + "安平县": {115.52, 38.23}, + "故城县": {115.97, 37.35}, + "景县": {116.27, 37.7}, + "阜城县": {116.15, 37.87}, + "冀州市": {115.57, 37.57}, + "深州市": {115.55, 38.02}, + "太原市": {112.55, 37.87}, + "小店区": {112.57, 37.73}, + "迎泽区": {112.57, 37.87}, + "杏花岭区": {112.57, 37.88}, + "尖草坪区": {112.48, 37.93}, + "万柏林区": {112.52, 37.87}, + "晋源区": {112.48, 37.73}, + "清徐县": {112.35, 37.6}, + "阳曲县": {112.67, 38.07}, + "娄烦县": {111.78, 38.07}, + "古交市": {112.17, 37.92}, + "大同市": {113.3, 40.08}, + "南郊区": {113.13, 40}, + "新荣区": {113.15, 40.27}, + "阳高县": {113.75, 40.37}, + "天镇县": {114.08, 40.42}, + "广灵县": {114.28, 39.77}, + "灵丘县": {114.23, 39.43}, + "浑源县": {113.68, 39.7}, + "左云县": {112.7, 40}, + "大同县": {113.6, 40.03}, + "阳泉市": {113.57, 37.85}, + "平定县": {113.62, 37.8}, + "盂县": {113.4, 38.08}, + "长治市": {113.12, 36.2}, + "长治县": {113.03, 36.05}, + "襄垣县": {113.05, 36.53}, + "屯留县": {112.88, 36.32}, + "平顺县": {113.43, 36.2}, + "黎城县": {113.38, 36.5}, + "壶关县": {113.2, 36.12}, + "长子县": {112.87, 36.12}, + "武乡县": {112.85, 36.83}, + "沁县": {112.7, 36.75}, + "沁源县": {112.33, 36.5}, + "潞城市": {113.22, 36.33}, + "晋城市": {112.83, 35.5}, + "沁水县": {112.18, 35.68}, + "阳城县": {112.42, 35.48}, + "陵川县": {113.27, 35.78}, + "泽州县": {112.83, 35.5}, + "高平市": {112.92, 35.8}, + "朔州市": {112.43, 39.33}, + "朔城区": {112.43, 39.33}, + "山阴县": {112.82, 39.52}, + "应县": {113.18, 39.55}, + "右玉县": {112.47, 39.98}, + "怀仁县": {113.08, 39.83}, + "晋中市": {112.75, 37.68}, + "榆次区": {112.75, 37.68}, + "榆社县": {112.97, 37.07}, + "左权县": {113.37, 37.07}, + "和顺县": {113.57, 37.33}, + "昔阳县": {113.7, 37.62}, + "寿阳县": {113.18, 37.88}, + "太谷县": {112.55, 37.42}, + "祁县": {112.33, 37.35}, + "平遥县": {112.17, 37.18}, + "灵石县": {111.77, 36.85}, + "介休市": {111.92, 37.03}, + "运城市": {110.98, 35.02}, + "盐湖区": {110.98, 35.02}, + "临猗县": {110.77, 35.15}, + "万荣县": {110.83, 35.42}, + "闻喜县": {111.22, 35.35}, + "稷山县": {110.97, 35.6}, + "新绛县": {111.22, 35.62}, + "绛县": {111.57, 35.48}, + "垣曲县": {111.67, 35.3}, + "夏县": {111.22, 35.15}, + "平陆县": {111.22, 34.83}, + "芮城县": {110.68, 34.7}, + "河津市": {110.7, 35.6}, + "忻州市": {112.73, 38.42}, + "忻府区": {112.73, 38.42}, + "定襄县": {112.95, 38.48}, + "五台县": {113.25, 38.73}, + "代县": {112.95, 39.07}, + "繁峙县": {113.25, 39.18}, + "宁武县": {112.3, 39}, + "静乐县": {111.93, 38.37}, + "神池县": {112.2, 39.08}, + "五寨县": {111.85, 38.9}, + "岢岚县": {111.57, 38.7}, + "河曲县": {111.13, 39.38}, + "偏关县": {111.5, 39.43}, + "原平市": {112.7, 38.73}, + "临汾市": {111.52, 36.08}, + "尧都区": {111.52, 36.08}, + "曲沃县": {111.47, 35.63}, + "翼城县": {111.72, 35.73}, + "襄汾县": {111.43, 35.88}, + "洪洞县": {111.67, 36.25}, + "古县": {111.92, 36.27}, + "安泽县": {112.25, 36.15}, + "浮山县": {111.83, 35.97}, + "吉县": {110.68, 36.1}, + "乡宁县": {110.83, 35.97}, + "大宁县": {110.75, 36.47}, + "隰县": {110.93, 36.7}, + "永和县": {110.63, 36.77}, + "蒲县": {111.08, 36.42}, + "汾西县": {111.57, 36.65}, + "侯马市": {111.35, 35.62}, + "霍州市": {111.72, 36.57}, + "吕梁市": {111.13, 37.52}, + "离石区": {111.13, 37.52}, + "文水县": {112.02, 37.43}, + "交城县": {112.15, 37.55}, + "兴县": {111.12, 38.47}, + "临县": {110.98, 37.95}, + "柳林县": {110.9, 37.43}, + "石楼县": {110.83, 37}, + "岚县": {111.67, 38.28}, + "方山县": {111.23, 37.88}, + "中阳县": {111.18, 37.33}, + "交口县": {111.2, 36.97}, + "孝义市": {111.77, 37.15}, + "汾阳市": {111.78, 37.27}, + "呼和浩特市": {111.73, 40.83}, + "回民区": {111.6, 40.8}, + "玉泉区": {111.67, 40.75}, + "赛罕区": {111.68, 40.8}, + "土默特左旗": {111.13, 40.72}, + "托克托县": {111.18, 40.27}, + "和林格尔县": {111.82, 40.38}, + "清水河县": {111.68, 39.92}, + "武川县": {111.45, 41.08}, + "包头市": {109.83, 40.65}, + "东河区": {110.02, 40.58}, + "昆都仑区": {109.83, 40.63}, + "石拐区": {110.27, 40.68}, + "九原区": {109.97, 40.6}, + "土默特右旗": {110.52, 40.57}, + "固阳县": {110.05, 41.03}, + "达尔罕茂明安联合旗": {110.43, 41.7}, + "乌海市": {106.82, 39.67}, + "海勃湾区": {106.83, 39.7}, + "海南区": {106.88, 39.43}, + "乌达区": {106.7, 39.5}, + "赤峰市": {118.92, 42.27}, + "红山区": {118.97, 42.28}, + "元宝山区": {119.28, 42.03}, + "松山区": {118.92, 42.28}, + "阿鲁科尔沁旗": {120.08, 43.88}, + "巴林左旗": {119.38, 43.98}, + "巴林右旗": {118.67, 43.52}, + "林西县": {118.05, 43.6}, + "克什克腾旗": {117.53, 43.25}, + "翁牛特旗": {119.02, 42.93}, + "喀喇沁旗": {118.7, 41.93}, + "宁城县": {119.33, 41.6}, + "敖汉旗": {119.9, 42.28}, + "通辽市": {122.27, 43.62}, + "科尔沁区": {122.27, 43.62}, + "科尔沁左翼中旗": {123.32, 44.13}, + "科尔沁左翼后旗": {122.35, 42.95}, + "开鲁县": {121.3, 43.6}, + "库伦旗": {121.77, 42.73}, + "奈曼旗": {120.65, 42.85}, + "扎鲁特旗": {120.92, 44.55}, + "霍林郭勒市": {119.65, 45.53}, + "鄂尔多斯市": {109.8, 39.62}, + "东胜区": {110, 39.82}, + "达拉特旗": {110.03, 40.4}, + "准格尔旗": {111.23, 39.87}, + "鄂托克前旗": {107.48, 38.18}, + "鄂托克旗": {107.98, 39.1}, + "杭锦旗": {108.72, 39.83}, + "乌审旗": {108.85, 38.6}, + "伊金霍洛旗": {109.73, 39.57}, + "呼伦贝尔市": {119.77, 49.22}, + "海拉尔区": {119.77, 49.22}, + "阿荣旗": {123.47, 48.13}, + "鄂伦春自治旗": {123.72, 50.58}, + "鄂温克族自治旗": {119.75, 49.13}, + "陈巴尔虎旗": {119.43, 49.32}, + "新巴尔虎左旗": {118.27, 48.22}, + "新巴尔虎右旗": {116.82, 48.67}, + "满洲里市": {117.45, 49.58}, + "牙克石市": {120.73, 49.28}, + "扎兰屯市": {122.75, 47.98}, + "额尔古纳市": {120.18, 50.23}, + "根河市": {121.52, 50.78}, + "巴彦淖尔市": {107.42, 40.75}, + "临河区": {107.4, 40.75}, + "五原县": {108.27, 41.1}, + "磴口县": {107.02, 40.33}, + "乌拉特前旗": {108.65, 40.72}, + "乌拉特中旗": {108.52, 41.57}, + "乌拉特后旗": {107.07, 41.1}, + "杭锦后旗": {107.15, 40.88}, + "乌兰察布市": {113.12, 40.98}, + "集宁区": {113.1, 41.03}, + "卓资县": {112.57, 40.9}, + "化德县": {114, 41.9}, + "商都县": {113.53, 41.55}, + "兴和县": {113.88, 40.88}, + "凉城县": {112.48, 40.53}, + "察哈尔右翼前旗": {113.22, 40.78}, + "察哈尔右翼中旗": {112.63, 41.27}, + "察哈尔右翼后旗": {113.18, 41.45}, + "四子王旗": {111.7, 41.52}, + "丰镇市": {113.15, 40.43}, + "兴安盟": {122.05, 46.08}, + "乌兰浩特市": {122.05, 46.08}, + "阿尔山市": {119.93, 47.18}, + "科尔沁右翼前旗": {121.92, 46.07}, + "科尔沁右翼中旗": {121.47, 45.05}, + "扎赉特旗": {122.9, 46.73}, + "突泉县": {121.57, 45.38}, + "锡林郭勒盟": {116.07, 43.95}, + "二连浩特市": {111.98, 43.65}, + "锡林浩特市": {116.07, 43.93}, + "阿巴嘎旗": {114.97, 44.02}, + "苏尼特左旗": {113.63, 43.85}, + "苏尼特右旗": {112.65, 42.75}, + "东乌珠穆沁旗": {116.97, 45.52}, + "西乌珠穆沁旗": {117.6, 44.58}, + "太仆寺旗": {115.28, 41.9}, + "镶黄旗": {113.83, 42.23}, + "正镶白旗": {115, 42.3}, + "正蓝旗": {116, 42.25}, + "多伦县": {116.47, 42.18}, + "阿拉善盟": {105.67, 38.83}, + "阿拉善左旗": {105.67, 38.83}, + "阿拉善右旗": {101.68, 39.2}, + "额济纳旗": {101.07, 41.97}, + "沈阳市": {123.43, 41.8}, + "和平区": {123.4, 41.78}, + "沈河区": {123.45, 41.8}, + "大东区": {123.47, 41.8}, + "皇姑区": {123.42, 41.82}, + "铁西区": {123.35, 41.8}, + "苏家屯区": {123.33, 41.67}, + "东陵区": {123.47, 41.77}, + "新城子区": {123.52, 42.05}, + "于洪区": {123.3, 41.78}, + "辽中县": {122.72, 41.52}, + "康平县": {123.35, 42.75}, + "法库县": {123.4, 42.5}, + "新民市": {122.82, 42}, + "大连市": {121.62, 38.92}, + "中山区": {121.63, 38.92}, + "西岗区": {121.6, 38.92}, + "沙河口区": {121.58, 38.9}, + "甘井子区": {121.57, 38.95}, + "旅顺口区": {121.27, 38.82}, + "金州区": {121.7, 39.1}, + "长海县": {122.58, 39.27}, + "瓦房店市": {122, 39.62}, + "普兰店市": {121.95, 39.4}, + "庄河市": {122.98, 39.7}, + "鞍山市": {122.98, 41.1}, + "立山区": {123, 41.15}, + "千山区": {122.97, 41.07}, + "台安县": {122.42, 41.38}, + "岫岩满族自治县": {123.28, 40.28}, + "海城市": {122.7, 40.88}, + "抚顺市": {123.98, 41.88}, + "新抚区": {123.88, 41.87}, + "东洲区": {124.02, 41.85}, + "望花区": {123.78, 41.85}, + "顺城区": {123.93, 41.88}, + "抚顺县": {123.9, 41.88}, + "新宾满族自治县": {125.03, 41.73}, + "清原满族自治县": {124.92, 42.1}, + "本溪市": {123.77, 41.3}, + "平山区": {123.77, 41.3}, + "溪湖区": {123.77, 41.33}, + "明山区": {123.82, 41.3}, + "南芬区": {123.73, 41.1}, + "本溪满族自治县": {124.12, 41.3}, + "桓仁满族自治县": {125.35, 41.27}, + "丹东市": {124.38, 40.13}, + "元宝区": {124.38, 40.13}, + "振兴区": {124.35, 40.08}, + "振安区": {124.42, 40.17}, + "宽甸满族自治县": {124.78, 40.73}, + "东港市": {124.15, 39.87}, + "凤城市": {124.07, 40.45}, + "锦州市": {121.13, 41.1}, + "古塔区": {121.12, 41.13}, + "凌河区": {121.15, 41.12}, + "太和区": {121.1, 41.1}, + "黑山县": {122.12, 41.7}, + "义县": {121.23, 41.53}, + "凌海市": {121.35, 41.17}, + "营口市": {122.23, 40.67}, + "站前区": {122.27, 40.68}, + "西市区": {122.22, 40.67}, + "鲅鱼圈区": {122.12, 40.27}, + "老边区": {122.37, 40.67}, + "盖州市": {122.35, 40.4}, + "大石桥市": {122.5, 40.65}, + "阜新市": {121.67, 42.02}, + "海州区": {121.65, 42.02}, + "太平区": {121.67, 42.02}, + "清河门区": {121.42, 41.75}, + "细河区": {121.68, 42.03}, + "阜新蒙古族自治县": {121.75, 42.07}, + "彰武县": {122.53, 42.38}, + "辽阳市": {123.17, 41.27}, + "白塔区": {123.17, 41.27}, + "文圣区": {123.18, 41.27}, + "宏伟区": {123.2, 41.2}, + "弓长岭区": {123.45, 41.13}, + "太子河区": {123.18, 41.25}, + "辽阳县": {123.07, 41.22}, + "灯塔市": {123.33, 41.42}, + "盘锦市": {122.07, 41.12}, + "双台子区": {122.05, 41.2}, + "兴隆台区": {122.07, 41.12}, + "大洼县": {122.07, 40.98}, + "盘山县": {122.02, 41.25}, + "铁岭市": {123.83, 42.28}, + "银州区": {123.85, 42.28}, + "铁岭县": {123.83, 42.3}, + "西丰县": {124.72, 42.73}, + "昌图县": {124.1, 42.78}, + "调兵山市": {123.55, 42.47}, + "开原市": {124.03, 42.55}, + "朝阳市": {120.45, 41.57}, + "双塔区": {120.45, 41.57}, + "龙城区": {120.43, 41.6}, + "朝阳县": {120.47, 41.58}, + "建平县": {119.63, 41.4}, + "北票市": {120.77, 41.8}, + "凌源市": {119.4, 41.25}, + "葫芦岛市": {120.83, 40.72}, + "连山区": {120.87, 40.77}, + "龙港区": {120.93, 40.72}, + "南票区": {120.75, 41.1}, + "绥中县": {120.33, 40.32}, + "建昌县": {119.8, 40.82}, + "兴城市": {120.72, 40.62}, + "长春市": {125.32, 43.9}, + "南关区": {125.33, 43.87}, + "宽城区": {125.32, 43.92}, + "朝阳区": {125.28, 43.83}, + "二道区": {125.37, 43.87}, + "绿园区": {125.25, 43.88}, + "双阳区": {125.67, 43.52}, + "农安县": {125.18, 44.43}, + "九台市": {125.83, 44.15}, + "榆树市": {126.55, 44.82}, + "德惠市": {125.7, 44.53}, + "吉林市": {126.55, 43.83}, + "昌邑区": {126.57, 43.88}, + "龙潭区": {126.57, 43.92}, + "船营区": {126.53, 43.83}, + "丰满区": {126.57, 43.82}, + "永吉县": {126.5, 43.67}, + "蛟河市": {127.33, 43.72}, + "桦甸市": {126.73, 42.97}, + "舒兰市": {126.95, 44.42}, + "磐石市": {126.05, 42.95}, + "四平市": {124.35, 43.17}, + "铁东区": {124.38, 43.17}, + "梨树县": {124.33, 43.32}, + "伊通满族自治县": {125.3, 43.35}, + "公主岭市": {124.82, 43.5}, + "双辽市": {123.5, 43.52}, + "辽源市": {125.13, 42.88}, + "龙山区": {125.12, 42.9}, + "西安区": {125.15, 42.92}, + "东丰县": {125.53, 42.68}, + "东辽县": {125, 42.92}, + "通化市": {125.93, 41.73}, + "东昌区": {125.95, 41.73}, + "二道江区": {126.03, 41.77}, + "通化县": {125.75, 41.68}, + "辉南县": {126.03, 42.68}, + "柳河县": {125.73, 42.28}, + "梅河口市": {125.68, 42.53}, + "集安市": {126.18, 41.12}, + "白山市": {126.42, 41.93}, + "八道江区": {126.4, 41.93}, + "抚松县": {127.28, 42.33}, + "靖宇县": {126.8, 42.4}, + "长白朝鲜族自治县": {128.2, 41.42}, + "临江市": {126.9, 41.8}, + "松原市": {124.82, 45.13}, + "宁江区": {124.8, 45.17}, + "长岭县": {123.98, 44.28}, + "乾安县": {124.02, 45.02}, + "扶余县": {126.02, 44.98}, + "白城市": {122.83, 45.62}, + "洮北区": {122.85, 45.62}, + "镇赉县": {123.2, 45.85}, + "通榆县": {123.08, 44.82}, + "洮南市": {122.78, 45.33}, + "大安市": {124.28, 45.5}, + "延边朝鲜族自治州": {129.5, 42.88}, + "延吉市": {129.5, 42.88}, + "图们市": {129.83, 42.97}, + "敦化市": {128.23, 43.37}, + "珲春市": {130.37, 42.87}, + "龙井市": {129.42, 42.77}, + "和龙市": {129, 42.53}, + "汪清县": {129.75, 43.32}, + "安图县": {128.9, 43.12}, + "哈尔滨市": {126.53, 45.8}, + "道里区": {126.62, 45.77}, + "南岗区": {126.68, 45.77}, + "道外区": {126.65, 45.78}, + "香坊区": {126.68, 45.72}, + "平房区": {126.62, 45.62}, + "松北区": {126.55, 45.8}, + "呼兰区": {126.58, 45.9}, + "依兰县": {129.55, 46.32}, + "方正县": {128.83, 45.83}, + "宾县": {127.48, 45.75}, + "巴彦县": {127.4, 46.08}, + "木兰县": {128.03, 45.95}, + "通河县": {128.75, 45.97}, + "延寿县": {128.33, 45.45}, + "双城市": {126.32, 45.37}, + "尚志市": {127.95, 45.22}, + "五常市": {127.15, 44.92}, + "齐齐哈尔市": {123.95, 47.33}, + "龙沙区": {123.95, 47.32}, + "建华区": {123.95, 47.35}, + "铁锋区": {123.98, 47.35}, + "昂昂溪区": {123.8, 47.15}, + "富拉尔基区": {123.62, 47.2}, + "龙江县": {123.18, 47.33}, + "依安县": {125.3, 47.88}, + "泰来县": {123.42, 46.4}, + "甘南县": {123.5, 47.92}, + "富裕县": {124.47, 47.82}, + "克山县": {125.87, 48.03}, + "克东县": {126.25, 48.03}, + "拜泉县": {126.08, 47.6}, + "讷河市": {124.87, 48.48}, + "鸡西市": {130.97, 45.3}, + "鸡冠区": {130.97, 45.3}, + "恒山区": {130.93, 45.2}, + "滴道区": {130.78, 45.37}, + "梨树区": {130.68, 45.08}, + "城子河区": {131, 45.33}, + "麻山区": {130.52, 45.2}, + "鸡东县": {131.13, 45.25}, + "虎林市": {132.98, 45.77}, + "密山市": {131.87, 45.55}, + "鹤岗市": {130.27, 47.33}, + "向阳区": {130.28, 47.33}, + "工农区": {130.25, 47.32}, + "兴安区": {130.22, 47.27}, + "东山区": {130.32, 47.33}, + "兴山区": {130.3, 47.37}, + "萝北县": {130.83, 47.58}, + "绥滨县": {131.85, 47.28}, + "双鸭山市": {131.15, 46.63}, + "尖山区": {131.17, 46.63}, + "岭东区": {131.13, 46.57}, + "四方台区": {131.33, 46.58}, + "集贤县": {131.13, 46.72}, + "友谊县": {131.8, 46.78}, + "宝清县": {132.2, 46.32}, + "饶河县": {134.02, 46.8}, + "大庆市": {125.03, 46.58}, + "萨尔图区": {125.02, 46.6}, + "龙凤区": {125.1, 46.53}, + "让胡路区": {124.85, 46.65}, + "红岗区": {124.88, 46.4}, + "大同区": {124.82, 46.03}, + "肇州县": {125.27, 45.7}, + "肇源县": {125.08, 45.52}, + "林甸县": {124.87, 47.18}, + "杜尔伯特蒙古族自治县": {124.45, 46.87}, + "伊春市": {128.9, 47.73}, + "南岔区": {129.28, 47.13}, + "友好区": {128.82, 47.85}, + "西林区": {129.28, 47.48}, + "翠峦区": {128.65, 47.72}, + "新青区": {129.53, 48.28}, + "美溪区": {129.13, 47.63}, + "金山屯区": {129.43, 47.42}, + "五营区": {129.25, 48.12}, + "乌马河区": {128.78, 47.72}, + "汤旺河区": {129.57, 48.45}, + "带岭区": {129.02, 47.02}, + "乌伊岭区": {129.42, 48.6}, + "红星区": {129.38, 48.23}, + "上甘岭区": {129.02, 47.97}, + "嘉荫县": {130.38, 48.88}, + "铁力市": {128.02, 46.98}, + "佳木斯市": {130.37, 46.82}, + "前进区": {130.37, 46.82}, + "东风区": {130.4, 46.82}, + "桦南县": {130.57, 46.23}, + "桦川县": {130.72, 47.02}, + "汤原县": {129.9, 46.73}, + "抚远县": {134.28, 48.37}, + "同江市": {132.52, 47.65}, + "富锦市": {132.03, 47.25}, + "七台河市": {130.95, 45.78}, + "新兴区": {130.83, 45.8}, + "桃山区": {130.97, 45.77}, + "茄子河区": {131.07, 45.77}, + "勃利县": {130.57, 45.75}, + "牡丹江市": {129.6, 44.58}, + "东安区": {129.62, 44.58}, + "阳明区": {129.63, 44.6}, + "爱民区": {129.58, 44.58}, + "东宁县": {131.12, 44.07}, + "林口县": {130.27, 45.3}, + "绥芬河市": {131.15, 44.42}, + "海林市": {129.38, 44.57}, + "宁安市": {129.47, 44.35}, + "穆棱市": {130.52, 44.92}, + "黑河市": {127.48, 50.25}, + "爱辉区": {127.48, 50.25}, + "逊克县": {128.47, 49.58}, + "孙吴县": {127.32, 49.42}, + "北安市": {126.52, 48.23}, + "五大连池市": {126.2, 48.52}, + "绥化市": {126.98, 46.63}, + "北林区": {126.98, 46.63}, + "望奎县": {126.48, 46.83}, + "兰西县": {126.28, 46.27}, + "青冈县": {126.1, 46.68}, + "庆安县": {127.52, 46.88}, + "明水县": {125.9, 47.18}, + "绥棱县": {127.1, 47.25}, + "安达市": {125.33, 46.4}, + "肇东市": {125.98, 46.07}, + "海伦市": {126.97, 47.47}, + "大兴安岭地区": {124.12, 50.42}, + "呼玛县": {126.65, 51.73}, + "塔河县": {124.7, 52.32}, + "漠河县": {122.53, 52.97}, + "上海市": {121.47, 31.23}, + "黄浦区": {121.48, 31.23}, + "卢湾区": {121.47, 31.22}, + "徐汇区": {121.43, 31.18}, + "长宁区": {121.42, 31.22}, + "静安区": {121.45, 31.23}, + "闸北区": {121.45, 31.25}, + "虹口区": {121.5, 31.27}, + "杨浦区": {121.52, 31.27}, + "闵行区": {121.38, 31.12}, + "宝山区": {121.48, 31.4}, + "嘉定区": {121.27, 31.38}, + "浦东新区": {121.53, 31.22}, + "金山区": {121.33, 30.75}, + "松江区": {121.22, 31.03}, + "青浦区": {121.12, 31.15}, + "南汇区": {121.75, 31.05}, + "奉贤区": {121.47, 30.92}, + "崇明县": {121.4, 31.62}, + "南京市": {118.78, 32.07}, + "玄武区": {118.8, 32.05}, + "白下区": {118.78, 32.03}, + "秦淮区": {118.8, 32.02}, + "建邺区": {118.75, 32.03}, + "下关区": {118.73, 32.08}, + "浦口区": {118.62, 32.05}, + "栖霞区": {118.88, 32.12}, + "雨花台区": {118.77, 32}, + "江宁区": {118.85, 31.95}, + "六合区": {118.83, 32.35}, + "溧水县": {119.02, 31.65}, + "高淳县": {118.88, 31.33}, + "无锡市": {120.3, 31.57}, + "崇安区": {120.3, 31.58}, + "南长区": {120.3, 31.57}, + "北塘区": {120.28, 31.58}, + "锡山区": {120.35, 31.6}, + "惠山区": {120.28, 31.68}, + "滨湖区": {120.27, 31.57}, + "江阴市": {120.27, 31.9}, + "宜兴市": {119.82, 31.35}, + "徐州市": {117.18, 34.27}, + "云龙区": {117.22, 34.25}, + "九里区": {117.13, 34.3}, + "贾汪区": {117.45, 34.45}, + "泉山区": {117.18, 34.25}, + "丰县": {116.6, 34.7}, + "沛县": {116.93, 34.73}, + "铜山县": {117.17, 34.18}, + "睢宁县": {117.95, 33.9}, + "新沂市": {118.35, 34.38}, + "邳州市": {117.95, 34.32}, + "常州市": {119.95, 31.78}, + "天宁区": {119.93, 31.75}, + "钟楼区": {119.93, 31.78}, + "戚墅堰区": {120.05, 31.73}, + "新北区": {119.97, 31.83}, + "武进区": {119.93, 31.72}, + "溧阳市": {119.48, 31.42}, + "金坛市": {119.57, 31.75}, + "苏州市": {120.58, 31.3}, + "沧浪区": {120.63, 31.3}, + "平江区": {120.63, 31.32}, + "金阊区": {120.6, 31.32}, + "虎丘区": {120.57, 31.3}, + "吴中区": {120.63, 31.27}, + "相城区": {120.63, 31.37}, + "常熟市": {120.75, 31.65}, + "张家港市": {120.55, 31.87}, + "昆山市": {120.98, 31.38}, + "吴江市": {120.63, 31.17}, + "太仓市": {121.1, 31.45}, + "南通市": {120.88, 31.98}, + "崇川区": {120.85, 32}, + "港闸区": {120.8, 32.03}, + "海安县": {120.45, 32.55}, + "如东县": {121.18, 32.32}, + "启东市": {121.65, 31.82}, + "如皋市": {120.57, 32.4}, + "通州市": {121.07, 32.08}, + "海门市": {121.17, 31.9}, + "连云港市": {119.22, 34.6}, + "连云区": {119.37, 34.75}, + "新浦区": {119.17, 34.6}, + "赣榆县": {119.12, 34.83}, + "东海县": {118.77, 34.53}, + "灌云县": {119.25, 34.3}, + "灌南县": {119.35, 34.08}, + "淮安市": {119.02, 33.62}, + "清河区": {119.02, 33.6}, + "楚州区": {119.13, 33.5}, + "淮阴区": {119.03, 33.63}, + "清浦区": {119.03, 33.58}, + "涟水县": {119.27, 33.78}, + "洪泽县": {118.83, 33.3}, + "盱眙县": {118.48, 33}, + "金湖县": {119.02, 33.02}, + "盐城市": {120.15, 33.35}, + "亭湖区": {120.13, 33.4}, + "盐都区": {120.15, 33.33}, + "响水县": {119.57, 34.2}, + "滨海县": {119.83, 33.98}, + "阜宁县": {119.8, 33.78}, + "射阳县": {120.25, 33.78}, + "建湖县": {119.8, 33.47}, + "东台市": {120.3, 32.85}, + "大丰市": {120.47, 33.2}, + "扬州市": {119.4, 32.4}, + "广陵区": {119.43, 32.38}, + "邗江区": {119.4, 32.38}, + "维扬区": {119.4, 32.42}, + "宝应县": {119.3, 33.23}, + "仪征市": {119.18, 32.27}, + "高邮市": {119.43, 32.78}, + "江都市": {119.55, 32.43}, + "镇江市": {119.45, 32.2}, + "京口区": {119.47, 32.2}, + "润州区": {119.4, 32.2}, + "丹徒区": {119.45, 32.13}, + "丹阳市": {119.57, 32}, + "扬中市": {119.82, 32.23}, + "句容市": {119.17, 31.95}, + "泰州市": {119.92, 32.45}, + "兴化市": {119.85, 32.92}, + "靖江市": {120.27, 32.02}, + "泰兴市": {120.02, 32.17}, + "姜堰市": {120.15, 32.52}, + "宿迁市": {118.28, 33.97}, + "宿城区": {118.25, 33.97}, + "宿豫区": {118.32, 33.95}, + "沭阳县": {118.77, 34.13}, + "泗阳县": {118.68, 33.72}, + "泗洪县": {118.22, 33.47}, + "杭州市": {120.15, 30.28}, + "上城区": {120.17, 30.25}, + "下城区": {120.17, 30.28}, + "江干区": {120.2, 30.27}, + "拱墅区": {120.13, 30.32}, + "滨江区": {120.2, 30.2}, + "萧山区": {120.27, 30.17}, + "余杭区": {120.3, 30.42}, + "桐庐县": {119.67, 29.8}, + "淳安县": {119.03, 29.6}, + "建德市": {119.28, 29.48}, + "富阳市": {119.95, 30.05}, + "临安市": {119.72, 30.23}, + "宁波市": {121.55, 29.88}, + "海曙区": {121.55, 29.87}, + "江东区": {121.57, 29.87}, + "北仑区": {121.85, 29.93}, + "镇海区": {121.72, 29.95}, + "鄞州区": {121.53, 29.83}, + "象山县": {121.87, 29.48}, + "宁海县": {121.43, 29.28}, + "余姚市": {121.15, 30.03}, + "慈溪市": {121.23, 30.17}, + "奉化市": {121.4, 29.65}, + "温州市": {120.7, 28}, + "鹿城区": {120.65, 28.02}, + "龙湾区": {120.82, 27.93}, + "洞头县": {121.15, 27.83}, + "永嘉县": {120.68, 28.15}, + "平阳县": {120.57, 27.67}, + "苍南县": {120.4, 27.5}, + "文成县": {120.08, 27.78}, + "泰顺县": {119.72, 27.57}, + "瑞安市": {120.63, 27.78}, + "乐清市": {120.95, 28.13}, + "嘉兴市": {120.75, 30.75}, + "秀洲区": {120.7, 30.77}, + "嘉善县": {120.92, 30.85}, + "海盐县": {120.95, 30.53}, + "海宁市": {120.68, 30.53}, + "平湖市": {121.02, 30.7}, + "桐乡市": {120.57, 30.63}, + "湖州市": {120.08, 30.9}, + "吴兴区": {120.12, 30.87}, + "南浔区": {120.43, 30.88}, + "德清县": {119.97, 30.53}, + "长兴县": {119.9, 31.02}, + "安吉县": {119.68, 30.63}, + "绍兴市": {120.57, 30}, + "越城区": {120.57, 30}, + "绍兴县": {120.47, 30.08}, + "新昌县": {120.9, 29.5}, + "诸暨市": {120.23, 29.72}, + "上虞市": {120.87, 30.03}, + "嵊州市": {120.82, 29.58}, + "金华市": {119.65, 29.08}, + "婺城区": {119.65, 29.08}, + "金东区": {119.7, 29.08}, + "武义县": {119.82, 28.9}, + "浦江县": {119.88, 29.45}, + "磐安县": {120.43, 29.05}, + "兰溪市": {119.45, 29.22}, + "义乌市": {120.07, 29.3}, + "东阳市": {120.23, 29.28}, + "永康市": {120.03, 28.9}, + "衢州市": {118.87, 28.93}, + "柯城区": {118.87, 28.93}, + "衢江区": {118.93, 28.98}, + "常山县": {118.52, 28.9}, + "开化县": {118.42, 29.13}, + "龙游县": {119.17, 29.03}, + "江山市": {118.62, 28.75}, + "舟山市": {122.2, 30}, + "定海区": {122.1, 30.02}, + "普陀区": {122.3, 29.95}, + "岱山县": {122.2, 30.25}, + "嵊泗县": {122.45, 30.73}, + "台州市": {121.43, 28.68}, + "椒江区": {121.43, 28.68}, + "黄岩区": {121.27, 28.65}, + "路桥区": {121.38, 28.58}, + "玉环县": {121.23, 28.13}, + "三门县": {121.38, 29.12}, + "天台县": {121.03, 29.13}, + "仙居县": {120.73, 28.87}, + "温岭市": {121.37, 28.37}, + "临海市": {121.12, 28.85}, + "丽水市": {119.92, 28.45}, + "莲都区": {119.92, 28.45}, + "青田县": {120.28, 28.15}, + "缙云县": {120.07, 28.65}, + "遂昌县": {119.27, 28.6}, + "松阳县": {119.48, 28.45}, + "云和县": {119.57, 28.12}, + "庆元县": {119.05, 27.62}, + "景宁畲族自治县": {119.63, 27.98}, + "龙泉市": {119.13, 28.08}, + "合肥市": {117.25, 31.83}, + "瑶海区": {117.3, 31.87}, + "庐阳区": {117.25, 31.88}, + "蜀山区": {117.27, 31.85}, + "包河区": {117.3, 31.8}, + "长丰县": {117.17, 32.48}, + "肥东县": {117.47, 31.88}, + "肥西县": {117.17, 31.72}, + "芜湖市": {118.38, 31.33}, + "镜湖区": {118.37, 31.35}, + "鸠江区": {118.38, 31.37}, + "芜湖县": {118.57, 31.15}, + "繁昌县": {118.2, 31.08}, + "南陵县": {118.33, 30.92}, + "蚌埠市": {117.38, 32.92}, + "龙子湖区": {117.38, 32.95}, + "蚌山区": {117.35, 32.95}, + "禹会区": {117.33, 32.93}, + "淮上区": {117.35, 32.97}, + "怀远县": {117.18, 32.97}, + "五河县": {117.88, 33.15}, + "固镇县": {117.32, 33.32}, + "淮南市": {117, 32.63}, + "大通区": {117.05, 32.63}, + "田家庵区": {117, 32.67}, + "谢家集区": {116.85, 32.6}, + "八公山区": {116.83, 32.63}, + "潘集区": {116.82, 32.78}, + "凤台县": {116.72, 32.7}, + "马鞍山市": {118.5, 31.7}, + "金家庄区": {118.48, 31.73}, + "花山区": {118.5, 31.72}, + "雨山区": {118.48, 31.68}, + "当涂县": {118.48, 31.55}, + "淮北市": {116.8, 33.95}, + "杜集区": {116.82, 34}, + "相山区": {116.8, 33.95}, + "烈山区": {116.8, 33.9}, + "濉溪县": {116.77, 33.92}, + "铜陵市": {117.82, 30.93}, + "铜官山区": {117.82, 30.93}, + "狮子山区": {117.85, 30.95}, + "铜陵县": {117.78, 30.95}, + "安庆市": {117.05, 30.53}, + "迎江区": {117.05, 30.5}, + "大观区": {117.03, 30.52}, + "怀宁县": {116.83, 30.72}, + "枞阳县": {117.2, 30.7}, + "潜山县": {116.57, 30.63}, + "太湖县": {116.27, 30.43}, + "宿松县": {116.12, 30.15}, + "望江县": {116.68, 30.13}, + "岳西县": {116.35, 30.85}, + "桐城市": {116.95, 31.05}, + "黄山市": {118.33, 29.72}, + "屯溪区": {118.33, 29.72}, + "黄山区": {118.13, 30.3}, + "徽州区": {118.33, 29.82}, + "歙县": {118.43, 29.87}, + "休宁县": {118.18, 29.78}, + "黟县": {117.93, 29.93}, + "祁门县": {117.72, 29.87}, + "滁州市": {118.32, 32.3}, + "琅琊区": {118.3, 32.3}, + "南谯区": {118.3, 32.32}, + "来安县": {118.43, 32.45}, + "全椒县": {118.27, 32.1}, + "定远县": {117.67, 32.53}, + "凤阳县": {117.57, 32.87}, + "天长市": {119, 32.7}, + "明光市": {117.98, 32.78}, + "阜阳市": {115.82, 32.9}, + "颍州区": {115.8, 32.88}, + "颍东区": {115.85, 32.92}, + "颍泉区": {115.8, 32.93}, + "临泉县": {115.25, 33.07}, + "太和县": {115.62, 33.17}, + "阜南县": {115.58, 32.63}, + "颍上县": {116.27, 32.63}, + "宿州市": {116.98, 33.63}, + "埇桥区": {116.97, 33.63}, + "砀山县": {116.35, 34.42}, + "萧县": {116.93, 34.18}, + "灵璧县": {117.55, 33.55}, + "泗县": {117.88, 33.48}, + "巢湖市": {117.87, 31.6}, + "居巢区": {117.85, 31.6}, + "庐江县": {117.28, 31.25}, + "无为县": {117.92, 31.3}, + "含山县": {118.1, 31.72}, + "和县": {118.37, 31.72}, + "六安市": {116.5, 31.77}, + "金安区": {116.5, 31.77}, + "裕安区": {116.48, 31.77}, + "寿县": {116.78, 32.58}, + "霍邱县": {116.27, 32.33}, + "舒城县": {116.93, 31.47}, + "金寨县": {115.92, 31.72}, + "霍山县": {116.33, 31.4}, + "亳州市": {115.78, 33.85}, + "谯城区": {115.77, 33.88}, + "涡阳县": {116.22, 33.52}, + "蒙城县": {116.57, 33.27}, + "利辛县": {116.2, 33.15}, + "池州市": {117.48, 30.67}, + "贵池区": {117.48, 30.65}, + "东至县": {117.02, 30.1}, + "石台县": {117.48, 30.22}, + "青阳县": {117.85, 30.65}, + "宣城市": {118.75, 30.95}, + "宣州区": {118.75, 30.95}, + "郎溪县": {119.17, 31.13}, + "广德县": {119.42, 30.9}, + "泾县": {118.4, 30.7}, + "绩溪县": {118.6, 30.07}, + "旌德县": {118.53, 30.28}, + "宁国市": {118.98, 30.63}, + "福州市": {119.3, 26.08}, + "鼓楼区": {119.3, 26.08}, + "台江区": {119.3, 26.07}, + "仓山区": {119.32, 26.05}, + "马尾区": {119.45, 26}, + "晋安区": {119.32, 26.08}, + "闽侯县": {119.13, 26.15}, + "连江县": {119.53, 26.2}, + "罗源县": {119.55, 26.48}, + "闽清县": {118.85, 26.22}, + "永泰县": {118.93, 25.87}, + "平潭县": {119.78, 25.52}, + "福清市": {119.38, 25.72}, + "长乐市": {119.52, 25.97}, + "厦门市": {118.08, 24.48}, + "思明区": {118.08, 24.45}, + "海沧区": {117.98, 24.47}, + "湖里区": {118.08, 24.52}, + "集美区": {118.1, 24.57}, + "同安区": {118.15, 24.73}, + "翔安区": {118.23, 24.62}, + "莆田市": {119, 25.43}, + "城厢区": {119, 25.43}, + "涵江区": {119.1, 25.45}, + "荔城区": {119.02, 25.43}, + "秀屿区": {119.08, 25.32}, + "仙游县": {118.68, 25.37}, + "三明市": {117.62, 26.27}, + "梅列区": {117.63, 26.27}, + "三元区": {117.6, 26.23}, + "明溪县": {117.2, 26.37}, + "清流县": {116.82, 26.18}, + "宁化县": {116.65, 26.27}, + "大田县": {117.85, 25.7}, + "尤溪县": {118.18, 26.17}, + "沙县": {117.78, 26.4}, + "将乐县": {117.47, 26.73}, + "泰宁县": {117.17, 26.9}, + "建宁县": {116.83, 26.83}, + "永安市": {117.37, 25.98}, + "泉州市": {118.67, 24.88}, + "鲤城区": {118.6, 24.92}, + "丰泽区": {118.6, 24.92}, + "洛江区": {118.67, 24.95}, + "泉港区": {118.88, 25.12}, + "惠安县": {118.8, 25.03}, + "安溪县": {118.18, 25.07}, + "永春县": {118.3, 25.32}, + "德化县": {118.23, 25.5}, + "金门县": {118.32, 24.43}, + "石狮市": {118.65, 24.73}, + "晋江市": {118.58, 24.82}, + "南安市": {118.38, 24.97}, + "漳州市": {117.65, 24.52}, + "芗城区": {117.65, 24.52}, + "龙文区": {117.72, 24.52}, + "云霄县": {117.33, 23.95}, + "漳浦县": {117.62, 24.13}, + "诏安县": {117.18, 23.72}, + "长泰县": {117.75, 24.62}, + "东山县": {117.43, 23.7}, + "南靖县": {117.37, 24.52}, + "平和县": {117.3, 24.37}, + "华安县": {117.53, 25.02}, + "龙海市": {117.82, 24.45}, + "南平市": {118.17, 26.65}, + "延平区": {118.17, 26.65}, + "顺昌县": {117.8, 26.8}, + "浦城县": {118.53, 27.92}, + "光泽县": {117.33, 27.55}, + "松溪县": {118.78, 27.53}, + "政和县": {118.85, 27.37}, + "邵武市": {117.48, 27.37}, + "武夷山市": {118.03, 27.77}, + "建瓯市": {118.32, 27.03}, + "建阳市": {118.12, 27.33}, + "龙岩市": {117.03, 25.1}, + "新罗区": {117.03, 25.1}, + "长汀县": {116.35, 25.83}, + "永定县": {116.73, 24.72}, + "上杭县": {116.42, 25.05}, + "武平县": {116.1, 25.1}, + "连城县": {116.75, 25.72}, + "漳平市": {117.42, 25.3}, + "宁德市": {119.52, 26.67}, + "蕉城区": {119.52, 26.67}, + "霞浦县": {120, 26.88}, + "古田县": {118.75, 26.58}, + "屏南县": {118.98, 26.92}, + "寿宁县": {119.5, 27.47}, + "周宁县": {119.33, 27.12}, + "柘荣县": {119.9, 27.23}, + "福安市": {119.65, 27.08}, + "福鼎市": {120.22, 27.33}, + "南昌市": {115.85, 28.68}, + "东湖区": {115.9, 28.68}, + "西湖区": {115.87, 28.67}, + "青云谱区": {115.92, 28.63}, + "湾里区": {115.73, 28.72}, + "青山湖区": {115.95, 28.68}, + "南昌县": {115.93, 28.55}, + "新建县": {115.82, 28.7}, + "安义县": {115.55, 28.85}, + "进贤县": {116.27, 28.37}, + "景德镇市": {117.17, 29.27}, + "昌江区": {117.17, 29.27}, + "珠山区": {117.2, 29.3}, + "浮梁县": {117.25, 29.37}, + "乐平市": {117.12, 28.97}, + "萍乡市": {113.85, 27.63}, + "安源区": {113.87, 27.65}, + "湘东区": {113.73, 27.65}, + "莲花县": {113.95, 27.13}, + "上栗县": {113.8, 27.88}, + "芦溪县": {114.03, 27.63}, + "九江市": {116, 29.7}, + "庐山区": {115.98, 29.68}, + "浔阳区": {115.98, 29.73}, + "九江县": {115.88, 29.62}, + "武宁县": {115.1, 29.27}, + "修水县": {114.57, 29.03}, + "永修县": {115.8, 29.03}, + "德安县": {115.77, 29.33}, + "星子县": {116.03, 29.45}, + "都昌县": {116.18, 29.27}, + "湖口县": {116.22, 29.73}, + "彭泽县": {116.55, 29.9}, + "瑞昌市": {115.67, 29.68}, + "新余市": {114.92, 27.82}, + "渝水区": {114.93, 27.8}, + "分宜县": {114.67, 27.82}, + "鹰潭市": {117.07, 28.27}, + "月湖区": {117.05, 28.23}, + "余江县": {116.82, 28.2}, + "贵溪市": {117.22, 28.28}, + "赣州市": {114.93, 25.83}, + "章贡区": {114.93, 25.87}, + "赣县": {115, 25.87}, + "信丰县": {114.93, 25.38}, + "大余县": {114.35, 25.4}, + "上犹县": {114.53, 25.8}, + "崇义县": {114.3, 25.7}, + "安远县": {115.38, 25.13}, + "龙南县": {114.78, 24.92}, + "定南县": {115.03, 24.78}, + "全南县": {114.52, 24.75}, + "宁都县": {116.02, 26.48}, + "于都县": {115.42, 25.95}, + "兴国县": {115.35, 26.33}, + "会昌县": {115.78, 25.6}, + "寻乌县": {115.65, 24.95}, + "石城县": {116.33, 26.33}, + "瑞金市": {116.03, 25.88}, + "南康市": {114.75, 25.65}, + "吉安市": {114.98, 27.12}, + "吉州区": {114.98, 27.12}, + "青原区": {115, 27.1}, + "吉安县": {114.9, 27.05}, + "吉水县": {115.13, 27.22}, + "峡江县": {115.33, 27.62}, + "新干县": {115.4, 27.77}, + "永丰县": {115.43, 27.32}, + "泰和县": {114.88, 26.8}, + "遂川县": {114.52, 26.33}, + "万安县": {114.78, 26.47}, + "安福县": {114.62, 27.38}, + "永新县": {114.23, 26.95}, + "井冈山市": {114.27, 26.72}, + "宜春市": {114.38, 27.8}, + "袁州区": {114.38, 27.8}, + "奉新县": {115.38, 28.7}, + "万载县": {114.43, 28.12}, + "上高县": {114.92, 28.23}, + "宜丰县": {114.78, 28.38}, + "靖安县": {115.35, 28.87}, + "铜鼓县": {114.37, 28.53}, + "丰城市": {115.78, 28.2}, + "樟树市": {115.53, 28.07}, + "高安市": {115.37, 28.42}, + "抚州市": {116.35, 28}, + "临川区": {116.35, 27.98}, + "南城县": {116.63, 27.55}, + "黎川县": {116.92, 27.3}, + "南丰县": {116.53, 27.22}, + "崇仁县": {116.05, 27.77}, + "乐安县": {115.83, 27.43}, + "宜黄县": {116.22, 27.55}, + "金溪县": {116.77, 27.92}, + "资溪县": {117.07, 27.7}, + "东乡县": {116.62, 28.23}, + "广昌县": {116.32, 26.83}, + "上饶市": {117.97, 28.45}, + "信州区": {117.95, 28.43}, + "上饶县": {117.92, 28.43}, + "广丰县": {118.18, 28.43}, + "玉山县": {118.25, 28.68}, + "铅山县": {117.7, 28.32}, + "横峰县": {117.6, 28.42}, + "弋阳县": {117.43, 28.4}, + "余干县": {116.68, 28.7}, + "鄱阳县": {116.67, 29}, + "万年县": {117.07, 28.7}, + "婺源县": {117.85, 29.25}, + "德兴市": {117.57, 28.95}, + "济南市": {116.98, 36.67}, + "历下区": {117.08, 36.67}, + "市中区": {117.57, 34.87}, + "槐荫区": {116.93, 36.65}, + "天桥区": {116.98, 36.68}, + "历城区": {117.07, 36.68}, + "长清区": {116.73, 36.55}, + "平阴县": {116.45, 36.28}, + "济阳县": {117.22, 36.98}, + "商河县": {117.15, 37.32}, + "章丘市": {117.53, 36.72}, + "青岛市": {120.38, 36.07}, + "市南区": {120.38, 36.07}, + "市北区": {120.38, 36.08}, + "四方区": {120.35, 36.1}, + "黄岛区": {120.18, 35.97}, + "崂山区": {120.47, 36.1}, + "李沧区": {120.43, 36.15}, + "城阳区": {120.37, 36.3}, + "胶州市": {120.03, 36.27}, + "即墨市": {120.45, 36.38}, + "平度市": {119.95, 36.78}, + "胶南市": {120.03, 35.87}, + "莱西市": {120.5, 36.87}, + "淄博市": {118.05, 36.82}, + "张店区": {118.03, 36.82}, + "博山区": {117.85, 36.5}, + "临淄区": {118.3, 36.82}, + "周村区": {117.87, 36.8}, + "桓台县": {118.08, 36.97}, + "高青县": {117.82, 37.17}, + "沂源县": {118.17, 36.18}, + "枣庄市": {117.32, 34.82}, + "薛城区": {117.25, 34.8}, + "峄城区": {117.58, 34.77}, + "台儿庄区": {117.73, 34.57}, + "山亭区": {117.45, 35.08}, + "滕州市": {117.15, 35.08}, + "东营市": {118.67, 37.43}, + "东营区": {118.5, 37.47}, + "河口区": {118.53, 37.88}, + "垦利县": {118.55, 37.58}, + "利津县": {118.25, 37.48}, + "广饶县": {118.4, 37.07}, + "烟台市": {121.43, 37.45}, + "芝罘区": {121.38, 37.53}, + "福山区": {121.25, 37.5}, + "牟平区": {121.6, 37.38}, + "莱山区": {121.43, 37.5}, + "长岛县": {120.73, 37.92}, + "龙口市": {120.52, 37.65}, + "莱阳市": {120.7, 36.98}, + "莱州市": {119.93, 37.18}, + "蓬莱市": {120.75, 37.82}, + "招远市": {120.4, 37.37}, + "栖霞市": {120.83, 37.3}, + "海阳市": {121.15, 36.78}, + "潍坊市": {119.15, 36.7}, + "潍城区": {119.1, 36.72}, + "寒亭区": {119.22, 36.77}, + "坊子区": {119.17, 36.67}, + "奎文区": {119.12, 36.72}, + "临朐县": {118.53, 36.52}, + "昌乐县": {118.82, 36.7}, + "青州市": {118.47, 36.68}, + "诸城市": {119.4, 36}, + "寿光市": {118.73, 36.88}, + "安丘市": {119.2, 36.43}, + "高密市": {119.75, 36.38}, + "昌邑市": {119.4, 36.87}, + "济宁市": {116.58, 35.42}, + "任城区": {116.58, 35.42}, + "微山县": {117.13, 34.82}, + "鱼台县": {116.65, 35}, + "金乡县": {116.3, 35.07}, + "嘉祥县": {116.33, 35.42}, + "汶上县": {116.48, 35.73}, + "泗水县": {117.27, 35.67}, + "梁山县": {116.08, 35.8}, + "曲阜市": {116.98, 35.58}, + "兖州市": {116.83, 35.55}, + "邹城市": {116.97, 35.4}, + "泰安市": {117.08, 36.2}, + "泰山区": {117.13, 36.18}, + "岱岳区": {117, 36.18}, + "宁阳县": {116.8, 35.77}, + "东平县": {116.47, 35.93}, + "新泰市": {117.77, 35.92}, + "肥城市": {116.77, 36.18}, + "威海市": {122.12, 37.52}, + "环翠区": {122.12, 37.5}, + "文登市": {122.05, 37.2}, + "荣成市": {122.42, 37.17}, + "乳山市": {121.53, 36.92}, + "日照市": {119.52, 35.42}, + "东港区": {119.45, 35.42}, + "岚山区": {119.33, 35.1}, + "五莲县": {119.2, 35.75}, + "莒县": {118.83, 35.58}, + "莱芜市": {117.67, 36.22}, + "莱城区": {117.65, 36.2}, + "钢城区": {117.8, 36.07}, + "临沂市": {118.35, 35.05}, + "兰山区": {118.33, 35.07}, + "罗庄区": {118.28, 34.98}, + "河东区": {118.4, 35.08}, + "沂南县": {118.47, 35.55}, + "郯城县": {118.35, 34.62}, + "沂水县": {118.62, 35.78}, + "苍山县": {118.05, 34.85}, + "费县": {117.97, 35.27}, + "平邑县": {117.63, 35.5}, + "莒南县": {118.83, 35.18}, + "蒙阴县": {117.93, 35.72}, + "临沭县": {118.65, 34.92}, + "德州市": {116.3, 37.45}, + "德城区": {116.3, 37.45}, + "陵县": {116.57, 37.33}, + "宁津县": {116.78, 37.65}, + "庆云县": {117.38, 37.78}, + "临邑县": {116.87, 37.18}, + "齐河县": {116.75, 36.8}, + "平原县": {116.43, 37.17}, + "夏津县": {116, 36.95}, + "武城县": {116.07, 37.22}, + "乐陵市": {117.23, 37.73}, + "禹城市": {116.63, 36.93}, + "聊城市": {115.98, 36.45}, + "东昌府区": {115.98, 36.45}, + "阳谷县": {115.78, 36.12}, + "莘县": {115.67, 36.23}, + "茌平县": {116.25, 36.58}, + "东阿县": {116.25, 36.33}, + "冠县": {115.43, 36.48}, + "高唐县": {116.23, 36.87}, + "临清市": {115.7, 36.85}, + "滨州市": {117.97, 37.38}, + "滨城区": {118, 37.38}, + "惠民县": {117.5, 37.48}, + "阳信县": {117.58, 37.63}, + "无棣县": {117.6, 37.73}, + "沾化县": {118.13, 37.7}, + "博兴县": {118.13, 37.15}, + "邹平县": {117.73, 36.88}, + "牡丹区": {115.43, 35.25}, + "曹县": {115.53, 34.83}, + "单县": {116.08, 34.8}, + "成武县": {115.88, 34.95}, + "巨野县": {116.08, 35.4}, + "郓城县": {115.93, 35.6}, + "鄄城县": {115.5, 35.57}, + "定陶县": {115.57, 35.07}, + "东明县": {115.08, 35.28}, + "郑州市": {113.62, 34.75}, + "中原区": {113.6, 34.75}, + "二七区": {113.65, 34.73}, + "管城回族区": {113.67, 34.75}, + "金水区": {113.65, 34.78}, + "上街区": {113.28, 34.82}, + "惠济区": {113.6, 34.87}, + "中牟县": {113.97, 34.72}, + "巩义市": {112.98, 34.77}, + "荥阳市": {113.4, 34.78}, + "新密市": {113.38, 34.53}, + "新郑市": {113.73, 34.4}, + "登封市": {113.03, 34.47}, + "开封市": {114.3, 34.8}, + "龙亭区": {114.35, 34.8}, + "顺河回族区": {114.35, 34.8}, + "杞县": {114.78, 34.55}, + "通许县": {114.47, 34.48}, + "尉氏县": {114.18, 34.42}, + "开封县": {114.43, 34.77}, + "兰考县": {114.82, 34.82}, + "洛阳市": {112.45, 34.62}, + "老城区": {112.47, 34.68}, + "西工区": {112.43, 34.67}, + "涧西区": {112.4, 34.67}, + "吉利区": {112.58, 34.9}, + "洛龙区": {112.45, 34.62}, + "孟津县": {112.43, 34.83}, + "新安县": {112.15, 34.72}, + "栾川县": {111.62, 33.78}, + "嵩县": {112.1, 34.15}, + "汝阳县": {112.47, 34.15}, + "宜阳县": {112.17, 34.52}, + "洛宁县": {111.65, 34.38}, + "伊川县": {112.42, 34.42}, + "偃师市": {112.78, 34.73}, + "平顶山市": {113.18, 33.77}, + "新华区": {113.3, 33.73}, + "卫东区": {113.33, 33.73}, + "石龙区": {112.88, 33.9}, + "湛河区": {113.28, 33.73}, + "宝丰县": {113.07, 33.88}, + "叶县": {113.35, 33.62}, + "鲁山县": {112.9, 33.73}, + "郏县": {113.22, 33.97}, + "舞钢市": {113.52, 33.3}, + "汝州市": {112.83, 34.17}, + "安阳市": {114.38, 36.1}, + "文峰区": {114.35, 36.08}, + "北关区": {114.35, 36.12}, + "殷都区": {114.3, 36.12}, + "龙安区": {114.32, 36.1}, + "安阳县": {114.35, 36.1}, + "汤阴县": {114.35, 35.92}, + "滑县": {114.52, 35.58}, + "内黄县": {114.9, 35.95}, + "林州市": {113.82, 36.07}, + "鹤壁市": {114.28, 35.75}, + "鹤山区": {114.15, 35.95}, + "山城区": {114.18, 35.9}, + "淇滨区": {114.3, 35.73}, + "浚县": {114.55, 35.67}, + "淇县": {114.2, 35.6}, + "新乡市": {113.9, 35.3}, + "红旗区": {113.87, 35.3}, + "卫滨区": {113.85, 35.3}, + "凤泉区": {113.92, 35.38}, + "牧野区": {113.9, 35.32}, + "新乡县": {113.8, 35.2}, + "获嘉县": {113.65, 35.27}, + "原阳县": {113.97, 35.05}, + "延津县": {114.2, 35.15}, + "封丘县": {114.42, 35.05}, + "长垣县": {114.68, 35.2}, + "卫辉市": {114.07, 35.4}, + "辉县市": {113.8, 35.47}, + "焦作市": {113.25, 35.22}, + "解放区": {113.22, 35.25}, + "中站区": {113.17, 35.23}, + "马村区": {113.32, 35.27}, + "山阳区": {113.25, 35.22}, + "修武县": {113.43, 35.23}, + "博爱县": {113.07, 35.17}, + "武陟县": {113.38, 35.1}, + "温县": {113.08, 34.93}, + "济源市": {112.58, 35.07}, + "沁阳市": {112.93, 35.08}, + "孟州市": {112.78, 34.9}, + "濮阳市": {115.03, 35.77}, + "华龙区": {115.07, 35.78}, + "清丰县": {115.12, 35.9}, + "南乐县": {115.2, 36.08}, + "范县": {115.5, 35.87}, + "台前县": {115.85, 36}, + "濮阳县": {115.02, 35.7}, + "许昌市": {113.85, 34.03}, + "魏都区": {113.82, 34.03}, + "许昌县": {113.83, 34}, + "鄢陵县": {114.2, 34.1}, + "襄城县": {113.48, 33.85}, + "禹州市": {113.47, 34.17}, + "长葛市": {113.77, 34.22}, + "漯河市": {114.02, 33.58}, + "郾城区": {114, 33.58}, + "召陵区": {114.07, 33.57}, + "舞阳县": {113.6, 33.43}, + "临颍县": {113.93, 33.82}, + "三门峡市": {111.2, 34.78}, + "湖滨区": {111.2, 34.78}, + "渑池县": {111.75, 34.77}, + "陕县": {111.08, 34.7}, + "卢氏县": {111.05, 34.05}, + "义马市": {111.87, 34.75}, + "灵宝市": {110.87, 34.52}, + "南阳市": {112.52, 33}, + "宛城区": {112.55, 33.02}, + "卧龙区": {112.53, 32.98}, + "南召县": {112.43, 33.5}, + "方城县": {113, 33.27}, + "西峡县": {111.48, 33.28}, + "镇平县": {112.23, 33.03}, + "内乡县": {111.85, 33.05}, + "淅川县": {111.48, 33.13}, + "社旗县": {112.93, 33.05}, + "唐河县": {112.83, 32.7}, + "新野县": {112.35, 32.52}, + "桐柏县": {113.4, 32.37}, + "邓州市": {112.08, 32.68}, + "商丘市": {115.65, 34.45}, + "梁园区": {115.63, 34.45}, + "睢阳区": {115.63, 34.38}, + "民权县": {115.13, 34.65}, + "睢县": {115.07, 34.45}, + "宁陵县": {115.32, 34.45}, + "柘城县": {115.3, 34.07}, + "虞城县": {115.85, 34.4}, + "夏邑县": {116.13, 34.23}, + "永城市": {116.43, 33.92}, + "信阳市": {114.07, 32.13}, + "浉河区": {114.05, 32.12}, + "平桥区": {114.12, 32.1}, + "罗山县": {114.53, 32.2}, + "光山县": {114.9, 32.02}, + "新县": {114.87, 31.63}, + "商城县": {115.4, 31.8}, + "固始县": {115.68, 32.18}, + "潢川县": {115.03, 32.13}, + "淮滨县": {115.4, 32.43}, + "息县": {114.73, 32.35}, + "周口市": {114.65, 33.62}, + "扶沟县": {114.38, 34.07}, + "西华县": {114.53, 33.8}, + "商水县": {114.6, 33.53}, + "沈丘县": {115.07, 33.4}, + "郸城县": {115.2, 33.65}, + "淮阳县": {114.88, 33.73}, + "太康县": {114.85, 34.07}, + "鹿邑县": {115.48, 33.87}, + "项城市": {114.9, 33.45}, + "驻马店市": {114.02, 32.98}, + "驿城区": {114.05, 32.97}, + "西平县": {114.02, 33.38}, + "上蔡县": {114.27, 33.27}, + "平舆县": {114.63, 32.97}, + "正阳县": {114.38, 32.6}, + "确山县": {114.02, 32.8}, + "泌阳县": {113.32, 32.72}, + "汝南县": {114.35, 33}, + "遂平县": {114, 33.15}, + "新蔡县": {114.98, 32.75}, + "武汉市": {114.3, 30.6}, + "江岸区": {114.3, 30.6}, + "江汉区": {114.27, 30.6}, + "硚口区": {114.27, 30.57}, + "汉阳区": {114.27, 30.55}, + "武昌区": {114.3, 30.57}, + "青山区": {114.38, 30.63}, + "洪山区": {114.33, 30.5}, + "东西湖区": {114.13, 30.62}, + "汉南区": {114.08, 30.32}, + "蔡甸区": {114.03, 30.58}, + "江夏区": {114.32, 30.35}, + "黄陂区": {114.37, 30.87}, + "新洲区": {114.8, 30.85}, + "黄石市": {115.03, 30.2}, + "黄石港区": {115.07, 30.23}, + "西塞山区": {115.12, 30.2}, + "下陆区": {114.97, 30.18}, + "铁山区": {114.9, 30.2}, + "阳新县": {115.2, 29.85}, + "大冶市": {114.97, 30.1}, + "十堰市": {110.78, 32.65}, + "茅箭区": {110.82, 32.6}, + "张湾区": {110.78, 32.65}, + "郧县": {110.82, 32.83}, + "郧西县": {110.42, 33}, + "竹山县": {110.23, 32.23}, + "竹溪县": {109.72, 32.32}, + "房县": {110.73, 32.07}, + "丹江口市": {111.52, 32.55}, + "宜昌市": {111.28, 30.7}, + "西陵区": {111.27, 30.7}, + "伍家岗区": {111.35, 30.65}, + "点军区": {111.27, 30.7}, + "猇亭区": {111.42, 30.53}, + "夷陵区": {111.32, 30.77}, + "远安县": {111.63, 31.07}, + "兴山县": {110.75, 31.35}, + "秭归县": {110.98, 30.83}, + "长阳土家族自治县": {111.18, 30.47}, + "五峰土家族自治县": {110.67, 30.2}, + "宜都市": {111.45, 30.4}, + "当阳市": {111.78, 30.82}, + "枝江市": {111.77, 30.43}, + "襄樊市": {112.15, 32.02}, + "襄城区": {112.15, 32.02}, + "樊城区": {112.13, 32.03}, + "襄阳区": {112.2, 32.08}, + "南漳县": {111.83, 31.78}, + "谷城县": {111.65, 32.27}, + "保康县": {111.25, 31.88}, + "老河口市": {111.67, 32.38}, + "枣阳市": {112.75, 32.13}, + "宜城市": {112.25, 31.72}, + "鄂州市": {114.88, 30.4}, + "梁子湖区": {114.67, 30.08}, + "华容区": {114.73, 30.53}, + "鄂城区": {114.88, 30.4}, + "荆门市": {112.2, 31.03}, + "东宝区": {112.2, 31.05}, + "掇刀区": {112.2, 30.98}, + "京山县": {113.1, 31.02}, + "沙洋县": {112.58, 30.7}, + "钟祥市": {112.58, 31.17}, + "孝感市": {113.92, 30.93}, + "孝南区": {113.92, 30.92}, + "孝昌县": {113.97, 31.25}, + "大悟县": {114.12, 31.57}, + "云梦县": {113.75, 31.02}, + "应城市": {113.57, 30.95}, + "安陆市": {113.68, 31.27}, + "汉川市": {113.83, 30.65}, + "荆州市": {112.23, 30.33}, + "沙市区": {112.25, 30.32}, + "荆州区": {112.18, 30.35}, + "公安县": {112.23, 30.07}, + "监利县": {112.88, 29.82}, + "江陵县": {112.42, 30.03}, + "石首市": {112.4, 29.73}, + "洪湖市": {113.45, 29.8}, + "松滋市": {111.77, 30.18}, + "黄冈市": {114.87, 30.45}, + "黄州区": {114.88, 30.43}, + "团风县": {114.87, 30.63}, + "红安县": {114.62, 31.28}, + "罗田县": {115.4, 30.78}, + "英山县": {115.67, 30.75}, + "浠水县": {115.27, 30.45}, + "蕲春县": {115.43, 30.23}, + "黄梅县": {115.93, 30.08}, + "麻城市": {115.03, 31.18}, + "武穴市": {115.55, 29.85}, + "咸宁市": {114.32, 29.85}, + "咸安区": {114.3, 29.87}, + "嘉鱼县": {113.9, 29.98}, + "通城县": {113.82, 29.25}, + "崇阳县": {114.03, 29.55}, + "通山县": {114.52, 29.6}, + "赤壁市": {113.88, 29.72}, + "随州市": {113.37, 31.72}, + "曾都区": {113.37, 31.72}, + "广水市": {113.82, 31.62}, + "恩施土家族苗族自治州": {109.47, 30.3}, + "恩施市": {109.47, 30.3}, + "利川市": {108.93, 30.3}, + "建始县": {109.73, 30.6}, + "巴东县": {110.33, 31.05}, + "宣恩县": {109.48, 29.98}, + "咸丰县": {109.15, 29.68}, + "来凤县": {109.4, 29.52}, + "鹤峰县": {110.03, 29.9}, + "仙桃市": {113.45, 30.37}, + "潜江市": {112.88, 30.42}, + "天门市": {113.17, 30.67}, + "神农架林区": {110.67, 31.75}, + "长沙市": {112.93, 28.23}, + "芙蓉区": {113.03, 28.18}, + "天心区": {112.98, 28.12}, + "岳麓区": {112.93, 28.23}, + "开福区": {112.98, 28.25}, + "雨花区": {113.03, 28.13}, + "长沙县": {113.07, 28.25}, + "望城县": {112.82, 28.37}, + "宁乡县": {112.55, 28.25}, + "浏阳市": {113.63, 28.15}, + "株洲市": {113.13, 27.83}, + "荷塘区": {113.17, 27.87}, + "芦淞区": {113.15, 27.83}, + "石峰区": {113.1, 27.87}, + "天元区": {113.12, 27.83}, + "株洲县": {113.13, 27.72}, + "攸县": {113.33, 27}, + "茶陵县": {113.53, 26.8}, + "炎陵县": {113.77, 26.48}, + "醴陵市": {113.48, 27.67}, + "湘潭市": {112.93, 27.83}, + "雨湖区": {112.9, 27.87}, + "岳塘区": {112.95, 27.87}, + "湘潭县": {112.95, 27.78}, + "湘乡市": {112.53, 27.73}, + "韶山市": {112.52, 27.93}, + "衡阳市": {112.57, 26.9}, + "珠晖区": {112.62, 26.9}, + "雁峰区": {112.6, 26.88}, + "石鼓区": {112.6, 26.9}, + "蒸湘区": {112.6, 26.9}, + "南岳区": {112.73, 27.25}, + "衡阳县": {112.37, 26.97}, + "衡南县": {112.67, 26.73}, + "衡山县": {112.87, 27.23}, + "衡东县": {112.95, 27.08}, + "祁东县": {112.12, 26.78}, + "耒阳市": {112.85, 26.42}, + "常宁市": {112.38, 26.42}, + "邵阳市": {111.47, 27.25}, + "双清区": {111.47, 27.23}, + "大祥区": {111.45, 27.23}, + "北塔区": {111.45, 27.25}, + "邵东县": {111.75, 27.25}, + "新邵县": {111.45, 27.32}, + "邵阳县": {111.27, 27}, + "隆回县": {111.03, 27.12}, + "洞口县": {110.57, 27.05}, + "绥宁县": {110.15, 26.58}, + "新宁县": {110.85, 26.43}, + "城步苗族自治县": {110.32, 26.37}, + "武冈市": {110.63, 26.73}, + "岳阳市": {113.12, 29.37}, + "岳阳楼区": {113.1, 29.37}, + "云溪区": {113.3, 29.47}, + "君山区": {113, 29.43}, + "岳阳县": {113.12, 29.15}, + "华容县": {112.57, 29.52}, + "湘阴县": {112.88, 28.68}, + "平江县": {113.58, 28.72}, + "汨罗市": {113.08, 28.8}, + "临湘市": {113.47, 29.48}, + "常德市": {111.68, 29.05}, + "武陵区": {111.68, 29.03}, + "鼎城区": {111.68, 29.02}, + "安乡县": {112.17, 29.42}, + "汉寿县": {111.97, 28.9}, + "澧县": {111.75, 29.63}, + "临澧县": {111.65, 29.45}, + "桃源县": {111.48, 28.9}, + "石门县": {111.38, 29.58}, + "津市市": {111.88, 29.62}, + "张家界市": {110.47, 29.13}, + "永定区": {110.48, 29.13}, + "武陵源区": {110.53, 29.35}, + "慈利县": {111.12, 29.42}, + "桑植县": {110.15, 29.4}, + "益阳市": {112.32, 28.6}, + "资阳区": {112.32, 28.6}, + "赫山区": {112.37, 28.6}, + "南县": {112.4, 29.38}, + "桃江县": {112.12, 28.53}, + "安化县": {111.22, 28.38}, + "沅江市": {112.38, 28.85}, + "郴州市": {113.02, 25.78}, + "北湖区": {113.02, 25.8}, + "苏仙区": {113.03, 25.8}, + "桂阳县": {112.73, 25.73}, + "宜章县": {112.95, 25.4}, + "永兴县": {113.1, 26.13}, + "嘉禾县": {112.37, 25.58}, + "临武县": {112.55, 25.28}, + "汝城县": {113.68, 25.55}, + "桂东县": {113.93, 26.08}, + "安仁县": {113.27, 26.7}, + "资兴市": {113.23, 25.98}, + "永州市": {111.62, 26.43}, + "冷水滩区": {111.6, 26.43}, + "祁阳县": {111.85, 26.58}, + "东安县": {111.28, 26.4}, + "双牌县": {111.65, 25.97}, + "道县": {111.58, 25.53}, + "江永县": {111.33, 25.28}, + "宁远县": {111.93, 25.6}, + "蓝山县": {112.18, 25.37}, + "新田县": {112.22, 25.92}, + "江华瑶族自治县": {111.58, 25.18}, + "怀化市": {110, 27.57}, + "鹤城区": {109.95, 27.55}, + "中方县": {109.93, 27.4}, + "沅陵县": {110.38, 28.47}, + "辰溪县": {110.18, 28}, + "溆浦县": {110.58, 27.92}, + "会同县": {109.72, 26.87}, + "麻阳苗族自治县": {109.8, 27.87}, + "新晃侗族自治县": {109.17, 27.37}, + "芷江侗族自治县": {109.68, 27.45}, + "靖州苗族侗族自治县": {109.68, 26.58}, + "通道侗族自治县": {109.78, 26.17}, + "洪江市": {109.82, 27.2}, + "娄底市": {112, 27.73}, + "娄星区": {112, 27.73}, + "双峰县": {112.2, 27.45}, + "新化县": {111.3, 27.75}, + "冷水江市": {111.43, 27.68}, + "涟源市": {111.67, 27.7}, + "湘西土家族苗族自治州": {109.73, 28.32}, + "吉首市": {109.73, 28.32}, + "泸溪县": {110.22, 28.22}, + "凤凰县": {109.6, 27.95}, + "花垣县": {109.48, 28.58}, + "保靖县": {109.65, 28.72}, + "古丈县": {109.95, 28.62}, + "永顺县": {109.85, 29}, + "龙山县": {109.43, 29.47}, + "广州市": {113.27, 23.13}, + "荔湾区": {113.23, 23.13}, + "越秀区": {113.27, 23.13}, + "海珠区": {113.25, 23.1}, + "天河区": {113.35, 23.12}, + "黄埔区": {113.45, 23.1}, + "番禺区": {113.35, 22.95}, + "花都区": {113.22, 23.4}, + "增城市": {113.83, 23.3}, + "从化市": {113.58, 23.55}, + "韶关市": {113.6, 24.82}, + "武江区": {113.57, 24.8}, + "浈江区": {113.6, 24.8}, + "曲江区": {113.6, 24.68}, + "始兴县": {114.07, 24.95}, + "仁化县": {113.75, 25.08}, + "翁源县": {114.13, 24.35}, + "乳源瑶族自治县": {113.27, 24.78}, + "新丰县": {114.2, 24.07}, + "乐昌市": {113.35, 25.13}, + "南雄市": {114.3, 25.12}, + "深圳市": {114.05, 22.55}, + "罗湖区": {114.12, 22.55}, + "福田区": {114.05, 22.53}, + "南山区": {113.92, 22.52}, + "宝安区": {113.9, 22.57}, + "龙岗区": {114.27, 22.73}, + "盐田区": {114.22, 22.55}, + "珠海市": {113.57, 22.27}, + "香洲区": {113.55, 22.27}, + "斗门区": {113.28, 22.22}, + "金湾区": {113.4, 22.07}, + "汕头市": {116.68, 23.35}, + "龙湖区": {116.72, 23.37}, + "金平区": {116.7, 23.37}, + "潮阳区": {116.6, 23.27}, + "潮南区": {116.43, 23.25}, + "澄海区": {116.77, 23.48}, + "南澳县": {117.02, 23.42}, + "佛山市": {113.12, 23.02}, + "南海区": {113.15, 23.03}, + "顺德区": {113.3, 22.8}, + "三水区": {112.87, 23.17}, + "高明区": {112.88, 22.9}, + "江门市": {113.08, 22.58}, + "新会区": {113.03, 22.47}, + "台山市": {112.78, 22.25}, + "开平市": {112.67, 22.38}, + "鹤山市": {112.97, 22.77}, + "恩平市": {112.3, 22.18}, + "湛江市": {110.35, 21.27}, + "赤坎区": {110.37, 21.27}, + "霞山区": {110.4, 21.2}, + "坡头区": {110.47, 21.23}, + "麻章区": {110.32, 21.27}, + "遂溪县": {110.25, 21.38}, + "徐闻县": {110.17, 20.33}, + "廉江市": {110.27, 21.62}, + "雷州市": {110.08, 20.92}, + "吴川市": {110.77, 21.43}, + "茂名市": {110.92, 21.67}, + "茂南区": {110.92, 21.63}, + "茂港区": {111.02, 21.47}, + "电白县": {111, 21.5}, + "高州市": {110.85, 21.92}, + "化州市": {110.63, 21.67}, + "信宜市": {110.95, 22.35}, + "肇庆市": {112.47, 23.05}, + "端州区": {112.48, 23.05}, + "鼎湖区": {112.57, 23.17}, + "广宁县": {112.43, 23.63}, + "怀集县": {112.18, 23.92}, + "封开县": {111.5, 23.43}, + "德庆县": {111.77, 23.15}, + "高要市": {112.45, 23.03}, + "四会市": {112.68, 23.33}, + "惠州市": {114.42, 23.12}, + "惠城区": {114.4, 23.08}, + "惠阳区": {114.47, 22.8}, + "博罗县": {114.28, 23.18}, + "惠东县": {114.72, 22.98}, + "龙门县": {114.25, 23.73}, + "梅州市": {116.12, 24.28}, + "梅江区": {116.12, 24.32}, + "梅县": {116.05, 24.28}, + "大埔县": {116.7, 24.35}, + "丰顺县": {116.18, 23.77}, + "五华县": {115.77, 23.93}, + "平远县": {115.88, 24.57}, + "蕉岭县": {116.17, 24.67}, + "兴宁市": {115.73, 24.15}, + "汕尾市": {115.37, 22.78}, + "海丰县": {115.33, 22.97}, + "陆河县": {115.65, 23.3}, + "陆丰市": {115.65, 22.95}, + "河源市": {114.7, 23.73}, + "源城区": {114.7, 23.73}, + "紫金县": {115.18, 23.63}, + "龙川县": {115.25, 24.1}, + "连平县": {114.48, 24.37}, + "和平县": {114.93, 24.45}, + "东源县": {114.77, 23.82}, + "阳江市": {111.98, 21.87}, + "江城区": {111.95, 21.87}, + "阳西县": {111.62, 21.75}, + "阳东县": {112.02, 21.88}, + "阳春市": {111.78, 22.18}, + "清远市": {113.03, 23.7}, + "清城区": {113.02, 23.7}, + "佛冈县": {113.53, 23.88}, + "阳山县": {112.63, 24.48}, + "连山壮族瑶族自治县": {112.08, 24.57}, + "连南瑶族自治县": {112.28, 24.72}, + "清新县": {112.98, 23.73}, + "英德市": {113.4, 24.18}, + "连州市": {112.38, 24.78}, + "东莞市": {113.75, 23.05}, + "中山市": {113.38, 22.52}, + "潮州市": {116.62, 23.67}, + "湘桥区": {116.63, 23.68}, + "潮安县": {116.68, 23.45}, + "饶平县": {117, 23.67}, + "揭阳市": {116.37, 23.55}, + "揭东县": {116.42, 23.57}, + "揭西县": {115.83, 23.43}, + "惠来县": {116.28, 23.03}, + "普宁市": {116.18, 23.3}, + "云浮市": {112.03, 22.92}, + "云城区": {112.03, 22.93}, + "新兴县": {112.23, 22.7}, + "郁南县": {111.53, 23.23}, + "云安县": {112, 23.08}, + "罗定市": {111.57, 22.77}, + "南宁市": {108.37, 22.82}, + "兴宁区": {108.38, 22.87}, + "江南区": {108.28, 22.78}, + "西乡塘区": {108.3, 22.83}, + "良庆区": {108.32, 22.77}, + "邕宁区": {108.48, 22.75}, + "武鸣县": {108.27, 23.17}, + "隆安县": {107.68, 23.18}, + "马山县": {108.17, 23.72}, + "上林县": {108.6, 23.43}, + "宾阳县": {108.8, 23.22}, + "横县": {109.27, 22.68}, + "柳州市": {109.42, 24.33}, + "柳南区": {109.38, 24.35}, + "柳江县": {109.33, 24.27}, + "柳城县": {109.23, 24.65}, + "鹿寨县": {109.73, 24.48}, + "融安县": {109.4, 25.23}, + "融水苗族自治县": {109.25, 25.07}, + "三江侗族自治县": {109.6, 25.78}, + "桂林市": {110.28, 25.28}, + "阳朔县": {110.48, 24.78}, + "临桂县": {110.2, 25.23}, + "灵川县": {110.32, 25.42}, + "全州县": {111.07, 25.93}, + "兴安县": {110.67, 25.62}, + "永福县": {109.98, 24.98}, + "灌阳县": {111.15, 25.48}, + "龙胜各族自治县": {110, 25.8}, + "资源县": {110.63, 26.03}, + "平乐县": {110.63, 24.63}, + "恭城瑶族自治县": {110.83, 24.83}, + "梧州市": {111.27, 23.48}, + "苍梧县": {111.23, 23.42}, + "藤县": {110.92, 23.38}, + "蒙山县": {110.52, 24.2}, + "岑溪市": {110.98, 22.92}, + "北海市": {109.12, 21.48}, + "铁山港区": {109.43, 21.53}, + "合浦县": {109.2, 21.67}, + "防城港市": {108.35, 21.7}, + "港口区": {108.37, 21.65}, + "防城区": {108.35, 21.77}, + "上思县": {107.98, 22.15}, + "东兴市": {107.97, 21.53}, + "钦州市": {108.62, 21.95}, + "钦北区": {108.63, 21.98}, + "灵山县": {109.3, 22.43}, + "浦北县": {109.55, 22.27}, + "贵港市": {109.6, 23.1}, + "覃塘区": {109.42, 23.13}, + "平南县": {110.38, 23.55}, + "桂平市": {110.08, 23.4}, + "玉林市": {110.17, 22.63}, + "容县": {110.55, 22.87}, + "陆川县": {110.27, 22.33}, + "博白县": {109.97, 22.28}, + "兴业县": {109.87, 22.75}, + "北流市": {110.35, 22.72}, + "百色市": {106.62, 23.9}, + "田阳县": {106.92, 23.73}, + "田东县": {107.12, 23.6}, + "平果县": {107.58, 23.32}, + "德保县": {106.62, 23.33}, + "靖西县": {106.42, 23.13}, + "那坡县": {105.83, 23.42}, + "凌云县": {106.57, 24.35}, + "乐业县": {106.55, 24.78}, + "田林县": {106.23, 24.3}, + "西林县": {105.1, 24.5}, + "隆林各族自治县": {105.33, 24.77}, + "贺州市": {111.55, 24.42}, + "昭平县": {110.8, 24.17}, + "钟山县": {111.3, 24.53}, + "富川瑶族自治县": {111.27, 24.83}, + "河池市": {108.07, 24.7}, + "金城江区": {108.05, 24.7}, + "南丹县": {107.53, 24.98}, + "天峨县": {107.17, 25}, + "凤山县": {107.05, 24.55}, + "东兰县": {107.37, 24.52}, + "罗城仫佬族自治县": {108.9, 24.78}, + "环江毛南族自治县": {108.25, 24.83}, + "巴马瑶族自治县": {107.25, 24.15}, + "都安瑶族自治县": {108.1, 23.93}, + "大化瑶族自治县": {107.98, 23.73}, + "宜州市": {108.67, 24.5}, + "来宾市": {109.23, 23.73}, + "忻城县": {108.67, 24.07}, + "象州县": {109.68, 23.97}, + "武宣县": {109.67, 23.6}, + "金秀瑶族自治县": {110.18, 24.13}, + "合山市": {108.87, 23.82}, + "崇左市": {107.37, 22.4}, + "扶绥县": {107.9, 22.63}, + "宁明县": {107.07, 22.13}, + "龙州县": {106.85, 22.35}, + "大新县": {107.2, 22.83}, + "天等县": {107.13, 23.08}, + "凭祥市": {106.75, 22.12}, + "海口市": {110.32, 20.03}, + "秀英区": {110.28, 20.02}, + "龙华区": {110.3, 20.03}, + "琼山区": {110.35, 20}, + "美兰区": {110.37, 20.03}, + "三亚市": {109.5, 18.25}, + "五指山市": {109.52, 18.78}, + "琼海市": {110.47, 19.25}, + "儋州市": {109.57, 19.52}, + "文昌市": {110.8, 19.55}, + "万宁市": {110.4, 18.8}, + "东方市": {108.63, 19.1}, + "定安县": {110.32, 19.7}, + "屯昌县": {110.1, 19.37}, + "澄迈县": {110, 19.73}, + "临高县": {109.68, 19.92}, + "白沙黎族自治县": {109.45, 19.23}, + "昌江黎族自治县": {109.05, 19.25}, + "乐东黎族自治县": {109.17, 18.75}, + "陵水黎族自治县": {110.03, 18.5}, + "保亭黎族苗族自治县": {109.7, 18.63}, + "琼中黎族苗族自治县": {109.83, 19.03}, + "重庆市": {106.55, 29.57}, + "万州区": {108.4, 30.82}, + "涪陵区": {107.4, 29.72}, + "渝中区": {106.57, 29.55}, + "大渡口区": {106.48, 29.48}, + "江北区": {106.57, 29.6}, + "沙坪坝区": {106.45, 29.53}, + "九龙坡区": {106.5, 29.5}, + "南岸区": {106.57, 29.52}, + "北碚区": {106.4, 29.8}, + "万盛区": {106.92, 28.97}, + "双桥区": {105.78, 29.48}, + "渝北区": {106.63, 29.72}, + "巴南区": {106.52, 29.38}, + "黔江区": {108.77, 29.53}, + "长寿区": {107.08, 29.87}, + "綦江县": {106.65, 29.03}, + "潼南县": {105.83, 30.18}, + "铜梁县": {106.05, 29.85}, + "大足县": {105.72, 29.7}, + "荣昌县": {105.58, 29.4}, + "璧山县": {106.22, 29.6}, + "梁平县": {107.8, 30.68}, + "城口县": {108.67, 31.95}, + "丰都县": {107.73, 29.87}, + "垫江县": {107.35, 30.33}, + "武隆县": {107.75, 29.33}, + "忠县": {108.02, 30.3}, + "开县": {108.42, 31.18}, + "云阳县": {108.67, 30.95}, + "奉节县": {109.47, 31.02}, + "巫山县": {109.88, 31.08}, + "巫溪县": {109.63, 31.4}, + "石柱土家族自治县": {108.12, 30}, + "秀山土家族苗族自治县": {108.98, 28.45}, + "酉阳土家族苗族自治县": {108.77, 28.85}, + "彭水苗族土家族自治县": {108.17, 29.3}, + "成都市": {104.07, 30.67}, + "锦江区": {104.08, 30.67}, + "青羊区": {104.05, 30.68}, + "金牛区": {104.05, 30.7}, + "武侯区": {104.05, 30.65}, + "成华区": {104.1, 30.67}, + "龙泉驿区": {104.27, 30.57}, + "青白江区": {104.23, 30.88}, + "新都区": {104.15, 30.83}, + "温江区": {103.83, 30.7}, + "金堂县": {104.43, 30.85}, + "双流县": {103.92, 30.58}, + "郫县": {103.88, 30.82}, + "大邑县": {103.52, 30.58}, + "蒲江县": {103.5, 30.2}, + "新津县": {103.82, 30.42}, + "都江堰市": {103.62, 31}, + "彭州市": {103.93, 30.98}, + "邛崃市": {103.47, 30.42}, + "崇州市": {103.67, 30.63}, + "自贡市": {104.78, 29.35}, + "自流井区": {104.77, 29.35}, + "贡井区": {104.72, 29.35}, + "大安区": {104.77, 29.37}, + "沿滩区": {104.87, 29.27}, + "荣县": {104.42, 29.47}, + "富顺县": {104.98, 29.18}, + "攀枝花市": {101.72, 26.58}, + "东区": {101.7, 26.55}, + "西区": {101.6, 26.6}, + "仁和区": {101.73, 26.5}, + "米易县": {102.12, 26.88}, + "盐边县": {101.85, 26.7}, + "泸州市": {105.43, 28.87}, + "江阳区": {105.45, 28.88}, + "纳溪区": {105.37, 28.77}, + "龙马潭区": {105.43, 28.9}, + "泸县": {105.38, 29.15}, + "合江县": {105.83, 28.82}, + "叙永县": {105.43, 28.17}, + "古蔺县": {105.82, 28.05}, + "德阳市": {104.38, 31.13}, + "旌阳区": {104.38, 31.13}, + "中江县": {104.68, 31.03}, + "罗江县": {104.5, 31.32}, + "广汉市": {104.28, 30.98}, + "什邡市": {104.17, 31.13}, + "绵竹市": {104.2, 31.35}, + "绵阳市": {104.73, 31.47}, + "涪城区": {104.73, 31.47}, + "游仙区": {104.75, 31.47}, + "三台县": {105.08, 31.1}, + "盐亭县": {105.38, 31.22}, + "安县": {104.57, 31.53}, + "梓潼县": {105.17, 31.63}, + "北川羌族自治县": {104.45, 31.82}, + "平武县": {104.53, 32.42}, + "江油市": {104.75, 31.78}, + "广元市": {105.83, 32.43}, + "元坝区": {105.97, 32.32}, + "朝天区": {105.88, 32.65}, + "旺苍县": {106.28, 32.23}, + "青川县": {105.23, 32.58}, + "剑阁县": {105.52, 32.28}, + "苍溪县": {105.93, 31.73}, + "遂宁市": {105.57, 30.52}, + "船山区": {105.57, 30.52}, + "安居区": {105.45, 30.35}, + "蓬溪县": {105.72, 30.78}, + "射洪县": {105.38, 30.87}, + "大英县": {105.25, 30.58}, + "内江市": {105.05, 29.58}, + "东兴区": {105.07, 29.6}, + "威远县": {104.67, 29.53}, + "资中县": {104.85, 29.78}, + "隆??县": {105.28, 29.35}, + "乐山市": {103.77, 29.57}, + "沙湾区": {103.55, 29.42}, + "五通桥区": {103.82, 29.4}, + "金口河区": {103.08, 29.25}, + "犍为县": {103.95, 29.22}, + "井研县": {104.07, 29.65}, + "夹江县": {103.57, 29.73}, + "沐川县": {103.9, 28.97}, + "峨边彝族自治县": {103.27, 29.23}, + "马边彝族自治县": {103.55, 28.83}, + "峨眉山市": {103.48, 29.6}, + "南充市": {106.08, 30.78}, + "顺庆区": {106.08, 30.78}, + "高坪区": {106.1, 30.77}, + "嘉陵区": {106.05, 30.77}, + "南部县": {106.07, 31.35}, + "营山县": {106.57, 31.08}, + "蓬安县": {106.42, 31.03}, + "仪陇县": {106.28, 31.27}, + "西充县": {105.88, 31}, + "阆中市": {106, 31.55}, + "眉山市": {103.83, 30.05}, + "东坡区": {103.83, 30.05}, + "仁寿县": {104.15, 30}, + "彭山县": {103.87, 30.2}, + "洪雅县": {103.37, 29.92}, + "丹棱县": {103.52, 30.02}, + "青神县": {103.85, 29.83}, + "宜宾市": {104.62, 28.77}, + "翠屏区": {104.62, 28.77}, + "宜宾县": {104.55, 28.7}, + "南溪县": {104.98, 28.85}, + "江安县": {105.07, 28.73}, + "长宁县": {104.92, 28.58}, + "高县": {104.52, 28.43}, + "珙县": {104.72, 28.45}, + "筠连县": {104.52, 28.17}, + "兴文县": {105.23, 28.3}, + "屏山县": {104.33, 28.83}, + "广安市": {106.63, 30.47}, + "岳池县": {106.43, 30.55}, + "武胜县": {106.28, 30.35}, + "邻水县": {106.93, 30.33}, + "华蓥市": {106.77, 30.38}, + "达州市": {107.5, 31.22}, + "通川区": {107.48, 31.22}, + "达县": {107.5, 31.2}, + "宣汉县": {107.72, 31.35}, + "开江县": {107.87, 31.08}, + "大竹县": {107.2, 30.73}, + "渠县": {106.97, 30.83}, + "万源市": {108.03, 32.07}, + "雅安市": {103, 29.98}, + "雨城区": {103, 29.98}, + "名山县": {103.12, 30.08}, + "荥经县": {102.85, 29.8}, + "汉源县": {102.65, 29.35}, + "石棉县": {102.37, 29.23}, + "天全县": {102.75, 30.07}, + "芦山县": {102.92, 30.15}, + "宝兴县": {102.82, 30.37}, + "巴中市": {106.77, 31.85}, + "巴州区": {106.77, 31.85}, + "通江县": {107.23, 31.92}, + "南江县": {106.83, 32.35}, + "平昌县": {107.1, 31.57}, + "资阳市": {104.65, 30.12}, + "雁江区": {104.65, 30.12}, + "安岳县": {105.33, 30.1}, + "乐至县": {105.02, 30.28}, + "简阳市": {104.55, 30.4}, + "阿坝藏族羌族自治州": {102.22, 31.9}, + "汶川县": {103.58, 31.48}, + "理县": {103.17, 31.43}, + "茂县": {103.85, 31.68}, + "松潘县": {103.6, 32.63}, + "九寨沟县": {104.23, 33.27}, + "金川县": {102.07, 31.48}, + "小金县": {102.37, 31}, + "黑水县": {102.98, 32.07}, + "马尔康县": {102.22, 31.9}, + "壤塘县": {100.98, 32.27}, + "阿坝县": {101.7, 32.9}, + "若尔盖县": {102.95, 33.58}, + "红原县": {102.55, 32.8}, + "甘孜藏族自治州": {101.97, 30.05}, + "康定县": {101.97, 30.05}, + "泸定县": {102.23, 29.92}, + "丹巴县": {101.88, 30.88}, + "九龙县": {101.5, 29}, + "雅江县": {101.02, 30.03}, + "道孚县": {101.12, 30.98}, + "炉霍县": {100.68, 31.4}, + "甘孜县": {99.98, 31.62}, + "新龙县": {100.32, 30.95}, + "德格县": {98.58, 31.82}, + "白玉县": {98.83, 31.22}, + "石渠县": {98.1, 32.98}, + "色达县": {100.33, 32.27}, + "理塘县": {100.27, 30}, + "巴塘县": {99.1, 30}, + "乡城县": {99.8, 28.93}, + "稻城县": {100.3, 29.03}, + "得荣县": {99.28, 28.72}, + "凉山彝族自治州": {102.27, 27.9}, + "西昌市": {102.27, 27.9}, + "木里藏族自治县": {101.28, 27.93}, + "盐源县": {101.5, 27.43}, + "德昌县": {102.18, 27.4}, + "会理县": {102.25, 26.67}, + "会东县": {102.58, 26.63}, + "宁南县": {102.77, 27.07}, + "普格县": {102.53, 27.38}, + "布拖县": {102.82, 27.72}, + "金阳县": {103.25, 27.7}, + "昭觉县": {102.85, 28.02}, + "喜德县": {102.42, 28.32}, + "冕宁县": {102.17, 28.55}, + "越西县": {102.52, 28.65}, + "甘洛县": {102.77, 28.97}, + "美姑县": {103.13, 28.33}, + "雷波县": {103.57, 28.27}, + "贵阳市": {106.63, 26.65}, + "南明区": {106.72, 26.57}, + "云岩区": {106.72, 26.62}, + "乌当区": {106.75, 26.63}, + "白云区": {106.65, 26.68}, + "小河区": {106.7, 26.53}, + "开阳县": {106.97, 27.07}, + "息烽县": {106.73, 27.1}, + "修文县": {106.58, 26.83}, + "清镇市": {106.47, 26.55}, + "六盘水市": {104.83, 26.6}, + "钟山区": {104.83, 26.6}, + "六枝特区": {105.48, 26.22}, + "水城县": {104.95, 26.55}, + "盘县": {104.47, 25.72}, + "遵义市": {106.92, 27.73}, + "红花岗区": {106.92, 27.65}, + "汇川区": {106.92, 27.73}, + "遵义县": {106.83, 27.53}, + "桐梓县": {106.82, 28.13}, + "绥阳县": {107.18, 27.95}, + "正安县": {107.43, 28.55}, + "道真仡佬族苗族自治县": {107.6, 28.88}, + "务川仡佬族苗族自治县": {107.88, 28.53}, + "凤冈县": {107.72, 27.97}, + "湄潭县": {107.48, 27.77}, + "余庆县": {107.88, 27.22}, + "习水县": {106.22, 28.32}, + "赤水市": {105.7, 28.58}, + "仁怀市": {106.42, 27.82}, + "安顺市": {105.95, 26.25}, + "西秀区": {105.92, 26.25}, + "平坝县": {106.25, 26.42}, + "普定县": {105.75, 26.32}, + "镇宁布依族苗族自治县": {105.77, 26.07}, + "关岭布依族苗族自治县": {105.62, 25.95}, + "紫云苗族布依族自治县": {106.08, 25.75}, + "铜仁地区": {109.18, 27.72}, + "铜仁市": {109.18, 27.72}, + "江口县": {108.85, 27.7}, + "玉屏侗族自治县": {108.92, 27.23}, + "石阡县": {108.23, 27.52}, + "思南县": {108.25, 27.93}, + "印江土家族苗族自治县": {108.4, 28}, + "德江县": {108.12, 28.27}, + "沿河土家族自治县": {108.5, 28.57}, + "松桃苗族自治县": {109.2, 28.17}, + "万山特区": {109.2, 27.52}, + "兴义市": {104.9, 25.08}, + "兴仁县": {105.18, 25.43}, + "普安县": {104.95, 25.78}, + "晴隆县": {105.22, 25.83}, + "贞丰县": {105.65, 25.38}, + "望谟县": {106.1, 25.17}, + "册亨县": {105.82, 24.98}, + "安龙县": {105.47, 25.12}, + "毕节地区": {105.28, 27.3}, + "毕节市": {105.28, 27.3}, + "大方县": {105.6, 27.15}, + "黔西县": {106.03, 27.03}, + "金沙县": {106.22, 27.47}, + "织金县": {105.77, 26.67}, + "纳雍县": {105.38, 26.78}, + "赫章县": {104.72, 27.13}, + "黔东南苗族侗族自治州": {107.97, 26.58}, + "凯里市": {107.97, 26.58}, + "黄平县": {107.9, 26.9}, + "施秉县": {108.12, 27.03}, + "三穗县": {108.68, 26.97}, + "镇远县": {108.42, 27.05}, + "岑巩县": {108.82, 27.18}, + "天柱县": {109.2, 26.92}, + "锦屏县": {109.2, 26.68}, + "剑河县": {108.45, 26.73}, + "台江县": {108.32, 26.67}, + "黎平县": {109.13, 26.23}, + "榕江县": {108.52, 25.93}, + "从江县": {108.9, 25.75}, + "雷山县": {108.07, 26.38}, + "麻江县": {107.58, 26.5}, + "丹寨县": {107.8, 26.2}, + "黔南布依族苗族自治州": {107.52, 26.27}, + "都匀市": {107.52, 26.27}, + "福泉市": {107.5, 26.7}, + "荔波县": {107.88, 25.42}, + "贵定县": {107.23, 26.58}, + "瓮安县": {107.47, 27.07}, + "独山县": {107.53, 25.83}, + "平塘县": {107.32, 25.83}, + "罗甸县": {106.75, 25.43}, + "长顺县": {106.45, 26.03}, + "龙里县": {106.97, 26.45}, + "惠水县": {106.65, 26.13}, + "三都水族自治县": {107.87, 25.98}, + "昆明市": {102.72, 25.05}, + "五华区": {102.7, 25.05}, + "盘龙区": {102.72, 25.03}, + "官渡区": {102.75, 25.02}, + "西山区": {102.67, 25.03}, + "东川区": {103.18, 26.08}, + "呈贡县": {102.8, 24.88}, + "晋宁县": {102.6, 24.67}, + "富民县": {102.5, 25.22}, + "宜良县": {103.15, 24.92}, + "石林彝族自治县": {103.27, 24.77}, + "嵩明县": {103.03, 25.35}, + "禄劝彝族苗族自治县": {102.47, 25.55}, + "寻甸回族彝族自治县": {103.25, 25.57}, + "安宁市": {102.48, 24.92}, + "曲靖市": {103.8, 25.5}, + "麒麟区": {103.8, 25.5}, + "马龙县": {103.58, 25.43}, + "陆良县": {103.67, 25.03}, + "师宗县": {103.98, 24.83}, + "罗平县": {104.3, 24.88}, + "富源县": {104.25, 25.67}, + "会泽县": {103.3, 26.42}, + "沾益县": {103.82, 25.62}, + "宣威市": {104.1, 26.22}, + "玉溪市": {102.55, 24.35}, + "江川县": {102.75, 24.28}, + "澄江县": {102.92, 24.67}, + "通海县": {102.75, 24.12}, + "华宁县": {102.93, 24.2}, + "易门县": {102.17, 24.67}, + "峨山彝族自治县": {102.4, 24.18}, + "新平彝族傣族自治县": {101.98, 24.07}, + "保山市": {99.17, 25.12}, + "隆阳区": {99.17, 25.12}, + "施甸县": {99.18, 24.73}, + "腾冲县": {98.5, 25.03}, + "龙陵县": {98.68, 24.58}, + "昌宁县": {99.6, 24.83}, + "昭通市": {103.72, 27.33}, + "昭阳区": {103.72, 27.33}, + "鲁甸县": {103.55, 27.2}, + "巧家县": {102.92, 26.92}, + "盐津县": {104.23, 28.12}, + "大关县": {103.88, 27.75}, + "永善县": {103.63, 28.23}, + "绥江县": {103.95, 28.6}, + "镇雄县": {104.87, 27.45}, + "彝良县": {104.05, 27.63}, + "威信县": {105.05, 27.85}, + "水富县": {104.4, 28.63}, + "丽江市": {100.23, 26.88}, + "古城区": {100.23, 26.88}, + "玉龙纳西族自治县": {100.23, 26.82}, + "永胜县": {100.75, 26.68}, + "华坪县": {101.27, 26.63}, + "宁蒗彝族自治县": {100.85, 27.28}, + "墨江哈尼族自治县": {101.68, 23.43}, + "景东彝族自治县": {100.83, 24.45}, + "景谷傣族彝族自治县": {100.7, 23.5}, + "江城哈尼族彝族自治县": {101.85, 22.58}, + "澜沧拉祜族自治县": {99.93, 22.55}, + "西盟佤族自治县": {99.62, 22.63}, + "临沧市": {100.08, 23.88}, + "临翔区": {100.08, 23.88}, + "凤庆县": {99.92, 24.6}, + "云县": {100.13, 24.45}, + "永德县": {99.25, 24.03}, + "镇康县": {98.83, 23.78}, + "耿马傣族佤族自治县": {99.4, 23.55}, + "沧源佤族自治县": {99.25, 23.15}, + "楚雄彝族自治州": {101.55, 25.03}, + "楚雄市": {101.55, 25.03}, + "双柏县": {101.63, 24.7}, + "牟定县": {101.53, 25.32}, + "南华县": {101.27, 25.2}, + "姚安县": {101.23, 25.5}, + "大姚县": {101.32, 25.73}, + "永仁县": {101.67, 26.07}, + "元谋县": {101.88, 25.7}, + "武定县": {102.4, 25.53}, + "禄丰县": {102.08, 25.15}, + "红河哈尼族彝族自治州": {103.4, 23.37}, + "个旧市": {103.15, 23.37}, + "开远市": {103.27, 23.72}, + "蒙自县": {103.4, 23.37}, + "屏边苗族自治县": {103.68, 22.98}, + "建水县": {102.83, 23.62}, + "石屏县": {102.5, 23.72}, + "弥勒县": {103.43, 24.4}, + "泸西县": {103.77, 24.53}, + "元阳县": {102.83, 23.23}, + "红河县": {102.42, 23.37}, + "绿春县": {102.4, 23}, + "河口瑶族自治县": {103.97, 22.52}, + "文山壮族苗族自治州": {104.25, 23.37}, + "文山县": {104.25, 23.37}, + "砚山县": {104.33, 23.62}, + "西畴县": {104.67, 23.45}, + "麻栗坡县": {104.7, 23.12}, + "马关县": {104.4, 23.02}, + "丘北县": {104.18, 24.05}, + "广南县": {105.07, 24.05}, + "富宁县": {105.62, 23.63}, + "西双版纳傣族自治州": {100.8, 22.02}, + "景洪市": {100.8, 22.02}, + "勐海县": {100.45, 21.97}, + "勐腊县": {101.57, 21.48}, + "大理白族自治州": {100.23, 25.6}, + "大理市": {100.23, 25.6}, + "漾濞彝族自治县": {99.95, 25.67}, + "祥云县": {100.55, 25.48}, + "宾川县": {100.58, 25.83}, + "弥渡县": {100.48, 25.35}, + "南涧彝族自治县": {100.52, 25.05}, + "巍山彝族回族自治县": {100.3, 25.23}, + "永平县": {99.53, 25.47}, + "云龙县": {99.37, 25.88}, + "洱源县": {99.95, 26.12}, + "剑川县": {99.9, 26.53}, + "鹤庆县": {100.18, 26.57}, + "德宏傣族景颇族自治州": {98.58, 24.43}, + "瑞丽市": {97.85, 24.02}, + "潞西市": {98.58, 24.43}, + "梁河县": {98.3, 24.82}, + "盈江县": {97.93, 24.72}, + "陇川县": {97.8, 24.2}, + "怒江傈僳族自治州": {98.85, 25.85}, + "泸水县": {98.85, 25.85}, + "福贡县": {98.87, 26.9}, + "贡山独龙族怒族自治县": {98.67, 27.73}, + "兰坪白族普米族自治县": {99.42, 26.45}, + "迪庆藏族自治州": {99.7, 27.83}, + "香格里拉县": {99.7, 27.83}, + "德钦县": {98.92, 28.48}, + "维西傈僳族自治县": {99.28, 27.18}, + "拉萨市": {91.13, 29.65}, + "林周县": {91.25, 29.9}, + "当雄县": {91.1, 30.48}, + "尼木县": {90.15, 29.45}, + "曲水县": {90.73, 29.37}, + "堆龙德庆县": {91, 29.65}, + "达孜县": {91.35, 29.68}, + "墨竹工卡县": {91.73, 29.83}, + "昌都地区": {97.18, 31.13}, + "昌都县": {97.18, 31.13}, + "江达县": {98.22, 31.5}, + "贡觉县": {98.27, 30.87}, + "类乌齐县": {96.6, 31.22}, + "丁青县": {95.6, 31.42}, + "察雅县": {97.57, 30.65}, + "八宿县": {96.92, 30.05}, + "左贡县": {97.85, 29.67}, + "芒康县": {98.6, 29.68}, + "洛隆县": {95.83, 30.75}, + "边坝县": {94.7, 30.93}, + "山南地区": {91.77, 29.23}, + "乃东县": {91.77, 29.23}, + "扎囊县": {91.33, 29.25}, + "贡嘎县": {90.98, 29.3}, + "桑日县": {92.02, 29.27}, + "琼结县": {91.68, 29.03}, + "曲松县": {92.2, 29.07}, + "措美县": {91.43, 28.43}, + "洛扎县": {90.87, 28.38}, + "加查县": {92.58, 29.15}, + "隆子县": {92.47, 28.42}, + "错那县": {91.95, 28}, + "浪卡子县": {90.4, 28.97}, + "日喀则地区": {88.88, 29.27}, + "日喀则市": {88.88, 29.27}, + "南木林县": {89.1, 29.68}, + "江孜县": {89.6, 28.92}, + "定日县": {87.12, 28.67}, + "萨迦县": {88.02, 28.9}, + "拉孜县": {87.63, 29.08}, + "昂仁县": {87.23, 29.3}, + "谢通门县": {88.27, 29.43}, + "白朗县": {89.27, 29.12}, + "仁布县": {89.83, 29.23}, + "康马县": {89.68, 28.57}, + "定结县": {87.77, 28.37}, + "仲巴县": {84.03, 29.77}, + "亚东县": {88.9, 27.48}, + "吉隆县": {85.3, 28.85}, + "聂拉木县": {85.98, 28.17}, + "萨嘎县": {85.23, 29.33}, + "岗巴县": {88.52, 28.28}, + "那曲地区": {92.07, 31.48}, + "那曲县": {92.07, 31.48}, + "嘉黎县": {93.25, 30.65}, + "比如县": {93.68, 31.48}, + "聂荣县": {92.3, 32.12}, + "安多县": {91.68, 32.27}, + "申扎县": {88.7, 30.93}, + "索县": {93.78, 31.88}, + "班戈县": {90.02, 31.37}, + "巴青县": {94.03, 31.93}, + "尼玛县": {87.23, 31.78}, + "阿里地区": {80.1, 32.5}, + "普兰县": {81.17, 30.3}, + "札达县": {79.8, 31.48}, + "噶尔县": {80.1, 32.5}, + "日土县": {79.72, 33.38}, + "革吉县": {81.12, 32.4}, + "改则县": {84.07, 32.3}, + "措勤县": {85.17, 31.02}, + "林芝地区": {94.37, 29.68}, + "林芝县": {94.37, 29.68}, + "工布江达县": {93.25, 29.88}, + "米林县": {94.22, 29.22}, + "墨脱县": {95.33, 29.33}, + "波密县": {95.77, 29.87}, + "察隅县": {97.47, 28.67}, + "朗县": {93.07, 29.05}, + "西安市": {108.93, 34.27}, + "新城区": {108.95, 34.27}, + "碑林区": {108.93, 34.23}, + "莲湖区": {108.93, 34.27}, + "灞桥区": {109.07, 34.27}, + "未央区": {108.93, 34.28}, + "雁塔区": {108.95, 34.22}, + "阎良区": {109.23, 34.65}, + "临潼区": {109.22, 34.37}, + "长安区": {108.93, 34.17}, + "蓝田县": {109.32, 34.15}, + "周至县": {108.2, 34.17}, + "户县": {108.6, 34.1}, + "高陵县": {109.08, 34.53}, + "铜川市": {108.93, 34.9}, + "王益区": {109.07, 35.07}, + "印台区": {109.1, 35.1}, + "耀州区": {108.98, 34.92}, + "宜君县": {109.12, 35.4}, + "宝鸡市": {107.13, 34.37}, + "渭滨区": {107.15, 34.37}, + "金台区": {107.13, 34.38}, + "陈仓区": {107.37, 34.37}, + "凤翔县": {107.38, 34.52}, + "岐山县": {107.62, 34.45}, + "扶风县": {107.87, 34.37}, + "眉县": {107.75, 34.28}, + "陇县": {106.85, 34.9}, + "千阳县": {107.13, 34.65}, + "麟游县": {107.78, 34.68}, + "凤县": {106.52, 33.92}, + "太白县": {107.32, 34.07}, + "咸阳市": {108.7, 34.33}, + "秦都区": {108.72, 34.35}, + "杨凌区": {108.07, 34.28}, + "渭城区": {108.73, 34.33}, + "三原县": {108.93, 34.62}, + "泾阳县": {108.83, 34.53}, + "乾县": {108.23, 34.53}, + "礼泉县": {108.42, 34.48}, + "永寿县": {108.13, 34.7}, + "彬县": {108.08, 35.03}, + "长武县": {107.78, 35.2}, + "旬邑县": {108.33, 35.12}, + "淳化县": {108.58, 34.78}, + "武功县": {108.2, 34.27}, + "兴平市": {108.48, 34.3}, + "渭南市": {109.5, 34.5}, + "临渭区": {109.48, 34.5}, + "华县": {109.77, 34.52}, + "潼关县": {110.23, 34.55}, + "大荔县": {109.93, 34.8}, + "合阳县": {110.15, 35.23}, + "澄城县": {109.93, 35.18}, + "蒲城县": {109.58, 34.95}, + "白水县": {109.58, 35.18}, + "富平县": {109.18, 34.75}, + "韩城市": {110.43, 35.48}, + "华阴市": {110.08, 34.57}, + "延安市": {109.48, 36.6}, + "宝塔区": {109.48, 36.6}, + "延长县": {110, 36.58}, + "延川县": {110.18, 36.88}, + "子长县": {109.67, 37.13}, + "安塞县": {109.32, 36.87}, + "志丹县": {108.77, 36.82}, + "甘泉县": {109.35, 36.28}, + "富县": {109.37, 35.98}, + "洛川县": {109.43, 35.77}, + "宜川县": {110.17, 36.05}, + "黄龙县": {109.83, 35.58}, + "黄陵县": {109.25, 35.58}, + "汉中市": {107.02, 33.07}, + "汉台区": {107.03, 33.07}, + "南郑县": {106.93, 33}, + "城固县": {107.33, 33.15}, + "洋县": {107.55, 33.22}, + "西乡县": {107.77, 32.98}, + "勉县": {106.67, 33.15}, + "宁强县": {106.25, 32.83}, + "略阳县": {106.15, 33.33}, + "镇巴县": {107.9, 32.53}, + "留坝县": {106.92, 33.62}, + "佛坪县": {107.98, 33.53}, + "榆林市": {109.73, 38.28}, + "榆阳区": {109.75, 38.28}, + "神木县": {110.5, 38.83}, + "府谷县": {111.07, 39.03}, + "横山县": {109.28, 37.95}, + "靖边县": {108.8, 37.6}, + "定边县": {107.6, 37.58}, + "绥德县": {110.25, 37.5}, + "米脂县": {110.18, 37.75}, + "佳县": {110.48, 38.02}, + "吴堡县": {110.73, 37.45}, + "清涧县": {110.12, 37.08}, + "子洲县": {110.03, 37.62}, + "安康市": {109.02, 32.68}, + "汉滨区": {109.02, 32.68}, + "汉阴县": {108.5, 32.9}, + "石泉县": {108.25, 33.05}, + "宁陕县": {108.32, 33.32}, + "紫阳县": {108.53, 32.52}, + "岚皋县": {108.9, 32.32}, + "平利县": {109.35, 32.4}, + "镇坪县": {109.52, 31.88}, + "旬阳县": {109.38, 32.83}, + "白河县": {110.1, 32.82}, + "商洛市": {109.93, 33.87}, + "商州区": {109.93, 33.87}, + "洛南县": {110.13, 34.08}, + "丹凤县": {110.33, 33.7}, + "商南县": {110.88, 33.53}, + "山阳县": {109.88, 33.53}, + "镇安县": {109.15, 33.43}, + "柞水县": {109.1, 33.68}, + "兰州市": {103.82, 36.07}, + "城关区": {103.83, 36.05}, + "西固区": {103.62, 36.1}, + "红古区": {102.87, 36.33}, + "永登县": {103.27, 36.73}, + "皋兰县": {103.95, 36.33}, + "榆中县": {104.12, 35.85}, + "嘉峪关市": {98.27, 39.8}, + "金昌市": {102.18, 38.5}, + "金川区": {102.18, 38.5}, + "永昌县": {101.97, 38.25}, + "白银市": {104.18, 36.55}, + "白银区": {104.18, 36.55}, + "平川区": {104.83, 36.73}, + "靖远县": {104.68, 36.57}, + "会宁县": {105.05, 35.7}, + "景泰县": {104.07, 37.15}, + "天水市": {105.72, 34.58}, + "清水县": {106.13, 34.75}, + "秦安县": {105.67, 34.87}, + "甘谷县": {105.33, 34.73}, + "武山县": {104.88, 34.72}, + "张家川回族自治县": {106.22, 35}, + "武威市": {102.63, 37.93}, + "凉州区": {102.63, 37.93}, + "民勤县": {103.08, 38.62}, + "古浪县": {102.88, 37.47}, + "天祝藏族自治县": {103.13, 36.98}, + "张掖市": {100.45, 38.93}, + "甘州区": {100.45, 38.93}, + "肃南裕固族自治县": {99.62, 38.83}, + "民乐县": {100.82, 38.43}, + "临泽县": {100.17, 39.13}, + "高台县": {99.82, 39.38}, + "山丹县": {101.08, 38.78}, + "平凉市": {106.67, 35.55}, + "崆峒区": {106.67, 35.55}, + "泾川县": {107.37, 35.33}, + "灵台县": {107.62, 35.07}, + "崇信县": {107.03, 35.3}, + "华亭县": {106.65, 35.22}, + "庄浪县": {106.05, 35.2}, + "静宁县": {105.72, 35.52}, + "酒泉市": {98.52, 39.75}, + "肃州区": {98.52, 39.75}, + "金塔县": {98.9, 39.98}, + "肃北蒙古族自治县": {94.88, 39.52}, + "阿克塞哈萨克族自治县": {94.33, 39.63}, + "玉门市": {97.05, 40.28}, + "敦煌市": {94.67, 40.13}, + "庆阳市": {107.63, 35.73}, + "西峰区": {107.63, 35.73}, + "庆城县": {107.88, 36}, + "环县": {107.3, 36.58}, + "华池县": {107.98, 36.47}, + "合水县": {108.02, 35.82}, + "正宁县": {108.37, 35.5}, + "宁县": {107.92, 35.5}, + "镇原县": {107.2, 35.68}, + "定西市": {104.62, 35.58}, + "安定区": {104.62, 35.58}, + "通渭县": {105.25, 35.2}, + "陇西县": {104.63, 35}, + "渭源县": {104.22, 35.13}, + "临洮县": {103.87, 35.38}, + "漳县": {104.47, 34.85}, + "岷县": {104.03, 34.43}, + "陇南市": {104.92, 33.4}, + "武都区": {104.92, 33.4}, + "成县": {105.72, 33.73}, + "文县": {104.68, 32.95}, + "宕昌县": {104.38, 34.05}, + "康县": {105.6, 33.33}, + "西和县": {105.3, 34.02}, + "礼县": {105.17, 34.18}, + "徽县": {106.08, 33.77}, + "两当县": {106.3, 33.92}, + "临夏回族自治州": {103.22, 35.6}, + "临夏市": {103.22, 35.6}, + "临夏县": {103, 35.5}, + "康乐县": {103.72, 35.37}, + "永靖县": {103.32, 35.93}, + "广河县": {103.58, 35.48}, + "和政县": {103.35, 35.43}, + "东乡族自治县": {103.4, 35.67}, + "甘南藏族自治州": {102.92, 34.98}, + "合作市": {102.92, 34.98}, + "临潭县": {103.35, 34.7}, + "卓尼县": {103.5, 34.58}, + "舟曲县": {104.37, 33.78}, + "迭部县": {103.22, 34.05}, + "玛曲县": {102.07, 34}, + "碌曲县": {102.48, 34.58}, + "夏河县": {102.52, 35.2}, + "西宁市": {101.78, 36.62}, + "城东区": {101.8, 36.62}, + "城中区": {101.78, 36.62}, + "城西区": {101.77, 36.62}, + "城北区": {101.77, 36.67}, + "大通回族土族自治县": {101.68, 36.93}, + "湟中县": {101.57, 36.5}, + "湟源县": {101.27, 36.68}, + "海东地区": {102.12, 36.5}, + "平安县": {102.12, 36.5}, + "民和回族土族自治县": {102.8, 36.33}, + "乐都县": {102.4, 36.48}, + "互助土族自治县": {101.95, 36.83}, + "化隆回族自治县": {102.27, 36.1}, + "循化撒拉族自治县": {102.48, 35.85}, + "海北藏族自治州": {100.9, 36.97}, + "门源回族自治县": {101.62, 37.38}, + "祁连县": {100.25, 38.18}, + "海晏县": {100.98, 36.9}, + "刚察县": {100.13, 37.33}, + "黄南藏族自治州": {102.02, 35.52}, + "同仁县": {102.02, 35.52}, + "尖扎县": {102.03, 35.93}, + "泽库县": {101.47, 35.03}, + "河南蒙古族自治县": {101.6, 34.73}, + "海南藏族自治州": {100.62, 36.28}, + "共和县": {100.62, 36.28}, + "同德县": {100.57, 35.25}, + "贵德县": {101.43, 36.05}, + "兴海县": {99.98, 35.58}, + "贵南县": {100.75, 35.58}, + "果洛藏族自治州": {100.23, 34.48}, + "玛沁县": {100.23, 34.48}, + "班玛县": {100.73, 32.93}, + "甘德县": {99.9, 33.97}, + "达日县": {99.65, 33.75}, + "久治县": {101.48, 33.43}, + "玛多县": {98.18, 34.92}, + "玉树藏族自治州": {97.02, 33}, + "玉树县": {97.02, 33}, + "杂多县": {95.3, 32.9}, + "称多县": {97.1, 33.37}, + "治多县": {95.62, 33.85}, + "囊谦县": {96.48, 32.2}, + "曲麻莱县": {95.8, 34.13}, + "海西蒙古族藏族自治州": {97.37, 37.37}, + "格尔木市": {94.9, 36.42}, + "德令哈市": {97.37, 37.37}, + "乌兰县": {98.48, 36.93}, + "都兰县": {98.08, 36.3}, + "天峻县": {99.02, 37.3}, + "银川市": {106.28, 38.47}, + "兴庆区": {106.28, 38.48}, + "西夏区": {106.18, 38.48}, + "金凤区": {106.25, 38.47}, + "永宁县": {106.25, 38.28}, + "贺兰县": {106.35, 38.55}, + "灵武市": {106.33, 38.1}, + "石嘴山市": {106.38, 39.02}, + "大武口区": {106.38, 39.02}, + "惠农区": {106.78, 39.25}, + "平罗县": {106.53, 38.9}, + "吴忠市": {106.2, 37.98}, + "利通区": {106.2, 37.98}, + "盐池县": {107.4, 37.78}, + "同心县": {105.92, 36.98}, + "青铜峡市": {106.07, 38.02}, + "固原市": {106.28, 36}, + "原州区": {106.28, 36}, + "西吉县": {105.73, 35.97}, + "隆德县": {106.12, 35.62}, + "泾源县": {106.33, 35.48}, + "彭阳县": {106.63, 35.85}, + "中卫市": {105.18, 37.52}, + "沙坡头区": {105.18, 37.52}, + "中宁县": {105.67, 37.48}, + "海原县": {105.65, 36.57}, + "乌鲁木齐市": {87.62, 43.82}, + "天山区": {87.65, 43.78}, + "沙依巴克区": {87.6, 43.78}, + "新市区": {87.6, 43.85}, + "水磨沟区": {87.63, 43.83}, + "头屯河区": {87.42, 43.87}, + "达坂城区": {88.3, 43.35}, + "乌鲁木齐县": {87.6, 43.8}, + "克拉玛依市": {84.87, 45.6}, + "独山子区": {84.85, 44.32}, + "克拉玛依区": {84.87, 45.6}, + "白碱滩区": {85.13, 45.7}, + "乌尔禾区": {85.68, 46.08}, + "吐鲁番地区": {89.17, 42.95}, + "吐鲁番市": {89.17, 42.95}, + "鄯善县": {90.22, 42.87}, + "托克逊县": {88.65, 42.78}, + "哈密地区": {93.52, 42.83}, + "哈密市": {93.52, 42.83}, + "伊吾县": {94.7, 43.25}, + "昌吉回族自治州": {87.3, 44.02}, + "昌吉市": {87.3, 44.02}, + "阜康市": {87.98, 44.15}, + "米泉市": {87.65, 43.97}, + "呼图壁县": {86.9, 44.18}, + "玛纳斯县": {86.22, 44.3}, + "奇台县": {89.58, 44.02}, + "吉木萨尔县": {89.18, 44}, + "木垒哈萨克自治县": {90.28, 43.83}, + "博尔塔拉蒙古自治州": {82.07, 44.9}, + "博乐市": {82.07, 44.9}, + "精河县": {82.88, 44.6}, + "温泉县": {81.03, 44.97}, + "巴音郭楞蒙古自治州": {86.15, 41.77}, + "库尔勒市": {86.15, 41.77}, + "轮台县": {84.27, 41.78}, + "尉犁县": {86.25, 41.33}, + "若羌县": {88.17, 39.02}, + "且末县": {85.53, 38.13}, + "焉耆回族自治县": {86.57, 42.07}, + "和静县": {86.4, 42.32}, + "和硕县": {86.87, 42.27}, + "博湖县": {86.63, 41.98}, + "阿克苏地区": {80.27, 41.17}, + "阿克苏市": {80.27, 41.17}, + "温宿县": {80.23, 41.28}, + "库车县": {82.97, 41.72}, + "沙雅县": {82.78, 41.22}, + "新和县": {82.6, 41.55}, + "拜城县": {81.87, 41.8}, + "乌什县": {79.23, 41.22}, + "阿瓦提县": {80.38, 40.63}, + "柯坪县": {79.05, 40.5}, + "阿图什市": {76.17, 39.72}, + "阿克陶县": {75.95, 39.15}, + "阿合奇县": {78.45, 40.93}, + "乌恰县": {75.25, 39.72}, + "喀什地区": {75.98, 39.47}, + "喀什市": {75.98, 39.47}, + "疏附县": {75.85, 39.38}, + "疏勒县": {76.05, 39.4}, + "英吉沙县": {76.17, 38.93}, + "泽普县": {77.27, 38.18}, + "莎车县": {77.23, 38.42}, + "叶城县": {77.42, 37.88}, + "麦盖提县": {77.65, 38.9}, + "岳普湖县": {76.77, 39.23}, + "伽师县": {76.73, 39.5}, + "巴楚县": {78.55, 39.78}, + "和田地区": {79.92, 37.12}, + "和田市": {79.92, 37.12}, + "和田县": {79.93, 37.1}, + "墨玉县": {79.73, 37.27}, + "皮山县": {78.28, 37.62}, + "洛浦县": {80.18, 37.07}, + "策勒县": {80.8, 37}, + "于田县": {81.67, 36.85}, + "民丰县": {82.68, 37.07}, + "伊犁哈萨克自治州": {81.32, 43.92}, + "伊宁市": {81.32, 43.92}, + "奎屯市": {84.9, 44.42}, + "伊宁县": {81.52, 43.98}, + "察布查尔锡伯自治县": {81.15, 43.83}, + "霍城县": {80.88, 44.05}, + "巩留县": {82.23, 43.48}, + "新源县": {83.25, 43.43}, + "昭苏县": {81.13, 43.15}, + "特克斯县": {81.83, 43.22}, + "尼勒克县": {82.5, 43.78}, + "塔城地区": {82.98, 46.75}, + "塔城市": {82.98, 46.75}, + "乌苏市": {84.68, 44.43}, + "额敏县": {83.63, 46.53}, + "沙湾县": {85.62, 44.33}, + "托里县": {83.6, 45.93}, + "裕民县": {82.98, 46.2}, + "和布克赛尔蒙古自治县": {85.72, 46.8}, + "阿勒泰地区": {88.13, 47.85}, + "阿勒泰市": {88.13, 47.85}, + "布尔津县": {86.85, 47.7}, + "富蕴县": {89.52, 47}, + "福海县": {87.5, 47.12}, + "哈巴河县": {86.42, 48.07}, + "青河县": {90.38, 46.67}, + "吉木乃县": {85.88, 47.43}, + "石河子市": {86.03, 44.3}, + "阿拉尔市": {81.28, 40.55}, + "图木舒克市": {79.13, 39.85}, + "五家渠市": {87.53, 44.17}, + "台北市": {121.5, 25.03}, + "高雄市": {120.28, 22.62}, + "基隆市": {121.73, 25.13}, + "台中市": {120.67, 24.15}, + "台南市": {120.2, 23}, + "新竹市": {120.95, 24.82}, + "嘉义市": {120.43, 23.48}, + "台北县": {121.47, 25.02}, + "宜兰县": {121.75, 24.77}, + "桃园县": {121.3, 24.97}, + "苗栗县": {120.8, 24.53}, + "台中县": {120.72, 24.25}, + "彰化县": {120.53, 24.08}, + "南投县": {120.67, 23.92}, + "云林县": {120.53, 23.72}, + "台南县": {120.32, 23.32}, + "高雄县": {120.37, 22.63}, + "屏东县": {120.48, 22.67}, + "台东县": {121.15, 22.75}, + "花莲县": {121.6, 23.98}, + "澎湖县": {119.58, 23.58}, + "北京": {116.407526, 39.90403}, + "天津": {117.200983, 39.084158}, + "河北": {114.468664, 38.037057}, + "山西": {112.562398, 37.873531}, + "内蒙古": {111.765617, 40.817498}, + "辽宁": {123.42944, 41.835441}, + "吉林": {125.32599, 43.896536}, + "黑龙江": {126.661669, 45.742347}, + "上海": {121.473701, 31.230416}, + "江苏": {118.763232, 32.061707}, + "浙江": {120.152791, 30.267446}, + "安徽": {117.284922, 31.861184}, + "福建": {119.295144, 26.100779}, + "江西": {115.909228, 28.675696}, + "山东": {117.020359, 36.66853}, + "河南": {113.753602, 34.765515}, + "湖北": {114.341861, 30.546498}, + "湖南": {112.98381, 28.112444}, + "广东": {113.26653, 23.132191}, + "广西": {108.327546, 22.815478}, + "海南": {110.349228, 20.017377}, + "重庆": {106.551556, 29.563009}, + "四川": {104.075931, 30.651651}, + "贵州": {106.70741, 26.598194}, + "云南": {102.710002, 25.045806}, + "西藏": {91.117212, 29.646922}, + "陕西": {108.954239, 34.265472}, + "甘肃": {103.826308, 36.059421}, + "青海": {101.780199, 36.620901}, + "宁夏": {106.258754, 38.471317}, + "新疆": {87.627704, 43.793026}, + "香港": {114.173355, 22.320048}, + "澳门": {113.54909, 22.198951}, + "台湾": {121.509062, 25.044332}, +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/datasets/mapfiles.go b/vendor/github.com/go-echarts/go-echarts/v2/datasets/mapfiles.go new file mode 100644 index 0000000000..64d8a3c575 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/datasets/mapfiles.go @@ -0,0 +1,404 @@ +package datasets + +var MapFileNames = map[string]string{ + "china": "china", + "world": "world", + "广东": "guangdong", + "安徽": "anhui", + "福建": "fujian", + "甘肃": "gansu", + "广西": "guangxi", + "贵州": "guizhou", + "海南": "hainan", + "河北": "hebei", + "黑龙江": "heilongjiang", + "河南": "henan", + "湖北": "hubei", + "湖南": "hunan", + "江苏": "jiangsu", + "江西": "jiangxi", + "吉林": "jilin", + "辽宁": "liaoning", + "内蒙古": "neimenggu", + "宁夏": "ningxia", + "青海": "qinghai", + "山东": "shandong", + "山西": "shanxi", + "陕西": "shanxi1", + "四川": "sichuan", + "台湾": "taiwan", + "新疆": "xinjiang", + "西藏": "xizang", + "云南": "yunnan", + "浙江": "zhejiang", + "七台河": "hei1_long2_jiang1_qi1_tai2_he2", + "万宁": "hai3_nan2_wan4_ning2", + "三亚": "hai3_nan2_san1_ya4", + "三明": "fu2_jian4_san1_ming2", + "三沙": "hai3_nan2_san1_sha1", + "三门峡": "he2_nan2_san1_men2_xia2", + "上海": "shanghai", + "上饶": "jiang1_xi1_shang4_rao2", + "东方": "hai3_nan2_dong1_fang1", + "东沙群岛": "guang3_dong1_dong1_sha1_qun2_dao3", + "东莞": "guang3_dong1_dong1_guan1", + "东营": "shan1_dong1_dong1_ying2", + "中卫": "ning2_xia4_zhong1_wei4", + "中山": "guang3_dong1_zhong1_shan1", + "临夏回族自治州": "gan1_su4_lin2_xia4_hui2_zu2_zi4_zhi4_zhou1", + "临汾": "shan1_xi1_lin2_fen2", + "临沂": "shan1_dong1_lin2_yi2", + "临沧": "yun2_nan2_lin2_cang1", + "临高县": "hai3_nan2_lin2_gao1_xian4", + "丹东": "liao2_ning2_dan1_dong1", + "丽水": "zhe4_jiang1_li4_shui3", + "丽江": "yun2_nan2_li4_jiang1", + "乌兰察布": "nei4_meng2_gu3_wu1_lan2_cha2_bu4", + "乌海": "nei4_meng2_gu3_wu1_hai3", + "乌鲁木齐": "xin1_jiang1_wu1_lu3_mu4_qi2", + "乐东黎族自治县": "hai3_nan2_le4_dong1_li2_zu2_zi4_zhi4_xian4", + "乐山": "si4_chuan1_le4_shan1", + "九江": "jiang1_xi1_jiu3_jiang1", + "云浮": "guang3_dong1_yun2_fu2", + "五家渠": "xin1_jiang1_wu3_jia1_qu2", + "五指山": "hai3_nan2_wu3_zhi3_shan1", + "亳州": "an1_hui1_bo2_zhou1", + "仙桃": "hu2_bei3_xian1_tao2", + "伊春": "hei1_long2_jiang1_yi1_chun1", + "伊犁哈萨克自治州": "xin1_jiang1_yi1_li2_ha1_sa4_ke4_zi4_zhi4_zhou1", + "佛山": "guang3_dong1_fo2_shan1", + "佳木斯": "hei1_long2_jiang1_jia1_mu4_si1", + "保亭黎族苗族自治县": "hai3_nan2_bao3_ting2_li2_zu2_miao2_zu2_zi4_zhi4_xian4", + "保定": "he2_bei3_bao3_ding4", + "保山": "yun2_nan2_bao3_shan1", + "信阳": "he2_nan2_xin4_yang2", + "儋州": "hai3_nan2_dan1_zhou1", + "克孜勒苏柯尔克孜自治州": "xin1_jiang1_ke4_zi1_le4_su1_ke1_er3_ke4_zi1_zi4_zhi4_zhou1", + "克拉玛依": "xin1_jiang1_ke4_la1_ma3_yi1", + "六安": "an1_hui1_liu4_an1", + "六盘水": "gui4_zhou1_liu4_pan2_shui3", + "兰州": "gan1_su4_lan2_zhou1", + "兴安盟": "nei4_meng2_gu3_xing1_an1_meng2", + "内江": "si4_chuan1_nei4_jiang1", + "凉山彝族自治州": "si4_chuan1_liang2_shan1_yi2_zu2_zi4_zhi4_zhou1", + "包头": "nei4_meng2_gu3_bao1_tou2", + "北京": "beijing", + "北屯": "xin1_jiang1_bei3_tun2", + "北海": "guang3_xi1_bei3_hai3", + "十堰": "hu2_bei3_shi2_yan4", + "南京": "jiang1_su1_nan2_jing1", + "南充": "si4_chuan1_nan2_chong1", + "南宁": "guang3_xi1_nan2_ning2", + "南平": "fu2_jian4_nan2_ping2", + "南昌": "jiang1_xi1_nan2_chang1", + "南通": "jiang1_su1_nan2_tong1", + "南阳": "he2_nan2_nan2_yang2", + "博尔塔拉蒙古自治州": "xin1_jiang1_bo2_er3_ta3_la1_meng2_gu3_zi4_zhi4_zhou1", + "厦门": "fu2_jian4_sha4_men2", + "双河": "xin1_jiang1_shuang1_he2", + "双鸭山": "hei1_long2_jiang1_shuang1_ya1_shan1", + "可克达拉": "xin1_jiang1_ke3_ke4_da2_la1", + "台州": "zhe4_jiang1_tai2_zhou1", + "合肥": "an1_hui1_he2_fei2", + "吉安": "jiang1_xi1_ji2_an1", + "吉林市": "ji2_lin2_ji2_lin2", + "吐鲁番": "xin1_jiang1_tu3_lu3_fan1", + "吕梁": "shan1_xi1_lv3_liang2", + "吴忠": "ning2_xia4_wu2_zhong1", + "周口": "he2_nan2_zhou1_kou3", + "呼伦贝尔": "nei4_meng2_gu3_hu1_lun2_bei4_er3", + "呼和浩特": "nei4_meng2_gu3_hu1_he2_hao4_te4", + "和田地区": "xin1_jiang1_he2_tian2_di4_qu1", + "咸宁": "hu2_bei3_xian2_ning2", + "咸阳": "shan3_xi1_xian2_yang2", + "哈密": "xin1_jiang1_ha1_mi4", + "哈尔滨": "hei1_long2_jiang1_ha1_er3_bin1", + "唐山": "he2_bei3_tang2_shan1", + "商丘": "he2_nan2_shang1_qiu1", + "商洛": "shan3_xi1_shang1_luo4", + "喀什地区": "xin1_jiang1_ka1_shi2_di4_qu1", + "嘉兴": "zhe4_jiang1_jia1_xing1", + "嘉峪关": "gan1_su4_jia1_yu4_guan1", + "四平": "ji2_lin2_si4_ping2", + "固原": "ning2_xia4_gu4_yuan2", + "图木舒克": "xin1_jiang1_tu2_mu4_shu1_ke4", + "塔城地区": "xin1_jiang1_ta3_cheng2_di4_qu1", + "大兴安岭地区": "hei1_long2_jiang1_da4_xing1_an1_ling2_di4_qu1", + "大同": "shan1_xi1_da4_tong2", + "大庆": "hei1_long2_jiang1_da4_qing4", + "大理白族自治州": "yun2_nan2_da4_li3_bai2_zu2_zi4_zhi4_zhou1", + "大连": "liao2_ning2_da4_lian2", + "天水": "gan1_su4_tian1_shui3", + "天津": "tianjin", + "天门": "hu2_bei3_tian1_men2", + "太原": "shan1_xi1_tai4_yuan2", + "威海": "shan1_dong1_wei1_hai3", + "娄底": "hu2_nan2_lou2_di3", + "孝感": "hu2_bei3_xiao4_gan3", + "宁德": "fu2_jian4_ning2_de2", + "宁波": "zhe4_jiang1_ning2_bo1", + "安庆": "an1_hui1_an1_qing4", + "安康": "shan3_xi1_an1_kang1", + "安阳": "he2_nan2_an1_yang2", + "安顺": "gui4_zhou1_an1_shun4", + "定安县": "hai3_nan2_ding4_an1_xian4", + "定西": "gan1_su4_ding4_xi1", + "宜宾": "si4_chuan1_yi2_bin1", + "宜昌": "hu2_bei3_yi2_chang1", + "宜春": "jiang1_xi1_yi2_chun1", + "宝鸡": "shan3_xi1_bao3_ji1", + "宣城": "an1_hui1_xuan1_cheng2", + "宿州": "an1_hui1_su4_zhou1", + "宿迁": "jiang1_su1_su4_qian1", + "屯昌县": "hai3_nan2_tun2_chang1_xian4", + "山南": "xi1_cang2_shan1_nan2", + "岳阳": "hu2_nan2_yue4_yang2", + "崇左": "guang3_xi1_chong2_zuo3", + "巴中": "si4_chuan1_ba1_zhong1", + "巴彦淖尔": "nei4_meng2_gu3_ba1_yan4_nao4_er3", + "巴音郭楞蒙古自治州": "xin1_jiang1_ba1_yin1_guo1_leng2_meng2_gu3_zi4_zhi4_zhou1", + "常州": "jiang1_su1_chang2_zhou1", + "常德": "hu2_nan2_chang2_de2", + "平凉": "gan1_su4_ping2_liang2", + "平顶山": "he2_nan2_ping2_ding3_shan1", + "广元": "si4_chuan1_guang3_yuan2", + "广安": "si4_chuan1_guang3_an1", + "广州": "guang3_dong1_guang3_zhou1", + "庆阳": "gan1_su4_qing4_yang2", + "廊坊": "he2_bei3_lang2_fang1", + "延安": "shan3_xi1_yan2_an1", + "延边朝鲜族自治州": "ji2_lin2_yan2_bian1_zhao1_xian1_zu2_zi4_zhi4_zhou1", + "开封": "he2_nan2_kai1_feng1", + "张家口": "he2_bei3_zhang1_jia1_kou3", + "张家界": "hu2_nan2_zhang1_jia1_jie4", + "张掖": "gan1_su4_zhang1_ye4", + "徐州": "jiang1_su1_xu2_zhou1", + "德宏傣族景颇族自治州": "yun2_nan2_de2_hong2_dai3_zu2_jing3_po3_zu2_zi4_zhi4_zhou1", + "德州": "shan1_dong1_de2_zhou1", + "德阳": "si4_chuan1_de2_yang2", + "忻州": "shan1_xi1_xin1_zhou1", + "怀化": "hu2_nan2_huai2_hua4", + "怒江傈僳族自治州": "yun2_nan2_nu4_jiang1_li4_su4_zu2_zi4_zhi4_zhou1", + "恩施土家族苗族自治州": "hu2_bei3_en1_shi1_tu3_jia1_zu2_miao2_zu2_zi4_zhi4_zhou1", + "惠州": "guang3_dong1_hui4_zhou1", + "成都": "si4_chuan1_cheng2_du1", + "扬州": "jiang1_su1_yang2_zhou1", + "承德": "he2_bei3_cheng2_de2", + "抚州": "jiang1_xi1_fu3_zhou1", + "抚顺": "liao2_ning2_fu3_shun4", + "拉萨": "xi1_cang2_la1_sa4", + "揭阳": "guang3_dong1_jie1_yang2", + "攀枝花": "si4_chuan1_pan1_zhi1_hua1", + "文山壮族苗族自治州": "yun2_nan2_wen2_shan1_zhuang4_zu2_miao2_zu2_zi4_zhi4_zhou1", + "文昌": "hai3_nan2_wen2_chang1", + "新乡": "he2_nan2_xin1_xiang1", + "新余": "jiang1_xi1_xin1_yu2", + "无锡": "jiang1_su1_wu2_xi2", + "日喀则": "xi1_cang2_ri4_ka1_ze2", + "日照": "shan1_dong1_ri4_zhao4", + "昆明": "yun2_nan2_kun1_ming2", + "昆玉": "xin1_jiang1_kun1_yu4", + "昌吉回族自治州": "xin1_jiang1_chang1_ji2_hui2_zu2_zi4_zhi4_zhou1", + "昌江黎族自治县": "hai3_nan2_chang1_jiang1_li2_zu2_zi4_zhi4_xian4", + "昌都": "xi1_cang2_chang1_du1", + "昭通": "yun2_nan2_zhao1_tong1", + "晋中": "shan1_xi1_jin4_zhong1", + "晋城": "shan1_xi1_jin4_cheng2", + "普洱": "yun2_nan2_pu3_er3", + "景德镇": "jiang1_xi1_jing3_de2_zhen4", + "曲靖": "yun2_nan2_qu1_jing4", + "朔州": "shan1_xi1_shuo4_zhou1", + "朝阳": "liao2_ning2_zhao1_yang2", + "本溪": "liao2_ning2_ben3_xi1", + "来宾": "guang3_xi1_lai2_bin1", + "杭州": "zhe4_jiang1_hang2_zhou1", + "松原": "ji2_lin2_song1_yuan2", + "林芝": "xi1_cang2_lin2_zhi1", + "果洛藏族自治州": "qing1_hai3_guo3_luo4_cang2_zu2_zi4_zhi4_zhou1", + "枣庄": "shan1_dong1_zao3_zhuang1", + "柳州": "guang3_xi1_liu3_zhou1", + "株洲": "hu2_nan2_zhu1_zhou1", + "桂林": "guang3_xi1_gui4_lin2", + "梅州": "guang3_dong1_mei2_zhou1", + "梧州": "guang3_xi1_wu2_zhou1", + "楚雄彝族自治州": "yun2_nan2_chu3_xiong2_yi2_zu2_zi4_zhi4_zhou1", + "榆林": "shan3_xi1_yu2_lin2", + "武威": "gan1_su4_wu3_wei1", + "武汉": "hu2_bei3_wu3_han4", + "毕节": "gui4_zhou1_bi4_jie2", + "永州": "hu2_nan2_yong3_zhou1", + "汉中": "shan3_xi1_han4_zhong1", + "汕头": "guang3_dong1_shan4_tou2", + "汕尾": "guang3_dong1_shan4_wei3", + "江门": "guang3_dong1_jiang1_men2", + "池州": "an1_hui1_chi2_zhou1", + "沈阳": "liao2_ning2_shen3_yang2", + "沧州": "he2_nan2_cang1_zhou1", + "河池": "guang3_xi1_he2_chi2", + "河源": "guang3_dong1_he2_yuan2", + "泉州": "fu2_jian4_quan2_zhou1", + "泰安": "shan1_dong1_tai4_an1", + "泰州": "jiang1_su1_tai4_zhou1", + "泸州": "si4_chuan1_lu2_zhou1", + "洛阳": "he2_nan2_luo4_yang2", + "济南": "shan1_dong1_ji4_nan2", + "济宁": "shan1_dong1_ji4_ning2", + "济源": "he2_nan2_ji4_yuan2", + "海东": "qing1_hai3_hai3_dong1", + "海北藏族自治州": "qing1_hai3_hai3_bei3_cang2_zu2_zi4_zhi4_zhou1", + "海南藏族自治州": "qing1_hai3_hai3_nan2_cang2_zu2_zi4_zhi4_zhou1", + "海口": "hai3_nan2_hai3_kou3", + "海西蒙古族藏族自治州": "qing1_hai3_hai3_xi1_meng2_gu3_zu2_cang2_zu2_zi4_zhi4_zhou1", + "淄博": "shan1_dong1_zi1_bo2", + "淮北": "an1_hui1_huai2_bei3", + "淮南": "an1_hui1_huai2_nan2", + "淮安": "jiang1_su1_huai2_an1", + "深圳": "guang3_dong1_shen1_zhen4", + "清远": "guang3_dong1_qing1_yuan3", + "温州": "zhe4_jiang1_wen1_zhou1", + "渭南": "shan3_xi1_wei4_nan2", + "湖州": "zhe4_jiang1_hu2_zhou1", + "湘潭": "hu2_nan2_xiang1_tan2", + "湘西土家族苗族自治州": "hu2_nan2_xiang1_xi1_tu3_jia1_zu2_miao2_zu2_zi4_zhi4_zhou1", + "湛江": "guang3_dong1_zhan4_jiang1", + "滁州": "an1_hui1_chu2_zhou1", + "滨州": "shan1_dong1_bin1_zhou1", + "漯河": "he2_nan2_ta4_he2", + "漳州": "fu2_jian4_zhang1_zhou1", + "潍坊": "shan1_dong1_wei2_fang1", + "潜江": "hu2_bei3_qian2_jiang1", + "潮州": "guang3_dong1_chao2_zhou1", + "澄迈县": "hai3_nan2_cheng2_mai4_xian4", + "澳门": "aomen", + "濮阳": "he2_nan2_pu2_yang2", + "烟台": "shan1_dong1_yan1_tai2", + "焦作": "he2_nan2_jiao1_zuo4", + "牡丹江": "hei1_long2_jiang1_mu3_dan1_jiang1", + "玉林": "guang3_xi1_yu4_lin2", + "玉树藏族自治州": "qing1_hai3_yu4_shu4_cang2_zu2_zi4_zhi4_zhou1", + "玉溪": "yun2_nan2_yu4_xi1", + "珠海": "guang3_dong1_zhu1_hai3", + "琼中黎族苗族自治县": "hai3_nan2_qiong2_zhong1_li2_zu2_miao2_zu2_zi4_zhi4_xian4", + "琼海": "hai3_nan2_qiong2_hai3", + "甘南藏族自治州": "gan1_su4_gan1_nan2_cang2_zu2_zi4_zhi4_zhou1", + "甘孜藏族自治州": "si4_chuan1_gan1_zi1_cang2_zu2_zi4_zhi4_zhou1", + "白城": "ji2_lin2_bai2_cheng2", + "白山": "ji2_lin2_bai2_shan1", + "白沙黎族自治县": "hai3_nan2_bai2_sha1_li2_zu2_zi4_zhi4_xian4", + "白银": "gan1_su4_bai2_yin2", + "百色": "guang3_xi1_bai3_se4", + "益阳": "hu2_nan2_yi4_yang2", + "盐城": "jiang1_su1_yan2_cheng2", + "盘锦": "liao2_ning2_pan2_jin3", + "眉山": "si4_chuan1_mei2_shan1", + "石嘴山": "ning2_xia4_shi2_zui3_shan1", + "石家庄": "he2_bei3_shi2_jia1_zhuang1", + "石河子": "xin1_jiang1_shi2_he2_zi3", + "神农架林区": "hu2_bei3_shen2_nong2_jia4_lin2_qu1", + "福州": "fu2_jian4_fu2_zhou1", + "秦皇岛": "he2_bei3_qin2_huang2_dao3", + "红河哈尼族彝族自治州": "yun2_nan2_hong2_he2_ha1_ni2_zu2_yi2_zu2_zi4_zhi4_zhou1", + "绍兴": "zhe4_jiang1_shao4_xing1", + "绥化": "hei1_long2_jiang1_sui1_hua4", + "绵阳": "si4_chuan1_mian2_yang2", + "聊城": "shan1_dong1_liao2_cheng2", + "肇庆": "guang3_dong1_zhao4_qing4", + "自贡": "si4_chuan1_zi4_gong4", + "舟山": "zhe4_jiang1_zhou1_shan1", + "芜湖": "an1_hui1_wu2_hu2", + "苏州": "jiang1_su1_su1_zhou1", + "茂名": "guang3_dong1_mao4_ming2", + "荆州": "hu2_bei3_jing1_zhou1", + "荆门": "hu2_bei3_jing1_men2", + "莆田": "fu2_jian4_fu3_tian2", + "莱芜": "shan1_dong1_lai2_wu2", + "菏泽": "shan1_dong1_he2_ze2", + "萍乡": "jiang1_xi1_ping2_xiang1", + "营口": "liao2_ning2_ying2_kou3", + "葫芦岛": "liao2_ning2_hu2_lu2_dao3", + "蚌埠": "an1_hui1_bang4_bu4", + "衡水": "he2_bei3_heng2_shui3", + "衡阳": "hu2_nan2_heng2_yang2", + "衢州": "zhe4_jiang1_qu2_zhou1", + "襄阳": "hu2_bei3_xiang1_yang2", + "西双版纳傣族自治州": "yun2_nan2_xi1_shuang1_ban3_na4_dai3_zu2_zi4_zhi4_zhou1", + "西宁": "qing1_hai3_xi1_ning2", + "西安": "shan3_xi1_xi1_an1", + "许昌": "he2_nan2_xu3_chang1", + "贵港": "guang3_xi1_gui4_gang3", + "贵阳": "gui4_zhou1_gui4_yang2", + "贺州": "guang3_xi1_he4_zhou1", + "资阳": "si4_chuan1_zi1_yang2", + "赣州": "jiang1_xi1_gan4_zhou1", + "赤峰": "nei4_meng2_gu3_chi4_feng1", + "辽源": "ji2_lin2_liao2_yuan2", + "辽阳": "liao2_ning2_liao2_yang2", + "达州": "si4_chuan1_da2_zhou1", + "运城": "shan1_xi1_yun4_cheng2", + "连云港": "jiang1_su1_lian2_yun2_gang3", + "迪庆藏族自治州": "yun2_nan2_di2_qing4_cang2_zu2_zi4_zhi4_zhou1", + "通化": "ji2_lin2_tong1_hua4", + "通辽": "nei4_meng2_gu3_tong1_liao2", + "遂宁": "si4_chuan1_sui4_ning2", + "遵义": "gui4_zhou1_zun1_yi4", + "邢台": "he2_bei3_xing2_tai2", + "那曲地区": "xi1_cang2_na4_qu1_di4_qu1", + "邯郸": "he2_bei3_han2_dan1", + "邵阳": "hu2_nan2_shao4_yang2", + "郑州": "he2_nan2_zheng4_zhou1", + "郴州": "hu2_nan2_chen1_zhou1", + "鄂尔多斯": "nei4_meng2_gu3_e4_er3_duo1_si1", + "鄂州": "hu2_bei3_e4_zhou1", + "酒泉": "gan1_su4_jiu3_quan2", + "重庆": "chongqing", + "金华": "zhe4_jiang1_jin1_hua2", + "金昌": "gan1_su4_jin1_chang1", + "钦州": "guang3_xi1_qin1_zhou1", + "铁岭": "liao2_ning2_tie3_ling2", + "铁门关": "xin1_jiang1_tie3_men2_guan1", + "铜仁": "gui4_zhou1_tong2_ren2", + "铜川": "shan3_xi1_tong2_chuan1", + "铜陵": "an1_hui1_tong2_ling2", + "银川": "ning2_xia4_yin2_chuan1", + "锡林郭勒盟": "nei4_meng2_gu3_xi2_lin2_guo1_le4_meng2", + "锦州": "liao2_ning2_jin3_zhou1", + "镇江": "jiang1_su1_zhen4_jiang1", + "长春": "ji2_lin2_chang2_chun1", + "长沙": "hu2_nan2_chang2_sha1", + "长治": "shan1_xi1_chang2_zhi4", + "阜新": "liao2_ning2_fu4_xin1", + "阜阳": "an1_hui1_fu4_yang2", + "防城港": "guang3_xi1_fang2_cheng2_gang3", + "阳江": "guang3_dong1_yang2_jiang1", + "阳泉": "shan1_xi1_yang2_quan2", + "阿克苏地区": "xin1_jiang1_a1_ke4_su1_di4_qu1", + "阿勒泰地区": "xin1_jiang1_a1_le4_tai4_di4_qu1", + "阿坝藏族羌族自治州": "si4_chuan1_a1_ba4_cang2_zu2_qiang1_zu2_zi4_zhi4_zhou1", + "阿拉善盟": "nei4_meng2_gu3_a1_la1_shan4_meng2", + "阿拉尔": "xin1_jiang1_a1_la1_er3", + "阿里地区": "xi1_cang2_a1_li3_di4_qu1", + "陇南": "gan1_su4_long3_nan2", + "陵水黎族自治县": "hai3_nan2_ling2_shui3_li2_zu2_zi4_zhi4_xian4", + "随州": "hu2_bei3_sui2_zhou1", + "雅安": "si4_chuan1_ya3_an1", + "青岛": "shan1_dong1_qing1_dao3", + "鞍山": "liao2_ning2_an1_shan1", + "韶关": "guang3_dong1_shao2_guan1", + "香港": "xianggang", + "马鞍山": "an1_hui1_ma3_an1_shan1", + "驻马店": "he2_nan2_zhu4_ma3_dian4", + "鸡西": "hei1_long2_jiang1_ji1_xi1", + "鹤壁": "he2_nan2_he4_bi4", + "鹤岗": "hei1_long2_jiang1_he4_gang3", + "鹰潭": "jiang1_xi1_ying1_tan2", + "黄冈": "hu2_bei3_huang2_gang1", + "黄南藏族自治州": "qing1_hai3_huang2_nan2_cang2_zu2_zi4_zhi4_zhou1", + "黄山": "an1_hui1_huang2_shan1", + "黄石": "hu2_bei3_huang2_shi2", + "黑河": "hei1_long2_jiang1_hei1_he2", + "黔东南苗族侗族自治州": "gui4_zhou1_qian2_dong1_nan2_miao2_zu2_tong1_zu2_zi4_zhi4_zhou1", + "黔南布依族苗族自治州": "gui4_zhou1_qian2_nan2_bu4_yi1_zu2_miao2_zu2_zi4_zhi4_zhou1", + "黔西南布依族苗族自治州": "gui4_zhou1_qian2_xi1_nan2_bu4_yi1_zu2_miao2_zu2_zi4_zhi4_zhou1", + "齐齐哈尔": "hei1_long2_jiang1_qi2_qi2_ha1_er3", + "龙岩": "fu2_jian4_long2_yan2", +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/opts/charts.go b/vendor/github.com/go-echarts/go-echarts/v2/opts/charts.go new file mode 100644 index 0000000000..569483666c --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/opts/charts.go @@ -0,0 +1,771 @@ +package opts + +// BarChart +// https://echarts.apache.org/en/option.html#series-bar +type BarChart struct { + Type string + // Name of stack. On the same category axis, the series with the + // same stack name would be put on top of each other. + Stack string + + // The gap between bars between different series, is a percent value like '30%', + // which means 30% of the bar width. + // Set barGap as '-100%' can overlap bars that belong to different series, + // which is useful when putting a series of bar as background. + // In a single coordinate system, this attribute is shared by multiple 'bar' series. + // This attribute should be set on the last 'bar' series in the coordinate system, + // then it will be adopted by all 'bar' series in the coordinate system. + BarGap string + + // The bar gap of a single series, defaults to be 20% of the category gap, + // can be set as a fixed value. + // In a single coordinate system, this attribute is shared by multiple 'bar' series. + // This attribute should be set on the last 'bar' series in the coordinate system, + // then it will be adopted by all 'bar' series in the coordinate system. + BarCategoryGap string + + // Index of x axis to combine with, which is useful for multiple x axes in one chart. + XAxisIndex int + + // Index of y axis to combine with, which is useful for multiple y axes in one chart. + YAxisIndex int + + ShowBackground bool + RoundCap bool + CoordSystem string +} + +// SunburstChart +// https://echarts.apache.org/en/option.html#series-sunburst +type SunburstChart struct { + // The action of clicking a sector + NodeClick string `json:"nodeClick,omitempty"` + // Sorting method that sectors use based on value + Sort string `json:"sort,omitempty"` + // If there is no name, whether need to render it. + RenderLabelForZeroData bool `json:"renderLabelForZeroData"` + // Selected mode + SelectedMode bool `json:"selectedMode"` + // Whether to enable animation. + Animation bool `json:"animation"` + // Whether to set graphic number threshold to animation + AnimationThreshold int `json:"animationThreshold,omitempty"` + // Duration of the first animation + AnimationDuration int `json:"animationDuration,omitempty"` + // Easing method used for the first animation + AnimationEasing string `json:"animationEasing,omitempty"` + // Delay before updating the first animation + AnimationDelay int `json:"animationDelay,omitempty"` + // Time for animation to complete + AnimationDurationUpdate int `json:"animationDurationUpdate,omitempty"` + // Easing method used for animation. + AnimationEasingUpdate string `json:"animationEasingUpdate,omitempty"` + // Delay before updating animation + AnimationDelayUpdate int `json:"animationDelayUpdate,omitempty"` +} + +// BarData +// https://echarts.apache.org/en/option.html#series-bar.data +type BarData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` + + // The style setting of the text label in a single bar. + Label *Label `json:"label,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Tooltip settings in this series data. + Tooltip *Tooltip `json:"tooltip,omitempty"` +} + +// Bar3DChart is the option set for a 3D bar chart. +type Bar3DChart struct { + // Shading is the coloring effect of 3D graphics in 3D Bar. + // The following three coloring methods are supported in echarts-gl: + // Options: + // + // * "color": Only display colors, not affected by other factors such as lighting. + // * "lambert": Through the classic [lambert] coloring, can express the light and dark that the light shows. + // * "realistic": Realistic rendering, combined with light.ambientCubemap and postEffect, + // can improve the quality and texture of the display. + // [Physical Based Rendering (PBR)] (https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/) + // is used in ECharts GL to represent realistic materials. + Shading string +} + +// BoxPlotData +// https://echarts.apache.org/en/option.html#series-boxplot.data +type BoxPlotData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` + + // The style setting of the text label in a single bar. + Label *Label `json:"label,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Emphasis settings in this series data. + Emphasis *Emphasis `json:"emphasis,omitempty"` + + // Tooltip settings in this series data. + Tooltip *Tooltip `json:"tooltip,omitempty"` +} + +// EffectScatterData +// https://echarts.apache.org/en/option.html#series-effectScatter.data +type EffectScatterData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// FunnelData +// https://echarts.apache.org/en/option.html#series-funnel.data +type FunnelData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// GeoData +type GeoData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// GaugeData +// https://echarts.apache.org/en/option.html#series-gauge.data +type GaugeData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// GraphChart is the option set for graph chart. +// https://echarts.apache.org/en/option.html#series-graph +type GraphChart struct { + // Graph layout. + // * 'none' No layout, use x, y provided in node as the position of node. + // * 'circular' Adopt circular layout, see the example Les Miserables. + // * 'force' Adopt force-directed layout, see the example Force, the + // detail about layout configurations are in graph.force + Layout string + + // Force is the option set for graph force layout. + Force *GraphForce + + // Whether to enable mouse zooming and translating. false by default. + // If either zooming or translating is wanted, it can be set to 'scale' or 'move'. + // Otherwise, set it to be true to enable both. + Roam bool + + // EdgeSymbol is the symbols of two ends of edge line. + // * 'circle' + // * 'arrow' + // * 'none' + // example: ["circle", "arrow"] or "circle" + EdgeSymbol interface{} + + // EdgeSymbolSize is size of symbol of two ends of edge line. Can be an array or a single number + // example: [5,10] or 5 + EdgeSymbolSize interface{} + + // Draggable allows you to move the nodes with the mouse if they are not fixed. + Draggable bool + + // Whether to focus/highlight the hover node and it's adjacencies. + FocusNodeAdjacency bool + + // The categories of node, which is optional. If there is a classification of nodes, + // the category of each node can be assigned through data[i].category. + // And the style of category will also be applied to the style of nodes. categories can also be used in legend. + Categories []*GraphCategory + + // EdgeLabel is the properties of an label of edge. + EdgeLabel *EdgeLabel `json:"edgeLabel"` + + // SymbolKeepAspect is whether to keep aspect for symbols in the form of path://. + SymbolKeepAspect bool +} + +// GraphNode represents a data node in graph chart. +// https://echarts.apache.org/en/option.html#series-graph.data +type GraphNode struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // x value of node position. + X float32 `json:"x,omitempty"` + + // y value of node position. + Y float32 `json:"y,omitempty"` + + // Value of data item. + Value float32 `json:"value,omitempty"` + + // If node are fixed when doing force directed layout. + Fixed bool `json:"fixed,omitempty"` + + // Index of category which the data item belongs to. + Category interface{} `json:"category,omitempty"` + + // Symbol of node of this category. + // Icon types provided by ECharts includes + // 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none' + // It can be set to an image with 'image://url' , in which URL is the link to an image, or dataURI of an image. + Symbol string `json:"symbol,omitempty"` + + // node of this category symbol size. It can be set to single numbers like 10, + // or use an array to represent width and height. For example, [20, 10] means symbol width is 20, and height is10. + SymbolSize interface{} `json:"symbolSize,omitempty"` + + // The style of this node. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // The tooltip of this node. + Tooltip *Tooltip `json:"tooltip,omitempty"` +} + +// GraphLink represents relationship between two data nodes. +// https://echarts.apache.org/en/option.html#series-graph.links +type GraphLink struct { + // A string representing the name of source node on edge. Can also be a number representing the node index. + Source interface{} `json:"source,omitempty"` + + // A string representing the name of target node on edge. Can also be a number representing node index. + Target interface{} `json:"target,omitempty"` + + // value of edge, can be mapped to edge length in force graph. + Value float32 `json:"value,omitempty"` + + // Label for this link. + Label *EdgeLabel `json:"label,omitempty"` +} + +// GraphCategory represents a category for data nodes. +// The categories of node, which is optional. If there is a classification of nodes, +// the category of each node can be assigned through data[i].category. +// And the style of category will also be applied to the style of nodes. categories can also be used in legend. +// https://echarts.apache.org/en/option.html#series-graph.categories +type GraphCategory struct { + // Name of category, which is used to correspond with legend and the content of tooltip. + Name string `json:"name"` + + // The label style of node in this category. + Label *Label `json:"label,omitempty"` +} + +// HeatMapChart is the option set for a heatmap chart. +// https://echarts.apache.org/en/option.html#series-heatmap +type HeatMapChart struct { + // Index of x axis to combine with, which is useful for multiple x axes in one chart. + XAxisIndex int + + // Index of y axis to combine with, which is useful for multiple y axes in one chart. + YAxisIndex int +} + +// HeatMapData +// https://echarts.apache.org/en/option.html#series-heatmap.data +type HeatMapData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// KlineData +// https://echarts.apache.org/en/option.html#series-candlestick.data +type KlineData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// LineChart is the options set for a line chart. +// https://echarts.apache.org/en/option.html#series-line +type LineChart struct { + // If stack the value. On the same category axis, the series with the same stack name would be put on top of each other. + // The effect of the below example could be seen through stack switching of toolbox on the top right corner: + Stack string + + // Whether to show as smooth curve. + // If is typed in boolean, then it means whether to enable smoothing. If is + // typed in number, valued from 0 to 1, then it means smoothness. A smaller value makes it less smooth. + Smooth bool + + // Whether to show as a step line. It can be true, false. Or 'start', 'middle', 'end'. + // Which will configure the turn point of step line. + Step interface{} + + // Index of x axis to combine with, which is useful for multiple x axes in one chart. + XAxisIndex int + + // Index of y axis to combine with, which is useful for multiple y axes in one chart. + YAxisIndex int + + // Whether to connect the line across null points. + ConnectNulls bool + + // Whether to show symbol. It would be shown during tooltip hover. + ShowSymbol bool + + // Icon types provided by ECharts includes + // 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none' + // Full documentation: https://echarts.apache.org/en/option.html#series-line.symbol + Symbol string + + // symbol size. It can be set to single numbers like 10, or use an array to represent width and height. For example, [20, 10] means symbol width is 20, and height is10. + // Full documentation: https://echarts.apache.org/en/option.html#series-line.symbolSize + SymbolSize interface{} + + // color for Line series. it affects Line series including symbols, unlike LineStyle.Color + Color string + + // SymbolKeepAspect is whether to keep aspect for symbols in the form of path://. + SymbolKeepAspect bool +} + +// LineChart is the options set for a chandlestick chart. +// https://echarts.apache.org/en/option.html#series-candlestick +type KlineChart struct { + // Specify bar width. Absolute value (like 10) or percentage (like '20%', according to band width) can be used. Auto adapt by default. + BarWidth string + + // Specify bar min width. Absolute value (like 10) or percentage (like '20%', according to band width) can be used. Auto adapt by default. + BarMinWidth string + + // Specify bar max width. Absolute value (like 10) or percentage (like '20%', according to band width) can be used. Auto adapt by default. + BarMaxWidth string +} + +// LineData +// https://echarts.apache.org/en/option.html#series-line.data +type LineData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` + + // Symbol of single data. + // Icon types provided by ECharts includes 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none' + // It can be set to an image with 'image://url' , in which URL is the link to an image, or dataURI of an image. + Symbol string `json:"symbol,omitempty"` + + // single data symbol size. It can be set to single numbers like 10, or + // use an array to represent width and height. For example, [20, 10] means symbol width is 20, and height is10 + SymbolSize int `json:"symbolSize,omitempty"` + + // Index of x axis to combine with, which is useful for multiple x axes in one chart. + XAxisIndex int + + // Index of y axis to combine with, which is useful for multiple y axes in one chart. + YAxisIndex int +} + +// LiquidChart +// reference https://github.com/ecomfe/echarts-liquidfill +type LiquidChart struct { + // Shape of single data. + // Icon types provided by ECharts includes 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none' + // It can be set to an image with 'image://url' , in which URL is the link to an image, or dataURI of an image. + Shape string + + // Whether to show outline + IsShowOutline bool + + // Whether to stop animation + IsWaveAnimation bool +} + +// LiquidData +// reference https://github.com/ecomfe/echarts-liquidfill +type LiquidData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// MapData +// https://echarts.apache.org/en/option.html#series-map.data +type MapData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// ParallelData +// https://echarts.apache.org/en/option.html#series-parallel.data +type ParallelData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// PieChart is the option set for a pie chart. +// https://echarts.apache.org/en/option.html#series-pie +type PieChart struct { + // Whether to show as Nightingale chart, which distinguishes data through radius. There are 2 optional modes: + // * 'radius' Use central angle to show the percentage of data, radius to show data size. + // * 'area' All the sectors will share the same central angle, the data size is shown only through radiuses. + RoseType string + + // Center position of Pie chart, the first of which is the horizontal position, and the second is the vertical position. + // Percentage is supported. When set in percentage, the item is relative to the container width, + // and the second item to the height. + // + // Example: + // + // Set to absolute pixel values ->> center: [400, 300] + // Set to relative percent ->> center: ['50%', '50%'] + Center interface{} + + // Radius of Pie chart. Value can be: + // * number: Specify outside radius directly. + // * string: For example, '20%', means that the outside radius is 20% of the viewport + // size (the little one between width and height of the chart container). + // + // Array.: The first item specifies the inside radius, and the + // second item specifies the outside radius. Each item follows the definitions above. + Radius interface{} +} + +// PieData +// https://echarts.apache.org/en/option.html#series-pie.data +type PieData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` + + // Whether the data item is selected. + Selected bool `json:"selected,omitempty"` + + // The label configuration of a single sector. + Label *Label `json:"label,omitempty"` + + // Graphic style of , emphasis is the style when it is highlighted, like being hovered by mouse, or highlighted via legend connect. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // tooltip settings in this series data. + Tooltip *Tooltip `json:"tooltip,omitempty"` +} + +// RadarData +// https://echarts.apache.org/en/option.html#series-radar +type RadarData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +// SankeyLink represents relationship between two data nodes. +// https://echarts.apache.org/en/option.html#series-sankey.links +type SankeyLink struct { + // The name of source node of edge + Source interface{} `json:"source,omitempty"` + + // The name of target node of edge + Target interface{} `json:"target,omitempty"` + + // The value of edge, which decides the width of edge. + Value float32 `json:"value,omitempty"` +} + +// SankeyNode represents a data node. +// https://echarts.apache.org/en/option.html#series-sankey.nodes +type SankeyNode struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value string `json:"value,omitempty"` + + // Depth of the node within the chart + Depth *int `json:"depth,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` +} + +// ScatterChart is the option set for a scatter chart. +// https://echarts.apache.org/en/option.html#series-scatter +type ScatterChart struct { + // Index of x axis to combine with, which is useful for multiple x axes in one chart. + XAxisIndex int + + // Index of x axis to combine with, which is useful for multiple y axes in one chart. + YAxisIndex int + + // SymbolKeepAspect is whether to keep aspect for symbols in the form of path://. + SymbolKeepAspect bool +} + +// ScatterData +// https://echarts.apache.org/en/option.html#series-scatter.data +type ScatterData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` + + // Symbol + Symbol string `json:"symbol,omitempty"` + + // SymbolSize + SymbolSize int `json:"symbolSize,omitempty"` + + // SymbolRotate + SymbolRotate int `json:"symbolRotate,omitempty"` + + // Index of x axis to combine with, which is useful for multiple x axes in one chart. + XAxisIndex int `json:"xAxisIndex,omitempty"` + + // Index of y axis to combine with, which is useful for multiple y axes in one chart. + YAxisIndex int `json:"yAxisIndex,omitempty"` +} + +// ThemeRiverData +// https://echarts.apache.org/en/option.html#series-themeRiver +type ThemeRiverData struct { + // the time attribute of time and theme. + Date string `json:"date,omitempty"` + + // the value of an event or theme at a time point. + Value float64 `json:"value,omitempty"` + + // the name of an event or theme. + Name string `json:"name,omitempty"` +} + +// ToList converts the themeriver data to a list +func (trd ThemeRiverData) ToList() [3]interface{} { + return [3]interface{}{trd.Date, trd.Value, trd.Name} +} + +// WordCloudChart is the option set for a word cloud chart. +type WordCloudChart struct { + // Shape of WordCloud + // Optional: "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow" + Shape string + + // range of font size + SizeRange []float32 + + // range of font rotation angle + RotationRange []float32 +} + +// WordCloudData +type WordCloudData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` +} + +type Chart3DData struct { + // Name of the data item. + Name string `json:"name,omitempty"` + + // Value of the data item. + // []interface{}{1, 2, 3} + Value []interface{} `json:"value,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // The style setting of the text label in a single bar. + Label *Label `json:"label,omitempty"` +} + +type TreeChart struct { + // The layout of the tree, which can be orthogonal and radial. + // * 'orthogonal' refer to the horizontal and vertical direction. + // * 'radial' refers to the view that the root node as the center and each layer of nodes as the ring. + Layout string + + // The direction of the orthogonal layout in the tree diagram. + // * 'from left to right' or 'LR' + // * 'from right to left' or 'RL' + // * 'from top to bottom' or 'TB' + // * 'from bottom to top' or 'BT' + Orient string `json:"orient,omitempty"` + + // Whether to enable mouse zooming and translating. false by default. + // If either zooming or translating is wanted, it can be set to 'scale' or 'move'. + // Otherwise, set it to be true to enable both. + Roam bool `json:"roam"` + + // Subtree collapses and expands interaction, default true. + ExpandAndCollapse bool `json:"expandAndCollapse,omitempty"` + + // The initial level (depth) of the tree. The root node is the 0th layer, then the first layer, the second layer, ... , until the leaf node. + // This configuration item is primarily used in conjunction with collapsing and expansion interactions. + // The purpose is to prevent the nodes from obscuring each other. If set as -1 or null or undefined, all nodes are expanded. + InitialTreeDepth int `json:"initialTreeDepth,omitempty"` + + // The style setting of the text label in a single bar. + Label *Label `json:"label,omitempty"` + + // Leaf node special configuration, the leaf node and non-leaf node label location is different. + Leaves *TreeLeaves `json:"leaves,omitempty"` + + // Distance between tree component and the sides of the container. + // value can be instant pixel value like 20; + // It can also be a percentage value relative to container width like '20%'; + Left string `json:"left,omitempty"` + Right string `json:"right,omitempty"` + Top string `json:"top,omitempty"` + Bottom string `json:"bottom,omitempty"` + + // SymbolKeepAspect is whether to keep aspect for symbols in the form of path://. + SymbolKeepAspect bool +} + +type TreeData struct { + // Name of the data item. + Name string `json:"name,omitempty"` + + // Value of the data item. + Value int `json:"value,omitempty"` + + Children []*TreeData `json:"children,omitempty"` + + // Symbol of node of this category. + // Icon types provided by ECharts includes + // 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none' + // It can be set to an image with 'image://url' , in which URL is the link to an image, or dataURI of an image. + Symbol string `json:"symbol,omitempty"` + + // node of this category symbol size. It can be set to single numbers like 10, + // or use an array to represent width and height. For example, [20, 10] means symbol width is 20, and height is10. + SymbolSize interface{} `json:"symbolSize,omitempty"` + + // If set as `true`, the node is collapsed in the initialization. + Collapsed bool `json:"collapsed,omitempty"` + + // LineStyle settings in this series data. + LineStyle *LineStyle `json:"lineStyle,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` +} + +type TreeMapChart struct { + // Whether to enable animation. + Animation bool `json:"animation"` + + // leafDepth represents how many levels are shown at most. For example, when leafDepth is set to 1, only one level will be shown. + // leafDepth is null/undefined by default, which means that "drill down" is disabled. + LeafDepth int `json:"leafDeapth,omitempty"` + + // Roam describes whether to enable mouse zooming and translating. false by default. + Roam bool `json:"roam"` + + // Label decribes the style of the label in each node. + Label *Label `json:"label,omitempty"` + + // UpperLabel is used to specify whether show label when the treemap node has children. + UpperLabel *UpperLabel `json:"upperLabel,omitempty"` + + // ColorMappingBy specifies the rule according to which each node obtain color from color list. + ColorMappingBy string `json:"colorMappingBy,omitempty"` + + // Levels provide configration for each node level + Levels *[]TreeMapLevel `json:"levels,omitempty"` + + // Distance between treemap component and the sides of the container. + // value can be instant pixel value like 20; + // It can also be a percentage value relative to container width like '20%'; + Left string `json:"left,omitempty"` + Right string `json:"right,omitempty"` + Top string `json:"top,omitempty"` + Bottom string `json:"bottom,omitempty"` +} + +type TreeMapNode struct { + // Name of the tree node item. + Name string `json:"name"` + + // Value of the tree node item. + Value int `json:"value,omitempty"` + + Children []TreeMapNode `json:"children,omitempty"` +} + +// SunBurstData data +type SunBurstData struct { + // Name of data item. + Name string `json:"name,omitempty"` + // Value of data item. + Value float64 `json:"value,omitempty"` + // sub item of data item + Children []*SunBurstData `json:"children,omitempty"` +} + +// CustomChart is the options set for a custom chart. +// https://echarts.apache.org/en/option.html#series-custom +type CustomChart struct { + // Index of x axis to combine with, which is useful for multiple x axes in one chart. + XAxisIndex int + + // Index of y axis to combine with, which is useful for multiple y axes in one chart. + YAxisIndex int + + // Custom series requires developers to write a render logic by themselves in JavaScript. + // This render logic is called RenderItem. Use opts.FuncOpts to embed JavaScript. + RenderItem string +} + +// CustomData +// https://echarts.apache.org/en/option.html#series-custom.data +type CustomData struct { + // Name of data item. + Name string `json:"name,omitempty"` + + // Value of a single data item. + Value interface{} `json:"value,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Emphasis settings in this series data. + Emphasis *Emphasis `json:"emphasis,omitempty"` + + // Tooltip settings in this series data. + Tooltip *Tooltip `json:"tooltip,omitempty"` +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/opts/global.go b/vendor/github.com/go-echarts/go-echarts/v2/opts/global.go new file mode 100644 index 0000000000..6f49e1eb10 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/opts/global.go @@ -0,0 +1,1704 @@ +package opts + +import ( + "fmt" + "math/rand" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/go-echarts/go-echarts/v2/types" +) + +const ( + EchartsJS = "echarts.min.js" + // CompatibleEchartsJS The 3d charts and 3rd charts not support in v5+ echarts version, back to v4 (v4.9.0) + CompatibleEchartsJS = "echarts@4.min.js" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// Initialization contains options for the canvas. +type Initialization struct { + // HTML title + PageTitle string `default:"Awesome go-echarts"` + + // Width of canvas + Width string `default:"900px"` + + // Height of canvas + Height string `default:"500px"` + + // BackgroundColor of canvas + BackgroundColor string + + // Chart unique ID + ChartID string + + // Assets host + AssetsHost string `default:"https://go-echarts.github.io/go-echarts-assets/assets/"` + + // Theme of chart + Theme string `default:"white"` +} + +// Validate validates the initialization configurations. +func (opt *Initialization) Validate() { + SetDefaultValue(opt) + if opt.ChartID == "" { + opt.ChartID = generateUniqueID() + } +} + +// SetDefaultValue set default values for the struct field. +// inspired from: https://github.com/mcuadros/go-defaults +func SetDefaultValue(ptr interface{}) { + elem := reflect.ValueOf(ptr).Elem() + walkField(elem) +} + +func walkField(val reflect.Value) { + t := val.Type() + + for i := 0; i < t.NumField(); i++ { + f := val.Field(i) + if f.Kind() == reflect.Struct { + walkField(f) + } + + if defaultVal := t.Field(i).Tag.Get("default"); defaultVal != "" { + setField(val.Field(i), defaultVal) + } + } +} + +// setField handles String/Bool types only. +func setField(field reflect.Value, defaultVal string) { + switch field.Kind() { + case reflect.String: + if field.String() == "" { + field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) + } + case reflect.Bool: + if val, err := strconv.ParseBool(defaultVal); err == nil { + field.Set(reflect.ValueOf(val).Convert(field.Type())) + } + } +} + +const ( + chartIDSize = 12 +) + +// generate the unique ID for each chart. +func generateUniqueID() string { + var b [chartIDSize]byte + for i := range b { + b[i] = randByte() + } + return string(b[:]) +} + +func randByte() byte { + c := 65 // A + if rand.Intn(10) > 5 { + c = 97 // a + } + return byte(c + rand.Intn(26)) +} + +// Title is the option set for a title component. +// https://echarts.apache.org/en/option.html#title +type Title struct { + // The main title text, supporting \n for newlines. + Title string `json:"text,omitempty"` + + // TextStyle of the main title. + TitleStyle *TextStyle `json:"textStyle,omitempty"` + + // The hyper link of main title text. + Link string `json:"link,omitempty"` + + // Subtitle text, supporting \n for newlines. + Subtitle string `json:"subtext,omitempty"` + + // TextStyle of the sub title. + SubtitleStyle *TextStyle `json:"subtextStyle,omitempty"` + + // The hyper link of sub title text. + SubLink string `json:"sublink,omitempty"` + + // Open the hyper link of main title in specified tab. + // options: + // 'self' opening it in current tab + // 'blank' opening it in a new tab + Target string `json:"target,omitempty"` + + // Distance between title component and the top side of the container. + // top value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'top', 'middle', or 'bottom'. + // If the left value is set to be 'top', 'middle', or 'bottom', + // then the component will be aligned automatically based on position. + Top string `json:"top,omitempty"` + + // Distance between title component and the bottom side of the container. + // bottom value can be instant pixel value like 20; + // it can also be a percentage value relative to container width like '20%'. + // Adaptive by default. + Bottom string `json:"bottom,omitempty"` + + // Distance between title component and the left side of the container. + // left value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'left', 'center', or 'right'. + // If the left value is set to be 'left', 'center', or 'right', + // then the component will be aligned automatically based on position. + Left string `json:"left,omitempty"` + + // Distance between title component and the right side of the container. + // right value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + // Adaptive by default. + Right string `json:"right,omitempty"` +} + +// Legend is the option set for a legend component. +// Legend component shows symbol, color and name of different series. You can click legends to toggle displaying series in the chart. +// https://echarts.apache.org/en/option.html#legend +type Legend struct { + // Whether to show the Legend, default true. + // Once you set other options, need to manually set it to true + Show bool `json:"show" default:"true"` + + // Type of legend. Optional values: + // "plain": Simple legend. (default) + // "scroll": Scrollable legend. It helps when too many legend items needed to be shown. + Type string `json:"type"` + + // Distance between legend component and the left side of the container. + // left value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'left', 'center', or 'right'. + // If the left value is set to be 'left', 'center', or 'right', then the component + // will be aligned automatically based on position. + Left string `json:"left,omitempty"` + + // Distance between legend component and the top side of the container. + // top value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'top', 'middle', or 'bottom'. + // If the left value is set to be 'top', 'middle', or 'bottom', then the component + // will be aligned automatically based on position. + Top string `json:"top,omitempty"` + + // Distance between legend component and the right side of the container. + // right value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + // Adaptive by default. + Right string `json:"right,omitempty"` + + // Distance between legend component and the bottom side of the container. + // bottom value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + // Adaptive by default. + Bottom string `json:"bottom,omitempty"` + + // Data array of legend. An array item is usually a name representing string. + // set Data as []string{} if you wants to hide the legend. + Data interface{} `json:"data,omitempty"` + + // The layout orientation of legend. + // Options: 'horizontal', 'vertical' + Orient string `json:"orient,omitempty"` + + // Legend color when not selected. + InactiveColor string `json:"inactiveColor,omitempty"` + + // State table of selected legend. + // example: + // var selected = map[string]bool{} + // selected["series1"] = true + // selected["series2"] = false + Selected map[string]bool `json:"selected,omitempty"` + + // Selected mode of legend, which controls whether series can be toggled displaying by clicking legends. + // It is enabled by default, and you may set it to be false to disabled it. + // Besides, it can be set to 'single' or 'multiple', for single selection and multiple selection. + SelectedMode string `json:"selectedMode,omitempty"` + + // Legend space around content. The unit is px. + // Default values for each position are 5. + // And they can be set to different values with left, right, top, and bottom. + // Examples: + // 1. Set padding to be 5 + // padding: 5 + // 2. Set the top and bottom paddings to be 5, and left and right paddings to be 10 + // padding: [5, 10] + // 3. Set each of the four paddings separately + // padding: [ + // 5, // up + // 10, // right + // 5, // down + // 10, // left + // ] + Padding interface{} `json:"padding,omitempty"` + + // Image width of legend symbol. + ItemWidth int `json:"itemWidth,omitempty"` + + // Image height of legend symbol. + ItemHeight int `json:"itemHeight,omitempty"` + + // Legend X position, right/left/center + X string `json:"x,omitempty"` + + // Legend Y position, right/left/center + Y string `json:"y,omitempty"` + + // Width of legend component. Adaptive by default. + Width string `json:"width,omitempty"` + + // Height of legend component. Adaptive by default. + Height string `json:"height,omitempty"` + + // Legend marker and text aligning. + // By default, it automatically calculates from component location and orientation. + // When left value of this component is 'right' and orient is 'vertical', it would be aligned to 'right'. + // Options: auto/left/right + Align string `json:"align,omitempty"` + + // Legend text style. + TextStyle *TextStyle `json:"textStyle,omitempty"` +} + +// Tooltip is the option set for a tooltip component. +// https://echarts.apache.org/en/option.html#tooltip +type Tooltip struct { + // Whether to show the tooltip component, including tooltip floating layer and axisPointer. + Show bool `json:"show"` + + // Type of triggering. + // Options: + // * 'item': Triggered by data item, which is mainly used for charts that + // don't have a category axis like scatter charts or pie charts. + // * 'axis': Triggered by axes, which is mainly used for charts that have category axes, + // like bar charts or line charts. + // * 'none': Trigger nothing. + Trigger string `json:"trigger,omitempty"` + + // Conditions to trigger tooltip. Options: + // * 'mousemove': Trigger when mouse moves. + // * 'click': Trigger when mouse clicks. + // * 'mousemove|click': Trigger when mouse clicks and moves. + // * 'none': Do not triggered by 'mousemove' and 'click'. Tooltip can be triggered and hidden + // manually by calling action.tooltip.showTip and action.tooltip.hideTip. + // It can also be triggered by axisPointer.handle in this case. + TriggerOn string `json:"triggerOn,omitempty"` + + // Whether mouse is allowed to enter the floating layer of tooltip, whose default value is false. + // If you need to interact in the tooltip like with links or buttons, it can be set as true. + Enterable bool `json:"enterable,omitempty"` + + // The content formatter of tooltip's floating layer which supports string template and callback function. + // + // 1. String template + // The template variables are {a}, {b}, {c}, {d} and {e}, which stands for series name, + // data name and data value and ect. When trigger is set to be 'axis', there may be data from multiple series. + // In this time, series index can be refereed as {a0}, {a1}, or {a2}. + // {a}, {b}, {c}, {d} have different meanings for different series types: + // + // * Line (area) charts, bar (column) charts, K charts: {a} for series name, + // {b} for category name, {c} for data value, {d} for none; + // * Scatter (bubble) charts: {a} for series name, {b} for data name, {c} for data value, {d} for none; + // * Map: {a} for series name, {b} for area name, {c} for merging data, {d} for none; + // * Pie charts, gauge charts, funnel charts: {a} for series name, {b} for data item name, + // {c} for data value, {d} for percentage. + // + // 2. Callback function + // The format of callback function: + // (params: Object|Array, ticket: string, callback: (ticket: string, html: string)) => string + // The first parameter params is the data that the formatter needs. Its format is shown as follows: + // { + // componentType: 'series', + // // Series type + // seriesType: string, + // // Series index in option.series + // seriesIndex: number, + // // Series name + // seriesName: string, + // // Data name, or category name + // name: string, + // // Data index in input data array + // dataIndex: number, + // // Original data as input + // data: Object, + // // Value of data. In most series it is the same as data. + // // But in some series it is some part of the data (e.g., in map, radar) + // value: number|Array|Object, + // // encoding info of coordinate system + // // Key: coord, like ('x' 'y' 'radius' 'angle') + // // value: Must be an array, not null/undefined. Contain dimension indices, like: + // // { + // // x: [2] // values on dimension index 2 are mapped to x axis. + // // y: [0] // values on dimension index 0 are mapped to y axis. + // // } + // encode: Object, + // // dimension names list + // dimensionNames: Array, + // // data dimension index, for example 0 or 1 or 2 ... + // // Only work in `radar` series. + // dimensionIndex: number, + // // Color of data + // color: string, + // + // // the percentage of pie chart + // percent: number, + // } + Formatter string `json:"formatter,omitempty"` + + ValueFormatter string `json:"valueFormatter,omitempty"` + + // Configuration item for axisPointer + AxisPointer *AxisPointer `json:"axisPointer,omitempty"` +} + +// AxisPointer is the option set for an axisPointer component +// https://echarts.apache.org/en/option.html#axisPointer +type AxisPointer struct { + + // Indicator type. + // Options: + // - 'line' line indicator. + // - 'shadow' shadow crosshair indicator. + // - 'none' no indicator displayed. + // - 'cross' crosshair indicator, which is actually the shortcut of enable two axisPointers of two orthometric axes. + Type string `json:"type,omitempty"` + + // Whether snap to point automatically. The default value is auto determined. + // This feature usually makes sense in value axis and time axis, where tiny points can be seeked automatically. + Snap bool `json:"snap,omitempty"` + + Link []AxisPointerLink `json:"link,omitempty"` + + Axis string `json:"axis,omitempty"` + + Show bool `json:"show"` + + Label *Label `json:"label,omitempty"` +} + +type AxisPointerLink struct { + XAxisIndex []int `json:"xAxisIndex,omitempty"` + YAxisIndex []int `json:"yAxisIndex,omitempty"` + XAxisName string `json:"xAxisName,omitempty"` + YAxisName string `json:"yAxisName,omitempty"` +} + +// Brush is an area-selecting component, with which user can select part of data from a chart to display in detail, or do calculations with them. +// https://echarts.apache.org/en/option.html#brush +type Brush struct { + + //XAxisIndex Assigns which of the xAxisIndex can use brush selecting. + XAxisIndex interface{} `json:"xAxisIndex,omitempty"` + + //Brushlink is a mapping of dataIndex. So data of every series with brushLink should be guaranteed to correspond to the other. + Brushlink interface{} `json:"brushlink,omitempty"` + + //OutOfBrush Defines visual effects of items out of selection + OutOfBrush *BrushOutOfBrush `json:"outOfBrush,omitempty"` +} + +// BrushOutOfBrush +// https://echarts.apache.org/en/option.html#brush.outOfBrush +type BrushOutOfBrush struct { + ColorAlpha float32 `json:"colorAlpha,omitempty"` +} + +// Toolbox is the option set for a toolbox component. +// https://echarts.apache.org/en/option.html#toolbox +type Toolbox struct { + // Whether to show toolbox component. + Show bool `json:"show"` + + // The layout orientation of toolbox's icon. + // Options: 'horizontal','vertical' + Orient string `json:"orient,omitempty"` + + // Distance between toolbox component and the left side of the container. + // left value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'left', 'center', or 'right'. + // If the left value is set to be 'left', 'center', or 'right', then the component + // will be aligned automatically based on position. + Left string `json:"left,omitempty"` + + // Distance between toolbox component and the top side of the container. + // top value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'top', 'middle', or 'bottom'. + // If the left value is set to be 'top', 'middle', or 'bottom', then the component + // will be aligned automatically based on position. + Top string `json:"top,omitempty"` + + // Distance between toolbox component and the right side of the container. + // right value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + // Adaptive by default. + Right string `json:"right,omitempty"` + + // Distance between toolbox component and the bottom side of the container. + // bottom value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + // Adaptive by default. + Bottom string `json:"bottom,omitempty"` + + // The configuration item for each tool. + // Besides the tools we provide, user-defined toolbox is also supported. + Feature *ToolBoxFeature `json:"feature,omitempty"` +} + +// ToolBoxFeature is a feature component under toolbox. +// https://echarts.apache.org/en/option.html#toolbox +type ToolBoxFeature struct { + // Save as image tool + SaveAsImage *ToolBoxFeatureSaveAsImage `json:"saveAsImage,omitempty"` + + // Data brush + Brush *ToolBoxFeatureBrush `json:"brush"` + + // Data area zooming, which only supports rectangular coordinate by now. + DataZoom *ToolBoxFeatureDataZoom `json:"dataZoom,omitempty"` + + // Data view tool, which could display data in current chart and updates chart after being edited. + DataView *ToolBoxFeatureDataView `json:"dataView,omitempty"` + + // Restore configuration item. + Restore *ToolBoxFeatureRestore `json:"restore,omitempty"` +} + +// ToolBoxFeatureSaveAsImage is the option for saving chart as image. +// https://echarts.apache.org/en/option.html#toolbox.feature.saveAsImage +type ToolBoxFeatureSaveAsImage struct { + // Whether to show the tool. + Show bool `json:"show"` + + // toolbox.feature.saveAsImage. type = 'png' + // File suffix of the image saved. + // If the renderer is set to be 'canvas' when chart initialized (default), t + // hen 'png' (default) and 'jpeg' are supported. + // If the renderer is set to be 'svg' when when chart initialized, then only 'svg' is supported + // for type ('svg' type is supported since v4.8.0). + Type string `json:"type,omitempty" default:"png"` + + // Name to save the image, whose default value is title.text. + Name string `json:"name,omitempty"` + + // Title for the tool. + Title string `json:"title,omitempty"` +} + +// ToolBoxFeatureBrush brush-selecting icon. +// https://echarts.apache.org/en/option.html#toolbox.feature.brush +type ToolBoxFeatureBrush struct { + + //Icons used, whose values are: + // 'rect': Enabling selecting with rectangle area. + // 'polygon': Enabling selecting with any shape. + // 'lineX': Enabling horizontal selecting. + // 'lineY': Enabling vertical selecting. + // 'keep': Switching between single selecting and multiple selecting. The latter one can select multiple areas, while the former one cancels previous selection. + // 'clear': Clearing all selection. + Type []string `json:"type,omitempty"` +} + +// ToolBoxFeatureDataZoom +// https://echarts.apache.org/en/option.html#toolbox.feature.dataZoom +type ToolBoxFeatureDataZoom struct { + // Whether to show the tool. + Show bool `json:"show"` + + //Defines which yAxis should be controlled. By default, it controls all y axes. + //If it is set to be false, then no y axis is controlled. + //If it is set to be then it controls axis with axisIndex of 3. + //If it is set to be [0, 3], it controls the x-axes with axisIndex of 0 and 3. + YAxisIndex interface{} `json:"yAxisIndex,omitempty"` + + // Restored and zoomed title text. + // m["zoom"] = "area zooming" + // m["back"] = "restore area zooming" + Title map[string]string `json:"title"` +} + +// ToolBoxFeatureDataView +// https://echarts.apache.org/en/option.html#toolbox.feature.dataView +type ToolBoxFeatureDataView struct { + // Whether to show the tool. + Show bool `json:"show"` + + // title for the tool. + Title string `json:"title,omitempty"` + + // There are 3 names in data view + // you could set them like this: []string["data view", "turn off", "refresh"] + Lang []string `json:"lang"` + + // Background color of the floating layer in data view. + BackgroundColor string `json:"backgroundColor"` +} + +// ToolBoxFeatureRestore +// https://echarts.apache.org/en/option.html#toolbox.feature.restore +type ToolBoxFeatureRestore struct { + // Whether to show the tool. + Show bool `json:"show"` + + // title for the tool. + Title string `json:"title,omitempty"` +} + +// AxisLabel settings related to axis label. +// https://echarts.apache.org/en/option.html#xAxis.axisLabel +type AxisLabel struct { + // Set this to false to prevent the axis label from appearing. + Show bool `json:"show"` + + // Interval of Axis label, which is available in category axis. + // It uses a strategy that labels do not overlap by default. + // You may set it to be 0 to display all labels compulsively. + // If it is set to be 1, it means that labels are shown once after one label. + // And if it is set to be 2, it means labels are shown once after two labels, and so on. + Interval string `json:"interval,omitempty"` + + // Set this to true so the axis labels face the inside direction. + Inside bool `json:"inside,omitempty"` + + // Rotation degree of axis label, which is especially useful when there is no enough space for category axis. + // Rotation degree is from -90 to 90. + Rotate float64 `json:"rotate,omitempty"` + + // The margin between the axis label and the axis line. + Margin float64 `json:"margin,omitempty"` + + // Formatter of axis label, which supports string template and callback function. + // + // Example: + // + // Use string template; template variable is the default label of axis {value} + // formatter: '{value} kg' + // + // Use callback function; function parameters are axis index + // + // + // formatter: function (value, index) { + // // Formatted to be month/day; display year only in the first label + // var date = new Date(value); + // var texts = [(date.getMonth() + 1), date.getDate()]; + // if (idx === 0) { + // texts.unshift(date.getYear()); + // } + // return texts.join('/'); + // } + Formatter string `json:"formatter,omitempty"` + + ShowMinLabel bool `json:"showMinLabel"` + ShowMaxLabel bool `json:"showMaxLabel"` + + // Color of axis label is set to be axisLine.lineStyle.color by default. Callback function is supported, + // in the following format: + // + // (val: string) => Color + // Parameter is the text of label, and return value is the color. See the following example: + // + // textStyle: { + // color: function (value, index) { + // return value >= 0 ? 'green' : 'red'; + // } + // } + Color string `json:"color,omitempty"` + + // axis label font style + FontStyle string `json:"fontStyle,omitempty"` + // axis label font weight + FontWeight string `json:"fontWeight,omitempty"` + // axis label font family + FontFamily string `json:"fontFamily,omitempty"` + // axis label font size + FontSize string `json:"fontSize,omitempty"` + // Horizontal alignment of axis label + Align string `json:"align,omitempty"` + // Vertical alignment of axis label + VerticalAlign string `json:"verticalAlign,omitempty"` + // Line height of the axis label + LineHeight string `json:"lineHeight,omitempty"` + + BackgroundColor string `json:"backgroundColor,omitempty"` +} + +type AxisTick struct { + // Set this to false to prevent the axis tick from showing. + Show bool `json:"show"` + + // interval of axisTick, which is available in category axis. is set to be the same as axisLabel.interval by default. + // It uses a strategy that labels do not overlap by default. + // You may set it to be 0 to display all labels compulsively. + // If it is set to be 1, it means that labels are shown once after one label. And if it is set to be 2, it means labels are shown once after two labels, and so on. + // On the other hand, you can control by callback function, whose format is shown below: + // (index:number, value: string) => boolean + // The first parameter is index of category, and the second parameter is the name of category. The return values decides whether to display label. + Interval string `json:"interval,omitempty"` + + // Align axis tick with label, which is available only when boundaryGap is set to be true in category axis. + AlignWithLabel bool `json:"alignWithLabel,omitempty"` +} + +// AxisLine controls settings related to axis line. +// https://echarts.apache.org/en/option.html#yAxis.axisLine +type AxisLine struct { + // Set this to false to prevent the axis line from showing. + Show bool `json:"show"` + + // Specifies whether X or Y axis lies on the other's origin position, where value is 0 on axis. + // Valid only if the other axis is of value type, and contains 0 value. + OnZero bool `json:"onZero,omitempty"` + + // When multiple axes exists, this option can be used to specify which axis can be "onZero" to. + OnZeroAxisIndex int `json:"onZeroAxisIndex,omitempty"` + + // Symbol of the two ends of the axis. It could be a string, representing the same symbol for two ends; or an array + // with two string elements, representing the two ends separately. It's set to be 'none' by default, meaning no + //arrow for either end. If it is set to be 'arrow', there shall be two arrows. If there should only one arrow + //at the end, it should set to be ['none', 'arrow']. + Symbol string `json:"symbol,omitempty"` + + // Size of the arrows at two ends. The first is the width perpendicular to the axis, the next is the width parallel to the axis. + SymbolSize []float64 `json:"symbolSize,omitempty"` + + // Arrow offset of axis. If is array, the first number is the offset of the arrow at the beginning, and the second + // number is the offset of the arrow at the end. If is number, it means the arrows have the same offset. + SymbolOffset []float64 `json:"symbolOffset,omitempty"` + + LineStyle *LineStyle `json:"lineStyle,omitempty"` +} + +// XAxis is the option set for X axis. +// https://echarts.apache.org/en/option.html#xAxis +type XAxis struct { + // Name of axis. + Name string `json:"name,omitempty"` + + // Location of axis name. + // + // Options: + // 'start' + // 'middle' or 'center' + // 'end' + NameLocation string `json:"nameLocation,omitempty"` + + // Gap between axis name and axis line. + NameGap int `json:"nameGap,omitempty"` + + // Type of axis. + // Option: + // * 'value': Numerical axis, suitable for continuous data. + // * 'category': Category axis, suitable for discrete category data. + // Category data can be auto retrieved from series.data or dataset.source, + // or can be specified via xAxis.data. + // * 'time' Time axis, suitable for continuous time series data. As compared to value axis, + // it has a better formatting for time and a different tick calculation method. For example, + // it decides to use month, week, day or hour for tick based on the range of span. + // * 'log' Log axis, suitable for log data. + Type string `json:"type,omitempty"` + + // Set this to false to prevent the axis from showing. + Show bool `json:"show,omitempty"` + + // Category data, available in type: 'category' axis. + Data interface{} `json:"data,omitempty"` + + // Number of segments that the axis is split into. Note that this number serves only as a + // recommendation, and the true segments may be adjusted based on readability. + // This is unavailable for category axis. + SplitNumber int `json:"splitNumber,omitempty"` + + // It is available only in numerical axis, i.e., type: 'value'. + // It specifies whether not to contain zero position of axis compulsively. + // When it is set to be true, the axis may not contain zero position, + // which is useful in the scatter chart for both value axes. + // This configuration item is unavailable when the min and max are set. + Scale bool `json:"scale,omitempty"` + + // The minimum value of axis. + // It can be set to a special value 'dataMin' so that the minimum value on this axis is set to be the minimum label. + // It will be automatically computed to make sure axis tick is equally distributed when not set. + Min interface{} `json:"min,omitempty"` + + // The maximum value of axis. + // It can be set to a special value 'dataMax' so that the minimum value on this axis is set to be the maximum label. + // It will be automatically computed to make sure axis tick is equally distributed when not set. + Max interface{} `json:"max,omitempty"` + + // Minimum gap between split lines. For 'time' axis, MinInterval is in unit of milliseconds. + MinInterval float64 `json:"minInterval,omitempty"` + + // Maximum gap between split lines. For 'time' axis, MaxInterval is in unit of milliseconds. + MaxInterval float64 `json:"maxInterval,omitempty"` + + // The index of grid which the x axis belongs to. Defaults to be in the first grid. + // default 0 + GridIndex int `json:"gridIndex,omitempty"` + + // Split area of X axis in grid area. + SplitArea *SplitArea `json:"splitArea,omitempty"` + + // Split line of X axis in grid area. + SplitLine *SplitLine `json:"splitLine,omitempty"` + + // Settings related to axis label. + AxisLabel *AxisLabel `json:"axisLabel,omitempty"` + + // Settings related to axis tick. + AxisTick *AxisTick `json:"axisTick,omitempty"` + + // Settings related to axis pointer. + AxisPointer *AxisPointer `json:"axisPointer,omitempty"` +} + +// YAxis is the option set for Y axis. +// https://echarts.apache.org/en/option.html#yAxis +type YAxis struct { + // Name of axis. + Name string `json:"name,omitempty"` + + // Location of axis name. + // + // Options: + // 'start' + // 'middle' or 'center' + // 'end' + NameLocation string `json:"nameLocation,omitempty"` + + // Gap between axis name and axis line. + NameGap int `json:"nameGap,omitempty"` + + // Type of axis. + // Option: + // * 'value': Numerical axis, suitable for continuous data. + // * 'category': Category axis, suitable for discrete category data. + // Category data can be auto retrieved from series.data or dataset.source, + // or can be specified via xAxis.data. + // * 'time' Time axis, suitable for continuous time series data. As compared to value axis, + // it has a better formatting for time and a different tick calculation method. For example, + // it decides to use month, week, day or hour for tick based on the range of span. + // * 'log' Log axis, suitable for log data. + Type string `json:"type,omitempty"` + + // Set this to false to prevent the axis from showing. + Show bool `json:"show,omitempty"` + + // Category data, available in type: 'category' axis. + Data interface{} `json:"data,omitempty"` + + // Number of segments that the axis is split into. Note that this number serves only as a + // recommendation, and the true segments may be adjusted based on readability. + // This is unavailable for category axis. + SplitNumber int `json:"splitNumber,omitempty"` + + // It is available only in numerical axis, i.e., type: 'value'. + // It specifies whether not to contain zero position of axis compulsively. + // When it is set to be true, the axis may not contain zero position, + // which is useful in the scatter chart for both value axes. + // This configuration item is unavailable when the min and max are set. + Scale bool `json:"scale,omitempty"` + + // The minimum value of axis. + // It can be set to a special value 'dataMin' so that the minimum value on this axis is set to be the minimum label. + // It will be automatically computed to make sure axis tick is equally distributed when not set. + Min interface{} `json:"min,omitempty"` + + // The maximum value of axis. + // It can be set to a special value 'dataMax' so that the minimum value on this axis is set to be the maximum label. + // It will be automatically computed to make sure axis tick is equally distributed when not set. + Max interface{} `json:"max,omitempty"` + + // The index of grid which the Y axis belongs to. Defaults to be in the first grid. + // default 0 + GridIndex int `json:"gridIndex,omitempty"` + + // Split area of Y axis in grid area. + SplitArea *SplitArea `json:"splitArea,omitempty"` + + // Split line of Y axis in grid area. + SplitLine *SplitLine `json:"splitLine,omitempty"` + + // Settings related to axis label. + AxisLabel *AxisLabel `json:"axisLabel,omitempty"` + + // Settings related to axis line. + AxisLine *AxisLine `json:"axisLine,omitempty"` + + // Settings related to axis pointer. + AxisPointer *AxisPointer `json:"axisPointer,omitempty"` +} + +// TextStyle is the option set for a text style component. +type TextStyle struct { + // Font color + Color string `json:"color,omitempty"` + + // Font style + // Options: 'normal', 'italic', 'oblique' + FontStyle string `json:"fontStyle,omitempty"` + + // Font size + FontSize int `json:"fontSize,omitempty"` + + // Font family the main title font family. + // Options: "sans-serif", 'serif' , 'monospace', 'Arial', 'Courier New', 'Microsoft YaHei', ... + FontFamily string `json:"fontFamily,omitempty"` + + // Padding title space around content. + // The unit is px. Default values for each position are 5. + // And they can be set to different values with left, right, top, and bottom. + Padding interface{} `json:"padding,omitempty"` + + // compatible for WordCloud + Normal *TextStyle `json:"normal,omitempty"` +} + +// SplitArea is the option set for a split area. +type SplitArea struct { + // Set this to true to show the splitArea. + Show bool `json:"show"` + + // Split area style. + AreaStyle *AreaStyle `json:"areaStyle,omitempty"` +} + +// SplitLine is the option set for a split line. +type SplitLine struct { + // Set this to true to show the splitLine. + Show bool `json:"show"` + + // Split line style. + LineStyle *LineStyle `json:"lineStyle,omitempty"` + + // Align split line with label, which is available only when boundaryGap is set to be true in category axis. + AlignWithLabel bool `json:"alignWithLabel,omitempty"` +} + +// Used to customize how to slice continuous data, and some specific view style for some pieces. +type Piece struct { + Min float32 `json:"min,omitempty"` + + Max float32 `json:"max,omitempty"` + + Lt float32 `json:"lt,omitempty"` + + Lte float32 `json:"lte,omitempty"` + + Gt float32 `json:"gt,omitempty"` + + Gte float32 `json:"gte,omitempty"` + + // Symbol color + Color string `json:"color,omitempty"` +} + +// VisualMap is a type of component for visual encoding, which maps the data to visual channels. +// https://echarts.apache.org/en/option.html#visualMap +type VisualMap struct { + // Mapping type. + // Options: "continuous", "piecewise" + Type string `json:"type,omitempty" default:"continuous"` + + // Whether show handles, which can be dragged to adjust "selected range". + Calculable bool `json:"calculable"` + + // Specify the min dataValue for the visualMap component. + // [visualMap.min, visualMax.max] make up the domain of visual mapping. + Min float32 `json:"min,omitempty"` + + // Specify the max dataValue for the visualMap component. + // [visualMap.min, visualMax.max] make up the domain of visual mapping. + Max float32 `json:"max,omitempty"` + + // Specify selected range, that is, the dataValue corresponding to the two handles. + Range []float32 `json:"range,omitempty"` + + // The label text on both ends, such as ['High', 'Low']. + Text []string `json:"text,omitempty"` + + // Specify which dimension should be used to fetch dataValue from series.data, and then map them to visual channel. + Dimension string `json:"dimension,omitempty"` + + // Define visual channels that will mapped from dataValues that are in selected range. + InRange *VisualMapInRange `json:"inRange,omitempty"` + + // Used to customize how to slice continuous data, and some specific view style for some pieces. + Pieces []Piece `json:"pieces,omitempty"` + + // Whether to show visualMap-piecewise component. If set as false, + // visualMap-piecewise component will not show, + // but it can still perform visual mapping from dataValue to visual channel in chart. + Show bool `json:"show"` + + // Distance between visualMap component and the left side of the container. + // left value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'left', 'center', or 'right'. + // If the left value is set to be 'left', 'center', or 'right', + // then the component will be aligned automatically based on position. + Left string `json:"left,omitempty"` + + // Distance between visualMap component and the right side of the container. + // right value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + Right string `json:"right,omitempty"` + + // Distance between visualMap component and the top side of the container. + // top value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'top', 'middle', or 'bottom'. + // If the left value is set to be 'top', 'middle', or 'bottom', + // then the component will be aligned automatically based on position. + Top string `json:"top,omitempty"` + + // Distance between visualMap component and the bottom side of the container. + // bottom value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + Bottom string `json:"bottom,omitempty"` + + // The layout orientation of legend. + // Options: 'horizontal', 'vertical' + Orient string `json:"orient,omitempty"` + + // Text style + TextStyle *TextStyle `json:"textStyle,omitempty"` +} + +// VisualMapInRange is a visual map instance in a range. +type VisualMapInRange struct { + // Color + Color []string `json:"color,omitempty"` + + // Symbol type at the two ends of the mark line. It can be an array for two ends, or assigned separately. + // Options: "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none" + Symbol string `json:"symbol,omitempty"` + + // Symbol size. + SymbolSize float32 `json:"symbolSize,omitempty"` +} + +// DataZoom is the option set for a zoom component. +// dataZoom component is used for zooming a specific area, which enables user to +// investigate data in detail, or get an overview of the data, or get rid of outlier points. +// https://echarts.apache.org/en/option.html#dataZoom +type DataZoom struct { + // Data zoom component of inside type, Options: "inside", "slider" + Type string `json:"type" default:"inside"` + + // The start percentage of the window out of the data extent, in the range of 0 ~ 100. + // default 0 + Start float32 `json:"start,omitempty"` + + // The end percentage of the window out of the data extent, in the range of 0 ~ 100. + // default 100 + End float32 `json:"end,omitempty"` + + // Specify the frame rate of views refreshing, with unit millisecond (ms). + // If animation set as true and animationDurationUpdate set as bigger than 0, + // you can keep throttle as the default value 100 (or set it as a value bigger than 0), + // otherwise it might be not smooth when dragging. + // If animation set as false or animationDurationUpdate set as 0, and data size is not very large, + // and it seems to be not smooth when dragging, you can set throttle as 0 to improve that. + Throttle float32 `json:"throttle,omitempty"` + + // Specify which xAxis is/are controlled by the dataZoom-inside when Cartesian coordinate system is used. + // By default the first xAxis that parallel to dataZoom are controlled when dataZoom-inside. + // Orient is set as 'horizontal'. But it is recommended to specify it explicitly but not use default value. + // If it is set as a single number, one axis is controlled, while if it is set as an Array , + // multiple axes are controlled. + XAxisIndex interface{} `json:"xAxisIndex,omitempty"` + + // Specify which yAxis is/are controlled by the dataZoom-inside when Cartesian coordinate system is used. + // By default the first yAxis that parallel to dataZoom are controlled when dataZoom-inside. + // Orient is set as 'vertical'. But it is recommended to specify it explicitly but not use default value. + // If it is set as a single number, one axis is controlled, while if it is set as an Array , + // multiple axes are controlled. + YAxisIndex interface{} `json:"yAxisIndex,omitempty"` + + // LabelFormatter is the formatter tool for the label. + // + // If it is a string, it will be a template. For instance, aaaa{value}bbbb, where {value} will be replaced by the value of actual data value. + // It can also be a callback function. For example: + // + // /** @param {*} value If axis.type is 'category', `value` is the index of axis.data. + // * else `value` is current value. + // * @param {string} valueStr Inner formatted string. + // * @return {string} Returns the label formatted. + // labelFormatter: function (value, valueStr) { + // return 'aaa' + value + 'bbb'; + // } + LabelFormatter string `json:"labelFormatter,omitempty"` +} + +// SingleAxis is the option set for single axis. +// https://echarts.apache.org/en/option.html#singleAxis +type SingleAxis struct { + // The minimum value of axis. + // It can be set to a special value 'dataMin' so that the minimum value on this axis is set to be the minimum label. + // It will be automatically computed to make sure axis tick is equally distributed when not set. + Min interface{} `json:"min,omitempty"` + + // The maximum value of axis. + // It can be set to a special value 'dataMax' so that the minimum value on this axis is set to be the maximum label. + // It will be automatically computed to make sure axis tick is equally distributed when not set. + Max interface{} `json:"max,omitempty"` + + // Type of axis. + // Option: + // * 'value': Numerical axis, suitable for continuous data. + // * 'category': Category axis, suitable for discrete category data. + // Category data can be auto retrieved from series.data or dataset.source, + // or can be specified via xAxis.data. + // * 'time' Time axis, suitable for continuous time series data. As compared to value axis, + // it has a better formatting for time and a different tick calculation method. For example, + // it decides to use month, week, day or hour for tick based on the range of span. + // * 'log' Log axis, suitable for log data. + Type string `json:"type,omitempty"` + + // Distance between grid component and the left side of the container. + // left value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'left', 'center', or 'right'. + // If the left value is set to be 'left', 'center', or 'right', + // then the component will be aligned automatically based on position. + Left string `json:"left,omitempty"` + + // Distance between grid component and the right side of the container. + // right value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + Right string `json:"right,omitempty"` + + // Distance between grid component and the top side of the container. + // top value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'; and it can also be 'top', 'middle', or 'bottom'. + // If the left value is set to be 'top', 'middle', or 'bottom', + // then the component will be aligned automatically based on position. + Top string `json:"top,omitempty"` + + // Distance between grid component and the bottom side of the container. + // bottom value can be instant pixel value like 20; it can also be a percentage + // value relative to container width like '20%'. + Bottom string `json:"bottom,omitempty"` +} + +// Indicator is the option set for a radar chart. +type Indicator struct { + // Indicator name + Name string `json:"name,omitempty"` + + // The maximum value of indicator. It is an optional configuration, but we recommend to set it manually. + Max float32 `json:"max,omitempty"` + + // The minimum value of indicator. It it an optional configuration, with default value of 0. + Min float32 `json:"min,omitempty"` + + // Specify a color the the indicator. + Color string `json:"color,omitempty"` +} + +// RadarComponent is the option set for a radar component. +// https://echarts.apache.org/en/option.html#radar +type RadarComponent struct { + // Indicator of radar chart, which is used to assign multiple variables(dimensions) in radar chart. + Indicator []*Indicator `json:"indicator,omitempty"` + + // Radar render type, in which 'polygon' and 'circle' are supported. + Shape string `json:"shape,omitempty"` + + // Segments of indicator axis. + // default 5 + SplitNumber int `json:"splitNumber,omitempty"` + + // Center position of , the first of which is the horizontal position, and the second is the vertical position. + // Percentage is supported. When set in percentage, the item is relative to the container width and height. + Center interface{} `json:"center,omitempty"` + + // Split area of axis in grid area. + SplitArea *SplitArea `json:"splitArea,omitempty"` + + // Split line of axis in grid area. + SplitLine *SplitLine `json:"splitLine,omitempty"` +} + +// GeoComponent is the option set for geo component. +// https://echarts.apache.org/en/option.html#geo +type GeoComponent struct { + // Map charts. + Map string `json:"map,omitempty"` + + // Graphic style of Map Area Border, emphasis is the style when it is highlighted, + // like being hovered by mouse, or highlighted via legend connect. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Set this to true, to prevent interaction with the axis. + Silent bool `json:"silent,omitempty"` +} + +// ParallelComponent is the option set for parallel component. +type ParallelComponent struct { + // Distance between parallel component and the left side of the container. + // Left value can be instant pixel value like 20. + // It can also be a percentage value relative to container width like '20%'; + // and it can also be 'left', 'center', or 'right'. + // If the left value is set to be 'left', 'center', or 'right', + // then the component will be aligned automatically based on position. + Left string `json:"left,omitempty"` + + // Distance between parallel component and the top side of the container. + // Top value can be instant pixel value like 20. + // It can also be a percentage value relative to container width like '20%'. + // and it can also be 'top', 'middle', or 'bottom'. + // If the left value is set to be 'top', 'middle', or 'bottom', + // then the component will be aligned automatically based on position. + Top string `json:"top,omitempty"` + + // Distance between parallel component and the right side of the container. + // Right value can be instant pixel value like 20. + // It can also be a percentage value relative to container width like '20%'. + Right string `json:"right,omitempty"` + + // Distance between parallel component and the bottom side of the container. + // Bottom value can be instant pixel value like 20. + // It can also be a percentage value relative to container width like '20%'. + Bottom string `json:"bottom,omitempty"` +} + +// ParallelAxis is the option set for a parallel axis. +type ParallelAxis struct { + // Dimension index of coordinate axis. + Dim int `json:"dim,omitempty"` + + // Name of axis. + Name string `json:"name,omitempty"` + + // The maximum value of axis. + // It can be set to a special value 'dataMax' so that the minimum value on this axis is set to be the maximum label. + // It will be automatically computed to make sure axis tick is equally distributed when not set. + // In category axis, it can also be set as the ordinal number. + Max interface{} `json:"max,omitempty"` + + // Compulsively set segmentation interval for axis. + Inverse bool `json:"inverse,omitempty"` + + // Location of axis name. Options: "start", "middle", "center", "end" + // default "end" + NameLocation string `json:"nameLocation,omitempty"` + + // Type of axis. + // Options: + // "value":Numerical axis, suitable for continuous data. + // "category" Category axis, suitable for discrete category data. Category data can be auto retrieved from series. + // "log" Log axis, suitable for log data. + Type string `json:"type,omitempty"` + + // Category data,works on (type: "category"). + Data interface{} `json:"data,omitempty"` +} + +// CalendarLabel is the option set for a calendar label: DayLabel, MonthLabel, YearLabel. +type CalendarLabel struct { + // Whether to show the label. + Show bool `json:"show"` + + // The margin between the month label and the axis line. + Margin float64 `json:"margin,omitempty"` + + // Position of year. + // Default: when orient is set as horizontal, position is left when orient is set as vertical, position is top. + // Options: 'left', 'right', 'top', 'bottom' + Position string `json:"position,omitempty"` + + // Text color. + Color string `json:"color,omitempty"` + + // Font style. + // Options: 'normal', 'italic', 'oblique' + FontStyle string `json:"fontStyle,omitempty"` + + // Font weight. + // Options: 'normal', 'bold', 'bolder', 'lighter', 100 | 200 | 300 | 400... + FontWeight string `json:"fontWeight,omitempty"` + + // Font family. + FontFamily string `json:"fontFamily,omitempty"` + + // Font size. + FontSize int `json:"fontSize,omitempty"` + + // Horizontal alignment of text, automatic by default. + // Options: 'left', 'center', 'right' + Align string `json:"align,omitempty"` + + // Vertical alignment of text, automatic by default. + // Options: 'top', 'middle', 'bottom' + VerticalAlign string `json:"verticalAlign,omitempty"` + + // Line height of text. + LineHeight int `json:"lineHeight,omitempty"` + + // Background color of label, which is transparent by default. + BackgroundColor string `json:"backgroundColor,omitempty"` + + // Border color of label. + BorderColor string `json:"borderColor,omitempty"` + + // Border width of label. + BorderWidth int `json:"borderWidth,omitempty"` + + // Border radius of label. + BorderRadius int `json:"borderRadius,omitempty"` + + // Border type of label. + // Options: 'solid', 'dashed', 'dotted' + BorderType string `json:"borderType,omitempty"` + + // Border dash offset of label. + BorderDashOffset int `json:"borderDashOffset,omitempty"` + + // Padding + Padding int `json:"padding,omitempty"` + + // Shadow blur of text block. + ShadowBlur int `json:"shadowBlur,omitempty"` + + // Shadow color of text block. + ShadowColor string `json:"shadowColor,omitempty"` + + // Shadow X offset of text block. + ShadowOffsetX int `json:"shadowOffsetX,omitempty"` + + // Shadow Y offset of text block. + ShadowOffsetY int `json:"shadowOffsetY,omitempty"` + + // Width + Width int `json:"width,omitempty"` + + // Height + Height int `json:"height,omitempty"` + + // Text border color. + TextBorderColor string `json:"textBorderColor,omitempty"` + + // Text border width. + TextBorderWidth int `json:"textBorderWidth,omitempty"` + + // Text border type + // Options: 'solid', 'dashed', 'dotted' + TextBorderType string `json:"textBorderType,omitempty"` + + // Text border dash offset. + TextBorderDashOffset int `json:"textBorderDashOffset,omitempty"` + + // Text shadow color. + TextShadowColor string `json:"textShadowColor,omitempty"` + + // Text shadow blur. + TextShadowBlur int `json:"textShadowBlur,omitempty"` + + // Text shadow X offset. + TextShadowOffsetX int `json:"textShadowOffsetX,omitempty"` + + // Text shadow Y offset. + TextShadowOffsetY int `json:"textShadowOffsetY,omitempty"` + + // Overflow + Overflow string `json:"overflow,omitempty"` + + // Ellipsis + Ellipsis bool `json:"ellipsis,omitempty"` + + // Silent + Silent bool `json:"silent,omitempty"` +} + +// Calendar is the option set for a calendar component. +// This works with the calendar coordinate system, and it is a heatmap calendar. +type Calendar struct { + ID string `json:"id,omitempty"` + Zlevel int `json:"zlevel,omitempty"` + Z int `json:"z,omitempty"` + // Distance between grid component and the left side of the container. + Left string `json:"left,omitempty"` + + // Distance between grid component and the right side of the container. + Right string `json:"right,omitempty"` + + // Distance between grid component and the top side of the container. + Top string `json:"top,omitempty"` + + // Distance between grid component and the bottom side of the container. + Bottom string `json:"bottom,omitempty"` + + // Width of grid component. + Width string `json:"width,omitempty"` + + // Height of grid component. Adaptive by default. + Height string `json:"height,omitempty"` + + // Required, range of Calendar coordinates, support multiple formats. + Range []string `json:"range,omitempty"` + + // The size of each rect of calendar coordinates. + CellSize string `json:"cellSize,omitempty"` + + // The layout orientation of legend. + // Options: 'horizontal', 'vertical' + Orient string `json:"orient,omitempty"` + + // Split line of X axis in grid area. + SplitLine *SplitLine `json:"splitLine,omitempty"` + + // Graphic style of Map Area Border, emphasis is the style when it is highlighted, + // like being hovered by mouse, or highlighted via legend connect. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Day Label + DayLabel *CalendarLabel `json:"dayLabel,omitempty"` + + // Month Label + MonthLabel *CalendarLabel `json:"monthLabel,omitempty"` + + // Year Label + YearLabel *CalendarLabel `json:"yearLabel,omitempty"` + + // Whether to ignore mouse events. Default value is false, for triggering and responding to mouse events. + Silent bool `json:"silent,omitempty"` +} + +// Polar Bar options +type Polar struct { + ID string `json:"id,omitempty"` + Zlevel int `json:"zlevel,omitempty"` + Z int `json:"z,omitempty"` + Center [2]string `json:"center,omitempty"` + Radius [2]string `json:"radius,omitempty"` + Tooltip Tooltip `json:"tooltip,omitempty"` +} + +type PolarAxisBase struct { + ID string `json:"id,omitempty"` + PolarIndex int `json:"polarIndex,omitempty"` + StartAngle float64 `json:"startAngle,omitempty"` + Type string `json:"type,omitempty"` + BoundaryGap bool `json:"boundaryGap,omitempty"` + Min float64 `json:"min,omitempty"` + Max float64 `json:"max,omitempty"` + Scale bool `json:"scale,omitempty"` + SplitNumber int `json:"splitNumber,omitempty"` + MinInterval float64 `json:"minInterval,omitempty"` + MaxInterval float64 `json:"maxInterval,omitempty"` + Interval float64 `json:"interval,omitempty"` + LogBase float64 `json:"logBase,omitempty"` + Silent bool `json:"silent,omitempty"` + TriggerEvent bool `json:"triggerEvent,omitempty"` +} + +type AngleAxis struct { + PolarAxisBase + Clockwise bool `json:"clockwise,omitempty"` +} + +type RadiusAxis struct { + PolarAxisBase + Name string `json:"name,omitempty"` + NameLocation string `json:"nameLocation,omitempty"` + NameTextStyle TextStyle `json:"nameTextStyle,omitempty"` + NameGap float64 `json:"nameGap,omitempty"` + NameRadius float64 `json:"nameRotate,omitempty"` + Inverse bool `json:"inverse,omitempty"` +} + +var newlineTabPat = regexp.MustCompile(`\n|\t`) +var commentPat = regexp.MustCompile(`(//.*)\n`) + +const funcMarker = "__f__" + +type JSFunctions struct { + Fns []string +} + +// AddJSFuncs adds a new JS function. +func (f *JSFunctions) AddJSFuncs(fn ...string) { + for i := 0; i < len(fn); i++ { + f.Fns = append(f.Fns, newlineTabPat.ReplaceAllString(fn[i], "")) + } +} + +// FuncOpts returns a string suitable for options expecting JavaScript code. +func FuncOpts(fn string) string { + return replaceJsFuncs(fn) +} + +// FuncStripCommentsOpts returns a string suitable for options expecting JavaScript code, +// stripping '//' comments. +func FuncStripCommentsOpts(fn string) string { + fn = commentPat.ReplaceAllString(fn, "") + return replaceJsFuncs(fn) +} + +// replace and clear up js functions string +func replaceJsFuncs(fn string) string { + fn = newlineTabPat.ReplaceAllString(fn, "") + return fmt.Sprintf("%s%s%s", funcMarker, fn, funcMarker) +} + +type Colors []string + +// Assets contains options for static assets. +type Assets struct { + JSAssets types.OrderedSet + CSSAssets types.OrderedSet + + CustomizedJSAssets types.OrderedSet + CustomizedCSSAssets types.OrderedSet +} + +// InitAssets inits the static assets storage. +func (opt *Assets) InitAssets() { + opt.JSAssets.Init(EchartsJS) + opt.CSSAssets.Init() + + opt.CustomizedJSAssets.Init() + opt.CustomizedCSSAssets.Init() +} + +// AddCustomizedJSAssets adds the customized javascript assets which will not be added the `host` prefix. +func (opt *Assets) AddCustomizedJSAssets(assets ...string) { + for i := 0; i < len(assets); i++ { + opt.CustomizedJSAssets.Add(assets[i]) + } +} + +// AddCustomizedCSSAssets adds the customized css assets which will not be added the `host` prefix. +func (opt *Assets) AddCustomizedCSSAssets(assets ...string) { + for i := 0; i < len(assets); i++ { + opt.CustomizedCSSAssets.Add(assets[i]) + } +} + +// Validate validates the static assets configurations +func (opt *Assets) Validate(host string) { + for i := 0; i < len(opt.JSAssets.Values); i++ { + if !strings.HasPrefix(opt.JSAssets.Values[i], host) { + opt.JSAssets.Values[i] = host + opt.JSAssets.Values[i] + } + } + + for i := 0; i < len(opt.CSSAssets.Values); i++ { + if !strings.HasPrefix(opt.CSSAssets.Values[i], host) { + opt.CSSAssets.Values[i] = host + opt.CSSAssets.Values[i] + } + } +} + +// XAxis3D contains options for X axis in the 3D coordinate. +type XAxis3D struct { + // Whether to display the x-axis. + Show bool `json:"show,omitempty"` + + // The name of the axis. + Name string `json:"name,omitempty"` + + // The index of the grid3D component used by the axis. The default is to use the first grid3D component. + Grid3DIndex int `json:"grid3DIndex,omitempty"` + + // The type of the axis. + // Optional: + // * 'value' The value axis. Suitable for continuous data. + // * 'category' The category axis. Suitable for the discrete category data. + // For this type, the category data must be set through data. + // * 'time' The timeline. Suitable for the continuous timing data. The time axis has a + // time format compared to the value axis, and the scale calculation is also different. + // For example, the scale of the month, week, day, and hour ranges can be determined according to the range of the span. + // * 'log' Logarithmic axis. Suitable for the logarithmic data. + Type string `json:"type,omitempty"` + + // The minimum value of axis. + // It can be set to a special value 'dataMin' so that the minimum value on this axis is set to be the minimum label. + // It will be automatically computed to make sure the axis tick is equally distributed when not set. + // In the category axis, it can also be set as the ordinal number. For example, + // if a category axis has data: ['categoryA', 'categoryB', 'categoryC'], + // and the ordinal 2 represents 'categoryC'. Moreover, it can be set as a negative number, like -3. + Min interface{} `json:"min,omitempty"` + + // The maximum value of the axis. + // It can be set to a special value 'dataMax' so that the minimum value on this axis is set to be the maximum label. + // It will be automatically computed to make sure the axis tick is equally distributed when not set. + // In the category axis, it can also be set as the ordinal number. For example, if a category axis + // has data: ['categoryA', 'categoryB', 'categoryC'], and the ordinal 2 represents 'categoryC'. + // Moreover, it can be set as a negative number, like -3. + Max interface{} `json:"max,omitempty"` + + // Category data, available in type: 'category' axis. + // If type is specified as 'category', but axis.data is not specified, axis.data will be auto + // collected from series.data. It brings convenience, but we should notice that axis.data provides + // then value range of the 'category' axis. If it is auto collected from series.data, + // Only the values appearing in series.data can be collected. For example, + // if series.data is empty, nothing will be collected. + Data interface{} `json:"data,omitempty"` +} + +// YAxis3D contains options for Y axis in the 3D coordinate. +type YAxis3D struct { + // Whether to display the y-axis. + Show bool `json:"show,omitempty"` + + // The name of the axis. + Name string `json:"name,omitempty"` + + // The index of the grid3D component used by the axis. The default is to use the first grid3D component. + Grid3DIndex int `json:"grid3DIndex,omitempty"` + + // The type of the axis. + // Optional: + // * 'value' The value axis. Suitable for continuous data. + // * 'category' The category axis. Suitable for the discrete category data. + // For this type, the category data must be set through data. + // * 'time' The timeline. Suitable for the continuous timing data. The time axis has a + // time format compared to the value axis, and the scale calculation is also different. + // For example, the scale of the month, week, day, and hour ranges can be determined according to the range of the span. + // * 'log' Logarithmic axis. Suitable for the logarithmic data. + Type string `json:"type,omitempty"` + + // The minimum value of axis. + // It can be set to a special value 'dataMin' so that the minimum value on this axis is set to be the minimum label. + // It will be automatically computed to make sure the axis tick is equally distributed when not set. + // In the category axis, it can also be set as the ordinal number. For example, + // if a category axis has data: ['categoryA', 'categoryB', 'categoryC'], + // and the ordinal 2 represents 'categoryC'. Moreover, it can be set as a negative number, like -3. + Min interface{} `json:"min,omitempty"` + + // The maximum value of the axis. + // It can be set to a special value 'dataMax' so that the minimum value on this axis is set to be the maximum label. + // It will be automatically computed to make sure the axis tick is equally distributed when not set. + // In the category axis, it can also be set as the ordinal number. For example, if a category axis + // has data: ['categoryA', 'categoryB', 'categoryC'], and the ordinal 2 represents 'categoryC'. + // Moreover, it can be set as a negative number, like -3. + Max interface{} `json:"max,omitempty"` + + // Category data, available in type: 'category' axis. + // If type is specified as 'category', but axis.data is not specified, axis.data will be auto + // collected from series.data. It brings convenience, but we should notice that axis.data provides + // then value range of the 'category' axis. If it is auto collected from series.data, + // Only the values appearing in series.data can be collected. For example, + // if series.data is empty, nothing will be collected. + Data interface{} `json:"data,omitempty"` +} + +// ZAxis3D contains options for Z axis in the 3D coordinate. +type ZAxis3D struct { + // Whether to display the z-axis. + Show bool `json:"show,omitempty"` + + // The name of the axis. + Name string `json:"name,omitempty"` + + // The index of the grid3D component used by the axis. The default is to use the first grid3D component. + Grid3DIndex int `json:"grid3DIndex,omitempty"` + + // The type of the axis. + // Optional: + // * 'value' The value axis. Suitable for continuous data. + // * 'category' The category axis. Suitable for the discrete category data. + // For this type, the category data must be set through data. + // * 'time' The timeline. Suitable for the continuous timing data. The time axis has a + // time format compared to the value axis, and the scale calculation is also different. + // For example, the scale of the month, week, day, and hour ranges can be determined according to the range of the span. + // * 'log' Logarithmic axis. Suitable for the logarithmic data. + Type string `json:"type,omitempty"` + + // The minimum value of axis. + // It can be set to a special value 'dataMin' so that the minimum value on this axis is set to be the minimum label. + // It will be automatically computed to make sure the axis tick is equally distributed when not set. + // In the category axis, it can also be set as the ordinal number. For example, + // if a category axis has data: ['categoryA', 'categoryB', 'categoryC'], + // and the ordinal 2 represents 'categoryC'. Moreover, it can be set as a negative number, like -3. + Min interface{} `json:"min,omitempty"` + + // The maximum value of the axis. + // It can be set to a special value 'dataMax' so that the minimum value on this axis is set to be the maximum label. + // It will be automatically computed to make sure the axis tick is equally distributed when not set. + // In the category axis, it can also be set as the ordinal number. For example, if a category axis + // has data: ['categoryA', 'categoryB', 'categoryC'], and the ordinal 2 represents 'categoryC'. + // Moreover, it can be set as a negative number, like -3. + Max interface{} `json:"max,omitempty"` + + // Category data, available in type: 'category' axis. + // If type is specified as 'category', but axis.data is not specified, axis.data will be auto + // collected from series.data. It brings convenience, but we should notice that axis.data provides + // then value range of the 'category' axis. If it is auto collected from series.data, + // Only the values appearing in series.data can be collected. For example, + // if series.data is empty, nothing will be collected. + Data interface{} `json:"data,omitempty"` +} + +// Grid3D contains options for the 3D coordinate. +type Grid3D struct { + // Whether to show the coordinate. + Show bool `json:"show,omitempty"` + + // 3D Cartesian coordinates width + // default 100 + BoxWidth float32 `json:"boxWidth,omitempty"` + + // 3D Cartesian coordinates height + // default 100 + BoxHeight float32 `json:"boxHeight,omitempty"` + + // 3D Cartesian coordinates depth + // default 100 + BoxDepth float32 `json:"boxDepth,omitempty"` + + // Rotate or scale fellows the mouse + ViewControl *ViewControl `json:"viewControl,omitempty"` +} + +// ViewControl contains options for view controlling. +type ViewControl struct { + // Whether to rotate automatically. + AutoRotate bool `json:"autoRotate,omitempty"` + + // Rotate Speed, (angle/s). + // default 10 + AutoRotateSpeed float32 `json:"autoRotateSpeed,omitempty"` +} + +// Grid Drawing grid in rectangular coordinate. +// https://echarts.apache.org/en/option.html#grid +type Grid struct { + // Distance between grid component and the left side of the container. + Left string `json:"left,omitempty"` + + // Distance between grid component and the right side of the container. + Right string `json:"right,omitempty"` + + // Distance between grid component and the top side of the container. + Top string `json:"top,omitempty"` + + // Distance between grid component and the bottom side of the container. + Bottom string `json:"bottom,omitempty"` + + // Width of grid component. + Width string `json:"width,omitempty"` + + ContainLabel bool `json:"containLabel,omitempty"` + + // Height of grid component. Adaptive by default. + Height string `json:"height,omitempty"` +} + +// Dataset brings convenience in data management separated with styles and enables data reuse by different series. +// More importantly, it enables data encoding from data to visual, which brings convenience in some scenarios. +// https://echarts.apache.org/en/option.html#dataset.id +type Dataset struct { + //source + Source interface{} `json:"source"` +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/opts/series.go b/vendor/github.com/go-echarts/go-echarts/v2/opts/series.go new file mode 100644 index 0000000000..b3d0724b61 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/opts/series.go @@ -0,0 +1,699 @@ +package opts + +import ( + "fmt" +) + +// Label contains options for a label text. +// https://echarts.apache.org/en/option.html#series-line.label +type Label struct { + // Whether to show label. + Show bool `json:"show"` + + // Color is the text color. + // If set as "auto", the color will assigned as visual color, such as series color. + Color string `json:"color,omitempty"` + + // font style. + // Options are: 'normal', 'italic', 'oblique' + FontStyle string `json:"fontStyle,omitempty"` + + // font thick weight. + // Options are: 'normal', 'bold', 'bolder', 'lighter', 100 | 200 | 300 | 400... + FontWeight string `json:"fontWeight,omitempty"` + + // font family. + // Can also be 'serif' , 'monospace', ... + FontFamily string `json:"fontFamily,omitempty"` + + // font size. + FontSize float32 `json:"fontSize,omitempty"` + + // Horizontal alignment of text, automatic by default. + // Options are: 'left', 'center', 'right' + Align string `json:"align,omitempty"` + + // Vertical alignment of text, automatic by default. + // Options are: 'top', 'middle', 'bottom' + VerticalAlign string `json:"verticalAlign,omitempty"` + + // Line height of the text fragment. + LineHeight float32 `json:"lineHeight,omitempty"` + + // Background color of the text fragment. + BackgroundColor string `json:"backgroundColor,omitempty"` + + // Border color of the text fragment. + BorderColor string `json:"borderColor,omitempty"` + + // Border width of the text fragment. + BorderWidth float32 `json:"borderWidth,omitempty"` + + // the text fragment border type. + // Possible values are: 'solid', 'dashed', 'dotted' + BorderType string `json:"borderType,omitempty"` + + // To set the line dash offset. With borderType , we can make the line style more flexible. + BorderDashOffset float32 `json:"borderDashOffset,omitempty"` + + // Border radius of the text fragment. + BorderRadius float32 `json:"borderRadius,omitempty"` + + // Padding of the text fragment, for example: + // padding: [3, 4, 5, 6]: represents padding of [top, right, bottom, left]. + // padding: 4: represents padding: [4, 4, 4, 4]. + // padding: [3, 4]: represents padding: [3, 4, 3, 4]. + // Notice, width and height specifies the width and height of the content, without padding. + Padding string `json:"padding,omitempty"` + + // Label position. Followings are the options: + // + // [x, y] + // Use relative percentage, or absolute pixel values to represent position of label + // relative to top-left corner of bounding box. For example: + // + // Absolute pixel values: position: [10, 10], + // Relative percentage: position: ["50%", "50%"] + // + // "top" + // "left" + // "right" + // "bottom" + // "inside" + // "insideLeft" + // "insideRight" + // "insideTop" + // "insideBottom" + // "insideTopLeft" + // "insideBottomLeft" + // "insideTopRight" + // "insideBottomRight" + Position string `json:"position,omitempty"` + + // Data label formatter, which supports string template and callback function. + // In either form, \n is supported to represent a new line. + // String template, Model variation includes: + // + // {a}: series name. + // {b}: the name of a data item. + // {c}: the value of a data item. + // {@xxx}: the value of a dimension named"xxx", for example,{@product}refers the value of"product"` dimension. + // {@[n]}: the value of a dimension at the index ofn, for example,{@[3]}` refers the value at dimensions[3]. + Formatter string `json:"formatter,omitempty"` +} + +// LabelLine Configuration of label guide line. +type LabelLine struct { + // Whether to show the label guide line. + Show bool `json:"show"` + // Whether to show the label guide line above the corresponding element. + ShowAbove bool `json:"showAbove"` + // The length of the second segment of guide line. + Length2 float64 `json:"length2,omitempty"` + // smoothness of guide line. + Smooth bool `json:"smooth"` + // Minimum turn angle between two segments of guide line + MinTurnAngle float64 `json:"minTurnAngle,omitempty"` + // The style of label line + LineStyle *LineStyle `json:"lineStyle,omitempty"` +} + +// Emphasis is the style when it is highlighted, like being hovered by mouse, or highlighted via legend connect. +type Emphasis struct { + // the emphasis style of label + Label *Label `json:"label,omitempty"` + + // the emphasis style of item + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` +} + +// ItemStyle represents a style of an item. +type ItemStyle struct { + // Color of chart + // Kline Up candle color + Color string `json:"color,omitempty"` + + // Kline Down candle color + Color0 string `json:"color0,omitempty"` + + // BorderColor is the hart border color + // Kline Up candle border color + BorderColor string `json:"borderColor,omitempty"` + + // Kline Down candle border color + BorderColor0 string `json:"borderColor0,omitempty"` + + // Color saturation of a border or gap. + BorderColorSaturation float32 `json:"borderColorSaturation,omitempty"` + + // Border width of a node + BorderWidth float32 `json:"borderWidth,omitempty"` + + // Gaps between child nodes. + GapWidth float32 `json:"gapWidth,omitempty"` + + // Opacity of the component. Supports value from 0 to 1, and the component will not be drawn when set to 0. + Opacity float32 `json:"opacity,omitempty"` +} + +// MarkLines represents a series of marklines. +type MarkLines struct { + Data []interface{} `json:"data,omitempty"` + MarkLineStyle +} + +// MarkLineStyle contains styling options for a MarkLine. +type MarkLineStyle struct { + // Symbol type at the two ends of the mark line. It can be an array for two ends, or assigned separately. + // Options: "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none" + Symbol []string `json:"symbol,omitempty"` + + // Symbol size. + SymbolSize float32 `json:"symbolSize,omitempty"` + + // Mark line text options. + Label *Label `json:"label,omitempty"` +} + +// CircularStyle contains styling options for circular layout. +type CircularStyle struct { + RotateLabel bool `json:"rotateLabel,omitempty"` +} + +// MarkLineNameTypeItem represents type for a MarkLine. +type MarkLineNameTypeItem struct { + // Mark line name. + Name string `json:"name,omitempty"` + + // Mark line type, options: "average", "min", "max". + Type string `json:"type,omitempty"` + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` +} + +// MarkLineNameYAxisItem defines a MarkLine on a Y axis. +type MarkLineNameYAxisItem struct { + // Mark line name + Name string `json:"name,omitempty"` + + // Y axis data + YAxis interface{} `json:"yAxis,omitempty"` + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` +} + +// MarkLineNameXAxisItem defines a MarkLine on a X axis. +type MarkLineNameXAxisItem struct { + // Mark line name + Name string `json:"name,omitempty"` + + // X axis data + XAxis interface{} `json:"xAxis,omitempty"` + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` +} + +// MarkLineNameCoordItem represents coordinates for a MarkLine. +type MarkLineNameCoordItem struct { + // Mark line name + Name string `json:"name,omitempty"` + + // Mark line start coordinate + Coordinate0 []interface{} + + // Mark line end coordinate + Coordinate1 []interface{} + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` +} + +// MarkAreas represents a series of markareas. +type MarkAreas struct { + Data []interface{} `json:"data,omitempty"` + MarkAreaStyle +} + +// MarkAreaStyle contains styling options for a MarkArea. +type MarkAreaStyle struct { + // Mark area text options. + Label *Label `json:"label,omitempty"` + + // ItemStyle settings + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` +} + +// MarkAreaNameTypeItem represents type for a MarkArea. +type MarkAreaNameTypeItem struct { + // Mark area name. + Name string `json:"name,omitempty"` + + // Mark area type, options: "average", "min", "max". + Type string `json:"type,omitempty"` + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` + + // ItemStyle settings + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` +} + +// MarkAreaNameYAxisItem defines a MarkArea on a Y axis. +type MarkAreaNameYAxisItem struct { + // Mark area name + Name string `json:"name,omitempty"` + + // Y axis data + YAxis interface{} `json:"yAxis,omitempty"` +} + +// MarkAreaNameXAxisItem defines a MarkArea on a X axis. +type MarkAreaNameXAxisItem struct { + // Mark area name + Name string `json:"name,omitempty"` + + // X axis data + XAxis interface{} `json:"xAxis,omitempty"` +} + +// MarkAreaNameCoordItem represents coordinates for a MarkArea. +type MarkAreaNameCoordItem struct { + // Mark area name + Name string `json:"name,omitempty"` + + // Mark area start coordinate + Coordinate0 []interface{} + + // Mark area end coordinate + Coordinate1 []interface{} + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` + + // Mark point text options. + Label *Label `json:"label,omitempty"` + + // ItemStyle settings + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` +} + +// MarkPoints represents a series of markpoints. +type MarkPoints struct { + Data []interface{} `json:"data,omitempty"` + MarkPointStyle +} + +// MarkPointStyle contains styling options for a MarkPoint. +type MarkPointStyle struct { + // Symbol type at the two ends of the mark line. It can be an array for two ends, or assigned separately. + // Options: "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none" + Symbol []string `json:"symbol,omitempty"` + + // Symbol size. + SymbolSize float32 `json:"symbolSize,omitempty"` + + // Symbol rotate. + SymbolRotate float32 `json:"symbolRotate,omitempty"` + + // Mark point text options. + Label *Label `json:"label,omitempty"` +} + +// MarkPointNameTypeItem represents type for a MarkPoint. +type MarkPointNameTypeItem struct { + // Name of markpoint + Name string `json:"name,omitempty"` + + // Mark point type, options: "average", "min", "max". + Type string `json:"type,omitempty"` + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` + + // ItemStyle settings + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` +} + +// MarkPointNameCoordItem represents coordinates for a MarkPoint. +type MarkPointNameCoordItem struct { + // Name of markpoint + Name string `json:"name,omitempty"` + + // Mark point coordinate + Coordinate []interface{} `json:"coord,omitempty"` + + // Value in mark point + Value string `json:"value,omitempty"` + + // Works only when type is assigned. + // It is used to state the dimension used to calculate maximum value or minimum value. + // It may be the direct name of a dimension, like x, + // or angle for line charts, or open, or close for candlestick charts. + ValueDim string `json:"valueDim,omitempty"` + + // Mark point text options. + Label *Label `json:"label,omitempty"` + + // ItemStyle settings + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Symbol type + // Options: "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none" + Symbol string `json:"symbol,omitempty"` + + // Symbol size. + SymbolSize float32 `json:"symbolSize,omitempty"` + + // Symbol rotate. + SymbolRotate float32 `json:"symbolRotate,omitempty"` +} + +// RippleEffect is the option set for the ripple effect. +type RippleEffect struct { + // The period duration of animation, in seconds. + // default 4(s) + Period float32 `json:"period,omitempty"` + + // The maximum zooming scale of ripples in animation. + // default 2.5 + Scale float32 `json:"scale,omitempty"` + + // The brush type for ripples. options: "stroke" and "fill". + // default "fill" + BrushType string `json:"brushType,omitempty"` +} + +// LineStyle is the option set for a link style component. +type LineStyle struct { + // Line color + Color string `json:"color,omitempty"` + + // Width of line. default 1 + Width float32 `json:"width,omitempty"` + + // Type of line,options: "solid", "dashed", "dotted". default "solid" + Type string `json:"type,omitempty"` + + // Opacity of the component. Supports value from 0 to 1, and the component will not be drawn when set to 0. + Opacity float32 `json:"opacity,omitempty"` + + // Curveness of edge. The values from 0 to 1 could be set. + // it would be larger as the the value becomes larger. default 0 + Curveness float32 `json:"curveness,omitempty"` +} + +// AreaStyle is the option set for an area style component. +type AreaStyle struct { + // Fill area color. + Color string `json:"color,omitempty"` + + // Opacity of the component. Supports value from 0 to 1, and the component will not be drawn when set to 0. + Opacity float32 `json:"opacity,omitempty"` +} + +// Configuration items about force-directed layout. Force-directed layout simulates +// spring/charge model, which will add a repulsion between 2 nodes and add a attraction +// between 2 nodes of each edge. In each iteration nodes will move under the effect +// of repulsion and attraction. After several iterations, the nodes will be static in a +// balanced position. As a result, the energy local minimum of this whole model will be realized. +// The result of force-directed layout has a good symmetries and clustering, which is also aesthetically pleasing. +type GraphForce struct { + // The initial layout before force-directed layout, which will influence on the result of force-directed layout. + // It defaults not to do any layout and use x, y provided in node as the position of node. + // If it doesn't exist, the position will be generated randomly. + // You can also use circular layout "circular". + InitLayout string `json:"initLayout,omitempty"` + + // The repulsion factor between nodes. The repulsion will be stronger and the distance + // between 2 nodes becomes further as this value becomes larger. + // It can be an array to represent the range of repulsion. In this case larger value have larger + // repulsion and smaller value will have smaller repulsion. + Repulsion float32 `json:"repulsion,omitempty"` + + // The gravity factor enforcing nodes approach to the center. The nodes will be + // closer to the center as the value becomes larger. default 0.1 + Gravity float32 `json:"gravity,omitempty"` + + // The distance between 2 nodes on edge. This distance is also affected by repulsion. + // It can be an array to represent the range of edge length. In this case edge with larger + // value will be shorter, which means two nodes are closer. And edge with smaller value will be longer. + // default 30 + EdgeLength float32 `json:"edgeLength,omitempty"` +} + +// Leaf node special configuration, the leaf node and non-leaf node label location is different. +type TreeLeaves struct { + // The style setting of the text label in a single bar. + Label *Label `json:"label,omitempty"` + + // LineStyle settings in this series data. + LineStyle *LineStyle `json:"lineStyle,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Emphasis settings in this series data. + Emphasis *Emphasis `json:"emphasis,omitempty"` +} + +// TreeMapLevel is level specific configuration. +type TreeMapLevel struct { + // Color defines a list for a node level, if empty, retreived from global color list. + Color []string `json:"color,omitempty"` + + // ColorAlpha indicates the range of tranparent rate (color alpha) for nodes in a level. + ColorAlpha []float32 `json:"colorAlpha,omitempty"` + + // ColorSaturation indicates the range of saturation (color alpha) for nodes in a level. + ColorSaturation []float32 `json:"colorSaturation,omitempty"` + + // ColorMappingBy specifies the rule according to which each node obtain color from color list. + ColorMappingBy string `json:"colorMappingBy,omitempty"` + + // UpperLabel is used to specify whether show label when the treemap node has children. + UpperLabel *UpperLabel `json:"upperLabel,omitempty"` + + // ItemStyle settings in this series data. + ItemStyle *ItemStyle `json:"itemStyle,omitempty"` + + // Emphasis settings in this series data. + Emphasis *Emphasis `json:"emphasis,omitempty"` +} + +// UpperLabel is used to specify whether show label when the treemap node has children. +// https://echarts.apache.org/en/option.html#series-treemap.upperLabel +type UpperLabel struct { + // Show is true to show upper label. + Show bool `json:"show,omitempty"` + + // Position is the label's position. + // * top + // * left + // * right + // * bottom + // * inside + // * insideLeft + // * insideRight + // * insideTop + // * insideBottom + // * insideTopLeft + // * insideBottomLeft + // * insideTopRight + // * insideBottomRight + Position string `json:"position,omitempty"` + + // Distance to the host graphic element. + // It is valid only when position is string value (like 'top', 'insideRight'). + Distance float32 `json:"distance,omitempty"` + + // Rotate label, from -90 degree to 90, positive value represents rotate anti-clockwise. + Rotate float32 `json:"rotate,omitempty"` + + // Whether to move text slightly. For example: [30, 40] means move 30 horizontally and move 40 vertically. + Offset []float32 `json:"offset,omitempty"` + + // Color is the text color + Color string `json:"color,omitempty"` + + // FontStyle + // * "normal" + // * "italic" + // * "oblique" + FontStyle string `json:"fontStyle,omitempty"` + + // FontWeight can be the string or a number + // * "normal" + // * "bold" + // * "bolder" + // * "lighter" + // 100 | 200 | 300| 400 ... + FontWeight interface{} `json:"fontWeight,omitempty"` + + // FontSize + FontSize float32 `json:"fontSize,omitempty"` + + // Align is a horizontal alignment of text, automatic by default. + // * "left" + // * "center" + // * "right" + Align string `json:"align,omitempty"` + + // Align is a horizontal alignment of text, automatic by default. + // * "top" + // * "middle" + // * "bottom" + VerticalAlign string `json:"verticalAlign,omitempty"` + + // Padding of the text fragment, for example: + // Padding: [3, 4, 5, 6]: represents padding of [top, right, bottom, left]. + // Padding: 4: represents padding: [4, 4, 4, 4]. + // Padding: [3, 4]: represents padding: [3, 4, 3, 4]. + Padding interface{} `json:"padding,omitempty"` + + // Width of text block + Width float32 `json:"width,omitempty"` + + // Height of text block + Height float32 `json:"height,omitempty"` + + // Upper label formatter, which supports string template and callback function. + // In either form, \n is supported to represent a new line. + // String template, Model variation includes: + // + // {a}: series name. + // {b}: the name of a data item. + // {c}: the value of a data item. + // {@xxx}: the value of a dimension named"xxx", for example,{@product}refers the value of"product"` dimension. + // {@[n]}: the value of a dimension at the index ofn, for example,{@[3]}` refers the value at dimensions[3]. + Formatter string `json:"formatter,omitempty"` +} + +// RGBColor returns the color with RGB format +func RGBColor(r, g, b uint16) string { + return fmt.Sprintf("rgb(%d,%d,%d)", r, g, b) +} + +// RGBAColor returns the color with RGBA format +func RGBAColor(r, g, b uint16, a float32) string { + return fmt.Sprintf("rgba(%d,%d,%d,%f)", r, g, b, a) +} + +// HSLColor returns the color with HSL format +func HSLColor(h, s, l float32) string { + return fmt.Sprintf("hsl(%f,%f%%,%f%%)", h, s, l) +} + +// HSLAColor returns the color with HSLA format +func HSLAColor(h, s, l, a float32) string { + return fmt.Sprintf("hsla(%f,%f%%,%f%%,%f)", h, s, l, a) +} + +// EdgeLabel is the properties of an label of edge. +// https://echarts.apache.org/en/option.html#series-graph.edgeLabel +type EdgeLabel struct { + // Show is true to show label on edge. + Show bool `json:"show,omitempty"` + + // Position is the label's position in line of edge. + // * "start" + // * "middle" + // * "end" + Position string `json:"position,omitempty"` + + // Color is the text color + Color string `json:"color,omitempty"` + + // FontStyle + // * "normal" + // * "italic" + // * "oblique" + FontStyle string `json:"fontStyle,omitempty"` + + // FontWeight can be the string or a number + // * "normal" + // * "bold" + // * "bolder" + // * "lighter" + // 100 | 200 | 300| 400 ... + FontWeight interface{} `json:"fontWeight,omitempty"` + + // FontSize + FontSize float32 `json:"fontSize,omitempty"` + + // Align is a horizontal alignment of text, automatic by default. + // * "left" + // * "center" + // * "right" + Align string `json:"align,omitempty"` + + // Align is a horizontal alignment of text, automatic by default. + // * "top" + // * "middle" + // * "bottom" + VerticalAlign string `json:"verticalAlign,omitempty"` + + // Padding of the text fragment, for example: + // Padding: [3, 4, 5, 6]: represents padding of [top, right, bottom, left]. + // Padding: 4: represents padding: [4, 4, 4, 4]. + // Padding: [3, 4]: represents padding: [3, 4, 3, 4]. + Padding interface{} `json:"padding,omitempty"` + + // Width of text block + Width float32 `json:"width,omitempty"` + + // Height of text block + Height float32 `json:"height,omitempty"` + + // Edge label formatter, which supports string template and callback function. + // In either form, \n is supported to represent a new line. + // String template, Model variation includes: + // + // {a}: series name. + // {b}: the name of a data item. + // {c}: the value of a data item. + // {@xxx}: the value of a dimension named"xxx", for example,{@product}refers the value of"product"` dimension. + // {@[n]}: the value of a dimension at the index ofn, for example,{@[3]}` refers the value at dimensions[3]. + Formatter string `json:"formatter,omitempty"` +} + +// Define what is encoded to for each dimension of data +// https://echarts.apache.org/en/option.html#series-candlestick.encode +type Encode struct { + X interface{} `json:"x"` + + Y interface{} `json:"y"` + + Tooltip interface{} `json:"tooltip,omitempty"` + + SeriesName interface{} `json:"seriesName,omitempty"` + + ItemID interface{} `json:"itemId,omitempty"` + + ItemName interface{} `json:"itemName,omitempty"` + + ItemGroupID interface{} `json:"itemGroupId,omitempty"` +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/render/engine.go b/vendor/github.com/go-echarts/go-echarts/v2/render/engine.go new file mode 100644 index 0000000000..08e9fc03ad --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/render/engine.go @@ -0,0 +1,118 @@ +package render + +import ( + "bytes" + "fmt" + "html/template" + "io" + "reflect" + "regexp" + + tpls "github.com/go-echarts/go-echarts/v2/templates" +) + +// Renderer +// Any kinds of charts have their render implementation and +// you can define your own render logic easily. +type Renderer interface { + Render(w io.Writer) error +} + +const ( + ModChart = "chart" + ModPage = "page" +) + +var pat = regexp.MustCompile(`(__f__")|("__f__)|(__f__)`) + +type pageRender struct { + c interface{} + before []func() +} + +// NewPageRender returns a render implementation for Page. +func NewPageRender(c interface{}, before ...func()) Renderer { + return &pageRender{c: c, before: before} +} + +// Render renders the page into the given io.Writer. +func (r *pageRender) Render(w io.Writer) error { + for _, fn := range r.before { + fn() + } + + contents := []string{tpls.HeaderTpl, tpls.BaseTpl, tpls.PageTpl} + tpl := MustTemplate(ModPage, contents) + + var buf bytes.Buffer + if err := tpl.ExecuteTemplate(&buf, ModPage, r.c); err != nil { + return err + } + + content := pat.ReplaceAll(buf.Bytes(), []byte("")) + + _, err := w.Write(content) + return err +} + +type chartRender struct { + c interface{} + before []func() +} + +// NewChartRender returns a render implementation for Chart. +func NewChartRender(c interface{}, before ...func()) Renderer { + return &chartRender{c: c, before: before} +} + +// Render renders the chart into the given io.Writer. +func (r *chartRender) Render(w io.Writer) error { + for _, fn := range r.before { + fn() + } + + contents := []string{tpls.HeaderTpl, tpls.BaseTpl, tpls.ChartTpl} + tpl := MustTemplate(ModChart, contents) + + var buf bytes.Buffer + if err := tpl.ExecuteTemplate(&buf, ModChart, r.c); err != nil { + return err + } + + content := pat.ReplaceAll(buf.Bytes(), []byte("")) + + _, err := w.Write(content) + return err +} + +// isSet check if the field exist in the chart instance +// Shamed copy from https://stackoverflow.com/questions/44675087/golang-template-variable-isset +func isSet(name string, data interface{}) bool { + v := reflect.ValueOf(data) + + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return false + } + + return v.FieldByName(name).IsValid() +} + +// MustTemplate creates a new template with the given name and parsed contents. +func MustTemplate(name string, contents []string) *template.Template { + tpl := template.New(name).Funcs(template.FuncMap{ + "safeJS": func(s interface{}) template.JS { + return template.JS(fmt.Sprint(s)) + }, + "isSet": isSet, + }) + tpl = template.Must(tpl.Parse(contents[0])) + + for _, cont := range contents[1:] { + tpl = template.Must(tpl.Parse(cont)) + } + return tpl +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/templates/base.tpl b/vendor/github.com/go-echarts/go-echarts/v2/templates/base.tpl new file mode 100644 index 0000000000..fcb3037c8e --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/templates/base.tpl @@ -0,0 +1,20 @@ +{{- define "base" }} +
+
+
+ + +{{ end }} \ No newline at end of file diff --git a/vendor/github.com/go-echarts/go-echarts/v2/templates/chart.tpl b/vendor/github.com/go-echarts/go-echarts/v2/templates/chart.tpl new file mode 100644 index 0000000000..1cc046943c --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/templates/chart.tpl @@ -0,0 +1,13 @@ +{{- define "chart" }} + + + {{- template "header" . }} + + {{- template "base" . }} + + + +{{ end }} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/templates/header.tpl b/vendor/github.com/go-echarts/go-echarts/v2/templates/header.tpl new file mode 100644 index 0000000000..49f7ffe8fb --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/templates/header.tpl @@ -0,0 +1,18 @@ +{{ define "header" }} + + + {{ .PageTitle }} +{{- range .JSAssets.Values }} + +{{- end }} +{{- range .CustomizedJSAssets.Values }} + +{{- end }} +{{- range .CSSAssets.Values }} + +{{- end }} +{{- range .CustomizedCSSAssets.Values }} + +{{- end }} + +{{ end }} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/templates/page.tpl b/vendor/github.com/go-echarts/go-echarts/v2/templates/page.tpl new file mode 100644 index 0000000000..2ba490ae65 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/templates/page.tpl @@ -0,0 +1,21 @@ +{{- define "page" }} + + + {{- template "header" . }} + +{{ if eq .Layout "none" }} + {{- range .Charts }} {{ template "base" . }} {{- end }} +{{ end }} + +{{ if eq .Layout "center" }} + + {{- range .Charts }} {{ template "base" . }} {{- end }} +{{ end }} + +{{ if eq .Layout "flex" }} + +
{{- range .Charts }} {{ template "base" . }} {{- end }}
+{{ end }} + + +{{ end }} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/templates/template.go b/vendor/github.com/go-echarts/go-echarts/v2/templates/template.go new file mode 100644 index 0000000000..59bd108b48 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/templates/template.go @@ -0,0 +1,17 @@ +package templates + +import _ "embed" + +// BaseTpl Should check the BaseActions field before call JSONNotEscapedAction since BaseActions only exist in RectCharts +// +//go:embed base.tpl +var BaseTpl string + +//go:embed chart.tpl +var ChartTpl string + +//go:embed header.tpl +var HeaderTpl string + +//go:embed page.tpl +var PageTpl string diff --git a/vendor/github.com/go-echarts/go-echarts/v2/types/charts.go b/vendor/github.com/go-echarts/go-echarts/v2/types/charts.go new file mode 100644 index 0000000000..877515ab6e --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/types/charts.go @@ -0,0 +1,33 @@ +package types + +// Chart Type contains string representations of chart types. +const ( + ChartBar = "bar" + ChartBar3D = "bar3D" + ChartBoxPlot = "boxplot" + ChartCartesian3D = "cartesian3D" + ChartEffectScatter = "effectScatter" + ChartFunnel = "funnel" + ChartGauge = "gauge" + ChartGeo = "geo" + ChartGraph = "graph" + ChartHeatMap = "heatmap" + ChartKline = "candlestick" + ChartLine = "line" + ChartLine3D = "line3D" + ChartLiquid = "liquidFill" + ChartMap = "map" + ChartParallel = "parallel" + ChartPie = "pie" + ChartRadar = "radar" + ChartSankey = "sankey" + ChartScatter = "scatter" + ChartScatter3D = "scatter3D" + ChartSurface3D = "surface" + ChartThemeRiver = "themeRiver" + ChartWordCloud = "wordCloud" + ChartTree = "tree" + ChartTreeMap = "treemap" + ChartSunburst = "sunburst" + ChartCustom = "custom" +) diff --git a/vendor/github.com/go-echarts/go-echarts/v2/types/lang.go b/vendor/github.com/go-echarts/go-echarts/v2/types/lang.go new file mode 100644 index 0000000000..c1c1ba4c89 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/types/lang.go @@ -0,0 +1,15 @@ +package types + +// thoughts on those boxed type for default value solution... +type ( + Bool *bool + Integer *int +) + +func newBool(val bool) Bool { + return &val +} + +func newInteger(val int) Integer { + return &val +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/types/orderedset.go b/vendor/github.com/go-echarts/go-echarts/v2/types/orderedset.go new file mode 100644 index 0000000000..085e79c5a9 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/types/orderedset.go @@ -0,0 +1,54 @@ +package types + +type Index struct { + index int +} + +// OrderedSet represents an ordered set. +type OrderedSet struct { + filter map[string]*Index + cur int + Values []string +} + +// Init creates a new OrderedSet instance, and adds any given items into this set. +func (o *OrderedSet) Init(items ...string) { + o.filter = make(map[string]*Index) + o.cur = 0 + for _, item := range items { + o.Add(item) + } +} + +// Add adds a new item into the ordered set +func (o *OrderedSet) Add(item string) { + if o.filter[item] == nil { + o.filter[item] = &Index{ + index: o.cur, + } + o.cur++ + o.Values = append(o.Values, item) + } +} + +func (o *OrderedSet) Remove(item string) { + if o.filter[item] != nil { + idx := o.filter[item].index + o.Values = append(o.Values[:idx], o.Values[idx+1:]...) + + renew := &OrderedSet{} + renew.Init(o.Values...) + + o.cur = renew.cur + o.filter = renew.filter + o.Values = renew.Values + } +} + +func (o *OrderedSet) Size() int { + return o.cur +} + +func (o *OrderedSet) Contains(item string) bool { + return o.filter[item] != nil +} diff --git a/vendor/github.com/go-echarts/go-echarts/v2/types/themes.go b/vendor/github.com/go-echarts/go-echarts/v2/types/themes.go new file mode 100644 index 0000000000..28f5f735e3 --- /dev/null +++ b/vendor/github.com/go-echarts/go-echarts/v2/types/themes.go @@ -0,0 +1,17 @@ +package types + +// Theme Type contains string representations of theme types. +const ( + ThemeChalk = "chalk" + ThemeEssos = "essos" + ThemeInfographic = "infographic" + ThemeMacarons = "macarons" + ThemePurplePassion = "purple-passion" + ThemeRoma = "roma" + ThemeRomantic = "romantic" + ThemeShine = "shine" + ThemeVintage = "vintage" + ThemeWalden = "walden" + ThemeWesteros = "westeros" + ThemeWonderland = "wonderland" +) diff --git a/vendor/github.com/go-redis/redis/.gitignore b/vendor/github.com/go-redis/redis/.gitignore deleted file mode 100644 index ebfe903bcd..0000000000 --- a/vendor/github.com/go-redis/redis/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.rdb -testdata/*/ diff --git a/vendor/github.com/go-redis/redis/.travis.yml b/vendor/github.com/go-redis/redis/.travis.yml deleted file mode 100644 index 06d7897b4e..0000000000 --- a/vendor/github.com/go-redis/redis/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -sudo: false -language: go - -services: - - redis-server - -go: - - 1.9.x - - 1.10.x - - 1.11.x - - 1.12.x - - tip - -matrix: - allow_failures: - - go: tip - -install: - - go get github.com/onsi/ginkgo - - go get github.com/onsi/gomega diff --git a/vendor/github.com/go-redis/redis/CHANGELOG.md b/vendor/github.com/go-redis/redis/CHANGELOG.md deleted file mode 100644 index 19645661a4..0000000000 --- a/vendor/github.com/go-redis/redis/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Changelog - -## Unreleased - -- Cluster and Ring pipelines process commands for each node in its own goroutine. - -## 6.14 - -- Added Options.MinIdleConns. -- Added Options.MaxConnAge. -- PoolStats.FreeConns is renamed to PoolStats.IdleConns. -- Add Client.Do to simplify creating custom commands. -- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. -- Lower memory usage. - -## v6.13 - -- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. -- Cluster client was optimized to use much less memory when reloading cluster state. -- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. -- Dialer.KeepAlive is set to 5 minutes by default. - -## v6.12 - -- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/LICENSE b/vendor/github.com/go-redis/redis/LICENSE deleted file mode 100644 index 298bed9bea..0000000000 --- a/vendor/github.com/go-redis/redis/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2013 The github.com/go-redis/redis Authors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/go-redis/redis/Makefile b/vendor/github.com/go-redis/redis/Makefile deleted file mode 100644 index fa3b4e004f..0000000000 --- a/vendor/github.com/go-redis/redis/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -all: testdeps - go test ./... - go test ./... -short -race - env GOOS=linux GOARCH=386 go test ./... - go vet - go get github.com/gordonklaus/ineffassign - ineffassign . - -testdeps: testdata/redis/src/redis-server - -bench: testdeps - go test ./... -test.run=NONE -test.bench=. -test.benchmem - -.PHONY: all test testdeps bench - -testdata/redis: - mkdir -p $@ - wget -qO- https://github.com/antirez/redis/archive/5.0.tar.gz | tar xvz --strip-components=1 -C $@ - -testdata/redis/src/redis-server: testdata/redis - sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $ -} - -func ExampleClient() { - err := client.Set("key", "value", 0).Err() - if err != nil { - panic(err) - } - - val, err := client.Get("key").Result() - if err != nil { - panic(err) - } - fmt.Println("key", val) - - val2, err := client.Get("key2").Result() - if err == redis.Nil { - fmt.Println("key2 does not exist") - } else if err != nil { - panic(err) - } else { - fmt.Println("key2", val2) - } - // Output: key value - // key2 does not exist -} -``` - -## Howto - -Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package. - -## Look and feel - -Some corner cases: - -```go -// SET key value EX 10 NX -set, err := client.SetNX("key", "value", 10*time.Second).Result() - -// SORT list LIMIT 0 2 ASC -vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() - -// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 -vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ - Min: "-inf", - Max: "+inf", - Offset: 0, - Count: 2, -}).Result() - -// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM -vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result() - -// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" -vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() -``` - -## Benchmark - -go-redis vs redigo: - -``` -BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op -BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op -BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op -``` - -Redis Cluster: - -``` -BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op -BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op -``` - -## See also - -- [Golang PostgreSQL ORM](https://github.com/go-pg/pg) -- [Golang msgpack](https://github.com/vmihailenco/msgpack) -- [Golang message task queue](https://github.com/vmihailenco/taskq) diff --git a/vendor/github.com/go-redis/redis/cluster.go b/vendor/github.com/go-redis/redis/cluster.go deleted file mode 100644 index ab2c76f05e..0000000000 --- a/vendor/github.com/go-redis/redis/cluster.go +++ /dev/null @@ -1,1627 +0,0 @@ -package redis - -import ( - "context" - "crypto/tls" - "fmt" - "math" - "math/rand" - "net" - "runtime" - "sort" - "sync" - "sync/atomic" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/hashtag" - "github.com/go-redis/redis/internal/pool" - "github.com/go-redis/redis/internal/proto" -) - -var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes") - -// ClusterOptions are used to configure a cluster client and should be -// passed to NewClusterClient. -type ClusterOptions struct { - // A seed list of host:port addresses of cluster nodes. - Addrs []string - - // The maximum number of retries before giving up. Command is retried - // on network errors and MOVED/ASK redirects. - // Default is 8 retries. - MaxRedirects int - - // Enables read-only commands on slave nodes. - ReadOnly bool - // Allows routing read-only commands to the closest master or slave node. - // It automatically enables ReadOnly. - RouteByLatency bool - // Allows routing read-only commands to the random master or slave node. - // It automatically enables ReadOnly. - RouteRandomly bool - - // Optional function that returns cluster slots information. - // It is useful to manually create cluster of standalone Redis servers - // and load-balance read/write operations between master and slaves. - // It can use service like ZooKeeper to maintain configuration information - // and Cluster.ReloadState to manually trigger state reloading. - ClusterSlots func() ([]ClusterSlot, error) - - // Optional hook that is called when a new node is created. - OnNewNode func(*Client) - - // Following options are copied from Options struct. - - OnConnect func(*Conn) error - - Password string - - MaxRetries int - MinRetryBackoff time.Duration - MaxRetryBackoff time.Duration - - DialTimeout time.Duration - ReadTimeout time.Duration - WriteTimeout time.Duration - - // PoolSize applies per cluster node and not for the whole cluster. - PoolSize int - MinIdleConns int - MaxConnAge time.Duration - PoolTimeout time.Duration - IdleTimeout time.Duration - IdleCheckFrequency time.Duration - - TLSConfig *tls.Config -} - -func (opt *ClusterOptions) init() { - if opt.MaxRedirects == -1 { - opt.MaxRedirects = 0 - } else if opt.MaxRedirects == 0 { - opt.MaxRedirects = 8 - } - - if (opt.RouteByLatency || opt.RouteRandomly) && opt.ClusterSlots == nil { - opt.ReadOnly = true - } - - if opt.PoolSize == 0 { - opt.PoolSize = 5 * runtime.NumCPU() - } - - switch opt.ReadTimeout { - case -1: - opt.ReadTimeout = 0 - case 0: - opt.ReadTimeout = 3 * time.Second - } - switch opt.WriteTimeout { - case -1: - opt.WriteTimeout = 0 - case 0: - opt.WriteTimeout = opt.ReadTimeout - } - - switch opt.MinRetryBackoff { - case -1: - opt.MinRetryBackoff = 0 - case 0: - opt.MinRetryBackoff = 8 * time.Millisecond - } - switch opt.MaxRetryBackoff { - case -1: - opt.MaxRetryBackoff = 0 - case 0: - opt.MaxRetryBackoff = 512 * time.Millisecond - } -} - -func (opt *ClusterOptions) clientOptions() *Options { - const disableIdleCheck = -1 - - return &Options{ - OnConnect: opt.OnConnect, - - MaxRetries: opt.MaxRetries, - MinRetryBackoff: opt.MinRetryBackoff, - MaxRetryBackoff: opt.MaxRetryBackoff, - Password: opt.Password, - readOnly: opt.ReadOnly, - - DialTimeout: opt.DialTimeout, - ReadTimeout: opt.ReadTimeout, - WriteTimeout: opt.WriteTimeout, - - PoolSize: opt.PoolSize, - MinIdleConns: opt.MinIdleConns, - MaxConnAge: opt.MaxConnAge, - PoolTimeout: opt.PoolTimeout, - IdleTimeout: opt.IdleTimeout, - IdleCheckFrequency: disableIdleCheck, - - TLSConfig: opt.TLSConfig, - } -} - -//------------------------------------------------------------------------------ - -type clusterNode struct { - Client *Client - - latency uint32 // atomic - generation uint32 // atomic - loading uint32 // atomic -} - -func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode { - opt := clOpt.clientOptions() - opt.Addr = addr - node := clusterNode{ - Client: NewClient(opt), - } - - node.latency = math.MaxUint32 - if clOpt.RouteByLatency { - go node.updateLatency() - } - - if clOpt.OnNewNode != nil { - clOpt.OnNewNode(node.Client) - } - - return &node -} - -func (n *clusterNode) String() string { - return n.Client.String() -} - -func (n *clusterNode) Close() error { - return n.Client.Close() -} - -func (n *clusterNode) updateLatency() { - const probes = 10 - - var latency uint32 - for i := 0; i < probes; i++ { - start := time.Now() - n.Client.Ping() - probe := uint32(time.Since(start) / time.Microsecond) - latency = (latency + probe) / 2 - } - atomic.StoreUint32(&n.latency, latency) -} - -func (n *clusterNode) Latency() time.Duration { - latency := atomic.LoadUint32(&n.latency) - return time.Duration(latency) * time.Microsecond -} - -func (n *clusterNode) MarkAsLoading() { - atomic.StoreUint32(&n.loading, uint32(time.Now().Unix())) -} - -func (n *clusterNode) Loading() bool { - const minute = int64(time.Minute / time.Second) - - loading := atomic.LoadUint32(&n.loading) - if loading == 0 { - return false - } - if time.Now().Unix()-int64(loading) < minute { - return true - } - atomic.StoreUint32(&n.loading, 0) - return false -} - -func (n *clusterNode) Generation() uint32 { - return atomic.LoadUint32(&n.generation) -} - -func (n *clusterNode) SetGeneration(gen uint32) { - for { - v := atomic.LoadUint32(&n.generation) - if gen < v || atomic.CompareAndSwapUint32(&n.generation, v, gen) { - break - } - } -} - -//------------------------------------------------------------------------------ - -type clusterNodes struct { - opt *ClusterOptions - - mu sync.RWMutex - allAddrs []string - allNodes map[string]*clusterNode - clusterAddrs []string - closed bool - - _generation uint32 // atomic -} - -func newClusterNodes(opt *ClusterOptions) *clusterNodes { - return &clusterNodes{ - opt: opt, - - allAddrs: opt.Addrs, - allNodes: make(map[string]*clusterNode), - } -} - -func (c *clusterNodes) Close() error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return nil - } - c.closed = true - - var firstErr error - for _, node := range c.allNodes { - if err := node.Client.Close(); err != nil && firstErr == nil { - firstErr = err - } - } - - c.allNodes = nil - c.clusterAddrs = nil - - return firstErr -} - -func (c *clusterNodes) Addrs() ([]string, error) { - var addrs []string - c.mu.RLock() - closed := c.closed - if !closed { - if len(c.clusterAddrs) > 0 { - addrs = c.clusterAddrs - } else { - addrs = c.allAddrs - } - } - c.mu.RUnlock() - - if closed { - return nil, pool.ErrClosed - } - if len(addrs) == 0 { - return nil, errClusterNoNodes - } - return addrs, nil -} - -func (c *clusterNodes) NextGeneration() uint32 { - return atomic.AddUint32(&c._generation, 1) -} - -// GC removes unused nodes. -func (c *clusterNodes) GC(generation uint32) { - var collected []*clusterNode - c.mu.Lock() - for addr, node := range c.allNodes { - if node.Generation() >= generation { - continue - } - - c.clusterAddrs = remove(c.clusterAddrs, addr) - delete(c.allNodes, addr) - collected = append(collected, node) - } - c.mu.Unlock() - - for _, node := range collected { - _ = node.Client.Close() - } -} - -func (c *clusterNodes) Get(addr string) (*clusterNode, error) { - var node *clusterNode - var err error - c.mu.RLock() - if c.closed { - err = pool.ErrClosed - } else { - node = c.allNodes[addr] - } - c.mu.RUnlock() - return node, err -} - -func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) { - node, err := c.Get(addr) - if err != nil { - return nil, err - } - if node != nil { - return node, nil - } - - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return nil, pool.ErrClosed - } - - node, ok := c.allNodes[addr] - if ok { - return node, err - } - - node = newClusterNode(c.opt, addr) - - c.allAddrs = appendIfNotExists(c.allAddrs, addr) - c.clusterAddrs = append(c.clusterAddrs, addr) - c.allNodes[addr] = node - - return node, err -} - -func (c *clusterNodes) All() ([]*clusterNode, error) { - c.mu.RLock() - defer c.mu.RUnlock() - - if c.closed { - return nil, pool.ErrClosed - } - - cp := make([]*clusterNode, 0, len(c.allNodes)) - for _, node := range c.allNodes { - cp = append(cp, node) - } - return cp, nil -} - -func (c *clusterNodes) Random() (*clusterNode, error) { - addrs, err := c.Addrs() - if err != nil { - return nil, err - } - - n := rand.Intn(len(addrs)) - return c.GetOrCreate(addrs[n]) -} - -//------------------------------------------------------------------------------ - -type clusterSlot struct { - start, end int - nodes []*clusterNode -} - -type clusterSlotSlice []*clusterSlot - -func (p clusterSlotSlice) Len() int { - return len(p) -} - -func (p clusterSlotSlice) Less(i, j int) bool { - return p[i].start < p[j].start -} - -func (p clusterSlotSlice) Swap(i, j int) { - p[i], p[j] = p[j], p[i] -} - -type clusterState struct { - nodes *clusterNodes - Masters []*clusterNode - Slaves []*clusterNode - - slots []*clusterSlot - - generation uint32 - createdAt time.Time -} - -func newClusterState( - nodes *clusterNodes, slots []ClusterSlot, origin string, -) (*clusterState, error) { - c := clusterState{ - nodes: nodes, - - slots: make([]*clusterSlot, 0, len(slots)), - - generation: nodes.NextGeneration(), - createdAt: time.Now(), - } - - originHost, _, _ := net.SplitHostPort(origin) - isLoopbackOrigin := isLoopback(originHost) - - for _, slot := range slots { - var nodes []*clusterNode - for i, slotNode := range slot.Nodes { - addr := slotNode.Addr - if !isLoopbackOrigin { - addr = replaceLoopbackHost(addr, originHost) - } - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return nil, err - } - - node.SetGeneration(c.generation) - nodes = append(nodes, node) - - if i == 0 { - c.Masters = appendUniqueNode(c.Masters, node) - } else { - c.Slaves = appendUniqueNode(c.Slaves, node) - } - } - - c.slots = append(c.slots, &clusterSlot{ - start: slot.Start, - end: slot.End, - nodes: nodes, - }) - } - - sort.Sort(clusterSlotSlice(c.slots)) - - time.AfterFunc(time.Minute, func() { - nodes.GC(c.generation) - }) - - return &c, nil -} - -func replaceLoopbackHost(nodeAddr, originHost string) string { - nodeHost, nodePort, err := net.SplitHostPort(nodeAddr) - if err != nil { - return nodeAddr - } - - nodeIP := net.ParseIP(nodeHost) - if nodeIP == nil { - return nodeAddr - } - - if !nodeIP.IsLoopback() { - return nodeAddr - } - - // Use origin host which is not loopback and node port. - return net.JoinHostPort(originHost, nodePort) -} - -func isLoopback(host string) bool { - ip := net.ParseIP(host) - if ip == nil { - return true - } - return ip.IsLoopback() -} - -func (c *clusterState) slotMasterNode(slot int) (*clusterNode, error) { - nodes := c.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *clusterState) slotSlaveNode(slot int) (*clusterNode, error) { - nodes := c.slotNodes(slot) - switch len(nodes) { - case 0: - return c.nodes.Random() - case 1: - return nodes[0], nil - case 2: - if slave := nodes[1]; !slave.Loading() { - return slave, nil - } - return nodes[0], nil - default: - var slave *clusterNode - for i := 0; i < 10; i++ { - n := rand.Intn(len(nodes)-1) + 1 - slave = nodes[n] - if !slave.Loading() { - return slave, nil - } - } - - // All slaves are loading - use master. - return nodes[0], nil - } -} - -func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { - const threshold = time.Millisecond - - nodes := c.slotNodes(slot) - if len(nodes) == 0 { - return c.nodes.Random() - } - - var node *clusterNode - for _, n := range nodes { - if n.Loading() { - continue - } - if node == nil || node.Latency()-n.Latency() > threshold { - node = n - } - } - return node, nil -} - -func (c *clusterState) slotRandomNode(slot int) *clusterNode { - nodes := c.slotNodes(slot) - n := rand.Intn(len(nodes)) - return nodes[n] -} - -func (c *clusterState) slotNodes(slot int) []*clusterNode { - i := sort.Search(len(c.slots), func(i int) bool { - return c.slots[i].end >= slot - }) - if i >= len(c.slots) { - return nil - } - x := c.slots[i] - if slot >= x.start && slot <= x.end { - return x.nodes - } - return nil -} - -//------------------------------------------------------------------------------ - -type clusterStateHolder struct { - load func() (*clusterState, error) - - state atomic.Value - reloading uint32 // atomic -} - -func newClusterStateHolder(fn func() (*clusterState, error)) *clusterStateHolder { - return &clusterStateHolder{ - load: fn, - } -} - -func (c *clusterStateHolder) Reload() (*clusterState, error) { - state, err := c.load() - if err != nil { - return nil, err - } - c.state.Store(state) - return state, nil -} - -func (c *clusterStateHolder) LazyReload() { - if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) { - return - } - go func() { - defer atomic.StoreUint32(&c.reloading, 0) - - _, err := c.Reload() - if err != nil { - return - } - time.Sleep(100 * time.Millisecond) - }() -} - -func (c *clusterStateHolder) Get() (*clusterState, error) { - v := c.state.Load() - if v != nil { - state := v.(*clusterState) - if time.Since(state.createdAt) > time.Minute { - c.LazyReload() - } - return state, nil - } - return c.Reload() -} - -func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) { - state, err := c.Reload() - if err == nil { - return state, nil - } - return c.Get() -} - -//------------------------------------------------------------------------------ - -// ClusterClient is a Redis Cluster client representing a pool of zero -// or more underlying connections. It's safe for concurrent use by -// multiple goroutines. -type ClusterClient struct { - cmdable - - ctx context.Context - - opt *ClusterOptions - nodes *clusterNodes - state *clusterStateHolder - cmdsInfoCache *cmdsInfoCache - - process func(Cmder) error - processPipeline func([]Cmder) error - processTxPipeline func([]Cmder) error -} - -// NewClusterClient returns a Redis Cluster client as described in -// http://redis.io/topics/cluster-spec. -func NewClusterClient(opt *ClusterOptions) *ClusterClient { - opt.init() - - c := &ClusterClient{ - opt: opt, - nodes: newClusterNodes(opt), - } - c.state = newClusterStateHolder(c.loadState) - c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) - - c.process = c.defaultProcess - c.processPipeline = c.defaultProcessPipeline - c.processTxPipeline = c.defaultProcessTxPipeline - - c.init() - if opt.IdleCheckFrequency > 0 { - go c.reaper(opt.IdleCheckFrequency) - } - - return c -} - -func (c *ClusterClient) init() { - c.cmdable.setProcessor(c.Process) -} - -// ReloadState reloads cluster state. If available it calls ClusterSlots func -// to get cluster slots information. -func (c *ClusterClient) ReloadState() error { - _, err := c.state.Reload() - return err -} - -func (c *ClusterClient) Context() context.Context { - if c.ctx != nil { - return c.ctx - } - return context.Background() -} - -func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient { - if ctx == nil { - panic("nil context") - } - c2 := c.clone() - c2.ctx = ctx - return c2 -} - -func (c *ClusterClient) clone() *ClusterClient { - cp := *c - cp.init() - return &cp -} - -// Options returns read-only Options that were used to create the client. -func (c *ClusterClient) Options() *ClusterOptions { - return c.opt -} - -func (c *ClusterClient) retryBackoff(attempt int) time.Duration { - return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) -} - -func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.Get(addr) - if err != nil { - return nil, err - } - if node == nil { - continue - } - - info, err := node.Client.Command().Result() - if err == nil { - return info, nil - } - if firstErr == nil { - firstErr = err - } - } - return nil, firstErr -} - -func (c *ClusterClient) cmdInfo(name string) *CommandInfo { - cmdsInfo, err := c.cmdsInfoCache.Get() - if err != nil { - return nil - } - - info := cmdsInfo[name] - if info == nil { - internal.Logf("info for cmd=%s not found", name) - } - return info -} - -func cmdSlot(cmd Cmder, pos int) int { - if pos == 0 { - return hashtag.RandomSlot() - } - firstKey := cmd.stringArg(pos) - return hashtag.Slot(firstKey) -} - -func (c *ClusterClient) cmdSlot(cmd Cmder) int { - args := cmd.Args() - if args[0] == "cluster" && args[1] == "getkeysinslot" { - return args[2].(int) - } - - cmdInfo := c.cmdInfo(cmd.Name()) - return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) -} - -func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return 0, nil, err - } - - cmdInfo := c.cmdInfo(cmd.Name()) - slot := c.cmdSlot(cmd) - - if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { - if c.opt.RouteByLatency { - node, err := state.slotClosestNode(slot) - return slot, node, err - } - - if c.opt.RouteRandomly { - node := state.slotRandomNode(slot) - return slot, node, nil - } - - node, err := state.slotSlaveNode(slot) - return slot, node, err - } - - node, err := state.slotMasterNode(slot) - return slot, node, err -} - -func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return nil, err - } - - nodes := state.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { - if len(keys) == 0 { - return fmt.Errorf("redis: Watch requires at least one key") - } - - slot := hashtag.Slot(keys[0]) - for _, key := range keys[1:] { - if hashtag.Slot(key) != slot { - err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") - return err - } - } - - node, err := c.slotMasterNode(slot) - if err != nil { - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - err = node.Client.Watch(fn, keys...) - if err == nil { - break - } - if err != Nil { - c.state.LazyReload() - } - - moved, ask, addr := internal.IsMovedError(err) - if moved || ask { - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - return err - } - continue - } - - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node, err = c.slotMasterNode(slot) - if err != nil { - return err - } - continue - } - - if internal.IsRetryableError(err, true) { - continue - } - - return err - } - - return err -} - -// Close closes the cluster client, releasing any open resources. -// -// It is rare to Close a ClusterClient, as the ClusterClient is meant -// to be long-lived and shared between many goroutines. -func (c *ClusterClient) Close() error { - return c.nodes.Close() -} - -// Do creates a Cmd from the args and processes the cmd. -func (c *ClusterClient) Do(args ...interface{}) *Cmd { - cmd := NewCmd(args...) - c.Process(cmd) - return cmd -} - -func (c *ClusterClient) WrapProcess( - fn func(oldProcess func(Cmder) error) func(Cmder) error, -) { - c.process = fn(c.process) -} - -func (c *ClusterClient) Process(cmd Cmder) error { - return c.process(cmd) -} - -func (c *ClusterClient) defaultProcess(cmd Cmder) error { - var node *clusterNode - var ask bool - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - if node == nil { - var err error - _, node, err = c.cmdSlotAndNode(cmd) - if err != nil { - cmd.setErr(err) - break - } - } - - var err error - if ask { - pipe := node.Client.Pipeline() - _ = pipe.Process(NewCmd("ASKING")) - _ = pipe.Process(cmd) - _, err = pipe.Exec() - _ = pipe.Close() - ask = false - } else { - err = node.Client.Process(cmd) - } - - // If there is no error - we are done. - if err == nil { - break - } - if err != Nil { - c.state.LazyReload() - } - - // If slave is loading - pick another node. - if c.opt.ReadOnly && internal.IsLoadingError(err) { - node.MarkAsLoading() - node = nil - continue - } - - var moved bool - var addr string - moved, ask, addr = internal.IsMovedError(err) - if moved || ask { - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - break - } - continue - } - - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node = nil - continue - } - - if internal.IsRetryableError(err, true) { - // First retry the same node. - if attempt == 0 { - continue - } - - // Second try random node. - node, err = c.nodes.Random() - if err != nil { - break - } - continue - } - - break - } - - return cmd.Err() -} - -// ForEachMaster concurrently calls the fn on each master node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - for _, master := range state.Masters { - wg.Add(1) - go func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - }(master) - } - wg.Wait() - - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// ForEachSlave concurrently calls the fn on each slave node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - for _, slave := range state.Slaves { - wg.Add(1) - go func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - }(slave) - } - wg.Wait() - - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// ForEachNode concurrently calls the fn on each known node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - worker := func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - } - - for _, node := range state.Masters { - wg.Add(1) - go worker(node) - } - for _, node := range state.Slaves { - wg.Add(1) - go worker(node) - } - - wg.Wait() - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// PoolStats returns accumulated connection pool stats. -func (c *ClusterClient) PoolStats() *PoolStats { - var acc PoolStats - - state, _ := c.state.Get() - if state == nil { - return &acc - } - - for _, node := range state.Masters { - s := node.Client.connPool.Stats() - acc.Hits += s.Hits - acc.Misses += s.Misses - acc.Timeouts += s.Timeouts - - acc.TotalConns += s.TotalConns - acc.IdleConns += s.IdleConns - acc.StaleConns += s.StaleConns - } - - for _, node := range state.Slaves { - s := node.Client.connPool.Stats() - acc.Hits += s.Hits - acc.Misses += s.Misses - acc.Timeouts += s.Timeouts - - acc.TotalConns += s.TotalConns - acc.IdleConns += s.IdleConns - acc.StaleConns += s.StaleConns - } - - return &acc -} - -func (c *ClusterClient) loadState() (*clusterState, error) { - if c.opt.ClusterSlots != nil { - slots, err := c.opt.ClusterSlots() - if err != nil { - return nil, err - } - return newClusterState(c.nodes, slots, "") - } - - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - if firstErr == nil { - firstErr = err - } - continue - } - - slots, err := node.Client.ClusterSlots().Result() - if err != nil { - if firstErr == nil { - firstErr = err - } - continue - } - - return newClusterState(c.nodes, slots, node.Client.opt.Addr) - } - - return nil, firstErr -} - -// reaper closes idle connections to the cluster. -func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { - ticker := time.NewTicker(idleCheckFrequency) - defer ticker.Stop() - - for range ticker.C { - nodes, err := c.nodes.All() - if err != nil { - break - } - - for _, node := range nodes { - _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() - if err != nil { - internal.Logf("ReapStaleConns failed: %s", err) - } - } - } -} - -func (c *ClusterClient) Pipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *ClusterClient) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.Pipeline().Pipelined(fn) -} - -func (c *ClusterClient) WrapProcessPipeline( - fn func(oldProcess func([]Cmder) error) func([]Cmder) error, -) { - c.processPipeline = fn(c.processPipeline) - c.processTxPipeline = fn(c.processTxPipeline) -} - -func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { - cmdsMap := newCmdsMap() - err := c.mapCmdsByNode(cmds, cmdsMap) - if err != nil { - setCmdsErr(cmds, err) - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - failedCmds := newCmdsMap() - var wg sync.WaitGroup - - for node, cmds := range cmdsMap.m { - wg.Add(1) - go func(node *clusterNode, cmds []Cmder) { - defer wg.Done() - - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } - return - } - - err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) - }(node, cmds) - } - - wg.Wait() - if len(failedCmds.m) == 0 { - break - } - cmdsMap = failedCmds - } - - return cmdsFirstErr(cmds) -} - -type cmdsMap struct { - mu sync.Mutex - m map[*clusterNode][]Cmder -} - -func newCmdsMap() *cmdsMap { - return &cmdsMap{ - m: make(map[*clusterNode][]Cmder), - } -} - -func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error { - state, err := c.state.Get() - if err != nil { - setCmdsErr(cmds, err) - return err - } - - cmdsAreReadOnly := c.cmdsAreReadOnly(cmds) - for _, cmd := range cmds { - var node *clusterNode - var err error - if cmdsAreReadOnly { - _, node, err = c.cmdSlotAndNode(cmd) - } else { - slot := c.cmdSlot(cmd) - node, err = state.slotMasterNode(slot) - } - if err != nil { - return err - } - cmdsMap.mu.Lock() - cmdsMap.m[node] = append(cmdsMap.m[node], cmd) - cmdsMap.mu.Unlock() - } - return nil -} - -func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { - for _, cmd := range cmds { - cmdInfo := c.cmdInfo(cmd.Name()) - if cmdInfo == nil || !cmdInfo.ReadOnly { - return false - } - } - return true -} - -func (c *ClusterClient) pipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, -) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmds...) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - return c.pipelineReadCmds(node, rd, cmds, failedCmds) - }) - return err -} - -func (c *ClusterClient) pipelineReadCmds( - node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, -) error { - var firstErr error - for _, cmd := range cmds { - err := cmd.readReply(rd) - if err == nil { - continue - } - - if c.checkMovedErr(cmd, err, failedCmds) { - continue - } - - if internal.IsRedisError(err) { - continue - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() - if firstErr == nil { - firstErr = err - } - } - return firstErr -} - -func (c *ClusterClient) checkMovedErr( - cmd Cmder, err error, failedCmds *cmdsMap, -) bool { - moved, ask, addr := internal.IsMovedError(err) - - if moved { - c.state.LazyReload() - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() - return true - } - - if ask { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], NewCmd("ASKING"), cmd) - failedCmds.mu.Unlock() - return true - } - - return false -} - -// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. -func (c *ClusterClient) TxPipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processTxPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.TxPipeline().Pipelined(fn) -} - -func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { - state, err := c.state.Get() - if err != nil { - return err - } - - cmdsMap := c.mapCmdsBySlot(cmds) - for slot, cmds := range cmdsMap { - node, err := state.slotMasterNode(slot) - if err != nil { - setCmdsErr(cmds, err) - continue - } - cmdsMap := map[*clusterNode][]Cmder{node: cmds} - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - failedCmds := newCmdsMap() - var wg sync.WaitGroup - - for node, cmds := range cmdsMap { - wg.Add(1) - go func(node *clusterNode, cmds []Cmder) { - defer wg.Done() - - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } - return - } - - err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) - }(node, cmds) - } - - wg.Wait() - if len(failedCmds.m) == 0 { - break - } - cmdsMap = failedCmds.m - } - } - - return cmdsFirstErr(cmds) -} - -func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { - cmdsMap := make(map[int][]Cmder) - for _, cmd := range cmds { - slot := c.cmdSlot(cmd) - cmdsMap[slot] = append(cmdsMap[slot], cmd) - } - return cmdsMap -} - -func (c *ClusterClient) txPipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, -) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return txPipelineWriteMulti(wr, cmds) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - err := c.txPipelineReadQueued(rd, cmds, failedCmds) - if err != nil { - setCmdsErr(cmds, err) - return err - } - return pipelineReadCmds(rd, cmds) - }) - return err -} - -func (c *ClusterClient) txPipelineReadQueued( - rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, -) error { - // Parse queued replies. - var statusCmd StatusCmd - if err := statusCmd.readReply(rd); err != nil { - return err - } - - for _, cmd := range cmds { - err := statusCmd.readReply(rd) - if err == nil { - continue - } - - if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { - continue - } - - return err - } - - // Parse number of replies. - line, err := rd.ReadLine() - if err != nil { - if err == Nil { - err = TxFailedErr - } - return err - } - - switch line[0] { - case proto.ErrorReply: - err := proto.ParseErrorReply(line) - for _, cmd := range cmds { - if !c.checkMovedErr(cmd, err, failedCmds) { - break - } - } - return err - case proto.ArrayReply: - // ok - default: - err := fmt.Errorf("redis: expected '*', but got line %q", line) - return err - } - - return nil -} - -func (c *ClusterClient) pubSub() *PubSub { - var node *clusterNode - pubsub := &PubSub{ - opt: c.opt.clientOptions(), - - newConn: func(channels []string) (*pool.Conn, error) { - if node != nil { - panic("node != nil") - } - - var err error - if len(channels) > 0 { - slot := hashtag.Slot(channels[0]) - node, err = c.slotMasterNode(slot) - } else { - node, err = c.nodes.Random() - } - if err != nil { - return nil, err - } - - cn, err := node.Client.newConn() - if err != nil { - node = nil - - return nil, err - } - - return cn, nil - }, - closeConn: func(cn *pool.Conn) error { - err := node.Client.connPool.CloseConn(cn) - node = nil - return err - }, - } - pubsub.init() - - return pubsub -} - -// Subscribe subscribes the client to the specified channels. -// Channels can be omitted to create empty subscription. -func (c *ClusterClient) Subscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.Subscribe(channels...) - } - return pubsub -} - -// PSubscribe subscribes the client to the given patterns. -// Patterns can be omitted to create empty subscription. -func (c *ClusterClient) PSubscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.PSubscribe(channels...) - } - return pubsub -} - -func appendUniqueNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { - for _, n := range nodes { - if n == node { - return nodes - } - } - return append(nodes, node) -} - -func appendIfNotExists(ss []string, es ...string) []string { -loop: - for _, e := range es { - for _, s := range ss { - if s == e { - continue loop - } - } - ss = append(ss, e) - } - return ss -} - -func remove(ss []string, es ...string) []string { - if len(es) == 0 { - return ss[:0] - } - for _, e := range es { - for i, s := range ss { - if s == e { - ss = append(ss[:i], ss[i+1:]...) - break - } - } - } - return ss -} diff --git a/vendor/github.com/go-redis/redis/cluster_commands.go b/vendor/github.com/go-redis/redis/cluster_commands.go deleted file mode 100644 index dff62c902d..0000000000 --- a/vendor/github.com/go-redis/redis/cluster_commands.go +++ /dev/null @@ -1,22 +0,0 @@ -package redis - -import "sync/atomic" - -func (c *ClusterClient) DBSize() *IntCmd { - cmd := NewIntCmd("dbsize") - var size int64 - err := c.ForEachMaster(func(master *Client) error { - n, err := master.DBSize().Result() - if err != nil { - return err - } - atomic.AddInt64(&size, n) - return nil - }) - if err != nil { - cmd.setErr(err) - return cmd - } - cmd.val = size - return cmd -} diff --git a/vendor/github.com/go-redis/redis/command.go b/vendor/github.com/go-redis/redis/command.go deleted file mode 100644 index c70973d3bf..0000000000 --- a/vendor/github.com/go-redis/redis/command.go +++ /dev/null @@ -1,1972 +0,0 @@ -package redis - -import ( - "fmt" - "net" - "strconv" - "strings" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/proto" -) - -type Cmder interface { - Name() string - Args() []interface{} - stringArg(int) string - - readReply(rd *proto.Reader) error - setErr(error) - - readTimeout() *time.Duration - - Err() error -} - -func setCmdsErr(cmds []Cmder, e error) { - for _, cmd := range cmds { - if cmd.Err() == nil { - cmd.setErr(e) - } - } -} - -func cmdsFirstErr(cmds []Cmder) error { - for _, cmd := range cmds { - if err := cmd.Err(); err != nil { - return err - } - } - return nil -} - -func writeCmd(wr *proto.Writer, cmds ...Cmder) error { - for _, cmd := range cmds { - err := wr.WriteArgs(cmd.Args()) - if err != nil { - return err - } - } - return nil -} - -func cmdString(cmd Cmder, val interface{}) string { - var ss []string - for _, arg := range cmd.Args() { - ss = append(ss, fmt.Sprint(arg)) - } - s := strings.Join(ss, " ") - if err := cmd.Err(); err != nil { - return s + ": " + err.Error() - } - if val != nil { - switch vv := val.(type) { - case []byte: - return s + ": " + string(vv) - default: - return s + ": " + fmt.Sprint(val) - } - } - return s - -} - -func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { - switch cmd.Name() { - case "eval", "evalsha": - if cmd.stringArg(2) != "0" { - return 3 - } - - return 0 - case "publish": - return 1 - } - if info == nil { - return 0 - } - return int(info.FirstKeyPos) -} - -//------------------------------------------------------------------------------ - -type baseCmd struct { - _args []interface{} - err error - - _readTimeout *time.Duration -} - -var _ Cmder = (*Cmd)(nil) - -func (cmd *baseCmd) Err() error { - return cmd.err -} - -func (cmd *baseCmd) Args() []interface{} { - return cmd._args -} - -func (cmd *baseCmd) stringArg(pos int) string { - if pos < 0 || pos >= len(cmd._args) { - return "" - } - s, _ := cmd._args[pos].(string) - return s -} - -func (cmd *baseCmd) Name() string { - if len(cmd._args) > 0 { - // Cmd name must be lower cased. - s := internal.ToLower(cmd.stringArg(0)) - cmd._args[0] = s - return s - } - return "" -} - -func (cmd *baseCmd) readTimeout() *time.Duration { - return cmd._readTimeout -} - -func (cmd *baseCmd) setReadTimeout(d time.Duration) { - cmd._readTimeout = &d -} - -func (cmd *baseCmd) setErr(e error) { - cmd.err = e -} - -//------------------------------------------------------------------------------ - -type Cmd struct { - baseCmd - - val interface{} -} - -func NewCmd(args ...interface{}) *Cmd { - return &Cmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *Cmd) Val() interface{} { - return cmd.val -} - -func (cmd *Cmd) Result() (interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *Cmd) String() (string, error) { - if cmd.err != nil { - return "", cmd.err - } - switch val := cmd.val.(type) { - case string: - return val, nil - default: - err := fmt.Errorf("redis: unexpected type=%T for String", val) - return "", err - } -} - -func (cmd *Cmd) Int() (int, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return int(val), nil - case string: - return strconv.Atoi(val) - default: - err := fmt.Errorf("redis: unexpected type=%T for Int", val) - return 0, err - } -} - -func (cmd *Cmd) Int64() (int64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return val, nil - case string: - return strconv.ParseInt(val, 10, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Int64", val) - return 0, err - } -} - -func (cmd *Cmd) Uint64() (uint64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return uint64(val), nil - case string: - return strconv.ParseUint(val, 10, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Uint64", val) - return 0, err - } -} - -func (cmd *Cmd) Float32() (float32, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return float32(val), nil - case string: - f, err := strconv.ParseFloat(val, 32) - if err != nil { - return 0, err - } - return float32(f), nil - default: - err := fmt.Errorf("redis: unexpected type=%T for Float32", val) - return 0, err - } -} - -func (cmd *Cmd) Float64() (float64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return float64(val), nil - case string: - return strconv.ParseFloat(val, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Float64", val) - return 0, err - } -} - -func (cmd *Cmd) Bool() (bool, error) { - if cmd.err != nil { - return false, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return val != 0, nil - case string: - return strconv.ParseBool(val) - default: - err := fmt.Errorf("redis: unexpected type=%T for Bool", val) - return false, err - } -} - -func (cmd *Cmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadReply(sliceParser) - return cmd.err -} - -// Implements proto.MultiBulkParse -func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { - vals := make([]interface{}, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(sliceParser) - if err != nil { - if err == Nil { - vals = append(vals, nil) - continue - } - if err, ok := err.(proto.RedisError); ok { - vals = append(vals, err) - continue - } - return nil, err - } - - switch v := v.(type) { - case string: - vals = append(vals, v) - default: - vals = append(vals, v) - } - } - return vals, nil -} - -//------------------------------------------------------------------------------ - -type SliceCmd struct { - baseCmd - - val []interface{} -} - -var _ Cmder = (*SliceCmd)(nil) - -func NewSliceCmd(args ...interface{}) *SliceCmd { - return &SliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *SliceCmd) Val() []interface{} { - return cmd.val -} - -func (cmd *SliceCmd) Result() ([]interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *SliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *SliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(sliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]interface{}) - return nil -} - -//------------------------------------------------------------------------------ - -type StatusCmd struct { - baseCmd - - val string -} - -var _ Cmder = (*StatusCmd)(nil) - -func NewStatusCmd(args ...interface{}) *StatusCmd { - return &StatusCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StatusCmd) Val() string { - return cmd.val -} - -func (cmd *StatusCmd) Result() (string, error) { - return cmd.val, cmd.err -} - -func (cmd *StatusCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StatusCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadString() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type IntCmd struct { - baseCmd - - val int64 -} - -var _ Cmder = (*IntCmd)(nil) - -func NewIntCmd(args ...interface{}) *IntCmd { - return &IntCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *IntCmd) Val() int64 { - return cmd.val -} - -func (cmd *IntCmd) Result() (int64, error) { - return cmd.val, cmd.err -} - -func (cmd *IntCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *IntCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadIntReply() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type DurationCmd struct { - baseCmd - - val time.Duration - precision time.Duration -} - -var _ Cmder = (*DurationCmd)(nil) - -func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { - return &DurationCmd{ - baseCmd: baseCmd{_args: args}, - precision: precision, - } -} - -func (cmd *DurationCmd) Val() time.Duration { - return cmd.val -} - -func (cmd *DurationCmd) Result() (time.Duration, error) { - return cmd.val, cmd.err -} - -func (cmd *DurationCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *DurationCmd) readReply(rd *proto.Reader) error { - var n int64 - n, cmd.err = rd.ReadIntReply() - if cmd.err != nil { - return cmd.err - } - cmd.val = time.Duration(n) * cmd.precision - return nil -} - -//------------------------------------------------------------------------------ - -type TimeCmd struct { - baseCmd - - val time.Time -} - -var _ Cmder = (*TimeCmd)(nil) - -func NewTimeCmd(args ...interface{}) *TimeCmd { - return &TimeCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *TimeCmd) Val() time.Time { - return cmd.val -} - -func (cmd *TimeCmd) Result() (time.Time, error) { - return cmd.val, cmd.err -} - -func (cmd *TimeCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *TimeCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(timeParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(time.Time) - return nil -} - -// Implements proto.MultiBulkParse -func timeParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d elements, expected 2", n) - } - - sec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - microsec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - return time.Unix(sec, microsec*1000), nil -} - -//------------------------------------------------------------------------------ - -type BoolCmd struct { - baseCmd - - val bool -} - -var _ Cmder = (*BoolCmd)(nil) - -func NewBoolCmd(args ...interface{}) *BoolCmd { - return &BoolCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *BoolCmd) Val() bool { - return cmd.val -} - -func (cmd *BoolCmd) Result() (bool, error) { - return cmd.val, cmd.err -} - -func (cmd *BoolCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *BoolCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadReply(nil) - // `SET key value NX` returns nil when key already exists. But - // `SETNX key value` returns bool (0/1). So convert nil to bool. - // TODO: is this okay? - if cmd.err == Nil { - cmd.val = false - cmd.err = nil - return nil - } - if cmd.err != nil { - return cmd.err - } - switch v := v.(type) { - case int64: - cmd.val = v == 1 - return nil - case string: - cmd.val = v == "OK" - return nil - default: - cmd.err = fmt.Errorf("got %T, wanted int64 or string", v) - return cmd.err - } -} - -//------------------------------------------------------------------------------ - -type StringCmd struct { - baseCmd - - val string -} - -var _ Cmder = (*StringCmd)(nil) - -func NewStringCmd(args ...interface{}) *StringCmd { - return &StringCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringCmd) Val() string { - return cmd.val -} - -func (cmd *StringCmd) Result() (string, error) { - return cmd.Val(), cmd.err -} - -func (cmd *StringCmd) Bytes() ([]byte, error) { - return []byte(cmd.val), cmd.err -} - -func (cmd *StringCmd) Int() (int, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.Atoi(cmd.Val()) -} - -func (cmd *StringCmd) Int64() (int64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseInt(cmd.Val(), 10, 64) -} - -func (cmd *StringCmd) Uint64() (uint64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseUint(cmd.Val(), 10, 64) -} - -func (cmd *StringCmd) Float32() (float32, error) { - if cmd.err != nil { - return 0, cmd.err - } - f, err := strconv.ParseFloat(cmd.Val(), 32) - if err != nil { - return 0, err - } - return float32(f), nil -} - -func (cmd *StringCmd) Float64() (float64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseFloat(cmd.Val(), 64) -} - -func (cmd *StringCmd) Scan(val interface{}) error { - if cmd.err != nil { - return cmd.err - } - return proto.Scan([]byte(cmd.val), val) -} - -func (cmd *StringCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadString() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type FloatCmd struct { - baseCmd - - val float64 -} - -var _ Cmder = (*FloatCmd)(nil) - -func NewFloatCmd(args ...interface{}) *FloatCmd { - return &FloatCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *FloatCmd) Val() float64 { - return cmd.val -} - -func (cmd *FloatCmd) Result() (float64, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *FloatCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *FloatCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadFloatReply() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type StringSliceCmd struct { - baseCmd - - val []string -} - -var _ Cmder = (*StringSliceCmd)(nil) - -func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { - return &StringSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringSliceCmd) Val() []string { - return cmd.val -} - -func (cmd *StringSliceCmd) Result() ([]string, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *StringSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { - return proto.ScanSlice(cmd.Val(), container) -} - -func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ss := make([]string, 0, n) - for i := int64(0); i < n; i++ { - switch s, err := rd.ReadString(); { - case err == Nil: - ss = append(ss, "") - case err != nil: - return nil, err - default: - ss = append(ss, s) - } - } - return ss, nil -} - -//------------------------------------------------------------------------------ - -type BoolSliceCmd struct { - baseCmd - - val []bool -} - -var _ Cmder = (*BoolSliceCmd)(nil) - -func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { - return &BoolSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *BoolSliceCmd) Val() []bool { - return cmd.val -} - -func (cmd *BoolSliceCmd) Result() ([]bool, error) { - return cmd.val, cmd.err -} - -func (cmd *BoolSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(boolSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]bool) - return nil -} - -// Implements proto.MultiBulkParse -func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - bools := make([]bool, 0, n) - for i := int64(0); i < n; i++ { - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - bools = append(bools, n == 1) - } - return bools, nil -} - -//------------------------------------------------------------------------------ - -type StringStringMapCmd struct { - baseCmd - - val map[string]string -} - -var _ Cmder = (*StringStringMapCmd)(nil) - -func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { - return &StringStringMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringStringMapCmd) Val() map[string]string { - return cmd.val -} - -func (cmd *StringStringMapCmd) Result() (map[string]string, error) { - return cmd.val, cmd.err -} - -func (cmd *StringStringMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStringMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]string, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type StringIntMapCmd struct { - baseCmd - - val map[string]int64 -} - -var _ Cmder = (*StringIntMapCmd)(nil) - -func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { - return &StringIntMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringIntMapCmd) Val() map[string]int64 { - return cmd.val -} - -func (cmd *StringIntMapCmd) Result() (map[string]int64, error) { - return cmd.val, cmd.err -} - -func (cmd *StringIntMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringIntMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]int64) - return nil -} - -// Implements proto.MultiBulkParse -func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]int64, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - m[key] = n - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type StringStructMapCmd struct { - baseCmd - - val map[string]struct{} -} - -var _ Cmder = (*StringStructMapCmd)(nil) - -func NewStringStructMapCmd(args ...interface{}) *StringStructMapCmd { - return &StringStructMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringStructMapCmd) Val() map[string]struct{} { - return cmd.val -} - -func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) { - return cmd.val, cmd.err -} - -func (cmd *StringStructMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStructMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]struct{}) - return nil -} - -// Implements proto.MultiBulkParse -func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]struct{}, n) - for i := int64(0); i < n; i++ { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = struct{}{} - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type XMessage struct { - ID string - Values map[string]interface{} -} - -type XMessageSliceCmd struct { - baseCmd - - val []XMessage -} - -var _ Cmder = (*XMessageSliceCmd)(nil) - -func NewXMessageSliceCmd(args ...interface{}) *XMessageSliceCmd { - return &XMessageSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XMessageSliceCmd) Val() []XMessage { - return cmd.val -} - -func (cmd *XMessageSliceCmd) Result() ([]XMessage, error) { - return cmd.val, cmd.err -} - -func (cmd *XMessageSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xMessageSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XMessage) - return nil -} - -// Implements proto.MultiBulkParse -func xMessageSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - msgs := make([]XMessage, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - id, err := rd.ReadString() - if err != nil { - return nil, err - } - - var values map[string]interface{} - - v, err := rd.ReadArrayReply(stringInterfaceMapParser) - if err != nil { - if err != proto.Nil { - return nil, err - } - } else { - values = v.(map[string]interface{}) - } - - msgs = append(msgs, XMessage{ - ID: id, - Values: values, - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return msgs, nil -} - -// Implements proto.MultiBulkParse -func stringInterfaceMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]interface{}, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type XStream struct { - Stream string - Messages []XMessage -} - -type XStreamSliceCmd struct { - baseCmd - - val []XStream -} - -var _ Cmder = (*XStreamSliceCmd)(nil) - -func NewXStreamSliceCmd(args ...interface{}) *XStreamSliceCmd { - return &XStreamSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XStreamSliceCmd) Val() []XStream { - return cmd.val -} - -func (cmd *XStreamSliceCmd) Result() ([]XStream, error) { - return cmd.val, cmd.err -} - -func (cmd *XStreamSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xStreamSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XStream) - return nil -} - -// Implements proto.MultiBulkParse -func xStreamSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XStream, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - stream, err := rd.ReadString() - if err != nil { - return nil, err - } - - v, err := rd.ReadArrayReply(xMessageSliceParser) - if err != nil { - return nil, err - } - - ret = append(ret, XStream{ - Stream: stream, - Messages: v.([]XMessage), - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return ret, nil -} - -//------------------------------------------------------------------------------ - -type XPending struct { - Count int64 - Lower string - Higher string - Consumers map[string]int64 -} - -type XPendingCmd struct { - baseCmd - val *XPending -} - -var _ Cmder = (*XPendingCmd)(nil) - -func NewXPendingCmd(args ...interface{}) *XPendingCmd { - return &XPendingCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XPendingCmd) Val() *XPending { - return cmd.val -} - -func (cmd *XPendingCmd) Result() (*XPending, error) { - return cmd.val, cmd.err -} - -func (cmd *XPendingCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.(*XPending) - return nil -} - -func xPendingParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - count, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - lower, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - higher, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - pending := &XPending{ - Count: count, - Lower: lower, - Higher: higher, - } - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - for i := int64(0); i < n; i++ { - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - consumerName, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumerPending, err := rd.ReadInt() - if err != nil { - return nil, err - } - - if pending.Consumers == nil { - pending.Consumers = make(map[string]int64) - } - pending.Consumers[consumerName] = consumerPending - - return nil, nil - }) - if err != nil { - return nil, err - } - } - return nil, nil - }) - if err != nil && err != Nil { - return nil, err - } - - return pending, nil -} - -//------------------------------------------------------------------------------ - -type XPendingExt struct { - Id string - Consumer string - Idle time.Duration - RetryCount int64 -} - -type XPendingExtCmd struct { - baseCmd - val []XPendingExt -} - -var _ Cmder = (*XPendingExtCmd)(nil) - -func NewXPendingExtCmd(args ...interface{}) *XPendingExtCmd { - return &XPendingExtCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XPendingExtCmd) Val() []XPendingExt { - return cmd.val -} - -func (cmd *XPendingExtCmd) Result() ([]XPendingExt, error) { - return cmd.val, cmd.err -} - -func (cmd *XPendingExtCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingExtSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.([]XPendingExt) - return nil -} - -func xPendingExtSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XPendingExt, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - id, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumer, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - idle, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - retryCount, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - ret = append(ret, XPendingExt{ - Id: id, - Consumer: consumer, - Idle: time.Duration(idle) * time.Millisecond, - RetryCount: retryCount, - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return ret, nil -} - -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ - -type ZSliceCmd struct { - baseCmd - - val []Z -} - -var _ Cmder = (*ZSliceCmd)(nil) - -func NewZSliceCmd(args ...interface{}) *ZSliceCmd { - return &ZSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ZSliceCmd) Val() []Z { - return cmd.val -} - -func (cmd *ZSliceCmd) Result() ([]Z, error) { - return cmd.val, cmd.err -} - -func (cmd *ZSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]Z) - return nil -} - -// Implements proto.MultiBulkParse -func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - zz := make([]Z, n/2) - for i := int64(0); i < n; i += 2 { - var err error - - z := &zz[i/2] - - z.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - return zz, nil -} - -//------------------------------------------------------------------------------ - -type ZWithKeyCmd struct { - baseCmd - - val ZWithKey -} - -var _ Cmder = (*ZWithKeyCmd)(nil) - -func NewZWithKeyCmd(args ...interface{}) *ZWithKeyCmd { - return &ZWithKeyCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ZWithKeyCmd) Val() ZWithKey { - return cmd.val -} - -func (cmd *ZWithKeyCmd) Result() (ZWithKey, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *ZWithKeyCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zWithKeyParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(ZWithKey) - return nil -} - -// Implements proto.MultiBulkParse -func zWithKeyParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 3 { - return nil, fmt.Errorf("got %d elements, expected 3", n) - } - - var z ZWithKey - var err error - - z.Key, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - return z, nil -} - -//------------------------------------------------------------------------------ - -type ScanCmd struct { - baseCmd - - page []string - cursor uint64 - - process func(cmd Cmder) error -} - -var _ Cmder = (*ScanCmd)(nil) - -func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { - return &ScanCmd{ - baseCmd: baseCmd{_args: args}, - process: process, - } -} - -func (cmd *ScanCmd) Val() (keys []string, cursor uint64) { - return cmd.page, cmd.cursor -} - -func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) { - return cmd.page, cmd.cursor, cmd.err -} - -func (cmd *ScanCmd) String() string { - return cmdString(cmd, cmd.page) -} - -func (cmd *ScanCmd) readReply(rd *proto.Reader) error { - cmd.page, cmd.cursor, cmd.err = rd.ReadScanReply() - return cmd.err -} - -// Iterator creates a new ScanIterator. -func (cmd *ScanCmd) Iterator() *ScanIterator { - return &ScanIterator{ - cmd: cmd, - } -} - -//------------------------------------------------------------------------------ - -type ClusterNode struct { - Id string - Addr string -} - -type ClusterSlot struct { - Start int - End int - Nodes []ClusterNode -} - -type ClusterSlotsCmd struct { - baseCmd - - val []ClusterSlot -} - -var _ Cmder = (*ClusterSlotsCmd)(nil) - -func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { - return &ClusterSlotsCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { - return cmd.val -} - -func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *ClusterSlotsCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(clusterSlotsParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]ClusterSlot) - return nil -} - -// Implements proto.MultiBulkParse -func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) { - slots := make([]ClusterSlot, n) - for i := 0; i < len(slots); i++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n < 2 { - err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) - return nil, err - } - - start, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - end, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - nodes := make([]ClusterNode, n-2) - for j := 0; j < len(nodes); j++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n != 2 && n != 3 { - err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) - return nil, err - } - - ip, err := rd.ReadString() - if err != nil { - return nil, err - } - - port, err := rd.ReadString() - if err != nil { - return nil, err - } - - nodes[j].Addr = net.JoinHostPort(ip, port) - - if n == 3 { - id, err := rd.ReadString() - if err != nil { - return nil, err - } - nodes[j].Id = id - } - } - - slots[i] = ClusterSlot{ - Start: int(start), - End: int(end), - Nodes: nodes, - } - } - return slots, nil -} - -//------------------------------------------------------------------------------ - -// GeoLocation is used with GeoAdd to add geospatial location. -type GeoLocation struct { - Name string - Longitude, Latitude, Dist float64 - GeoHash int64 -} - -// GeoRadiusQuery is used with GeoRadius to query geospatial index. -type GeoRadiusQuery struct { - Radius float64 - // Can be m, km, ft, or mi. Default is km. - Unit string - WithCoord bool - WithDist bool - WithGeoHash bool - Count int - // Can be ASC or DESC. Default is no sort order. - Sort string - Store string - StoreDist string -} - -type GeoLocationCmd struct { - baseCmd - - q *GeoRadiusQuery - locations []GeoLocation -} - -var _ Cmder = (*GeoLocationCmd)(nil) - -func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { - args = append(args, q.Radius) - if q.Unit != "" { - args = append(args, q.Unit) - } else { - args = append(args, "km") - } - if q.WithCoord { - args = append(args, "withcoord") - } - if q.WithDist { - args = append(args, "withdist") - } - if q.WithGeoHash { - args = append(args, "withhash") - } - if q.Count > 0 { - args = append(args, "count", q.Count) - } - if q.Sort != "" { - args = append(args, q.Sort) - } - if q.Store != "" { - args = append(args, "store") - args = append(args, q.Store) - } - if q.StoreDist != "" { - args = append(args, "storedist") - args = append(args, q.StoreDist) - } - return &GeoLocationCmd{ - baseCmd: baseCmd{_args: args}, - q: q, - } -} - -func (cmd *GeoLocationCmd) Val() []GeoLocation { - return cmd.locations -} - -func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) { - return cmd.locations, cmd.err -} - -func (cmd *GeoLocationCmd) String() string { - return cmdString(cmd, cmd.locations) -} - -func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) - if cmd.err != nil { - return cmd.err - } - cmd.locations = v.([]GeoLocation) - return nil -} - -func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - var loc GeoLocation - var err error - - loc.Name, err = rd.ReadString() - if err != nil { - return nil, err - } - if q.WithDist { - loc.Dist, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - if q.WithGeoHash { - loc.GeoHash, err = rd.ReadIntReply() - if err != nil { - return nil, err - } - } - if q.WithCoord { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n != 2 { - return nil, fmt.Errorf("got %d coordinates, expected 2", n) - } - - loc.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - loc.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - - return &loc, nil - } -} - -func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - locs := make([]GeoLocation, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(newGeoLocationParser(q)) - if err != nil { - return nil, err - } - switch vv := v.(type) { - case string: - locs = append(locs, GeoLocation{ - Name: vv, - }) - case *GeoLocation: - locs = append(locs, *vv) - default: - return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) - } - } - return locs, nil - } -} - -//------------------------------------------------------------------------------ - -type GeoPos struct { - Longitude, Latitude float64 -} - -type GeoPosCmd struct { - baseCmd - - positions []*GeoPos -} - -var _ Cmder = (*GeoPosCmd)(nil) - -func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { - return &GeoPosCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *GeoPosCmd) Val() []*GeoPos { - return cmd.positions -} - -func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *GeoPosCmd) String() string { - return cmdString(cmd, cmd.positions) -} - -func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(geoPosSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.positions = v.([]*GeoPos) - return nil -} - -func geoPosSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - positions := make([]*GeoPos, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(geoPosParser) - if err != nil { - if err == Nil { - positions = append(positions, nil) - continue - } - return nil, err - } - switch v := v.(type) { - case *GeoPos: - positions = append(positions, v) - default: - return nil, fmt.Errorf("got %T, expected *GeoPos", v) - } - } - return positions, nil -} - -func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) { - var pos GeoPos - var err error - - pos.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - pos.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - return &pos, nil -} - -//------------------------------------------------------------------------------ - -type CommandInfo struct { - Name string - Arity int8 - Flags []string - FirstKeyPos int8 - LastKeyPos int8 - StepCount int8 - ReadOnly bool -} - -type CommandsInfoCmd struct { - baseCmd - - val map[string]*CommandInfo -} - -var _ Cmder = (*CommandsInfoCmd)(nil) - -func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { - return &CommandsInfoCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { - return cmd.val -} - -func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *CommandsInfoCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(commandInfoSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]*CommandInfo) - return nil -} - -// Implements proto.MultiBulkParse -func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]*CommandInfo, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(commandInfoParser) - if err != nil { - return nil, err - } - vv := v.(*CommandInfo) - m[vv.Name] = vv - - } - return m, nil -} - -func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { - var cmd CommandInfo - var err error - - if n != 6 { - return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n) - } - - cmd.Name, err = rd.ReadString() - if err != nil { - return nil, err - } - - arity, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.Arity = int8(arity) - - flags, err := rd.ReadReply(stringSliceParser) - if err != nil { - return nil, err - } - cmd.Flags = flags.([]string) - - firstKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.FirstKeyPos = int8(firstKeyPos) - - lastKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.LastKeyPos = int8(lastKeyPos) - - stepCount, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.StepCount = int8(stepCount) - - for _, flag := range cmd.Flags { - if flag == "readonly" { - cmd.ReadOnly = true - break - } - } - - return &cmd, nil -} - -//------------------------------------------------------------------------------ - -type cmdsInfoCache struct { - fn func() (map[string]*CommandInfo, error) - - once internal.Once - cmds map[string]*CommandInfo -} - -func newCmdsInfoCache(fn func() (map[string]*CommandInfo, error)) *cmdsInfoCache { - return &cmdsInfoCache{ - fn: fn, - } -} - -func (c *cmdsInfoCache) Get() (map[string]*CommandInfo, error) { - err := c.once.Do(func() error { - cmds, err := c.fn() - if err != nil { - return err - } - c.cmds = cmds - return nil - }) - return c.cmds, err -} diff --git a/vendor/github.com/go-redis/redis/commands.go b/vendor/github.com/go-redis/redis/commands.go deleted file mode 100644 index 653e4abe96..0000000000 --- a/vendor/github.com/go-redis/redis/commands.go +++ /dev/null @@ -1,2583 +0,0 @@ -package redis - -import ( - "errors" - "io" - "time" - - "github.com/go-redis/redis/internal" -) - -func usePrecise(dur time.Duration) bool { - return dur < time.Second || dur%time.Second != 0 -} - -func formatMs(dur time.Duration) int64 { - if dur > 0 && dur < time.Millisecond { - internal.Logf( - "specified duration is %s, but minimal supported value is %s", - dur, time.Millisecond, - ) - } - return int64(dur / time.Millisecond) -} - -func formatSec(dur time.Duration) int64 { - if dur > 0 && dur < time.Second { - internal.Logf( - "specified duration is %s, but minimal supported value is %s", - dur, time.Second, - ) - } - return int64(dur / time.Second) -} - -func appendArgs(dst, src []interface{}) []interface{} { - if len(src) == 1 { - if ss, ok := src[0].([]string); ok { - for _, s := range ss { - dst = append(dst, s) - } - return dst - } - } - - for _, v := range src { - dst = append(dst, v) - } - return dst -} - -type Cmdable interface { - Pipeline() Pipeliner - Pipelined(fn func(Pipeliner) error) ([]Cmder, error) - - TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) - TxPipeline() Pipeliner - - Command() *CommandsInfoCmd - ClientGetName() *StringCmd - Echo(message interface{}) *StringCmd - Ping() *StatusCmd - Quit() *StatusCmd - Del(keys ...string) *IntCmd - Unlink(keys ...string) *IntCmd - Dump(key string) *StringCmd - Exists(keys ...string) *IntCmd - Expire(key string, expiration time.Duration) *BoolCmd - ExpireAt(key string, tm time.Time) *BoolCmd - Keys(pattern string) *StringSliceCmd - Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd - Move(key string, db int64) *BoolCmd - ObjectRefCount(key string) *IntCmd - ObjectEncoding(key string) *StringCmd - ObjectIdleTime(key string) *DurationCmd - Persist(key string) *BoolCmd - PExpire(key string, expiration time.Duration) *BoolCmd - PExpireAt(key string, tm time.Time) *BoolCmd - PTTL(key string) *DurationCmd - RandomKey() *StringCmd - Rename(key, newkey string) *StatusCmd - RenameNX(key, newkey string) *BoolCmd - Restore(key string, ttl time.Duration, value string) *StatusCmd - RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd - Sort(key string, sort *Sort) *StringSliceCmd - SortStore(key, store string, sort *Sort) *IntCmd - SortInterfaces(key string, sort *Sort) *SliceCmd - Touch(keys ...string) *IntCmd - TTL(key string) *DurationCmd - Type(key string) *StatusCmd - Scan(cursor uint64, match string, count int64) *ScanCmd - SScan(key string, cursor uint64, match string, count int64) *ScanCmd - HScan(key string, cursor uint64, match string, count int64) *ScanCmd - ZScan(key string, cursor uint64, match string, count int64) *ScanCmd - Append(key, value string) *IntCmd - BitCount(key string, bitCount *BitCount) *IntCmd - BitOpAnd(destKey string, keys ...string) *IntCmd - BitOpOr(destKey string, keys ...string) *IntCmd - BitOpXor(destKey string, keys ...string) *IntCmd - BitOpNot(destKey string, key string) *IntCmd - BitPos(key string, bit int64, pos ...int64) *IntCmd - Decr(key string) *IntCmd - DecrBy(key string, decrement int64) *IntCmd - Get(key string) *StringCmd - GetBit(key string, offset int64) *IntCmd - GetRange(key string, start, end int64) *StringCmd - GetSet(key string, value interface{}) *StringCmd - Incr(key string) *IntCmd - IncrBy(key string, value int64) *IntCmd - IncrByFloat(key string, value float64) *FloatCmd - MGet(keys ...string) *SliceCmd - MSet(pairs ...interface{}) *StatusCmd - MSetNX(pairs ...interface{}) *BoolCmd - Set(key string, value interface{}, expiration time.Duration) *StatusCmd - SetBit(key string, offset int64, value int) *IntCmd - SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd - SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd - SetRange(key string, offset int64, value string) *IntCmd - StrLen(key string) *IntCmd - HDel(key string, fields ...string) *IntCmd - HExists(key, field string) *BoolCmd - HGet(key, field string) *StringCmd - HGetAll(key string) *StringStringMapCmd - HIncrBy(key, field string, incr int64) *IntCmd - HIncrByFloat(key, field string, incr float64) *FloatCmd - HKeys(key string) *StringSliceCmd - HLen(key string) *IntCmd - HMGet(key string, fields ...string) *SliceCmd - HMSet(key string, fields map[string]interface{}) *StatusCmd - HSet(key, field string, value interface{}) *BoolCmd - HSetNX(key, field string, value interface{}) *BoolCmd - HVals(key string) *StringSliceCmd - BLPop(timeout time.Duration, keys ...string) *StringSliceCmd - BRPop(timeout time.Duration, keys ...string) *StringSliceCmd - BRPopLPush(source, destination string, timeout time.Duration) *StringCmd - LIndex(key string, index int64) *StringCmd - LInsert(key, op string, pivot, value interface{}) *IntCmd - LInsertBefore(key string, pivot, value interface{}) *IntCmd - LInsertAfter(key string, pivot, value interface{}) *IntCmd - LLen(key string) *IntCmd - LPop(key string) *StringCmd - LPush(key string, values ...interface{}) *IntCmd - LPushX(key string, value interface{}) *IntCmd - LRange(key string, start, stop int64) *StringSliceCmd - LRem(key string, count int64, value interface{}) *IntCmd - LSet(key string, index int64, value interface{}) *StatusCmd - LTrim(key string, start, stop int64) *StatusCmd - RPop(key string) *StringCmd - RPopLPush(source, destination string) *StringCmd - RPush(key string, values ...interface{}) *IntCmd - RPushX(key string, value interface{}) *IntCmd - SAdd(key string, members ...interface{}) *IntCmd - SCard(key string) *IntCmd - SDiff(keys ...string) *StringSliceCmd - SDiffStore(destination string, keys ...string) *IntCmd - SInter(keys ...string) *StringSliceCmd - SInterStore(destination string, keys ...string) *IntCmd - SIsMember(key string, member interface{}) *BoolCmd - SMembers(key string) *StringSliceCmd - SMembersMap(key string) *StringStructMapCmd - SMove(source, destination string, member interface{}) *BoolCmd - SPop(key string) *StringCmd - SPopN(key string, count int64) *StringSliceCmd - SRandMember(key string) *StringCmd - SRandMemberN(key string, count int64) *StringSliceCmd - SRem(key string, members ...interface{}) *IntCmd - SUnion(keys ...string) *StringSliceCmd - SUnionStore(destination string, keys ...string) *IntCmd - XAdd(a *XAddArgs) *StringCmd - XDel(stream string, ids ...string) *IntCmd - XLen(stream string) *IntCmd - XRange(stream, start, stop string) *XMessageSliceCmd - XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd - XRevRange(stream string, start, stop string) *XMessageSliceCmd - XRevRangeN(stream string, start, stop string, count int64) *XMessageSliceCmd - XRead(a *XReadArgs) *XStreamSliceCmd - XReadStreams(streams ...string) *XStreamSliceCmd - XGroupCreate(stream, group, start string) *StatusCmd - XGroupCreateMkStream(stream, group, start string) *StatusCmd - XGroupSetID(stream, group, start string) *StatusCmd - XGroupDestroy(stream, group string) *IntCmd - XGroupDelConsumer(stream, group, consumer string) *IntCmd - XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd - XAck(stream, group string, ids ...string) *IntCmd - XPending(stream, group string) *XPendingCmd - XPendingExt(a *XPendingExtArgs) *XPendingExtCmd - XClaim(a *XClaimArgs) *XMessageSliceCmd - XClaimJustID(a *XClaimArgs) *StringSliceCmd - XTrim(key string, maxLen int64) *IntCmd - XTrimApprox(key string, maxLen int64) *IntCmd - BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd - BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd - ZAdd(key string, members ...Z) *IntCmd - ZAddNX(key string, members ...Z) *IntCmd - ZAddXX(key string, members ...Z) *IntCmd - ZAddCh(key string, members ...Z) *IntCmd - ZAddNXCh(key string, members ...Z) *IntCmd - ZAddXXCh(key string, members ...Z) *IntCmd - ZIncr(key string, member Z) *FloatCmd - ZIncrNX(key string, member Z) *FloatCmd - ZIncrXX(key string, member Z) *FloatCmd - ZCard(key string) *IntCmd - ZCount(key, min, max string) *IntCmd - ZLexCount(key, min, max string) *IntCmd - ZIncrBy(key string, increment float64, member string) *FloatCmd - ZInterStore(destination string, store ZStore, keys ...string) *IntCmd - ZPopMax(key string, count ...int64) *ZSliceCmd - ZPopMin(key string, count ...int64) *ZSliceCmd - ZRange(key string, start, stop int64) *StringSliceCmd - ZRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd - ZRank(key, member string) *IntCmd - ZRem(key string, members ...interface{}) *IntCmd - ZRemRangeByRank(key string, start, stop int64) *IntCmd - ZRemRangeByScore(key, min, max string) *IntCmd - ZRemRangeByLex(key, min, max string) *IntCmd - ZRevRange(key string, start, stop int64) *StringSliceCmd - ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd - ZRevRank(key, member string) *IntCmd - ZScore(key, member string) *FloatCmd - ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd - PFAdd(key string, els ...interface{}) *IntCmd - PFCount(keys ...string) *IntCmd - PFMerge(dest string, keys ...string) *StatusCmd - BgRewriteAOF() *StatusCmd - BgSave() *StatusCmd - ClientKill(ipPort string) *StatusCmd - ClientKillByFilter(keys ...string) *IntCmd - ClientList() *StringCmd - ClientPause(dur time.Duration) *BoolCmd - ClientID() *IntCmd - ConfigGet(parameter string) *SliceCmd - ConfigResetStat() *StatusCmd - ConfigSet(parameter, value string) *StatusCmd - ConfigRewrite() *StatusCmd - DBSize() *IntCmd - FlushAll() *StatusCmd - FlushAllAsync() *StatusCmd - FlushDB() *StatusCmd - FlushDBAsync() *StatusCmd - Info(section ...string) *StringCmd - LastSave() *IntCmd - Save() *StatusCmd - Shutdown() *StatusCmd - ShutdownSave() *StatusCmd - ShutdownNoSave() *StatusCmd - SlaveOf(host, port string) *StatusCmd - Time() *TimeCmd - Eval(script string, keys []string, args ...interface{}) *Cmd - EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd - ScriptExists(hashes ...string) *BoolSliceCmd - ScriptFlush() *StatusCmd - ScriptKill() *StatusCmd - ScriptLoad(script string) *StringCmd - DebugObject(key string) *StringCmd - Publish(channel string, message interface{}) *IntCmd - PubSubChannels(pattern string) *StringSliceCmd - PubSubNumSub(channels ...string) *StringIntMapCmd - PubSubNumPat() *IntCmd - ClusterSlots() *ClusterSlotsCmd - ClusterNodes() *StringCmd - ClusterMeet(host, port string) *StatusCmd - ClusterForget(nodeID string) *StatusCmd - ClusterReplicate(nodeID string) *StatusCmd - ClusterResetSoft() *StatusCmd - ClusterResetHard() *StatusCmd - ClusterInfo() *StringCmd - ClusterKeySlot(key string) *IntCmd - ClusterGetKeysInSlot(slot int, count int) *StringSliceCmd - ClusterCountFailureReports(nodeID string) *IntCmd - ClusterCountKeysInSlot(slot int) *IntCmd - ClusterDelSlots(slots ...int) *StatusCmd - ClusterDelSlotsRange(min, max int) *StatusCmd - ClusterSaveConfig() *StatusCmd - ClusterSlaves(nodeID string) *StringSliceCmd - ClusterFailover() *StatusCmd - ClusterAddSlots(slots ...int) *StatusCmd - ClusterAddSlotsRange(min, max int) *StatusCmd - GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd - GeoPos(key string, members ...string) *GeoPosCmd - GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoDist(key string, member1, member2, unit string) *FloatCmd - GeoHash(key string, members ...string) *StringSliceCmd - ReadOnly() *StatusCmd - ReadWrite() *StatusCmd - MemoryUsage(key string, samples ...int) *IntCmd -} - -type StatefulCmdable interface { - Cmdable - Auth(password string) *StatusCmd - Select(index int) *StatusCmd - SwapDB(index1, index2 int) *StatusCmd - ClientSetName(name string) *BoolCmd -} - -var _ Cmdable = (*Client)(nil) -var _ Cmdable = (*Tx)(nil) -var _ Cmdable = (*Ring)(nil) -var _ Cmdable = (*ClusterClient)(nil) - -type cmdable struct { - process func(cmd Cmder) error -} - -func (c *cmdable) setProcessor(fn func(Cmder) error) { - c.process = fn -} - -type statefulCmdable struct { - cmdable - process func(cmd Cmder) error -} - -func (c *statefulCmdable) setProcessor(fn func(Cmder) error) { - c.process = fn - c.cmdable.setProcessor(fn) -} - -//------------------------------------------------------------------------------ - -func (c *statefulCmdable) Auth(password string) *StatusCmd { - cmd := NewStatusCmd("auth", password) - c.process(cmd) - return cmd -} - -func (c *cmdable) Echo(message interface{}) *StringCmd { - cmd := NewStringCmd("echo", message) - c.process(cmd) - return cmd -} - -func (c *cmdable) Ping() *StatusCmd { - cmd := NewStatusCmd("ping") - c.process(cmd) - return cmd -} - -func (c *cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { - cmd := NewIntCmd("wait", numSlaves, int(timeout/time.Millisecond)) - c.process(cmd) - return cmd -} - -func (c *cmdable) Quit() *StatusCmd { - panic("not implemented") -} - -func (c *statefulCmdable) Select(index int) *StatusCmd { - cmd := NewStatusCmd("select", index) - c.process(cmd) - return cmd -} - -func (c *statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { - cmd := NewStatusCmd("swapdb", index1, index2) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) Command() *CommandsInfoCmd { - cmd := NewCommandsInfoCmd("command") - c.process(cmd) - return cmd -} - -func (c *cmdable) Del(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "del" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Unlink(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "unlink" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Dump(key string) *StringCmd { - cmd := NewStringCmd("dump", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Exists(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "exists" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Expire(key string, expiration time.Duration) *BoolCmd { - cmd := NewBoolCmd("expire", key, formatSec(expiration)) - c.process(cmd) - return cmd -} - -func (c *cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { - cmd := NewBoolCmd("expireat", key, tm.Unix()) - c.process(cmd) - return cmd -} - -func (c *cmdable) Keys(pattern string) *StringSliceCmd { - cmd := NewStringSliceCmd("keys", pattern) - c.process(cmd) - return cmd -} - -func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd { - cmd := NewStatusCmd( - "migrate", - host, - port, - key, - db, - formatMs(timeout), - ) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) Move(key string, db int64) *BoolCmd { - cmd := NewBoolCmd("move", key, db) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectRefCount(key string) *IntCmd { - cmd := NewIntCmd("object", "refcount", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectEncoding(key string) *StringCmd { - cmd := NewStringCmd("object", "encoding", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectIdleTime(key string) *DurationCmd { - cmd := NewDurationCmd(time.Second, "object", "idletime", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Persist(key string) *BoolCmd { - cmd := NewBoolCmd("persist", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { - cmd := NewBoolCmd("pexpire", key, formatMs(expiration)) - c.process(cmd) - return cmd -} - -func (c *cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { - cmd := NewBoolCmd( - "pexpireat", - key, - tm.UnixNano()/int64(time.Millisecond), - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) PTTL(key string) *DurationCmd { - cmd := NewDurationCmd(time.Millisecond, "pttl", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) RandomKey() *StringCmd { - cmd := NewStringCmd("randomkey") - c.process(cmd) - return cmd -} - -func (c *cmdable) Rename(key, newkey string) *StatusCmd { - cmd := NewStatusCmd("rename", key, newkey) - c.process(cmd) - return cmd -} - -func (c *cmdable) RenameNX(key, newkey string) *BoolCmd { - cmd := NewBoolCmd("renamenx", key, newkey) - c.process(cmd) - return cmd -} - -func (c *cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { - cmd := NewStatusCmd( - "restore", - key, - formatMs(ttl), - value, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { - cmd := NewStatusCmd( - "restore", - key, - formatMs(ttl), - value, - "replace", - ) - c.process(cmd) - return cmd -} - -type Sort struct { - By string - Offset, Count int64 - Get []string - Order string - Alpha bool -} - -func (sort *Sort) args(key string) []interface{} { - args := []interface{}{"sort", key} - if sort.By != "" { - args = append(args, "by", sort.By) - } - if sort.Offset != 0 || sort.Count != 0 { - args = append(args, "limit", sort.Offset, sort.Count) - } - for _, get := range sort.Get { - args = append(args, "get", get) - } - if sort.Order != "" { - args = append(args, sort.Order) - } - if sort.Alpha { - args = append(args, "alpha") - } - return args -} - -func (c *cmdable) Sort(key string, sort *Sort) *StringSliceCmd { - cmd := NewStringSliceCmd(sort.args(key)...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SortStore(key, store string, sort *Sort) *IntCmd { - args := sort.args(key) - if store != "" { - args = append(args, "store", store) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { - cmd := NewSliceCmd(sort.args(key)...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Touch(keys ...string) *IntCmd { - args := make([]interface{}, len(keys)+1) - args[0] = "touch" - for i, key := range keys { - args[i+1] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) TTL(key string) *DurationCmd { - cmd := NewDurationCmd(time.Second, "ttl", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Type(key string) *StatusCmd { - cmd := NewStatusCmd("type", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"scan", cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"sscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"hscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"zscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) Append(key, value string) *IntCmd { - cmd := NewIntCmd("append", key, value) - c.process(cmd) - return cmd -} - -type BitCount struct { - Start, End int64 -} - -func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { - args := []interface{}{"bitcount", key} - if bitCount != nil { - args = append( - args, - bitCount.Start, - bitCount.End, - ) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "bitop" - args[1] = op - args[2] = destKey - for i, key := range keys { - args[3+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { - return c.bitOp("and", destKey, keys...) -} - -func (c *cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { - return c.bitOp("or", destKey, keys...) -} - -func (c *cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { - return c.bitOp("xor", destKey, keys...) -} - -func (c *cmdable) BitOpNot(destKey string, key string) *IntCmd { - return c.bitOp("not", destKey, key) -} - -func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { - args := make([]interface{}, 3+len(pos)) - args[0] = "bitpos" - args[1] = key - args[2] = bit - switch len(pos) { - case 0: - case 1: - args[3] = pos[0] - case 2: - args[3] = pos[0] - args[4] = pos[1] - default: - panic("too many arguments") - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Decr(key string) *IntCmd { - cmd := NewIntCmd("decr", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) DecrBy(key string, decrement int64) *IntCmd { - cmd := NewIntCmd("decrby", key, decrement) - c.process(cmd) - return cmd -} - -// Redis `GET key` command. It returns redis.Nil error when key does not exist. -func (c *cmdable) Get(key string) *StringCmd { - cmd := NewStringCmd("get", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetBit(key string, offset int64) *IntCmd { - cmd := NewIntCmd("getbit", key, offset) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetRange(key string, start, end int64) *StringCmd { - cmd := NewStringCmd("getrange", key, start, end) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetSet(key string, value interface{}) *StringCmd { - cmd := NewStringCmd("getset", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) Incr(key string) *IntCmd { - cmd := NewIntCmd("incr", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) IncrBy(key string, value int64) *IntCmd { - cmd := NewIntCmd("incrby", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) IncrByFloat(key string, value float64) *FloatCmd { - cmd := NewFloatCmd("incrbyfloat", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) MGet(keys ...string) *SliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "mget" - for i, key := range keys { - args[1+i] = key - } - cmd := NewSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd { - args := make([]interface{}, 1, 1+len(pairs)) - args[0] = "mset" - args = appendArgs(args, pairs) - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { - args := make([]interface{}, 1, 1+len(pairs)) - args[0] = "msetnx" - args = appendArgs(args, pairs) - cmd := NewBoolCmd(args...) - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration]` command. -// -// Use expiration for `SETEX`-like behavior. -// Zero expiration means the key has no expiration time. -func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { - args := make([]interface{}, 3, 4) - args[0] = "set" - args[1] = key - args[2] = value - if expiration > 0 { - if usePrecise(expiration) { - args = append(args, "px", formatMs(expiration)) - } else { - args = append(args, "ex", formatSec(expiration)) - } - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SetBit(key string, offset int64, value int) *IntCmd { - cmd := NewIntCmd( - "setbit", - key, - offset, - value, - ) - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration] NX` command. -// -// Zero expiration means the key has no expiration time. -func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd { - var cmd *BoolCmd - if expiration == 0 { - // Use old `SETNX` to support old Redis versions. - cmd = NewBoolCmd("setnx", key, value) - } else { - if usePrecise(expiration) { - cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "nx") - } else { - cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx") - } - } - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration] XX` command. -// -// Zero expiration means the key has no expiration time. -func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { - var cmd *BoolCmd - if expiration == 0 { - cmd = NewBoolCmd("set", key, value, "xx") - } else { - if usePrecise(expiration) { - cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "xx") - } else { - cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "xx") - } - } - c.process(cmd) - return cmd -} - -func (c *cmdable) SetRange(key string, offset int64, value string) *IntCmd { - cmd := NewIntCmd("setrange", key, offset, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) StrLen(key string) *IntCmd { - cmd := NewIntCmd("strlen", key) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) HDel(key string, fields ...string) *IntCmd { - args := make([]interface{}, 2+len(fields)) - args[0] = "hdel" - args[1] = key - for i, field := range fields { - args[2+i] = field - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HExists(key, field string) *BoolCmd { - cmd := NewBoolCmd("hexists", key, field) - c.process(cmd) - return cmd -} - -func (c *cmdable) HGet(key, field string) *StringCmd { - cmd := NewStringCmd("hget", key, field) - c.process(cmd) - return cmd -} - -func (c *cmdable) HGetAll(key string) *StringStringMapCmd { - cmd := NewStringStringMapCmd("hgetall", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HIncrBy(key, field string, incr int64) *IntCmd { - cmd := NewIntCmd("hincrby", key, field, incr) - c.process(cmd) - return cmd -} - -func (c *cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { - cmd := NewFloatCmd("hincrbyfloat", key, field, incr) - c.process(cmd) - return cmd -} - -func (c *cmdable) HKeys(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("hkeys", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HLen(key string) *IntCmd { - cmd := NewIntCmd("hlen", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { - args := make([]interface{}, 2+len(fields)) - args[0] = "hmget" - args[1] = key - for i, field := range fields { - args[2+i] = field - } - cmd := NewSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HMSet(key string, fields map[string]interface{}) *StatusCmd { - args := make([]interface{}, 2+len(fields)*2) - args[0] = "hmset" - args[1] = key - i := 2 - for k, v := range fields { - args[i] = k - args[i+1] = v - i += 2 - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HSet(key, field string, value interface{}) *BoolCmd { - cmd := NewBoolCmd("hset", key, field, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { - cmd := NewBoolCmd("hsetnx", key, field, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) HVals(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("hvals", key) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "blpop" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewStringSliceCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "brpop" - for i, key := range keys { - args[1+i] = key - } - args[len(keys)+1] = formatSec(timeout) - cmd := NewStringSliceCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { - cmd := NewStringCmd( - "brpoplpush", - source, - destination, - formatSec(timeout), - ) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) LIndex(key string, index int64) *StringCmd { - cmd := NewStringCmd("lindex", key, index) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, op, pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, "before", pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, "after", pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LLen(key string) *IntCmd { - cmd := NewIntCmd("llen", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPop(key string) *StringCmd { - cmd := NewStringCmd("lpop", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(values)) - args[0] = "lpush" - args[1] = key - args = appendArgs(args, values) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("lpushx", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LRange(key string, start, stop int64) *StringSliceCmd { - cmd := NewStringSliceCmd( - "lrange", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) LRem(key string, count int64, value interface{}) *IntCmd { - cmd := NewIntCmd("lrem", key, count, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { - cmd := NewStatusCmd("lset", key, index, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LTrim(key string, start, stop int64) *StatusCmd { - cmd := NewStatusCmd( - "ltrim", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPop(key string) *StringCmd { - cmd := NewStringCmd("rpop", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPopLPush(source, destination string) *StringCmd { - cmd := NewStringCmd("rpoplpush", source, destination) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(values)) - args[0] = "rpush" - args[1] = key - args = appendArgs(args, values) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("rpushx", key, value) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "sadd" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SCard(key string) *IntCmd { - cmd := NewIntCmd("scard", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) SDiff(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sdiff" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sdiffstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SInter(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sinter" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sinterstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SIsMember(key string, member interface{}) *BoolCmd { - cmd := NewBoolCmd("sismember", key, member) - c.process(cmd) - return cmd -} - -// Redis `SMEMBERS key` command output as a slice -func (c *cmdable) SMembers(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("smembers", key) - c.process(cmd) - return cmd -} - -// Redis `SMEMBERS key` command output as a map -func (c *cmdable) SMembersMap(key string) *StringStructMapCmd { - cmd := NewStringStructMapCmd("smembers", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) SMove(source, destination string, member interface{}) *BoolCmd { - cmd := NewBoolCmd("smove", source, destination, member) - c.process(cmd) - return cmd -} - -// Redis `SPOP key` command. -func (c *cmdable) SPop(key string) *StringCmd { - cmd := NewStringCmd("spop", key) - c.process(cmd) - return cmd -} - -// Redis `SPOP key count` command. -func (c *cmdable) SPopN(key string, count int64) *StringSliceCmd { - cmd := NewStringSliceCmd("spop", key, count) - c.process(cmd) - return cmd -} - -// Redis `SRANDMEMBER key` command. -func (c *cmdable) SRandMember(key string) *StringCmd { - cmd := NewStringCmd("srandmember", key) - c.process(cmd) - return cmd -} - -// Redis `SRANDMEMBER key count` command. -func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { - cmd := NewStringSliceCmd("srandmember", key, count) - c.process(cmd) - return cmd -} - -func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "srem" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SUnion(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sunion" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sunionstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -type XAddArgs struct { - Stream string - MaxLen int64 // MAXLEN N - MaxLenApprox int64 // MAXLEN ~ N - ID string - Values map[string]interface{} -} - -func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { - args := make([]interface{}, 0, 6+len(a.Values)*2) - args = append(args, "xadd") - args = append(args, a.Stream) - if a.MaxLen > 0 { - args = append(args, "maxlen", a.MaxLen) - } else if a.MaxLenApprox > 0 { - args = append(args, "maxlen", "~", a.MaxLenApprox) - } - if a.ID != "" { - args = append(args, a.ID) - } else { - args = append(args, "*") - } - for k, v := range a.Values { - args = append(args, k) - args = append(args, v) - } - - cmd := NewStringCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XDel(stream string, ids ...string) *IntCmd { - args := []interface{}{"xdel", stream} - for _, id := range ids { - args = append(args, id) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XLen(stream string) *IntCmd { - cmd := NewIntCmd("xlen", stream) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrange", stream, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrange", stream, start, stop, "count", count) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop, "count", count) - c.process(cmd) - return cmd -} - -type XReadArgs struct { - Streams []string - Count int64 - Block time.Duration -} - -func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 5+len(a.Streams)) - args = append(args, "xread") - if a.Count > 0 { - args = append(args, "count") - args = append(args, a.Count) - } - if a.Block >= 0 { - args = append(args, "block") - args = append(args, int64(a.Block/time.Millisecond)) - } - args = append(args, "streams") - for _, s := range a.Streams { - args = append(args, s) - } - - cmd := NewXStreamSliceCmd(args...) - if a.Block >= 0 { - cmd.setReadTimeout(a.Block) - } - c.process(cmd) - return cmd -} - -func (c *cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { - return c.XRead(&XReadArgs{ - Streams: streams, - Block: -1, - }) -} - -func (c *cmdable) XGroupCreate(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "create", stream, group, start) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupCreateMkStream(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "create", stream, group, start, "mkstream") - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupSetID(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "setid", stream, group, start) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupDestroy(stream, group string) *IntCmd { - cmd := NewIntCmd("xgroup", "destroy", stream, group) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { - cmd := NewIntCmd("xgroup", "delconsumer", stream, group, consumer) - c.process(cmd) - return cmd -} - -type XReadGroupArgs struct { - Group string - Consumer string - // List of streams and ids. - Streams []string - Count int64 - Block time.Duration - NoAck bool -} - -func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 8+len(a.Streams)) - args = append(args, "xreadgroup", "group", a.Group, a.Consumer) - if a.Count > 0 { - args = append(args, "count", a.Count) - } - if a.Block >= 0 { - args = append(args, "block", int64(a.Block/time.Millisecond)) - } - if a.NoAck { - args = append(args, "noack") - } - args = append(args, "streams") - for _, s := range a.Streams { - args = append(args, s) - } - - cmd := NewXStreamSliceCmd(args...) - if a.Block >= 0 { - cmd.setReadTimeout(a.Block) - } - c.process(cmd) - return cmd -} - -func (c *cmdable) XAck(stream, group string, ids ...string) *IntCmd { - args := []interface{}{"xack", stream, group} - for _, id := range ids { - args = append(args, id) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XPending(stream, group string) *XPendingCmd { - cmd := NewXPendingCmd("xpending", stream, group) - c.process(cmd) - return cmd -} - -type XPendingExtArgs struct { - Stream string - Group string - Start string - End string - Count int64 - Consumer string -} - -func (c *cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { - args := make([]interface{}, 0, 7) - args = append(args, "xpending", a.Stream, a.Group, a.Start, a.End, a.Count) - if a.Consumer != "" { - args = append(args, a.Consumer) - } - cmd := NewXPendingExtCmd(args...) - c.process(cmd) - return cmd -} - -type XClaimArgs struct { - Stream string - Group string - Consumer string - MinIdle time.Duration - Messages []string -} - -func (c *cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { - args := xClaimArgs(a) - cmd := NewXMessageSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { - args := xClaimArgs(a) - args = append(args, "justid") - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func xClaimArgs(a *XClaimArgs) []interface{} { - args := make([]interface{}, 0, 4+len(a.Messages)) - args = append(args, - "xclaim", - a.Stream, - a.Group, a.Consumer, - int64(a.MinIdle/time.Millisecond)) - for _, id := range a.Messages { - args = append(args, id) - } - return args -} - -func (c *cmdable) XTrim(key string, maxLen int64) *IntCmd { - cmd := NewIntCmd("xtrim", key, "maxlen", maxLen) - c.process(cmd) - return cmd -} - -func (c *cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { - cmd := NewIntCmd("xtrim", key, "maxlen", "~", maxLen) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -// Z represents sorted set member. -type Z struct { - Score float64 - Member interface{} -} - -// ZWithKey represents sorted set member including the name of the key where it was popped. -type ZWithKey struct { - Z - Key string -} - -// ZStore is used as an arg to ZInterStore and ZUnionStore. -type ZStore struct { - Weights []float64 - // Can be SUM, MIN or MAX. - Aggregate string -} - -// Redis `BZPOPMAX key [key ...] timeout` command. -func (c *cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "bzpopmax" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewZWithKeyCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -// Redis `BZPOPMIN key [key ...] timeout` command. -func (c *cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "bzpopmin" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewZWithKeyCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) zAdd(a []interface{}, n int, members ...Z) *IntCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member - } - cmd := NewIntCmd(a...) - c.process(cmd) - return cmd -} - -// Redis `ZADD key score member [score member ...]` command. -func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { - const n = 2 - a := make([]interface{}, n+2*len(members)) - a[0], a[1] = "zadd", key - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key NX score member [score member ...]` command. -func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "nx" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key XX score member [score member ...]` command. -func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "xx" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key CH score member [score member ...]` command. -func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "ch" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key NX CH score member [score member ...]` command. -func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "nx", "ch" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key XX CH score member [score member ...]` command. -func (c *cmdable) ZAddXXCh(key string, members ...Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "xx", "ch" - return c.zAdd(a, n, members...) -} - -func (c *cmdable) zIncr(a []interface{}, n int, members ...Z) *FloatCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member - } - cmd := NewFloatCmd(a...) - c.process(cmd) - return cmd -} - -// Redis `ZADD key INCR score member` command. -func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { - const n = 3 - a := make([]interface{}, n+2) - a[0], a[1], a[2] = "zadd", key, "incr" - return c.zIncr(a, n, member) -} - -// Redis `ZADD key NX INCR score member` command. -func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "nx" - return c.zIncr(a, n, member) -} - -// Redis `ZADD key XX INCR score member` command. -func (c *cmdable) ZIncrXX(key string, member Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "xx" - return c.zIncr(a, n, member) -} - -func (c *cmdable) ZCard(key string) *IntCmd { - cmd := NewIntCmd("zcard", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZCount(key, min, max string) *IntCmd { - cmd := NewIntCmd("zcount", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZLexCount(key, min, max string) *IntCmd { - cmd := NewIntCmd("zlexcount", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { - cmd := NewFloatCmd("zincrby", key, increment, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "zinterstore" - args[1] = destination - args[2] = len(keys) - for i, key := range keys { - args[3+i] = key - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { - args := []interface{}{ - "zpopmax", - key, - } - - switch len(count) { - case 0: - break - case 1: - args = append(args, count[0]) - default: - panic("too many arguments") - } - - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { - args := []interface{}{ - "zpopmin", - key, - } - - switch len(count) { - case 0: - break - case 1: - args = append(args, count[0]) - default: - panic("too many arguments") - } - - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { - args := []interface{}{ - "zrange", - key, - start, - stop, - } - if withScores { - args = append(args, "withscores") - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { - return c.zRange(key, start, stop, false) -} - -func (c *cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { - cmd := NewZSliceCmd("zrange", key, start, stop, "withscores") - c.process(cmd) - return cmd -} - -type ZRangeBy struct { - Min, Max string - Offset, Count int64 -} - -func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *StringSliceCmd { - args := []interface{}{zcmd, key, opt.Min, opt.Max} - if withScores { - args = append(args, "withscores") - } - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRangeBy("zrangebyscore", key, opt, false) -} - -func (c *cmdable) ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRangeBy("zrangebylex", key, opt, false) -} - -func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { - args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRank(key, member string) *IntCmd { - cmd := NewIntCmd("zrank", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "zrem" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { - cmd := NewIntCmd( - "zremrangebyrank", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { - cmd := NewIntCmd("zremrangebyscore", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { - cmd := NewIntCmd("zremrangebylex", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { - cmd := NewStringSliceCmd("zrevrange", key, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { - cmd := NewZSliceCmd("zrevrange", key, start, stop, "withscores") - c.process(cmd) - return cmd -} - -func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { - args := []interface{}{zcmd, key, opt.Max, opt.Min} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRevRangeBy("zrevrangebyscore", key, opt) -} - -func (c *cmdable) ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRevRangeBy("zrevrangebylex", key, opt) -} - -func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { - args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRank(key, member string) *IntCmd { - cmd := NewIntCmd("zrevrank", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZScore(key, member string) *FloatCmd { - cmd := NewFloatCmd("zscore", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "zunionstore" - args[1] = dest - args[2] = len(keys) - for i, key := range keys { - args[3+i] = key - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(els)) - args[0] = "pfadd" - args[1] = key - args = appendArgs(args, els) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) PFCount(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "pfcount" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "pfmerge" - args[1] = dest - for i, key := range keys { - args[2+i] = key - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) BgRewriteAOF() *StatusCmd { - cmd := NewStatusCmd("bgrewriteaof") - c.process(cmd) - return cmd -} - -func (c *cmdable) BgSave() *StatusCmd { - cmd := NewStatusCmd("bgsave") - c.process(cmd) - return cmd -} - -func (c *cmdable) ClientKill(ipPort string) *StatusCmd { - cmd := NewStatusCmd("client", "kill", ipPort) - c.process(cmd) - return cmd -} - -// ClientKillByFilter is new style synx, while the ClientKill is old -// CLIENT KILL