Documentation for folder with (include_subdirs qualified)

Hello,

I have a library that uses some (include_subdirs qualified) and I would like to write some module documentation for one of the folders, and I’m stuck.

The folder looks like

soteria/lib/
├── dune          # (include_subdirs qualified)
├── submodule1
│   ├── x.ml
│   └── x.mli
└── submodule2
    ├── dune      # (documentation)
    ├── index.mld # Was hoping to have this...
    ├── a.ml
    └── b.ml

I was hoping to be able to write some index.mld in submodule2 with a dune file specifying (documentation) but that makes dune crash.

Alternatively I could write submodule2.ml and write documentation in there but I’d have to copy module A = A module B = B ...and keep adding lines every time I add a module.. It’s not very expensive but it’s the kind of tiny paper cuts that are annoying when projects grow.

Any advice?

I managed to get away with the following make_entry_point.ml

let () =
  if Array.length Sys.argv != 2 then
    Printf.eprintf "Usage: %s <folder>" Sys.argv.(0)

let normalise_path path =
  let sep =
    if String.length Filename.dir_sep != 1 then
      failwith "dir_sep is not a character"
    else Filename.dir_sep.[0]
  in
  let with_indirections =
    if Filename.is_relative path then Filename.concat (Sys.getcwd ()) path
    else path
  in
  let parts = String.split_on_char sep with_indirections in
  let rec aux acc = function
    | [] -> String.concat Filename.dir_sep (List.rev acc)
    | x :: tl when String.equal Filename.current_dir_name x -> aux acc tl
    | x :: tl when String.equal Filename.parent_dir_name x -> (
        match acc with [] -> aux (x :: acc) tl | _ :: acc_tl -> aux acc_tl tl)
    | x :: tl -> aux (x :: acc) tl
  in
  aux [] parts

let folder = Sys.argv.(1) |> normalise_path

let doc_file =
  Filename.concat folder (Filename.basename folder ^ ".mld") |> normalise_path

let doc_content = In_channel.with_open_text doc_file In_channel.input_all

let module_list =
  Sys.readdir folder
  |> Array.to_seq
  |> Seq.filter (fun f ->
         Filename.check_suffix f ".ml" && not (Filename.check_suffix f ".pp.ml"))
  |> Seq.map (fun f ->
         let f = Filename.chop_suffix f ".ml" in
         if String.length f = 0 then failwith "Empty filename";
         let f = String.capitalize_ascii f in
         Printf.sprintf "module %s = %s" f f)
  |> List.of_seq
  |> String.concat "\n"

let whole_file = Printf.sprintf "(**\n%s\n*)\n\n\n%s" doc_content module_list
let () = Printf.printf "%s\n" whole_file

(though admittedly frutrating that most of the code is about normalising a path, and stdlib has no means of splitting a string on a string, only on a char)

and the following dune rule:

(rule
 (deps submodule2.mld ../../../scripts/make_entry_point.exe)
 (target submodule2.ml)
 (action
  (with-stdout-to %{target} (run ../../../scripts/make_entry_point.exe .))))