You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2021/01/08 23:05:40 UTC
[trafficcontrol] branch master updated: Ort diff-tool for t3c
(#5337)
This is an automated email from the ASF dual-hosted git repository.
ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 7a3506a Ort diff-tool for t3c (#5337)
7a3506a is described below
commit 7a3506a19a064f787b1f02de28c12feab24ff7c1
Author: Joe Pappano <jo...@cable.comcast.com>
AuthorDate: Fri Jan 8 18:05:26 2021 -0500
Ort diff-tool for t3c (#5337)
* Initial commit of t3c-diff tool
* Initial commit of t3c-util package
* Added regex to only print changes.
* Changed variable name.
* Fixed typos, and diff variable that collided with package name.
* Adding new dependency to vendor folder
* Adding new dependency to vendor folder one more time to get subdirectories.
* Swapped regex for less than, greater than, and ampersand with html.UnescapeString.
* Swapped regex for less than, greater than, and ampersand with html.UnescapeString.
* Added license info for the kylelemons/godebug component
* Updated paths for license info for the kylelemons/godebug component
---
.dependency_license | 1 +
LICENSE | 4 +
traffic_ops_ort/t3c-diff-tool/t3c-diff-tool.go | 71 ++++
traffic_ops_ort/t3cutil/t3cutil.go | 87 ++++
vendor/github.com/kylelemons/godebug/.travis.yml | 10 +
vendor/github.com/kylelemons/godebug/LICENSE | 202 +++++++++
vendor/github.com/kylelemons/godebug/README.md | 65 +++
vendor/github.com/kylelemons/godebug/diff/diff.go | 189 +++++++++
.../kylelemons/godebug/diff/diff_test.go | 228 +++++++++++
vendor/github.com/kylelemons/godebug/go.mod | 3 +
vendor/github.com/kylelemons/godebug/pretty/doc.go | 22 +
.../kylelemons/godebug/pretty/examples_test.go | 373 +++++++++++++++++
.../github.com/kylelemons/godebug/pretty/public.go | 188 +++++++++
.../kylelemons/godebug/pretty/public_test.go | 155 +++++++
.../kylelemons/godebug/pretty/reflect.go | 255 ++++++++++++
.../kylelemons/godebug/pretty/reflect_test.go | 456 +++++++++++++++++++++
.../kylelemons/godebug/pretty/structure.go | 223 ++++++++++
.../kylelemons/godebug/pretty/structure_test.go | 371 +++++++++++++++++
18 files changed, 2903 insertions(+)
diff --git a/.dependency_license b/.dependency_license
index cca8aab..3d7039f 100644
--- a/.dependency_license
+++ b/.dependency_license
@@ -111,6 +111,7 @@ jquery\.tree\.min\.css$, MIT
jquery\.dataTables\..*\.(css|js)$, MIT
github\.com/basho/backoff/.*, MIT
github\.com/dchest/siphash/.*, CC0
+github\.com/kylelemons/godebug.*, Apache-2.0
github\.com/pkg/errors\..*, BSD
traffic_portal/app/src/assets/js/chartjs/angular-chart\..*, BSD
traffic_portal/app/src/assets/css/jsonformatter\..*, Apache-2.0
diff --git a/LICENSE b/LICENSE
index 63d8da3..ebfc84d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -625,6 +625,10 @@ For the kolo/xmlrpc component:
@traffic_ops/traffic_ops_golang/vendor/github.com/kolo/xmlrpc/*
./traffic_ops/traffic_ops_golang/vendor/github.com/kolo/xmlrpc/LICENSE
+For the kylelemons/godebug component:
+@vendor/github.com/kylelemons/godebug/*
+.vendor/github.com/kylelemons/godebug/LICENSE
+
For the labbsr0x/bindman-dns-webhook component:
@traffic_ops/traffic_ops_golang/vendor/github.com/labbsr0x/bindman-dns-webhook/*
./traffic_ops/traffic_ops_golang/vendor/github.com/labbsr0x/bindman-dns-webhook/LICENSE
diff --git a/traffic_ops_ort/t3c-diff-tool/t3c-diff-tool.go b/traffic_ops_ort/t3c-diff-tool/t3c-diff-tool.go
new file mode 100644
index 0000000..8e8d623
--- /dev/null
+++ b/traffic_ops_ort/t3c-diff-tool/t3c-diff-tool.go
@@ -0,0 +1,71 @@
+package main
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import (
+ "fmt"
+ "os"
+ "regexp"
+ "strings"
+
+ "github.com/apache/trafficcontrol/traffic_ops_ort/t3cutil"
+ "github.com/kylelemons/godebug/diff"
+ "github.com/pborman/getopt/v2"
+)
+
+func main() {
+ tropsFile := getopt.StringLong("trops-file", 't', "", "Required: Config file name in Traffic Ops")
+ diskFile := getopt.StringLong("disk-file", 'd', "", "Required: Config file on disk")
+ help := getopt.BoolLong("help", 'h', "Print usage info and exit")
+ getopt.ParseV2()
+
+ if *help {
+ getopt.PrintUsage(os.Stdout)
+ os.Exit(0)
+ }
+ if len(strings.TrimSpace(*tropsFile)) == 0 || len(strings.TrimSpace(*diskFile)) == 0 {
+ getopt.PrintUsage(os.Stdout)
+ os.Exit(1)
+ }
+ trafOpsInput := t3cutil.ReadFile(*tropsFile)
+ diskInput := t3cutil.ReadFile(*diskFile)
+
+ trOpsData := strings.Split(string(trafOpsInput), "\n")
+ trOpsData = t3cutil.UnencodeFilter(trOpsData)
+ trOpsData = t3cutil.CommentsFilter(trOpsData)
+ trOps := strings.Join(trOpsData, "\n")
+ trOps = t3cutil.NewLineFilter(trOps)
+
+ diskData := strings.Split(string(diskInput), "\n")
+ diskData = t3cutil.UnencodeFilter(diskData)
+ diskData = t3cutil.CommentsFilter(diskData)
+ disk := strings.Join(diskData, "\n")
+ disk = t3cutil.NewLineFilter(disk)
+
+ if trOps != disk {
+ match := regexp.MustCompile(`(?m)^\+.*|^-.*`)
+ changes := diff.Diff(disk, trOps)
+ for _, change := range match.FindAllString(changes, -1) {
+ fmt.Println(change)
+ }
+ } else {
+ os.Exit(0)
+ }
+}
diff --git a/traffic_ops_ort/t3cutil/t3cutil.go b/traffic_ops_ort/t3cutil/t3cutil.go
new file mode 100644
index 0000000..ba8b728
--- /dev/null
+++ b/traffic_ops_ort/t3cutil/t3cutil.go
@@ -0,0 +1,87 @@
+package t3cutil
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import (
+ "fmt"
+ "html"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "strings"
+)
+
+// commentsFilter is used to remove comment
+// lines from config files while making
+// comparisons.
+func CommentsFilter(body []string) []string {
+ var newlines []string
+
+ newlines = make([]string, 0)
+
+ for ii := range body {
+ line := body[ii]
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+ newlines = append(newlines, line)
+ }
+
+ return newlines
+}
+
+// NewLineFilter removes carriage returns
+// from config files while making comparisons.
+func NewLineFilter(str string) string {
+ str = strings.ReplaceAll(str, "\r\n", "\n")
+ return strings.TrimSpace(str)
+}
+
+// ReadFile reads a file and returns the
+// file contents.
+func ReadFile(f string) []byte {
+ data, err := ioutil.ReadFile(f)
+ if err != nil {
+ fmt.Println("Error reading file ", f)
+ os.Exit(1)
+ }
+ return data
+}
+
+// UnencodeFilter translates HTML escape
+// sequences while making config file comparisons.
+func UnencodeFilter(body []string) []string {
+ var newlines []string
+
+ newlines = make([]string, 0)
+ sp := regexp.MustCompile(`\s+`)
+ el := regexp.MustCompile(`^\s+|\s+$`)
+
+ for ii := range body {
+ s := body[ii]
+ s = sp.ReplaceAllString(s, " ")
+ s = el.ReplaceAllString(s, "")
+ s = html.UnescapeString(s)
+ s = strings.TrimSpace(s)
+ newlines = append(newlines, s)
+ }
+
+ return newlines
+}
diff --git a/vendor/github.com/kylelemons/godebug/.travis.yml b/vendor/github.com/kylelemons/godebug/.travis.yml
new file mode 100644
index 0000000..3ddfb8a
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/.travis.yml
@@ -0,0 +1,10 @@
+language: go
+
+go:
+ - "1.14.x"
+ - "1.15.x"
+
+arch:
+ - amd64
+ - arm64
+ - ppc64le
diff --git a/vendor/github.com/kylelemons/godebug/LICENSE b/vendor/github.com/kylelemons/godebug/LICENSE
new file mode 100644
index 0000000..d645695
--- /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 0000000..f225f48
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/README.md
@@ -0,0 +1,65 @@
+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 [cmp][cmp] or [go-spew][spew].
+
+[cmp]: https://godoc.org/github.com/google/go-cmp/cmp
+[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 0000000..71b459f
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/diff/diff.go
@@ -0,0 +1,189 @@
+// 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 (
+ "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
+}
+
+func (c *Chunk) empty() bool {
+ return len(c.Added) == 0 && len(c.Deleted) == 0 && len(c.Equal) == 0
+}
+
+// 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")
+ return Render(DiffChunks(aLines, bLines))
+}
+
+// Render renders the slice of chunks into a representation that prefixes
+// the lines with '+', '-', or ' ' depending on whether the line was added,
+// removed, or equal (respectively).
+func Render(chunks []Chunk) string {
+ buf := new(strings.Builder)
+ 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
+
+ // We'll need these quantities a lot.
+ alen, blen := len(a), len(b) // M, N
+
+ // At most, it will require len(a) deletions and len(b) additions
+ // to transform a into b.
+ maxPath := alen + blen // MAX
+ if maxPath == 0 {
+ // degenerate case: two empty lists are the same
+ return nil
+ }
+
+ // Store the endpoint of the path for diagonals.
+ // We store only the a index, because the b index on any diagonal
+ // (which we know during the loop below) is aidx-diag.
+ // endpoint[maxPath] represents the 0 diagonal.
+ //
+ // Stated differently:
+ // endpoint[d] contains the aidx of a furthest reaching path in diagonal d
+ endpoint := make([]int, 2*maxPath+1) // V
+
+ saved := make([][]int, 0, 8) // Vs
+ save := func() {
+ dup := make([]int, len(endpoint))
+ copy(dup, endpoint)
+ saved = append(saved, dup)
+ }
+
+ var editDistance int // D
+dLoop:
+ for editDistance = 0; editDistance <= maxPath; editDistance++ {
+ // The 0 diag(onal) represents equality of a and b. Each diagonal to
+ // the left is numbered one lower, to the right is one higher, from
+ // -alen to +blen. Negative diagonals favor differences from a,
+ // positive diagonals favor differences from b. The edit distance to a
+ // diagonal d cannot be shorter than d itself.
+ //
+ // The iterations of this loop cover either odds or evens, but not both,
+ // If odd indices are inputs, even indices are outputs and vice versa.
+ for diag := -editDistance; diag <= editDistance; diag += 2 { // k
+ var aidx int // x
+ switch {
+ case diag == -editDistance:
+ // This is a new diagonal; copy from previous iter
+ aidx = endpoint[maxPath-editDistance+1] + 0
+ case diag == editDistance:
+ // This is a new diagonal; copy from previous iter
+ aidx = endpoint[maxPath+editDistance-1] + 1
+ case endpoint[maxPath+diag+1] > endpoint[maxPath+diag-1]:
+ // diagonal d+1 was farther along, so use that
+ aidx = endpoint[maxPath+diag+1] + 0
+ default:
+ // diagonal d-1 was farther (or the same), so use that
+ aidx = endpoint[maxPath+diag-1] + 1
+ }
+ // On diagonal d, we can compute bidx from aidx.
+ bidx := aidx - diag // y
+ // See how far we can go on this diagonal before we find a difference.
+ for aidx < alen && bidx < blen && a[aidx] == b[bidx] {
+ aidx++
+ bidx++
+ }
+ // Store the end of the current edit chain.
+ endpoint[maxPath+diag] = aidx
+ // If we've found the end of both inputs, we're done!
+ if aidx >= alen && bidx >= blen {
+ save() // save the final path
+ break dLoop
+ }
+ }
+ save() // save the current path
+ }
+ if editDistance == 0 {
+ return nil
+ }
+ chunks := make([]Chunk, editDistance+1)
+
+ x, y := alen, blen
+ for d := editDistance; d > 0; d-- {
+ endpoint := saved[d]
+ diag := x - y
+ insert := diag == -d || (diag != d && endpoint[maxPath+diag-1] < endpoint[maxPath+diag+1])
+
+ x1 := endpoint[maxPath+diag]
+ var x0, xM, kk int
+ if insert {
+ kk = diag + 1
+ x0 = endpoint[maxPath+kk]
+ xM = x0
+ } else {
+ kk = diag - 1
+ x0 = endpoint[maxPath+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]
+ }
+ if chunks[0].empty() {
+ chunks = chunks[1:]
+ }
+ if len(chunks) == 0 {
+ return nil
+ }
+ 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 0000000..ebdd450
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/diff/diff_test.go
@@ -0,0 +1,228 @@
+// 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: "nil",
+ },
+ {
+ desc: "empty",
+ A: []string{},
+ B: []string{},
+ },
+ {
+ desc: "same",
+ A: []string{"foo"},
+ B: []string{"foo"},
+ },
+ {
+ desc: "a empty",
+ A: []string{},
+ },
+ {
+ desc: "b empty",
+ B: []string{},
+ },
+ {
+ desc: "b nil",
+ A: []string{"foo"},
+ chunks: []Chunk{
+ 0: {Deleted: []string{"foo"}},
+ },
+ },
+ {
+ desc: "a nil",
+ B: []string{"foo"},
+ chunks: []Chunk{
+ 0: {Added: []string{"foo"}},
+ },
+ },
+ {
+ desc: "start with change",
+ A: []string{"a", "b", "c"},
+ B: []string{"A", "b", "c"},
+ chunks: []Chunk{
+ 0: {Deleted: []string{"a"}},
+ 1: {Added: []string{"A"}, Equal: []string{"b", "c"}},
+ },
+ },
+ {
+ 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 {
+ t.Run(test.desc, func(t *testing.T) {
+ got := DiffChunks(test.A, test.B)
+ if got, want := len(got), len(test.chunks); got != want {
+ t.Errorf("edit distance = %v, want %v", got-1, want-1)
+ return
+ }
+ for i := range got {
+ got, want := got[i], test.chunks[i]
+ if got, want := got.Added, want.Added; !reflect.DeepEqual(got, want) {
+ t.Errorf("chunks[%d]: Added = %v, want %v", i, got, want)
+ }
+ if got, want := got.Deleted, want.Deleted; !reflect.DeepEqual(got, want) {
+ t.Errorf("chunks[%d]: Deleted = %v, want %v", i, got, want)
+ }
+ if got, want := got.Equal, want.Equal; !reflect.DeepEqual(got, want) {
+ t.Errorf("chunks[%d]: Equal = %v, want %v", i, got, want)
+ }
+ }
+ })
+ }
+}
+
+func TestRender(t *testing.T) {
+ tests := []struct {
+ desc string
+ chunks []Chunk
+ out string
+ }{
+ {
+ desc: "ordering",
+ chunks: []Chunk{
+ {
+ Added: []string{"1"},
+ Deleted: []string{"2"},
+ Equal: []string{"3"},
+ },
+ {
+ Added: []string{"4"},
+ Deleted: []string{"5"},
+ },
+ },
+ out: strings.TrimSpace(`
++1
+-2
+ 3
++4
+-5
+ `),
+ },
+ {
+ desc: "only_added",
+ chunks: []Chunk{
+ {
+ Added: []string{"1"},
+ },
+ },
+ out: strings.TrimSpace(`
++1
+ `),
+ },
+ {
+ desc: "only_deleted",
+ chunks: []Chunk{
+ {
+ Deleted: []string{"1"},
+ },
+ },
+ out: strings.TrimSpace(`
+-1
+ `),
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ if got, want := Render(test.chunks), test.out; got != want {
+ t.Errorf("Render(%q):", test.chunks)
+ t.Errorf("GOT\n%s", got)
+ t.Errorf("WANT\n%s", 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/go.mod b/vendor/github.com/kylelemons/godebug/go.mod
new file mode 100644
index 0000000..92e0120
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/go.mod
@@ -0,0 +1,3 @@
+module github.com/kylelemons/godebug
+
+go 1.11
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 0000000..c7b4356
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/doc.go
@@ -0,0 +1,22 @@
+// 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
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 0000000..8578b95
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/examples_test.go
@@ -0,0 +1,373 @@
+// 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"
+ "net"
+ "reflect"
+
+ "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 ExampleConfig_fmtFormatter() {
+ pretty.DefaultFormatter[reflect.TypeOf(&net.IPNet{})] = fmt.Sprint
+ pretty.DefaultFormatter[reflect.TypeOf(net.HardwareAddr{})] = fmt.Sprint
+ pretty.Print(&net.IPNet{
+ IP: net.IPv4(192, 168, 1, 100),
+ Mask: net.CIDRMask(24, 32),
+ })
+ pretty.Print(net.HardwareAddr{1, 2, 3, 4, 5, 6})
+
+ // Output:
+ // 192.168.1.100/24
+ // 01:02:03:04:05:06
+}
+
+func ExampleConfig_customFormatter() {
+ pretty.DefaultFormatter[reflect.TypeOf(&net.IPNet{})] = func(n *net.IPNet) string {
+ return fmt.Sprintf("CIDR=%s", n)
+ }
+ pretty.Print(&net.IPNet{
+ IP: net.IPv4(192, 168, 1, 100),
+ Mask: net.CIDRMask(24, 32),
+ })
+
+ // Output:
+ // CIDR=192.168.1.100/24
+}
+
+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,
+ // }
+}
+
+type ListNode struct {
+ Value int
+ Next *ListNode
+}
+
+func circular(nodes int) *ListNode {
+ final := &ListNode{
+ Value: nodes,
+ }
+ final.Next = final
+
+ recent := final
+ for i := nodes - 1; i > 0; i-- {
+ n := &ListNode{
+ Value: i,
+ Next: recent,
+ }
+ final.Next = n
+ recent = n
+ }
+ return recent
+}
+
+func ExamplePrint_withCycles() {
+ pretty.CycleTracker.Print(circular(3))
+
+ // Output:
+ // <#1> {
+ // Value: 1,
+ // Next: {
+ // Value: 2,
+ // Next: {
+ // Value: 3,
+ // Next: <see #1>,
+ // },
+ // },
+ // }
+}
+
+func ExampleCompare_withCycles() {
+ got, want := circular(3), circular(3)
+
+ // Make the got one broken
+ got.Next.Next.Next = got.Next
+
+ fmt.Printf("Diff: (-got +want)\n%s", pretty.CycleTracker.Compare(got, want))
+
+ // Output:
+ // Diff: (-got +want)
+ // -{
+ // +<#1> {
+ // Value: 1,
+ // - Next: <#1> {
+ // + Next: {
+ // Value: 2,
+ // Next: {
+ // Value: 3,
+ // Next: <see #1>,
+ // },
+ // },
+ // }
+}
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 0000000..fbc5d7a
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/public.go
@@ -0,0 +1,188 @@
+// 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"
+ "net"
+ "reflect"
+ "time"
+
+ "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.
+
+ // Type-specific overrides
+ //
+ // Formatter maps a type to a function that will provide a one-line string
+ // representation of the input value. Conceptually:
+ // Formatter[reflect.TypeOf(v)](v) = "v as a string"
+ //
+ // Note that the first argument need not explicitly match the type, it must
+ // merely be callable with it.
+ //
+ // When processing an input value, if its type exists as a key in Formatter:
+ // 1) If the value is nil, no stringification is performed.
+ // This allows overriding of PrintStringers and PrintTextMarshalers.
+ // 2) The value will be called with the input as its only argument.
+ // The function must return a string as its first return value.
+ //
+ // In addition to func literals, two common values for this will be:
+ // fmt.Sprint (function) func Sprint(...interface{}) string
+ // Type.String (method) func (Type) String() string
+ //
+ // Note that neither of these work if the String method is a pointer
+ // method and the input will be provided as a value. In that case,
+ // use a function that calls .String on the formal value parameter.
+ Formatter map[reflect.Type]interface{}
+
+ // If TrackCycles is enabled, pretty will detect and track
+ // self-referential structures. If a self-referential structure (aka a
+ // "recursive" value) is detected, numbered placeholders will be emitted.
+ //
+ // Pointer tracking is disabled by default for performance reasons.
+ TrackCycles bool
+}
+
+// Default Config objects
+var (
+ // DefaultFormatter is the default set of overrides for stringification.
+ DefaultFormatter = map[reflect.Type]interface{}{
+ reflect.TypeOf(time.Time{}): fmt.Sprint,
+ reflect.TypeOf(net.IP{}): fmt.Sprint,
+ reflect.TypeOf((*error)(nil)).Elem(): fmt.Sprint,
+ }
+
+ // CompareConfig is the default configuration used for Compare.
+ CompareConfig = &Config{
+ Diffable: true,
+ IncludeUnexported: true,
+ Formatter: DefaultFormatter,
+ }
+
+ // DefaultConfig is the default configuration used for all other top-level functions.
+ DefaultConfig = &Config{
+ Formatter: DefaultFormatter,
+ }
+
+ // CycleTracker is a convenience config for formatting and comparing recursive structures.
+ CycleTracker = &Config{
+ Diffable: true,
+ Formatter: DefaultFormatter,
+ TrackCycles: true,
+ }
+)
+
+func (cfg *Config) fprint(buf *bytes.Buffer, vals ...interface{}) {
+ ref := &reflector{
+ Config: cfg,
+ }
+ if cfg.TrackCycles {
+ ref.pointerTracker = new(pointerTracker)
+ }
+ for i, val := range vals {
+ if i > 0 {
+ buf.WriteByte('\n')
+ }
+ newFormatter(cfg, buf).write(ref.val2node(reflect.ValueOf(val)))
+ }
+}
+
+// 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 a and b, using the CompareConfig.
+//
+// Each line in the output is prefixed with '+', '-', or ' ' to indicate which
+// side it's from. Lines from the a side are marked with '-', lines from the
+// b side are marked with '+' and lines that are the same on both sides are
+// marked with ' '.
+//
+// The comparison is based on the intentionally-untyped output of Print, and as
+// such this comparison is pretty forviving. In particular, if the types of or
+// types within in a and b are different but have the same representation,
+// Compare will not indicate any differences between them.
+func Compare(a, b interface{}) string {
+ return CompareConfig.Compare(a, b)
+}
+
+// Compare returns a string containing a line-by-line unified diff of the
+// values in got and want according to the cfg.
+//
+// Each line in the output is prefixed with '+', '-', or ' ' to indicate which
+// side it's from. Lines from the a side are marked with '-', lines from the
+// b side are marked with '+' and lines that are the same on both sides are
+// marked with ' '.
+//
+// The comparison is based on the intentionally-untyped output of Print, and as
+// such this comparison is pretty forviving. In particular, if the types of or
+// types within in a and b are different but have the same representation,
+// Compare will not indicate any differences between them.
+func (cfg *Config) Compare(a, b interface{}) string {
+ diffCfg := *cfg
+ diffCfg.Diffable = true
+ return diff.Diff(cfg.Sprint(a), cfg.Sprint(b))
+}
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 0000000..b25a9ca
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/public_test.go
@@ -0,0 +1,155 @@
+// 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"
+ "time"
+)
+
+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)
+ }
+ }
+}
+
+func TestRegressions(t *testing.T) {
+ tests := []struct {
+ issue string
+ config *Config
+ value interface{}
+ want string
+ }{
+ {
+ issue: "kylelemons/godebug#13",
+ config: &Config{
+ PrintStringers: true,
+ },
+ value: struct{ Day *time.Weekday }{},
+ want: "{Day: nil}",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.issue, func(t *testing.T) {
+ if got, want := test.config.Sprint(test.value), test.want; got != want {
+ t.Errorf("%#v.Sprint(%#v) = %q, want %q", test.config, test.value, got, 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 0000000..214e622
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/reflect.go
@@ -0,0 +1,255 @@
+// 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)
+}
+
+// pointerTracker is a helper for tracking pointer chasing to detect cycles.
+type pointerTracker struct {
+ addrs map[uintptr]int // addr[address] = seen count
+
+ lastID int
+ ids map[uintptr]int // ids[address] = id
+}
+
+// track tracks following a reference (pointer, slice, map, etc). Every call to
+// track should be paired with a call to untrack.
+func (p *pointerTracker) track(ptr uintptr) {
+ if p.addrs == nil {
+ p.addrs = make(map[uintptr]int)
+ }
+ p.addrs[ptr]++
+}
+
+// untrack registers that we have backtracked over the reference to the pointer.
+func (p *pointerTracker) untrack(ptr uintptr) {
+ p.addrs[ptr]--
+ if p.addrs[ptr] == 0 {
+ delete(p.addrs, ptr)
+ }
+}
+
+// seen returns whether the pointer was previously seen along this path.
+func (p *pointerTracker) seen(ptr uintptr) bool {
+ _, ok := p.addrs[ptr]
+ return ok
+}
+
+// keep allocates an ID for the given address and returns it.
+func (p *pointerTracker) keep(ptr uintptr) int {
+ if p.ids == nil {
+ p.ids = make(map[uintptr]int)
+ }
+ if _, ok := p.ids[ptr]; !ok {
+ p.lastID++
+ p.ids[ptr] = p.lastID
+ }
+ return p.ids[ptr]
+}
+
+// id returns the ID for the given address.
+func (p *pointerTracker) id(ptr uintptr) (int, bool) {
+ if p.ids == nil {
+ p.ids = make(map[uintptr]int)
+ }
+ id, ok := p.ids[ptr]
+ return id, ok
+}
+
+// reflector adds local state to the recursive reflection logic.
+type reflector struct {
+ *Config
+ *pointerTracker
+}
+
+// follow handles following a possiblly-recursive reference to the given value
+// from the given ptr address.
+func (r *reflector) follow(ptr uintptr, val reflect.Value) node {
+ if r.pointerTracker == nil {
+ // Tracking disabled
+ return r.val2node(val)
+ }
+
+ // If a parent already followed this, emit a reference marker
+ if r.seen(ptr) {
+ id := r.keep(ptr)
+ return ref{id}
+ }
+
+ // Track the pointer we're following while on this recursive branch
+ r.track(ptr)
+ defer r.untrack(ptr)
+ n := r.val2node(val)
+
+ // If the recursion used this ptr, wrap it with a target marker
+ if id, ok := r.id(ptr); ok {
+ return target{id, n}
+ }
+
+ // Otherwise, return the node unadulterated
+ return n
+}
+
+func (r *reflector) val2node(val reflect.Value) (ret node) {
+ if !val.IsValid() {
+ return rawVal("nil")
+ }
+
+ if val.CanInterface() {
+ // Detect panics in calling functions on nil pointers.
+ //
+ // We still want to call them, as it's possible that a nil value is
+ // valid for the particular type.
+ //
+ // If we detect a panic, just return raw nil.
+ if val.Kind() == reflect.Ptr && val.IsNil() {
+ defer func() {
+ if r := recover(); r != nil {
+ ret = rawVal("nil")
+ }
+ }()
+ }
+
+ v := val.Interface()
+ if formatter, ok := r.Formatter[val.Type()]; ok {
+ if formatter != nil {
+ res := reflect.ValueOf(formatter).Call([]reflect.Value{val})
+ return rawVal(res[0].Interface().(string))
+ }
+ } else {
+ if s, ok := v.(fmt.Stringer); ok && r.PrintStringers {
+ return stringVal(s.String())
+ }
+ if t, ok := v.(encoding.TextMarshaler); ok && r.PrintTextMarshalers {
+ if raw, err := t.MarshalText(); err == nil { // if NOT an error
+ return stringVal(string(raw))
+ }
+ }
+ }
+ }
+
+ switch kind := val.Kind(); kind {
+ case reflect.Ptr:
+ if val.IsNil() {
+ return rawVal("nil")
+ }
+ return r.follow(val.Pointer(), val.Elem())
+ case reflect.Interface:
+ if val.IsNil() {
+ return rawVal("nil")
+ }
+ return r.val2node(val.Elem())
+ case reflect.String:
+ return stringVal(val.String())
+ case reflect.Slice:
+ n := list{}
+ length := val.Len()
+ ptr := val.Pointer()
+ for i := 0; i < length; i++ {
+ n = append(n, r.follow(ptr, val.Index(i)))
+ }
+ return n
+ case reflect.Array:
+ n := list{}
+ length := val.Len()
+ for i := 0; i < length; i++ {
+ n = append(n, r.val2node(val.Index(i)))
+ }
+ return n
+ case reflect.Map:
+ // Extract the keys and sort them for stable iteration
+ keys := val.MapKeys()
+ pairs := make([]mapPair, 0, len(keys))
+ for _, key := range keys {
+ pairs = append(pairs, mapPair{
+ key: new(formatter).compactString(r.val2node(key)), // can't be cyclic
+ value: val.MapIndex(key),
+ })
+ }
+ sort.Sort(byKey(pairs))
+
+ // Process the keys into the final representation
+ ptr, n := val.Pointer(), keyvals{}
+ for _, pair := range pairs {
+ n = append(n, keyval{
+ key: pair.key,
+ val: r.follow(ptr, pair.value),
+ })
+ }
+ return n
+ case reflect.Struct:
+ n := keyvals{}
+ typ := val.Type()
+ fields := typ.NumField()
+ for i := 0; i < fields; i++ {
+ sf := typ.Field(i)
+ if !r.IncludeUnexported && sf.PkgPath != "" {
+ continue
+ }
+ field := val.Field(i)
+ if r.SkipZeroFields && isZeroVal(field) {
+ continue
+ }
+ n = append(n, keyval{sf.Name, r.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())
+}
+
+type mapPair struct {
+ key string
+ value reflect.Value
+}
+
+type byKey []mapPair
+
+func (v byKey) Len() int { return len(v) }
+func (v byKey) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
+func (v byKey) Less(i, j int) bool { return v[i].key < v[j].key }
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 0000000..1a67d88
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/reflect_test.go
@@ -0,0 +1,456 @@
+// 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 (
+ "fmt"
+ "net"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestVal2nodeDefault(t *testing.T) {
+ err := fmt.Errorf("err")
+ var errNil error
+
+ 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"),
+ },
+ {
+ desc: "time.Time",
+ raw: time.Unix(1257894000, 0).UTC(),
+ want: rawVal("2009-11-10 23:00:00 +0000 UTC"),
+ },
+ {
+ desc: "net.IP",
+ raw: net.IPv4(127, 0, 0, 1),
+ want: rawVal("127.0.0.1"),
+ },
+ {
+ desc: "error",
+ raw: &err,
+ want: rawVal("err"),
+ },
+ {
+ desc: "nil error",
+ raw: &errNil,
+ want: rawVal("<nil>"),
+ },
+ }
+
+ for _, test := range tests {
+ ref := &reflector{
+ Config: DefaultConfig,
+ }
+ if got, want := ref.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 with 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", rawVal("2009-02-13 23:31:30 +0000 UTC")},
+ },
+ },
+ {
+ desc: "time with nil Formatter",
+ raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()},
+ cfg: &Config{
+ PrintStringers: true,
+ Formatter: map[reflect.Type]interface{}{
+ reflect.TypeOf(time.Time{}): nil,
+ },
+ },
+ want: keyvals{
+ {"Date", keyvals{}},
+ },
+ },
+ {
+ desc: "time with 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 with 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")},
+ },
+ },
+ {
+ desc: "nil with PrintStringers",
+ raw: struct{ Date *time.Time }{},
+ cfg: &Config{
+ PrintStringers: true,
+ },
+ want: keyvals{
+ {"Date", rawVal("nil")},
+ },
+ },
+ {
+ desc: "nilIsFine with PrintStringers",
+ raw: struct{ V *nilIsFine }{},
+ cfg: &Config{
+ PrintStringers: true,
+ },
+ want: keyvals{
+ {"V", stringVal("<nil is fine>")},
+ },
+ },
+ {
+ desc: "nilIsFine non-nil with PrintStringers",
+ raw: struct{ V *nilIsFine }{V: new(nilIsFine)},
+ cfg: &Config{
+ PrintStringers: true,
+ },
+ want: keyvals{
+ {"V", stringVal("<not nil is fine>")},
+ },
+ },
+ {
+ desc: "circular list",
+ raw: circular(3),
+ cfg: CycleTracker,
+ want: target{1, keyvals{
+ {"Value", rawVal("1")},
+ {"Next", keyvals{
+ {"Value", rawVal("2")},
+ {"Next", keyvals{
+ {"Value", rawVal("3")},
+ {"Next", ref{1}},
+ }},
+ }},
+ }},
+ },
+ {
+ desc: "self referential maps",
+ raw: selfRef(),
+ cfg: CycleTracker,
+ want: target{1, keyvals{
+ {"ID", rawVal("1")},
+ {"Child", keyvals{
+ {"2", target{2, keyvals{
+ {"ID", rawVal("2")},
+ {"Child", keyvals{
+ {"3", target{3, keyvals{
+ {"ID", rawVal("3")},
+ {"Child", keyvals{
+ {"1", ref{1}},
+ {"2", ref{2}},
+ {"3", ref{3}},
+ }},
+ }}},
+ }},
+ }}},
+ }},
+ }},
+ },
+ {
+ desc: "maps of cycles",
+ raw: map[string]*ListNode{
+ "1. one": circular(1),
+ "2. two": circular(2),
+ "3. three": circular(1),
+ },
+ cfg: CycleTracker,
+ want: keyvals{
+ {"1. one", target{1, keyvals{
+ {"Value", rawVal("1")},
+ {"Next", ref{1}},
+ }}},
+ {"2. two", target{2, keyvals{
+ {"Value", rawVal("1")},
+ {"Next", keyvals{
+ {"Value", rawVal("2")},
+ {"Next", ref{2}},
+ }},
+ }}},
+ {"3. three", target{3, keyvals{
+ {"Value", rawVal("1")},
+ {"Next", ref{3}},
+ }}},
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ ref := &reflector{
+ Config: test.cfg,
+ }
+ if test.cfg.TrackCycles {
+ ref.pointerTracker = new(pointerTracker)
+ }
+ if got, want := ref.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) {
+ t.Errorf(" got %#v", got)
+ t.Errorf("want %#v", want)
+ t.Errorf("Diff: (-got +want)\n%s", Compare(got, want))
+ }
+ })
+ }
+}
+
+type ListNode struct {
+ Value int
+ Next *ListNode
+}
+
+func circular(nodes int) *ListNode {
+ final := &ListNode{
+ Value: nodes,
+ }
+ final.Next = final
+
+ recent := final
+ for i := nodes - 1; i > 0; i-- {
+ n := &ListNode{
+ Value: i,
+ Next: recent,
+ }
+ final.Next = n
+ recent = n
+ }
+ return recent
+}
+
+type SelfReferential struct {
+ ID int
+ Child map[int]*SelfReferential
+}
+
+func selfRef() *SelfReferential {
+ sr1 := &SelfReferential{
+ ID: 1,
+ Child: make(map[int]*SelfReferential),
+ }
+ sr2 := &SelfReferential{
+ ID: 2,
+ Child: make(map[int]*SelfReferential),
+ }
+ sr3 := &SelfReferential{
+ ID: 3,
+ Child: make(map[int]*SelfReferential),
+ }
+
+ // Build a cycle
+ sr1.Child[2] = sr2
+ sr2.Child[3] = sr3
+ sr3.Child[1] = sr1
+
+ // Throw in some other stuff for funzies
+ sr3.Child[2] = sr2
+ sr3.Child[3] = sr3
+ return sr1
+}
+
+func BenchmarkVal2node(b *testing.B) {
+ benchmarks := []struct {
+ desc string
+ cfg *Config
+ raw interface{}
+ }{
+ {
+ desc: "struct",
+ cfg: DefaultConfig,
+ raw: struct{ Zaphod, Ford string }{"beeblebrox", "prefect"},
+ },
+ {
+ desc: "map",
+ cfg: DefaultConfig,
+ raw: map[[2]int]string{
+ [2]int{-1, 2}: "school",
+ [2]int{0, 0}: "origin",
+ [2]int{1, 3}: "home",
+ },
+ },
+ {
+ desc: "track/struct",
+ cfg: CycleTracker,
+ raw: struct{ Zaphod, Ford string }{"beeblebrox", "prefect"},
+ },
+ {
+ desc: "track/map",
+ cfg: CycleTracker,
+ raw: map[[2]int]string{
+ [2]int{-1, 2}: "school",
+ [2]int{0, 0}: "origin",
+ [2]int{1, 3}: "home",
+ },
+ },
+ {
+ desc: "circlist/small",
+ cfg: CycleTracker,
+ raw: circular(3),
+ },
+ {
+ desc: "circlist/med",
+ cfg: CycleTracker,
+ raw: circular(300),
+ },
+ {
+ desc: "circlist/large",
+ cfg: CycleTracker,
+ raw: circular(3000),
+ },
+ {
+ desc: "mapofcirc/small",
+ cfg: CycleTracker,
+ raw: map[string]*ListNode{
+ "1. one": circular(1),
+ "2. two": circular(2),
+ "3. three": circular(1),
+ },
+ },
+ {
+ desc: "selfrefmap/small",
+ cfg: CycleTracker,
+ raw: selfRef,
+ },
+ }
+
+ for _, bench := range benchmarks {
+ b.Run(bench.desc, func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ ref := &reflector{
+ Config: bench.cfg,
+ }
+ if bench.cfg.TrackCycles {
+ ref.pointerTracker = new(pointerTracker)
+ }
+ ref.val2node(reflect.ValueOf(bench.raw))
+ }
+ })
+ }
+}
+
+type nilIsFine struct{}
+
+func (n *nilIsFine) String() string {
+ if n == nil {
+ return "<nil is fine>"
+ }
+ return "<not nil is fine>"
+}
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 0000000..d876f60
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/structure.go
@@ -0,0 +1,223 @@
+// 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 (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+// a formatter stores stateful formatting information as well as being
+// an io.Writer for simplicity.
+type formatter struct {
+ *bufio.Writer
+ *Config
+
+ // Self-referential structure tracking
+ tagNumbers map[int]int // tagNumbers[id] = <#n>
+}
+
+// newFormatter creates a new buffered formatter. For the output to be written
+// to the given writer, this must be accompanied by a call to write (or Flush).
+func newFormatter(cfg *Config, w io.Writer) *formatter {
+ return &formatter{
+ Writer: bufio.NewWriter(w),
+ Config: cfg,
+ tagNumbers: make(map[int]int),
+ }
+}
+
+func (f *formatter) write(n node) {
+ defer f.Flush()
+ n.format(f, "")
+}
+
+func (f *formatter) tagFor(id int) int {
+ if tag, ok := f.tagNumbers[id]; ok {
+ return tag
+ }
+ if f.tagNumbers == nil {
+ return 0
+ }
+ tag := len(f.tagNumbers) + 1
+ f.tagNumbers[id] = tag
+ return tag
+}
+
+type node interface {
+ format(f *formatter, indent string)
+}
+
+func (f *formatter) compactString(n node) string {
+ switch k := n.(type) {
+ case stringVal:
+ return string(k)
+ case rawVal:
+ return string(k)
+ }
+
+ buf := new(bytes.Buffer)
+ f2 := newFormatter(&Config{Compact: true}, buf)
+ f2.tagNumbers = f.tagNumbers // reuse tagNumbers just in case
+ f2.write(n)
+ return buf.String()
+}
+
+type stringVal string
+
+func (str stringVal) format(f *formatter, indent string) {
+ f.WriteString(strconv.Quote(string(str)))
+}
+
+type rawVal string
+
+func (r rawVal) format(f *formatter, indent string) {
+ f.WriteString(string(r))
+}
+
+type keyval struct {
+ key string
+ val node
+}
+
+type keyvals []keyval
+
+func (l keyvals) format(f *formatter, indent string) {
+ f.WriteByte('{')
+
+ switch {
+ case f.Compact:
+ // All on one line:
+ for i, kv := range l {
+ if i > 0 {
+ f.WriteByte(',')
+ }
+ f.WriteString(kv.key)
+ f.WriteByte(':')
+ kv.val.format(f, indent)
+ }
+ case f.Diffable:
+ f.WriteByte('\n')
+ inner := indent + " "
+ // Each value gets its own line:
+ for _, kv := range l {
+ f.WriteString(inner)
+ f.WriteString(kv.key)
+ f.WriteString(": ")
+ kv.val.format(f, inner)
+ f.WriteString(",\n")
+ }
+ f.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 {
+ f.WriteString(",\n")
+ f.WriteString(alignKey)
+ }
+ f.WriteString(kv.key)
+ f.WriteString(": ")
+ f.WriteString(alignValue[len(kv.key):])
+ kv.val.format(f, inner)
+ }
+ }
+
+ f.WriteByte('}')
+}
+
+type list []node
+
+func (l list) format(f *formatter, indent string) {
+ if max := f.ShortList; max > 0 {
+ short := f.compactString(l)
+ if len(short) <= max {
+ f.WriteString(short)
+ return
+ }
+ }
+
+ f.WriteByte('[')
+
+ switch {
+ case f.Compact:
+ // All on one line:
+ for i, v := range l {
+ if i > 0 {
+ f.WriteByte(',')
+ }
+ v.format(f, indent)
+ }
+ case f.Diffable:
+ f.WriteByte('\n')
+ inner := indent + " "
+ // Each value gets its own line:
+ for _, v := range l {
+ f.WriteString(inner)
+ v.format(f, inner)
+ f.WriteString(",\n")
+ }
+ f.WriteString(indent)
+ default:
+ inner := indent + " "
+ // First and last line shared with bracket:
+ for i, v := range l {
+ if i > 0 {
+ f.WriteString(",\n")
+ f.WriteString(inner)
+ }
+ v.format(f, inner)
+ }
+ }
+
+ f.WriteByte(']')
+}
+
+type ref struct {
+ id int
+}
+
+func (r ref) format(f *formatter, indent string) {
+ fmt.Fprintf(f, "<see #%d>", f.tagFor(r.id))
+}
+
+type target struct {
+ id int
+ value node
+}
+
+func (t target) format(f *formatter, indent string) {
+ tag := fmt.Sprintf("<#%d> ", f.tagFor(t.id))
+ switch {
+ case f.Diffable, f.Compact:
+ // no indent changes
+ default:
+ indent += strings.Repeat(" ", len(tag))
+ }
+ f.WriteString(tag)
+ t.value.format(f, indent)
+}
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 0000000..7fb831c
--- /dev/null
+++ b/vendor/github.com/kylelemons/godebug/pretty/structure_test.go
@@ -0,0 +1,371 @@
+// 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 TestFormat(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",
+ },
+ },
+ {
+ },
+]`,
+ },
+ {
+ desc: "recursive",
+ node: target{1, keyvals{
+ {"Value", rawVal("1")},
+ {"Next", keyvals{
+ {"Value", rawVal("2")},
+ {"Next", keyvals{
+ {"Value", rawVal("3")},
+ {"Next", ref{1}},
+ }},
+ }},
+ }},
+ normal: `
+<#1> {Value: 1,
+ Next: {Value: 2,
+ Next: {Value: 3,
+ Next: <see #1>}}}`,
+ diffable: `
+<#1> {
+ Value: 1,
+ Next: {
+ Value: 2,
+ Next: {
+ Value: 3,
+ Next: <see #1>,
+ },
+ },
+}`,
+ },
+ {
+ desc: "print in order",
+ node: list{
+ target{2, keyvals{
+ {"Next", ref{1}},
+ }},
+ target{1, keyvals{
+ {"Next", ref{2}},
+ }},
+ },
+ normal: `
+[<#1> {Next: <see #2>},
+ <#2> {Next: <see #1>}]`,
+ diffable: `
+[
+ <#1> {
+ Next: <see #2>,
+ },
+ <#2> {
+ Next: <see #1>,
+ },
+]`,
+ },
+ }
+
+ normal := &Config{}
+ diffable := &Config{Diffable: true}
+ 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)
+ newFormatter(normal, buf).write(test.node)
+ 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()
+
+ newFormatter(diffable, buf).write(test.node)
+ 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 := new(formatter).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)
+ newFormatter(cfg, buf).write(test.node)
+ if got, want := buf.String(), test.want; got != want {
+ t.Errorf("%#v:\ngot:\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)
+ newFormatter(cfg, buf).write(benchNode)
+ b.SetBytes(int64(buf.Len()))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ newFormatter(cfg, buf).write(benchNode)
+ }
+}
+
+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}) }