upgradehelper.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
  2. // Copyright (C) 2021 Tulir Asokan
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package config
  17. import (
  18. "fmt"
  19. "os"
  20. "strings"
  21. "gopkg.in/yaml.v3"
  22. )
  23. type YAMLMap map[string]YAMLNode
  24. type YAMLList []YAMLNode
  25. type YAMLNode struct {
  26. *yaml.Node
  27. Map YAMLMap
  28. List YAMLList
  29. }
  30. type YAMLType uint32
  31. const (
  32. Null YAMLType = 1 << iota
  33. Bool
  34. Str
  35. Int
  36. Float
  37. Timestamp
  38. List
  39. Map
  40. Binary
  41. )
  42. func (t YAMLType) String() string {
  43. switch t {
  44. case Null:
  45. return NullTag
  46. case Bool:
  47. return BoolTag
  48. case Str:
  49. return StrTag
  50. case Int:
  51. return IntTag
  52. case Float:
  53. return FloatTag
  54. case Timestamp:
  55. return TimestampTag
  56. case List:
  57. return SeqTag
  58. case Map:
  59. return MapTag
  60. case Binary:
  61. return BinaryTag
  62. default:
  63. panic(fmt.Errorf("can't convert type %d to string", t))
  64. }
  65. }
  66. func tagToType(tag string) YAMLType {
  67. switch tag {
  68. case NullTag:
  69. return Null
  70. case BoolTag:
  71. return Bool
  72. case StrTag:
  73. return Str
  74. case IntTag:
  75. return Int
  76. case FloatTag:
  77. return Float
  78. case TimestampTag:
  79. return Timestamp
  80. case SeqTag:
  81. return List
  82. case MapTag:
  83. return Map
  84. case BinaryTag:
  85. return Binary
  86. default:
  87. return 0
  88. }
  89. }
  90. const (
  91. NullTag = "!!null"
  92. BoolTag = "!!bool"
  93. StrTag = "!!str"
  94. IntTag = "!!int"
  95. FloatTag = "!!float"
  96. TimestampTag = "!!timestamp"
  97. SeqTag = "!!seq"
  98. MapTag = "!!map"
  99. BinaryTag = "!!binary"
  100. )
  101. func fromNode(node *yaml.Node) YAMLNode {
  102. switch node.Kind {
  103. case yaml.DocumentNode:
  104. return fromNode(node.Content[0])
  105. case yaml.AliasNode:
  106. return fromNode(node.Alias)
  107. case yaml.MappingNode:
  108. return YAMLNode{
  109. Node: node,
  110. Map: parseYAMLMap(node),
  111. }
  112. case yaml.SequenceNode:
  113. return YAMLNode{
  114. Node: node,
  115. List: parseYAMLList(node),
  116. }
  117. default:
  118. return YAMLNode{Node: node}
  119. }
  120. }
  121. func (yn *YAMLNode) toNode() *yaml.Node {
  122. switch {
  123. case yn.Map != nil && yn.Node.Kind == yaml.MappingNode:
  124. yn.Content = yn.Map.toNodes()
  125. case yn.List != nil && yn.Node.Kind == yaml.SequenceNode:
  126. yn.Content = yn.List.toNodes()
  127. }
  128. return yn.Node
  129. }
  130. func parseYAMLList(node *yaml.Node) YAMLList {
  131. data := make(YAMLList, len(node.Content))
  132. for i, item := range node.Content {
  133. data[i] = fromNode(item)
  134. }
  135. return data
  136. }
  137. func (yl YAMLList) toNodes() []*yaml.Node {
  138. nodes := make([]*yaml.Node, len(yl))
  139. for i, item := range yl {
  140. nodes[i] = item.toNode()
  141. }
  142. return nodes
  143. }
  144. func parseYAMLMap(node *yaml.Node) YAMLMap {
  145. if len(node.Content)%2 != 0 {
  146. panic(fmt.Errorf("uneven number of items in YAML map (%d)", len(node.Content)))
  147. }
  148. data := make(YAMLMap, len(node.Content)/2)
  149. for i := 0; i < len(node.Content); i += 2 {
  150. key := node.Content[i]
  151. value := node.Content[i+1]
  152. if key.Kind == yaml.ScalarNode {
  153. data[key.Value] = fromNode(value)
  154. }
  155. }
  156. return data
  157. }
  158. func (ym YAMLMap) toNodes() []*yaml.Node {
  159. nodes := make([]*yaml.Node, len(ym)*2)
  160. i := 0
  161. for key, value := range ym {
  162. nodes[i] = makeStringNode(key)
  163. nodes[i+1] = value.toNode()
  164. i += 2
  165. }
  166. return nodes
  167. }
  168. func makeStringNode(val string) *yaml.Node {
  169. var node yaml.Node
  170. node.SetString(val)
  171. return &node
  172. }
  173. type UpgradeHelper struct {
  174. base YAMLNode
  175. cfg YAMLNode
  176. }
  177. func NewUpgradeHelper(base, cfg *yaml.Node) *UpgradeHelper {
  178. return &UpgradeHelper{
  179. base: fromNode(base),
  180. cfg: fromNode(cfg),
  181. }
  182. }
  183. func (helper *UpgradeHelper) Copy(allowedTypes YAMLType, path ...string) {
  184. base, cfg := helper.base, helper.cfg
  185. var ok bool
  186. for _, item := range path {
  187. base = base.Map[item]
  188. cfg, ok = cfg.Map[item]
  189. if !ok {
  190. return
  191. }
  192. }
  193. if allowedTypes&tagToType(cfg.Tag) == 0 {
  194. _, _ = fmt.Fprintf(os.Stderr, "Ignoring incorrect config field type %s at %s\n", cfg.Tag, strings.Join(path, "->"))
  195. return
  196. }
  197. base.Tag = cfg.Tag
  198. base.Style = cfg.Style
  199. switch base.Kind {
  200. case yaml.ScalarNode:
  201. base.Value = cfg.Value
  202. case yaml.SequenceNode, yaml.MappingNode:
  203. base.Content = cfg.Content
  204. }
  205. }
  206. func getNode(cfg YAMLNode, path []string) *YAMLNode {
  207. var ok bool
  208. for _, item := range path {
  209. cfg, ok = cfg.Map[item]
  210. if !ok {
  211. return nil
  212. }
  213. }
  214. return &cfg
  215. }
  216. func (helper *UpgradeHelper) GetNode(path ...string) *YAMLNode {
  217. return getNode(helper.cfg, path)
  218. }
  219. func (helper *UpgradeHelper) GetBaseNode(path ...string) *YAMLNode {
  220. return getNode(helper.base, path)
  221. }
  222. func (helper *UpgradeHelper) Get(tag YAMLType, path ...string) (string, bool) {
  223. node := helper.GetNode(path...)
  224. if node == nil || node.Kind != yaml.ScalarNode || tag&tagToType(node.Tag) == 0 {
  225. return "", false
  226. }
  227. return node.Value, true
  228. }
  229. func (helper *UpgradeHelper) GetBase(path ...string) string {
  230. return helper.GetBaseNode(path...).Value
  231. }
  232. func (helper *UpgradeHelper) Set(tag YAMLType, value string, path ...string) {
  233. base := helper.base
  234. for _, item := range path {
  235. base = base.Map[item]
  236. }
  237. base.Tag = tag.String()
  238. base.Value = value
  239. }
  240. func (helper *UpgradeHelper) SetMap(value YAMLMap, path ...string) {
  241. base := helper.base
  242. for _, item := range path {
  243. base = base.Map[item]
  244. }
  245. if base.Tag != MapTag || base.Kind != yaml.MappingNode {
  246. panic(fmt.Errorf("invalid target for SetMap(%+v): tag:%s, kind:%d", path, base.Tag, base.Kind))
  247. }
  248. base.Content = value.toNodes()
  249. }