defmodule SMPPEX.Pdu.PP do
@moduledoc """
Module for colored pretty printing Pdu structs.
"""
alias IO.ANSI, as: C
alias SMPPEX.Pdu
alias SMPPEX.Protocol.TlvFormat
@pad ""
@indent " "
@field_inspect_limit 999_999
@spec format(pdu :: Pdu.t(), indent :: String.t(), pad :: String.t()) :: iolist
@doc """
Forms an iolist containing colored Pdu dump.
`indent` is the string prepended to each line of the dump ("#{@indent}" by default).
`pad` is the string prepended to nested lines of the dump together with `indent`.
The default is "#{@pad}".
## Example
iex> pdu = SMPPEX.Pdu.Factory.submit_sm({"from", 1, 1}, {"to", 1, 1}, "hello")
Then `pdu |> SMPPEX.Pdu.PP.format |> IO.puts()` will print:
```
pdu: submit_sm
command_id: 4
command_status: 0 (ok)
sequence_number: 0
mandatory fields:
dest_addr_npi: 1
dest_addr_ton: 1
destination_addr: "to"
registered_delivery: 0
short_message: "hello"
source_addr: "from"
source_addr_npi: 1
source_addr_ton: 1
optional fields: []
```
"""
def format(pdu, indent \\ @indent, pad \\ @pad) do
[
"\n",
C.reset(),
pdu
|> pdu_lines
|> Enum.map(fn [section_head | section_lines] ->
[
pad,
section_head,
"\n",
section_lines
|> Enum.map(fn line ->
[pad, indent, line, "\n"]
end)
]
end)
]
end
defp pdu_lines(pdu) do
[
header(pdu),
mandatory_fields(pdu),
optional_fields(pdu)
]
end
defp header(pdu) do
[
name(pdu),
[pp_field_name("command_id"), ": ", pdu |> Pdu.command_id() |> inspect |> pp_val],
[pp_field_name("command_status"), ": ", pdu |> Pdu.command_status() |> pp_command_status],
[pp_field_name("sequence_number"), ": ", pdu |> Pdu.sequence_number() |> inspect |> pp_val]
]
end
defp name(pdu) do
["pdu: ", pp_command_name(pdu)]
end
defp mandatory_fields(pdu) do
[["mandatory fields:", pdu |> Pdu.mandatory_fields() |> pp_empty_list]] ++
(pdu |> Pdu.mandatory_fields() |> Map.to_list() |> pp_fields)
end
defp optional_fields(pdu) do
[["optional fields:", pdu |> Pdu.optional_fields() |> pp_empty_list]] ++
(pdu |> Pdu.optional_fields() |> Map.to_list() |> name_known_tlvs |> pp_fields)
end
defp name_known_tlvs(_, res \\ [])
defp name_known_tlvs([], res), do: Enum.reverse(res)
defp name_known_tlvs([{k, v} | left], res) do
case TlvFormat.name_by_id(k) do
{:ok, name} -> name_known_tlvs(left, [{name, v} | res])
:unknown -> name_known_tlvs(left, [{k, v} | res])
end
end
defp pp_empty_list(map) when map == %{}, do: " []"
defp pp_empty_list(_), do: ""
defp pp_command_status(status) do
case status do
0 -> [C.green(), C.bright(), "0 (ok)", C.reset()]
_ -> [C.red(), C.bright(), "#{status} (error)", C.reset()]
end
end
defp pp_field_name(field_name) do
[C.green(), field_name, C.reset()]
end
defp pp_val(str) do
[C.yellow(), str, C.reset()]
end
defp pp_fields(fields) do
fields
|> Enum.sort()
|> Enum.map(fn {key, val} ->
[
key |> to_string |> pp_field_name,
": ",
val |> inspect(limit: @field_inspect_limit) |> pp_val
]
end)
end
defp pp_command_name(pdu) do
name = pdu |> Pdu.command_name() |> to_string
[C.cyan(), C.bright(), name, C.reset()]
end
end