An RFC extension for odoc
0
fork

Configure Feed

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

Initial commit: odoc-rfc-extension

RFC citation support for odoc documentation.
Renders @rfc tags as links to IETF RFC documents.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Jon Ludlam 6b7f89d3

+176
+12
dune-project
··· 1 + (lang dune 3.18) 2 + (using dune_site 0.1) 3 + (name odoc-rfc-extension) 4 + (generate_opam_files true) 5 + 6 + (package 7 + (name odoc-rfc-extension) 8 + (synopsis "RFC reference extension for odoc") 9 + (description "Provides @rfc tags for linking to IETF RFCs in odoc documentation") 10 + (depends 11 + (ocaml (>= 4.14)) 12 + odoc))
+27
odoc-rfc-extension.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "RFC reference extension for odoc" 4 + description: 5 + "Provides @rfc tags for linking to IETF RFCs in odoc documentation" 6 + depends: [ 7 + "dune" {>= "3.18"} 8 + "ocaml" {>= "4.14"} 9 + "odoc" 10 + ] 11 + build: [ 12 + ["dune" "subst"] {dev} 13 + [ 14 + "dune" 15 + "build" 16 + "-p" 17 + name 18 + "-j" 19 + jobs 20 + "--promote-install-files=false" 21 + "@install" 22 + "@runtest" {with-test} 23 + "@doc" {with-doc} 24 + ] 25 + ["dune" "install" "-p" name "--create-install-files" name] 26 + ] 27 + x-maintenance-intent: ["(latest)"]
+9
src/dune
··· 1 + (library 2 + (public_name odoc-rfc-extension.impl) 3 + (name rfc_extension) 4 + (libraries odoc.extension_api)) 5 + 6 + (plugin 7 + (name odoc-rfc-extension) 8 + (libraries odoc-rfc-extension.impl) 9 + (site (odoc extensions)))
+128
src/rfc_extension.ml
··· 1 + (** RFC extension for odoc. 2 + 3 + Provides tags for linking to IETF RFCs: 4 + - [@rfc 9110] - Link to RFC 9110 5 + - [@rfc 9110 Section 5.5] - Link to RFC 9110 Section 5.5 6 + - [@rfc 9110 section-5.5] - Link to RFC 9110 with anchor 7 + 8 + The extension generates links to https://www.rfc-editor.org/rfc/rfcNNNN 9 + *) 10 + 11 + open Odoc_extension_api 12 + 13 + let prefix = "rfc" 14 + 15 + (** CSS styles for RFC references *) 16 + let rfc_css = {| 17 + /* RFC extension styles */ 18 + .rfc-reference { 19 + font-family: monospace; 20 + background: #f5f5f5; 21 + padding: 0.1em 0.3em; 22 + border-radius: 3px; 23 + border: 1px solid #ddd; 24 + } 25 + .rfc-reference a { 26 + text-decoration: none; 27 + color: #0366d6; 28 + } 29 + .rfc-reference a:hover { 30 + text-decoration: underline; 31 + } 32 + |} 33 + 34 + (** Parse RFC reference from tag content. 35 + Supports formats: 36 + - "9110" -> RFC 9110 37 + - "9110 Section 5.5" -> RFC 9110 Section 5.5 38 + - "9110 section-5.5" -> RFC 9110 with anchor #section-5.5 39 + - "RFC 9110" -> RFC 9110 (optional RFC prefix) 40 + *) 41 + let parse_rfc_reference content = 42 + let text = String.trim (text_of_nestable_block_elements content) in 43 + (* Remove optional "RFC " prefix *) 44 + let text = 45 + if String.length text > 4 && 46 + (String.sub text 0 4 = "RFC " || String.sub text 0 4 = "rfc ") then 47 + String.sub text 4 (String.length text - 4) 48 + else 49 + text 50 + in 51 + (* Split on first space to get RFC number and optional section *) 52 + match String.index_opt text ' ' with 53 + | None -> 54 + (* Just RFC number *) 55 + (text, None) 56 + | Some i -> 57 + let rfc_num = String.sub text 0 i in 58 + let rest = String.trim (String.sub text (i + 1) (String.length text - i - 1)) in 59 + (* Check if it's "Section X.Y" or an anchor like "section-5.5" *) 60 + if String.length rest > 8 && 61 + (String.sub rest 0 8 = "Section " || String.sub rest 0 8 = "section ") then 62 + (rfc_num, Some (`Section (String.sub rest 8 (String.length rest - 8)))) 63 + else if String.length rest > 0 && rest.[0] = '#' then 64 + (rfc_num, Some (`Anchor (String.sub rest 1 (String.length rest - 1)))) 65 + else if String.contains rest '-' then 66 + (* Treat as anchor if it contains a hyphen (e.g., "section-5.5") *) 67 + (rfc_num, Some (`Anchor rest)) 68 + else 69 + (rfc_num, Some (`Section rest)) 70 + 71 + (** Generate URL for RFC reference *) 72 + let rfc_url rfc_num section = 73 + let base = Printf.sprintf "https://www.rfc-editor.org/rfc/rfc%s" rfc_num in 74 + match section with 75 + | None -> base 76 + | Some (`Anchor anchor) -> base ^ "#" ^ anchor 77 + | Some (`Section sec) -> 78 + (* Convert "5.5" to "section-5.5" anchor *) 79 + let anchor = "section-" ^ (String.map (fun c -> if c = ' ' then '-' else c) sec) in 80 + base ^ "#" ^ anchor 81 + 82 + (** Generate display text for RFC reference *) 83 + let rfc_display_text rfc_num section = 84 + match section with 85 + | None -> Printf.sprintf "RFC %s" rfc_num 86 + | Some (`Anchor _) -> Printf.sprintf "RFC %s" rfc_num 87 + | Some (`Section sec) -> Printf.sprintf "RFC %s Section %s" rfc_num sec 88 + 89 + (** Document phase - generate RFC link *) 90 + let to_document ~tag:_ content = 91 + let rfc_num, section = parse_rfc_reference content in 92 + let url = rfc_url rfc_num section in 93 + let display = rfc_display_text rfc_num section in 94 + 95 + (* Create inline link wrapped in styled span *) 96 + let link_inline = Inline.[{ 97 + attr = []; 98 + desc = Link { 99 + target = External url; 100 + content = [{ attr = []; desc = Text display }]; 101 + tooltip = Some (Printf.sprintf "IETF %s" display); 102 + } 103 + }] in 104 + 105 + (* Wrap in a span with rfc-reference class *) 106 + let content = Block.[{ 107 + attr = [ "rfc-reference" ]; 108 + desc = Inline link_inline 109 + }] in 110 + 111 + { 112 + content; 113 + overrides = []; 114 + resources = [ 115 + Css_url "extensions/rfc.css"; 116 + ]; 117 + } 118 + 119 + (* Register extension and support files *) 120 + let () = 121 + Registry.register (module struct 122 + let prefix = prefix 123 + let to_document = to_document 124 + end); 125 + Registry.register_support_file ~prefix { 126 + filename = "extensions/rfc.css"; 127 + content = rfc_css; 128 + }