From 725a94214a47eadc1fcfe99731a614a38a1e5646 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Fri, 5 Aug 2016 09:49:38 -0700 Subject: [PATCH 1/4] storage: add storage with static clients --- storage/memory/static_clients_test.go | 48 +++++++++++++++++++++++ storage/static_clients.go | 55 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 storage/memory/static_clients_test.go create mode 100644 storage/static_clients.go diff --git a/storage/memory/static_clients_test.go b/storage/memory/static_clients_test.go new file mode 100644 index 00000000..e34b070c --- /dev/null +++ b/storage/memory/static_clients_test.go @@ -0,0 +1,48 @@ +package memory + +import ( + "reflect" + "testing" + + "github.com/coreos/poke/storage" +) + +func TestStaticClients(t *testing.T) { + s := New() + + c1 := storage.Client{ID: "foo", Secret: "foo_secret"} + c2 := storage.Client{ID: "bar", Secret: "bar_secret"} + s.CreateClient(c1) + s2 := storage.WithStaticClients(s, []storage.Client{c2}) + + tests := []struct { + id string + s storage.Storage + wantErr bool + wantClient storage.Client + }{ + {"foo", s, false, c1}, + {"bar", s, true, storage.Client{}}, + {"foo", s2, true, storage.Client{}}, + {"bar", s2, false, c2}, + } + + for i, tc := range tests { + gotClient, err := tc.s.GetClient(tc.id) + if err != nil { + if !tc.wantErr { + t.Errorf("case %d: GetClient(%q) %v", i, tc.id, err) + } + continue + } + + if tc.wantErr { + t.Errorf("case %d: GetClient(%q) expected error", i, tc.id) + continue + } + + if !reflect.DeepEqual(tc.wantClient, gotClient) { + t.Errorf("case %d: expected=%#v got=%#v", i, tc.wantClient, gotClient) + } + } +} diff --git a/storage/static_clients.go b/storage/static_clients.go new file mode 100644 index 00000000..d7932393 --- /dev/null +++ b/storage/static_clients.go @@ -0,0 +1,55 @@ +package storage + +import "errors" + +// Tests for this code are in the "memory" package, since this package doesn't +// define a concrete storage implementation. + +// staticClientsStorage is a storage that only allow read-only actions on clients. +// All read actions return from the list of clients stored in memory, not the +// underlying +type staticClientsStorage struct { + Storage + + // A read-only set of clients. + clients []Client + clientsByID map[string]Client +} + +// WithStaticClients returns a storage with a read-only set of clients. Write actions, +// such as creating other clients, will fail. +// +// In the future the returned storage may allow creating and storing additional clients +// in the underlying storage. +func WithStaticClients(s Storage, staticClients []Client) Storage { + clientsByID := make(map[string]Client, len(staticClients)) + for _, client := range staticClients { + clientsByID[client.ID] = client + } + return staticClientsStorage{s, staticClients, clientsByID} +} + +func (s staticClientsStorage) GetClient(id string) (Client, error) { + if client, ok := s.clientsByID[id]; ok { + return client, nil + } + return Client{}, ErrNotFound +} + +func (s staticClientsStorage) ListClients() ([]Client, error) { + clients := make([]Client, len(s.clients)) + copy(clients, s.clients) + return clients, nil +} + +func (s staticClientsStorage) CreateClient(c Client) error { + return errors.New("static clients: read-only cannot create client") +} + +func (s staticClientsStorage) DeleteClient(id string) error { + return errors.New("static clients: read-only cannot delete client") +} + +func (s staticClientsStorage) UpdateClient(id string, updater func(old Client) (Client, error)) error { + return errors.New("static clients: read-only cannot update client") +} From 53d1be4a87e8ef4913325ef53204e6d3de6dd9db Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Fri, 5 Aug 2016 09:50:22 -0700 Subject: [PATCH 2/4] *: load static clients from config file --- cmd/poke/config.go | 6 ++++++ cmd/poke/config_test.go | 39 +++++++++++++++++++++++++++++++++++++++ cmd/poke/serve.go | 6 +++++- example/config-dev.yaml | 27 +++++++++++++++++++++++++++ server/handlers.go | 6 +++++- storage/memory/memory.go | 12 ++++++++++++ storage/storage.go | 14 +++++++------- 7 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 cmd/poke/config_test.go create mode 100644 example/config-dev.yaml diff --git a/cmd/poke/config.go b/cmd/poke/config.go index 3d840a40..fc06b208 100644 --- a/cmd/poke/config.go +++ b/cmd/poke/config.go @@ -9,6 +9,7 @@ import ( "github.com/coreos/poke/connector/mock" "github.com/coreos/poke/storage" "github.com/coreos/poke/storage/kubernetes" + "github.com/coreos/poke/storage/memory" ) // Config is the config format for the main application. @@ -17,6 +18,8 @@ type Config struct { Storage Storage `yaml:"storage"` Connectors []Connector `yaml:"connectors"` Web Web `yaml:"web"` + + StaticClients []storage.Client `yaml:"staticClients"` } // Web is the config format for the HTTP server. @@ -46,9 +49,12 @@ func (s *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error { var c struct { Config StorageConfig `yaml:"config"` } + // TODO(ericchiang): replace this with a registration process. switch storageMeta.Type { case "kubernetes": c.Config = &kubernetes.Config{} + case "memory": + c.Config = &memory.Config{} default: return fmt.Errorf("unknown storage type %q", storageMeta.Type) } diff --git a/cmd/poke/config_test.go b/cmd/poke/config_test.go new file mode 100644 index 00000000..f4158bc3 --- /dev/null +++ b/cmd/poke/config_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "testing" + + "github.com/coreos/poke/storage" + "github.com/kylelemons/godebug/pretty" + + yaml "gopkg.in/yaml.v2" +) + +func TestUnmarshalClients(t *testing.T) { + data := `staticClients: +- id: example-app + redirectURIs: + - 'http://127.0.0.1:5555/callback' + name: 'Example App' + secret: ZXhhbXBsZS1hcHAtc2VjcmV0 +` + var c Config + if err := yaml.Unmarshal([]byte(data), &c); err != nil { + t.Fatal(err) + } + + wantClients := []storage.Client{ + { + ID: "example-app", + Name: "Example App", + Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", + RedirectURIs: []string{ + "http://127.0.0.1:5555/callback", + }, + }, + } + + if diff := pretty.Compare(wantClients, c.StaticClients); diff != "" { + t.Errorf("did not get expected clients: %s", diff) + } +} diff --git a/cmd/poke/serve.go b/cmd/poke/serve.go index 28611f50..24bb4917 100644 --- a/cmd/poke/serve.go +++ b/cmd/poke/serve.go @@ -7,10 +7,11 @@ import ( "log" "net/http" + "github.com/spf13/cobra" yaml "gopkg.in/yaml.v2" "github.com/coreos/poke/server" - "github.com/spf13/cobra" + "github.com/coreos/poke/storage" ) func commandServe() *cobra.Command { @@ -83,6 +84,9 @@ func serve(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("initializing storage: %v", err) } + if len(c.StaticClients) > 0 { + s = storage.WithStaticClients(s, c.StaticClients) + } serverConfig := server.Config{ Issuer: c.Issuer, diff --git a/example/config-dev.yaml b/example/config-dev.yaml new file mode 100644 index 00000000..d3d01afb --- /dev/null +++ b/example/config-dev.yaml @@ -0,0 +1,27 @@ +issuer: http://127.0.0.1:5556 +storage: + # NOTE(ericchiang): This will be replaced by sqlite3 in the future. + type: memory + +web: + http: 127.0.0.1:5556 + +connectors: +- type: mock + id: mock + name: Mock +- type: github + id: github + name: GitHub + config: + clientID: "$GITHUB_CLIENT_ID" + clientSecret: "$GITHUB_CLIENT_SECRET" + redirectURI: http://127.0.0.1:5556/callback/github + org: kubernetes + +staticClients: +- id: example-app + redirectURIs: + - 'http://127.0.0.1:5555/callback' + name: 'Example App' + secret: ZXhhbXBsZS1hcHAtc2VjcmV0 diff --git a/server/handlers.go b/server/handlers.go index 85214a41..76bdbc5f 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -2,6 +2,7 @@ package server import ( "encoding/json" + "errors" "fmt" "log" "net/http" @@ -125,7 +126,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) { for id := range s.connectors { connectorInfos[i] = connectorInfo{ DisplayName: id, - URL: s.absPath("/auth", id) + "?state=" + state, + URL: s.absPath("/auth", id), } i++ } @@ -224,6 +225,9 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) } func (s *Server) finalizeLogin(identity connector.Identity, authReqID, connectorID string, conn connector.Connector) (string, error) { + if authReqID == "" { + return "", errors.New("no auth request ID passed") + } claims := storage.Claims{ UserID: identity.UserID, Username: identity.Username, diff --git a/storage/memory/memory.go b/storage/memory/memory.go index 748e9528..d4bb1bcd 100644 --- a/storage/memory/memory.go +++ b/storage/memory/memory.go @@ -18,6 +18,18 @@ func New() storage.Storage { } } +// Config is an implementation of a storage configuration. +// +// TODO(ericchiang): Actually define a storage config interface and have registration. +type Config struct { + // The in memory implementation has no config. +} + +// Open always returns a new in memory storage. +func (c *Config) Open() (storage.Storage, error) { + return New(), nil +} + type memStorage struct { mu sync.Mutex diff --git a/storage/storage.go b/storage/storage.go index 88b11542..399b35cd 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -81,19 +81,19 @@ type Storage interface { // * Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth // * Public clients: https://developers.google.com/api-client-library/python/auth/installed-app type Client struct { - ID string - Secret string - RedirectURIs []string + ID string `json:"id" yaml:"id"` + Secret string `json:"secret" yaml:"secret"` + RedirectURIs []string `json:"redirectURIs" yaml:"redirectURIs"` // TrustedPeers are a list of peers which can issue tokens on this client's behalf. // Clients inherently trust themselves. - TrustedPeers []string + TrustedPeers []string `json:"trustedPeers" yaml:"trustedPeers"` // Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob" - Public bool + Public bool `json:"public" yaml:"public"` - Name string - LogoURL string + Name string `json:"name" yaml:"name"` + LogoURL string `json:"logoURL" yaml:"logoURL"` } // Claims represents the ID Token claims supported by the server. From df258306dc89d8b5d87e52ff33604706f09ce50e Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Fri, 5 Aug 2016 09:50:57 -0700 Subject: [PATCH 3/4] *: add github.com/kylelemons/godebug to dependencies --- glide.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/glide.yaml b/glide.yaml index 179b67ee..46fae0e5 100644 --- a/glide.yaml +++ b/glide.yaml @@ -87,3 +87,9 @@ import: - trace - lex/httplex - internal/timeseries + +- package: github.com/kylelemons/godebug + subpackages: + - diff + - pretty + version: eadb3ce320cbab8393bea5ca17bebac3f78a021b From ad6af5800334252531f245c07ee5c0d7c05c83cf Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Fri, 5 Aug 2016 09:51:37 -0700 Subject: [PATCH 4/4] *: revendor --- glide.lock | 9 +- .../github.com/kylelemons/godebug/.travis.yml | 5 + vendor/github.com/kylelemons/godebug/LICENSE | 202 +++++++++++ .../github.com/kylelemons/godebug/README.md | 64 ++++ .../kylelemons/godebug/diff/diff.go | 133 ++++++++ .../kylelemons/godebug/diff/diff_test.go | 120 +++++++ .../kylelemons/godebug/pretty/.gitignore | 5 + .../kylelemons/godebug/pretty/doc.go | 25 ++ .../godebug/pretty/examples_test.go | 281 ++++++++++++++++ .../kylelemons/godebug/pretty/public.go | 116 +++++++ .../kylelemons/godebug/pretty/public_test.go | 128 +++++++ .../kylelemons/godebug/pretty/reflect.go | 114 +++++++ .../kylelemons/godebug/pretty/reflect_test.go | 168 ++++++++++ .../kylelemons/godebug/pretty/structure.go | 160 +++++++++ .../godebug/pretty/structure_test.go | 316 ++++++++++++++++++ 15 files changed, 1844 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/kylelemons/godebug/.travis.yml create mode 100644 vendor/github.com/kylelemons/godebug/LICENSE create mode 100644 vendor/github.com/kylelemons/godebug/README.md create mode 100644 vendor/github.com/kylelemons/godebug/diff/diff.go create mode 100644 vendor/github.com/kylelemons/godebug/diff/diff_test.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/.gitignore create mode 100644 vendor/github.com/kylelemons/godebug/pretty/doc.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/examples_test.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/public.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/public_test.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/reflect.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/reflect_test.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/structure.go create mode 100644 vendor/github.com/kylelemons/godebug/pretty/structure_test.go diff --git a/glide.lock b/glide.lock index d7e0deee..b47cea0f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 66d8717b42e620d52ceec62e7cb3f2bc23f21bfe35547587338d85564bd330c9 -updated: 2016-07-31T12:19:29.812965492-07:00 +hash: 2b694ffd26e854f519064b43f437545dfb7cfdc437b3890552dc1f6e7955a2b5 +updated: 2016-08-03T23:07:24.402853601-07:00 imports: - name: github.com/ericchiang/oidc version: 69fec81d167d815f4f455c741b2a94ffaf547ed2 @@ -16,6 +16,11 @@ imports: version: e7e23673cac3f529f49e22f94e4af6d12bb49dba - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/kylelemons/godebug + version: eadb3ce320cbab8393bea5ca17bebac3f78a021b + subpackages: + - diff + - pretty - name: github.com/mitchellh/go-homedir version: 756f7b183b7ab78acdbbee5c7f392838ed459dda - name: github.com/pquerna/cachecontrol diff --git a/vendor/github.com/kylelemons/godebug/.travis.yml b/vendor/github.com/kylelemons/godebug/.travis.yml new file mode 100644 index 00000000..e7efe160 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/.travis.yml @@ -0,0 +1,5 @@ +language: go + +go: + - 1.6 + - tip diff --git a/vendor/github.com/kylelemons/godebug/LICENSE b/vendor/github.com/kylelemons/godebug/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/kylelemons/godebug/README.md b/vendor/github.com/kylelemons/godebug/README.md new file mode 100644 index 00000000..dc5ec731 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/README.md @@ -0,0 +1,64 @@ +Pretty Printing for Go +====================== + +[![godebug build status][ciimg]][ci] + +Have you ever wanted to get a pretty-printed version of a Go data structure, +complete with indentation? I have found this especially useful in unit tests +and in debugging my code, and thus godebug was born! + +[ciimg]: https://travis-ci.org/kylelemons/godebug.svg?branch=master +[ci]: https://travis-ci.org/kylelemons/godebug + +Quick Examples +-------------- + +By default, pretty will write out a very compact representation of a data structure. +From the [Print example][printex]: + +``` +{Name: "Spaceship Heart of Gold", + Crew: {Arthur Dent: "Along for the Ride", + Ford Prefect: "A Hoopy Frood", + Trillian: "Human", + Zaphod Beeblebrox: "Galactic President"}, + Androids: 1, + Stolen: true} +``` + +It can also produce a much more verbose, one-item-per-line representation suitable for +[computing diffs][diffex]. See the documentation for more examples and customization. + +[printex]: https://godoc.org/github.com/kylelemons/godebug/pretty#example-Print +[diffex]: https://godoc.org/github.com/kylelemons/godebug/pretty#example-Compare + +Documentation +------------- + +Documentation for this package is available at [godoc.org][doc]: + + * Pretty: [![godoc for godebug/pretty][prettyimg]][prettydoc] + * Diff: [![godoc for godebug/diff][diffimg]][diffdoc] + +[doc]: https://godoc.org/ +[prettyimg]: https://godoc.org/github.com/kylelemons/godebug/pretty?status.png +[prettydoc]: https://godoc.org/github.com/kylelemons/godebug/pretty +[diffimg]: https://godoc.org/github.com/kylelemons/godebug/diff?status.png +[diffdoc]: https://godoc.org/github.com/kylelemons/godebug/diff + +Installation +------------ + +These packages are available via `go get`: + +```bash +$ go get -u github.com/kylelemons/godebug/{pretty,diff} +``` + +Other Packages +-------------- + +If `godebug/pretty` is not granular enough, I highly recommend +checking out [go-spew][spew]. + +[spew]: http://godoc.org/github.com/davecgh/go-spew/spew diff --git a/vendor/github.com/kylelemons/godebug/diff/diff.go b/vendor/github.com/kylelemons/godebug/diff/diff.go new file mode 100644 index 00000000..8d7716b9 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/diff/diff.go @@ -0,0 +1,133 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package diff implements a linewise diff algorithm. +package diff + +import ( + "bytes" + "fmt" + "strings" +) + +// Chunk represents a piece of the diff. A chunk will not have both added and +// deleted lines. Equal lines are always after any added or deleted lines. +// A Chunk may or may not have any lines in it, especially for the first or last +// chunk in a computation. +type Chunk struct { + Added []string + Deleted []string + Equal []string +} + +// Diff returns a string containing a line-by-line unified diff of the linewise +// changes required to make A into B. Each line is prefixed with '+', '-', or +// ' ' to indicate if it should be added, removed, or is correct respectively. +func Diff(A, B string) string { + aLines := strings.Split(A, "\n") + bLines := strings.Split(B, "\n") + + chunks := DiffChunks(aLines, bLines) + + buf := new(bytes.Buffer) + for _, c := range chunks { + for _, line := range c.Added { + fmt.Fprintf(buf, "+%s\n", line) + } + for _, line := range c.Deleted { + fmt.Fprintf(buf, "-%s\n", line) + } + for _, line := range c.Equal { + fmt.Fprintf(buf, " %s\n", line) + } + } + return strings.TrimRight(buf.String(), "\n") +} + +// DiffChunks uses an O(D(N+M)) shortest-edit-script algorithm +// to compute the edits required from A to B and returns the +// edit chunks. +func DiffChunks(A, B []string) []Chunk { + // algorithm: http://www.xmailserver.org/diff2.pdf + + N, M := len(A), len(B) + MAX := N + M + V := make([]int, 2*MAX+1) + Vs := make([][]int, 0, 8) + + var D int +dLoop: + for D = 0; D <= MAX; D++ { + for k := -D; k <= D; k += 2 { + var x int + if k == -D || (k != D && V[MAX+k-1] < V[MAX+k+1]) { + x = V[MAX+k+1] + } else { + x = V[MAX+k-1] + 1 + } + y := x - k + for x < N && y < M && A[x] == B[y] { + x++ + y++ + } + V[MAX+k] = x + if x >= N && y >= M { + Vs = append(Vs, append(make([]int, 0, len(V)), V...)) + break dLoop + } + } + Vs = append(Vs, append(make([]int, 0, len(V)), V...)) + } + if D == 0 { + return nil + } + chunks := make([]Chunk, D+1) + + x, y := N, M + for d := D; d > 0; d-- { + V := Vs[d] + k := x - y + insert := k == -d || (k != d && V[MAX+k-1] < V[MAX+k+1]) + + x1 := V[MAX+k] + var x0, xM, kk int + if insert { + kk = k + 1 + x0 = V[MAX+kk] + xM = x0 + } else { + kk = k - 1 + x0 = V[MAX+kk] + xM = x0 + 1 + } + y0 := x0 - kk + + var c Chunk + if insert { + c.Added = B[y0:][:1] + } else { + c.Deleted = A[x0:][:1] + } + if xM < x1 { + c.Equal = A[xM:][:x1-xM] + } + + x, y = x0, y0 + chunks[d] = c + } + if x > 0 { + chunks[0].Equal = A[:x] + } + return chunks +} diff --git a/vendor/github.com/kylelemons/godebug/diff/diff_test.go b/vendor/github.com/kylelemons/godebug/diff/diff_test.go new file mode 100644 index 00000000..ed3cb9ab --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/diff/diff_test.go @@ -0,0 +1,120 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package diff + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +func TestDiff(t *testing.T) { + tests := []struct { + desc string + A, B []string + chunks []Chunk + }{ + { + desc: "constitution", + A: []string{ + "We the People of the United States, in Order to form a more perfect Union,", + "establish Justice, insure domestic Tranquility, provide for the common defence,", + "and secure the Blessings of Liberty to ourselves", + "and our Posterity, do ordain and establish this Constitution for the United", + "States of America.", + }, + B: []string{ + "We the People of the United States, in Order to form a more perfect Union,", + "establish Justice, insure domestic Tranquility, provide for the common defence,", + "promote the general Welfare, and secure the Blessings of Liberty to ourselves", + "and our Posterity, do ordain and establish this Constitution for the United", + "States of America.", + }, + chunks: []Chunk{ + 0: { + Equal: []string{ + "We the People of the United States, in Order to form a more perfect Union,", + "establish Justice, insure domestic Tranquility, provide for the common defence,", + }, + }, + 1: { + Deleted: []string{ + "and secure the Blessings of Liberty to ourselves", + }, + }, + 2: { + Added: []string{ + "promote the general Welfare, and secure the Blessings of Liberty to ourselves", + }, + Equal: []string{ + "and our Posterity, do ordain and establish this Constitution for the United", + "States of America.", + }, + }, + }, + }, + } + + for _, test := range tests { + got := DiffChunks(test.A, test.B) + if got, want := len(got), len(test.chunks); got != want { + t.Errorf("%s: edit distance = %v, want %v", test.desc, got-1, want-1) + continue + } + for i := range got { + got, want := got[i], test.chunks[i] + if got, want := got.Added, want.Added; !reflect.DeepEqual(got, want) { + t.Errorf("%s[%d]: Added = %v, want %v", test.desc, i, got, want) + } + if got, want := got.Deleted, want.Deleted; !reflect.DeepEqual(got, want) { + t.Errorf("%s[%d]: Deleted = %v, want %v", test.desc, i, got, want) + } + if got, want := got.Equal, want.Equal; !reflect.DeepEqual(got, want) { + t.Errorf("%s[%d]: Equal = %v, want %v", test.desc, i, got, want) + } + } + } +} + +func ExampleDiff() { + constitution := strings.TrimSpace(` +We the People of the United States, in Order to form a more perfect Union, +establish Justice, insure domestic Tranquility, provide for the common defence, +promote the general Welfare, and secure the Blessings of Liberty to ourselves +and our Posterity, do ordain and establish this Constitution for the United +States of America. +`) + + got := strings.TrimSpace(` +:wq +We the People of the United States, in Order to form a more perfect Union, +establish Justice, insure domestic Tranquility, provide for the common defence, +and secure the Blessings of Liberty to ourselves +and our Posterity, do ordain and establish this Constitution for the United +States of America. +`) + + fmt.Println(Diff(got, constitution)) + + // Output: + // -:wq + // We the People of the United States, in Order to form a more perfect Union, + // establish Justice, insure domestic Tranquility, provide for the common defence, + // -and secure the Blessings of Liberty to ourselves + // +promote the general Welfare, and secure the Blessings of Liberty to ourselves + // and our Posterity, do ordain and establish this Constitution for the United + // States of America. +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/.gitignore b/vendor/github.com/kylelemons/godebug/pretty/.gitignore new file mode 100644 index 00000000..fa9a735d --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/.gitignore @@ -0,0 +1,5 @@ +*.test +*.bench +*.golden +*.txt +*.prof diff --git a/vendor/github.com/kylelemons/godebug/pretty/doc.go b/vendor/github.com/kylelemons/godebug/pretty/doc.go new file mode 100644 index 00000000..03b5718a --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/doc.go @@ -0,0 +1,25 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package pretty pretty-prints Go structures. +// +// This package uses reflection to examine a Go value and can +// print out in a nice, aligned fashion. It supports three +// modes (normal, compact, and extended) for advanced use. +// +// See the Reflect and Print examples for what the output looks like. +package pretty + +// TODO: +// - Catch cycles diff --git a/vendor/github.com/kylelemons/godebug/pretty/examples_test.go b/vendor/github.com/kylelemons/godebug/pretty/examples_test.go new file mode 100644 index 00000000..0df291cd --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/examples_test.go @@ -0,0 +1,281 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty_test + +import ( + "fmt" + + "github.com/kylelemons/godebug/pretty" +) + +func ExampleConfig_Sprint() { + type Pair [2]int + type Map struct { + Name string + Players map[string]Pair + Obstacles map[Pair]string + } + + m := Map{ + Name: "Rock Creek", + Players: map[string]Pair{ + "player1": {1, 3}, + "player2": {0, -1}, + }, + Obstacles: map[Pair]string{ + Pair{0, 0}: "rock", + Pair{2, 1}: "pond", + Pair{1, 1}: "stream", + Pair{0, 1}: "stream", + }, + } + + // Specific output formats + compact := &pretty.Config{ + Compact: true, + } + diffable := &pretty.Config{ + Diffable: true, + } + + // Print out a summary + fmt.Printf("Players: %s\n", compact.Sprint(m.Players)) + + // Print diffable output + fmt.Printf("Map State:\n%s", diffable.Sprint(m)) + + // Output: + // Players: {player1:[1,3],player2:[0,-1]} + // Map State: + // { + // Name: "Rock Creek", + // Players: { + // player1: [ + // 1, + // 3, + // ], + // player2: [ + // 0, + // -1, + // ], + // }, + // Obstacles: { + // [0,0]: "rock", + // [0,1]: "stream", + // [1,1]: "stream", + // [2,1]: "pond", + // }, + // } +} + +func ExamplePrint() { + type ShipManifest struct { + Name string + Crew map[string]string + Androids int + Stolen bool + } + + manifest := &ShipManifest{ + Name: "Spaceship Heart of Gold", + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + "Trillian": "Human", + "Ford Prefect": "A Hoopy Frood", + "Arthur Dent": "Along for the Ride", + }, + Androids: 1, + Stolen: true, + } + + pretty.Print(manifest) + + // Output: + // {Name: "Spaceship Heart of Gold", + // Crew: {Arthur Dent: "Along for the Ride", + // Ford Prefect: "A Hoopy Frood", + // Trillian: "Human", + // Zaphod Beeblebrox: "Galactic President"}, + // Androids: 1, + // Stolen: true} +} + +var t = struct { + Errorf func(string, ...interface{}) +}{ + Errorf: func(format string, args ...interface{}) { + fmt.Println(fmt.Sprintf(format, args...) + "\n") + }, +} + +func ExampleCompare_testing() { + // Code under test: + + type ShipManifest struct { + Name string + Crew map[string]string + Androids int + Stolen bool + } + + // AddCrew tries to add the given crewmember to the manifest. + AddCrew := func(m *ShipManifest, name, title string) { + if m.Crew == nil { + m.Crew = make(map[string]string) + } + m.Crew[title] = name + } + + // Test function: + tests := []struct { + desc string + before *ShipManifest + name, title string + after *ShipManifest + }{ + { + desc: "add first", + before: &ShipManifest{}, + name: "Zaphod Beeblebrox", + title: "Galactic President", + after: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + }, + }, + }, + { + desc: "add another", + before: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + }, + }, + name: "Trillian", + title: "Human", + after: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + "Trillian": "Human", + }, + }, + }, + { + desc: "overwrite", + before: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + }, + }, + name: "Zaphod Beeblebrox", + title: "Just this guy, you know?", + after: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Just this guy, you know?", + }, + }, + }, + } + + for _, test := range tests { + AddCrew(test.before, test.name, test.title) + if diff := pretty.Compare(test.before, test.after); diff != "" { + t.Errorf("%s: post-AddCrew diff: (-got +want)\n%s", test.desc, diff) + } + } + + // Output: + // add first: post-AddCrew diff: (-got +want) + // { + // Name: "", + // Crew: { + // - Galactic President: "Zaphod Beeblebrox", + // + Zaphod Beeblebrox: "Galactic President", + // }, + // Androids: 0, + // Stolen: false, + // } + // + // add another: post-AddCrew diff: (-got +want) + // { + // Name: "", + // Crew: { + // - Human: "Trillian", + // + Trillian: "Human", + // Zaphod Beeblebrox: "Galactic President", + // }, + // Androids: 0, + // Stolen: false, + // } + // + // overwrite: post-AddCrew diff: (-got +want) + // { + // Name: "", + // Crew: { + // - Just this guy, you know?: "Zaphod Beeblebrox", + // - Zaphod Beeblebrox: "Galactic President", + // + Zaphod Beeblebrox: "Just this guy, you know?", + // }, + // Androids: 0, + // Stolen: false, + // } +} + +func ExampleCompare_debugging() { + type ShipManifest struct { + Name string + Crew map[string]string + Androids int + Stolen bool + } + + reported := &ShipManifest{ + Name: "Spaceship Heart of Gold", + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + "Trillian": "Human", + "Ford Prefect": "A Hoopy Frood", + "Arthur Dent": "Along for the Ride", + }, + Androids: 1, + Stolen: true, + } + + expected := &ShipManifest{ + Name: "Spaceship Heart of Gold", + Crew: map[string]string{ + "Trillian": "Human", + "Rowan Artosok": "Captain", + }, + Androids: 1, + Stolen: false, + } + + fmt.Println(pretty.Compare(reported, expected)) + // Output: + // { + // Name: "Spaceship Heart of Gold", + // Crew: { + // - Arthur Dent: "Along for the Ride", + // - Ford Prefect: "A Hoopy Frood", + // + Rowan Artosok: "Captain", + // Trillian: "Human", + // - Zaphod Beeblebrox: "Galactic President", + // }, + // Androids: 1, + // - Stolen: true, + // + Stolen: false, + // } +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/public.go b/vendor/github.com/kylelemons/godebug/pretty/public.go new file mode 100644 index 00000000..4bf893da --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/public.go @@ -0,0 +1,116 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "bytes" + "fmt" + "io" + "reflect" + + "github.com/kylelemons/godebug/diff" +) + +// A Config represents optional configuration parameters for formatting. +// +// Some options, notably ShortList, dramatically increase the overhead +// of pretty-printing a value. +type Config struct { + // Verbosity options + Compact bool // One-line output. Overrides Diffable. + Diffable bool // Adds extra newlines for more easily diffable output. + + // Field and value options + IncludeUnexported bool // Include unexported fields in output + PrintStringers bool // Call String on a fmt.Stringer + PrintTextMarshalers bool // Call MarshalText on an encoding.TextMarshaler + SkipZeroFields bool // Skip struct fields that have a zero value. + + // Output transforms + ShortList int // Maximum character length for short lists if nonzero. +} + +// Default Config objects +var ( + // CompareConfig is the default configuration used for Compare. + CompareConfig = &Config{ + Diffable: true, + IncludeUnexported: true, + } + + // DefaultConfig is the default configuration used for all other top-level functions. + DefaultConfig = &Config{} +) + +func (cfg *Config) fprint(buf *bytes.Buffer, vals ...interface{}) { + for i, val := range vals { + if i > 0 { + buf.WriteByte('\n') + } + cfg.val2node(reflect.ValueOf(val)).WriteTo(buf, "", cfg) + } +} + +// Print writes the DefaultConfig representation of the given values to standard output. +func Print(vals ...interface{}) { + DefaultConfig.Print(vals...) +} + +// Print writes the configured presentation of the given values to standard output. +func (cfg *Config) Print(vals ...interface{}) { + fmt.Println(cfg.Sprint(vals...)) +} + +// Sprint returns a string representation of the given value according to the DefaultConfig. +func Sprint(vals ...interface{}) string { + return DefaultConfig.Sprint(vals...) +} + +// Sprint returns a string representation of the given value according to cfg. +func (cfg *Config) Sprint(vals ...interface{}) string { + buf := new(bytes.Buffer) + cfg.fprint(buf, vals...) + return buf.String() +} + +// Fprint writes the representation of the given value to the writer according to the DefaultConfig. +func Fprint(w io.Writer, vals ...interface{}) (n int64, err error) { + return DefaultConfig.Fprint(w, vals...) +} + +// Fprint writes the representation of the given value to the writer according to the cfg. +func (cfg *Config) Fprint(w io.Writer, vals ...interface{}) (n int64, err error) { + buf := new(bytes.Buffer) + cfg.fprint(buf, vals...) + return buf.WriteTo(w) +} + +// Compare returns a string containing a line-by-line unified diff of the +// values in got and want, using the CompareConfig. +// +// Each line in the output is prefixed with '+', '-', or ' ' to indicate if it +// should be added to, removed from, or is correct for the "got" value with +// respect to the "want" value. +func Compare(got, want interface{}) string { + return CompareConfig.Compare(got, want) +} + +// Compare returns a string containing a line-by-line unified diff of the +// values in got and want according to the cfg. +func (cfg *Config) Compare(got, want interface{}) string { + diffCfg := *cfg + diffCfg.Diffable = true + return diff.Diff(cfg.Sprint(got), cfg.Sprint(want)) +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/public_test.go b/vendor/github.com/kylelemons/godebug/pretty/public_test.go new file mode 100644 index 00000000..01fd3ffe --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/public_test.go @@ -0,0 +1,128 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "testing" +) + +func TestDiff(t *testing.T) { + type example struct { + Name string + Age int + Friends []string + } + + tests := []struct { + desc string + got, want interface{} + diff string + }{ + { + desc: "basic struct", + got: example{ + Name: "Zaphd", + Age: 42, + Friends: []string{ + "Ford Prefect", + "Trillian", + "Marvin", + }, + }, + want: example{ + Name: "Zaphod", + Age: 42, + Friends: []string{ + "Ford Prefect", + "Trillian", + }, + }, + diff: ` { +- Name: "Zaphd", ++ Name: "Zaphod", + Age: 42, + Friends: [ + "Ford Prefect", + "Trillian", +- "Marvin", + ], + }`, + }, + } + + for _, test := range tests { + if got, want := Compare(test.got, test.want), test.diff; got != want { + t.Errorf("%s:", test.desc) + t.Errorf(" got: %q", got) + t.Errorf(" want: %q", want) + } + } +} + +func TestSkipZeroFields(t *testing.T) { + type example struct { + Name string + Species string + Age int + Friends []string + } + + tests := []struct { + desc string + got, want interface{} + diff string + }{ + { + desc: "basic struct", + got: example{ + Name: "Zaphd", + Species: "Betelgeusian", + Age: 42, + }, + want: example{ + Name: "Zaphod", + Species: "Betelgeusian", + Age: 42, + Friends: []string{ + "Ford Prefect", + "Trillian", + "", + }, + }, + diff: ` { +- Name: "Zaphd", ++ Name: "Zaphod", + Species: "Betelgeusian", + Age: 42, ++ Friends: [ ++ "Ford Prefect", ++ "Trillian", ++ "", ++ ], + }`, + }, + } + + cfg := *CompareConfig + cfg.SkipZeroFields = true + + for _, test := range tests { + if got, want := cfg.Compare(test.got, test.want), test.diff; got != want { + t.Errorf("%s:", test.desc) + t.Errorf(" got: %q", got) + t.Errorf(" want: %q", want) + } + } +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/reflect.go b/vendor/github.com/kylelemons/godebug/pretty/reflect.go new file mode 100644 index 00000000..f41407e4 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/reflect.go @@ -0,0 +1,114 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "encoding" + "fmt" + "reflect" + "sort" +) + +func isZeroVal(val reflect.Value) bool { + if !val.CanInterface() { + return false + } + z := reflect.Zero(val.Type()).Interface() + return reflect.DeepEqual(val.Interface(), z) +} + +func (c *Config) val2node(val reflect.Value) node { + // TODO(kevlar): pointer tracking? + + if !val.IsValid() { + return rawVal("nil") + } + + if val.CanInterface() { + v := val.Interface() + if s, ok := v.(fmt.Stringer); ok && c.PrintStringers { + return stringVal(s.String()) + } + if t, ok := v.(encoding.TextMarshaler); ok && c.PrintTextMarshalers { + if raw, err := t.MarshalText(); err == nil { // if NOT an error + return stringVal(string(raw)) + } + } + } + + switch kind := val.Kind(); kind { + case reflect.Ptr, reflect.Interface: + if val.IsNil() { + return rawVal("nil") + } + return c.val2node(val.Elem()) + case reflect.String: + return stringVal(val.String()) + case reflect.Slice, reflect.Array: + n := list{} + length := val.Len() + for i := 0; i < length; i++ { + n = append(n, c.val2node(val.Index(i))) + } + return n + case reflect.Map: + n := keyvals{} + keys := val.MapKeys() + for _, key := range keys { + // TODO(kevlar): Support arbitrary type keys? + n = append(n, keyval{compactString(c.val2node(key)), c.val2node(val.MapIndex(key))}) + } + sort.Sort(n) + return n + case reflect.Struct: + n := keyvals{} + typ := val.Type() + fields := typ.NumField() + for i := 0; i < fields; i++ { + sf := typ.Field(i) + if !c.IncludeUnexported && sf.PkgPath != "" { + continue + } + field := val.Field(i) + if c.SkipZeroFields && isZeroVal(field) { + continue + } + n = append(n, keyval{sf.Name, c.val2node(field)}) + } + return n + case reflect.Bool: + if val.Bool() { + return rawVal("true") + } + return rawVal("false") + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rawVal(fmt.Sprintf("%d", val.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rawVal(fmt.Sprintf("%d", val.Uint())) + case reflect.Uintptr: + return rawVal(fmt.Sprintf("0x%X", val.Uint())) + case reflect.Float32, reflect.Float64: + return rawVal(fmt.Sprintf("%v", val.Float())) + case reflect.Complex64, reflect.Complex128: + return rawVal(fmt.Sprintf("%v", val.Complex())) + } + + // Fall back to the default %#v if we can + if val.CanInterface() { + return rawVal(fmt.Sprintf("%#v", val.Interface())) + } + + return rawVal(val.String()) +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/reflect_test.go b/vendor/github.com/kylelemons/godebug/pretty/reflect_test.go new file mode 100644 index 00000000..c44c94c0 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/reflect_test.go @@ -0,0 +1,168 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "reflect" + "testing" + "time" +) + +func TestVal2nodeDefault(t *testing.T) { + tests := []struct { + desc string + raw interface{} + want node + }{ + { + desc: "nil", + raw: nil, + want: rawVal("nil"), + }, + { + desc: "nil ptr", + raw: (*int)(nil), + want: rawVal("nil"), + }, + { + desc: "nil slice", + raw: []string(nil), + want: list{}, + }, + { + desc: "nil map", + raw: map[string]string(nil), + want: keyvals{}, + }, + { + desc: "string", + raw: "zaphod", + want: stringVal("zaphod"), + }, + { + desc: "slice", + raw: []string{"a", "b"}, + want: list{stringVal("a"), stringVal("b")}, + }, + { + desc: "map", + raw: map[string]string{ + "zaphod": "beeblebrox", + "ford": "prefect", + }, + want: keyvals{ + {"ford", stringVal("prefect")}, + {"zaphod", stringVal("beeblebrox")}, + }, + }, + { + desc: "map of [2]int", + raw: map[[2]int]string{ + [2]int{-1, 2}: "school", + [2]int{0, 0}: "origin", + [2]int{1, 3}: "home", + }, + want: keyvals{ + {"[-1,2]", stringVal("school")}, + {"[0,0]", stringVal("origin")}, + {"[1,3]", stringVal("home")}, + }, + }, + { + desc: "struct", + raw: struct{ Zaphod, Ford string }{"beeblebrox", "prefect"}, + want: keyvals{ + {"Zaphod", stringVal("beeblebrox")}, + {"Ford", stringVal("prefect")}, + }, + }, + { + desc: "int", + raw: 3, + want: rawVal("3"), + }, + } + + for _, test := range tests { + if got, want := DefaultConfig.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) { + t.Errorf("%s: got %#v, want %#v", test.desc, got, want) + } + } +} + +func TestVal2node(t *testing.T) { + tests := []struct { + desc string + raw interface{} + cfg *Config + want node + }{ + { + desc: "struct default", + raw: struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "BAD"}, + cfg: DefaultConfig, + want: keyvals{ + {"Zaphod", stringVal("beeblebrox")}, + {"Ford", stringVal("prefect")}, + }, + }, + { + desc: "struct w/ IncludeUnexported", + raw: struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "GOOD"}, + cfg: &Config{ + IncludeUnexported: true, + }, + want: keyvals{ + {"Zaphod", stringVal("beeblebrox")}, + {"Ford", stringVal("prefect")}, + {"foo", stringVal("GOOD")}, + }, + }, + { + desc: "time default", + raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, + cfg: DefaultConfig, + want: keyvals{ + {"Date", keyvals{}}, + }, + }, + { + desc: "time w/ PrintTextMarshalers", + raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, + cfg: &Config{ + PrintTextMarshalers: true, + }, + want: keyvals{ + {"Date", stringVal("2009-02-13T23:31:30Z")}, + }, + }, + { + desc: "time w/ PrintStringers", + raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, + cfg: &Config{ + PrintStringers: true, + }, + want: keyvals{ + {"Date", stringVal("2009-02-13 23:31:30 +0000 UTC")}, + }, + }, + } + + for _, test := range tests { + if got, want := test.cfg.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) { + t.Errorf("%s: got %#v, want %#v", test.desc, got, want) + } + } +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/structure.go b/vendor/github.com/kylelemons/godebug/pretty/structure.go new file mode 100644 index 00000000..a2f3bb7e --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/structure.go @@ -0,0 +1,160 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "bytes" + "strconv" + "strings" +) + +type node interface { + WriteTo(w *bytes.Buffer, indent string, cfg *Config) +} + +func compactString(n node) string { + switch k := n.(type) { + case stringVal: + return string(k) + case rawVal: + return string(k) + } + + buf := new(bytes.Buffer) + n.WriteTo(buf, "", &Config{Compact: true}) + return buf.String() +} + +type stringVal string + +func (str stringVal) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + w.WriteString(strconv.Quote(string(str))) +} + +type rawVal string + +func (r rawVal) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + w.WriteString(string(r)) +} + +type keyval struct { + key string + val node +} + +type keyvals []keyval + +func (l keyvals) Len() int { return len(l) } +func (l keyvals) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyvals) Less(i, j int) bool { return l[i].key < l[j].key } + +func (l keyvals) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + w.WriteByte('{') + + switch { + case cfg.Compact: + // All on one line: + for i, kv := range l { + if i > 0 { + w.WriteByte(',') + } + w.WriteString(kv.key) + w.WriteByte(':') + kv.val.WriteTo(w, indent, cfg) + } + case cfg.Diffable: + w.WriteByte('\n') + inner := indent + " " + // Each value gets its own line: + for _, kv := range l { + w.WriteString(inner) + w.WriteString(kv.key) + w.WriteString(": ") + kv.val.WriteTo(w, inner, cfg) + w.WriteString(",\n") + } + w.WriteString(indent) + default: + keyWidth := 0 + for _, kv := range l { + if kw := len(kv.key); kw > keyWidth { + keyWidth = kw + } + } + alignKey := indent + " " + alignValue := strings.Repeat(" ", keyWidth) + inner := alignKey + alignValue + " " + // First and last line shared with bracket: + for i, kv := range l { + if i > 0 { + w.WriteString(",\n") + w.WriteString(alignKey) + } + w.WriteString(kv.key) + w.WriteString(": ") + w.WriteString(alignValue[len(kv.key):]) + kv.val.WriteTo(w, inner, cfg) + } + } + + w.WriteByte('}') +} + +type list []node + +func (l list) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + if max := cfg.ShortList; max > 0 { + short := compactString(l) + if len(short) <= max { + w.WriteString(short) + return + } + } + + w.WriteByte('[') + + switch { + case cfg.Compact: + // All on one line: + for i, v := range l { + if i > 0 { + w.WriteByte(',') + } + v.WriteTo(w, indent, cfg) + } + case cfg.Diffable: + w.WriteByte('\n') + inner := indent + " " + // Each value gets its own line: + for _, v := range l { + w.WriteString(inner) + v.WriteTo(w, inner, cfg) + w.WriteString(",\n") + } + w.WriteString(indent) + default: + inner := indent + " " + // First and last line shared with bracket: + for i, v := range l { + if i > 0 { + w.WriteString(",\n") + w.WriteString(inner) + } + v.WriteTo(w, inner, cfg) + } + } + + w.WriteByte(']') +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/structure_test.go b/vendor/github.com/kylelemons/godebug/pretty/structure_test.go new file mode 100644 index 00000000..5fd2d9a5 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/structure_test.go @@ -0,0 +1,316 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "bytes" + "strings" + "testing" +) + +func TestWriteTo(t *testing.T) { + tests := []struct { + desc string + node node + + // All strings have a leading newline trimmed before comparison: + normal string + diffable string + }{ + { + desc: "string", + node: stringVal("zaphod"), + normal: `"zaphod"`, + diffable: `"zaphod"`, + }, + { + desc: "raw", + node: rawVal("42"), + normal: `42`, + diffable: `42`, + }, + { + desc: "keyvals", + node: keyvals{ + {"name", stringVal("zaphod")}, + {"age", rawVal("42")}, + }, + normal: ` +{name: "zaphod", + age: 42}`, + diffable: ` +{ + name: "zaphod", + age: 42, +}`, + }, + { + desc: "empty list", + node: list{}, + normal: ` +[]`, + diffable: ` +[ +]`, + }, + { + desc: "empty nested list", + node: list{list{}}, + normal: ` +[[]]`, + diffable: ` +[ + [ + ], +]`, + }, + { + desc: "list", + node: list{ + stringVal("zaphod"), + rawVal("42"), + }, + normal: ` +["zaphod", + 42]`, + diffable: ` +[ + "zaphod", + 42, +]`, + }, + { + desc: "empty keyvals", + node: keyvals{}, + normal: ` +{}`, + diffable: ` +{ +}`, + }, + { + desc: "empty nested keyvals", + node: keyvals{{"k", keyvals{}}}, + normal: ` +{k: {}}`, + diffable: ` +{ + k: { + }, +}`, + }, + { + desc: "nested", + node: list{ + stringVal("first"), + list{rawVal("1"), rawVal("2"), rawVal("3")}, + keyvals{ + {"trillian", keyvals{ + {"race", stringVal("human")}, + {"age", rawVal("36")}, + }}, + {"zaphod", keyvals{ + {"occupation", stringVal("president of the galaxy")}, + {"features", stringVal("two heads")}, + }}, + }, + keyvals{}, + }, + normal: ` +["first", + [1, + 2, + 3], + {trillian: {race: "human", + age: 36}, + zaphod: {occupation: "president of the galaxy", + features: "two heads"}}, + {}]`, + diffable: ` +[ + "first", + [ + 1, + 2, + 3, + ], + { + trillian: { + race: "human", + age: 36, + }, + zaphod: { + occupation: "president of the galaxy", + features: "two heads", + }, + }, + { + }, +]`, + }, + } + + for _, test := range tests { + // For readability, we have a newline that won't be there in the output + test.normal = strings.TrimPrefix(test.normal, "\n") + test.diffable = strings.TrimPrefix(test.diffable, "\n") + + buf := new(bytes.Buffer) + test.node.WriteTo(buf, "", &Config{}) + if got, want := buf.String(), test.normal; got != want { + t.Errorf("%s: normal rendendered incorrectly\ngot:\n%s\nwant:\n%s", test.desc, got, want) + } + buf.Reset() + test.node.WriteTo(buf, "", &Config{Diffable: true}) + if got, want := buf.String(), test.diffable; got != want { + t.Errorf("%s: diffable rendendered incorrectly\ngot:\n%s\nwant:\n%s", test.desc, got, want) + } + } +} + +func TestCompactString(t *testing.T) { + tests := []struct { + node + compact string + }{ + { + stringVal("abc"), + "abc", + }, + { + rawVal("2"), + "2", + }, + { + list{ + rawVal("2"), + rawVal("3"), + }, + "[2,3]", + }, + { + keyvals{ + {"name", stringVal("zaphod")}, + {"age", rawVal("42")}, + }, + `{name:"zaphod",age:42}`, + }, + { + list{ + list{ + rawVal("0"), + rawVal("1"), + rawVal("2"), + rawVal("3"), + }, + list{ + rawVal("1"), + rawVal("2"), + rawVal("3"), + rawVal("0"), + }, + list{ + rawVal("2"), + rawVal("3"), + rawVal("0"), + rawVal("1"), + }, + }, + `[[0,1,2,3],[1,2,3,0],[2,3,0,1]]`, + }, + } + + for _, test := range tests { + if got, want := compactString(test.node), test.compact; got != want { + t.Errorf("%#v: compact = %q, want %q", test.node, got, want) + } + } +} + +func TestShortList(t *testing.T) { + cfg := &Config{ + ShortList: 16, + } + + tests := []struct { + node + want string + }{ + { + list{ + list{ + rawVal("0"), + rawVal("1"), + rawVal("2"), + rawVal("3"), + }, + list{ + rawVal("1"), + rawVal("2"), + rawVal("3"), + rawVal("0"), + }, + list{ + rawVal("2"), + rawVal("3"), + rawVal("0"), + rawVal("1"), + }, + }, + `[[0,1,2,3], + [1,2,3,0], + [2,3,0,1]]`, + }, + } + + for _, test := range tests { + buf := new(bytes.Buffer) + test.node.WriteTo(buf, "", cfg) + if got, want := buf.String(), test.want; got != want { + t.Errorf("%#v: got:\n%s\nwant:\n%s", test.node, got, want) + } + } +} + +var benchNode = keyvals{ + {"list", list{ + rawVal("0"), + rawVal("1"), + rawVal("2"), + rawVal("3"), + }}, + {"keyvals", keyvals{ + {"a", stringVal("b")}, + {"c", stringVal("e")}, + {"d", stringVal("f")}, + }}, +} + +func benchOpts(b *testing.B, cfg *Config) { + buf := new(bytes.Buffer) + benchNode.WriteTo(buf, "", cfg) + b.SetBytes(int64(buf.Len())) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + buf.Reset() + benchNode.WriteTo(buf, "", cfg) + } +} + +func BenchmarkWriteDefault(b *testing.B) { benchOpts(b, DefaultConfig) } +func BenchmarkWriteShortList(b *testing.B) { benchOpts(b, &Config{ShortList: 16}) } +func BenchmarkWriteCompact(b *testing.B) { benchOpts(b, &Config{Compact: true}) } +func BenchmarkWriteDiffable(b *testing.B) { benchOpts(b, &Config{Diffable: true}) }