mirror of https://github.com/dexidp/dex.git
25 changed files with 2054 additions and 11 deletions
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
@ -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 |
||||||
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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") |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
language: go |
||||||
|
|
||||||
|
go: |
||||||
|
- 1.6 |
||||||
|
- tip |
||||||
@ -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. |
||||||
@ -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 |
||||||
@ -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 |
||||||
|
} |
||||||
@ -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.
|
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
*.test |
||||||
|
*.bench |
||||||
|
*.golden |
||||||
|
*.txt |
||||||
|
*.prof |
||||||
@ -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
|
||||||
@ -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,
|
||||||
|
// }
|
||||||
|
} |
||||||
@ -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)) |
||||||
|
} |
||||||
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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()) |
||||||
|
} |
||||||
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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(']') |
||||||
|
} |
||||||
@ -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}) } |
||||||
Loading…
Reference in new issue