blob: 707a7990efed6bbe9829c075179fde18be6e913d [file] [log] [blame]
// Copyright 2021 CUE Authors
//
// 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 cue
import (
"bytes"
"fmt"
"math/big"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/format"
"cuelang.org/go/internal/core/export"
)
// TODO:
// * allow '-' to strip outer curly braces?
// - simplify output; can be used in combination with other flags
// * advertise:
// c like v, but print comments
// a like c, but print attributes and package-local hidden fields as well
// Format prints a CUE value.
//
// WARNING:
// although we are narrowing down the semantics, the verbs and options
// are still subject to change. this API is experimental although it is
// likely getting close to the final design.
//
// It recognizes the following verbs:
//
// v print CUE value
//
// The verbs support the following flags:
// # print as schema and include definitions.
// The result is printed as a self-contained file, instead of an the
// expression format.
// + evaluate: resolve defaults and error on incomplete errors
//
// Indentation can be controlled as follows:
// width indent the cue block by <width> tab stops (e.g. %2v)
// precision convert tabs to <precision> spaces (e.g. %.2v), where
// a value of 0 means no indentation or newlines (TODO).
//
// If the value kind corresponds to one of the following Go types, the
// usual Go formatting verbs for that type can be used:
//
// Int: b,d,o,O,q,x,X
// Float: f,e,E,g,G
// String/Bytes: s,q,x,X
//
// The %v directive will be used if the type is not supported for that verb.
//
func (v Value) Format(state fmt.State, verb rune) {
if v.v == nil {
fmt.Fprint(state, "<nil>")
return
}
switch verb {
case 'a':
formatCUE(state, v, true, true)
case 'c':
formatCUE(state, v, true, false)
case 'v':
formatCUE(state, v, false, false)
case 'd', 'o', 'O', 'U':
var i big.Int
if _, err := v.Int(&i); err != nil {
formatCUE(state, v, false, false)
return
}
i.Format(state, verb)
case 'f', 'e', 'E', 'g', 'G':
d, err := v.Decimal()
if err != nil {
formatCUE(state, v, false, false)
return
}
d.Format(state, verb)
case 's', 'q':
// TODO: this drops other formatting directives
msg := "%s"
if verb == 'q' {
msg = "%q"
}
if b, err := v.Bytes(); err == nil {
fmt.Fprintf(state, msg, b)
} else {
s := fmt.Sprintf("%+v", v)
fmt.Fprintf(state, msg, s)
}
case 'x', 'X':
switch v.Kind() {
case StringKind, BytesKind:
b, _ := v.Bytes()
// TODO: this drops other formatting directives
msg := "%x"
if verb == 'X' {
msg = "%X"
}
fmt.Fprintf(state, msg, b)
case IntKind, NumberKind:
var i big.Int
_, _ = v.Int(&i)
i.Format(state, verb)
case FloatKind:
dec, _ := v.Decimal()
dec.Format(state, verb)
default:
formatCUE(state, v, false, false)
}
default:
formatCUE(state, v, false, false)
}
}
func formatCUE(state fmt.State, v Value, showDocs, showAll bool) {
pkgPath := v.instance().ID()
p := *export.Simplified
isDef := false
switch {
case state.Flag('#'):
isDef = true
p = export.Profile{
ShowOptional: true,
ShowDefinitions: true,
ShowHidden: true,
}
case state.Flag('+'):
p = *export.Final
fallthrough
default:
p.ShowHidden = showAll
}
p.ShowDocs = showDocs
p.ShowAttributes = showAll
var n ast.Node
if isDef {
n, _ = p.Def(v.idx, pkgPath, v.v)
} else {
n, _ = p.Value(v.idx, pkgPath, v.v)
}
formatExpr(state, n)
}
func formatExpr(state fmt.State, n ast.Node) {
opts := make([]format.Option, 0, 3)
if state.Flag('-') {
opts = append(opts, format.Simplify())
}
// TODO: handle verbs to allow formatting based on type:
if width, ok := state.Width(); ok {
opts = append(opts, format.IndentPrefix(width))
}
// TODO: consider this: should tabs or spaces be the default?
if tabwidth, ok := state.Precision(); ok {
// TODO: 0 means no newlines.
opts = append(opts,
format.UseSpaces(tabwidth),
format.TabIndent(false))
}
// TODO: consider this.
// else if state.Flag(' ') {
// opts = append(opts,
// format.UseSpaces(4),
// format.TabIndent(false))
// }
b, _ := format.Node(n, opts...)
b = bytes.Trim(b, "\n\r")
_, _ = state.Write(b)
}