github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/internal/config/config_map.go (about) 1 package config 2 3 import ( 4 "errors" 5 6 "gopkg.in/yaml.v3" 7 ) 8 9 // This type implements a low-level get/set config that is backed by an in-memory tree of Yaml 10 // nodes. It allows us to interact with a yaml-based config programmatically, preserving any 11 // comments that were present when the yaml was parsed. 12 type ConfigMap struct { 13 Root *yaml.Node 14 } 15 16 type ConfigEntry struct { 17 KeyNode *yaml.Node 18 ValueNode *yaml.Node 19 Index int 20 } 21 22 type NotFoundError struct { 23 error 24 } 25 26 func (cm *ConfigMap) Empty() bool { 27 return cm.Root == nil || len(cm.Root.Content) == 0 28 } 29 30 func (cm *ConfigMap) GetStringValue(key string) (string, error) { 31 entry, err := cm.FindEntry(key) 32 if err != nil { 33 return "", err 34 } 35 return entry.ValueNode.Value, nil 36 } 37 38 func (cm *ConfigMap) SetStringValue(key, value string) error { 39 entry, err := cm.FindEntry(key) 40 41 var notFound *NotFoundError 42 43 valueNode := entry.ValueNode 44 45 if err != nil && errors.As(err, ¬Found) { 46 keyNode := &yaml.Node{ 47 Kind: yaml.ScalarNode, 48 Value: key, 49 } 50 valueNode = &yaml.Node{ 51 Kind: yaml.ScalarNode, 52 Tag: "!!str", 53 Value: "", 54 } 55 56 cm.Root.Content = append(cm.Root.Content, keyNode, valueNode) 57 } else if err != nil { 58 return err 59 } 60 61 valueNode.Value = value 62 63 return nil 64 } 65 66 func (cm *ConfigMap) FindEntry(key string) (ce *ConfigEntry, err error) { 67 err = nil 68 69 ce = &ConfigEntry{} 70 71 // Content slice goes [key1, value1, key2, value2, ...] 72 topLevelPairs := cm.Root.Content 73 for i, v := range topLevelPairs { 74 // Skip every other slice item since we only want to check against keys 75 if i%2 != 0 { 76 continue 77 } 78 if v.Value == key { 79 ce.KeyNode = v 80 ce.Index = i 81 if i+1 < len(topLevelPairs) { 82 ce.ValueNode = topLevelPairs[i+1] 83 } 84 return 85 } 86 } 87 88 return ce, &NotFoundError{errors.New("not found")} 89 } 90 91 func (cm *ConfigMap) RemoveEntry(key string) { 92 newContent := []*yaml.Node{} 93 94 content := cm.Root.Content 95 for i := 0; i < len(content); i++ { 96 if content[i].Value == key { 97 i++ // skip the next node which is this key's value 98 } else { 99 newContent = append(newContent, content[i]) 100 } 101 } 102 103 cm.Root.Content = newContent 104 }