github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/utils/config/config_hierarchy.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "errors" 19 "strings" 20 ) 21 22 const ( 23 namespaceSep = "::" 24 ) 25 26 var ErrUnknownConfig = errors.New("config not found") 27 28 // ConfigHierarchy is a hierarchical read-only configuration store. When a key is looked up in the ConfigHierarchy it 29 // will go through its configs in order and will return the first value for a given key that is found. Configs are 30 // iterated in order, so the configurations added first have the highest priority. 31 type ConfigHierarchy struct { 32 configs []ReadWriteConfig 33 nameToConfig map[string]ReadWriteConfig 34 } 35 36 var _ ReadableConfig = &ConfigHierarchy{} 37 var _ WritableConfig = &ConfigHierarchy{} 38 var _ ReadWriteConfig = &ConfigHierarchy{} 39 40 // NewConfigHierarchy creates an empty ConfigurationHierarchy 41 func NewConfigHierarchy() *ConfigHierarchy { 42 return &ConfigHierarchy{[]ReadWriteConfig{}, map[string]ReadWriteConfig{}} 43 } 44 45 // AddConfig adds a ReadWriteConfig to the hierarchy. Newly added configs are at a lower priority than the configs that 46 // were added previously. Though the ConfigHierarchy does not support modification of stored values in the configs 47 // directly, the configs it manages must implement the WritableConfig interface. 48 func (ch *ConfigHierarchy) AddConfig(name string, cs ReadWriteConfig) { 49 name = strings.TrimSpace(strings.ToLower(name)) 50 51 if _, ok := ch.nameToConfig[name]; ok { 52 panic("Adding 2 configs with the same name is not a valid operation.") 53 } 54 55 ch.configs = append(ch.configs, cs) 56 ch.nameToConfig[name] = cs 57 } 58 59 // GetConfig retrieves a config by name. ReadWriteConfig instances can be modified 60 func (ch *ConfigHierarchy) GetConfig(name string) (ReadWriteConfig, bool) { 61 name = strings.TrimSpace(strings.ToLower(name)) 62 63 cs, ok := ch.nameToConfig[name] 64 return cs, ok 65 } 66 67 // GetString iterates through all configs in the order they were added looking for a given key. The first valid value 68 // found will be returned. 69 func (ch *ConfigHierarchy) GetString(k string) (string, error) { 70 ns, paramName := splitParamName(k) 71 72 if ns != "" { 73 if cfg, ok := ch.nameToConfig[ns]; ok { 74 return cfg.GetString(paramName) 75 } else { 76 return "", errors.New("Hierarchy does not have a config named " + ns) 77 } 78 } 79 80 for _, cs := range ch.configs { 81 val, err := cs.GetString(k) 82 83 if err != nil && err != ErrConfigParamNotFound { 84 return "", err 85 } else if err == nil { 86 return val, nil 87 } 88 } 89 90 return "", ErrConfigParamNotFound 91 } 92 93 func (ch *ConfigHierarchy) GetStringOrDefault(key, defStr string) string { 94 if val, err := ch.GetString(key); err == nil { 95 return val 96 } 97 return defStr 98 } 99 100 // SetStrings will set the value of configuration parameters in memory, and persist any changes to the backing file. 101 // For ConfigHierarchies update parameter names must be of the format config_name::param_name 102 func (ch *ConfigHierarchy) SetStrings(updates map[string]string) error { 103 namespacedUpdates := make(map[string]map[string]string) 104 for cfgName := range ch.nameToConfig { 105 namespacedUpdates[cfgName] = make(map[string]string) 106 } 107 108 for k, v := range updates { 109 ns, paramName := splitParamName(k) 110 111 if ns == "" { 112 // panicing in cases where developers have used this function incorrectly 113 panic("Calls to SetStrings for a ConfigHierarchy must include the config name. " + k + " is not in the format config_name::param_name") 114 } 115 116 if _, ok := namespacedUpdates[ns]; !ok { 117 return errors.New(ns + " is not a known config in this hierarchy.") 118 } else { 119 namespacedUpdates[ns][paramName] = v 120 } 121 } 122 123 for ns, updates := range namespacedUpdates { 124 err := ch.nameToConfig[ns].SetStrings(updates) 125 126 if err != nil { 127 return err 128 } 129 } 130 131 return nil 132 } 133 134 func splitParamName(paramName string) (string, string) { 135 tokens := strings.Split(paramName, namespaceSep) 136 137 nonEmpty := make([]string, 0, len(tokens)) 138 for _, token := range tokens { 139 trimmed := strings.TrimSpace(strings.ToLower(token)) 140 141 if len(trimmed) > 0 { 142 nonEmpty = append(nonEmpty, trimmed) 143 } 144 } 145 146 switch len(nonEmpty) { 147 case 0: 148 return "", "" 149 case 1: 150 return "", nonEmpty[0] 151 default: 152 return nonEmpty[0], strings.Join(nonEmpty[1:], "::") 153 } 154 } 155 156 // Iter will perform a callback for each value in a config until all values have been exhausted or until the 157 // callback returns true indicating that it should stop. For ConfigHierchies, parameter names which are provided 158 // to the callbacks are of the format config_name::param_name 159 func (ch *ConfigHierarchy) Iter(cb func(string, string) (stop bool)) { 160 stop := false 161 for cfgName, cfg := range ch.nameToConfig { 162 cfg.Iter(func(cfgParamName string, cfgParamVal string) bool { 163 stop = cb(cfgName+namespaceSep+cfgParamName, cfgParamVal) 164 return stop 165 }) 166 167 if stop { 168 break 169 } 170 } 171 } 172 173 // Unset removes a configuration parameter from the config 174 func (ch *ConfigHierarchy) Unset(params []string) error { 175 namespacedDeletes := make(map[string][]string) 176 177 for cfgName := range ch.nameToConfig { 178 namespacedDeletes[cfgName] = []string{} 179 } 180 181 for _, param := range params { 182 ns, paramName := splitParamName(param) 183 184 if ns == "" { 185 // panicing in cases where developers have used this function incorrectly 186 panic("Calls to Unset for a ConfigHierarchy must include the config name. " + param + " is not in the format config_name::param_name") 187 } 188 189 if _, ok := namespacedDeletes[ns]; !ok { 190 return errors.New(ns + " is not a known config in this hierarchy.") 191 } else { 192 namespacedDeletes[ns] = append(namespacedDeletes[ns], paramName) 193 } 194 } 195 196 for ns, deletes := range namespacedDeletes { 197 err := ch.nameToConfig[ns].Unset(deletes) 198 199 if err != nil { 200 return err 201 } 202 } 203 204 return nil 205 } 206 207 // Size returns the number of properties contained within the config. For config hierarchy it returns the sum of the 208 // sizes of all elements in the hierarchy. This is the number of elements that would be seen when calling Iter on 209 // the hierarchy. 210 func (ch *ConfigHierarchy) Size() int { 211 size := 0 212 for _, cfg := range ch.configs { 213 size += cfg.Size() 214 } 215 216 return size 217 }