diff --git a/lib/nerves_motd.ex b/lib/nerves_motd.ex index 9b30e05..5ffa32d 100644 --- a/lib/nerves_motd.ex +++ b/lib/nerves_motd.ex @@ -26,7 +26,6 @@ defmodule NervesMOTD do {:logo, IO.ANSI.ansidata()} | {:extra_rows, [row()]} | {:show_tip, boolean()} - | {:extra_tips, [String.t()]} @typedoc """ One row of information @@ -53,7 +52,6 @@ defmodule NervesMOTD do * `: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. - * `:extra_tips` - additional custom tips as a list of strings. """ @spec print([option()]) :: :ok def print(opts \\ []) do @@ -92,11 +90,13 @@ defmodule NervesMOTD do @spec tips([option()]) :: IO.ANSI.ansidata() defp tips(opts) do if opts[:show_tip] do - """ + case Tips.random() do + {:ok, tip} -> + ["\n=== Tip of the day ===\n", tip, "\n"] - === Tip of the day === - #{Tips.random(opts)} - """ + _error -> + [] + end else [] end diff --git a/lib/nerves_motd/tips.ex b/lib/nerves_motd/tips.ex index 4d30d99..ec262bc 100644 --- a/lib/nerves_motd/tips.ex +++ b/lib/nerves_motd/tips.ex @@ -1,29 +1,49 @@ defmodule NervesMOTD.Tips do @moduledoc false - @divider "\n\n" - @default_tips File.read!("config/default_tips.txt") - |> String.split(@divider, trim: true) - |> Enum.reject(&(&1 == "")) + # Returns a tuple list of locaton and byte size of each tip entry in the provided file. + index_file = fn filename, divider when is_binary(filename) and is_binary(divider) -> + divider_byte_size = byte_size(divider) - @type tip :: String.t() + {indices, _} = + File.read!(filename) + |> String.trim() + |> String.split(divider, trim: true) + |> Enum.reduce({[], 0}, fn tip, {indices, current_location} -> + how_many_bytes = byte_size(tip) + next_location = current_location + how_many_bytes + divider_byte_size + new_index = {current_location, how_many_bytes} - @doc """ - List all tips - """ - @spec all(keyword) :: [tip] - def all(opts \\ []) do - extra_tips = Keyword.get(opts, :extra_tips, []) - @default_tips ++ extra_tips + {[new_index | indices], next_location} + end) + + indices end + @default_tips_file "priv/default_tips.txt" + @divider "%%%" + @indices Application.app_dir(:nerves_motd, [@default_tips_file]) |> index_file.(@divider) + + def indices, do: @indices + @doc """ Pick one tip randomly """ - @spec random(keyword) :: tip - def random(opts \\ []) do - all(opts) - |> Enum.reject(&(&1 == "")) - |> Enum.random() + @spec random() :: {:ok, String.t()} | {:error, any} + def random() do + {location, how_many_bytes} = Enum.random(@indices) + read_file_at_location(tips_file(), location, how_many_bytes) + end + + defp tips_file do + # this is different from the one at compile time + Application.app_dir(:nerves_motd, [@default_tips_file]) + end + + defp read_file_at_location(filename, location, how_many_bytes) do + with {:ok, io_device} <- File.open(filename, [:read]), + {:ok, data} <- :file.pread(io_device, location, how_many_bytes), + :ok <- File.close(io_device), + do: {:ok, String.trim(data)} end end diff --git a/config/default_tips.txt b/priv/default_tips.txt similarity index 97% rename from config/default_tips.txt rename to priv/default_tips.txt index 083ed1f..d738dd8 100644 --- a/config/default_tips.txt +++ b/priv/default_tips.txt @@ -1,75 +1,75 @@ 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 - +%%% Configuring the Elixir Logger to show SASL reports can help debug unexpected GenServer restarts. https://hexdocs.pm/logger/Logger.html#module-erlang-otp-integration - +%%% 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 - +%%% Get all of the default packages for starting a new Nerves project by depending on `:nerves_pack` https://github.com/nerves-project/nerves_pack - +%%% 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 - +%%% 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. - +%%% Make small code changes to your running application by copy/pasting Elixir source files at the IEx prompt. - +%%% 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. - +%%% 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 - +%%% 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. - +%%% 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 - +%%% 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` - +%%% 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` - +%%% Run `Nerves.Runtime.revert` to go back to the previously loaded firmware. - +%%% See if someone has already implemented support for a sensor or other hardware device that you have by checking https://elixir-circuits.github.io/. - +%%% 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!) - +%%% 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 - +%%% 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 - +%%% Use `mix firmware.unpack` to decompress a local copy of your firmware on host and inspect the files within before installing on device - +%%% Use the `RamoopsLogger` backend to store log messages in DRAM that can survive unexpected reboots. https://github.com/smartrent/ramoops_logger - +%%% 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 diff --git a/test/nerves_motd/tips_test.exs b/test/nerves_motd/tips_test.exs index 643fe11..b56c497 100644 --- a/test/nerves_motd/tips_test.exs +++ b/test/nerves_motd/tips_test.exs @@ -3,22 +3,8 @@ defmodule NervesMOTD.TipsTest do alias NervesMOTD.Tips doctest NervesMOTD.Tips - @extra_tip """ - There are only two hard things in Computer Science: cache invalidation and naming things. - """ - - test "all/1 returns a list of default tips" do - assert [tip | _] = Tips.all() - assert is_binary(tip) - end - - test "all/1 with extra tips returns tips that contain extra tips" do - tips = Tips.all(extra_tips: [@extra_tip]) - assert @extra_tip in tips - end - - test "random/1 returns a string" do - tip = Tips.random(extra_tips: [@extra_tip]) + test "random/0 returns a string" do + assert {:ok, tip} = Tips.random() assert is_binary(tip) end end