{"id":13726260,"url":"https://github.com/realworldocaml/mdx","last_synced_at":"2025-04-12T19:41:51.362Z","repository":{"id":33253297,"uuid":"140317506","full_name":"realworldocaml/mdx","owner":"realworldocaml","description":"Execute code blocks inside your documentation","archived":false,"fork":false,"pushed_at":"2025-03-31T14:29:19.000Z","size":16617,"stargazers_count":277,"open_issues_count":59,"forks_count":46,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-03T23:09:07.774Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/realworldocaml.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-09T17:06:07.000Z","updated_at":"2025-03-31T14:29:23.000Z","dependencies_parsed_at":"2024-12-31T10:19:27.410Z","dependency_job_id":null,"html_url":"https://github.com/realworldocaml/mdx","commit_stats":null,"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realworldocaml%2Fmdx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realworldocaml%2Fmdx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realworldocaml%2Fmdx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realworldocaml%2Fmdx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/realworldocaml","download_url":"https://codeload.github.com/realworldocaml/mdx/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625107,"owners_count":21135510,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:02:57.275Z","updated_at":"2025-04-12T19:41:51.341Z","avatar_url":"https://github.com/realworldocaml.png","language":"OCaml","funding_links":[],"categories":["OCaml"],"sub_categories":[],"readme":"[![Build Status](https://img.shields.io/endpoint?url=https%3A%2F%2Fci.ocamllabs.io%2Fbadge%2Frealworldocaml%2Fmdx%2Fmain\u0026logo=ocaml)](https://ci.ocamllabs.io/github/realworldocaml/mdx)\n\n## MDX\n\nMDX allows to execute code blocks inside markdown and mli/mld documentation\nto help keeping them up to date.\n\nUse the\n[dune stanza][dune-mdx]\nto enable it on your documentation.\n\n[dune-mdx]: https://dune.readthedocs.io/en/stable/reference/dune/mdx.html\n\n`mdx` is released on opam and can be installed by running:\n\n```sh\n$ opam install mdx\n```\n\nIf you want to contribute to the project, please see the\n[CONTRIBUTING.md](CONTRIBUTING.md).\n\n### Basic Usage\n\nYou can use MDX with your Markdown or `.ml{i,d}` documentation, which ensures\ncode in multi-line or verbatim code blocks is correct.\n\nTo enable MDX on specific files you must first enable it for your project by\nadding the following stanza to your `dune-project`:\n```\n(using mdx 0.2)\n```\n\nNote that version `0.2` of the MDX stanza is only available in dune `3.0` or\nhigher. You can use the first, `0.1` version with dune `2.4` or higher.\n\nThen add the following in the relevant `dune` file:\n```\n(mdx)\n```\nThat enables MDX on all markdown files in the folder.\nThe MDX stanza can be further configured. Please visit the relevant section of\n[dune's manual][dune-mdx]\nfor more information.\n\nMDX supports various type of code blocks but the most common are OCaml toplevel\nblocks. We illustrate one in our example below. In a Markdown file, you\nwould write something similar to this:\n\n````markdown\nLet's look at how good OCaml is with integers and strings:\n```ocaml\n# 1 + 2;;\n- : int = 2\n# \"a\" ^ \"bc\";;\n- : string = \"ab\"\n```\n````\nor in an `mli` file:\n```ocaml\n(** Let's look at how good OCaml is with integers and strings:\n    {@ocaml[\n    # 1 + 2;;\n    - : int = 2\n    # \"a\" ^ \"bc\";;\n    - : string = \"ab\"\n    ]}\n*)\n```\n\nThe content of the toplevel blocks looks just like an interactive toplevel\nsession. Phrases, i.e., the toplevel \"input\", start with a `#` and end with `;;`.\nThe toplevel evaluation, or \"output\" follows each phrase.\n\nNow you probably have noticed that `1 + 2` is not equal to `2` nor is `\"a\" ^ \"bc\"`\nto `\"ab\"`. Somebody must have updated the phrases, but then forgot to update\nthe evaluation.\n\nThat's exactly why MDX is here!\n\nIf you enable MDX for this file and then ran `dune runtest`, this would be the\nresult:\n\n````\n$ dune runtest\nFile \"README.md\", line 1, characters 0-0:\n       git (internal) (exit 1)\n(cd _build/default \u0026\u0026 /usr/bin/git --no-pager diff --no-index --color=always -u README.md .mdx/README.md.corrected)\ndiff --git a/README.md b/.mdx/README.md.corrected\nindex 181b86f..458ecec 100644\n--- a/README.md\n+++ b/.mdx/README.md.corrected\n@@ -1,13 +1,13 @@\nLet's look at how good OCaml is with integers and strings:\n```ocaml\n# 1 + 2;;\n-- : int = 2\n+- : int = 3\n# \"a\" ^ \"bc\";;\n-- : string = \"ab\"\n+- : string = \"abc\"\n```\n````\n\nThe test run just failed and dune is showing the diff between what we have\nlocally and what should be, according to MDX.\nThis uses dune's promotion workflow so at this point you can either investigate\nit further if you're surprised by this diff or if you're happy with it, simply\naccept it by running:\n\n```\ndune promote\n```\n\nNow the documentation is up-to-date and running `dune runtest` again should be\nsuccessful!\n\nNote that to use the `dune runtest/promote` workflow with `mli` or `mld` files,\nyou will need to adjust the `mdx` stanza in the `dune` file, as by\n[default][dune-mdx],\nDune only checks markdown files with `mdx`.  E.g.,\n\n```\n(mdx\n (files :standard - *.mli))\n```\n\n### Supported Extensions\n\n#### Labels\n\nThe blocks can be parameterized by `mdx`-specific labels, that\nwill change the way `mdx` interprets the block.\n\nThe markdown syntax is: `\u003c!-- $MDX LABELS --\u003e`, where `LABELS` is a list of\nvalid labels separated by a comma. This line has to immediately precede the\nblock it is attached to.\n\n    \u003c!-- $MDX LABELS --\u003e\n    ```ocaml\n    ```\n\nThe `.mli` and `.mld` syntax for this is is slightly different to match the conventions of\nOCaml documentation comments:\n\n    (** This is an documentation comment with an ocaml block\n        {@ocaml LABELS [\n        ]}\n    *)\n\nThe possible labels are:\n\n- `skip` -- ignore this block\n- `ocaml`, `cram`, `toplevel`, `include` -- set the block type\n- `version=VERSION` -- set OCaml version\n- `non-deterministic[=output|command]` -- see \"Non-deterministic tests\" section\n- `dir=PATH` -- set the directory where the tests should be run\n- `source-tree=PATH` -- does nothing?\n- `file=PATH` -- see the \"File sync\" section\n- `part=PART` -- see the \"File sync\" section\n- `env=ENV` -- see the \"Named execution environments\" section\n- `set-VAR=VALUE` -- set an environment variable\n- `unset-VAR` -- unset an environment variable\n\n#### Shell Scripts\n\n`ocaml-mdx` interprets shell scripts inside `sh` code blocks as cram-like tests. The\nsyntax is the following:\n\n- Lines beginning with a dollar sign and a space are\n  *commands* and will be run in the shell.\n- Multi-lines commands end by `\\` and continue with two spaces and\n  a `\u003e` sign on the next line:\n\n       ```sh\n       $ \u003cline1\u003e \\\n       \u003e \u003cline2\u003e \\\n       \u003e \u003cline3\u003e\n       ```\n- Commands support the heredoc syntax (`\u003c\u003c`):\n\n       ```sh\n       $ cat \u003c\u003cEOF \\\n       \u003e hello\\\n       \u003e world\\\n       \u003e EOF\n       hello\n       world\n       ```\n- Lines beginning without a dollar sign are considered command *outputs*.\n- Command outputs can contain *ellipses*: `...`. These will\n  match any possible outputs (on zero, one or multiple lines).\n- Arbitrary padding with whitespace is supported, as long as it is consistent\n  inside a code block.\n\nHere is an example of a markdown file using shell scripts inside code blocks,\nwith a padding of 3:\n\n    ```sh\n       $ for i in `seq 1 10`\n       1\n       ...\n       10\n    ```\n\nMDX will also consider exit codes when the syntax `[\u003cexit code\u003e]`is used:\n\n    ```sh\n    $ exit 1\n    [1]\n    ```\n\nNote that nothing will be displayed when the exit code is 0 (e.g. in case\nof success).\n\n#### OCaml Code\n\nMDX interprets OCaml fragments. It understands _normal_ code fragments and\n_toplevel_ code fragments (starting with a `#` sign and optionally ending with\n`;;`). Arbitrary whitespace padding is supported, at long as it stays\nconsistent within a code block.\n\nToplevel fragments interleave OCaml code and their corresponding outputs.\n\nHere is an example of normal OCaml code:\n\n    ```ocaml\n    print_endline \"42\"\n    ```\n\nHere is an examples of toplevel OCaml code:\n\n    ```ocaml\n    # print_endline \"42\"\n    42\n    ```\n\n### File sync\n\nMDX is also capable of including content from files in fenced code blocks\nusing the label `file`. OCaml files can be sliced using named blocks:\n\n```ocaml\n(* $MDX part-begin=partName *)\nlet meaning_of_life () =\n  print_endline \"42\"\n(* $MDX part-end *)\n```\n\nThese can then be included in the document:\n\n    \u003c!-- $MDX file=sync_to_md.ml,part=partName --\u003e\n    ```ocaml\n    ```\n\nNon-OCaml files can also be read and included in a block:\n\n    \u003c!-- $MDX file=any_file.txt --\u003e\n    ```\n    ```\nHowever, part splitting is only supported for OCaml files.\n\n### Tests\n\n#### Cram Tests\n\nCram tests can be executed and checked with `ocaml-mdx test \u003cfile.md\u003e`.\n\n    ```sh\n     $ for i in `seq 1 10`; do echo $i; done\n     1\n     ...\n     10\n     ```\n\nIf the output is not consistent with what is expected,\n`\u003cfile.md\u003e.corrected` is generated.\n\n#### OCaml\n\nTo execute OCaml code and toplevel fragments, uses `ocaml-mdx test \u003cfile.md\u003e`.\n\n    ```ocaml\n    # print_endline \"42\"\n    42\n    ```\n\nIf the output is not consistent with what is expected\n`\u003cfile.md\u003e.corrected` is generated.\n\n#### Non-deterministic Tests\n\n**Non-deterministic Outputs**\n\n`ocaml-mdx test` supports non-deterministic outputs:\n\n    \u003c!-- $MDX non-deterministic=output --\u003e\n    ```sh\n    $ \u003ccommand\u003e\n    \u003coutput\u003e\n    ```\n\nIn that case, `ppx test \u003cfile\u003e` will run the command but will not\ngenerate `\u003cfile\u003e.corrected` if the new output differs from the one\ndescribed in the file. Use `ocaml-mdx test --non-deterministic \u003cfile\u003e` to come\nback to the default behaviour.\n\n**Non-deterministic Commands**\n\n`ocaml-mdx test` supports non-deterministic commands:\n\n    \u003c!-- $MDX non-deterministic=command --\u003e\n    ```ocaml\n    # Random.int 10;;\n    - : int = 5\n    ```\n\nIn that case, `ocaml-mdx test \u003cfile\u003e` will *not* run the command. Use `ocaml-mdx test\n--non-deterministic \u003cfile\u003e` to come back to the default behaviour.\n\nAlternatively, instead of passing the option it is also possible to set the\nenvironment variable `MDX_RUN_NON_DETERMINISTIC` to make MDX execute\nnon-deterministic blocks. This is useful when not calling MDX directly but\nthrough other commands like `dune` or Makefiles etc. Use\n`MDX_RUN_NON_DETERMINISTIC=1 ocaml-mdx test` in this case.\n\n#### Named execution environments (since mdx 1.1.0)\n\nSeparate environments can be defined for blocks:\n\n`x` holds the value `1` in the environment `e1`.\n\n    \u003c!-- $MDX env=e1 --\u003e\n    ```ocaml\n    let x = 1;;\n    ```\n\n    \u003c!-- $MDX env=e1 --\u003e\n    ```ocaml\n    module M = struct let k = 42 let f x = x * k end;;\n    ```\n\n`x` holds the value `3` in the environment `e2`.\n\n    \u003c!-- $MDX env=e2 --\u003e\n    ```ocaml\n    let x = 3;;\n    ```\n\nWe can retrieve the value of `x` in environment `e1`:\n\n    \u003c!-- $MDX env=e1 --\u003e\n    ```ocaml\n    # print_int x;;\n    1\n    - : unit = ()\n    # print_int M.k;;\n    42\n    - : unit = ()\n    # M.f;;\n    - : int -\u003e int = \u003cfun\u003e\n    ```\n\n#### Matching on the OCaml version (since mdx 1.2.0)\n\nBlocks can be processed or ignored depending on the current version of OCaml.\n\nFor example to have a different outcome whether we are past OCaml 4.06:\n\n    \u003c!-- $MDX version\u003c4.06 --\u003e\n    ```ocaml\n    # let f x = x + 1\n    val f : int -\u003e int = \u003cfun\u003e\n    # let f y =\n      y^\"foo\"\n    val f : bytes -\u003e bytes = \u003cfun\u003e\n    ```\n\n    \u003c!-- $MDX version\u003e=4.06 --\u003e\n    ```ocaml\n    # let f x = x + 1\n    val f : int -\u003e int = \u003cfun\u003e\n    # let f y =\n      y^\"foo\"\n    val f : string -\u003e string = \u003cfun\u003e\n    ```\n\nThe available operators are `\u003c\u003e`, `\u003e=`, `\u003e`, `\u003c=`, `\u003c` and `=`.\nThe version number can be of the following forms:\n- `*`\n- `X`\n- `X.Y`\n- `X.Y.Z`\n\n#### Matching based on the `os_type` (since mdx 2.4.0)\n\nBlock can be processed or ignored depending on the current\n[`os_type`](https://v2.ocaml.org/api/Sys.html#VALos_type).\n\nFor example, different blocks could be enabled depending on whether we are on\nWindows or not:\n\n    ```ocaml\n    #require \"unix\"\n    ```\n\n    \u003c!-- $MDX os_type\u003c\u003eWin32 --\u003e\n    ```ocaml\n    # Unix.nice 0\n    - : int = 0\n    ```\n\n    \u003c!-- $MDX os_type=Win32 --\u003e\n    ```ocaml\n    # Unix.nice 0\n    Exception: Invalid_argument \"Unix.nice not implemented\".\n    ```\n\nThe `os_type` values should be written in ASCII and are compared case\ninsensitively.\n\n#### Environment variables declaration\n\nEnvironment variables can be declared at the beginning of a block:\n\n    \u003c!-- $MDX set-FOO=bar,set-BAR=foo --\u003e\n    ```ocaml\n    # print_endline (Sys.getenv \"FOO\")\n    bar\n    - : unit = ()\n    # print_endline (Sys.getenv \"BAR\")\n    foo\n    - : unit = ()\n    ```\n\nThose variables are then available in the subsequent blocks\n\n    ```ocaml\n    # print_endline (Sys.getenv \"FOO\")\n    bar\n    - : unit = ()\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealworldocaml%2Fmdx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frealworldocaml%2Fmdx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealworldocaml%2Fmdx/lists"}