From 8e0201452c5dfe18b9e8140dea7dbce96e578d8e Mon Sep 17 00:00:00 2001 From: Nathan Rebours Date: Thu, 21 Jul 2022 11:41:40 +0200 Subject: [PATCH 1/6] Introduce new syntax for explicit block type This introduce a new, more explicit labels `block-type=...` which is meant to replace the four separate labels `cram`, `ocaml`, `toplevel` and `include` in the future. It is added for a transition period at the end of which the four labels of the apocalypse should be removed in favor of this new one. Signed-off-by: Nathan Rebours --- CHANGES.md | 3 + lib/block.ml | 2 +- lib/label.ml | 82 ++++++++++++------- lib/label.mli | 4 +- .../mdx-test/expect/block-type/test-case.md | 21 +++++ .../expect/block-type/test-case.md.expected | 23 ++++++ test/bin/mdx-test/expect/dune.inc | 12 +++ .../failure/block-type-value/test-case.md | 12 +++ .../block-type-value/test-case.md.expected | 2 + test/bin/mdx-test/failure/dune.inc | 26 ++++++ .../include-without-file-label/test-case.md | 5 ++ .../test-case.md.expected | 1 + test/lib/test_block.ml | 4 +- 13 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 test/bin/mdx-test/expect/block-type/test-case.md create mode 100644 test/bin/mdx-test/expect/block-type/test-case.md.expected create mode 100644 test/bin/mdx-test/failure/block-type-value/test-case.md create mode 100644 test/bin/mdx-test/failure/block-type-value/test-case.md.expected create mode 100644 test/bin/mdx-test/failure/include-without-file-label/test-case.md create mode 100644 test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected diff --git a/CHANGES.md b/CHANGES.md index a10ab881c..1731c5a89 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,9 @@ #### Added - Report all parsing errors in Markdown files (#389, @NathanReb) +- Add alternative syntax for explicitly setting the block-type. + The new label `block-type=...` can be set to `ocaml`, `toplevel`, `cram` or + `include`. (#, @NathanReb) #### Changed diff --git a/lib/block.ml b/lib/block.ml index 4a53ce329..9ebe0270f 100644 --- a/lib/block.ml +++ b/lib/block.ml @@ -414,7 +414,7 @@ let infer_block ~loc ~config ~header ~contents ~errors = let mk ~loc ~section ~labels ~legacy_labels ~header ~contents ~errors = let block_kind = - get_label (function Block_kind x -> Some x | _ -> None) labels + get_label (function Block_type x -> Some x | _ -> None) labels in let config = get_block_config labels in (match block_kind with diff --git a/lib/label.ml b/lib/label.ml index f9f93d73d..f128c74c3 100644 --- a/lib/label.ml +++ b/lib/label.ml @@ -71,7 +71,13 @@ type non_det = Nd_output | Nd_command let default_non_det = Nd_output -type block_kind = OCaml | Cram | Toplevel | Include +type block_type = OCaml | Cram | Toplevel | Include + +let pp_block_type ppf = function + | OCaml -> Fmt.string ppf "ocaml" + | Cram -> Fmt.string ppf "cram" + | Toplevel -> Fmt.string ppf "toplevel" + | Include -> Fmt.string ppf "include" type t = | Dir of string @@ -84,13 +90,7 @@ type t = | Version of Relation.t * Ocaml_version.t | Set of string * string | Unset of string - | Block_kind of block_kind - -let pp_block_kind ppf = function - | OCaml -> Fmt.string ppf "ocaml" - | Cram -> Fmt.string ppf "cram" - | Toplevel -> Fmt.string ppf "toplevel" - | Include -> Fmt.string ppf "include" + | Block_type of block_type let pp ppf = function | Dir d -> Fmt.pf ppf "dir=%s" d @@ -106,7 +106,7 @@ let pp ppf = function Fmt.pf ppf "version%a%a" Relation.pp op Ocaml_version.pp v | Set (v, x) -> Fmt.pf ppf "set-%s=%s" v x | Unset x -> Fmt.pf ppf "unset-%s" x - | Block_kind bk -> pp_block_kind ppf bk + | Block_type bt -> Fmt.pf ppf "block-type=%a" pp_block_type bt let is_prefix ~prefix s = let len_prefix = String.length prefix in @@ -140,41 +140,65 @@ let requires_value ~label ~value f = let requires_eq_value ~label ~value f = requires_value ~label ~value (fun op value -> - match op with Relation.Eq -> Ok (f value) | _ -> non_eq_op ~label) + match op with Relation.Eq -> f value | _ -> non_eq_op ~label) + +let version_of_string s = + match Ocaml_version.of_string s with + | Ok v -> Ok v + | Error (`Msg e) -> Util.Result.errorf "Invalid version: %s." e + +let parse_non_det_value ~label s = + match s with + | "output" -> Ok Nd_output + | "command" -> Ok Nd_command + | s -> + let allowed_values = [ ""; {|"command"|}; {|"output"|} ] in + invalid_value ~label ~allowed_values s + +let parse_block_type_value ~label s = + match s with + | "ocaml" -> Ok OCaml + | "cram" -> Ok Cram + | "toplevel" -> Ok Toplevel + | "include" -> Ok Include + | s -> + let allowed_values = + [ {|"ocaml"|}; {|"cram"|}; {|"toplevel"|}; {|"include"|} ] + in + invalid_value ~label ~allowed_values s let interpret label value = + let open Util.Result.Infix in match label with | "skip" -> doesnt_accept_value ~label ~value Skip - | "ocaml" -> doesnt_accept_value ~label ~value (Block_kind OCaml) - | "cram" -> doesnt_accept_value ~label ~value (Block_kind Cram) - | "toplevel" -> doesnt_accept_value ~label ~value (Block_kind Toplevel) - | "include" -> doesnt_accept_value ~label ~value (Block_kind Include) + | "ocaml" -> doesnt_accept_value ~label ~value (Block_type OCaml) + | "cram" -> doesnt_accept_value ~label ~value (Block_type Cram) + | "toplevel" -> doesnt_accept_value ~label ~value (Block_type Toplevel) + | "include" -> doesnt_accept_value ~label ~value (Block_type Include) | v when is_prefix ~prefix:"unset-" v -> doesnt_accept_value ~label ~value (Unset (split_prefix ~prefix:"unset-" v)) | "version" -> requires_value ~label ~value (fun op v -> - match Ocaml_version.of_string v with - | Ok v -> Ok (Version (op, v)) - | Error (`Msg e) -> - Util.Result.errorf "Invalid `version` label value: %s." e) + version_of_string v >>= fun v -> Ok (Version (op, v))) | "non-deterministic" -> ( match value with | None -> Ok (Non_det None) - | Some (Relation.Eq, "output") -> Ok (Non_det (Some Nd_output)) - | Some (Relation.Eq, "command") -> Ok (Non_det (Some Nd_command)) - | Some (Relation.Eq, v) -> - let allowed_values = [ ""; {|"command"|}; {|"output"|} ] in - invalid_value ~label ~allowed_values v + | Some (Relation.Eq, s) -> + parse_non_det_value ~label s >>= fun nd -> Ok (Non_det (Some nd)) | Some _ -> non_eq_op ~label) - | "dir" -> requires_eq_value ~label ~value (fun x -> Dir x) - | "source-tree" -> requires_eq_value ~label ~value (fun x -> Source_tree x) - | "file" -> requires_eq_value ~label ~value (fun x -> File x) - | "part" -> requires_eq_value ~label ~value (fun x -> Part x) - | "env" -> requires_eq_value ~label ~value (fun x -> Env x) + | "dir" -> requires_eq_value ~label ~value (fun x -> Ok (Dir x)) + | "source-tree" -> + requires_eq_value ~label ~value (fun x -> Ok (Source_tree x)) + | "file" -> requires_eq_value ~label ~value (fun x -> Ok (File x)) + | "part" -> requires_eq_value ~label ~value (fun x -> Ok (Part x)) + | "env" -> requires_eq_value ~label ~value (fun x -> Ok (Env x)) + | "block-type" -> + requires_eq_value ~label ~value (fun x -> + parse_block_type_value ~label x >>= fun bt -> Ok (Block_type bt)) | l when is_prefix ~prefix:"set-" l -> requires_eq_value ~label ~value (fun x -> - Set (split_prefix ~prefix:"set-" l, x)) + Ok (Set (split_prefix ~prefix:"set-" l, x))) | l -> Error (`Msg (Format.sprintf "`%s` is not a valid label." l)) let of_string s = diff --git a/lib/label.mli b/lib/label.mli index fdba0a603..5ae377bfc 100644 --- a/lib/label.mli +++ b/lib/label.mli @@ -29,7 +29,7 @@ type non_det = Nd_output | Nd_command val default_non_det : non_det -type block_kind = OCaml | Cram | Toplevel | Include +type block_type = OCaml | Cram | Toplevel | Include type t = | Dir of string @@ -42,7 +42,7 @@ type t = | Version of Relation.t * Ocaml_version.t | Set of string * string | Unset of string - | Block_kind of block_kind + | Block_type of block_type val pp : Format.formatter -> t -> unit diff --git a/test/bin/mdx-test/expect/block-type/test-case.md b/test/bin/mdx-test/expect/block-type/test-case.md new file mode 100644 index 000000000..fc7370cb1 --- /dev/null +++ b/test/bin/mdx-test/expect/block-type/test-case.md @@ -0,0 +1,21 @@ +It is possible to explicitly state the type of a block using the +`block-type` label, working around language header and content based +inference which can sometime lead to troublesome error messages. + +The following blocks use a volontarily misleading language header that would +normally lead to errors if we let MDX infer the type of block based on them. + +```sh block-type=toplevel +# 1 + 1;; +``` + +```sh block-type=ocaml +let x = 2 +``` + +```ocaml block-type=cram +$ echo "boom" +``` + +The include block type is somewhat redundant with the `file=...` label as +so it is not tested here. diff --git a/test/bin/mdx-test/expect/block-type/test-case.md.expected b/test/bin/mdx-test/expect/block-type/test-case.md.expected new file mode 100644 index 000000000..2060729ea --- /dev/null +++ b/test/bin/mdx-test/expect/block-type/test-case.md.expected @@ -0,0 +1,23 @@ +It is possible to explicitly state the type of a block using the +`block-type` label, working around language header and content based +inference which can sometime lead to troublesome error messages. + +The following blocks use a volontarily misleading language header that would +normally lead to errors if we let MDX infer the type of block based on them. + +```ocaml block-type=toplevel +# 1 + 1;; +- : int = 2 +``` + +```ocaml block-type=ocaml +let x = 2 +``` + +```sh block-type=cram +$ echo "boom" +boom +``` + +The include block type is somewhat redundant with the `file=...` label as +so it is not tested here. diff --git a/test/bin/mdx-test/expect/dune.inc b/test/bin/mdx-test/expect/dune.inc index 54b10eeba..c66e114c3 100644 --- a/test/bin/mdx-test/expect/dune.inc +++ b/test/bin/mdx-test/expect/dune.inc @@ -11,6 +11,18 @@ (alias runtest) (action (diff bash-fence/test-case.md.expected bash-fence.actual))) +(rule + (target block-type.actual) + (deps (package mdx) (source_tree block-type)) + (action + (with-stdout-to %{target} + (chdir block-type + (run ocaml-mdx test --output - test-case.md))))) + +(rule + (alias runtest) + (action (diff block-type/test-case.md.expected block-type.actual))) + (rule (target casual-file-inc.actual) (deps (package mdx) (source_tree casual-file-inc)) diff --git a/test/bin/mdx-test/failure/block-type-value/test-case.md b/test/bin/mdx-test/failure/block-type-value/test-case.md new file mode 100644 index 000000000..b0c586cba --- /dev/null +++ b/test/bin/mdx-test/failure/block-type-value/test-case.md @@ -0,0 +1,12 @@ +This tests that erros are properly reported when the `block-type` label +is misused. + +It requires a value + +```ocaml block-type +``` + +It only accept a fixed set of values + +```ocaml block-type=invalid +``` diff --git a/test/bin/mdx-test/failure/block-type-value/test-case.md.expected b/test/bin/mdx-test/failure/block-type-value/test-case.md.expected new file mode 100644 index 000000000..a94e21377 --- /dev/null +++ b/test/bin/mdx-test/failure/block-type-value/test-case.md.expected @@ -0,0 +1,2 @@ +[mdx] Fatal error: File "test-case.md", lines 6-7: invalid code block: Label `block-type` requires a value. +[mdx] Fatal error: File "test-case.md", lines 11-12: invalid code block: "invalid" is not a valid value for label `block-type`. Valid values are "ocaml", "cram", "toplevel" and "include". diff --git a/test/bin/mdx-test/failure/dune.inc b/test/bin/mdx-test/failure/dune.inc index 9bf381ab6..87739745f 100644 --- a/test/bin/mdx-test/failure/dune.inc +++ b/test/bin/mdx-test/failure/dune.inc @@ -12,6 +12,19 @@ (alias runtest) (action (diff block-locations/test-case.md.expected block-locations.actual))) +(rule + (target block-type-value.actual) + (deps (package mdx) (source_tree block-type-value)) + (action + (with-accepted-exit-codes 1 + (with-outputs-to %{target} + (chdir block-type-value + (run %{bin:ocaml-mdx} test test-case.md)))))) + +(rule + (alias runtest) + (action (diff block-type-value/test-case.md.expected block-type-value.actual))) + (rule (target both-prelude.actual) (deps (package mdx) (source_tree both-prelude)) @@ -65,6 +78,19 @@ (enabled_if (<> %{os_type} Win32)) (action (diff in-toplevel/test-case.md.expected in-toplevel.actual))) +(rule + (target include-without-file-label.actual) + (deps (package mdx) (source_tree include-without-file-label)) + (action + (with-accepted-exit-codes 1 + (with-outputs-to %{target} + (chdir include-without-file-label + (run %{bin:ocaml-mdx} test test-case.md)))))) + +(rule + (alias runtest) + (action (diff include-without-file-label/test-case.md.expected include-without-file-label.actual))) + (rule (target invalid-label.actual) (deps (package mdx) (source_tree invalid-label)) diff --git a/test/bin/mdx-test/failure/include-without-file-label/test-case.md b/test/bin/mdx-test/failure/include-without-file-label/test-case.md new file mode 100644 index 000000000..1d7279202 --- /dev/null +++ b/test/bin/mdx-test/failure/include-without-file-label/test-case.md @@ -0,0 +1,5 @@ +Explicitly setting the block-type to include has little benefits except +for warning you that the `file=...` label is mandatory. + +```ocaml block-type=include +``` diff --git a/test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected b/test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected new file mode 100644 index 000000000..5f6e485ae --- /dev/null +++ b/test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected @@ -0,0 +1 @@ +[mdx] Fatal error: File "test-case.md", lines 4-5: invalid code block: `file` label is required for include blocks. diff --git a/test/lib/test_block.ml b/test/lib/test_block.ml index 86312d883..f9d687bf0 100644 --- a/test/lib/test_block.ml +++ b/test/lib/test_block.ml @@ -38,10 +38,10 @@ let test_mk = (test_name, `Quick, test_fun) in [ - make_test ~name:"invalid ocaml" ~labels:[ Block_kind OCaml ] + make_test ~name:"invalid ocaml" ~labels:[ Block_type OCaml ] ~header:(Some OCaml) ~contents:[ "# let x = 2;;" ] ~expected:(Error (`Msg "toplevel syntax is not allowed in OCaml blocks.")); - make_test ~name:"invalid toplevel" ~labels:[ Block_kind Toplevel ] + make_test ~name:"invalid toplevel" ~labels:[ Block_type Toplevel ] ~header:(Some OCaml) ~contents:[ "let x = 2;;" ] ~expected:(Error (`Msg "invalid toplevel syntax in toplevel blocks.")); ] From 53bfb95954bd048955f1082a77b7d031a5ea6cac Mon Sep 17 00:00:00 2001 From: Nathan Rebours Date: Fri, 22 Jul 2022 12:02:00 +0200 Subject: [PATCH 2/6] Rename block-type label to type Signed-off-by: Nathan Rebours --- lib/label.ml | 4 ++-- test/bin/mdx-test/expect/block-type/test-case.md | 9 ++++++--- .../bin/mdx-test/expect/block-type/test-case.md.expected | 9 ++++++--- test/bin/mdx-test/failure/block-type-value/test-case.md | 6 ++++-- .../failure/block-type-value/test-case.md.expected | 4 ++-- .../failure/include-without-file-label/test-case.md | 3 ++- .../include-without-file-label/test-case.md.expected | 2 +- 7 files changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/label.ml b/lib/label.ml index f128c74c3..3c31ceded 100644 --- a/lib/label.ml +++ b/lib/label.ml @@ -106,7 +106,7 @@ let pp ppf = function Fmt.pf ppf "version%a%a" Relation.pp op Ocaml_version.pp v | Set (v, x) -> Fmt.pf ppf "set-%s=%s" v x | Unset x -> Fmt.pf ppf "unset-%s" x - | Block_type bt -> Fmt.pf ppf "block-type=%a" pp_block_type bt + | Block_type bt -> Fmt.pf ppf "type=%a" pp_block_type bt let is_prefix ~prefix s = let len_prefix = String.length prefix in @@ -193,7 +193,7 @@ let interpret label value = | "file" -> requires_eq_value ~label ~value (fun x -> Ok (File x)) | "part" -> requires_eq_value ~label ~value (fun x -> Ok (Part x)) | "env" -> requires_eq_value ~label ~value (fun x -> Ok (Env x)) - | "block-type" -> + | "type" -> requires_eq_value ~label ~value (fun x -> parse_block_type_value ~label x >>= fun bt -> Ok (Block_type bt)) | l when is_prefix ~prefix:"set-" l -> diff --git a/test/bin/mdx-test/expect/block-type/test-case.md b/test/bin/mdx-test/expect/block-type/test-case.md index fc7370cb1..98b4bd329 100644 --- a/test/bin/mdx-test/expect/block-type/test-case.md +++ b/test/bin/mdx-test/expect/block-type/test-case.md @@ -5,15 +5,18 @@ inference which can sometime lead to troublesome error messages. The following blocks use a volontarily misleading language header that would normally lead to errors if we let MDX infer the type of block based on them. -```sh block-type=toplevel + +```sh # 1 + 1;; ``` -```sh block-type=ocaml + +```sh let x = 2 ``` -```ocaml block-type=cram + +```ocaml $ echo "boom" ``` diff --git a/test/bin/mdx-test/expect/block-type/test-case.md.expected b/test/bin/mdx-test/expect/block-type/test-case.md.expected index 2060729ea..d152dcb8e 100644 --- a/test/bin/mdx-test/expect/block-type/test-case.md.expected +++ b/test/bin/mdx-test/expect/block-type/test-case.md.expected @@ -5,16 +5,19 @@ inference which can sometime lead to troublesome error messages. The following blocks use a volontarily misleading language header that would normally lead to errors if we let MDX infer the type of block based on them. -```ocaml block-type=toplevel + +```ocaml # 1 + 1;; - : int = 2 ``` -```ocaml block-type=ocaml + +```ocaml let x = 2 ``` -```sh block-type=cram + +```sh $ echo "boom" boom ``` diff --git a/test/bin/mdx-test/failure/block-type-value/test-case.md b/test/bin/mdx-test/failure/block-type-value/test-case.md index b0c586cba..71e020bda 100644 --- a/test/bin/mdx-test/failure/block-type-value/test-case.md +++ b/test/bin/mdx-test/failure/block-type-value/test-case.md @@ -3,10 +3,12 @@ is misused. It requires a value -```ocaml block-type + +```ocaml ``` It only accept a fixed set of values -```ocaml block-type=invalid + +```ocaml ``` diff --git a/test/bin/mdx-test/failure/block-type-value/test-case.md.expected b/test/bin/mdx-test/failure/block-type-value/test-case.md.expected index a94e21377..8931f1eff 100644 --- a/test/bin/mdx-test/failure/block-type-value/test-case.md.expected +++ b/test/bin/mdx-test/failure/block-type-value/test-case.md.expected @@ -1,2 +1,2 @@ -[mdx] Fatal error: File "test-case.md", lines 6-7: invalid code block: Label `block-type` requires a value. -[mdx] Fatal error: File "test-case.md", lines 11-12: invalid code block: "invalid" is not a valid value for label `block-type`. Valid values are "ocaml", "cram", "toplevel" and "include". +[mdx] Fatal error: File "test-case.md", lines 6-8: invalid code block: Label `type` requires a value. +[mdx] Fatal error: File "test-case.md", lines 12-14: invalid code block: "invalid" is not a valid value for label `type`. Valid values are "ocaml", "cram", "toplevel" and "include". diff --git a/test/bin/mdx-test/failure/include-without-file-label/test-case.md b/test/bin/mdx-test/failure/include-without-file-label/test-case.md index 1d7279202..45cd9e5b8 100644 --- a/test/bin/mdx-test/failure/include-without-file-label/test-case.md +++ b/test/bin/mdx-test/failure/include-without-file-label/test-case.md @@ -1,5 +1,6 @@ Explicitly setting the block-type to include has little benefits except for warning you that the `file=...` label is mandatory. -```ocaml block-type=include + +```ocaml ``` diff --git a/test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected b/test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected index 5f6e485ae..2deddf767 100644 --- a/test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected +++ b/test/bin/mdx-test/failure/include-without-file-label/test-case.md.expected @@ -1 +1 @@ -[mdx] Fatal error: File "test-case.md", lines 4-5: invalid code block: `file` label is required for include blocks. +[mdx] Fatal error: File "test-case.md", lines 4-6: invalid code block: `file` label is required for include blocks. From 64cd7968a5b83158389249d6cd495bc3725199b0 Mon Sep 17 00:00:00 2001 From: Nathan Rebours Date: Fri, 22 Jul 2022 12:03:15 +0200 Subject: [PATCH 3/6] Update CHANGES.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1731c5a89..d0d536da1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ - Report all parsing errors in Markdown files (#389, @NathanReb) - Add alternative syntax for explicitly setting the block-type. The new label `block-type=...` can be set to `ocaml`, `toplevel`, `cram` or - `include`. (#, @NathanReb) + `include`. (#385, @NathanReb) #### Changed From 426bd176fd3aace19af7c1dfc91d5fa5b7e42d7c Mon Sep 17 00:00:00 2001 From: Nathan Rebours Date: Fri, 5 Nov 2021 17:51:36 +0100 Subject: [PATCH 4/6] Fix doc comments Signed-off-by: Nathan Rebours --- lib/mdx.mli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mdx.mli b/lib/mdx.mli index 38d432a00..41f3978c7 100644 --- a/lib/mdx.mli +++ b/lib/mdx.mli @@ -17,7 +17,7 @@ (** [Mdx] is a library to manipulate markdown code blocks. [mdx] allows to execute code blocks inside markdown files. The - supported code {{!Block}blocks} are either {{!Cram}cram-like} + supported code {{!module-Block}blocks} are either {{!module-Cram}cram-like} tests, raw OCaml fragments or {{!Toplevel}toplevel} phrases. Cram tests and toplevel phrases are sequences of commands and From dad5f3343597b8620c4ce57d32884c8cbbd4766f Mon Sep 17 00:00:00 2001 From: Nathan Rebours Date: Fri, 5 Nov 2021 18:56:28 +0100 Subject: [PATCH 5/6] TMP: Add an MDX manual using odoc .mld files Signed-off-by: Nathan Rebours --- doc/dune | 2 + doc/dune_stanza.mld | 2 + doc/index.mld | 111 ++++++++++++++++++++++++++++++++++++++++ doc/labels.mld | 90 ++++++++++++++++++++++++++++++++ doc/markdown_syntax.mld | 1 + doc/mli_syntax.mld | 1 + doc/preludes.mld | 12 +++++ doc/types_of_blocks.mld | 27 ++++++++++ doc/using_libs.mld | 1 + 9 files changed, 247 insertions(+) create mode 100644 doc/dune create mode 100644 doc/dune_stanza.mld create mode 100644 doc/index.mld create mode 100644 doc/labels.mld create mode 100644 doc/markdown_syntax.mld create mode 100644 doc/mli_syntax.mld create mode 100644 doc/preludes.mld create mode 100644 doc/types_of_blocks.mld create mode 100644 doc/using_libs.mld diff --git a/doc/dune b/doc/dune new file mode 100644 index 000000000..716ec4ccc --- /dev/null +++ b/doc/dune @@ -0,0 +1,2 @@ +(documentation + (package mdx)) diff --git a/doc/dune_stanza.mld b/doc/dune_stanza.mld new file mode 100644 index 000000000..7653c3965 --- /dev/null +++ b/doc/dune_stanza.mld @@ -0,0 +1,2 @@ +{0 Dune Stanza} + diff --git a/doc/index.mld b/doc/index.mld new file mode 100644 index 000000000..85ed0c91c --- /dev/null +++ b/doc/index.mld @@ -0,0 +1,111 @@ +{0 MDX Manual} + +Welcome to the MDX Manual! + +MDX is a tool to help developpers and authors keep their documentation +up-to-date. +It ensures that the code examples you write compile and behave the way you +expect them to by actually running them. + +It is for example used to verify all of +{{:https://dev.realworldocaml.org/}Realworlocaml}'s examples! + +MDX is released on opam. You can install it in your switch by running: +{@sh[ + opam install mdx +]} + +{1 Table of Content} +- {{!page-markdown_syntax}Markdown MDX Syntax} +- {{!page-mli_syntax}.mli MDX Syntax} +- {{!page-types_of_blocks}Types of Blocks} +- {{!page-labels}Labels} +- {{!page-dune_stanza}Dune stanza} +- {{!page-preludes}Preludes} +- {{!page-using_libs}Using Libraries in your code examples} + +{1 Basic Usage} + +You can use MDX with your Markdown or [.mli] documentation, where it will ensure +the correctness of code respectively written in multi-line code blocks and +verbatim code blocks. + +To enable MDX, you need to add [(mdx)] stanzas to the right dune files. Before +that you will need to enable MDX for your project by adding the following to +your [dune-project]: + +{@dune[ + (using mdx 0.2) +]} + +You can then add the following in the relevant dune file: +{@dune[ + (mdx) +]} +That will enable MDX on all markdown files in the folder. +The MDX stanza can be further configured. If you want to know more about it, +visit the {{!page-mdx_dune_stanza}corresponding section of this manual} or the +one in +{{:https://dune.readthedocs.io/en/latest/dune-files.html#mdx-since-2-4}dune's manual}. + +MDX supports various type of code blocks but the most common are OCaml toplevel +blocks so we'll look at one of those for our example. In a markdown file, you +would write something along those lines: + +{@markdown[ + 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 [;;]. +Each of them is followed by the toplevel evaluation, or "output" which you +probably are already familiar with. + +Now you probably have noticed that [1 + 2] is not equal to [3] nor ["a" ^ "bc"] +to ["ab"]. Somebody must have updated the phrases but they then forgot to update +the evaluation. + +That's exactly what MDX is here for! + +If MDX were enabled for this file and they ran [dune runtest], here's what they +would have gotten: + +{@sh[ + $ 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: + +{@sh[ + dune promote +]} + +Now the documentation is up-to-date and running [dune runtest] again should be +successful! diff --git a/doc/labels.mld b/doc/labels.mld new file mode 100644 index 000000000..976aafefe --- /dev/null +++ b/doc/labels.mld @@ -0,0 +1,90 @@ +{0 Labels} + +MDX blocks behaviour can be customised through the use of labels. + +This section documents in detail what each existing label does and how to use +it. + +{1:dir Dir} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:source_tree Source Tree} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:file File} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:part Part} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:env Env} + +{2 Description} + +This label allows you to assign an environment to an OCaml block. That means +you benefit from whole the code that has been previously evaluated in that +environment, be it from other code blocks or {{!page-preludes}preludes}. + +{2 Syntax} + +[env=] + +The environement label expects a single string value which corresponds to the +name of the environment to use. + +[.mli] example: +{v +(** Here is how to use this function: + {@ocaml env=foo[ + # f 0;; + - : int = 0 + ]} *) +v} + +[.md] example: +{@markdown[ +Here is how to use this function: + +```ocaml +# f 0;; +- : int = 0 +``` +]} + +{2 Applies to} + +- {{!page-types_of_blocks.ocaml_toplevel} OCaml Toplevel Blocks} +- {{!page-types_of_blocks.ocaml} OCaml Blocks} + +{2 Default} + +When absent, the block will be evaluated in the default environment, e.g. +the environment shared by all blocks without an [env] label. diff --git a/doc/markdown_syntax.mld b/doc/markdown_syntax.mld new file mode 100644 index 000000000..1333ed77b --- /dev/null +++ b/doc/markdown_syntax.mld @@ -0,0 +1 @@ +TODO diff --git a/doc/mli_syntax.mld b/doc/mli_syntax.mld new file mode 100644 index 000000000..1333ed77b --- /dev/null +++ b/doc/mli_syntax.mld @@ -0,0 +1 @@ +TODO diff --git a/doc/preludes.mld b/doc/preludes.mld new file mode 100644 index 000000000..37a16551e --- /dev/null +++ b/doc/preludes.mld @@ -0,0 +1,12 @@ +{0 Preludes} + +Preludes are fragments of code that can be evaluated ahead of running the +code in OCaml and OCaml toplevel MDX blocks. They can be used to globally +open modules, register toplevel printers, set some globals like +[Printexc.record_backtrace], you name it! + +They are especially useful if you want to hide or share this kind of setup +phase, e.g. if you are writing polished documentation you want to publish +or you are using MDX to add tests in your mli files directly. + +TODO diff --git a/doc/types_of_blocks.mld b/doc/types_of_blocks.mld new file mode 100644 index 000000000..a6c297892 --- /dev/null +++ b/doc/types_of_blocks.mld @@ -0,0 +1,27 @@ +{0 Types of Blocks} + +MDX searches for code blocks within your documentation to make sure they are up +to date. +It supports different type of block contents, each have their own behaviour. + +If a code block does not belong to one of the categories described below, MDX +will simply ignore it. + +The type of a block can either be set explicitly using a label, otherwise MDX +will try to infer the type of the block based on its language tag, labels and +content. +Setting the block type explicitly allows MDX to better report syntax errors +or invalid labels. + +{1:ocaml_toplevel OCaml Toplevel Blocks} + +OCaml Toplevel Blocks are composed of a sequence of toplevel phrases, starting +with a [#] and a space and ending with [;;] followed by the output of the +toplevel evaluation of the phrase. + + +{1:ocaml OCaml Blocks} + +{1:file_include File Include Blocks} + +{1:shell Shell Blocks} diff --git a/doc/using_libs.mld b/doc/using_libs.mld new file mode 100644 index 000000000..1333ed77b --- /dev/null +++ b/doc/using_libs.mld @@ -0,0 +1 @@ +TODO From dfc13b6da2bccc130ba48cff84a7696fca212215 Mon Sep 17 00:00:00 2001 From: Nathan Rebours Date: Fri, 5 Aug 2022 14:33:56 +0200 Subject: [PATCH 6/6] tmp --- doc/labels.mld | 192 ++++++++++++++++++++++++++++++++++++++-- doc/types_of_blocks.mld | 13 ++- 2 files changed, 199 insertions(+), 6 deletions(-) diff --git a/doc/labels.mld b/doc/labels.mld index 976aafefe..48af25f35 100644 --- a/doc/labels.mld +++ b/doc/labels.mld @@ -9,12 +9,48 @@ it. {2 Description} +This label allows you to specify the working directory from which the block +should be evaluated. + {2 Syntax} +[dir=] + +The dir label expects a single string value which corresponds to the path +to the directory from which the block must be evaluated. It should be a path +relative to the directory containing the current [.mli]/[.mld]/[.md] file. + +[.mli] example: +{v +(** We will list the files in subdir: + {@sh dir=subdir[ + $ ls + something.ml something_else.ml + ]} *) +v} + +[.md] example: +{@markdown[ +We will list the files in subdir: + +```sh +$ ls +something.ml something_else.ml +``` +]} + {2 Applies to} +- {{!page-types_of_blocks.ocaml_toplevel} OCaml Toplevel Blocks} +- {{!page-types_of_blocks.ocaml} OCaml Blocks} +- {{!page-types_of_blocks.file_include} File Include Blocks} +- {{!page-types_of_blocks.shell} Shell Blocks} + {2 Default} +When absent, the block will be evaluated from the directory of +the file being tested by MDX. + {1:source_tree Source Tree} {2 Description} @@ -29,12 +65,23 @@ it. {2 Description} +This label allows you + {2 Syntax} +[file= +```ocaml +# 1 + 1;; +- : int = 3 +``` +]} + +{2 Applies to} + +- {{!page-types_of_blocks.ocaml_toplevel} OCaml Toplevel Blocks} +- {{!page-types_of_blocks.ocaml} OCaml Blocks} +- {{!page-types_of_blocks.file_include} File Include Blocks} +- {{!page-types_of_blocks.shell} Shell Blocks} + +{2 Default} + +By default MDX will interpret any block it knows how to deal with and skip +any other block. + +{1:non_det Non Deterministic} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:version Version} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:set Set} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:unset Unset} + +{2 Description} + +{2 Syntax} + +{2 Applies to} + +{2 Default} + +{1:type Type} + +{2 Description} + +This label allows you to explicitly set the type of the block as described +in {{!page.types_of_blocks}this section}. +Explicitly setting the type of the block instead of relying on MDX to infer +it will provide better error messages and guidance in case of syntax errors or +labels misuse. + +{2 Syntax} + +[type=] + +The [type] label accepts one of the following values: +- [toplevel] for OCaml Toplevel Blocks +- [ocaml] for OCaml Blocks +- [cram] for Shell Blocks +- [include] for File Include Blocks + +[.mli] example: +{v +(** The following block is a toplevel block: + {@ocaml type=toplevel[ + # 1 + 1;; + - : int = 2 + ]} *) +v} + +[.md] example: +{@markdown[ +The following block is a toplevel block: + +```ocaml +# 1 + 1;; +- : int = 2 +``` +]} + +{2 Applies to} + +- {{!page-types_of_blocks.ocaml_toplevel} OCaml Toplevel Blocks} +- {{!page-types_of_blocks.ocaml} OCaml Blocks} +- {{!page-types_of_blocks.file_include} File Include Blocks} +- {{!page-types_of_blocks.shell} Shell Blocks} + +{2 Default} + +By default, MDX will infer the type of the block based on its language header, +its content and its labels. diff --git a/doc/types_of_blocks.mld b/doc/types_of_blocks.mld index a6c297892..5ea90efd1 100644 --- a/doc/types_of_blocks.mld +++ b/doc/types_of_blocks.mld @@ -7,7 +7,8 @@ It supports different type of block contents, each have their own behaviour. If a code block does not belong to one of the categories described below, MDX will simply ignore it. -The type of a block can either be set explicitly using a label, otherwise MDX +The type of a block can either be set explicitly using the +{{!page-labels.type}[type]} label, otherwise MDX will try to infer the type of the block based on its language tag, labels and content. Setting the block type explicitly allows MDX to better report syntax errors @@ -24,4 +25,14 @@ toplevel evaluation of the phrase. {1:file_include File Include Blocks} +File Include Blocks are synchronized with the content of another file. Upon +running MDX, the content of the block will be refreshed to match the content +of the file specified in the block's {{!page-labels.file}[file]} label. + +It can be any kind of text file, [.ml], [dune], [.c], the content will simply be +copied as is. + +It is possible for OCaml files to only include specific parts of the file, using +the {{!page-labels.part}[part]} label and delimiter comments in the source file. + {1:shell Shell Blocks}