123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315(** Odoc Extension API
This module provides the interface for odoc tag extensions.
Extensions are dynamically loaded plugins that handle custom tags
like [@note], [@rfc], [@example], etc.
*)(** {1 Re-exported Types}
These are the odoc types that extensions need to work with.
*)moduleComment=Odoc_model.CommentmoduleLocation_=Odoc_model.Location_moduleBlock=Odoc_document.Types.BlockmoduleInline=Odoc_document.Types.InlinemoduleDescription=Odoc_document.Types.DescriptionmoduleUrl=Odoc_document.UrlmoduleTarget=Odoc_document.Types.Target(** {1 Extension Types} *)(** Resources that can be injected into the page (HTML only) *)typeresource=Odoc_extension_registry.resource=|Js_urlofstring(** External JavaScript: <script src="..."> *)|Css_urlofstring(** External CSS: <link rel="stylesheet" href="..."> *)|Js_inlineofstring(** Inline JavaScript: <script>...</script> *)|Css_inlineofstring(** Inline CSS: <style>...</style> *)(** Binary asset generated by an extension.
Assets are written alongside the HTML output. To reference an asset
in your content, use the placeholder [__ODOC_ASSET__filename__] which
will be replaced with the correct relative path during HTML generation. *)typeasset=Odoc_extension_registry.asset={asset_filename:string;(** Filename for the asset, e.g., "diagram-1.png" *)asset_content:bytes;(** Binary content *)}(** {1 Extension Documentation}
Extensions can register documentation describing their options and usage.
This information is displayed by [odoc extensions --help]. *)(** Documentation for a single option *)typeoption_doc=Odoc_extension_registry.option_doc={opt_name:string;(** Option name, e.g., "width" *)opt_description:string;(** What the option does *)opt_default:stringoption;(** Default value if any *)}(** Documentation/metadata for an extension *)typeextension_info=Odoc_extension_registry.extension_info={info_kind:[`Tag|`Code_block];(** Type of extension *)info_prefix:string;(** The prefix this extension handles *)info_description:string;(** Short description *)info_options:option_doclist;(** Supported options *)info_example:stringoption;(** Example usage *)}(** Output from the document phase *)typeextension_output={content:Block.t;(** Universal content - used by all backends unless overridden *)overrides:(string*string)list;(** Backend-specific raw content overrides.
E.g., [("html", "<div>...</div>"); ("markdown", "...")] *)resources:resourcelist;(** Page-level resources (JS/CSS). Only used by HTML backend. *)assets:assetlist;(** Binary assets to write alongside HTML output.
Reference in content using [__ODOC_ASSET__filename__] placeholder. *)}(** Raised when an extension receives a tag variant it doesn't support *)exceptionUnsupported_tagofstring(** {1 Extension Interface} *)(** The signature that all tag extensions must implement *)moduletypeExtension=sigvalprefix:string(** The tag prefix this extension handles.
E.g., "note" handles [@note], "admonition" handles [@admonition.note] *)valto_document:tag:string->Comment.nestable_block_elementLocation_.with_locationlist->extension_output(** Document phase: convert tag to renderable content.
Called during document generation. Returns content plus any
page-level resources needed (JS/CSS). *)end(** {1 Code Block Extensions}
Extensions can also handle code blocks like [{@dot[...]}] or
[{@mermaid[...]}]. These extensions receive the language tag,
metadata (key=value pairs), and the code content.
*)(** Metadata for code blocks *)typecode_block_meta=Odoc_extension_registry.code_block_meta={language:string;(** The language tag, e.g., "dot" or "mermaid" *)tags:Odoc_parser.Ast.code_block_taglist;(** Additional metadata tags like [width=500] or [format=svg].
Each tag is either [`Tag name] for bare tags or
[`Binding (key, value)] for key=value pairs. *)}(** The signature that code block extensions must implement *)moduletypeCode_Block_Extension=sigvalprefix:string(** The language prefix this extension handles.
E.g., "dot" handles [{@dot[...]}], "mermaid" handles [{@mermaid[...]}] *)valto_document:code_block_meta->string->extension_outputoption(** Transform a code block. Takes metadata and code content.
Returns [Some output] to replace the code block, or [None] to
fall back to default rendering.
Example metadata for [{@dot width=500 format=svg[digraph {...}]}]:
- [meta.language = "dot"]
- [meta.tags = [`Binding ("width", "500"); `Binding ("format", "svg")]]
- content = "digraph {...}" *)end(** {1 Support Files}
Extensions can register support files (CSS, JS, images, etc.) that
will be output by [odoc support-files].
*)typesupport_file=Odoc_extension_registry.support_file={filename:string;(** Relative path, e.g., "extensions/admonition.css" *)content:string;(** File content *)}(** {1 Extension Registry}
Extensions register themselves here when loaded.
odoc queries the registry when processing custom tags.
*)moduleRegistry=structletregister(moduleE:Extension)=lethandlertagcontent=tryletresult=E.to_document~tagcontentinSome{Odoc_extension_registry.content=result.content;overrides=result.overrides;resources=result.resources;assets=result.assets;}withUnsupported_tag_->NoneinOdoc_extension_registry.register_handler~prefix:E.prefixhandlerletregister_code_block(moduleE:Code_Block_Extension)=lethandlermetacontent=matchE.to_documentmetacontentwith|Someresult->Some{Odoc_extension_registry.content=result.content;overrides=result.overrides;resources=result.resources;assets=result.assets;}|None->NoneinOdoc_extension_registry.register_code_block_handler~prefix:E.prefixhandler(** Register a support file for this extension.
The file will be output when [odoc support-files] is run. *)letregister_support_file~prefixfile=Odoc_extension_registry.register_support_file~prefixfileletfindprefix=Odoc_extension_registry.find_handler~prefixletfind_code_blockprefix=Odoc_extension_registry.find_code_block_handler~prefixletlist_prefixes()=Odoc_extension_registry.list_prefixes()letlist_code_block_prefixes()=Odoc_extension_registry.list_code_block_prefixes()letlist_support_files()=Odoc_extension_registry.list_support_files()(** Register documentation for an extension.
This will be displayed by [odoc extensions]. *)letregister_extension_infoinfo=Odoc_extension_registry.register_extension_infoinfo(** List all registered extension documentation *)letlist_extension_infos()=Odoc_extension_registry.list_extension_infos()end(** {1 Helper Functions} *)(** Extract plain text from nestable block elements (for simple parsing) *)letrectext_of_inline(inline:Comment.inline_elementLocation_.with_location)=matchinline.Location_.valuewith|`Space->" "|`Wordw->w|`Code_spanc->c|`Math_spanm->m|`Raw_markup(_,r)->r|`Styled(_,inlines)->text_of_inlinesinlines|`Reference(_,content)->text_of_link_contentcontent|`Link(_,content)->text_of_link_contentcontentandtext_of_inlinesinlines=String.concat""(List.maptext_of_inlineinlines)andtext_of_link_content(content:Comment.link_content)=String.concat""(List.maptext_of_non_linkcontent)andtext_of_non_link(el:Comment.non_link_inline_elementLocation_.with_location)=matchel.Location_.valuewith|`Space->" "|`Wordw->w|`Code_spanc->c|`Math_spanm->m|`Raw_markup(_,r)->r|`Styled(_,content)->text_of_link_contentcontentlettext_of_paragraph(p:Comment.paragraph)=text_of_inlinespletrectext_of_nestable_block_elementselements=letbuf=Buffer.create256inList.iter(fun(el:Comment.nestable_block_elementLocation_.with_location)->matchel.Location_.valuewith|`Paragraphp->Buffer.add_stringbuf(text_of_paragraphp)|`Code_blockc->Buffer.add_stringbufc.content.Location_.value|`Math_blockm->Buffer.add_stringbufm|`Verbatimv->Buffer.add_stringbufv|`Modules_->()|`Table_->()|`List(_,items)->List.iter(funitem->Buffer.add_stringbuf(text_of_nestable_block_elementsitem))items|`Media_->())elements;Buffer.contentsbuf(** Create a simple paragraph block *)letparagraphtext=letinline=Inline.[{attr=[];desc=Texttext}]inBlock.[{attr=[];desc=Paragraphinline}](** Create an inline link *)letlink~url~text=Inline.[{attr=[];desc=Link{target=Externalurl;content=[{attr=[];desc=Texttext}];tooltip=None}}](** Create an empty extension output with just content *)letsimple_outputcontent={content;overrides=[];resources=[];assets=[]}(** {1 Code Block Metadata Helpers} *)(** Get the value of a binding from code block tags.
E.g., for [{@dot width=500[...]}], [get_binding "width" meta.tags]
returns [Some "500"]. *)letget_bindingkeytags=List.find_map(function|`Binding(k,v)->ifk.Odoc_parser.Loc.value=keythenSomev.Odoc_parser.Loc.valueelseNone|`Tag_->None)tags(** Check if a bare tag is present in code block tags.
E.g., for [{@ocaml line-numbers[...]}], [has_tag "line-numbers" meta.tags]
returns [true]. *)lethas_tagnametags=List.exists(function|`Tagt->t.Odoc_parser.Loc.value=name|`Binding_->false)tags(** Get all bindings as a list of (key, value) pairs *)letget_all_bindingstags=List.filter_map(function|`Binding(k,v)->Some(k.Odoc_parser.Loc.value,v.Odoc_parser.Loc.value)|`Tag_->None)tags(** Get all bare tags as a list of names *)letget_all_tagstags=List.filter_map(function|`Tagt->Somet.Odoc_parser.Loc.value|`Binding_->None)tags