From 8d975ea45257693c24af62b18bb325cc14961823 Mon Sep 17 00:00:00 2001 From: Masatoshi N Date: Wed, 14 Jun 2023 08:05:45 -0400 Subject: [PATCH] feat: show tip of the day --- lib/nerves_motd.ex | 22 +++- lib/nerves_motd/tips.ex | 210 +++++++++++++++++++++++++++++++++ test/nerves_motd/tips_test.exs | 13 ++ test/nerves_motd_test.exs | 19 +++ 4 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 lib/nerves_motd/tips.ex create mode 100644 test/nerves_motd/tips_test.exs diff --git a/lib/nerves_motd.ex b/lib/nerves_motd.ex index 9fde363..81e0475 100644 --- a/lib/nerves_motd.ex +++ b/lib/nerves_motd.ex @@ -14,6 +14,7 @@ defmodule NervesMOTD do \e[38;5;24m███▌ \e[38;5;74m▀▀████\e[0m """ + alias NervesMOTD.Tips alias NervesMOTD.Utils @excluded_ifnames [~c"lo", ~c"lo0"] @@ -47,6 +48,7 @@ defmodule NervesMOTD do an empty logo (`""`) to remove it completely. * `:extra_rows` - a list of custom rows or a callback for returning rows. The callback can be a 0-arity function reference or MFArgs tuple. + * `:show tip` - a boolean flag to show the tip of the day. """ @spec print([option()]) :: :ok def print(opts \\ []) do @@ -63,7 +65,8 @@ defmodule NervesMOTD do "\n", """ Nerves CLI help: https://hexdocs.pm/nerves/iex-with-nerves.html - """ + """, + tips(opts) ] |> IO.ANSI.format() |> IO.puts() @@ -81,6 +84,23 @@ defmodule NervesMOTD do Keyword.get(opts, :logo, @logo) end + @spec tips([option()]) :: IO.ANSI.ansidata() + defp tips(opts) do + if opts[:show_tip] do + {title, content} = Tips.random() + + """ + ========================================================================== + [Tip of the day] #{title} + -------------------------------------------------------------------------- + #{String.strip(content)} + ========================================================================== + """ + else + [] + end + end + @spec rows(map(), list()) :: [[cell()]] defp rows(apps, opts) do [ diff --git a/lib/nerves_motd/tips.ex b/lib/nerves_motd/tips.ex new file mode 100644 index 0000000..3827ba7 --- /dev/null +++ b/lib/nerves_motd/tips.ex @@ -0,0 +1,210 @@ +defmodule NervesMOTD.Tips do + @moduledoc false + + @tips [ + { + "Toolshed", + """ + Want more traditional shell tools in your Nerves IEx? + + Use Toolshed! + + Its included by default and has may familiar functions, such as `cat`, + `ls`, or even `weather` 🌤️ + + https://hexdocs.pm/toolshed + """ + }, + { + "Kernek log message", + """ + Need more than Elixir logs? Run `dmesg` to see log messages from device + drivers and the Linux kernel. Nerves also routes kernel log messages to + the Elixir logger. + """ + }, + { + "erlinit", + """ + Erlinit is a small program that starts the Erlang VM on boot. It has many + options - especially for debugging startup issues. + + https://hexdocs.pm/nerves/advanced-configuration.html#overriding-erlinit-config-from-mix-config + """ + }, + { + "Disable automatic reboot", + """ + Nerves automatically reboots devices when the Erlang VM exits. This can + make some debugging harder, but you can easily disable it to let those + sessions hang for debugging + """ + }, + { + "Whats in a firmware?", + """ + Use `mix firmware.unpack` to decompress a local copy of your firmware on + host and inspect the files within before installing on device + """ + }, + { + "Elixir Circuits libraries", + """ + See if someone has already implemented support for a sensor or other + hardware device that you have by checking + https://elixir-circuits.github.io/. + """ + }, + { + "Read-only filesystems", + """ + Nerves stores all BEAM files, the Erlang runtime and various support + libraries/apps in a compressed and read-only SquashFS filesystem. + + Need to write to disk? Use the application partition mounted R/W at + `/data` + """ + }, + { + "SASL log messages", + """ + Configuring the Elixir Logger to show SASL reports can help debug + unexpected GenServer restarts. + + https://hexdocs.pm/logger/Logger.html#module-erlang-otp-integration + """ + }, + { + "Viewing the file system", + """ + Sometimes, you just need to see whats on your Nerves device's disk. + Luckily, there are a few ways to do that. You can even use VSCode! + + https://embedded-elixir.com/post/2021-05-08-nerves-file-peeking/#sshfs + """ + }, + { + "NervesPack", + """ + Get all of the default packages for starting a new Nerves project by + depending on `:nerves_pack` + + https://github.com/nerves-project/nerves_pack + """ + }, + { + "Hardware watchdogs", + """ + Nerves enables hardware watchdogs and connects them to Erlang's heart + feature to detect and recover from the Erlang VM hanging. + + See https://embedded-elixir.com/post/2018-12-10-heart/ to learn more + about this. + """ + }, + { + "Copy and paste", + """ + Make small code changes to your running application by copy/pasting + Elixir source files at the IEx prompt. + """ + }, + { + "Read-only filesystems", + """ + Nerves stores all BEAM files, the Erlang runtime and various support + libraries and apps in a compressed and read-only SquashFS filesystem. + + The writable application partition is mounted at `/data` + """ + }, + { + "Device serial numbers", + """ + Identify your Nerves devices using a unique ID that's already programmed + into the hardware using the boardid command. Details at + https://github.com/nerves-project/boardid/ and the boardid.config file. + """ + }, + { + "Need an example?", + """ + Nerves maintains a set of examples for common use-cases with devices. + Things like running a phoenix app, using SQLite, controlling GPIO, using + Zig lang, and more! + + https://github.com/nerves-project/nerves_examples + """ + }, + { + "Logs", + """ + Use `RingLogger.next` to dump the current log buffer to screen. + + Use `log_attach` from `Toolshed` to attach the current session to the + Elixir logger for live logs. + + https://hexdocs.pm/toolshed/Toolshed.Log.html#log_attach/1 + """ + }, + { + "Eye from the Sky", + """ + Get some insights to Nerves internals with this high level overview of + the Nerves architecture and choice to use the BEAM VM + + https://youtu.be/VzOaSvTcvU4 + """ + }, + { + "Revert to the previously loaded firmware", + """ + Run `Nerves.Runtime.revert` to go back to the previously loaded firmware. + """ + }, + { + "Compiling systems", + """ + Compiling systems on linux or via `mix nerves.system.shell`? + + Don't forgot about `make help` and other tips from the Buildroot manual + (thanks @ericr3r) + + https://buildroot.org/downloads/manual/manual.html#make-tips + """ + }, + { + "Order matters", + """ + Sometimes when compiling Nerves systems, order matters! + + Use `make show-build-order` while in `mix nerves.system.shell` to see the + compilation order and make sure all your 🦆🦆 are in a row (thanks + @ericr3r!) + """ + }, + { + "RamoopsLogger", + """ + Use the `RamoopsLogger` backend to store log messages in DRAM that can + survive unexpected reboots. + + https://github.com/smartrent/ramoops_logger + """ + } + ] + + @type tip :: {title :: String.t(), content :: String.t()} + + @doc """ + List all Nerves tips + """ + @spec all() :: [tip] + def all(), do: @tips + + @doc """ + Pick one Nerves tip randomly + """ + @spec random() :: tip + def random(), do: Enum.random(@tips) +end diff --git a/test/nerves_motd/tips_test.exs b/test/nerves_motd/tips_test.exs new file mode 100644 index 0000000..46bbcfc --- /dev/null +++ b/test/nerves_motd/tips_test.exs @@ -0,0 +1,13 @@ +defmodule NervesMOTD.TipsTest do + use ExUnit.Case + alias NervesMOTD.Tips + doctest NervesMOTD.Tips + + test "all" do + assert [{_title, _content} | _] = Tips.all() + end + + test "random" do + assert {_title, _content} = Tips.random() + end +end diff --git a/test/nerves_motd_test.exs b/test/nerves_motd_test.exs index d3a4393..89aa489 100644 --- a/test/nerves_motd_test.exs +++ b/test/nerves_motd_test.exs @@ -347,4 +347,23 @@ defmodule NervesMOTDTest do assert capture_motd() == "" end + + describe "tip of the day" do + setup _context do + NervesMOTD.MockRuntime + |> Mox.expect(:applications, 1, default_applications_code()) + |> Mox.expect(:active_partition, 1, fn -> "A" end) + |> Mox.expect(:firmware_validity, 1, fn -> :valid end) + + :ok + end + + test "does nothing by default" do + refute capture_motd() =~ ~r/Tip of the day/ + end + + test "shows tip when enabled" do + assert capture_motd(show_tip: true) =~ ~r/Tip of the day/ + end + end end