meta.go raw

   1  package toml
   2  
   3  import (
   4  	"strings"
   5  )
   6  
   7  // MetaData allows access to meta information about TOML data that's not
   8  // accessible otherwise.
   9  //
  10  // It allows checking if a key is defined in the TOML data, whether any keys
  11  // were undecoded, and the TOML type of a key.
  12  type MetaData struct {
  13  	context Key // Used only during decoding.
  14  
  15  	keyInfo map[string]keyInfo
  16  	mapping map[string]any
  17  	keys    []Key
  18  	decoded map[string]struct{}
  19  	data    []byte // Input file; for errors.
  20  }
  21  
  22  // IsDefined reports if the key exists in the TOML data.
  23  //
  24  // The key should be specified hierarchically, for example to access the TOML
  25  // key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive.
  26  //
  27  // Returns false for an empty key.
  28  func (md *MetaData) IsDefined(key ...string) bool {
  29  	if len(key) == 0 {
  30  		return false
  31  	}
  32  
  33  	var (
  34  		hash      map[string]any
  35  		ok        bool
  36  		hashOrVal any = md.mapping
  37  	)
  38  	for _, k := range key {
  39  		if hash, ok = hashOrVal.(map[string]any); !ok {
  40  			return false
  41  		}
  42  		if hashOrVal, ok = hash[k]; !ok {
  43  			return false
  44  		}
  45  	}
  46  	return true
  47  }
  48  
  49  // Type returns a string representation of the type of the key specified.
  50  //
  51  // Type will return the empty string if given an empty key or a key that does
  52  // not exist. Keys are case sensitive.
  53  func (md *MetaData) Type(key ...string) string {
  54  	if ki, ok := md.keyInfo[Key(key).String()]; ok {
  55  		return ki.tomlType.typeString()
  56  	}
  57  	return ""
  58  }
  59  
  60  // Keys returns a slice of every key in the TOML data, including key groups.
  61  //
  62  // Each key is itself a slice, where the first element is the top of the
  63  // hierarchy and the last is the most specific. The list will have the same
  64  // order as the keys appeared in the TOML data.
  65  //
  66  // All keys returned are non-empty.
  67  func (md *MetaData) Keys() []Key {
  68  	return md.keys
  69  }
  70  
  71  // Undecoded returns all keys that have not been decoded in the order in which
  72  // they appear in the original TOML document.
  73  //
  74  // This includes keys that haven't been decoded because of a [Primitive] value.
  75  // Once the Primitive value is decoded, the keys will be considered decoded.
  76  //
  77  // Also note that decoding into an empty interface will result in no decoding,
  78  // and so no keys will be considered decoded.
  79  //
  80  // In this sense, the Undecoded keys correspond to keys in the TOML document
  81  // that do not have a concrete type in your representation.
  82  func (md *MetaData) Undecoded() []Key {
  83  	undecoded := make([]Key, 0, len(md.keys))
  84  	for _, key := range md.keys {
  85  		if _, ok := md.decoded[key.String()]; !ok {
  86  			undecoded = append(undecoded, key)
  87  		}
  88  	}
  89  	return undecoded
  90  }
  91  
  92  // Key represents any TOML key, including key groups. Use [MetaData.Keys] to get
  93  // values of this type.
  94  type Key []string
  95  
  96  func (k Key) String() string {
  97  	// This is called quite often, so it's a bit funky to make it faster.
  98  	var b strings.Builder
  99  	b.Grow(len(k) * 25)
 100  outer:
 101  	for i, kk := range k {
 102  		if i > 0 {
 103  			b.WriteByte('.')
 104  		}
 105  		if kk == "" {
 106  			b.WriteString(`""`)
 107  		} else {
 108  			for _, r := range kk {
 109  				// "Inline" isBareKeyChar
 110  				if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-') {
 111  					b.WriteByte('"')
 112  					b.WriteString(dblQuotedReplacer.Replace(kk))
 113  					b.WriteByte('"')
 114  					continue outer
 115  				}
 116  			}
 117  			b.WriteString(kk)
 118  		}
 119  	}
 120  	return b.String()
 121  }
 122  
 123  func (k Key) maybeQuoted(i int) string {
 124  	if k[i] == "" {
 125  		return `""`
 126  	}
 127  	for _, r := range k[i] {
 128  		if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-' {
 129  			continue
 130  		}
 131  		return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
 132  	}
 133  	return k[i]
 134  }
 135  
 136  // Like append(), but only increase the cap by 1.
 137  func (k Key) add(piece string) Key {
 138  	newKey := make(Key, len(k)+1)
 139  	copy(newKey, k)
 140  	newKey[len(k)] = piece
 141  	return newKey
 142  }
 143  
 144  func (k Key) parent() Key  { return k[:len(k)-1] } // all except the last piece.
 145  func (k Key) last() string { return k[len(k)-1] }  // last piece of this key.
 146