github.com/oam-dev/kubevela@v1.9.11/pkg/appfile/dryrun/report.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package dryrun
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"math"
    23  
    24  	"github.com/aryann/difflib"
    25  	"github.com/fatih/color"
    26  )
    27  
    28  var (
    29  	red    = color.New(color.FgRed)
    30  	green  = color.New(color.FgGreen)
    31  	yellow = color.New(color.FgYellow)
    32  	white  = color.New(color.FgWhite)
    33  )
    34  
    35  // NewReportDiffOption creats a new ReportDiffOption that can formats and prints
    36  // diff report into an io.Writer
    37  func NewReportDiffOption(ctx int, to io.Writer) *ReportDiffOption {
    38  	return &ReportDiffOption{
    39  		DiffMsgs: map[DiffType]string{
    40  			AddDiff:    "has been added(+)",
    41  			ModifyDiff: "has been modified(*)",
    42  			RemoveDiff: "has been removed(-)",
    43  			NoDiff:     "has no change",
    44  		},
    45  		Context: ctx,
    46  		To:      to,
    47  	}
    48  }
    49  
    50  // ReportDiffOption contains options to formats and prints diff report
    51  type ReportDiffOption struct {
    52  	DiffMsgs map[DiffType]string
    53  	Context  int
    54  	To       io.Writer
    55  }
    56  
    57  // PrintDiffReport formats and prints diff data into target io.Writer
    58  func (r *ReportDiffOption) PrintDiffReport(diff *DiffEntry) {
    59  	r.printDiffReport(diff, "")
    60  }
    61  
    62  func (r *ReportDiffOption) printDiffReport(diff *DiffEntry, prefix string) {
    63  	var header string
    64  	switch diff.Kind {
    65  	case AppKind:
    66  		header = "Application"
    67  	case AppConfigCompKind:
    68  	case RawCompKind:
    69  		header = "Component"
    70  	case TraitKind:
    71  		header = "Trait"
    72  	case PolicyKind:
    73  		header = "External Policy"
    74  	case WorkflowKind:
    75  		header = "External Workflow"
    76  	case ReferredObject:
    77  		header = "Referred Object"
    78  	default:
    79  		return
    80  	}
    81  	if diff.Kind != AppConfigCompKind {
    82  		editMsg := r.DiffMsgs[diff.DiffType]
    83  		if diff.DiffType != NoDiff {
    84  			_, _ = yellow.Fprintf(r.To, "* %s%s (%s) %s\n", prefix, header, diff.Name, editMsg)
    85  			printDiffs(diff.Diffs, r.Context, r.To)
    86  		} else {
    87  			_, _ = white.Fprintf(r.To, "* %s%s (%s) %s\n", prefix, header, diff.Name, editMsg)
    88  		}
    89  	}
    90  	for _, sub := range diff.Subs {
    91  		var subPrefix string
    92  		if sub.Kind == TraitKind && diff.Kind == AppConfigCompKind {
    93  			subPrefix = fmt.Sprintf("Component (%s) / ", diff.Name)
    94  		}
    95  		r.printDiffReport(sub, subPrefix)
    96  	}
    97  }
    98  
    99  func printDiffs(diffs []difflib.DiffRecord, context int, to io.Writer) {
   100  	if context > 0 {
   101  		ctx := calculateContext(diffs)
   102  		skip := false
   103  		for i, diff := range diffs {
   104  			if ctx[i] <= context {
   105  				// only print the line whose distance to a closest diff is less
   106  				// than context
   107  				printDiffRecord(to, diff)
   108  				skip = false
   109  			} else if !skip {
   110  				fmt.Fprint(to, "...\n")
   111  				// skip print if next line is still omitted
   112  				skip = true
   113  			}
   114  
   115  		}
   116  	} else {
   117  		for _, diff := range diffs {
   118  			printDiffRecord(to, diff)
   119  		}
   120  	}
   121  }
   122  
   123  // calculateContext calculate the min distance from each line to its closest diff
   124  func calculateContext(diffs []difflib.DiffRecord) map[int]int {
   125  	ctx := map[int]int{}
   126  	// retrieve forward to calculate the min distance from each line to a
   127  	// changed line behind it
   128  	changeLineNum := -1
   129  	for i, diff := range diffs {
   130  		if diff.Delta != difflib.Common {
   131  			changeLineNum = i
   132  		}
   133  		distance := math.MaxInt32
   134  		if changeLineNum != -1 {
   135  			distance = i - changeLineNum
   136  		}
   137  		ctx[i] = distance
   138  	}
   139  	// retrieve backward to calculate the min distance from each line to a
   140  	// changed line before it
   141  	changeLineNum = -1
   142  	for i := len(diffs) - 1; i >= 0; i-- {
   143  		if diffs[i].Delta != difflib.Common {
   144  			changeLineNum = i
   145  		}
   146  		if changeLineNum != -1 {
   147  			distance := changeLineNum - i
   148  			if distance < ctx[i] {
   149  				ctx[i] = distance
   150  			}
   151  		}
   152  	}
   153  	return ctx
   154  }
   155  
   156  func printDiffRecord(to io.Writer, diff difflib.DiffRecord) {
   157  	data := diff.Payload
   158  	switch diff.Delta {
   159  	case difflib.RightOnly:
   160  		_, _ = green.Fprintf(to, "+ %s\n", data)
   161  	case difflib.LeftOnly:
   162  		_, _ = red.Fprintf(to, "- %s\n", data)
   163  	case difflib.Common:
   164  		_, _ = fmt.Fprintf(to, "  %s\n", data)
   165  	}
   166  }