Packages

A more traditional experience for the :debugger module

Current section

Files

Jump to
exbug lib exbug.ex
Raw

lib/exbug.ex

defmodule Exbug do
@regex ~r/#EXBUG START(?s)(.*)#EXBUG STOP/
@on_load :clean
@moduledoc """
Provides a traditional debugging experince for Elixir
by providing a thin layer around the :debugger & :int modules.
To be used with `iex -S mix`.
**How it works**
Be warned, this is more of a hack than anything. I feel pretty dirty about
it, but here we go.
**Background**
the `iex` shell is a great tool. It's so great, it can event be customised
with a `.iex.exs` file. `IEx` will load this file if it's present in the
directory in which `iex` is run.
**iex.exs hack**
`Exbug.__using__/1` appends your `.iex.exs` file with the code needed to
start the Erlang graphical debugger and mark the hosting module for
debugging
**`debug/0`**
Next, to set a breakpoint you must use the `debug/0` function. It marks sets
a breakpoint on the same line the `debug()` statement is on. I'm sorry for
the next part.
Next, it uses `:timer.sleep/1` to sleep the calling process until the
:debugger and :int module have time to catch up to the calling process.
**Why would you do that?!**
As you would suspect, the :debugger & :int modules runs in a different
process to the calling module. If you _don't_ sleep the calling process,
the code will continue executing and the breakpoint won't be set in time.
Please don't use this in production.
**What about my existing .iex.exs file?**
Exbug uses deliminters to denote any appended code. The `@on_load` attribute
is used so that when the `Exbug` module is loaded, and previously appended
Exbug code is cleaned out and removed
**Example**
```
# controller.ex
defmodule Controller do
use Exbug
def show(conn, _) do
debug()
# ...
end
end
```
```
> mix compile --force && iex -S mix
...
iex> Controller.show(conn, nil)
# debugger is started and breakpoint is hit
```
"""
@doc """
Path to the projects .iex.exs file
"""
def dot_iex_path do
Path.join([File.cwd!, ".iex.exs"])
end
@doc """
Writes the provided contents to the projects .iex.exs file
"""
def write_to_dot_iex(contents, file \\ File) do
file.write(Exbug.dot_iex_path(), contents, [:append])
end
def dot_iex_content(module) do
"""
#EXBUG START
:debugger.start(:local)
:int.ni(#{module})
#EXBUG STOP
"""
end
@doc """
Cleans the .iex.exs file of all exbug statments
"""
def clean(file \\ File) do
case file.read(dot_iex_path()) do
{:ok, contents} ->
contents = Regex.replace(@regex, contents, "")
contents = String.trim(contents)
File.write!(dot_iex_path(), contents)
{:error, _} -> :ok
end
end
@doc """
Starts a debugger with the requested line and module marked for debugging
"""
defmacro __using__(_) do
quote do
import Exbug
__MODULE__
|> dot_iex_content
|> write_to_dot_iex
end
end
@doc """
adds a breakpoint
"""
defmacro debug do
quote do
line = Macro.Env.location(__ENV__) |> Keyword.get(:line)
:int.break(__MODULE__, line)
:timer.sleep(1)
end
end
end