github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/internal/config/config_map.go (about)

     1  package config
     2  
     3  import (
     4  	"errors"
     5  
     6  	"gopkg.in/yaml.v3"
     7  )
     8  
     9  // This type implements a low-level get/set config that is backed by an in-memory tree of Yaml
    10  // nodes. It allows us to interact with a yaml-based config programmatically, preserving any
    11  // comments that were present when the yaml was parsed.
    12  type ConfigMap struct {
    13  	Root *yaml.Node
    14  }
    15  
    16  type ConfigEntry struct {
    17  	KeyNode   *yaml.Node
    18  	ValueNode *yaml.Node
    19  	Index     int
    20  }
    21  
    22  type NotFoundError struct {
    23  	error
    24  }
    25  
    26  func (cm *ConfigMap) Empty() bool {
    27  	return cm.Root == nil || len(cm.Root.Content) == 0
    28  }
    29  
    30  func (cm *ConfigMap) GetStringValue(key string) (string, error) {
    31  	entry, err := cm.FindEntry(key)
    32  	if err != nil {
    33  		return "", err
    34  	}
    35  	return entry.ValueNode.Value, nil
    36  }
    37  
    38  func (cm *ConfigMap) SetStringValue(key, value string) error {
    39  	entry, err := cm.FindEntry(key)
    40  
    41  	var notFound *NotFoundError
    42  
    43  	valueNode := entry.ValueNode
    44  
    45  	if err != nil && errors.As(err, &notFound) {
    46  		keyNode := &yaml.Node{
    47  			Kind:  yaml.ScalarNode,
    48  			Value: key,
    49  		}
    50  		valueNode = &yaml.Node{
    51  			Kind:  yaml.ScalarNode,
    52  			Tag:   "!!str",
    53  			Value: "",
    54  		}
    55  
    56  		cm.Root.Content = append(cm.Root.Content, keyNode, valueNode)
    57  	} else if err != nil {
    58  		return err
    59  	}
    60  
    61  	valueNode.Value = value
    62  
    63  	return nil
    64  }
    65  
    66  func (cm *ConfigMap) FindEntry(key string) (ce *ConfigEntry, err error) {
    67  	err = nil
    68  
    69  	ce = &ConfigEntry{}
    70  
    71  	// Content slice goes [key1, value1, key2, value2, ...]
    72  	topLevelPairs := cm.Root.Content
    73  	for i, v := range topLevelPairs {
    74  		// Skip every other slice item since we only want to check against keys
    75  		if i%2 != 0 {
    76  			continue
    77  		}
    78  		if v.Value == key {
    79  			ce.KeyNode = v
    80  			ce.Index = i
    81  			if i+1 < len(topLevelPairs) {
    82  				ce.ValueNode = topLevelPairs[i+1]
    83  			}
    84  			return
    85  		}
    86  	}
    87  
    88  	return ce, &NotFoundError{errors.New("not found")}
    89  }
    90  
    91  func (cm *ConfigMap) RemoveEntry(key string) {
    92  	newContent := []*yaml.Node{}
    93  
    94  	content := cm.Root.Content
    95  	for i := 0; i < len(content); i++ {
    96  		if content[i].Value == key {
    97  			i++ // skip the next node which is this key's value
    98  		} else {
    99  			newContent = append(newContent, content[i])
   100  		}
   101  	}
   102  
   103  	cm.Root.Content = newContent
   104  }