# Documentation for folder with (include\_subdirs qualified)

**URL:** <https://discuss.ocaml.org/t/documentation-for-folder-with-include-subdirs-qualified/17210>\
**Category:** Learning\
**Tags:** documentation, dune\
**Created:** [September 5, 2025, 12:35pm UTC](https://discuss.ocaml.org/t/documentation-for-folder-with-include-subdirs-qualified/17210 "2025-09-05T12:35:27Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![giltho](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/giltho/32/3521_2.png) [@giltho](https://discuss.ocaml.org/u/giltho)\
**Post date:** [September 5, 2025, 12:35pm UTC](https://discuss.ocaml.org/t/documentation-for-folder-with-include-subdirs-qualified/17210/1 "2025-09-05T12:35:27Z")

</div>

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

```auto
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?

---

<div class="post-metadata">

**Author:** ![giltho](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/giltho/32/3521_2.png) [@giltho](https://discuss.ocaml.org/u/giltho)\
**Post date:** [September 5, 2025, 2:48pm UTC](https://discuss.ocaml.org/t/documentation-for-folder-with-include-subdirs-qualified/17210/2 "2025-09-05T14:48:11Z")

</div>

I managed to get away with the following `make_entry_point.ml`

```ocaml
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:

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

```
