github.com/goatapp/phraseapp-go@v3.0.0+incompatible/phraseapp/config.go (about) 1 package phraseapp 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 9 "gopkg.in/yaml.v2" 10 ) 11 12 // Config contains all information from a .phraseapp.yml config file 13 type Config struct { 14 Credentials 15 Debug bool `cli:"opt --verbose -v desc='Verbose output'"` 16 17 Page *int 18 PerPage *int 19 20 DefaultProjectID string 21 DefaultFileFormat string 22 23 Defaults map[string]map[string]interface{} 24 25 Targets []byte 26 Sources []byte 27 } 28 29 const configName = ".phraseapp.yml" 30 31 // ReadConfig reads a .phraseapp.yml config file 32 func ReadConfig() (*Config, error) { 33 cfg := &Config{} 34 rawCfg := struct{ PhraseApp *Config }{PhraseApp: cfg} 35 36 content, err := configContent() 37 switch { 38 case err != nil: 39 return nil, err 40 case content == nil: 41 return cfg, nil 42 default: 43 return cfg, yaml.Unmarshal(content, rawCfg) 44 } 45 } 46 47 func configContent() ([]byte, error) { 48 path, err := configPath() 49 switch { 50 case err != nil: 51 return nil, err 52 case path == "": 53 return nil, nil 54 default: 55 return ioutil.ReadFile(path) 56 } 57 } 58 59 func configPath() (string, error) { 60 if possiblePath := os.Getenv("PHRASEAPP_CONFIG"); possiblePath != "" { 61 _, err := os.Stat(possiblePath) 62 if err == nil { 63 return possiblePath, nil 64 } 65 66 if os.IsNotExist(err) { 67 err = fmt.Errorf("file %q (from PHRASEAPP_CONFIG environment variable) doesn't exist", possiblePath) 68 } 69 70 return "", err 71 } 72 73 workingDir, err := os.Getwd() 74 if err != nil { 75 return "", nil 76 } 77 78 possiblePath := filepath.Join(workingDir, configName) 79 if _, err := os.Stat(possiblePath); err == nil { 80 return possiblePath, nil 81 } 82 83 possiblePath = defaultConfigDir() 84 if _, err := os.Stat(possiblePath); err != nil { 85 return "", nil 86 } 87 88 return possiblePath, nil 89 } 90 91 func (cfg *Config) UnmarshalYAML(unmarshal func(i interface{}) error) error { 92 m := map[string]interface{}{} 93 err := ParseYAMLToMap(unmarshal, map[string]interface{}{ 94 "access_token": &cfg.Credentials.Token, 95 "host": &cfg.Credentials.Host, 96 "debug": &cfg.Debug, 97 "page": &cfg.Page, 98 "perpage": &cfg.PerPage, 99 "project_id": &cfg.DefaultProjectID, 100 "file_format": &cfg.DefaultFileFormat, 101 "push": &cfg.Sources, 102 "pull": &cfg.Targets, 103 "defaults": &m, 104 }) 105 if err != nil { 106 return err 107 } 108 109 cfg.Defaults = map[string]map[string]interface{}{} 110 for path, rawConfig := range m { 111 cfg.Defaults[path], err = ValidateIsRawMap("defaults."+path, rawConfig) 112 if err != nil { 113 return err 114 } 115 } 116 117 return nil 118 } 119 120 const cfgValueErrStr = "configuration key %q has invalid value: %T\nsee https://phraseapp.com/docs/developers/cli/configuration/" 121 const cfgKeyErrStr = "configuration key %q has invalid type: %T\nsee https://phraseapp.com/docs/developers/cli/configuration/" 122 const cfgInvalidKeyErrStr = "configuration key %q unknown\nsee https://phraseapp.com/docs/developers/cli/configuration/" 123 124 func ValidateIsString(k string, v interface{}) (string, error) { 125 s, ok := v.(string) 126 if !ok { 127 return "", fmt.Errorf(cfgValueErrStr, k, v) 128 } 129 return s, nil 130 } 131 132 func ValidateIsBool(k string, v interface{}) (bool, error) { 133 b, ok := v.(bool) 134 if !ok { 135 return false, fmt.Errorf(cfgValueErrStr, k, v) 136 } 137 return b, nil 138 } 139 140 func ValidateIsInt(k string, v interface{}) (int, error) { 141 i, ok := v.(int) 142 if !ok { 143 return 0, fmt.Errorf(cfgValueErrStr, k, v) 144 } 145 return i, nil 146 } 147 148 func ValidateIsRawMap(k string, v interface{}) (map[string]interface{}, error) { 149 raw, ok := v.(map[interface{}]interface{}) 150 if !ok { 151 return nil, fmt.Errorf(cfgValueErrStr, k, v) 152 } 153 154 ps := map[string]interface{}{} 155 for mk, mv := range raw { 156 s, ok := mk.(string) 157 if !ok { 158 return nil, fmt.Errorf(cfgKeyErrStr, fmt.Sprintf("%s.%v", k, mk), mk) 159 } 160 ps[s] = mv 161 } 162 return ps, nil 163 } 164 165 func ConvertToStringMap(raw map[string]interface{}) (map[string]string, error) { 166 ps := map[string]string{} 167 for mk, mv := range raw { 168 switch v := mv.(type) { 169 case string: 170 ps[mk] = v 171 case bool: 172 ps[mk] = fmt.Sprintf("%t", v) 173 case int: 174 ps[mk] = fmt.Sprintf("%d", v) 175 default: 176 return nil, fmt.Errorf("invalid type of key %q: %T", mk, mv) 177 } 178 } 179 return ps, nil 180 } 181 182 // Calls the YAML parser function (see yaml.v2/Unmarshaler interface) with a map 183 // of string to interface. This map is then iterated to match against the given 184 // map of keys to fields, validates the type and sets the fields accordingly. 185 func ParseYAMLToMap(unmarshal func(interface{}) error, keysToField map[string]interface{}) error { 186 m := map[string]interface{}{} 187 if err := unmarshal(m); err != nil { 188 return err 189 } 190 191 var err error 192 for k, v := range m { 193 value, found := keysToField[k] 194 if !found { 195 return fmt.Errorf(cfgInvalidKeyErrStr, k) 196 } 197 198 switch val := value.(type) { 199 case *string: 200 *val, err = ValidateIsString(k, v) 201 case *int: 202 *val, err = ValidateIsInt(k, v) 203 case **int: 204 *val = new(int) 205 **val, err = ValidateIsInt(k, v) 206 case *bool: 207 *val, err = ValidateIsBool(k, v) 208 case *map[string]interface{}: 209 *val, err = ValidateIsRawMap(k, v) 210 case *[]byte: 211 *val, err = yaml.Marshal(v) 212 default: 213 err = fmt.Errorf(cfgValueErrStr, k, value) 214 } 215 if err != nil { 216 return err 217 } 218 } 219 220 return nil 221 }