Elixir basics · Lesson 30 (open contents)
01 · Let the code speak02 · Meet six kinds of values03 · Put values together04 · Build a new list05 · Make the shapes match06 · Choose with case07 · Parse a number safely08 · Unpack &1 and the pipe09 · Put code in a project10 · Ask precise true-or-false questions11 · Read and update nested data12 · Count visible text and bytes13 · Let function clauses choose14 · Solve a list twice15 · Choose the smallest clear control form16 · Give domain data a shape17 · Put one tested module in Mix18 · Make success and failure predictable19 · Read and write one real file20 · Take only the data you need21 · Give different data one shared action22 · Write a callback contract23 · Write down the data contract24 · Test behavior and boundaries25 · Turn a project into a command26 · Write the project promise27 · Parse one real line28 · Keep the public API small29 · Make the promise executable30 · Package the edge and leave clues31 · Prove the project is done
ELIXIR · PROJECT · LESSON 3035 minutes

Package the edge and leave clues

Keep CLI parsing thin, log useful context, and build the escript.

01 · Start with the whole example

Run this first

Run it once, change one input, and compare the new result.

Elixir
# lib/cleaner/cli.ex
defmodule Cleaner.CLI do
  require Logger

  def main([path]) do
    with {:ok, text} <- File.read(path),
         {:ok, records} <- parse_lines(String.split(text, "\n")),
         {:ok, counts} <- Cleaner.run(records) do
      Logger.info("cleaned records path=#{inspect(path)} count=#{length(records)}")
      IO.inspect(counts, label: "levels")
      :ok
    else
      {:error, reason} -> fail(reason)
    end
  end

  def main(_args), do: {:error, :usage}

  defp parse_lines(lines) do
    Enum.reduce_while(lines, {:ok, []}, fn line, {:ok, records} ->
      case Cleaner.parse_line(line) do
        {:ok, record} -> {:cont, {:ok, [record | records]}}
        :skip -> {:cont, {:ok, records}}
        {:error, reason} -> {:halt, {:error, reason}}
      end
    end)
    |> then(fn
      {:ok, records} -> {:ok, Enum.reverse(records)}
      error -> error
    end)
  end

  defp fail(reason) do
    Logger.error("clean_failed reason=#{inspect(reason)}")
    {:error, reason}
  end
end
Check the result first
  • The CLI reads one file, calls the project API, logs the path and accepted record count, and prints the level summary.
02 · Take the code apart

Read from the first line down

  1. Read Logger.info/1: Write one useful operational message.
  2. Read main([path]): Accept exactly one file argument.
  3. Read mix escript.build: Package the command after tests pass.
03 · Meet the new symbols

Symbols are not secret signs

Logger.info/1

Write one useful operational message.

main([path])

Accept exactly one file argument.

mix escript.build

Package the command after tests pass.

04 · Ideas inside the code

Match each name to its meaning

01

thin entry point

A CLI entry point parses arguments, calls the API, prints a result, and does little else.

02

structured context

Log fields such as path and count separately from the message.

03

privacy boundary

Operational clues should not expose full user data or secrets.

05 · Make the idea clear

Why these forms are useful

Run the example first. Predict one result, then change one input and run it again.

A CLI entry point parses arguments, calls the API, prints a result, and does little else.

Log fields such as path and count separately from the message.

06 · Change it yourself

Close the answer and try

Return a usage error when the CLI receives no path.

Practice starting point
def main([]), do: ____

Target result: Return {:error, :usage}.

Stuck? Read one hint

Keep the error tagged.

After you run it, see one answer
One answer
def main([]), do: {:error, :usage}
Think about it: Should a log line copy every source line?

No. Record enough context to diagnose the run without exposing full input.

Take with you

Remember these three lines

  1. 1

    Keep CLI code thin.

  2. 2

    Log decisions and counts.

  3. 3

    Run tests before packaging.

Lesson completeRun the code once, then mark this lesson as done.