mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-30 07:42:12 +01:00 
			
		
		
		
	Updates #11058 Change-Id: I35e7ef9b90e83cac04ca93fd964ad00ed5b48430 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			117 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| // nocasemaps provides efficient functions to set and get entries in Go maps
 | |
| // keyed by a string, where the string is always lower-case.
 | |
| package nocasemaps
 | |
| 
 | |
| import (
 | |
| 	"unicode"
 | |
| 	"unicode/utf8"
 | |
| )
 | |
| 
 | |
| // TODO(https://github.com/golang/go/discussions/54245):
 | |
| // Define a generic Map type instead. The main reason to avoid that is because
 | |
| // there is currently no convenient API for iteration.
 | |
| // An opaque Map type would force callers to interact with the map through
 | |
| // the methods, preventing accidental interactions with the underlying map
 | |
| // without using functions in this package.
 | |
| 
 | |
| const stackArraySize = 32
 | |
| 
 | |
| // Get is equivalent to:
 | |
| //
 | |
| //	v := m[strings.ToLower(k)]
 | |
| func Get[K ~string, V any](m map[K]V, k K) V {
 | |
| 	if isLowerASCII(string(k)) {
 | |
| 		return m[k]
 | |
| 	}
 | |
| 	var a [stackArraySize]byte
 | |
| 	return m[K(appendToLower(a[:0], string(k)))]
 | |
| }
 | |
| 
 | |
| // GetOk is equivalent to:
 | |
| //
 | |
| //	v, ok := m[strings.ToLower(k)]
 | |
| func GetOk[K ~string, V any](m map[K]V, k K) (V, bool) {
 | |
| 	if isLowerASCII(string(k)) {
 | |
| 		v, ok := m[k]
 | |
| 		return v, ok
 | |
| 	}
 | |
| 	var a [stackArraySize]byte
 | |
| 	v, ok := m[K(appendToLower(a[:0], string(k)))]
 | |
| 	return v, ok
 | |
| }
 | |
| 
 | |
| // Set is equivalent to:
 | |
| //
 | |
| //	m[strings.ToLower(k)] = v
 | |
| func Set[K ~string, V any](m map[K]V, k K, v V) {
 | |
| 	if isLowerASCII(string(k)) {
 | |
| 		m[k] = v
 | |
| 		return
 | |
| 	}
 | |
| 	// TODO(https://go.dev/issues/55930): This currently always allocates.
 | |
| 	// An optimization to the compiler and runtime could make this allocate-free
 | |
| 	// in the event that we are overwriting a map entry.
 | |
| 	//
 | |
| 	// Alternatively, we could use string interning.
 | |
| 	// See an example intern data structure, see:
 | |
| 	//	https://github.com/go-json-experiment/json/blob/master/intern.go
 | |
| 	var a [stackArraySize]byte
 | |
| 	m[K(appendToLower(a[:0], string(k)))] = v
 | |
| }
 | |
| 
 | |
| // Delete is equivalent to:
 | |
| //
 | |
| //	delete(m, strings.ToLower(k))
 | |
| func Delete[K ~string, V any](m map[K]V, k K) {
 | |
| 	if isLowerASCII(string(k)) {
 | |
| 		delete(m, k)
 | |
| 		return
 | |
| 	}
 | |
| 	var a [stackArraySize]byte
 | |
| 	delete(m, K(appendToLower(a[:0], string(k))))
 | |
| }
 | |
| 
 | |
| // AppendSliceElem is equivalent to:
 | |
| //
 | |
| //	append(m[strings.ToLower(k)], v)
 | |
| func AppendSliceElem[K ~string, S []E, E any](m map[K]S, k K, vs ...E) {
 | |
| 	// if the key is already lowercased
 | |
| 	if isLowerASCII(string(k)) {
 | |
| 		m[k] = append(m[k], vs...)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// if key needs to become lowercase, uses appendToLower
 | |
| 	var a [stackArraySize]byte
 | |
| 	s := appendToLower(a[:0], string(k))
 | |
| 	m[K(s)] = append(m[K(s)], vs...)
 | |
| }
 | |
| 
 | |
| func isLowerASCII(s string) bool {
 | |
| 	for i := range len(s) {
 | |
| 		if c := s[i]; c >= utf8.RuneSelf || ('A' <= c && c <= 'Z') {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func appendToLower(b []byte, s string) []byte {
 | |
| 	for i := 0; i < len(s); i++ {
 | |
| 		switch c := s[i]; {
 | |
| 		case 'A' <= c && c <= 'Z':
 | |
| 			b = append(b, c+('a'-'A'))
 | |
| 		case c < utf8.RuneSelf:
 | |
| 			b = append(b, c)
 | |
| 		default:
 | |
| 			r, n := utf8.DecodeRuneInString(s[i:])
 | |
| 			b = utf8.AppendRune(b, unicode.ToLower(r))
 | |
| 			i += n - 1 // -1 to compensate for i++ in loop advancement
 | |
| 		}
 | |
| 	}
 | |
| 	return b
 | |
| }
 |