Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 113 additions & 111 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,135 @@
[![Build Status](https://img.shields.io/endpoint?url=https%3A%2F%2Fci.ocamllabs.io%2Fbadge%2Frealworldocaml%2Fmdx%2Fmain&logo=ocaml)](https://ci.ocamllabs.io/github/realworldocaml/mdx)

## mdx -- executable code blocks inside markdown files
## MDX

`mdx` allows to execute code blocks inside markdown files.
There are (currently) two sub-commands, corresponding
to two modes of operations: pre-processing (`ocaml-mdx pp`)
and tests (`ocaml-mdx test`).
MDX allows to execute code blocks inside markdown and mli documentation
to help keeping them up to date.

The pre-processor mode allows to mix documentation and code,
and to practice "literate programming" using markdown and OCaml.
Use the
[dune stanza](https://dune.readthedocs.io/en/latest/dune-files.html#mdx-since-2-4)
to enable it on your documentation.

The test mode allows to ensure that shell scripts and OCaml fragments
in the documentation always stays up-to-date.

`mdx` is released as a single binary (called `ocaml-mdx`) and
can be installed using opam:
`mdx` is released on opam and can be installed by running:

```sh
$ opam install mdx
```

If you want to contribute or hack on the project, please see the
If you want to contribute to the project, please see the
[CONTRIBUTING.md](CONTRIBUTING.md).

### Basic Usage

You can use MDX with your Markdown or `.mli` documentation, which ensures
code in multi-line or verbatim code blocks is correct.

To enable MDX on specific files you must first enable it for your project by
adding the following stanza to your `dune-project`:
```
(using mdx 0.2)
```

Note that version `0.2` of the MDX stanza is only available in dune `3.0` or
higher. You can use the first, `0.1` version with dune `2.4` or higher.

Then add the following in the relevant `dune` file:
```
(mdx)
```
That enables MDX on all markdown files in the folder.
The MDX stanza can be further configured. Please visit the relevant section of
[dune's manual](https://dune.readthedocs.io/en/latest/dune-files.html#mdx-since-2-4)
for more information.

MDX supports various type of code blocks but the most common are OCaml toplevel
blocks. We illustrate one in our example below. In a Markdown file, you
would write something similar to this:

````markdown
Let's look at how good OCaml is with integers and strings:
```ocaml
# 1 + 2;;
- : int = 2
# "a" ^ "bc";;
- : string = "ab"
```
````
or in an `mli` file:
```ocaml
(** Let's look at how good OCaml is with integers and strings:
{@ocaml[
# 1 + 2;;
- : int = 2
# "a" ^ "bc";;
- : string = "ab"
]}
*)
```

The content of the toplevel blocks looks just like an interactive toplevel
session. Phrases, i.e., the toplevel "input", start with a `#` and end with `;;`.
The toplevel evaluation, or "output" follows each phrase.

Now you probably have noticed that `1 + 2` is not equal to `2` nor is `"a" ^ "bc"`
to `"ab"`. Somebody must have updated the phrases, but then forgot to update
the evaluation.

That's exactly why MDX is here!

If you enable MDX for this file and then ran `dune runtest`, this would be the
result:

````
$ dune runtest
File "README.md", line 1, characters 0-0:
git (internal) (exit 1)
(cd _build/default && /usr/bin/git --no-pager diff --no-index --color=always -u README.md .mdx/README.md.corrected)
diff --git a/README.md b/.mdx/README.md.corrected
index 181b86f..458ecec 100644
--- a/README.md
+++ b/.mdx/README.md.corrected
@@ -1,13 +1,13 @@
Let's look at how good OCaml is with integers and strings:
```ocaml
# 1 + 2;;
-- : int = 2
+- : int = 3
# "a" ^ "bc";;
-- : string = "ab"
+- : string = "abc"
```
````

The test run just failed and dune is showing the diff between what we have
locally and what should be, according to MDX.
This uses dune's promotion workflow so at this point you can either investigate
it further if you're surprised by this diff or if you're happy with it, simply
accept it by running:

```
dune promote
```

Now the documentation is up-to-date and running `dune runtest` again should be
successful!

### Supported Extensions

#### Labels

The blocks in markdown files can be parameterized by `mdx`-specific labels, that
The blocks can be parameterized by `mdx`-specific labels, that
will change the way `mdx` interprets the block.

The syntax is: `<!-- $MDX LABELS -->`, where `LABELS` is a list of valid labels
separated by a comma. This line has to immediately precede the block it is
attached to.
The markdown syntax is: `<!-- $MDX LABELS -->`, where `LABELS` is a list of
valid labels separated by a comma. This line has to immediately precede the
block it is attached to.

<!-- $MDX LABELS -->
```ocaml
```

This syntax is the recommended way to define labels since `mdx` 1.7.0, to use
the previous syntax please refer to the
[mdx 1.6.0 README](https://github.com/realworldocaml/mdx/blob/1.6.0/README.md).

It is also possible to use labels in OCaml interface files (`mli`), the syntax
for this is is slightly different to match the conventions of OCaml
documentation comments:
The `.mli` syntax for this is is slightly different to match the conventions of
OCaml documentation comments:

(** This is an documentation comment with an ocaml block
{@ocaml LABELS [
Expand Down Expand Up @@ -93,7 +178,7 @@ with a padding of 3:
10
```

`ocaml-mdx` will also consider exit codes when the syntax `[<exit code>]`is used:
MDX will also consider exit codes when the syntax `[<exit code>]`is used:

```sh
$ exit 1
Expand All @@ -105,7 +190,7 @@ of success).

#### OCaml Code

`ocaml-mdx` interprets OCaml fragments. It understands _normal_ code fragments and
MDX interprets OCaml fragments. It understands _normal_ code fragments and
_toplevel_ code fragments (starting with a `#` sign and optionally ending with
`;;`). Arbitrary whitespace padding is supported, at long as it stays
consistent within a code block.
Expand All @@ -126,7 +211,8 @@ Here is an examples of toplevel OCaml code:
```

### File sync
`mdx` is also capable of including content from files in fenced code blocks

MDX is also capable of including content from files in fenced code blocks
using the label `file`. OCaml files can be sliced using named blocks:

```ocaml
Expand All @@ -149,38 +235,6 @@ Non-OCaml files can also be read and included in a block:
```
However, part splitting is only supported for OCaml files.

### Pre-processing

`ocaml-mdx pp` allows to transform a markdown file into a valid
OCaml file, which can be passed to OCaml using the `-pp`
option.

For instance, given the following `file.md` document:

```ocaml
# print_endline "42"
42
```

Can be compiled and executed using:

```sh
$ ocamlc -pp 'ocaml-mdx pp' -impl file.md -o file.exe
$ ./file.exe
42
```

This can be automated using `dune`:

```
(rule
((targets (file.ml))
(deps (file.md))
(action (with-stdout-to ${@} (run ocaml-mdx pp ${<})))))

(executable ((name file)))
```

### Tests

#### Cram Tests
Expand Down Expand Up @@ -209,51 +263,6 @@ To execute OCaml code and toplevel fragments, uses `ocaml-mdx test <file.md>`.
If the output is not consistent with what is expected
`<file.md>.corrected` is generated.

#### Integration with Dune

To test that the code blocks of `file.md` stay consistent, one can use
dune's `mdx` stanza:

```
(mdx
(files file.md))
```

This allows to test the consistency of a markdown file using the normal dev
workflow:

```
$ dune runtest
```

will display a diff of the output if something has changed. For instance:

```
$ dune runtest
------ file.md
++++++ file.md.corrected
File "file.md", line 23, characters 0-1:
|
|```sh
-| $ for i in `seq 1 3`; do echo $i; done
+| $ for i in `seq 1 4`; do echo $i; done
| 1
| 2
| 3
+| 4
|```
```

And the changes can then be accepted using:

```
$ dune promote
```

For further details about the mdx stanza you should read the
[according section](https://dune.readthedocs.io/en/latest/dune-files.html#mdx-since-2-4)
in the dune documentation.

#### Non-deterministic Tests

**Non-deterministic Outputs**
Expand Down Expand Up @@ -373,10 +382,3 @@ Those variables are then available in the subsequent blocks
bar
- : unit = ()
```

### Sections

It is possible to test or execute only a subset of the file using
sections using the `--section` option (short name is `-s`). For
instance `ocaml-mdx pp -s foo` will only consider the section matching the
perl regular expression `foo`.