cli/internal/config/config_file.go
2020-04-20 13:57:16 -05:00

181 lines
3.6 KiB
Go

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
}