mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-07 22:35:37 +00:00
Reference: https://spec.json5.org/ Co-authored-by: Codex <codex@openai.com> Generated-with: OpenAI Codex CLI (partial) Signed-off-by: Robin H. Johnson <rjohnson@coreweave.com>
348 lines
8.1 KiB
Go
348 lines
8.1 KiB
Go
//go:build !yq_nojson5
|
|
|
|
package yqlib
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/goccy/go-json"
|
|
)
|
|
|
|
type json5Encoder struct {
|
|
prefs JsonPreferences
|
|
indentString string
|
|
}
|
|
|
|
func NewJSON5Encoder(prefs JsonPreferences) Encoder {
|
|
indentString := ""
|
|
for i := 0; i < prefs.Indent; i++ {
|
|
indentString += " "
|
|
}
|
|
return &json5Encoder{prefs: prefs, indentString: indentString}
|
|
}
|
|
|
|
func (je *json5Encoder) CanHandleAliases() bool {
|
|
return false
|
|
}
|
|
|
|
func (je *json5Encoder) PrintDocumentSeparator(_ io.Writer) error {
|
|
return nil
|
|
}
|
|
|
|
func (je *json5Encoder) PrintLeadingContent(_ io.Writer, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
func (je *json5Encoder) Encode(writer io.Writer, node *CandidateNode) error {
|
|
if node.Kind == ScalarNode && je.prefs.UnwrapScalar {
|
|
return writeString(writer, node.Value+"\n")
|
|
}
|
|
|
|
destination := writer
|
|
tempBuffer := bytes.NewBuffer(nil)
|
|
if je.prefs.ColorsEnabled {
|
|
destination = tempBuffer
|
|
}
|
|
|
|
if err := writeJSON5CommentBlock(destination, node.HeadComment, je.indentString, 0); err != nil {
|
|
return err
|
|
}
|
|
if err := encodeJSON5Node(destination, node, je.indentString, 0); err != nil {
|
|
return err
|
|
}
|
|
if err := writeJSON5InlineComment(destination, node.LineComment, true); err != nil {
|
|
return err
|
|
}
|
|
if err := writeString(destination, "\n"); err != nil {
|
|
return err
|
|
}
|
|
if err := writeJSON5CommentBlock(destination, node.FootComment, je.indentString, 0); err != nil {
|
|
return err
|
|
}
|
|
|
|
if je.prefs.ColorsEnabled {
|
|
return colorizeAndPrint(tempBuffer.Bytes(), writer)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func encodeJSON5Node(writer io.Writer, node *CandidateNode, indentString string, depth int) error {
|
|
if node == nil {
|
|
return writeString(writer, "null")
|
|
}
|
|
|
|
switch node.Kind {
|
|
case AliasNode:
|
|
return encodeJSON5Node(writer, node.Alias, indentString, depth)
|
|
case ScalarNode:
|
|
return writeString(writer, json5ScalarString(node))
|
|
case SequenceNode:
|
|
return encodeJSON5Sequence(writer, node, indentString, depth)
|
|
case MappingNode:
|
|
return encodeJSON5Mapping(writer, node, indentString, depth)
|
|
default:
|
|
return writeString(writer, "null")
|
|
}
|
|
}
|
|
|
|
func encodeJSON5Sequence(writer io.Writer, node *CandidateNode, indentString string, depth int) error {
|
|
if err := writeString(writer, "["); err != nil {
|
|
return err
|
|
}
|
|
if len(node.Content) == 0 {
|
|
return writeString(writer, "]")
|
|
}
|
|
|
|
pretty := indentString != ""
|
|
if pretty {
|
|
if err := writeString(writer, "\n"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i, child := range node.Content {
|
|
if pretty {
|
|
if err := writeJSON5CommentBlock(writer, child.HeadComment, indentString, depth+1); err != nil {
|
|
return err
|
|
}
|
|
if err := writeString(writer, strings.Repeat(indentString, depth+1)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := encodeJSON5Node(writer, child, indentString, depth+1); err != nil {
|
|
return err
|
|
}
|
|
if err := writeJSON5InlineComment(writer, child.LineComment, true); err != nil {
|
|
return err
|
|
}
|
|
if i != len(node.Content)-1 {
|
|
if err := writeString(writer, ","); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if pretty {
|
|
if err := writeString(writer, "\n"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if pretty {
|
|
if err := writeString(writer, strings.Repeat(indentString, depth)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return writeString(writer, "]")
|
|
}
|
|
|
|
func encodeJSON5Mapping(writer io.Writer, node *CandidateNode, indentString string, depth int) error {
|
|
if err := writeString(writer, "{"); err != nil {
|
|
return err
|
|
}
|
|
if len(node.Content) == 0 {
|
|
return writeString(writer, "}")
|
|
}
|
|
|
|
pretty := indentString != ""
|
|
if pretty {
|
|
if err := writeString(writer, "\n"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := 0; i < len(node.Content); i += 2 {
|
|
keyNode := node.Content[i]
|
|
valueNode := node.Content[i+1]
|
|
|
|
keyBytes, err := json.Marshal(keyNode.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if pretty {
|
|
if err := writeJSON5CommentBlock(writer, keyNode.HeadComment, indentString, depth+1); err != nil {
|
|
return err
|
|
}
|
|
if err := writeString(writer, strings.Repeat(indentString, depth+1)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := writeString(writer, string(keyBytes)); err != nil {
|
|
return err
|
|
}
|
|
if err := writeJSON5InlineComment(writer, keyNode.LineComment, true); err != nil {
|
|
return err
|
|
}
|
|
if err := writeString(writer, ":"); err != nil {
|
|
return err
|
|
}
|
|
if pretty && strings.TrimSpace(valueNode.HeadComment) != "" && strings.Contains(valueNode.HeadComment, "\n") {
|
|
if err := writeString(writer, "\n"); err != nil {
|
|
return err
|
|
}
|
|
if err := writeJSON5CommentBlock(writer, valueNode.HeadComment, indentString, depth+1); err != nil {
|
|
return err
|
|
}
|
|
if err := writeString(writer, strings.Repeat(indentString, depth+1)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if pretty {
|
|
if err := writeString(writer, " "); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := writeJSON5InlineComment(writer, valueNode.HeadComment, false); err != nil {
|
|
return err
|
|
}
|
|
if strings.TrimSpace(valueNode.HeadComment) != "" {
|
|
if err := writeString(writer, " "); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := encodeJSON5Node(writer, valueNode, indentString, depth+1); err != nil {
|
|
return err
|
|
}
|
|
if err := writeJSON5InlineComment(writer, valueNode.LineComment, true); err != nil {
|
|
return err
|
|
}
|
|
if i != len(node.Content)-2 {
|
|
if err := writeString(writer, ","); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if pretty {
|
|
if err := writeString(writer, "\n"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if pretty {
|
|
if err := writeString(writer, strings.Repeat(indentString, depth)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return writeString(writer, "}")
|
|
}
|
|
|
|
func json5ScalarString(node *CandidateNode) string {
|
|
tag := node.guessTagFromCustomType()
|
|
|
|
switch tag {
|
|
case "!!null":
|
|
return "null"
|
|
case "!!bool":
|
|
if isTruthyNode(node) {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
case "!!int":
|
|
value, err := node.GetValueRep()
|
|
if err != nil {
|
|
return "null"
|
|
}
|
|
intBytes, err := json.Marshal(value)
|
|
if err != nil {
|
|
return "null"
|
|
}
|
|
return string(intBytes)
|
|
case "!!float":
|
|
return json5FloatString(node.Value)
|
|
case "!!str":
|
|
stringBytes, err := json.Marshal(node.Value)
|
|
if err != nil {
|
|
return "null"
|
|
}
|
|
return string(stringBytes)
|
|
default:
|
|
stringBytes, err := json.Marshal(node.Value)
|
|
if err != nil {
|
|
return "null"
|
|
}
|
|
return string(stringBytes)
|
|
}
|
|
}
|
|
|
|
func json5FloatString(value string) string {
|
|
switch strings.ToLower(value) {
|
|
case ".inf", "+.inf", "inf", "+inf", "+infinity", "infinity":
|
|
return "Infinity"
|
|
case "-.inf", "-inf", "-infinity":
|
|
return "-Infinity"
|
|
case ".nan", "nan":
|
|
return "NaN"
|
|
}
|
|
|
|
parsed, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
return "null"
|
|
}
|
|
|
|
if math.IsNaN(parsed) {
|
|
return "NaN"
|
|
}
|
|
if math.IsInf(parsed, 1) {
|
|
return "Infinity"
|
|
}
|
|
if math.IsInf(parsed, -1) {
|
|
return "-Infinity"
|
|
}
|
|
|
|
floatBytes, err := json.Marshal(parsed)
|
|
if err != nil {
|
|
return "null"
|
|
}
|
|
return string(floatBytes)
|
|
}
|
|
|
|
func writeJSON5CommentBlock(writer io.Writer, comment string, indentString string, depth int) error {
|
|
comment = strings.ReplaceAll(comment, "\r\n", "\n")
|
|
comment = strings.TrimSpace(comment)
|
|
if comment == "" {
|
|
return nil
|
|
}
|
|
|
|
indent := strings.Repeat(indentString, depth)
|
|
lines := strings.Split(comment, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
line = strings.TrimSpace(strings.TrimPrefix(line, "#"))
|
|
line = strings.TrimSpace(strings.TrimPrefix(line, "//"))
|
|
if err := writeString(writer, indent); err != nil {
|
|
return err
|
|
}
|
|
if err := writeString(writer, "// "+line+"\n"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeJSON5InlineComment(writer io.Writer, comment string, forceLeadingSpace bool) error {
|
|
comment = strings.ReplaceAll(comment, "\r\n", "\n")
|
|
comment = strings.TrimSpace(comment)
|
|
if comment == "" {
|
|
return nil
|
|
}
|
|
comment = strings.TrimSpace(strings.TrimPrefix(comment, "#"))
|
|
comment = strings.TrimSpace(strings.TrimPrefix(comment, "//"))
|
|
comment = strings.ReplaceAll(comment, "\n", " ")
|
|
comment = strings.Join(strings.Fields(comment), " ")
|
|
|
|
prefix := ""
|
|
if forceLeadingSpace {
|
|
prefix = " "
|
|
}
|
|
return writeString(writer, prefix+"/* "+comment+" */")
|
|
}
|