summaryrefslogtreecommitdiff
path: root/vendor/github.com/google/yamlfmt/internal
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-15 16:37:08 -0600
committermo khan <mo@mokhan.ca>2025-07-17 16:30:22 -0600
commit45df4d0d9b577fecee798d672695fe24ff57fb1b (patch)
tree1b99bf645035b58e0d6db08c7a83521f41f7a75b /vendor/github.com/google/yamlfmt/internal
parentf94f79608393d4ab127db63cc41668445ef6b243 (diff)
feat: migrate from Cedar to SpiceDB authorization system
This is a major architectural change that replaces the Cedar policy-based authorization system with SpiceDB's relation-based authorization. Key changes: - Migrate from Rust to Go implementation - Replace Cedar policies with SpiceDB schema and relationships - Switch from envoy `ext_authz` with Cedar to SpiceDB permission checks - Update build system and dependencies for Go ecosystem - Maintain Envoy integration for external authorization This change enables more flexible permission modeling through SpiceDB's Google Zanzibar inspired relation-based system, supporting complex hierarchical permissions that were difficult to express in Cedar. Breaking change: Existing Cedar policies and Rust-based configuration will no longer work and need to be migrated to SpiceDB schema.
Diffstat (limited to 'vendor/github.com/google/yamlfmt/internal')
-rw-r--r--vendor/github.com/google/yamlfmt/internal/collections/errors.go34
-rw-r--r--vendor/github.com/google/yamlfmt/internal/collections/set.go71
-rw-r--r--vendor/github.com/google/yamlfmt/internal/collections/slice.go24
-rw-r--r--vendor/github.com/google/yamlfmt/internal/features/eof_newline.go39
-rw-r--r--vendor/github.com/google/yamlfmt/internal/features/trim_whitespace.go43
-rw-r--r--vendor/github.com/google/yamlfmt/internal/gitlab/codequality.go79
-rw-r--r--vendor/github.com/google/yamlfmt/internal/hotfix/retain_line_break.go104
-rw-r--r--vendor/github.com/google/yamlfmt/internal/hotfix/strip_directives.go101
-rw-r--r--vendor/github.com/google/yamlfmt/internal/logger/debug.go53
-rw-r--r--vendor/github.com/google/yamlfmt/internal/multilinediff/multilinediff.go130
10 files changed, 678 insertions, 0 deletions
diff --git a/vendor/github.com/google/yamlfmt/internal/collections/errors.go b/vendor/github.com/google/yamlfmt/internal/collections/errors.go
new file mode 100644
index 00000000..c800700c
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/collections/errors.go
@@ -0,0 +1,34 @@
+// Copyright 2024 Google LLC
+//
+// 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 collections
+
+import "errors"
+
+type Errors []error
+
+func (errs Errors) Combine() error {
+ errMessage := ""
+
+ for _, err := range errs {
+ if err != nil {
+ errMessage += err.Error() + "\n"
+ }
+ }
+
+ if len(errMessage) == 0 {
+ return nil
+ }
+ return errors.New(errMessage)
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/collections/set.go b/vendor/github.com/google/yamlfmt/internal/collections/set.go
new file mode 100644
index 00000000..97f70bc3
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/collections/set.go
@@ -0,0 +1,71 @@
+// Copyright 2024 Google LLC
+//
+// 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 collections
+
+type Set[T comparable] map[T]struct{}
+
+func (s Set[T]) Add(el ...T) {
+ for _, el := range el {
+ s[el] = struct{}{}
+ }
+}
+
+func (s Set[T]) Remove(el T) bool {
+ if !s.Contains(el) {
+ return false
+ }
+ delete(s, el)
+ return true
+}
+
+func (s Set[T]) Contains(el T) bool {
+ _, ok := s[el]
+ return ok
+}
+
+func (s Set[T]) ToSlice() []T {
+ sl := []T{}
+ for el := range s {
+ sl = append(sl, el)
+ }
+ return sl
+}
+
+func (s Set[T]) Clone() Set[T] {
+ newSet := Set[T]{}
+ for el := range s {
+ newSet.Add(el)
+ }
+ return newSet
+}
+
+func (s Set[T]) Equals(rhs Set[T]) bool {
+ if len(s) != len(rhs) {
+ return false
+ }
+ rhsClone := rhs.Clone()
+ for el := range s {
+ rhsClone.Remove(el)
+ }
+ return len(rhsClone) == 0
+}
+
+func SliceToSet[T comparable](sl []T) Set[T] {
+ set := Set[T]{}
+ for _, el := range sl {
+ set.Add(el)
+ }
+ return set
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/collections/slice.go b/vendor/github.com/google/yamlfmt/internal/collections/slice.go
new file mode 100644
index 00000000..b4a9f3b6
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/collections/slice.go
@@ -0,0 +1,24 @@
+// Copyright 2024 Google LLC
+//
+// 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 collections
+
+func SliceContains[T comparable](haystack []T, needle T) bool {
+ for _, e := range haystack {
+ if e == needle {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/features/eof_newline.go b/vendor/github.com/google/yamlfmt/internal/features/eof_newline.go
new file mode 100644
index 00000000..d77c3905
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/features/eof_newline.go
@@ -0,0 +1,39 @@
+// Copyright 2024 Google LLC
+//
+// 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 features
+
+import (
+ "context"
+
+ "github.com/google/yamlfmt"
+)
+
+func MakeFeatureEOFNewline(linebreakStr string) yamlfmt.Feature {
+ return yamlfmt.Feature{
+ Name: "EOF Newline",
+ AfterAction: eofNewlineFeature(linebreakStr),
+ }
+}
+
+func eofNewlineFeature(linebreakStr string) yamlfmt.FeatureFunc {
+ return func(_ context.Context, content []byte) (context.Context, []byte, error) {
+ // This check works in both linebreak modes.
+ if len(content) == 0 || content[len(content)-1] != '\n' {
+ linebreakBytes := []byte(linebreakStr)
+ content = append(content, linebreakBytes...)
+ }
+ return nil, content, nil
+ }
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/features/trim_whitespace.go b/vendor/github.com/google/yamlfmt/internal/features/trim_whitespace.go
new file mode 100644
index 00000000..7b5bd636
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/features/trim_whitespace.go
@@ -0,0 +1,43 @@
+// Copyright 2024 Google LLC
+//
+// 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 features
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "strings"
+
+ "github.com/google/yamlfmt"
+)
+
+func MakeFeatureTrimTrailingWhitespace(linebreakStr string) yamlfmt.Feature {
+ return yamlfmt.Feature{
+ Name: "Trim Trailing Whitespace",
+ BeforeAction: trimTrailingWhitespaceFeature(linebreakStr),
+ }
+}
+
+func trimTrailingWhitespaceFeature(linebreakStr string) yamlfmt.FeatureFunc {
+ return func(_ context.Context, content []byte) (context.Context, []byte, error) {
+ buf := bytes.NewBuffer(content)
+ s := bufio.NewScanner(buf)
+ newLines := []string{}
+ for s.Scan() {
+ newLines = append(newLines, strings.TrimRight(s.Text(), " "))
+ }
+ return nil, []byte(strings.Join(newLines, linebreakStr)), nil
+ }
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/gitlab/codequality.go b/vendor/github.com/google/yamlfmt/internal/gitlab/codequality.go
new file mode 100644
index 00000000..e03de2d6
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/gitlab/codequality.go
@@ -0,0 +1,79 @@
+// Copyright 2024 GitLab, Inc.
+//
+// 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 gitlab generates GitLab Code Quality reports.
+package gitlab
+
+import (
+ "crypto/sha256"
+ "fmt"
+
+ "github.com/google/yamlfmt"
+)
+
+// CodeQuality represents a single code quality finding.
+//
+// Documentation: https://docs.gitlab.com/ee/ci/testing/code_quality.html#code-quality-report-format
+type CodeQuality struct {
+ Description string `json:"description,omitempty"`
+ Name string `json:"check_name,omitempty"`
+ Fingerprint string `json:"fingerprint,omitempty"`
+ Severity Severity `json:"severity,omitempty"`
+ Location Location `json:"location,omitempty"`
+}
+
+// Location is the location of a Code Quality finding.
+type Location struct {
+ Path string `json:"path,omitempty"`
+}
+
+// NewCodeQuality creates a new CodeQuality object from a yamlfmt.FileDiff.
+//
+// If the file did not change, i.e. the diff is empty, an empty struct and false is returned.
+func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) {
+ if !diff.Diff.Changed() {
+ return CodeQuality{}, false
+ }
+
+ return CodeQuality{
+ Description: "Not formatted correctly, run yamlfmt to resolve.",
+ Name: "yamlfmt",
+ Fingerprint: fingerprint(diff),
+ Severity: Major,
+ Location: Location{
+ Path: diff.Path,
+ },
+ }, true
+}
+
+// fingerprint returns a 256-bit SHA256 hash of the original unformatted file.
+// This is used to uniquely identify a code quality finding.
+func fingerprint(diff yamlfmt.FileDiff) string {
+ hash := sha256.New()
+
+ fmt.Fprint(hash, diff.Diff.Original)
+
+ return fmt.Sprintf("%x", hash.Sum(nil)) //nolint:perfsprint
+}
+
+// Severity is the severity of a code quality finding.
+type Severity string
+
+const (
+ Info Severity = "info"
+ Minor Severity = "minor"
+ Major Severity = "major"
+ Critical Severity = "critical"
+ Blocker Severity = "blocker"
+)
diff --git a/vendor/github.com/google/yamlfmt/internal/hotfix/retain_line_break.go b/vendor/github.com/google/yamlfmt/internal/hotfix/retain_line_break.go
new file mode 100644
index 00000000..a7a139e6
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/hotfix/retain_line_break.go
@@ -0,0 +1,104 @@
+// Copyright 2022 Google LLC
+//
+// 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.
+
+// The features in this file are to retain line breaks.
+// The basic idea is to insert/remove placeholder comments in the yaml document before and after the format process.
+
+package hotfix
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "strings"
+
+ "github.com/google/yamlfmt"
+)
+
+const lineBreakPlaceholder = "#magic___^_^___line"
+
+type paddinger struct {
+ strings.Builder
+}
+
+func (p *paddinger) adjust(txt string) {
+ var indentSize int
+ for i := 0; i < len(txt) && txt[i] == ' '; i++ { // yaml only allows space to indent.
+ indentSize++
+ }
+ // Grows if the given size is larger than us and always return the max padding.
+ for diff := indentSize - p.Len(); diff > 0; diff-- {
+ p.WriteByte(' ')
+ }
+}
+
+func MakeFeatureRetainLineBreak(linebreakStr string, chomp bool) yamlfmt.Feature {
+ return yamlfmt.Feature{
+ Name: "Retain Line Breaks",
+ BeforeAction: replaceLineBreakFeature(linebreakStr, chomp),
+ AfterAction: restoreLineBreakFeature(linebreakStr),
+ }
+}
+
+func replaceLineBreakFeature(newlineStr string, chomp bool) yamlfmt.FeatureFunc {
+ return func(_ context.Context, content []byte) (context.Context, []byte, error) {
+ var buf bytes.Buffer
+ reader := bytes.NewReader(content)
+ scanner := bufio.NewScanner(reader)
+ var inLineBreaks bool
+ var padding paddinger
+ for scanner.Scan() {
+ txt := scanner.Text()
+ padding.adjust(txt)
+ if strings.TrimSpace(txt) == "" { // line break or empty space line.
+ if chomp && inLineBreaks {
+ continue
+ }
+ buf.WriteString(padding.String()) // prepend some padding incase literal multiline strings.
+ buf.WriteString(lineBreakPlaceholder)
+ buf.WriteString(newlineStr)
+ inLineBreaks = true
+ } else {
+ buf.WriteString(txt)
+ buf.WriteString(newlineStr)
+ inLineBreaks = false
+ }
+ }
+ return nil, buf.Bytes(), scanner.Err()
+ }
+}
+
+func restoreLineBreakFeature(newlineStr string) yamlfmt.FeatureFunc {
+ return func(_ context.Context, content []byte) (context.Context, []byte, error) {
+ var buf bytes.Buffer
+ reader := bytes.NewReader(content)
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ txt := scanner.Text()
+ if strings.TrimSpace(txt) == "" {
+ // The basic yaml lib inserts newline when there is a comment(either placeholder or by user)
+ // followed by optional line breaks and a `---` multi-documents.
+ // To fix it, the empty line could only be inserted by us.
+ continue
+ }
+ if strings.HasPrefix(strings.TrimLeft(txt, " "), lineBreakPlaceholder) {
+ buf.WriteString(newlineStr)
+ continue
+ }
+ buf.WriteString(txt)
+ buf.WriteString(newlineStr)
+ }
+ return nil, buf.Bytes(), scanner.Err()
+ }
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/hotfix/strip_directives.go b/vendor/github.com/google/yamlfmt/internal/hotfix/strip_directives.go
new file mode 100644
index 00000000..63e1c6e6
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/hotfix/strip_directives.go
@@ -0,0 +1,101 @@
+// Copyright 2024 Google LLC
+//
+// 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 hotfix
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "strings"
+
+ "github.com/google/yamlfmt"
+)
+
+type directiveKey string
+
+var contextDirectivesKey directiveKey = "directives"
+
+type Directive struct {
+ line int
+ content string
+}
+
+func ContextWithDirectives(ctx context.Context, directives []Directive) context.Context {
+ return context.WithValue(ctx, contextDirectivesKey, directives)
+}
+
+func DirectivesFromContext(ctx context.Context) []Directive {
+ return ctx.Value(contextDirectivesKey).([]Directive)
+}
+
+func MakeFeatureStripDirectives(lineSepChar string) yamlfmt.Feature {
+ return yamlfmt.Feature{
+ Name: "Strip Directives",
+ BeforeAction: stripDirectivesFeature(lineSepChar),
+ AfterAction: restoreDirectivesFeature(lineSepChar),
+ }
+}
+
+func stripDirectivesFeature(lineSepChar string) yamlfmt.FeatureFunc {
+ return func(ctx context.Context, content []byte) (context.Context, []byte, error) {
+ directives := []Directive{}
+ reader := bytes.NewReader(content)
+ scanner := bufio.NewScanner(reader)
+ result := ""
+ currLine := 1
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.HasPrefix(line, "%") {
+ directives = append(directives, Directive{
+ line: currLine,
+ content: line,
+ })
+ } else {
+ result += line + lineSepChar
+ }
+ currLine++
+ }
+ return ContextWithDirectives(ctx, directives), []byte(result), nil
+ }
+}
+
+func restoreDirectivesFeature(lineSepChar string) yamlfmt.FeatureFunc {
+ return func(ctx context.Context, content []byte) (context.Context, []byte, error) {
+ directives := DirectivesFromContext(ctx)
+ directiveIdx := 0
+ doneDirectives := directiveIdx == len(directives)
+ reader := bytes.NewReader(content)
+ scanner := bufio.NewScanner(reader)
+ result := ""
+ currLine := 1
+ for scanner.Scan() {
+ if !doneDirectives && currLine == directives[directiveIdx].line {
+ result += directives[directiveIdx].content + lineSepChar
+ currLine++
+ directiveIdx++
+ doneDirectives = directiveIdx == len(directives)
+ }
+ result += scanner.Text() + lineSepChar
+ currLine++
+ }
+ // Edge case: There technically can be a directive as the final line. This would be
+ // useless as far as I can tell so maybe yamlfmt should just remove it anyway LOL but
+ // no we'll keep it.
+ if !doneDirectives && currLine == directives[directiveIdx].line {
+ result += directives[directiveIdx].content + lineSepChar
+ }
+ return ctx, []byte(result), nil
+ }
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/logger/debug.go b/vendor/github.com/google/yamlfmt/internal/logger/debug.go
new file mode 100644
index 00000000..5a82c3c1
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/logger/debug.go
@@ -0,0 +1,53 @@
+// Copyright 2024 Google LLC
+//
+// 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 logger
+
+import (
+ "fmt"
+
+ "github.com/google/yamlfmt/internal/collections"
+)
+
+type DebugCode int
+
+const (
+ DebugCodeAny DebugCode = iota
+ DebugCodeConfig
+ DebugCodePaths
+ DebugCodeDiffs
+)
+
+var (
+ supportedDebugCodes = map[string][]DebugCode{
+ "config": {DebugCodeConfig},
+ "paths": {DebugCodePaths},
+ "diffs": {DebugCodeDiffs},
+ "all": {DebugCodeConfig, DebugCodePaths, DebugCodeDiffs},
+ }
+ activeDebugCodes = collections.Set[DebugCode]{}
+)
+
+func ActivateDebugCode(code string) {
+ if debugCodes, ok := supportedDebugCodes[code]; ok {
+ activeDebugCodes.Add(debugCodes...)
+ }
+}
+
+// Debug prints a message if the given debug code is active.
+func Debug(code DebugCode, msg string, args ...any) {
+ if activeDebugCodes.Contains(code) {
+ fmt.Printf("[DEBUG]: %s\n", fmt.Sprintf(msg, args...))
+ }
+}
diff --git a/vendor/github.com/google/yamlfmt/internal/multilinediff/multilinediff.go b/vendor/github.com/google/yamlfmt/internal/multilinediff/multilinediff.go
new file mode 100644
index 00000000..dea8cc6d
--- /dev/null
+++ b/vendor/github.com/google/yamlfmt/internal/multilinediff/multilinediff.go
@@ -0,0 +1,130 @@
+// Copyright 2024 Google LLC
+//
+// 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 multilinediff
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+)
+
+// Get the diff between two strings.
+func Diff(a, b, lineSep string) (string, int) {
+ reporter := Reporter{LineSep: lineSep}
+ cmp.Diff(
+ a, b,
+ cmpopts.AcyclicTransformer("multiline", func(s string) []string {
+ return strings.Split(s, lineSep)
+ }),
+ cmp.Reporter(&reporter),
+ )
+ return reporter.String(), reporter.DiffCount
+}
+
+type diffType int
+
+const (
+ diffTypeEqual diffType = iota
+ diffTypeChange
+ diffTypeAdd
+)
+
+type diffLine struct {
+ diff diffType
+ old string
+ new string
+}
+
+func (l diffLine) toLine(length int) string {
+ line := ""
+
+ switch l.diff {
+ case diffTypeChange:
+ line += "- "
+ case diffTypeAdd:
+ line += "+ "
+ default:
+ line += " "
+ }
+
+ line += l.old
+
+ for i := 0; i < length-len(l.old); i++ {
+ line += " "
+ }
+
+ line += " "
+
+ line += l.new
+
+ return line
+}
+
+// A pretty reporter to pass into cmp.Diff using the cmd.Reporter function.
+type Reporter struct {
+ LineSep string
+ DiffCount int
+
+ path cmp.Path
+ lines []diffLine
+}
+
+func (r *Reporter) PushStep(ps cmp.PathStep) {
+ r.path = append(r.path, ps)
+}
+
+func (r *Reporter) Report(rs cmp.Result) {
+ line := diffLine{}
+ vOld, vNew := r.path.Last().Values()
+ if !rs.Equal() {
+ r.DiffCount++
+ if vOld.IsValid() {
+ line.diff = diffTypeChange
+ line.old = fmt.Sprintf("%+v", vOld)
+ }
+ if vNew.IsValid() {
+ if line.diff == diffTypeEqual {
+ line.diff = diffTypeAdd
+ }
+ line.new = fmt.Sprintf("%+v", vNew)
+ }
+ } else {
+ line.old = fmt.Sprintf("%+v", vOld)
+ line.new = fmt.Sprintf("%+v", vOld)
+ }
+ r.lines = append(r.lines, line)
+}
+
+func (r *Reporter) PopStep() {
+ r.path = r.path[:len(r.path)-1]
+}
+
+func (r *Reporter) String() string {
+ maxLen := 0
+ for _, l := range r.lines {
+ if len(l.old) > maxLen {
+ maxLen = len(l.old)
+ }
+ }
+
+ diffLines := []string{}
+ for _, l := range r.lines {
+ diffLines = append(diffLines, l.toLine(maxLen))
+ }
+
+ return strings.Join(diffLines, r.LineSep)
+}