lib/gnuplot/commands.ex

defmodule Gnuplot.Commands do
  @moduledoc """
  Protocol that convert Elixir terms to Gnuplot commands.
  """

  defprotocol Command do
    @spec formatg(term()) :: String.t()

    @doc "Format Elixir term as a Gnuplot String"
    def formatg(cmd)
  end

  defimpl Command, for: Atom do
    def formatg(a), do: Atom.to_string(a)
  end

  defimpl Command, for: Float do
    def formatg(x), do: Float.to_string(x)
  end

  defimpl Command, for: Integer do
    def formatg(x), do: Integer.to_string(x)
  end

  defimpl Command, for: BitString do
    def formatg(s) when is_binary(s), do: "\"" <> String.replace(s, "\"", "'") <> "\""
  end

  defimpl Command, for: Range do
    def formatg(%{first: f, last: l}) do
      "[" <> Command.formatg(f) <> ":" <> Command.formatg(l) <> "]"
    end
  end

  defimpl Command, for: List do
    @spec formatg(maybe_improper_list()) :: binary()
    def formatg(xs = [x | _]) when is_atom(x) or is_binary(x) do
      Enum.map_join(xs, " ", &Command.formatg/1)
    end

    def formatg(xs), do: List.to_string(xs)
  end

  defmodule List do
    @moduledoc """
    Comma separated list.

    Most lists are joined with whitespace, however the plot and splot commands require multiple plots to be comma separated.
    """
    defstruct xs: []
  end

  defimpl Command, for: List do
    def formatg(%{xs: xs}) do
      Enum.map_join(xs, ",", &Command.formatg/1)
    end
  end

  @doc """
  Convert Elixir terms to Gnuplot strings.
  """
  @spec format(list(list())) :: String.t()
  def format(cmds) do
    Enum.map_join(cmds, ";\n", &Command.formatg/1)
  end
end