Files
2026-06-02 11:56:18 +01:00

138 lines
3.1 KiB
Go

// Package terminalenv prepares environment variables for terminal sessions.
package terminalenv
import (
"runtime"
"sort"
"strings"
)
const (
envLANG = "LANG"
envLCAll = "LC_ALL"
envLCType = "LC_CTYPE"
envTerm = "TERM"
defaultTerm = "xterm-256color"
)
// LocalShellEnv returns an environment suitable for a local interactive PTY.
func LocalShellEnv(env []string) []string {
env = setEnv(env, envTerm, defaultTerm)
return ensureUTF8CharacterLocale(env, runtime.GOOS)
}
// SSHEnvVars returns environment variables to request for an SSH session.
// Profile locale values are preserved; otherwise a UTF-8 character locale is
// requested by default.
func SSHEnvVars(profileEnv map[string]string) map[string]string {
result := map[string]string{}
for key, value := range profileEnv {
key = strings.TrimSpace(key)
if key == "" {
continue
}
result[key] = value
}
if hasAnyLocaleMap(result) {
return result
}
result[envLCType] = DefaultUTF8Locale(runtime.GOOS)
return result
}
// SortedEnvKeys returns stable keys for deterministic environment requests.
func SortedEnvKeys(env map[string]string) []string {
keys := make([]string, 0, len(env))
for key := range env {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
// DefaultUTF8Locale returns a locale name broadly available on the target OS.
func DefaultUTF8Locale(goos string) string {
if goos == "darwin" {
return "en_US.UTF-8"
}
return "C.UTF-8"
}
func ensureUTF8CharacterLocale(env []string, goos string) []string {
if hasEffectiveUTF8Locale(env) {
return env
}
locale := DefaultUTF8Locale(goos)
if hasEnv(env, envLCAll) {
return setEnv(env, envLCAll, locale)
}
if hasEnv(env, envLCType) {
return setEnv(env, envLCType, locale)
}
if hasEnv(env, envLANG) {
return setEnv(env, envLCType, locale)
}
return setEnv(env, envLANG, locale)
}
func hasEffectiveUTF8Locale(env []string) bool {
values := envToMap(env)
if value, ok := values[envLCAll]; ok {
return isUTF8Locale(value)
}
if value, ok := values[envLCType]; ok {
return isUTF8Locale(value)
}
if value, ok := values[envLANG]; ok {
return isUTF8Locale(value)
}
return false
}
func hasAnyLocaleMap(env map[string]string) bool {
_, hasLCAll := env[envLCAll]
_, hasLCType := env[envLCType]
_, hasLANG := env[envLANG]
return hasLCAll || hasLCType || hasLANG
}
func isUTF8Locale(value string) bool {
normalized := strings.ToLower(strings.ReplaceAll(value, "-", ""))
return strings.Contains(normalized, "utf8")
}
func setEnv(env []string, key, value string) []string {
prefix := key + "="
result := make([]string, 0, len(env)+1)
for _, item := range env {
if strings.HasPrefix(item, prefix) {
continue
}
result = append(result, item)
}
return append(result, prefix+value)
}
func hasEnv(env []string, key string) bool {
prefix := key + "="
for _, item := range env {
if strings.HasPrefix(item, prefix) {
return true
}
}
return false
}
func envToMap(env []string) map[string]string {
result := map[string]string{}
for _, item := range env {
key, value, ok := strings.Cut(item, "=")
if ok {
result[key] = value
}
}
return result
}