new config infrastructure

- adds config get and config set commands
- supports arbitrary k/v strings set at top and host level
- supports writing an updated config, preserving comments
- supports mostly lazy evaluation of yaml
This commit is contained in:
vilmibm 2020-04-14 17:05:44 -05:00
parent 82bd7b97cf
commit a325db3051
15 changed files with 858 additions and 150 deletions

View file

@ -10,57 +10,159 @@ import (
"gopkg.in/yaml.v3"
)
const defaultHostname = "github.com"
type configEntry struct {
User string
Token string `yaml:"oauth_token"`
}
func parseOrSetupConfigFile(fn string) (*configEntry, error) {
entry, err := parseConfigFile(fn)
func parseOrSetupConfigFile(fn string) (Config, error) {
config, err := ParseConfig(fn)
if err != nil && errors.Is(err, os.ErrNotExist) {
return setupConfigFile(fn)
}
return entry, err
return config, err
}
func parseConfigFile(fn string) (*configEntry, error) {
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()
return parseConfig(f)
}
// ParseDefaultConfig reads the configuration file
func ParseDefaultConfig() (*configEntry, error) {
return parseConfigFile(configFile())
}
func parseConfig(r io.Reader) (*configEntry, error) {
data, err := ioutil.ReadAll(r)
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
var config yaml.Node
err = yaml.Unmarshal(data, &config)
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 nil, err
return err
}
if len(config.Content) < 1 {
return nil, fmt.Errorf("malformed config")
defer cfgFile.Close()
n, err := cfgFile.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
for i := 0; i < len(config.Content[0].Content)-1; i = i + 2 {
if config.Content[0].Content[i].Value == defaultHostname {
var entries []configEntry
err = config.Content[0].Content[i+1].Decode(&entries)
if err != nil {
return nil, err
}
return &entries[0], nil
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 nil, fmt.Errorf("could not find config entry for %q", defaultHostname)
return true
}
func migrateConfig(fn string, root *yaml.Node) error {
// TODO i'm sorry
newConfigData := map[string]map[string]map[string]string{}
newConfigData["hosts"] = map[string]map[string]string{}
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] = map[string]string{}
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: %s", 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: %s", err)
}
}
return NewConfig(root), nil
}