greatly simplify environment variables handling

This commit is contained in:
2026-07-13 23:46:08 +02:00
parent f479499309
commit 3e9557586b
17 changed files with 69 additions and 322 deletions
-147
View File
@@ -1,147 +0,0 @@
import Foundation
import Testing
import TOML
@testable import phbar
// MARK: - expand
@Test func envExpandDollarBraceForm() {
let env = PHEnvironment(variables: ["ACCENT": "#ff0000"])
#expect(env.expand("${ACCENT}") == "#ff0000")
}
@Test func envExpandDollarForm() {
let env = PHEnvironment(variables: ["FOO": "bar"])
#expect(env.expand("$FOO") == "bar")
}
@Test func envExpandsEmbeddedReferences() {
let env = PHEnvironment(variables: ["HOME": "/Users/tom", "BIN": "/opt/bin"])
#expect(env.expand("$HOME/bin:${BIN}/extra") == "/Users/tom/bin:/opt/bin/extra")
}
@Test func envExpandLeavesUnresolvedIntact() {
// Unresolved references stay literal instead of being blanked, so a typo
// remains visible.
let env = PHEnvironment(variables: [:])
#expect(env.expand("$MISSING and ${ALSO_MISSING}") == "$MISSING and ${ALSO_MISSING}")
}
@Test func envExpandIgnoresNonIdentifierDollar() {
let env = PHEnvironment(variables: ["FOO": "bar"])
// A `$` not followed by an identifier name is left untouched.
#expect(env.expand("cost: $5 and $FOO") == "cost: $5 and bar")
}
// MARK: - merging
@Test func envMergingOverridesExistingKeys() {
let env = PHEnvironment(variables: ["PATH": "/usr/bin", "HOME": "/u"])
let merged = env.merging(["PATH": "/usr/bin:/extra"])
#expect(merged.variables["PATH"] == "/usr/bin:/extra")
#expect(merged.variables["HOME"] == "/u")
}
@Test func envMergingNilReturnsSameVariables() {
let env = PHEnvironment(variables: ["A": "1"])
#expect(env.merging(nil).variables == ["A": "1"])
}
// MARK: - [env] value expansion (config load)
private func makeConfig(env: [String: String]?) -> PHConfig {
PHConfig(theme: "default", window: "default", blocks: nil, env: env, monitors: nil)
}
@Test func resolvingEnvExpandsValuesAgainstGivenEnvironment() {
// `PATH = "$PATH:/extra"` extends an inherited variable.
let config = makeConfig(env: [
"PATH": "$PATH:/opt/phbar/bin",
"WORKDIR": "${TMPDIR}/phbar",
"PLAIN": "literal",
])
let resolved = config.resolvingEnv(in: PHEnvironment(variables: [
"PATH": "/usr/bin",
"TMPDIR": "/var/tmp",
]))
#expect(resolved.env?["PATH"] == "/usr/bin:/opt/phbar/bin")
#expect(resolved.env?["WORKDIR"] == "/var/tmp/phbar")
#expect(resolved.env?["PLAIN"] == "literal")
}
@Test func resolvingEnvDefaultsToProcessEnvironment() {
// `$TMPDIR` is set in phbar's launch environment, so this also exercises
// real process-env expansion end to end.
guard let tmpdir = ProcessInfo.processInfo.environment["TMPDIR"] else { return }
let config = makeConfig(env: ["OUT": "$TMPDIR"])
let resolved = config.resolvingEnv()
#expect(resolved.env?["OUT"] == tmpdir)
}
@Test func resolvingEnvLeavesConfigUntouchedWhenEnvAbsent() {
let config = makeConfig(env: nil)
#expect(config.resolvingEnv(in: .process).env == nil)
}
// MARK: - @EnvExpanded wrapper
/// Stand-in Decodable that mirrors how a theme field opts into expansion.
private struct Color: Decodable {
@EnvExpanded var hex: String
}
@Test func envExpandedResolvesAgainstInjectedEnvironment() throws {
let decoder = TOMLDecoder()
decoder.userInfo[PHEnvironment.userInfoKey] = PHEnvironment(variables: ["ACCENT": "#ff0000"])
let color = try decoder.decode(Color.self, from: #"hex = "$ACCENT""#)
#expect(color.hex == "#ff0000")
}
@Test func envExpandedFallsBackToLaunchEnvironment() throws {
// Without injected userInfo the wrapper falls back to phbar's launch
// environment (`ProcessInfo`), which is a snapshot taken at process start.
guard let path = ProcessInfo.processInfo.environment["PATH"] else { return }
let color = try TOMLDecoder().decode(Color.self, from: #"hex = "$PATH""#)
#expect(color.hex == path)
}
@Test func envExpandedLeavesUnresolvedIntact() throws {
let color = try TOMLDecoder().decode(Color.self, from: #"hex = "$NEVER_SET""#)
#expect(color.hex == "$NEVER_SET")
}
// MARK: - Theme integration
@Test func themeStyleColorExpandsEnvReference() throws {
let decoder = TOMLDecoder()
decoder.userInfo[PHEnvironment.userInfoKey] = PHEnvironment(variables: ["ACCENT": "#ff0000"])
let style = try decoder.decode(PHThemeStyleColor.self, from: #"color = "$ACCENT""#)
#expect(style.color == "#ff0000")
}
@Test func themeTextFontExpandsEnvReference() throws {
let decoder = TOMLDecoder()
decoder.userInfo[PHEnvironment.userInfoKey] = PHEnvironment(variables: ["FONT": "Comic Code"])
let text = try decoder.decode(
PHThemeText.self,
from: """
name = "default"
font = "$FONT"
size = 14
weight = "regular"
style = "normal"
"""
)
#expect(text.fontFamily == "Comic Code")
}