TioComodo
TioComodo provides a simple, embeddable Read-Eval-Print Loop (REPL) for your Elixir applications. It allows you to define a custom set of commands and run them in an interactive terminal session, for a simple terminal I/O application. TioComodo avoids native dependencies for ease of use. For a more advanced TUI, look at other packages like Ratatouille.
Installation
To use TioComodo in your project, add it to your list of dependencies in mix.exs.
def deps do
[
{:tio_comodo, "~> 0.1.2"}
]
endIntegration Guide
Follow these steps to integrate the TioComodo REPL into your Elixir application.
1. Create Commands Using the Simple Provider (Recommended)
The easiest way to add commands to your REPL is to use the built-in default provider TioComodo.Repl.Provider and supply a simple command map via :simple_provider.
Create a module that exposes commands/0 returning a map of command names to {module, function, []}:
# lib/my_app/repl/commands.ex
defmodule MyApp.Repl.Commands do
@moduledoc "Commands for the REPL"
def commands do
%{
"hello" => {__MODULE__, :hello, []},
"time" => {__MODULE__, :time, []},
"quit" => {__MODULE__, :quit, []}
}
end
def hello(args), do: {:ok, "Hello, #{Enum.join(args, " ")}!"}
def time(_args), do: {:ok, "Current time is: #{DateTime.utc_now() |> DateTime.to_string()}"}
def quit(_args), do: {:stop, :normal, "Goodbye!"}
end2. Configure the Simple Provider
Tell TioComodo to use your simple command map. In your config/config.exs, add the following:
# config/config.exs
import Config
config :tio_comodo,
simple_provider: {MyApp.Repl.Commands, :commands}
With this setup, you do not need to implement the full provider behaviour; the default provider will dispatch commands based on your commands/0 map and also supply tab-completions from the command names.
3. Configure Colorscheme (Optional)
TioComodo includes a beautiful default colorscheme inspired by Gruvbox, but you can customize the colors to match your preferences. In your config/config.exs, add a colorscheme configuration:
# config/config.exs
import Config
config :tio_comodo,
simple_provider: {MyApp.Repl.Commands, :commands},
colorscheme: [
user: :green, # Color for user input
background: :black, # Background color
prompt: :blue, # Prompt color
error: :red, # Error message color
success: :green, # Success message color
warning: :yellow, # Warning message color
info: :blue, # Info message color
completion: :cyan # Tab completion color
]
Available colors include standard terminal colors like :red, :green, :blue, :yellow, :cyan, :magenta, :white, and :black. You can also use lighter versions like :light_red, :light_green, etc. Note that colors must be specified as atoms (with colons), not as strings.
Optional: Add a Catchall Handler
You can optionally configure a catchall handler that will receive any input that doesn't match a defined command:
# lib/my_app/repl/commands.ex
defmodule MyApp.Repl.Commands do
@moduledoc "Commands for the REPL"
def commands do
%{
"hello" => {__MODULE__, :hello, []},
"time" => {__MODULE__, :time, []},
"quit" => {__MODULE__, :quit, []},
"catchall_handler" =>, {__MODULE__, :handle_unknown, []}
}
end
def hello(args), do: {:ok, "Hello, #{Enum.join(args, " ")}!"}
def time(_args), do: {:ok, "Current time is: #{DateTime.utc_now() |> DateTime.to_string()}"}
def quit(_args), do: {:stop, :normal, "Goodbye!"}
# Catchall handler receives the full input string
def handle_unknown(input) do
{:ok, "I don't understand: #{input}. Try 'hello', 'time', or 'quit'."}
end
endThe catchall handler will not appear in tab-completion suggestions.
4. Alternative: Create a Custom Command Provider
If you need more control over command parsing and dispatch, you can implement a full command provider module.
Create a new file, for example, at lib/my_app/repl/custom_commands.ex:
# lib/my_app/repl/custom_commands.ex
defmodule MyApp.Repl.CustomCommands do
@moduledoc "Provides commands for the interactive REPL."
@doc "Handles command dispatch."
def dispatch(command_line) do
# Simple parsing: command is the first word, args are the rest.
[command | args] = String.split(command_line)
case command do
"hello" -> hello(args)
"time" -> time(args)
"quit" -> quit(args)
_ -> {:error, "Unknown command: #{command}"}
end
end
# Command Implementations
defp hello([]), do: {:ok, "Hello, World!"}
defp hello(args), do: {:ok, "Hello, #{Enum.join(args, " ")}!"}
defp time(_args) do
{:ok, "Current time is: #{DateTime.utc_now() |> DateTime.to_string()}"}
end
defp quit(_args) do
# This special tuple signals the REPL server to stop.
{:stop, :normal, "Goodbye!"}
end
endThen configure TioComodo to use your custom command provider:
# config/config.exs
import Config
config :tio_comodo,
provider: MyApp.Repl.CustomCommands5. Update Your Application Supervisor
To run the REPL when your application starts, you need to add the TioComodo.Repl.Server to your application's supervision tree. You also need a lightweight process to listen for the REPL's termination signal to ensure a clean shutdown of the entire application.
Modify your lib/my_app/application.ex file:
# lib/my_app/application.ex
defmodule MyApp.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
# This process waits for the REPL to terminate, then stops the entire VM.
parent = spawn_link(fn ->
receive do
:repl_terminated -> :init.stop()
end
end)
children = [
# Start the TioComodo REPL server, passing it the parent PID.
# The server will send :repl_terminated to the parent when it exits.
{TioComodo.Repl.Server, prompt: "my_app> ", name: MyApp.Repl, parent: parent}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
endExplanation:
-
We
spawn_linka new, lightweight process whose only job is to wait for a:repl_terminatedmessage. -
When the user issues the
quitcommand, the REPL server sends this message to the spawned process (itsparent). -
Upon receiving the message, the process calls
:init.stop(), which gracefully terminates the entire Erlang VM, ensuring your application exits cleanly. -
The
TioComodo.Repl.Serveris started as a child in the supervision tree, configured with a custom prompt and the parent's PID.
6. Run Your Application
Now you are ready to run your application's REPL. Use the following command, and the interactive prompt will appear.
$ mix run --no-halt
my_app>
You can now use the hello, time, and quit commands.
External Output
TioComodo supports displaying output from external processes without interrupting user input. This is useful for long-running processes, notifications, or any background activity that needs to communicate with the user.
Basic Usage
Use the output/1 function to send messages to the REPL from any process:
# Send output to the default REPL server
TioComodo.Repl.Server.output("Process completed!")
# Send output to a named REPL server
TioComodo.Repl.Server.output(MyApp.Repl, "Custom message")Example: Long-Running Process
Here's a complete example showing how to handle long-running processes:
# lib/my_app/repl/commands.ex
defmodule MyApp.Repl.Commands do
def commands do
%{
"hello" => {__MODULE__, :hello, []},
"long_task" => {__MODULE__, :long_task, []},
"quit" => {__MODULE__, :quit, []}
}
end
def hello(args), do: {:ok, "Hello, #{Enum.join(args, " ")}!"}
def long_task(_args) do
# Start the long-running process
spawn(fn ->
TioComodo.Repl.Server.output("Starting long task...")
# Simulate work
Process.sleep(2000)
TioComodo.Repl.Server.output("Task 50% complete...")
Process.sleep(2000)
TioComodo.Repl.Server.output("Task completed successfully!")
end)
{:ok, "Long task started in background"}
end
def quit(_args), do: {:stop, :normal, "Goodbye!"}
endHow It Works
- External output appears immediately without interrupting keyboard input
- The current input line is preserved and redrawn after external output
- Multiple external processes can send output concurrently
- Output is displayed with proper newline handling for terminal formatting
Use Cases
- Background jobs: Notify users when long-running tasks complete
- System monitoring: Display real-time status updates
- Notifications: Show alerts or warnings from other parts of your application
- Progress updates: Provide feedback during lengthy operations