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  }