package config import ( "errors" "fmt" "io" "io/ioutil" "os" "path" "github.com/mitchellh/go-homedir" "gopkg.in/yaml.v3" ) func ConfigDir() string { dir, _ := homedir.Expand("~/.config/gh") return dir } func ConfigFile() string { return path.Join(ConfigDir(), "config.yml") } func ParseOrSetupConfigFile(fn string) (Config, error) { config, err := ParseConfig(fn) if err != nil && errors.Is(err, os.ErrNotExist) { return setupConfigFile(fn) } return config, err } func ParseDefaultConfig() (Config, error) { return ParseConfig(ConfigFile()) } var ReadConfigFile = func(fn string) ([]byte, error) { f, err := os.Open(fn) if err != nil { return nil, err } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return nil, err } return data, nil } var WriteConfigFile = func(fn string, data []byte) error { cfgFile, err := os.OpenFile(ConfigFile(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) // cargo coded from setup if err != nil { return err } defer cfgFile.Close() n, err := cfgFile.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } return err } var BackupConfigFile = func(fn string) error { return os.Rename(fn, fn+".bak") } func parseConfigFile(fn string) ([]byte, *yaml.Node, error) { data, err := ReadConfigFile(fn) if err != nil { return nil, nil, err } var root yaml.Node err = yaml.Unmarshal(data, &root) if err != nil { return data, nil, err } if len(root.Content) < 1 { return data, &root, fmt.Errorf("malformed config") } if root.Content[0].Kind != yaml.MappingNode { return data, &root, fmt.Errorf("expected a top level map") } return data, &root, nil } func isLegacy(root *yaml.Node) bool { for _, v := range root.Content[0].Content { if v.Value == "hosts" { return false } } return true } func migrateConfig(fn string, root *yaml.Node) error { type ConfigEntry map[string]string type ConfigHash map[string]ConfigEntry newConfigData := map[string]ConfigHash{} newConfigData["hosts"] = ConfigHash{} topLevelKeys := root.Content[0].Content for i, x := range topLevelKeys { if x.Value == "" { continue } if i+1 == len(topLevelKeys) { break } hostname := x.Value newConfigData["hosts"][hostname] = ConfigEntry{} authKeys := topLevelKeys[i+1].Content[0].Content for j, y := range authKeys { if j+1 == len(authKeys) { break } switch y.Value { case "user": newConfigData["hosts"][hostname]["user"] = authKeys[j+1].Value case "oauth_token": newConfigData["hosts"][hostname]["oauth_token"] = authKeys[j+1].Value } } } if _, ok := newConfigData["hosts"][defaultHostname]; !ok { return errors.New("could not find default host configuration") } defaultHostConfig := newConfigData["hosts"][defaultHostname] if _, ok := defaultHostConfig["user"]; !ok { return errors.New("default host configuration missing user") } if _, ok := defaultHostConfig["oauth_token"]; !ok { return errors.New("default host configuration missing oauth_token") } newConfig, err := yaml.Marshal(newConfigData) if err != nil { return err } err = BackupConfigFile(fn) if err != nil { return fmt.Errorf("failed to back up existing config: %w", err) } return WriteConfigFile(fn, newConfig) } func ParseConfig(fn string) (Config, error) { _, root, err := parseConfigFile(fn) if err != nil { return nil, err } if isLegacy(root) { err = migrateConfig(fn, root) if err != nil { return nil, err } _, root, err = parseConfigFile(fn) if err != nil { return nil, fmt.Errorf("failed to reparse migrated config: %w", err) } } return NewConfig(root), nil }