package yqlib import ( "bufio" "errors" "fmt" "io" "strings" "github.com/magiconair/properties" ) type propertiesEncoder struct { prefs PropertiesPreferences } func NewPropertiesEncoder(prefs PropertiesPreferences) Encoder { return &propertiesEncoder{ prefs: prefs, } } func (pe *propertiesEncoder) CanHandleAliases() bool { return false } func (pe *propertiesEncoder) PrintDocumentSeparator(_ io.Writer) error { return nil } func (pe *propertiesEncoder) PrintLeadingContent(writer io.Writer, content string) error { reader := bufio.NewReader(strings.NewReader(content)) for { readline, errReading := reader.ReadString('\n') if errReading != nil && !errors.Is(errReading, io.EOF) { return errReading } if strings.Contains(readline, "$yqDocSeparator$") { if err := pe.PrintDocumentSeparator(writer); err != nil { return err } } else { if err := writeString(writer, readline); err != nil { return err } } if errors.Is(errReading, io.EOF) { if readline != "" { // the last comment we read didn't have a newline, put one in if err := writeString(writer, "\n"); err != nil { return err } } break } } return nil } func (pe *propertiesEncoder) Encode(writer io.Writer, node *CandidateNode) error { if node.Kind == ScalarNode { return writeString(writer, node.Value+"\n") } mapKeysToStrings(node) p := properties.NewProperties() p.WriteSeparator = pe.prefs.KeyValueSeparator err := pe.doEncode(p, node, "", nil) if err != nil { return err } _, err = p.WriteComment(writer, "#", properties.UTF8) return err } func (pe *propertiesEncoder) doEncode(p *properties.Properties, node *CandidateNode, path string, keyNode *CandidateNode) error { comments := "" if keyNode != nil { // include the key node comments if present comments = headAndLineComment(keyNode) } comments = comments + headAndLineComment(node) commentsWithSpaces := strings.ReplaceAll(comments, "\n", "\n ") p.SetComments(path, strings.Split(commentsWithSpaces, "\n")) switch node.Kind { case ScalarNode: var nodeValue string if pe.prefs.UnwrapScalar || !strings.Contains(node.Value, " ") { nodeValue = node.Value } else { nodeValue = fmt.Sprintf("%q", node.Value) } _, _, err := p.Set(path, nodeValue) return err case SequenceNode: return pe.encodeArray(p, node.Content, path) case MappingNode: return pe.encodeMap(p, node.Content, path) case AliasNode: return pe.doEncode(p, node.Alias, path, nil) default: return fmt.Errorf("Unsupported node %v", node.Tag) } } func (pe *propertiesEncoder) appendPath(path string, key interface{}) string { if path == "" { return fmt.Sprintf("%v", key) } switch key.(type) { case int: if pe.prefs.UseArrayBrackets { return fmt.Sprintf("%v[%v]", path, key) } } return fmt.Sprintf("%v.%v", path, key) } func (pe *propertiesEncoder) encodeArray(p *properties.Properties, kids []*CandidateNode, path string) error { for index, child := range kids { err := pe.doEncode(p, child, pe.appendPath(path, index), nil) if err != nil { return err } } return nil } func (pe *propertiesEncoder) encodeMap(p *properties.Properties, kids []*CandidateNode, path string) error { for index := 0; index < len(kids); index = index + 2 { key := kids[index] value := kids[index+1] err := pe.doEncode(p, value, pe.appendPath(path, key.Value), key) if err != nil { return err } } return nil }