defmodule Systemd.UnitName do @moduledoc """ Pure helpers for formatting systemd unit names. Systemd unit names are ordinary strings, but callers often need to format the same patterns repeatedly: typed names such as `dbus.service`, template names such as `my_app@.service`, and instance names such as `my_app@4000.service`. This module keeps that formatting explicit without introducing a struct or a deployment-specific abstraction. ## Examples iex> Systemd.UnitName.new("dbus", :service) "dbus.service" iex> Systemd.UnitName.template("my_app", :service) "my_app@.service" iex> Systemd.UnitName.instance("my_app", 4000, :service) "my_app@4000.service" iex> Systemd.UnitName.ensure_type("my_app@4000", :service) "my_app@4000.service" iex> Systemd.UnitName.drop_type("my_app@4000.service") "my_app@4000" """ @type unit_type :: :service | :socket | :timer | :target | :mount | :path | String.t() @doc """ Formats a typed systemd unit name. If `name` already ends with the requested type suffix, it is returned unchanged. """ @spec new(String.t(), unit_type()) :: String.t() def new(name, type) when is_binary(name), do: ensure_type(name, type) @doc """ Formats a systemd template unit name. """ @spec template(String.t(), unit_type()) :: String.t() def template(name, type) when is_binary(name) do name |> base_name() |> Kernel.<>("@") |> ensure_type(type) end @doc """ Formats a systemd instance unit name. """ @spec instance(String.t(), String.Chars.t(), unit_type()) :: String.t() def instance(name, instance, type) when is_binary(name) do base = base_name(name) instance = to_string(instance) base |> Kernel.<>("@" <> instance) |> ensure_type(type) end @doc """ Ensures a unit name has the suffix for `type`. """ @spec ensure_type(String.t(), unit_type()) :: String.t() def ensure_type(name, type) when is_binary(name) do suffix = suffix(type) if String.ends_with?(name, suffix) do name else name <> suffix end end @doc """ Drops the final systemd unit type suffix from a name. """ @spec drop_type(String.t()) :: String.t() def drop_type(name) when is_binary(name) do Enum.reduce_while(known_suffixes(), name, fn suffix, name -> if String.ends_with?(name, suffix) do {:halt, String.trim_trailing(name, suffix)} else {:cont, name} end end) end defp base_name(name) do name |> drop_type() |> String.trim_trailing("@") end defp known_suffixes do ~w(.service .socket .timer .target .mount .path) end defp suffix(type) when is_atom(type), do: "." <> Atom.to_string(type) defp suffix(type) when is_binary(type) do type = String.trim_leading(type, ".") "." <> type end end