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:
parent
82bd7b97cf
commit
a325db3051
15 changed files with 858 additions and 150 deletions
205
context/config_type.go
Normal file
205
context/config_type.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const defaultHostname = "github.com"
|
||||
const defaultGitProtocol = "https"
|
||||
|
||||
// This interface describes interacting with some persistent configuration for gh.
|
||||
type Config interface {
|
||||
Hosts() ([]*HostConfig, error)
|
||||
Get(string, string) (string, error)
|
||||
Set(string, string, string) error
|
||||
Write() error
|
||||
configForHost(string) (*HostConfig, error)
|
||||
parseHosts(*yaml.Node) ([]*HostConfig, error)
|
||||
}
|
||||
|
||||
type NotFoundError struct {
|
||||
error
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
ConfigMap
|
||||
Host string
|
||||
}
|
||||
|
||||
// This type implements a low-level get/set config that is backed by an in-memory tree of Yaml
|
||||
// nodes. It allows us to interact with a yaml-based config programmatically, preserving any
|
||||
// comments that were present when the yaml waas parsed.
|
||||
type ConfigMap struct {
|
||||
Root *yaml.Node
|
||||
}
|
||||
|
||||
func (cm *ConfigMap) GetStringValue(key string) (string, error) {
|
||||
_, valueNode, err := cm.FindEntry(key)
|
||||
var notFound *NotFoundError
|
||||
|
||||
if err != nil && errors.As(err, ¬Found) {
|
||||
return defaultFor(key), nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if valueNode.Value == "" {
|
||||
return defaultFor(key), nil
|
||||
}
|
||||
|
||||
return valueNode.Value, nil
|
||||
}
|
||||
|
||||
func (cm *ConfigMap) SetStringValue(key, value string) error {
|
||||
_, valueNode, err := cm.FindEntry(key)
|
||||
|
||||
var notFound *NotFoundError
|
||||
|
||||
if err != nil && errors.As(err, ¬Found) {
|
||||
keyNode := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: key,
|
||||
}
|
||||
valueNode = &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "",
|
||||
}
|
||||
|
||||
cm.Root.Content = append(cm.Root.Content, keyNode, valueNode)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valueNode.Value = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *ConfigMap) FindEntry(key string) (keyNode, valueNode *yaml.Node, err error) {
|
||||
err = nil
|
||||
|
||||
topLevelKeys := cm.Root.Content
|
||||
for i, v := range topLevelKeys {
|
||||
if v.Value == key && i+1 < len(topLevelKeys) {
|
||||
keyNode = v
|
||||
valueNode = topLevelKeys[i+1]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, &NotFoundError{errors.New("not found")}
|
||||
}
|
||||
|
||||
func NewConfig(root *yaml.Node) Config {
|
||||
return &fileConfig{
|
||||
ConfigMap: ConfigMap{Root: root.Content[0]},
|
||||
documentRoot: root,
|
||||
}
|
||||
}
|
||||
|
||||
// This type implements a Config interface and represents a config file on disk.
|
||||
type fileConfig struct {
|
||||
ConfigMap
|
||||
documentRoot *yaml.Node
|
||||
hosts []*HostConfig
|
||||
}
|
||||
|
||||
func (c *fileConfig) Get(hostname, key string) (string, error) {
|
||||
if hostname == "" {
|
||||
return c.GetStringValue(key)
|
||||
} else {
|
||||
hostCfg, err := c.configForHost(hostname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hostCfg.GetStringValue(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *fileConfig) Set(hostname, key, value string) error {
|
||||
if hostname == "" {
|
||||
return c.SetStringValue(key, value)
|
||||
} else {
|
||||
hostCfg, err := c.configForHost(hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hostCfg.SetStringValue(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *fileConfig) configForHost(hostname string) (*HostConfig, error) {
|
||||
hosts, err := c.Hosts()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse hosts config: %s", err)
|
||||
}
|
||||
|
||||
for _, hc := range hosts {
|
||||
if hc.Host == hostname {
|
||||
return hc, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not find config entry for %q", hostname)
|
||||
}
|
||||
|
||||
func (c *fileConfig) Write() error {
|
||||
marshalled, err := yaml.Marshal(c.documentRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WriteConfigFile(configFile(), marshalled)
|
||||
}
|
||||
|
||||
func (c *fileConfig) Hosts() ([]*HostConfig, error) {
|
||||
if len(c.hosts) > 0 {
|
||||
return c.hosts, nil
|
||||
}
|
||||
|
||||
_, hostsEntry, err := c.FindEntry("hosts")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find hosts config: %s", err)
|
||||
}
|
||||
|
||||
hostConfigs, err := c.parseHosts(hostsEntry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse hosts config: %s", err)
|
||||
}
|
||||
|
||||
c.hosts = hostConfigs
|
||||
|
||||
return hostConfigs, nil
|
||||
}
|
||||
|
||||
func (c *fileConfig) parseHosts(hostsEntry *yaml.Node) ([]*HostConfig, error) {
|
||||
hostConfigs := []*HostConfig{}
|
||||
|
||||
for i := 0; i < len(hostsEntry.Content)-1; i = i + 2 {
|
||||
hostname := hostsEntry.Content[i].Value
|
||||
hostRoot := hostsEntry.Content[i+1]
|
||||
hostConfig := HostConfig{
|
||||
ConfigMap: ConfigMap{Root: hostRoot},
|
||||
Host: hostname,
|
||||
}
|
||||
hostConfigs = append(hostConfigs, &hostConfig)
|
||||
}
|
||||
|
||||
if len(hostConfigs) == 0 {
|
||||
return nil, errors.New("could not find any host configurations")
|
||||
}
|
||||
|
||||
return hostConfigs, nil
|
||||
}
|
||||
|
||||
func defaultFor(key string) string {
|
||||
// we only have a set default for one setting right now
|
||||
switch key {
|
||||
case "git_protocol":
|
||||
return defaultGitProtocol
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue