Path() builds OS-aware absolute paths using Env("DS") — single point
of responsibility for filesystem paths. Relative paths anchor to
DIR_HOME. cleanPath resolves .. and double separators.
Env() now falls through to os.Getenv for unknown keys — universal
replacement for os.Getenv. Core keys (OS, DIR_HOME, etc.) take
precedence, arbitrary env vars pass through.
New exports: Path, PathBase, PathDir, PathExt, PathIsAbs, PathGlob,
CleanPath. Info init moved to init() so Path() can be used during
population without init cycle. DIR_HOME respects CORE_HOME env var
override for agent workspace sandboxing.
13 path tests, 17 env tests — all passing.
Co-Authored-By: Virgil <virgil@lethean.io>
134 lines
3.8 KiB
Go
134 lines
3.8 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
// System information registry for the Core framework.
|
|
// Read-only key-value store of environment facts, populated once at init.
|
|
// Env is environment. Config is ours.
|
|
//
|
|
// System keys:
|
|
//
|
|
// core.Env("OS") // "darwin"
|
|
// core.Env("ARCH") // "arm64"
|
|
// core.Env("GO") // "go1.26"
|
|
// core.Env("DS") // "/" (directory separator)
|
|
// core.Env("PS") // ":" (path list separator)
|
|
// core.Env("HOSTNAME") // "cladius"
|
|
// core.Env("USER") // "snider"
|
|
// core.Env("PID") // "12345"
|
|
// core.Env("NUM_CPU") // "10"
|
|
//
|
|
// Directory keys:
|
|
//
|
|
// core.Env("DIR_HOME") // "/Users/snider"
|
|
// core.Env("DIR_CONFIG") // "~/Library/Application Support"
|
|
// core.Env("DIR_CACHE") // "~/Library/Caches"
|
|
// core.Env("DIR_DATA") // "~/Library" (platform-specific)
|
|
// core.Env("DIR_TMP") // "/tmp"
|
|
// core.Env("DIR_CWD") // current working directory
|
|
// core.Env("DIR_DOWNLOADS") // "~/Downloads"
|
|
// core.Env("DIR_CODE") // "~/Code"
|
|
//
|
|
// Timestamp keys:
|
|
//
|
|
// core.Env("CORE_START") // "2026-03-22T14:30:00Z"
|
|
package core
|
|
|
|
import (
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// SysInfo holds read-only system information, populated once at init.
|
|
type SysInfo struct {
|
|
values map[string]string
|
|
}
|
|
|
|
// systemInfo is declared empty — populated in init() so Path() can be used
|
|
// without creating an init cycle.
|
|
var systemInfo = &SysInfo{values: make(map[string]string)}
|
|
|
|
func init() {
|
|
i := systemInfo
|
|
|
|
// System
|
|
i.values["OS"] = runtime.GOOS
|
|
i.values["ARCH"] = runtime.GOARCH
|
|
i.values["GO"] = runtime.Version()
|
|
i.values["DS"] = string(os.PathSeparator)
|
|
i.values["PS"] = string(os.PathListSeparator)
|
|
i.values["PID"] = strconv.Itoa(os.Getpid())
|
|
i.values["NUM_CPU"] = strconv.Itoa(runtime.NumCPU())
|
|
i.values["USER"] = Username()
|
|
|
|
if h, err := os.Hostname(); err == nil {
|
|
i.values["HOSTNAME"] = h
|
|
}
|
|
|
|
// Directories — DS and DIR_HOME set first so Path() can use them.
|
|
// CORE_HOME overrides os.UserHomeDir() (e.g., agent workspaces).
|
|
if d := os.Getenv("CORE_HOME"); d != "" {
|
|
i.values["DIR_HOME"] = d
|
|
} else if d, err := os.UserHomeDir(); err == nil {
|
|
i.values["DIR_HOME"] = d
|
|
}
|
|
|
|
// Derived directories via Path() — single point of responsibility
|
|
i.values["DIR_DOWNLOADS"] = Path("Downloads")
|
|
i.values["DIR_CODE"] = Path("Code")
|
|
if d, err := os.UserConfigDir(); err == nil {
|
|
i.values["DIR_CONFIG"] = d
|
|
}
|
|
if d, err := os.UserCacheDir(); err == nil {
|
|
i.values["DIR_CACHE"] = d
|
|
}
|
|
i.values["DIR_TMP"] = os.TempDir()
|
|
if d, err := os.Getwd(); err == nil {
|
|
i.values["DIR_CWD"] = d
|
|
}
|
|
|
|
// Platform-specific data directory
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
i.values["DIR_DATA"] = Path(Env("DIR_HOME"), "Library")
|
|
case "windows":
|
|
if d := os.Getenv("LOCALAPPDATA"); d != "" {
|
|
i.values["DIR_DATA"] = d
|
|
}
|
|
default:
|
|
if xdg := os.Getenv("XDG_DATA_HOME"); xdg != "" {
|
|
i.values["DIR_DATA"] = xdg
|
|
} else if Env("DIR_HOME") != "" {
|
|
i.values["DIR_DATA"] = Path(Env("DIR_HOME"), ".local", "share")
|
|
}
|
|
}
|
|
|
|
// Timestamps
|
|
i.values["CORE_START"] = time.Now().UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
// Env returns a system information value by key.
|
|
// Core keys (OS, DIR_HOME, DS, etc.) are pre-populated at init.
|
|
// Unknown keys fall through to os.Getenv — making Env a universal
|
|
// replacement for os.Getenv.
|
|
//
|
|
// core.Env("OS") // "darwin" (pre-populated)
|
|
// core.Env("DIR_HOME") // "/Users/snider" (pre-populated)
|
|
// core.Env("FORGE_TOKEN") // falls through to os.Getenv
|
|
func Env(key string) string {
|
|
if v := systemInfo.values[key]; v != "" {
|
|
return v
|
|
}
|
|
return os.Getenv(key)
|
|
}
|
|
|
|
// EnvKeys returns all available environment keys.
|
|
//
|
|
// keys := core.EnvKeys()
|
|
func EnvKeys() []string {
|
|
keys := make([]string, 0, len(systemInfo.values))
|
|
for k := range systemInfo.values {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|