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 <[email protected]>
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}) }

Reply via email to