github.com/solo-io/cue@v0.4.7/cue/format.go (about)

     1  // Copyright 2021 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cue
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"math/big"
    21  
    22  	"github.com/solo-io/cue/cue/ast"
    23  	"github.com/solo-io/cue/cue/format"
    24  	"github.com/solo-io/cue/internal/core/export"
    25  )
    26  
    27  // TODO:
    28  // * allow '-' to strip outer curly braces?
    29  //     -    simplify output; can be used in combination with other flags
    30  // * advertise:
    31  //     c    like v, but print comments
    32  //     a    like c, but print attributes and package-local hidden fields as well
    33  
    34  // Format prints a CUE value.
    35  //
    36  // WARNING:
    37  //     although we are narrowing down the semantics, the verbs and options
    38  //     are still subject to change. this API is experimental although it is
    39  //     likely getting close to the final design.
    40  //
    41  // It recognizes the following verbs:
    42  //
    43  //     v    print CUE value
    44  //
    45  // The verbs support the following flags:
    46  //     #    print as schema and include definitions.
    47  //          The result is printed as a self-contained file, instead of an the
    48  //          expression format.
    49  //     +    evaluate: resolve defaults and error on incomplete errors
    50  //
    51  // Indentation can be controlled as follows:
    52  //     width      indent the cue block by <width> tab stops (e.g. %2v)
    53  //     precision  convert tabs to <precision> spaces (e.g. %.2v), where
    54  //                a value of 0 means no indentation or newlines (TODO).
    55  //
    56  // If the value kind corresponds to one of the following Go types, the
    57  // usual Go formatting verbs for that type can be used:
    58  //
    59  //     Int:          b,d,o,O,q,x,X
    60  //     Float:        f,e,E,g,G
    61  //     String/Bytes: s,q,x,X
    62  //
    63  // The %v directive will be used if the type is not supported for that verb.
    64  //
    65  func (v Value) Format(state fmt.State, verb rune) {
    66  	if v.v == nil {
    67  		fmt.Fprint(state, "<nil>")
    68  		return
    69  	}
    70  
    71  	switch verb {
    72  	case 'a':
    73  		formatCUE(state, v, true, true)
    74  	case 'c':
    75  		formatCUE(state, v, true, false)
    76  	case 'v':
    77  		formatCUE(state, v, false, false)
    78  
    79  	case 'd', 'o', 'O', 'U':
    80  		var i big.Int
    81  		if _, err := v.Int(&i); err != nil {
    82  			formatCUE(state, v, false, false)
    83  			return
    84  		}
    85  		i.Format(state, verb)
    86  
    87  	case 'f', 'e', 'E', 'g', 'G':
    88  		d, err := v.Decimal()
    89  		if err != nil {
    90  			formatCUE(state, v, false, false)
    91  			return
    92  		}
    93  		d.Format(state, verb)
    94  
    95  	case 's', 'q':
    96  		// TODO: this drops other formatting directives
    97  		msg := "%s"
    98  		if verb == 'q' {
    99  			msg = "%q"
   100  		}
   101  
   102  		if b, err := v.Bytes(); err == nil {
   103  			fmt.Fprintf(state, msg, b)
   104  		} else {
   105  			s := fmt.Sprintf("%+v", v)
   106  			fmt.Fprintf(state, msg, s)
   107  		}
   108  
   109  	case 'x', 'X':
   110  		switch v.Kind() {
   111  		case StringKind, BytesKind:
   112  			b, _ := v.Bytes()
   113  			// TODO: this drops other formatting directives
   114  			msg := "%x"
   115  			if verb == 'X' {
   116  				msg = "%X"
   117  			}
   118  			fmt.Fprintf(state, msg, b)
   119  
   120  		case IntKind, NumberKind:
   121  			var i big.Int
   122  			_, _ = v.Int(&i)
   123  			i.Format(state, verb)
   124  
   125  		case FloatKind:
   126  			dec, _ := v.Decimal()
   127  			dec.Format(state, verb)
   128  
   129  		default:
   130  			formatCUE(state, v, false, false)
   131  		}
   132  
   133  	default:
   134  		formatCUE(state, v, false, false)
   135  	}
   136  }
   137  
   138  func formatCUE(state fmt.State, v Value, showDocs, showAll bool) {
   139  
   140  	pkgPath := v.instance().ID()
   141  
   142  	p := *export.Simplified
   143  
   144  	isDef := false
   145  	switch {
   146  	case state.Flag('#'):
   147  		isDef = true
   148  		p = export.Profile{
   149  			ShowOptional:    true,
   150  			ShowDefinitions: true,
   151  			ShowHidden:      true,
   152  		}
   153  
   154  	case state.Flag('+'):
   155  		p = *export.Final
   156  		fallthrough
   157  
   158  	default:
   159  		p.ShowHidden = showAll
   160  	}
   161  
   162  	p.ShowDocs = showDocs
   163  	p.ShowAttributes = showAll
   164  
   165  	var n ast.Node
   166  	if isDef {
   167  		n, _ = p.Def(v.idx, pkgPath, v.v)
   168  	} else {
   169  		n, _ = p.Value(v.idx, pkgPath, v.v)
   170  	}
   171  
   172  	formatExpr(state, n)
   173  }
   174  
   175  func formatExpr(state fmt.State, n ast.Node) {
   176  	opts := make([]format.Option, 0, 3)
   177  	if state.Flag('-') {
   178  		opts = append(opts, format.Simplify())
   179  	}
   180  	// TODO: handle verbs to allow formatting based on type:
   181  	if width, ok := state.Width(); ok {
   182  		opts = append(opts, format.IndentPrefix(width))
   183  	}
   184  	// TODO: consider this: should tabs or spaces be the default?
   185  	if tabwidth, ok := state.Precision(); ok {
   186  		// TODO: 0 means no newlines.
   187  		opts = append(opts,
   188  			format.UseSpaces(tabwidth),
   189  			format.TabIndent(false))
   190  	}
   191  	// TODO: consider this.
   192  	//  else if state.Flag(' ') {
   193  	// 	opts = append(opts,
   194  	// 		format.UseSpaces(4),
   195  	// 		format.TabIndent(false))
   196  	// }
   197  
   198  	b, _ := format.Node(n, opts...)
   199  	b = bytes.Trim(b, "\n\r")
   200  	_, _ = state.Write(b)
   201  }