Current section
Files
Jump to
Current section
Files
lib/mndp/render.ex
defmodule MNDP.Render do
@moduledoc """
Helpers for rendering the MNDP struct
"""
@type t() :: %__MODULE__{
printed_rows: [String.t()],
first_run: boolean()
}
# TODO: keep track of terminal width and redraw header and divider when changed
defstruct printed_rows: [], first_run: true
@mac_width String.length("1A:2B:3C:4D:5E:6F")
@ip_width String.length("111.222.333.444")
@version_width String.length("10.10.10")
@identity_width String.length("nerves-abcd")
# TODO: show more or less columns depending on terminal width instead of fixed list
@headers [
identity: @identity_width,
mac: @mac_width,
ip_v4: @ip_width,
ifname: nil,
version: @version_width,
uptime: nil,
seen: nil
]
@cols length(@headers)
@doc delegate_to: {MNDP, :print_discovered, 1}
def print_discovered(module \\ %__MODULE__{})
def print_discovered(nil), do: print_discovered()
def print_discovered(%__MODULE__{} = module) do
terminal_width = terminal_width() - 3 * (@cols + 1)
static_width =
Enum.reduce(@headers, 0, fn
{_, nil}, acc -> acc
{_, width}, acc -> acc + width
end)
flexible_cols_count = Enum.count(@headers, &is_nil(elem(&1, 1)))
flexible_col_width = round((terminal_width - static_width) / flexible_cols_count)
divider = String.duplicate("-", terminal_width) |> List.duplicate(@cols)
rows = MNDP.list_discovered() |> to_rows()
format =
Enum.map_join(@headers, "|", fn
{_, nil} -> " ~#{flexible_col_width}s "
{_, width} -> " ~#{width}s "
end)
format = "|#{format}|~n"
if module.first_run do
:io.fwrite(format, Keyword.keys(@headers))
:io.fwrite(format, divider)
else
for _row <- module.printed_rows do
move_cursor_up(1)
clear_line()
end
end
for mndp <- rows do
:io.fwrite(format, mndp)
end
%{module | first_run: false, printed_rows: rows}
end
def to_rows(mndps) do
Enum.map(mndps, fn mndp ->
[
mndp.identity,
print_mac(mndp.mac),
print_ip(mndp.ip_v4),
mndp.interface,
mndp.version,
"#{mndp.uptime}s",
"#{DateTime.diff(DateTime.utc_now(), mndp.last_seen)}s ago"
]
end)
end
defp move_cursor_up(n) do
IO.write(IO.ANSI.cursor_up(n))
end
defp clear_line() do
IO.write(IO.ANSI.clear_line())
end
defp terminal_width do
case :io.columns() do
{:ok, width} -> width
_ -> 80
end
end
defp print_mac(mac) do
mac |> Enum.map_join(":", &Integer.to_string(&1, 16))
end
defp print_ip(ip) do
case :inet.ntoa(ip) do
{:error, :einval} -> "unknown"
address -> address |> to_string()
end
end
end