yaml.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. package yaml
  2. import (
  3. "bytes"
  4. "errors"
  5. "gopkg.in/yaml.v3"
  6. )
  7. func Unmarshal(in []byte, out interface{}) (err error) {
  8. return yaml.Unmarshal(in, out)
  9. }
  10. func Encode(v any, indent int) ([]byte, error) {
  11. b := bytes.NewBuffer(nil)
  12. e := yaml.NewEncoder(b)
  13. e.SetIndent(indent)
  14. if err := e.Encode(v); err != nil {
  15. return nil, err
  16. }
  17. return b.Bytes(), nil
  18. }
  19. // Patch - change key/value pair in YAML file without break formatting
  20. func Patch(src []byte, key string, value any, path ...string) ([]byte, error) {
  21. nodeParent, err := FindParent(src, path...)
  22. if err != nil {
  23. return nil, err
  24. }
  25. var dst []byte
  26. if nodeParent != nil {
  27. dst, err = AddOrReplace(src, key, value, nodeParent)
  28. } else {
  29. dst, err = AddToEnd(src, key, value, path...)
  30. }
  31. if err = yaml.Unmarshal(dst, map[string]any{}); err != nil {
  32. return nil, err
  33. }
  34. return dst, nil
  35. }
  36. // FindParent - return YAML Node from path of keys (tree)
  37. func FindParent(src []byte, path ...string) (*yaml.Node, error) {
  38. if len(src) == 0 {
  39. return nil, nil
  40. }
  41. var root yaml.Node
  42. if err := yaml.Unmarshal(src, &root); err != nil {
  43. return nil, err
  44. }
  45. if root.Content == nil {
  46. return nil, nil
  47. }
  48. parent := root.Content[0] // yaml.DocumentNode
  49. for _, name := range path {
  50. if parent == nil {
  51. break
  52. }
  53. _, parent = FindChild(parent, name)
  54. }
  55. return parent, nil
  56. }
  57. // FindChild - search and return YAML key/value pair for current Node
  58. func FindChild(node *yaml.Node, name string) (key, value *yaml.Node) {
  59. for i, child := range node.Content {
  60. if child.Value != name {
  61. continue
  62. }
  63. return child, node.Content[i+1]
  64. }
  65. return nil, nil
  66. }
  67. func FirstChild(node *yaml.Node) *yaml.Node {
  68. if node.Content == nil {
  69. return node
  70. }
  71. return node.Content[0]
  72. }
  73. func LastChild(node *yaml.Node) *yaml.Node {
  74. if node.Content == nil {
  75. return node
  76. }
  77. return LastChild(node.Content[len(node.Content)-1])
  78. }
  79. func AddOrReplace(src []byte, key string, value any, nodeParent *yaml.Node) ([]byte, error) {
  80. v := map[string]any{key: value}
  81. put, err := Encode(v, 2)
  82. if err != nil {
  83. return nil, err
  84. }
  85. if nodeKey, nodeValue := FindChild(nodeParent, key); nodeKey != nil {
  86. put = AddIndent(put, nodeKey.Column-1)
  87. i0 := LineOffset(src, nodeKey.Line)
  88. i1 := LineOffset(src, LastChild(nodeValue).Line+1)
  89. if i1 < 0 { // no new line on the end of file
  90. if value != nil {
  91. return append(src[:i0], put...), nil
  92. }
  93. return src[:i0], nil
  94. }
  95. dst := make([]byte, 0, len(src)+len(put))
  96. dst = append(dst, src[:i0]...)
  97. if value != nil {
  98. dst = append(dst, put...)
  99. }
  100. return append(dst, src[i1:]...), nil
  101. }
  102. put = AddIndent(put, FirstChild(nodeParent).Column-1)
  103. i := LineOffset(src, LastChild(nodeParent).Line+1)
  104. if i < 0 { // no new line on the end of file
  105. src = append(src, '\n')
  106. if value != nil {
  107. src = append(src, put...)
  108. }
  109. return src, nil
  110. }
  111. dst := make([]byte, 0, len(src)+len(put))
  112. dst = append(dst, src[:i]...)
  113. if value != nil {
  114. dst = append(dst, put...)
  115. }
  116. return append(dst, src[i:]...), nil
  117. }
  118. func AddToEnd(src []byte, key string, value any, path ...string) ([]byte, error) {
  119. if len(path) > 1 || value == nil {
  120. return nil, errors.New("config: path not exist")
  121. }
  122. v := map[string]map[string]any{
  123. path[0]: {key: value},
  124. }
  125. put, err := Encode(v, 2)
  126. if err != nil {
  127. return nil, err
  128. }
  129. dst := make([]byte, 0, len(src)+len(put)+10)
  130. dst = append(dst, src...)
  131. if l := len(src); l > 0 && src[l-1] != '\n' {
  132. dst = append(dst, '\n')
  133. }
  134. return append(dst, put...), nil
  135. }
  136. func AddPrefix(src, pre []byte) (dst []byte) {
  137. for len(src) > 0 {
  138. dst = append(dst, pre...)
  139. i := bytes.IndexByte(src, '\n') + 1
  140. if i == 0 {
  141. dst = append(dst, src...)
  142. break
  143. }
  144. dst = append(dst, src[:i]...)
  145. src = src[i:]
  146. }
  147. return
  148. }
  149. func AddIndent(src []byte, indent int) (dst []byte) {
  150. pre := make([]byte, indent)
  151. for i := 0; i < indent; i++ {
  152. pre[i] = ' '
  153. }
  154. return AddPrefix(src, pre)
  155. }
  156. func LineOffset(b []byte, line int) (offset int) {
  157. for l := 1; ; l++ {
  158. if l == line {
  159. return offset
  160. }
  161. i := bytes.IndexByte(b[offset:], '\n') + 1
  162. if i == 0 {
  163. break
  164. }
  165. offset += i
  166. }
  167. return -1
  168. }