defmodule Systemd.UnitFile.Value do @moduledoc """ Helpers for systemd directive values. The unit-file parser preserves directive values as raw strings. These helpers provide focused value-level parsing for callers that need shell-like words or simple quoted strings without baking value semantics into every directive. """ @type word :: String.t() @doc """ Splits a directive value into whitespace-separated words. Double and single quotes group whitespace. A backslash escapes the following character outside single quotes. """ @spec words(String.t()) :: {:ok, [word()]} | {:error, term()} def words(value) when is_binary(value) do value |> String.to_charlist() |> do_words([], [], :bare) end defp do_words([], current, words, :bare) do {:ok, finish_words(current, words)} end defp do_words([], _current, _words, quote) when quote in [:single, :double] do {:error, {:unterminated_quote, quote}} end defp do_words([char | rest], current, words, :bare) when char in [?\s, ?\t] do do_words(rest, [], maybe_push_word(current, words), :bare) end defp do_words([?' | rest], current, words, :bare), do: do_words(rest, current, words, :single) defp do_words([?" | rest], current, words, :bare), do: do_words(rest, current, words, :double) defp do_words([?\\, char | rest], current, words, :bare), do: do_words(rest, [char | current], words, :bare) defp do_words([char | rest], current, words, :bare), do: do_words(rest, [char | current], words, :bare) defp do_words([?' | rest], current, words, :single), do: do_words(rest, current, words, :bare) defp do_words([char | rest], current, words, :single), do: do_words(rest, [char | current], words, :single) defp do_words([?" | rest], current, words, :double), do: do_words(rest, current, words, :bare) defp do_words([?\\, char | rest], current, words, :double), do: do_words(rest, [char | current], words, :double) defp do_words([char | rest], current, words, :double), do: do_words(rest, [char | current], words, :double) defp finish_words(current, words) do current |> maybe_push_word(words) |> Enum.reverse() end defp maybe_push_word([], words), do: words defp maybe_push_word(current, words), do: [current |> Enum.reverse() |> to_string() | words] end