My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add custom inline extensions to odoc

Support [{&name payload}] inline syntax in .mld pages and docstrings.
Plugins register handlers via [Registry.register_inline] in the odoc
extension API; each handler receives the raw payload string and
returns HTML that is spliced into the rendered output.

Implementation rides on the existing [Raw_markup] AST variant with a
synthetic target prefix [odoc-ext:<name>]. The lexer emits
[Raw_markup (Some ("odoc-ext:" ^ name), payload)] when it sees
[{&name payload}]; the HTML generator's [raw_markup] function detects
the prefix and dispatches to the inline-handler registry, falling
back to emitting the payload raw if no handler is registered.

This keeps the patch small (no new AST variant, no backend pattern
match audits) at the cost of a lightly-punned Raw_markup target. Can
be promoted to a proper variant later.

Two smoke-test plugins ship in odoc-jons-plugins:

{&kbd Ctrl-K} -> <kbd>Ctrl-K</kbd>
{&margin an aside} -> <span class="margin-note">an aside</span>

Both render end-to-end after [opam reinstall odoc odoc-jons-plugins].

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+69
+69
src/odoc_jons_plugins.ml
··· 729 729 let () = 730 730 Api.Registry.register (module Figure) 731 731 732 + (* --- Inline extensions --- 733 + 734 + Custom inline elements written as [{&name payload}] in .mld text. 735 + The registry calls us with the raw payload string; we return a 736 + chunk of HTML that is spliced into the output verbatim. 737 + 738 + The extension sets up its own CSS via a separate block-level hook 739 + on first use; that's inconvenient to track per-page, so we register 740 + the CSS once via the shell support file (already pulled into every 741 + page by the jon-shell plugin). *) 742 + 743 + module Margin = struct 744 + let prefix = "margin" 745 + 746 + let escape s = 747 + let b = Buffer.create (Stdlib.String.length s) in 748 + Stdlib.String.iter (fun c -> 749 + match c with 750 + | '&' -> Buffer.add_string b "&amp;" 751 + | '<' -> Buffer.add_string b "&lt;" 752 + | '>' -> Buffer.add_string b "&gt;" 753 + | '"' -> Buffer.add_string b "&quot;" 754 + | c -> Buffer.add_char b c 755 + ) s; 756 + Buffer.contents b 757 + 758 + let to_html payload = 759 + Printf.sprintf {|<span class="margin-note">%s</span>|} (escape payload) 760 + end 761 + 762 + module Kbd = struct 763 + let prefix = "kbd" 764 + let escape = Margin.escape 765 + let to_html payload = 766 + Printf.sprintf {|<kbd>%s</kbd>|} (escape payload) 767 + end 768 + 769 + let inline_extensions_css = {| 770 + /* Inline extension: margin note */ 771 + .margin-note { 772 + display: inline-block; 773 + padding: 0.1em 0.5em; 774 + margin: 0 0.2em; 775 + font-size: 0.85em; 776 + color: var(--text-muted, #666); 777 + background: var(--surface-alt, #f3f3f3); 778 + border-radius: 3px; 779 + border-left: 2px solid var(--accent-color, #b44e2d); 780 + } 781 + @media (prefers-color-scheme: dark) { 782 + .margin-note { 783 + background: rgba(255,255,255,0.04); 784 + color: var(--text-muted, #aaa); 785 + } 786 + } 787 + |} 788 + 789 + let () = 790 + Api.Registry.register_inline (module Margin); 791 + Api.Registry.register_inline (module Kbd); 792 + (* Inline extensions don't have a per-page resource hook; ship their 793 + CSS via the shell plugin's support-file mechanism so it is 794 + available wherever the shell runs. *) 795 + Odoc_extension_registry.register_support_file ~prefix:"jon-shell" 796 + { 797 + filename = "extensions/inline-extensions.css"; 798 + content = Inline inline_extensions_css; 799 + } 800 + 732 801 (* --- Recent posts extension --- *) 733 802 734 803 module Recent_posts = struct