An RFC extension for odoc
at main 129 lines 4.0 kB view raw
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 11open Odoc_extension_api 12 13let prefix = "rfc" 14 15(** CSS styles for RFC references *) 16let 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*) 41let 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 *) 72let 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 *) 83let 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 *) 90let 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 assets = []; 118 } 119 120(* Register extension and support files *) 121let () = 122 Registry.register (module struct 123 let prefix = prefix 124 let to_document = to_document 125 end); 126 Registry.register_support_file ~prefix { 127 filename = "extensions/rfc.css"; 128 content = Inline rfc_css; 129 }