package ast import ( "fmt" "io" "math" "strconv" "strings" "github.com/goccy/go-yaml/token" "golang.org/x/xerrors" ) var ( ErrInvalidTokenType = xerrors.New("invalid token type") ErrInvalidAnchorName = xerrors.New("invalid anchor name") ErrInvalidAliasName = xerrors.New("invalid alias name") ) // NodeType type identifier of node type NodeType int const ( // UnknownNodeType type identifier for default UnknownNodeType NodeType = iota // DocumentType type identifier for document node DocumentType // NullType type identifier for null node NullType // BoolType type identifier for boolean node BoolType // IntegerType type identifier for integer node IntegerType // FloatType type identifier for float node FloatType // InfinityType type identifier for infinity node InfinityType // NanType type identifier for nan node NanType // StringType type identifier for string node StringType // MergeKeyType type identifier for merge key node MergeKeyType // LiteralType type identifier for literal node LiteralType // MappingType type identifier for mapping node MappingType // MappingKeyType type identifier for mapping key node MappingKeyType // MappingValueType type identifier for mapping value node MappingValueType // SequenceType type identifier for sequence node SequenceType // AnchorType type identifier for anchor node AnchorType // AliasType type identifier for alias node AliasType // DirectiveType type identifier for directive node DirectiveType // TagType type identifier for tag node TagType // CommentType type identifier for comment node CommentType // CommentGroupType type identifier for comment group node CommentGroupType ) // String node type identifier to text func (t NodeType) String() string { switch t { case UnknownNodeType: return "UnknownNode" case DocumentType: return "Document" case NullType: return "Null" case BoolType: return "Bool" case IntegerType: return "Integer" case FloatType: return "Float" case InfinityType: return "Infinity" case NanType: return "Nan" case StringType: return "String" case MergeKeyType: return "MergeKey" case LiteralType: return "Literal" case MappingType: return "Mapping" case MappingKeyType: return "MappingKey" case MappingValueType: return "MappingValue" case SequenceType: return "Sequence" case AnchorType: return "Anchor" case AliasType: return "Alias" case DirectiveType: return "Directive" case TagType: return "Tag" case CommentType: return "Comment" case CommentGroupType: return "CommentGroup" } return "" } // String node type identifier to YAML Structure name // based on https://yaml.org/spec/1.2/spec.html func (t NodeType) YAMLName() string { switch t { case UnknownNodeType: return "unknown" case DocumentType: return "document" case NullType: return "null" case BoolType: return "boolean" case IntegerType: return "int" case FloatType: return "float" case InfinityType: return "inf" case NanType: return "nan" case StringType: return "string" case MergeKeyType: return "merge key" case LiteralType: return "scalar" case MappingType: return "mapping" case MappingKeyType: return "key" case MappingValueType: return "value" case SequenceType: return "sequence" case AnchorType: return "anchor" case AliasType: return "alias" case DirectiveType: return "directive" case TagType: return "tag" case CommentType: return "comment" case CommentGroupType: return "comment" } return "" } // Node type of node type Node interface { io.Reader // String node to text String() string // GetToken returns token instance GetToken() *token.Token // Type returns type of node Type() NodeType // AddColumn add column number to child nodes recursively AddColumn(int) // SetComment set comment token to node SetComment(*CommentGroupNode) error // Comment returns comment token instance GetComment() *CommentGroupNode // GetPath returns YAMLPath for the current node GetPath() string // SetPath set YAMLPath for the current node SetPath(string) // MarshalYAML MarshalYAML() ([]byte, error) // already read length readLen() int // append read length addReadLen(int) // clean read length clearLen() // String node to text without comment stringWithoutComment() string } // ScalarNode type for scalar node type ScalarNode interface { Node GetValue() interface{} } type BaseNode struct { Path string Comment *CommentGroupNode read int } func addCommentString(base string, node *CommentGroupNode) string { return fmt.Sprintf("%s %s", base, node.String()) } func (n *BaseNode) readLen() int { return n.read } func (n *BaseNode) clearLen() { n.read = 0 } func (n *BaseNode) addReadLen(len int) { n.read += len } // GetPath returns YAMLPath for the current node. func (n *BaseNode) GetPath() string { if n == nil { return "" } return n.Path } // SetPath set YAMLPath for the current node. func (n *BaseNode) SetPath(path string) { if n == nil { return } n.Path = path } // GetComment returns comment token instance func (n *BaseNode) GetComment() *CommentGroupNode { return n.Comment } // SetComment set comment token func (n *BaseNode) SetComment(node *CommentGroupNode) error { n.Comment = node return nil } func min(a, b int) int { if a < b { return a } return b } func readNode(p []byte, node Node) (int, error) { s := node.String() readLen := node.readLen() remain := len(s) - readLen if remain == 0 { node.clearLen() return 0, io.EOF } size := min(remain, len(p)) for idx, b := range s[readLen : readLen+size] { p[idx] = byte(b) } node.addReadLen(size) return size, nil } // Null create node for null value func Null(tk *token.Token) Node { return &NullNode{ BaseNode: &BaseNode{}, Token: tk, } } // Bool create node for boolean value func Bool(tk *token.Token) Node { b, _ := strconv.ParseBool(tk.Value) return &BoolNode{ BaseNode: &BaseNode{}, Token: tk, Value: b, } } // Integer create node for integer value func Integer(tk *token.Token) Node { value := removeUnderScoreFromNumber(tk.Value) switch tk.Type { case token.BinaryIntegerType: // skip two characters because binary token starts with '0b' skipCharacterNum := 2 negativePrefix := "" if value[0] == '-' { skipCharacterNum++ negativePrefix = "-" } if len(negativePrefix) > 0 { i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 2, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } } i, _ := strconv.ParseUint(negativePrefix+value[skipCharacterNum:], 2, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } case token.OctetIntegerType: // octet token starts with '0o' or '-0o' or '0' or '-0' skipCharacterNum := 1 negativePrefix := "" if value[0] == '-' { skipCharacterNum++ if len(value) > 2 && value[2] == 'o' { skipCharacterNum++ } negativePrefix = "-" } else { if value[1] == 'o' { skipCharacterNum++ } } if len(negativePrefix) > 0 { i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 8, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } } i, _ := strconv.ParseUint(value[skipCharacterNum:], 8, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } case token.HexIntegerType: // hex token starts with '0x' or '-0x' skipCharacterNum := 2 negativePrefix := "" if value[0] == '-' { skipCharacterNum++ negativePrefix = "-" } if len(negativePrefix) > 0 { i, _ := strconv.ParseInt(negativePrefix+value[skipCharacterNum:], 16, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } } i, _ := strconv.ParseUint(value[skipCharacterNum:], 16, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } } if value[0] == '-' || value[0] == '+' { i, _ := strconv.ParseInt(value, 10, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } } i, _ := strconv.ParseUint(value, 10, 64) return &IntegerNode{ BaseNode: &BaseNode{}, Token: tk, Value: i, } } // Float create node for float value func Float(tk *token.Token) Node { f, _ := strconv.ParseFloat(removeUnderScoreFromNumber(tk.Value), 64) return &FloatNode{ BaseNode: &BaseNode{}, Token: tk, Value: f, } } // Infinity create node for .inf or -.inf value func Infinity(tk *token.Token) *InfinityNode { node := &InfinityNode{ BaseNode: &BaseNode{}, Token: tk, } switch tk.Value { case ".inf", ".Inf", ".INF": node.Value = math.Inf(0) case "-.inf", "-.Inf", "-.INF": node.Value = math.Inf(-1) } return node } // Nan create node for .nan value func Nan(tk *token.Token) *NanNode { return &NanNode{ BaseNode: &BaseNode{}, Token: tk, } } // String create node for string value func String(tk *token.Token) *StringNode { return &StringNode{ BaseNode: &BaseNode{}, Token: tk, Value: tk.Value, } } // Comment create node for comment func Comment(tk *token.Token) *CommentNode { return &CommentNode{ BaseNode: &BaseNode{}, Token: tk, } } func CommentGroup(comments []*token.Token) *CommentGroupNode { nodes := []*CommentNode{} for _, comment := range comments { nodes = append(nodes, Comment(comment)) } return &CommentGroupNode{ BaseNode: &BaseNode{}, Comments: nodes, } } // MergeKey create node for merge key ( << ) func MergeKey(tk *token.Token) *MergeKeyNode { return &MergeKeyNode{ BaseNode: &BaseNode{}, Token: tk, } } // Mapping create node for map func Mapping(tk *token.Token, isFlowStyle bool, values ...*MappingValueNode) *MappingNode { node := &MappingNode{ BaseNode: &BaseNode{}, Start: tk, IsFlowStyle: isFlowStyle, Values: []*MappingValueNode{}, } node.Values = append(node.Values, values...) return node } // MappingValue create node for mapping value func MappingValue(tk *token.Token, key Node, value Node) *MappingValueNode { return &MappingValueNode{ BaseNode: &BaseNode{}, Start: tk, Key: key, Value: value, } } // MappingKey create node for map key ( '?' ). func MappingKey(tk *token.Token) *MappingKeyNode { return &MappingKeyNode{ BaseNode: &BaseNode{}, Start: tk, } } // Sequence create node for sequence func Sequence(tk *token.Token, isFlowStyle bool) *SequenceNode { return &SequenceNode{ BaseNode: &BaseNode{}, Start: tk, IsFlowStyle: isFlowStyle, Values: []Node{}, } } func Anchor(tk *token.Token) *AnchorNode { return &AnchorNode{ BaseNode: &BaseNode{}, Start: tk, } } func Alias(tk *token.Token) *AliasNode { return &AliasNode{ BaseNode: &BaseNode{}, Start: tk, } } func Document(tk *token.Token, body Node) *DocumentNode { return &DocumentNode{ BaseNode: &BaseNode{}, Start: tk, Body: body, } } func Directive(tk *token.Token) *DirectiveNode { return &DirectiveNode{ BaseNode: &BaseNode{}, Start: tk, } } func Literal(tk *token.Token) *LiteralNode { return &LiteralNode{ BaseNode: &BaseNode{}, Start: tk, } } func Tag(tk *token.Token) *TagNode { return &TagNode{ BaseNode: &BaseNode{}, Start: tk, } } // File contains all documents in YAML file type File struct { Name string Docs []*DocumentNode } // Read implements (io.Reader).Read func (f *File) Read(p []byte) (int, error) { for _, doc := range f.Docs { n, err := doc.Read(p) if err == io.EOF { continue } return n, nil } return 0, io.EOF } // String all documents to text func (f *File) String() string { docs := []string{} for _, doc := range f.Docs { docs = append(docs, doc.String()) } return strings.Join(docs, "\n") } func (f *File) stringWithoutComment() string { return f.String() } // DocumentNode type of Document type DocumentNode struct { *BaseNode Start *token.Token // position of DocumentHeader ( `---` ) End *token.Token // position of DocumentEnd ( `...` ) Body Node } // Read implements (io.Reader).Read func (d *DocumentNode) Read(p []byte) (int, error) { return readNode(p, d) } // Type returns DocumentNodeType func (d *DocumentNode) Type() NodeType { return DocumentType } // GetToken returns token instance func (d *DocumentNode) GetToken() *token.Token { return d.Body.GetToken() } // AddColumn add column number to child nodes recursively func (d *DocumentNode) AddColumn(col int) { if d.Body != nil { d.Body.AddColumn(col) } } // String document to text func (d *DocumentNode) String() string { doc := []string{} if d.Start != nil { doc = append(doc, d.Start.Value) } doc = append(doc, d.Body.String()) if d.End != nil { doc = append(doc, d.End.Value) } return strings.Join(doc, "\n") } func (d *DocumentNode) stringWithoutComment() string { return d.String() } // MarshalYAML encodes to a YAML text func (d *DocumentNode) MarshalYAML() ([]byte, error) { return []byte(d.String()), nil } func removeUnderScoreFromNumber(num string) string { return strings.ReplaceAll(num, "_", "") } // NullNode type of null node type NullNode struct { *BaseNode Token *token.Token } // Read implements (io.Reader).Read func (n *NullNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns NullType func (n *NullNode) Type() NodeType { return NullType } // GetToken returns token instance func (n *NullNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *NullNode) AddColumn(col int) { n.Token.AddColumn(col) } // GetValue returns nil value func (n *NullNode) GetValue() interface{} { return nil } // String returns `null` text func (n *NullNode) String() string { if n.Comment != nil { return fmt.Sprintf("null %s", n.Comment.String()) } return n.stringWithoutComment() } func (n *NullNode) stringWithoutComment() string { return "null" } // MarshalYAML encodes to a YAML text func (n *NullNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // IntegerNode type of integer node type IntegerNode struct { *BaseNode Token *token.Token Value interface{} // int64 or uint64 value } // Read implements (io.Reader).Read func (n *IntegerNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns IntegerType func (n *IntegerNode) Type() NodeType { return IntegerType } // GetToken returns token instance func (n *IntegerNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *IntegerNode) AddColumn(col int) { n.Token.AddColumn(col) } // GetValue returns int64 value func (n *IntegerNode) GetValue() interface{} { return n.Value } // String int64 to text func (n *IntegerNode) String() string { if n.Comment != nil { return addCommentString(n.Token.Value, n.Comment) } return n.stringWithoutComment() } func (n *IntegerNode) stringWithoutComment() string { return n.Token.Value } // MarshalYAML encodes to a YAML text func (n *IntegerNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // FloatNode type of float node type FloatNode struct { *BaseNode Token *token.Token Precision int Value float64 } // Read implements (io.Reader).Read func (n *FloatNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns FloatType func (n *FloatNode) Type() NodeType { return FloatType } // GetToken returns token instance func (n *FloatNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *FloatNode) AddColumn(col int) { n.Token.AddColumn(col) } // GetValue returns float64 value func (n *FloatNode) GetValue() interface{} { return n.Value } // String float64 to text func (n *FloatNode) String() string { if n.Comment != nil { return addCommentString(n.Token.Value, n.Comment) } return n.stringWithoutComment() } func (n *FloatNode) stringWithoutComment() string { return n.Token.Value } // MarshalYAML encodes to a YAML text func (n *FloatNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // StringNode type of string node type StringNode struct { *BaseNode Token *token.Token Value string } // Read implements (io.Reader).Read func (n *StringNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns StringType func (n *StringNode) Type() NodeType { return StringType } // GetToken returns token instance func (n *StringNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *StringNode) AddColumn(col int) { n.Token.AddColumn(col) } // GetValue returns string value func (n *StringNode) GetValue() interface{} { return n.Value } // escapeSingleQuote escapes s to a single quoted scalar. // https://yaml.org/spec/1.2.2/#732-single-quoted-style func escapeSingleQuote(s string) string { var sb strings.Builder growLen := len(s) + // s includes also one ' from the doubled pair 2 + // opening and closing ' strings.Count(s, "'") // ' added by ReplaceAll sb.Grow(growLen) sb.WriteString("'") sb.WriteString(strings.ReplaceAll(s, "'", "''")) sb.WriteString("'") return sb.String() } // String string value to text with quote or literal header if required func (n *StringNode) String() string { switch n.Token.Type { case token.SingleQuoteType: quoted := escapeSingleQuote(n.Value) if n.Comment != nil { return addCommentString(quoted, n.Comment) } return quoted case token.DoubleQuoteType: quoted := strconv.Quote(n.Value) if n.Comment != nil { return addCommentString(quoted, n.Comment) } return quoted } lbc := token.DetectLineBreakCharacter(n.Value) if strings.Contains(n.Value, lbc) { // This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same. // It works mostly, but inconsistencies occur if line break characters are mixed. header := token.LiteralBlockHeader(n.Value) space := strings.Repeat(" ", n.Token.Position.Column-1) values := []string{} for _, v := range strings.Split(n.Value, lbc) { values = append(values, fmt.Sprintf("%s %s", space, v)) } block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space)) return fmt.Sprintf("%s%s%s", header, lbc, block) } else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') { return fmt.Sprintf(`'%s'`, n.Value) } if n.Comment != nil { return addCommentString(n.Value, n.Comment) } return n.Value } func (n *StringNode) stringWithoutComment() string { switch n.Token.Type { case token.SingleQuoteType: quoted := fmt.Sprintf(`'%s'`, n.Value) return quoted case token.DoubleQuoteType: quoted := strconv.Quote(n.Value) return quoted } lbc := token.DetectLineBreakCharacter(n.Value) if strings.Contains(n.Value, lbc) { // This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same. // It works mostly, but inconsistencies occur if line break characters are mixed. header := token.LiteralBlockHeader(n.Value) space := strings.Repeat(" ", n.Token.Position.Column-1) values := []string{} for _, v := range strings.Split(n.Value, lbc) { values = append(values, fmt.Sprintf("%s %s", space, v)) } block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s %s", lbc, space)), fmt.Sprintf(" %s", space)) return fmt.Sprintf("%s%s%s", header, lbc, block) } else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') { return fmt.Sprintf(`'%s'`, n.Value) } return n.Value } // MarshalYAML encodes to a YAML text func (n *StringNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // LiteralNode type of literal node type LiteralNode struct { *BaseNode Start *token.Token Value *StringNode } // Read implements (io.Reader).Read func (n *LiteralNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns LiteralType func (n *LiteralNode) Type() NodeType { return LiteralType } // GetToken returns token instance func (n *LiteralNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *LiteralNode) AddColumn(col int) { n.Start.AddColumn(col) if n.Value != nil { n.Value.AddColumn(col) } } // GetValue returns string value func (n *LiteralNode) GetValue() interface{} { return n.String() } // String literal to text func (n *LiteralNode) String() string { origin := n.Value.GetToken().Origin lit := strings.TrimRight(strings.TrimRight(origin, " "), "\n") if n.Comment != nil { return fmt.Sprintf("%s %s\n%s", n.Start.Value, n.Comment.String(), lit) } return fmt.Sprintf("%s\n%s", n.Start.Value, lit) } func (n *LiteralNode) stringWithoutComment() string { return n.String() } // MarshalYAML encodes to a YAML text func (n *LiteralNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // MergeKeyNode type of merge key node type MergeKeyNode struct { *BaseNode Token *token.Token } // Read implements (io.Reader).Read func (n *MergeKeyNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns MergeKeyType func (n *MergeKeyNode) Type() NodeType { return MergeKeyType } // GetToken returns token instance func (n *MergeKeyNode) GetToken() *token.Token { return n.Token } // GetValue returns '<<' value func (n *MergeKeyNode) GetValue() interface{} { return n.Token.Value } // String returns '<<' value func (n *MergeKeyNode) String() string { return n.Token.Value } func (n *MergeKeyNode) stringWithoutComment() string { return n.Token.Value } // AddColumn add column number to child nodes recursively func (n *MergeKeyNode) AddColumn(col int) { n.Token.AddColumn(col) } // MarshalYAML encodes to a YAML text func (n *MergeKeyNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // BoolNode type of boolean node type BoolNode struct { *BaseNode Token *token.Token Value bool } // Read implements (io.Reader).Read func (n *BoolNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns BoolType func (n *BoolNode) Type() NodeType { return BoolType } // GetToken returns token instance func (n *BoolNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *BoolNode) AddColumn(col int) { n.Token.AddColumn(col) } // GetValue returns boolean value func (n *BoolNode) GetValue() interface{} { return n.Value } // String boolean to text func (n *BoolNode) String() string { if n.Comment != nil { return addCommentString(n.Token.Value, n.Comment) } return n.stringWithoutComment() } func (n *BoolNode) stringWithoutComment() string { return n.Token.Value } // MarshalYAML encodes to a YAML text func (n *BoolNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // InfinityNode type of infinity node type InfinityNode struct { *BaseNode Token *token.Token Value float64 } // Read implements (io.Reader).Read func (n *InfinityNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns InfinityType func (n *InfinityNode) Type() NodeType { return InfinityType } // GetToken returns token instance func (n *InfinityNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *InfinityNode) AddColumn(col int) { n.Token.AddColumn(col) } // GetValue returns math.Inf(0) or math.Inf(-1) func (n *InfinityNode) GetValue() interface{} { return n.Value } // String infinity to text func (n *InfinityNode) String() string { if n.Comment != nil { return addCommentString(n.Token.Value, n.Comment) } return n.stringWithoutComment() } func (n *InfinityNode) stringWithoutComment() string { return n.Token.Value } // MarshalYAML encodes to a YAML text func (n *InfinityNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // NanNode type of nan node type NanNode struct { *BaseNode Token *token.Token } // Read implements (io.Reader).Read func (n *NanNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns NanType func (n *NanNode) Type() NodeType { return NanType } // GetToken returns token instance func (n *NanNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *NanNode) AddColumn(col int) { n.Token.AddColumn(col) } // GetValue returns math.NaN() func (n *NanNode) GetValue() interface{} { return math.NaN() } // String returns .nan func (n *NanNode) String() string { if n.Comment != nil { return addCommentString(n.Token.Value, n.Comment) } return n.stringWithoutComment() } func (n *NanNode) stringWithoutComment() string { return n.Token.Value } // MarshalYAML encodes to a YAML text func (n *NanNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // MapNode interface of MappingValueNode / MappingNode type MapNode interface { MapRange() *MapNodeIter } // MapNodeIter is an iterator for ranging over a MapNode type MapNodeIter struct { values []*MappingValueNode idx int } const ( startRangeIndex = -1 ) // Next advances the map iterator and reports whether there is another entry. // It returns false when the iterator is exhausted. func (m *MapNodeIter) Next() bool { m.idx++ next := m.idx < len(m.values) return next } // Key returns the key of the iterator's current map node entry. func (m *MapNodeIter) Key() Node { return m.values[m.idx].Key } // Value returns the value of the iterator's current map node entry. func (m *MapNodeIter) Value() Node { return m.values[m.idx].Value } // MappingNode type of mapping node type MappingNode struct { *BaseNode Start *token.Token End *token.Token IsFlowStyle bool Values []*MappingValueNode } func (n *MappingNode) startPos() *token.Position { if len(n.Values) == 0 { return n.Start.Position } return n.Values[0].Key.GetToken().Position } // Merge merge key/value of map. func (n *MappingNode) Merge(target *MappingNode) { keyToMapValueMap := map[string]*MappingValueNode{} for _, value := range n.Values { key := value.Key.String() keyToMapValueMap[key] = value } column := n.startPos().Column - target.startPos().Column target.AddColumn(column) for _, value := range target.Values { mapValue, exists := keyToMapValueMap[value.Key.String()] if exists { mapValue.Value = value.Value } else { n.Values = append(n.Values, value) } } } // SetIsFlowStyle set value to IsFlowStyle field recursively. func (n *MappingNode) SetIsFlowStyle(isFlow bool) { n.IsFlowStyle = isFlow for _, value := range n.Values { value.SetIsFlowStyle(isFlow) } } // Read implements (io.Reader).Read func (n *MappingNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns MappingType func (n *MappingNode) Type() NodeType { return MappingType } // GetToken returns token instance func (n *MappingNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *MappingNode) AddColumn(col int) { n.Start.AddColumn(col) n.End.AddColumn(col) for _, value := range n.Values { value.AddColumn(col) } } func (n *MappingNode) flowStyleString(commentMode bool) string { values := []string{} for _, value := range n.Values { values = append(values, strings.TrimLeft(value.String(), " ")) } mapText := fmt.Sprintf("{%s}", strings.Join(values, ", ")) if commentMode && n.Comment != nil { return addCommentString(mapText, n.Comment) } return mapText } func (n *MappingNode) blockStyleString(commentMode bool) string { values := []string{} for _, value := range n.Values { values = append(values, value.String()) } mapText := strings.Join(values, "\n") if commentMode && n.Comment != nil { value := values[0] var spaceNum int for i := 0; i < len(value); i++ { if value[i] != ' ' { break } spaceNum++ } comment := n.Comment.StringWithSpace(spaceNum) return fmt.Sprintf("%s\n%s", comment, mapText) } return mapText } // String mapping values to text func (n *MappingNode) String() string { if len(n.Values) == 0 { if n.Comment != nil { return addCommentString("{}", n.Comment) } return "{}" } commentMode := true if n.IsFlowStyle || len(n.Values) == 0 { return n.flowStyleString(commentMode) } return n.blockStyleString(commentMode) } func (n *MappingNode) stringWithoutComment() string { commentMode := false if n.IsFlowStyle || len(n.Values) == 0 { return n.flowStyleString(commentMode) } return n.blockStyleString(commentMode) } // MapRange implements MapNode protocol func (n *MappingNode) MapRange() *MapNodeIter { return &MapNodeIter{ idx: startRangeIndex, values: n.Values, } } // MarshalYAML encodes to a YAML text func (n *MappingNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // MappingKeyNode type of tag node type MappingKeyNode struct { *BaseNode Start *token.Token Value Node } // Read implements (io.Reader).Read func (n *MappingKeyNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns MappingKeyType func (n *MappingKeyNode) Type() NodeType { return MappingKeyType } // GetToken returns token instance func (n *MappingKeyNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *MappingKeyNode) AddColumn(col int) { n.Start.AddColumn(col) if n.Value != nil { n.Value.AddColumn(col) } } // String tag to text func (n *MappingKeyNode) String() string { return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String()) } func (n *MappingKeyNode) stringWithoutComment() string { return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String()) } // MarshalYAML encodes to a YAML text func (n *MappingKeyNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // MappingValueNode type of mapping value type MappingValueNode struct { *BaseNode Start *token.Token Key Node Value Node } // Replace replace value node. func (n *MappingValueNode) Replace(value Node) error { column := n.Value.GetToken().Position.Column - value.GetToken().Position.Column value.AddColumn(column) n.Value = value return nil } // Read implements (io.Reader).Read func (n *MappingValueNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns MappingValueType func (n *MappingValueNode) Type() NodeType { return MappingValueType } // GetToken returns token instance func (n *MappingValueNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *MappingValueNode) AddColumn(col int) { n.Start.AddColumn(col) if n.Key != nil { n.Key.AddColumn(col) } if n.Value != nil { n.Value.AddColumn(col) } } // SetIsFlowStyle set value to IsFlowStyle field recursively. func (n *MappingValueNode) SetIsFlowStyle(isFlow bool) { switch value := n.Value.(type) { case *MappingNode: value.SetIsFlowStyle(isFlow) case *MappingValueNode: value.SetIsFlowStyle(isFlow) case *SequenceNode: value.SetIsFlowStyle(isFlow) } } // String mapping value to text func (n *MappingValueNode) String() string { if n.Comment != nil { return fmt.Sprintf( "%s\n%s", n.Comment.StringWithSpace(n.Key.GetToken().Position.Column-1), n.toString(), ) } return n.toString() } func (n *MappingValueNode) toString() string { space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1) keyIndentLevel := n.Key.GetToken().Position.IndentLevel valueIndentLevel := n.Value.GetToken().Position.IndentLevel keyComment := n.Key.GetComment() if _, ok := n.Value.(ScalarNode); ok { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if keyIndentLevel < valueIndentLevel { if keyComment != nil { return fmt.Sprintf( "%s%s: %s\n%s", space, n.Key.stringWithoutComment(), keyComment.String(), n.Value.String(), ) } return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String()) } else if m, ok := n.Value.(*MappingNode); ok && (m.IsFlowStyle || len(m.Values) == 0) { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if s, ok := n.Value.(*SequenceNode); ok && (s.IsFlowStyle || len(s.Values) == 0) { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if _, ok := n.Value.(*AnchorNode); ok { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if _, ok := n.Value.(*AliasNode); ok { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } if keyComment != nil { return fmt.Sprintf( "%s%s: %s\n%s", space, n.Key.stringWithoutComment(), keyComment.String(), n.Value.String(), ) } if m, ok := n.Value.(*MappingNode); ok && m.Comment != nil { return fmt.Sprintf( "%s%s: %s", space, n.Key.String(), strings.TrimLeft(n.Value.String(), " "), ) } return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String()) } func (n *MappingValueNode) stringWithoutComment() string { space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1) keyIndentLevel := n.Key.GetToken().Position.IndentLevel valueIndentLevel := n.Value.GetToken().Position.IndentLevel if _, ok := n.Value.(ScalarNode); ok { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if keyIndentLevel < valueIndentLevel { return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String()) } else if m, ok := n.Value.(*MappingNode); ok && (m.IsFlowStyle || len(m.Values) == 0) { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if s, ok := n.Value.(*SequenceNode); ok && (s.IsFlowStyle || len(s.Values) == 0) { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if _, ok := n.Value.(*AnchorNode); ok { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } else if _, ok := n.Value.(*AliasNode); ok { return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String()) } return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String()) } // MapRange implements MapNode protocol func (n *MappingValueNode) MapRange() *MapNodeIter { return &MapNodeIter{ idx: startRangeIndex, values: []*MappingValueNode{n}, } } // MarshalYAML encodes to a YAML text func (n *MappingValueNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // ArrayNode interface of SequenceNode type ArrayNode interface { ArrayRange() *ArrayNodeIter } // ArrayNodeIter is an iterator for ranging over a ArrayNode type ArrayNodeIter struct { values []Node idx int } // Next advances the array iterator and reports whether there is another entry. // It returns false when the iterator is exhausted. func (m *ArrayNodeIter) Next() bool { m.idx++ next := m.idx < len(m.values) return next } // Value returns the value of the iterator's current array entry. func (m *ArrayNodeIter) Value() Node { return m.values[m.idx] } // Len returns length of array func (m *ArrayNodeIter) Len() int { return len(m.values) } // SequenceNode type of sequence node type SequenceNode struct { *BaseNode Start *token.Token End *token.Token IsFlowStyle bool Values []Node ValueComments []*CommentGroupNode } // Replace replace value node. func (n *SequenceNode) Replace(idx int, value Node) error { if len(n.Values) <= idx { return xerrors.Errorf( "invalid index for sequence: sequence length is %d, but specified %d index", len(n.Values), idx, ) } column := n.Values[idx].GetToken().Position.Column - value.GetToken().Position.Column value.AddColumn(column) n.Values[idx] = value return nil } // Merge merge sequence value. func (n *SequenceNode) Merge(target *SequenceNode) { column := n.Start.Position.Column - target.Start.Position.Column target.AddColumn(column) for _, value := range target.Values { n.Values = append(n.Values, value) } } // SetIsFlowStyle set value to IsFlowStyle field recursively. func (n *SequenceNode) SetIsFlowStyle(isFlow bool) { n.IsFlowStyle = isFlow for _, value := range n.Values { switch value := value.(type) { case *MappingNode: value.SetIsFlowStyle(isFlow) case *MappingValueNode: value.SetIsFlowStyle(isFlow) case *SequenceNode: value.SetIsFlowStyle(isFlow) } } } // Read implements (io.Reader).Read func (n *SequenceNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns SequenceType func (n *SequenceNode) Type() NodeType { return SequenceType } // GetToken returns token instance func (n *SequenceNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *SequenceNode) AddColumn(col int) { n.Start.AddColumn(col) n.End.AddColumn(col) for _, value := range n.Values { value.AddColumn(col) } } func (n *SequenceNode) flowStyleString() string { values := []string{} for _, value := range n.Values { values = append(values, value.String()) } return fmt.Sprintf("[%s]", strings.Join(values, ", ")) } func (n *SequenceNode) blockStyleString() string { space := strings.Repeat(" ", n.Start.Position.Column-1) values := []string{} if n.Comment != nil { values = append(values, n.Comment.StringWithSpace(n.Start.Position.Column-1)) } for idx, value := range n.Values { valueStr := value.String() splittedValues := strings.Split(valueStr, "\n") trimmedFirstValue := strings.TrimLeft(splittedValues[0], " ") diffLength := len(splittedValues[0]) - len(trimmedFirstValue) if len(splittedValues) > 1 && value.Type() == StringType || value.Type() == LiteralType { // If multi-line string, the space characters for indent have already been added, so delete them. for i := 1; i < len(splittedValues); i++ { splittedValues[i] = strings.TrimLeft(splittedValues[i], " ") } } newValues := []string{trimmedFirstValue} for i := 1; i < len(splittedValues); i++ { if len(splittedValues[i]) <= diffLength { // this line is \n or white space only newValues = append(newValues, "") continue } trimmed := splittedValues[i][diffLength:] newValues = append(newValues, fmt.Sprintf("%s %s", space, trimmed)) } newValue := strings.Join(newValues, "\n") if len(n.ValueComments) == len(n.Values) && n.ValueComments[idx] != nil { values = append(values, n.ValueComments[idx].StringWithSpace(n.Start.Position.Column-1)) } values = append(values, fmt.Sprintf("%s- %s", space, newValue)) } return strings.Join(values, "\n") } // String sequence to text func (n *SequenceNode) String() string { if n.IsFlowStyle || len(n.Values) == 0 { return n.flowStyleString() } return n.blockStyleString() } func (n *SequenceNode) stringWithoutComment() string { if n.IsFlowStyle || len(n.Values) == 0 { return n.flowStyleString() } return n.blockStyleString() } // ArrayRange implements ArrayNode protocol func (n *SequenceNode) ArrayRange() *ArrayNodeIter { return &ArrayNodeIter{ idx: startRangeIndex, values: n.Values, } } // MarshalYAML encodes to a YAML text func (n *SequenceNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // AnchorNode type of anchor node type AnchorNode struct { *BaseNode Start *token.Token Name Node Value Node } func (n *AnchorNode) SetName(name string) error { if n.Name == nil { return ErrInvalidAnchorName } s, ok := n.Name.(*StringNode) if !ok { return ErrInvalidAnchorName } s.Value = name return nil } // Read implements (io.Reader).Read func (n *AnchorNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns AnchorType func (n *AnchorNode) Type() NodeType { return AnchorType } // GetToken returns token instance func (n *AnchorNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *AnchorNode) AddColumn(col int) { n.Start.AddColumn(col) if n.Name != nil { n.Name.AddColumn(col) } if n.Value != nil { n.Value.AddColumn(col) } } // String anchor to text func (n *AnchorNode) String() string { value := n.Value.String() if len(strings.Split(value, "\n")) > 1 { return fmt.Sprintf("&%s\n%s", n.Name.String(), value) } else if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle { return fmt.Sprintf("&%s\n%s", n.Name.String(), value) } else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle { return fmt.Sprintf("&%s\n%s", n.Name.String(), value) } return fmt.Sprintf("&%s %s", n.Name.String(), value) } func (n *AnchorNode) stringWithoutComment() string { return n.String() } // MarshalYAML encodes to a YAML text func (n *AnchorNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // AliasNode type of alias node type AliasNode struct { *BaseNode Start *token.Token Value Node } func (n *AliasNode) SetName(name string) error { if n.Value == nil { return ErrInvalidAliasName } s, ok := n.Value.(*StringNode) if !ok { return ErrInvalidAliasName } s.Value = name return nil } // Read implements (io.Reader).Read func (n *AliasNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns AliasType func (n *AliasNode) Type() NodeType { return AliasType } // GetToken returns token instance func (n *AliasNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *AliasNode) AddColumn(col int) { n.Start.AddColumn(col) if n.Value != nil { n.Value.AddColumn(col) } } // String alias to text func (n *AliasNode) String() string { return fmt.Sprintf("*%s", n.Value.String()) } func (n *AliasNode) stringWithoutComment() string { return fmt.Sprintf("*%s", n.Value.String()) } // MarshalYAML encodes to a YAML text func (n *AliasNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // DirectiveNode type of directive node type DirectiveNode struct { *BaseNode Start *token.Token Value Node } // Read implements (io.Reader).Read func (n *DirectiveNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns DirectiveType func (n *DirectiveNode) Type() NodeType { return DirectiveType } // GetToken returns token instance func (n *DirectiveNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *DirectiveNode) AddColumn(col int) { if n.Value != nil { n.Value.AddColumn(col) } } // String directive to text func (n *DirectiveNode) String() string { return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String()) } func (n *DirectiveNode) stringWithoutComment() string { return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String()) } // MarshalYAML encodes to a YAML text func (n *DirectiveNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // TagNode type of tag node type TagNode struct { *BaseNode Start *token.Token Value Node } // Read implements (io.Reader).Read func (n *TagNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns TagType func (n *TagNode) Type() NodeType { return TagType } // GetToken returns token instance func (n *TagNode) GetToken() *token.Token { return n.Start } // AddColumn add column number to child nodes recursively func (n *TagNode) AddColumn(col int) { n.Start.AddColumn(col) if n.Value != nil { n.Value.AddColumn(col) } } // String tag to text func (n *TagNode) String() string { return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String()) } func (n *TagNode) stringWithoutComment() string { return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String()) } // MarshalYAML encodes to a YAML text func (n *TagNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // CommentNode type of comment node type CommentNode struct { *BaseNode Token *token.Token } // Read implements (io.Reader).Read func (n *CommentNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns TagType func (n *CommentNode) Type() NodeType { return CommentType } // GetToken returns token instance func (n *CommentNode) GetToken() *token.Token { return n.Token } // AddColumn add column number to child nodes recursively func (n *CommentNode) AddColumn(col int) { if n.Token == nil { return } n.Token.AddColumn(col) } // String comment to text func (n *CommentNode) String() string { return fmt.Sprintf("#%s", n.Token.Value) } func (n *CommentNode) stringWithoutComment() string { return "" } // MarshalYAML encodes to a YAML text func (n *CommentNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // CommentGroupNode type of comment node type CommentGroupNode struct { *BaseNode Comments []*CommentNode } // Read implements (io.Reader).Read func (n *CommentGroupNode) Read(p []byte) (int, error) { return readNode(p, n) } // Type returns TagType func (n *CommentGroupNode) Type() NodeType { return CommentType } // GetToken returns token instance func (n *CommentGroupNode) GetToken() *token.Token { if len(n.Comments) > 0 { return n.Comments[0].Token } return nil } // AddColumn add column number to child nodes recursively func (n *CommentGroupNode) AddColumn(col int) { for _, comment := range n.Comments { comment.AddColumn(col) } } // String comment to text func (n *CommentGroupNode) String() string { values := []string{} for _, comment := range n.Comments { values = append(values, comment.String()) } return strings.Join(values, "\n") } func (n *CommentGroupNode) StringWithSpace(col int) string { space := strings.Repeat(" ", col) values := []string{} for _, comment := range n.Comments { values = append(values, space+comment.String()) } return strings.Join(values, "\n") } func (n *CommentGroupNode) stringWithoutComment() string { return "" } // MarshalYAML encodes to a YAML text func (n *CommentGroupNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } // Visitor has Visit method that is invokded for each node encountered by Walk. // If the result visitor w is not nil, Walk visits each of the children of node with the visitor w, // followed by a call of w.Visit(nil). type Visitor interface { Visit(Node) Visitor } // Walk traverses an AST in depth-first order: It starts by calling v.Visit(node); node must not be nil. // If the visitor w returned by v.Visit(node) is not nil, // Walk is invoked recursively with visitor w for each of the non-nil children of node, // followed by a call of w.Visit(nil). func Walk(v Visitor, node Node) { if v = v.Visit(node); v == nil { return } switch n := node.(type) { case *CommentNode: case *NullNode: walkComment(v, n.BaseNode) case *IntegerNode: walkComment(v, n.BaseNode) case *FloatNode: walkComment(v, n.BaseNode) case *StringNode: walkComment(v, n.BaseNode) case *MergeKeyNode: walkComment(v, n.BaseNode) case *BoolNode: walkComment(v, n.BaseNode) case *InfinityNode: walkComment(v, n.BaseNode) case *NanNode: walkComment(v, n.BaseNode) case *LiteralNode: walkComment(v, n.BaseNode) Walk(v, n.Value) case *DirectiveNode: walkComment(v, n.BaseNode) Walk(v, n.Value) case *TagNode: walkComment(v, n.BaseNode) Walk(v, n.Value) case *DocumentNode: walkComment(v, n.BaseNode) Walk(v, n.Body) case *MappingNode: walkComment(v, n.BaseNode) for _, value := range n.Values { Walk(v, value) } case *MappingKeyNode: walkComment(v, n.BaseNode) Walk(v, n.Value) case *MappingValueNode: walkComment(v, n.BaseNode) Walk(v, n.Key) Walk(v, n.Value) case *SequenceNode: walkComment(v, n.BaseNode) for _, value := range n.Values { Walk(v, value) } case *AnchorNode: walkComment(v, n.BaseNode) Walk(v, n.Name) Walk(v, n.Value) case *AliasNode: walkComment(v, n.BaseNode) Walk(v, n.Value) } } func walkComment(v Visitor, base *BaseNode) { if base == nil { return } if base.Comment == nil { return } Walk(v, base.Comment) } type filterWalker struct { typ NodeType results []Node } func (v *filterWalker) Visit(n Node) Visitor { if v.typ == n.Type() { v.results = append(v.results, n) } return v } type parentFinder struct { target Node } func (f *parentFinder) walk(parent, node Node) Node { if f.target == node { return parent } switch n := node.(type) { case *CommentNode: return nil case *NullNode: return nil case *IntegerNode: return nil case *FloatNode: return nil case *StringNode: return nil case *MergeKeyNode: return nil case *BoolNode: return nil case *InfinityNode: return nil case *NanNode: return nil case *LiteralNode: return f.walk(node, n.Value) case *DirectiveNode: return f.walk(node, n.Value) case *TagNode: return f.walk(node, n.Value) case *DocumentNode: return f.walk(node, n.Body) case *MappingNode: for _, value := range n.Values { if found := f.walk(node, value); found != nil { return found } } case *MappingKeyNode: return f.walk(node, n.Value) case *MappingValueNode: if found := f.walk(node, n.Key); found != nil { return found } return f.walk(node, n.Value) case *SequenceNode: for _, value := range n.Values { if found := f.walk(node, value); found != nil { return found } } case *AnchorNode: if found := f.walk(node, n.Name); found != nil { return found } return f.walk(node, n.Value) case *AliasNode: return f.walk(node, n.Value) } return nil } // Parent get parent node from child node. func Parent(root, child Node) Node { finder := &parentFinder{target: child} return finder.walk(root, root) } // Filter returns a list of nodes that match the given type. func Filter(typ NodeType, node Node) []Node { walker := &filterWalker{typ: typ} Walk(walker, node) return walker.results } // FilterFile returns a list of nodes that match the given type. func FilterFile(typ NodeType, file *File) []Node { results := []Node{} for _, doc := range file.Docs { walker := &filterWalker{typ: typ} Walk(walker, doc) results = append(results, walker.results...) } return results } type ErrInvalidMergeType struct { dst Node src Node } func (e *ErrInvalidMergeType) Error() string { return fmt.Sprintf("cannot merge %s into %s", e.src.Type(), e.dst.Type()) } // Merge merge document, map, sequence node. func Merge(dst Node, src Node) error { if doc, ok := src.(*DocumentNode); ok { src = doc.Body } err := &ErrInvalidMergeType{dst: dst, src: src} switch dst.Type() { case DocumentType: node := dst.(*DocumentNode) return Merge(node.Body, src) case MappingType: node := dst.(*MappingNode) target, ok := src.(*MappingNode) if !ok { return err } node.Merge(target) return nil case SequenceType: node := dst.(*SequenceNode) target, ok := src.(*SequenceNode) if !ok { return err } node.Merge(target) return nil } return err }