@@ -23,149 +23,157 @@ func Encode(v any, indent int) ([]byte, error) {
2323 return b .Bytes (), nil
2424}
2525
26- // Patch - change key/value pair in YAML file without break formatting
27- func Patch (src []byte , key string , value any , path ... string ) ([]byte , error ) {
28- nodeParent , err := FindParent (src , path ... )
26+ func Patch (in []byte , path []string , value any ) ([]byte , error ) {
27+ out , err := patch (in , path , value )
2928 if err != nil {
3029 return nil , err
3130 }
3231
33- var dst []byte
34-
35- if nodeParent != nil {
36- dst , err = AddOrReplace (src , key , value , nodeParent )
37- } else {
38- dst , err = AddToEnd (src , key , value , path ... )
39- }
40-
41- if err = yaml .Unmarshal (dst , map [string ]any {}); err != nil {
32+ // validate
33+ if err = yaml .Unmarshal (out , map [string ]any {}); err != nil {
4234 return nil , err
4335 }
4436
45- return dst , nil
37+ return out , nil
4638}
4739
48- // FindParent - return YAML Node from path of keys (tree)
49- func FindParent (src []byte , path ... string ) (* yaml.Node , error ) {
50- if len (src ) == 0 {
51- return nil , nil
52- }
53-
40+ func patch (in []byte , path []string , value any ) ([]byte , error ) {
5441 var root yaml.Node
55- if err := yaml .Unmarshal (src , & root ); err != nil {
42+ if err := yaml .Unmarshal (in , & root ); err != nil {
43+ // invalid yaml
5644 return nil , err
5745 }
5846
59- if root .Content == nil {
60- return nil , nil
47+ // empty in
48+ if len (root .Content ) != 1 {
49+ return addToEnd (in , path , value )
6150 }
6251
63- parent := root .Content [0 ] // yaml.DocumentNode
64- for _ , name := range path {
65- if parent == nil {
66- break
67- }
68- _ , parent = FindChild (parent , name )
52+ // yaml is not dict
53+ if root .Content [0 ].Kind != yaml .MappingNode {
54+ return nil , errors .New ("yaml: can't patch" )
6955 }
70- return parent , nil
71- }
7256
73- // FindChild - search and return YAML key/value pair for current Node
74- func FindChild (node * yaml.Node , name string ) (key , value * yaml.Node ) {
75- for i , child := range node .Content {
76- if child .Value != name {
77- continue
78- }
79- return child , node .Content [i + 1 ]
80- }
57+ // dict items list
58+ nodes := root .Content [0 ].Content
8159
82- return nil , nil
83- }
60+ n := len (path ) - 1
8461
85- func FirstChild (node * yaml.Node ) * yaml.Node {
86- if node .Content == nil {
87- return node
62+ // parent node key/value
63+ pKey , pVal := findNode (nodes , path [:n ])
64+ if pKey == nil {
65+ // no parent node
66+ return addToEnd (in , path , value )
8867 }
89- return node .Content [0 ]
90- }
9168
92- func LastChild (node * yaml.Node ) * yaml.Node {
93- if node .Content == nil {
94- return node
69+ var paste []byte
70+
71+ if value != nil {
72+ // nil value means delete key
73+ var err error
74+ v := map [string ]any {path [n ]: value }
75+ if paste , err = Encode (v , 2 ); err != nil {
76+ return nil , err
77+ }
9578 }
96- return LastChild (node .Content [len (node .Content )- 1 ])
97- }
9879
99- func AddOrReplace (src []byte , key string , value any , nodeParent * yaml.Node ) ([]byte , error ) {
100- v := map [string ]any {key : value }
101- put , err := Encode (v , 2 )
102- if err != nil {
103- return nil , err
80+ iKey , _ := findNode (pVal .Content , path [n :])
81+ if iKey != nil {
82+ // key item not nil (replace value)
83+ paste = addIndent (paste , iKey .Column - 1 )
84+
85+ i0 , i1 := nodeBounds (in , iKey )
86+ return join (in [:i0 ], paste , in [i1 :]), nil
10487 }
10588
106- if nodeKey , nodeValue := FindChild (nodeParent , key ); nodeKey != nil {
107- put = AddIndent (put , nodeKey .Column - 1 )
89+ if pVal .Content != nil {
90+ // parent value not nil (use first child indent)
91+ paste = addIndent (paste , pVal .Column - 1 )
92+ } else {
93+ // parent value is nil (use parent indent + 2)
94+ paste = addIndent (paste , pKey .Column + 1 )
95+ }
10896
109- i0 := LineOffset (src , nodeKey .Line )
110- i1 := LineOffset (src , LastChild (nodeValue ).Line + 1 )
97+ _ , i1 := nodeBounds (in , pKey )
98+ return join (in [:i1 ], paste , in [i1 :]), nil
99+ }
111100
112- if i1 < 0 { // no new line on the end of file
113- if value != nil {
114- return append (src [:i0 ], put ... ), nil
101+ func findNode (nodes []* yaml.Node , keys []string ) (key , value * yaml.Node ) {
102+ for i , name := range keys {
103+ for j := 0 ; j < len (nodes ); j += 2 {
104+ if nodes [j ].Value == name {
105+ if i < len (keys )- 1 {
106+ nodes = nodes [j + 1 ].Content
107+ break
108+ }
109+ return nodes [j ], nodes [j + 1 ]
115110 }
116- return src [:i0 ], nil
117- }
118-
119- dst := make ([]byte , 0 , len (src )+ len (put ))
120- dst = append (dst , src [:i0 ]... )
121- if value != nil {
122- dst = append (dst , put ... )
123111 }
124- return append (dst , src [i1 :]... ), nil
125112 }
113+ return nil , nil
114+ }
126115
127- put = AddIndent (put , FirstChild (nodeParent ).Column - 1 )
116+ func nodeBounds (in []byte , node * yaml.Node ) (offset0 , offset1 int ) {
117+ // start from next line after node
118+ offset0 = lineOffset (in , node .Line )
119+ offset1 = lineOffset (in , node .Line + 1 )
128120
129- i := LineOffset (src , LastChild (nodeParent ).Line + 1 )
121+ if offset1 < 0 {
122+ return offset0 , len (in )
123+ }
130124
131- if i < 0 { // no new line on the end of file
132- src = append (src , '\n' )
133- if value != nil {
134- src = append (src , put ... )
125+ for i := offset1 ; i < len (in ); {
126+ indent , length := parseLine (in [i :])
127+ if indent + 1 != length {
128+ if node .Column < indent + 1 {
129+ offset1 = i + length
130+ } else {
131+ break
132+ }
135133 }
136- return src , nil
134+ i += length
137135 }
138136
139- dst := make ([]byte , 0 , len (src )+ len (put ))
140- dst = append (dst , src [:i ]... )
141- if value != nil {
142- dst = append (dst , put ... )
143- }
144- return append (dst , src [i :]... ), nil
137+ return
145138}
146139
147- func AddToEnd ( src []byte , key string , value any , path ... string ) ([]byte , error ) {
148- if len (path ) > 1 || value == nil {
149- return nil , errors .New ("config : path not exist" )
140+ func addToEnd ( in []byte , path [] string , value any ) ([]byte , error ) {
141+ if len (path ) != 2 || value == nil {
142+ return nil , errors .New ("yaml : path not exist" )
150143 }
151144
152145 v := map [string ]map [string ]any {
153- path [0 ]: {key : value },
146+ path [0 ]: {path [ 1 ] : value },
154147 }
155- put , err := Encode (v , 2 )
148+ paste , err := Encode (v , 2 )
156149 if err != nil {
157150 return nil , err
158151 }
159152
160- dst := make ([]byte , 0 , len (src )+ len (put )+ 10 )
161- dst = append (dst , src ... )
162- if l := len (src ); l > 0 && src [l - 1 ] != '\n' {
163- dst = append (dst , '\n' )
153+ return join (in , paste ), nil
154+ }
155+
156+ func join (items ... []byte ) []byte {
157+ n := len (items ) - 1
158+ for _ , b := range items {
159+ n += len (b )
160+ }
161+
162+ buf := make ([]byte , 0 , n )
163+ for _ , b := range items {
164+ if len (b ) == 0 {
165+ continue
166+ }
167+ if n = len (buf ); n > 0 && buf [n - 1 ] != '\n' {
168+ buf = append (buf , '\n' )
169+ }
170+ buf = append (buf , b ... )
164171 }
165- return append (dst , put ... ), nil
172+
173+ return buf
166174}
167175
168- func AddPrefix (src , pre []byte ) (dst []byte ) {
176+ func addPrefix (src , pre []byte ) (dst []byte ) {
169177 for len (src ) > 0 {
170178 dst = append (dst , pre ... )
171179 i := bytes .IndexByte (src , '\n' ) + 1
@@ -180,25 +188,43 @@ func AddPrefix(src, pre []byte) (dst []byte) {
180188 return
181189}
182190
183- func AddIndent ( src []byte , indent int ) (dst []byte ) {
191+ func addIndent ( in []byte , indent int ) (dst []byte ) {
184192 pre := make ([]byte , indent )
185193 for i := 0 ; i < indent ; i ++ {
186194 pre [i ] = ' '
187195 }
188- return AddPrefix ( src , pre )
196+ return addPrefix ( in , pre )
189197}
190198
191- func LineOffset ( b []byte , line int ) (offset int ) {
199+ func lineOffset ( in []byte , line int ) (offset int ) {
192200 for l := 1 ; ; l ++ {
193201 if l == line {
194202 return offset
195203 }
196204
197- i := bytes .IndexByte (b [offset :], '\n' ) + 1
205+ i := bytes .IndexByte (in [offset :], '\n' ) + 1
198206 if i == 0 {
199207 break
200208 }
201209 offset += i
202210 }
203211 return - 1
204212}
213+
214+ func parseLine (b []byte ) (indent int , length int ) {
215+ prefix := true
216+ for ; length < len (b ); length ++ {
217+ switch b [length ] {
218+ case ' ' :
219+ if prefix {
220+ indent ++
221+ }
222+ case '\n' :
223+ length ++
224+ return
225+ default :
226+ prefix = false
227+ }
228+ }
229+ return
230+ }
0 commit comments