github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/repl/format.go (about)

     1  package repl
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/internal/lang/marks"
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  // FormatValue formats a value in a way that resembles Terraform language syntax
    13  // and uses the type conversion functions where necessary to indicate exactly
    14  // what type it is given, so that equality test failures can be quickly
    15  // understood.
    16  func FormatValue(v cty.Value, indent int) string {
    17  	if !v.IsKnown() {
    18  		return "(known after apply)"
    19  	}
    20  	if v.HasMark(marks.Sensitive) {
    21  		return "(sensitive)"
    22  	}
    23  	if v.IsNull() {
    24  		ty := v.Type()
    25  		switch {
    26  		case ty == cty.DynamicPseudoType:
    27  			return "null"
    28  		case ty == cty.String:
    29  			return "tostring(null)"
    30  		case ty == cty.Number:
    31  			return "tonumber(null)"
    32  		case ty == cty.Bool:
    33  			return "tobool(null)"
    34  		case ty.IsListType():
    35  			return fmt.Sprintf("tolist(null) /* of %s */", ty.ElementType().FriendlyName())
    36  		case ty.IsSetType():
    37  			return fmt.Sprintf("toset(null) /* of %s */", ty.ElementType().FriendlyName())
    38  		case ty.IsMapType():
    39  			return fmt.Sprintf("tomap(null) /* of %s */", ty.ElementType().FriendlyName())
    40  		default:
    41  			return fmt.Sprintf("null /* %s */", ty.FriendlyName())
    42  		}
    43  	}
    44  
    45  	ty := v.Type()
    46  	switch {
    47  	case ty.IsPrimitiveType():
    48  		switch ty {
    49  		case cty.String:
    50  			if formatted, isMultiline := formatMultilineString(v, indent); isMultiline {
    51  				return formatted
    52  			}
    53  			return strconv.Quote(v.AsString())
    54  		case cty.Number:
    55  			bf := v.AsBigFloat()
    56  			return bf.Text('f', -1)
    57  		case cty.Bool:
    58  			if v.True() {
    59  				return "true"
    60  			} else {
    61  				return "false"
    62  			}
    63  		}
    64  	case ty.IsObjectType():
    65  		return formatMappingValue(v, indent)
    66  	case ty.IsTupleType():
    67  		return formatSequenceValue(v, indent)
    68  	case ty.IsListType():
    69  		return fmt.Sprintf("tolist(%s)", formatSequenceValue(v, indent))
    70  	case ty.IsSetType():
    71  		return fmt.Sprintf("toset(%s)", formatSequenceValue(v, indent))
    72  	case ty.IsMapType():
    73  		return fmt.Sprintf("tomap(%s)", formatMappingValue(v, indent))
    74  	}
    75  
    76  	// Should never get here because there are no other types
    77  	return fmt.Sprintf("%#v", v)
    78  }
    79  
    80  func formatMultilineString(v cty.Value, indent int) (string, bool) {
    81  	str := v.AsString()
    82  	lines := strings.Split(str, "\n")
    83  	if len(lines) < 2 {
    84  		return "", false
    85  	}
    86  
    87  	// If the value is indented, we use the indented form of heredoc for readability.
    88  	operator := "<<"
    89  	if indent > 0 {
    90  		operator = "<<-"
    91  	}
    92  
    93  	// Default delimiter is "End Of Text" by convention
    94  	delimiter := "EOT"
    95  
    96  OUTER:
    97  	for {
    98  		// Check if any of the lines are in conflict with the delimiter. The
    99  		// parser allows leading and trailing whitespace, so we must remove it
   100  		// before comparison.
   101  		for _, line := range lines {
   102  			// If the delimiter matches a line, extend it and start again
   103  			if strings.TrimSpace(line) == delimiter {
   104  				delimiter = delimiter + "_"
   105  				continue OUTER
   106  			}
   107  		}
   108  
   109  		// None of the lines match the delimiter, so we're ready
   110  		break
   111  	}
   112  
   113  	// Write the heredoc, with indentation as appropriate.
   114  	var buf strings.Builder
   115  
   116  	buf.WriteString(operator)
   117  	buf.WriteString(delimiter)
   118  	for _, line := range lines {
   119  		buf.WriteByte('\n')
   120  		buf.WriteString(strings.Repeat(" ", indent))
   121  		buf.WriteString(line)
   122  	}
   123  	buf.WriteByte('\n')
   124  	buf.WriteString(strings.Repeat(" ", indent))
   125  	buf.WriteString(delimiter)
   126  
   127  	return buf.String(), true
   128  }
   129  
   130  func formatMappingValue(v cty.Value, indent int) string {
   131  	var buf strings.Builder
   132  	count := 0
   133  	buf.WriteByte('{')
   134  	indent += 2
   135  	for it := v.ElementIterator(); it.Next(); {
   136  		count++
   137  		k, v := it.Element()
   138  		buf.WriteByte('\n')
   139  		buf.WriteString(strings.Repeat(" ", indent))
   140  		buf.WriteString(FormatValue(k, indent))
   141  		buf.WriteString(" = ")
   142  		buf.WriteString(FormatValue(v, indent))
   143  	}
   144  	indent -= 2
   145  	if count > 0 {
   146  		buf.WriteByte('\n')
   147  		buf.WriteString(strings.Repeat(" ", indent))
   148  	}
   149  	buf.WriteByte('}')
   150  	return buf.String()
   151  }
   152  
   153  func formatSequenceValue(v cty.Value, indent int) string {
   154  	var buf strings.Builder
   155  	count := 0
   156  	buf.WriteByte('[')
   157  	indent += 2
   158  	for it := v.ElementIterator(); it.Next(); {
   159  		count++
   160  		_, v := it.Element()
   161  		buf.WriteByte('\n')
   162  		buf.WriteString(strings.Repeat(" ", indent))
   163  		buf.WriteString(FormatValue(v, indent))
   164  		buf.WriteByte(',')
   165  	}
   166  	indent -= 2
   167  	if count > 0 {
   168  		buf.WriteByte('\n')
   169  		buf.WriteString(strings.Repeat(" ", indent))
   170  	}
   171  	buf.WriteByte(']')
   172  	return buf.String()
   173  }