# 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](https://hex.pm/packages/ratatouille).
## Installation
To use TioComodo in your project, add it to your list of dependencies in `mix.exs`.
```elixir
def deps do
[
{:tio_comodo, "~> 0.1.1"}
]
end
```
## Integration 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, []}`:
```elixir
# 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!"}
end
```
### 2. Configure the Simple Provider
Tell TioComodo to use your simple command map. In your `config/config.exs`, add the following:
```elixir
# 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:
```elixir
# 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:
```elixir
# 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!"}
# Catchall handler receives the full input string
def handle_unknown(input) do
{:ok, "I don't understand: #{input}. Try 'hello', 'time', or 'quit'."}
end
end
```
Configure both the simple provider and the catchall handler:
```elixir
# config/config.exs
import Config
config :tio_comodo,
simple_provider: {MyApp.Repl.Commands, :commands},
catchall_handler: {MyApp.Repl.Commands, :handle_unknown}
```
The 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`:
```elixir
# 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
end
```
Then configure TioComodo to use your custom command provider:
```elixir
# config/config.exs
import Config
config :tio_comodo,
provider: MyApp.Repl.CustomCommands
```
### 5. 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:
```elixir
# 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
end
```
**Explanation:**
- We `spawn_link` a new, lightweight process whose only job is to wait for a `:repl_terminated` message.
- When the user issues the `quit` command, the REPL server sends this message to the spawned process (its `parent`).
- Upon receiving the message, the process calls `:init.stop()`, which gracefully terminates the entire Erlang VM, ensuring your application exits cleanly.
- The `TioComodo.Repl.Server` is 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.
```bash
$ mix run --no-halt
my_app>
```
You can now use the `hello`, `time`, and `quit` commands.