Cmdliner - documenting a complex option

I am using Cmdliner for argument processing. One of my options accepts an expression syntax that I would like to document. An option in Cmdliner by itself only is documented by a string – so a more complex explanation is difficult to fit in. Hence, would like to add a section following the options to document this. Is there a way to control the placement of such a section in the list of sections? Is there a good alternative?

let filter =
  let doc =
    {|Filter segments before including them in the result, using a boolean
    expression. The expression typically is like "speed > 4.0 && rate > 20",
    providing a predicate that a segment must satisfy. The expression may use
    variables to access properties of the segment under evaluation and must evaluate to true or false. The expression language provides arithmetic over numbers, literals, as well as relational and logical operations. |}
  in
  C.Arg.(value & opt string "true" & info [ "filter" ] ~doc ~docv:"FILTER")

Yes the way sections are ordered is described in the documentation here. It’s a bit complicated but tries to do the right thing® without having to specify too much things manually.

Note that you could document the option in the section itself. By using the option’s info docs argument with the name of your section, the option should show up at the end of the section.

Also this feature request could be an alternative, but for a couple of reasons I’m a bit reluctant.

Depending on what you mean by ‘good’, for subcommands you can also do as opam does and add an additional command with a non-rendering name to get a new paragraph. This is essentially just a hack for the fact that cmdliner#180 is unresolved

You could do the same for options, but it doesn’t look quite as nice…

AFAIU that’s not what’s happening here. That’s not the way subcommands are rendered by cmdliner: you only get to see the command’s manpage NAME synopsis (doc argument) in COMMANDS list renderings.

It rather seems that opam never migrated to Cmdliner’s support for arbitrary sub command nesting and is still using a positional argument for the third level.

What you see here is the result of this function which uses indented paragraphs for the rendering.

The opam use case is documenting a sub command, not an option - is that correct? So I believe this is different from documenting an option. I am for now using the mechanism @dbuenzli described: use the info ~docs parameter to link to a non-standard section ("FILTERS”) which has more facilities available, including paragraphs.

let manual desc =
  [
    `S C.Manpage.s_description
  ; `P desc
  ; `S C.Manpage.s_commands
  ; `S C.Manpage.s_arguments
  ; `S C.Manpage.s_options
  ; `S "FILTERS"
  ; `Blocks filters
  ...
  ; `S C.Manpage.s_authors
  ; `P "(c)2025 Christian Lindig <lindig@gmail.com>"
  ]
let filter =
  let doc =
    {|Filter segments before including them in the result, using a boolean
    expression. The expression typically is like $(b,speed > 4.0 && rate > 20),
    providing a predicate that a segment must satisfy. The expression may use
    variables to access properties of the segment under evaluation and must
    evaluate to true or false. The expression language provides arithmetic over
    numbers, literals, as well as relational and logical operations. |}
  in
  C.Arg.(
    value & opt string "true"
    & info [ "filter" ] ~doc ~docs:"FILTERS" ~docv:"FILTER")

Yes and it’s working around limitations earlier cmdliner versions had (only one level of subcommands).

So it wouldn’t even be applicable to subcommands as they exist now – and I don’t think you could do hacks like @WardBrian mentioned without actually changing the parsed command line :–)

In fact that’s one of the reasons I’m not too keen on acting on the feature request mentioned earlier.

I guess once I give in paragraphs people will want the other facilities but 1) I’m not sure that’s even possible (and not really keen on trying to find out) and 2) you’d need to introduce a surface syntax since those doc argument are strings not Manpage.t values.

P.S. You can probably drop C.Manpage.s_{commands,arguments} from your code above. You just need to mention the standard section after which you want FILTERS to be inserted.

Yep, it definitely adds an actual option to the enumeration. It also inserts a spurious “Note:” into opams custom shell completion for the command, so like I said, maybe not a “good” alternative