README.md

# Docception

[![Hex](https://img.shields.io/hexpm/v/docception.svg)](https://hex.pm/packages/docception)
![Build Status](https://github.com/evnu/docception/workflows/CI/badge.svg?branch=master)
![License](https://img.shields.io/hexpm/l/docception.svg)

Run doctests on arbitrary markdown files.

## Usage

    $ mix docception markdown-files

See the `example/` project on how to use an alias in `mix.exs` to run Docception with `mix test`.

## Installation

```elixir
def deps do
  [
    {:docception, "~> 0.3", runtime: false}
  ]
end
```

## Disclaimer

This tool executes any Elixir doctest it encounters (think `eval`). Ensure that it does not
encounter any harmful code!

## Example

This file can also be checked with docception. The following example is run through doctest and
results in an error:

    iex> :hello
    :crash

```
$ mix docception README.md
** (ExUnit.AssertionError)

Doctest failed
code:  :hello === :crash
left:  :hello
right: :crash
```

The following example works:

    iex> :hello
    :hello

## TODOs

* [x] Clean up temporary directory where `.beam` files are stored
* [x] Do not hard-code the temporary directory
* [x] Fix "wrong indent" warnings for heredocs
* [x] Try to fix wrong line number reports (the offset is always the same!)
* [x] Check if this also works when a doctest refers to dependencies of the project
* [x] Document how to use it with an alias in order to simplify running it in CI
* [x] Publish
* [ ] Determine how to ensure that errors are written before the task ends; avoid sleeping
      in mix task.

      The root cause of this is `ExUnit.CLIFormatter`. The formatter writes the error message.
      Docception can possibly exit before the formatter is done writing. This could be solvable
      by using a wrapper around `ExUnit.CLIFormatter` as a custom formatter instead. This custom
      formatter would then be shut down manually. Alas, I did not find a way to inject such a
      custom formatter.
* [x] Check if anybody is actually interested in this

## How..?

Docception's approach is pretty simple:

1. Read in a markdown file
1. Create an [Erlang Form](http://erlang.org/doc/apps/erts/absform.html) that makes the file
   look like something that can be executed.
   1. Add a module attribute
   1. Export an `__info__/1` function which wraps `module_info/1`
1. Compile that thing into a binary
1. Store the resulting `.beam` in the filesystem to make `Code.fetch_docs/1` work
1. Call into the undocumented-and-totally-not-for-public-use `ExUnit.DocTest.__doctests__/1`
   function
1. For each of the quoted doctests, spawn a process and call let it run the quoted doctest
1. Gather the results and raise on error

So, except for calling into `__doctests__/1`, this is pretty straight forward. Note that the
implementation relies heavily on the hard work that went into `elixir_erl.erl` from Elixir itself.
Docception needs to create a binary where the `'Docs'` chunk matches such chunks as generated by
`elixir_erl.erl`.