diff --git a/README.md b/README.md index b3fb2d45..fd5f4721 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,6 @@ The possible labels are: - `env=ENV` -- see the "Named execution environments" section - `set-VAR=VALUE` -- set an environment variable - `unset-VAR` -- unset an environment variable -- `exec` -- execute this block. Only allowed when including an OCaml file. #### Shell Scripts @@ -252,8 +251,6 @@ These can then be included in the document: ```ocaml ``` -Use the `exec` flag to execute as an OCaml block. - Non-OCaml files can also be read and included in a block: diff --git a/lib/block.ml b/lib/block.ml index 460f12d1..64df66dd 100644 --- a/lib/block.ml +++ b/lib/block.ml @@ -30,29 +30,36 @@ let locate_errors ~loc r = (fun l -> List.map (fun (`Msg m) -> `Msg (locate_error_msg ~loc m)) l) r -type ocaml_kind = Impl | Intf +module OCaml_kind = struct + type t = Impl | Intf + + let infer_from_file file = + match Filename.(remove_extension (basename file), extension file) with + | _, (".ml" | ".mlt" | ".eliom") -> Some Impl + | _, (".mli" | ".eliomi") -> Some Intf + | _ -> None +end module Header = struct - type t = Shell of [ `Sh | `Bash ] | OCaml of ocaml_kind | Other of string + type t = Shell of [ `Sh | `Bash ] | OCaml | Other of string let pp ppf = function | Shell `Sh -> Fmt.string ppf "sh" | Shell `Bash -> Fmt.string ppf "bash" - | OCaml _ -> Fmt.string ppf "ocaml" + | OCaml -> Fmt.string ppf "ocaml" | Other s -> Fmt.string ppf s let of_string = function | "" -> None | "sh" -> Some (Shell `Sh) | "bash" -> Some (Shell `Bash) - | "ocaml" -> Some (OCaml Impl) + | "ocaml" -> Some OCaml | s -> Some (Other s) let infer_from_file file = match Filename.(remove_extension (basename file), extension file) with | ("dune" | "dune-project"), _ -> Some (Other "scheme") - | _, (".ml" | ".mlt" | ".eliom") -> Some (OCaml Impl) - | _, (".mli" | ".eliomi") -> Some (OCaml Intf) + | _, (".ml" | ".mli" | ".mlt" | ".eliom" | ".eliomi") -> Some OCaml | _, ".sh" -> Some (Shell `Sh) | _ -> None end @@ -85,7 +92,6 @@ type ocaml_value = { non_det : Label.non_det option; errors : Output.t list; header : Header.t option; - kind : ocaml_kind; } type toplevel_value = { env : Ocaml_env.t; non_det : Label.non_det option } @@ -93,6 +99,7 @@ type toplevel_value = { env : Ocaml_env.t; non_det : Label.non_det option } type include_ocaml_file = { part_included : string option; ocaml_value : ocaml_value option; + kind : OCaml_kind.t; } type include_other_file = { header : Header.t option } @@ -140,8 +147,8 @@ let header t = | Raw { header; _ } -> header | OCaml { header; _ } -> header | Cram { language; _ } -> Some (Header.Shell language) - | Toplevel _ -> Some (Header.OCaml Impl) - | Include { file_kind = Fk_ocaml _; _ } -> Some (Header.OCaml Impl) + | Toplevel _ -> Some Header.OCaml + | Include { file_kind = Fk_ocaml _; _ } -> Some Header.OCaml | Include { file_kind = Fk_other b; _ } -> b.header let dump_value ppf = function @@ -206,7 +213,7 @@ let pp_error ?syntax ?delim ppf outputs = outputs err_delim | _ -> () -let has_output t = +let has_errors t = match get_ocaml_value t with | Some { errors = _ :: _; _ } -> true | _ -> false @@ -221,7 +228,7 @@ let pp_value ?syntax ppf t = let pp_footer ?syntax ppf t = let delim = - if has_output t then ( + if has_errors t then ( pp_value ?syntax ppf t; None) else t.delim @@ -392,20 +399,20 @@ let get_block_config l = file_inc = get_label (function File x -> Some x | _ -> None) l; } -let mk_ocaml_value env non_det errors header ~kind = - { env = Ocaml_env.mk env; non_det; errors; header; kind } +let mk_ocaml_value env non_det errors header = + { env = Ocaml_env.mk env; non_det; errors; header } -let mk_ocaml ~loc ~config ~header ~contents ~errors ~kind = +let mk_ocaml ~loc ~config ~header ~contents ~errors = + let kind = "OCaml" in match config with | { file_inc = None; part = None; env; non_det; _ } -> ( (* TODO: why does this call guess_ocaml_kind when infer_block already did? *) match guess_ocaml_kind contents with - | `Code -> Ok (OCaml (mk_ocaml_value env non_det errors header ~kind)) + | `Code -> Ok (OCaml (mk_ocaml_value env non_det errors header)) | `Toplevel -> loc_error ~loc "toplevel syntax is not allowed in OCaml blocks.") - | { file_inc = Some _; _ } -> - label_not_allowed ~loc ~label:"file" ~kind:"OCaml" - | { part = Some _; _ } -> label_not_allowed ~loc ~label:"part" ~kind:"OCaml" + | { file_inc = Some _; _ } -> label_not_allowed ~loc ~label:"file" ~kind + | { part = Some _; _ } -> label_not_allowed ~loc ~label:"part" ~kind let mk_cram ~loc ?language ~config ~header ~errors () = let kind = "shell" in @@ -437,31 +444,40 @@ let mk_toplevel ~loc ~config ~contents ~errors = | { part = Some _; _ } -> label_not_allowed ~loc ~label:"part" ~kind let mk_include ~loc ~config ~header ~errors = + let kind = "include" in match config with | { file_inc = Some file_included; part; non_det; env; _ } -> ( - let* () = check_no_errors ~loc errors in - match header with - | Some (Header.OCaml k) -> + let kind = + match header with + | Some Header.OCaml -> `OCaml + | None -> ( + match OCaml_kind.infer_from_file file_included with + | Some _ -> `OCaml + | None -> `Other) + | _ -> `Other + in + match kind with + | `OCaml -> let kind = - match Header.infer_from_file file_included with - | Some (Header.OCaml k') -> k' - | _ -> k + Util.Option.value ~default:OCaml_kind.Impl + (OCaml_kind.infer_from_file file_included) in + let part_included = part in let ocaml_value = match kind with - | Impl -> Some (mk_ocaml_value env non_det errors header ~kind) + | Impl -> Some (mk_ocaml_value env non_det errors header) | Intf -> None in - let file_kind = Fk_ocaml { part_included = part; ocaml_value } in + let file_kind = Fk_ocaml { part_included; ocaml_value; kind } in Ok (Include { file_included; file_kind }) - | _ -> ( + | `Other -> ( match part with | None -> let file_kind = Fk_other { header } in Ok (Include { file_included; file_kind }) | Some _ -> label_not_allowed ~loc ~label:"part" ~kind:"non-OCaml include")) - | { file_inc = None; _ } -> label_required ~loc ~label:"file" ~kind:"include" + | { file_inc = None; _ } -> label_required ~loc ~label:"file" ~kind let infer_block ~loc ~config ~header ~contents ~errors = match config with @@ -470,9 +486,9 @@ let infer_block ~loc ~config ~header ~contents ~errors = match header with | Some (Header.Shell language) -> mk_cram ~loc ~language ~config ~header ~errors () - | Some (Header.OCaml kind) -> ( + | Some Header.OCaml -> ( match guess_ocaml_kind contents with - | `Code -> mk_ocaml ~loc ~config ~header ~contents ~errors ~kind + | `Code -> mk_ocaml ~loc ~config ~header ~contents ~errors | `Toplevel -> mk_toplevel ~loc ~config ~contents ~errors) | _ -> let* () = @@ -488,7 +504,7 @@ let mk ~loc ~section ~labels ~legacy_labels ~header ~delim ~contents ~errors = let config = get_block_config labels in let* value = match block_kind with - | Some OCaml -> mk_ocaml ~loc ~config ~header ~contents ~errors ~kind:Impl + | Some OCaml -> mk_ocaml ~loc ~config ~header ~contents ~errors | Some Cram -> mk_cram ~loc ~config ~header ~errors () | Some Toplevel -> mk_toplevel ~loc ~config ~contents ~errors | Some Include -> mk_include ~loc ~config ~header ~errors @@ -554,7 +570,4 @@ let is_active ?section:s t = | None -> Re.execp (Re.Perl.compile_pat p) "") | None -> true in - let executable = - match t.value with OCaml { kind = Intf; _ } -> false | _ -> true - in - active && t.version_enabled && t.os_type_enabled && (not t.skip) && executable + active && t.version_enabled && t.os_type_enabled && not t.skip diff --git a/lib/block.mli b/lib/block.mli index f3226185..ceafef82 100644 --- a/lib/block.mli +++ b/lib/block.mli @@ -16,10 +16,12 @@ (** Code blocks headers. *) -type ocaml_kind = Impl | Intf +module OCaml_kind : sig + type t = Impl | Intf +end module Header : sig - type t = Shell of [ `Sh | `Bash ] | OCaml of ocaml_kind | Other of string + type t = Shell of [ `Sh | `Bash ] | OCaml | Other of string val pp : Format.formatter -> t -> unit val of_string : string -> t option @@ -37,7 +39,6 @@ type ocaml_value = { errors : Output.t list; (** [header] defines whether a header was specified for the block. *) header : Header.t option; - kind : ocaml_kind; } type toplevel_value = { @@ -51,6 +52,7 @@ type include_ocaml_file = { (** [part_included] is the part of the file to synchronize with. If lines is not specified synchronize the whole file. *) ocaml_value : ocaml_value option; + kind : OCaml_kind.t; } type include_other_file = { header : Header.t option } diff --git a/lib/mli_parser.ml b/lib/mli_parser.ml index 65fb2fc6..e8a97766 100644 --- a/lib/mli_parser.ml +++ b/lib/mli_parser.ml @@ -138,7 +138,7 @@ let make_block code_block file_contents = Ok (header, language_label :: labels) | None -> (* If not specified, blocks are run as ocaml blocks *) - Ok (Some (OCaml Impl), []) + Ok (Some OCaml, []) in match handle_header code_block.Code_block.metadata with | Error _ as e -> e diff --git a/lib/test/mdx_test.ml b/lib/test/mdx_test.ml index 1b4171ca..a756623a 100644 --- a/lib/test/mdx_test.ml +++ b/lib/test/mdx_test.ml @@ -358,8 +358,10 @@ let run_exn ~non_deterministic ~silent_eval ~record_backtrace ~syntax ~silent match Block.value t with | Raw _ -> print_block () | Include - { file_included; file_kind = Fk_ocaml { part_included; ocaml_value } } - -> + { + file_included; + file_kind = Fk_ocaml { part_included; ocaml_value; _ }; + } -> assert (syntax <> Some Cram); let new_block = update_file_or_block ?root file file_included t part_included diff --git a/test/bin/mdx-test/expect/dune.inc b/test/bin/mdx-test/expect/dune.inc index f489a3e2..26dbaa5a 100644 --- a/test/bin/mdx-test/expect/dune.inc +++ b/test/bin/mdx-test/expect/dune.inc @@ -179,6 +179,18 @@ (alias runtest) (action (diff errors/test-case.md errors.actual))) +(rule + (target exec-include.actual) + (deps (package mdx) (source_tree exec-include)) + (action + (with-stdout-to %{target} + (chdir exec-include + (run ocaml-mdx test --output - test-case.md))))) + +(rule + (alias runtest) + (action (diff exec-include/test-case.md exec-include.actual))) + (rule (target exit.actual) (deps (package mdx) (source_tree exit)) diff --git a/test/bin/mdx-test/expect/exec-include/code.ml b/test/bin/mdx-test/expect/exec-include/code.ml new file mode 100644 index 00000000..d407ee51 --- /dev/null +++ b/test/bin/mdx-test/expect/exec-include/code.ml @@ -0,0 +1,7 @@ +(* $MDX part-begin=OK *) +let f x = x + 1 +(* $MDX part-end *) + +(* $MDX part-begin=KO *) +let k = x = 1 +(* $MDX part-end *) diff --git a/test/bin/mdx-test/expect/exec-include/code.mli b/test/bin/mdx-test/expect/exec-include/code.mli new file mode 100644 index 00000000..da5e52f2 --- /dev/null +++ b/test/bin/mdx-test/expect/exec-include/code.mli @@ -0,0 +1 @@ +val f : int -> int diff --git a/test/bin/mdx-test/expect/exec-include/test-case.md b/test/bin/mdx-test/expect/exec-include/test-case.md new file mode 100644 index 00000000..1a120870 --- /dev/null +++ b/test/bin/mdx-test/expect/exec-include/test-case.md @@ -0,0 +1,34 @@ +.mli files are included but not executed: + +```ocaml +val f : int -> int +``` + +Use `skip` to not include the .mli files: + +```ocaml +``` + +.ml files are included and executed: + +```ocaml +let f x = x + 1 +``` + + +```ocaml +let k = x = 1 +``` +```mdx-error +Line 1, characters 9-10: +Error: Unbound value x +``` + +Use `skip` to not include the .ml files: + +```ocaml +``` + + +```ocaml +``` diff --git a/test/lib/test_block.ml b/test/lib/test_block.ml index e2128cb5..be45ed86 100644 --- a/test/lib/test_block.ml +++ b/test/lib/test_block.ml @@ -14,8 +14,7 @@ let test_infer_from_file = make_test ~file:"dune" ~expected:(Some (Other "scheme")); make_test ~file:"dune-project" ~expected:(Some (Other "scheme")); make_test ~file:"foo.sh" ~expected:(Some (Shell `Sh)); - make_test ~file:"foo/foo/foo.ml" ~expected:(Some (OCaml Impl)); - make_test ~file:"foo/foo/foo.mli" ~expected:(Some (OCaml Intf)); + make_test ~file:"foo/foo/foo.ml" ~expected:(Some OCaml); ] let test_mk = @@ -40,10 +39,10 @@ let test_mk = in [ make_test ~name:"invalid ocaml" ~labels:[ Block_kind OCaml ] - ~header:(Some (OCaml Impl)) ~contents:[ "# let x = 2;;" ] + ~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 ] - ~header:(Some (OCaml Impl)) ~contents:[ "let x = 2;;" ] + ~header:(Some OCaml) ~contents:[ "let x = 2;;" ] ~expected:(Error (`Msg "invalid toplevel syntax in toplevel blocks.")); ]