github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/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 // ConfigHierarchy is a hierarchical read-only configuration store. When a key is looked up in the ConfigHierarchy it 27 // will go through its configs in order and will return the first value for a given key that is found. Configs are 28 // iterated in order, so the configurations added first have the highest priority. 29 type ConfigHierarchy struct { 30 configs []ReadWriteConfig 31 nameToConfig map[string]ReadWriteConfig 32 } 33 34 // NewConfigHierarchy creates an empty ConfigurationHierarchy 35 func NewConfigHierarchy() *ConfigHierarchy { 36 return &ConfigHierarchy{[]ReadWriteConfig{}, map[string]ReadWriteConfig{}} 37 } 38 39 // AddConfig adds a ReadWriteConfig to the hierarchy. Newly added configs are at a lower priority than the configs that 40 // were added previously. Though the ConfigHierarchy does not support modification of stored values in the configs 41 // directly, the configs it manages must implement the WriteConfig interface. 42 func (ch *ConfigHierarchy) AddConfig(name string, cs ReadWriteConfig) { 43 name = strings.TrimSpace(strings.ToLower(name)) 44 45 if _, ok := ch.nameToConfig[name]; ok { 46 panic("Adding 2 configs with the same name is not a valid operation.") 47 } 48 49 ch.configs = append(ch.configs, cs) 50 ch.nameToConfig[name] = cs 51 } 52 53 // GetConfig retrieves a config by name. ReadWriteConfig instances can be modified 54 func (ch *ConfigHierarchy) GetConfig(name string) (ReadWriteConfig, bool) { 55 name = strings.TrimSpace(strings.ToLower(name)) 56 57 cs, ok := ch.nameToConfig[name] 58 return cs, ok 59 } 60 61 // GetString iterates through all configs in the order they were added looking for a given key. The first valid value 62 // found will be returned. 63 func (ch *ConfigHierarchy) GetString(k string) (string, error) { 64 ns, paramName := splitParamName(k) 65 66 if ns != "" { 67 if cfg, ok := ch.nameToConfig[ns]; ok { 68 return cfg.GetString(paramName) 69 } else { 70 return "", errors.New("Hierarchy does not have a config named " + ns) 71 } 72 } 73 74 for _, cs := range ch.configs { 75 val, err := cs.GetString(k) 76 77 if err != nil && err != ErrConfigParamNotFound { 78 return "", err 79 } else if err == nil { 80 return val, nil 81 } 82 } 83 84 return "", ErrConfigParamNotFound 85 } 86 87 // SetStrings will set the value of configuration parameters in memory, and persist any changes to the backing file. 88 // For ConfigHierarchies update parameter names must be of the format config_name::param_name 89 func (ch *ConfigHierarchy) SetStrings(updates map[string]string) error { 90 namespacedUpdates := make(map[string]map[string]string) 91 for cfgName := range ch.nameToConfig { 92 namespacedUpdates[cfgName] = make(map[string]string) 93 } 94 95 for k, v := range updates { 96 ns, paramName := splitParamName(k) 97 98 if ns == "" { 99 // panicing in cases where developers have used this function incorrectly 100 panic("Calls to SetStrings for a ConfigHierarchy must include the config name. " + k + " is not in the format config_name::param_name") 101 } 102 103 if _, ok := namespacedUpdates[ns]; !ok { 104 return errors.New(ns + " is not a known config in this hierarchy.") 105 } else { 106 namespacedUpdates[ns][paramName] = v 107 } 108 } 109 110 for ns, updates := range namespacedUpdates { 111 err := ch.nameToConfig[ns].SetStrings(updates) 112 113 if err != nil { 114 return err 115 } 116 } 117 118 return nil 119 } 120 121 func splitParamName(paramName string) (string, string) { 122 tokens := strings.Split(paramName, namespaceSep) 123 124 nonEmpty := make([]string, 0, len(tokens)) 125 for _, token := range tokens { 126 trimmed := strings.TrimSpace(strings.ToLower(token)) 127 128 if len(trimmed) > 0 { 129 nonEmpty = append(nonEmpty, trimmed) 130 } 131 } 132 133 switch len(nonEmpty) { 134 case 0: 135 return "", "" 136 case 1: 137 return "", nonEmpty[0] 138 default: 139 return nonEmpty[0], strings.Join(nonEmpty[1:], "::") 140 } 141 } 142 143 // Iter will perform a callback for each value in a config until all values have been exhausted or until the 144 // callback returns true indicating that it should stop. For ConfigHierchies, parameter names which are provided 145 // to the callbacks are of the format config_name::param_name 146 func (ch *ConfigHierarchy) Iter(cb func(string, string) (stop bool)) { 147 stop := false 148 for cfgName, cfg := range ch.nameToConfig { 149 cfg.Iter(func(cfgParamName string, cfgParamVal string) bool { 150 stop = cb(cfgName+namespaceSep+cfgParamName, cfgParamVal) 151 return stop 152 }) 153 154 if stop { 155 break 156 } 157 } 158 } 159 160 // Unset removes a configuration parameter from the config 161 func (ch *ConfigHierarchy) Unset(params []string) error { 162 namespacedDeletes := make(map[string][]string) 163 164 for cfgName := range ch.nameToConfig { 165 namespacedDeletes[cfgName] = []string{} 166 } 167 168 for _, param := range params { 169 ns, paramName := splitParamName(param) 170 171 if ns == "" { 172 // panicing in cases where developers have used this function incorrectly 173 panic("Calls to Unset for a ConfigHierarchy must include the config name. " + param + " is not in the format config_name::param_name") 174 } 175 176 if _, ok := namespacedDeletes[ns]; !ok { 177 return errors.New(ns + " is not a known config in this hierarchy.") 178 } else { 179 namespacedDeletes[ns] = append(namespacedDeletes[ns], paramName) 180 } 181 } 182 183 for ns, deletes := range namespacedDeletes { 184 err := ch.nameToConfig[ns].Unset(deletes) 185 186 if err != nil { 187 return err 188 } 189 } 190 191 return nil 192 } 193 194 // Size returns the number of properties contained within the config. For config hierarchy it returns the sum of the 195 // sizes of all elements in the hierarchy. This is the number of elements that would be seen when calling Iter on 196 // the hierarchy. 197 func (ch *ConfigHierarchy) Size() int { 198 size := 0 199 for _, cfg := range ch.configs { 200 size += cfg.Size() 201 } 202 203 return size 204 }