defmodule Xamal.EnvFile do @moduledoc """ Generates env files for deployment. Encodes an env map as KEY=value lines with proper escaping. Compatible with Kamal's env file format. """ @doc """ Generate env file content from a map of key-value pairs. Returns a string with one KEY=value per line. """ def encode(env) when map_size(env) == 0, do: "\n" def encode(env) when is_map(env) do env |> Enum.sort_by(fn {key, _value} -> key end) |> Enum.map(fn {key, value} -> [to_string(key), "=", escape_value(value), "\n"] end) |> IO.iodata_to_binary() end @doc """ Write env file content to a path. """ def write!(env, path) do File.mkdir_p!(Path.dirname(path)) File.write!(path, encode(env)) end # Escape a value for use in an env file. # Handles special characters, preserves non-ASCII (UTF-8). defp escape_value(value) when is_binary(value) do value |> String.to_charlist() |> Enum.chunk_by(&ascii?/1) |> Enum.map(&escape_chunk/1) |> IO.iodata_to_binary() end defp escape_value(value), do: escape_value(to_string(value)) defp escape_chunk(chunk) do string = List.to_string(chunk) if Enum.all?(chunk, &ascii?/1), do: escape_ascii(string), else: string end defp ascii?(char), do: char <= 127 defp escape_ascii(value) do value |> String.replace("\\", "\\\\") |> String.replace("\n", "\\n") |> String.replace("\r", "\\r") |> String.replace("\t", "\\t") |> String.replace("\"", "\\\"") end end