Packages

fnord

0.9.38

AI code archaeology

Current section

Files

Jump to
fnord lib util env.ex
Raw

lib/util/env.ex

defmodule Util.Env do
@moduledoc """
Utilities for interpreting environment variables used in fnord.
Provide canonical parsing helpers so different runtime contexts treat
environment values consistently (escript, mix run, CI, etc.).
Reads consult a `Services.Globals`-scoped override before the real
environment, so a process tree can see a different value than the VM at
large (see `put_override/2`). In-VM env reads should always route through
this module rather than `System.get_env` so they honor that scoping.
"""
# Overrides live under {:env_override, var} in the :fnord Globals scope.
# :unset is the "pretend the variable is not set" sentinel: a nil override
# value cannot express that, because Globals returns the caller's default
# for a missing key and nil for an explicit nil alike.
@typep override :: binary | :unset | :no_override
@doc """
Return true when the provided environment value is considered truthy.
Recognizes the common truthy values (case-insensitive): "1", "true", and "yes".
Returns false for nil, empty strings, and other values.
"""
@spec looks_truthy?(binary) :: boolean
def looks_truthy?(env_var_name) do
case fetch_env(env_var_name) do
{:ok, v} ->
truthy_value?(v)
{:error, :not_set} ->
false
end
end
# Internal helper: interpret a raw env value (string) as truthy/falsey
@spec truthy_value?(binary) :: boolean
defp truthy_value?(val) do
val
|> String.trim()
|> String.downcase()
|> case do
"1" -> true
"true" -> true
"yes" -> true
_ -> false
end
end
@spec mcp_debug_enabled?() :: boolean
def mcp_debug_enabled? do
looks_truthy?("FNORD_DEBUG_MCP")
end
@spec cursor_rules_debug_enabled?() :: boolean
def cursor_rules_debug_enabled? do
looks_truthy?("FNORD_DEBUG_CURSOR_RULES")
end
@spec get_env(binary, any) :: any
def get_env(var, default \\ nil) do
case fetch_env(var) do
{:ok, v} -> v
{:error, :not_set} -> default
end
end
@spec fetch_env(binary) :: {:ok, binary} | {:error, :not_set}
def fetch_env(var) do
case get_override(var) do
:no_override ->
case System.get_env(var) do
nil -> {:error, :not_set}
v -> {:ok, v}
end
:unset ->
{:error, :not_set}
v when is_binary(v) ->
{:ok, v}
end
end
@doc """
Override `var` for the current `Services.Globals` scope (process tree).
Reads through this module see the override; the real environment - and
therefore subprocesses - do not. `nil` makes the variable appear unset.
This is the async-safe alternative to `put_env/2` for tests: System env is
VM-global, so a per-test put/delete races every concurrently running test,
while an override dies with the test's process tree.
"""
@spec put_override(binary, binary | nil) :: :ok
def put_override(var, nil) do
Services.Globals.put_env(:fnord, {:env_override, var}, :unset)
end
def put_override(var, value) when is_binary(value) do
Services.Globals.put_env(:fnord, {:env_override, var}, value)
end
@spec get_override(binary) :: override
defp get_override(var) do
# get_override, not get_env: env-override keys have no Application-env
# counterpart, and get_env's no-root fallback would pass the tuple key to
# Application.get_env - a runtime deprecation warning on Elixir >= 1.20
# that lands in whatever test capture happens to be active.
Services.Globals.get_override(:fnord, {:env_override, var}, :no_override)
end
@doc """
Set the environment variable to the given value.
"""
@spec put_env(binary, binary) :: :ok
def put_env(var, value) do
System.put_env(var, value)
end
@doc """
Delete the environment variable.
"""
@spec delete_env(binary) :: :ok
def delete_env(var) do
System.delete_env(var)
end
end