this repo has no description
1
fork

Configure Feed

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

Merge commit '00303068b71a252adc868f2e8004d06cad5b8942'

-975
-28
dune-project
··· 42 42 (package (name odoc-bench)) 43 43 (package (name sherlodoc)) 44 44 45 - ; Example extension packages 46 - (package 47 - (name odoc-dot-extension) 48 - (synopsis "Graphviz/DOT diagram support for odoc documentation") 49 - (description "Renders {@dot[...]} code blocks as SVG diagrams using Graphviz. 50 - Supports width, height, and layout engine options.") 51 - (depends 52 - (ocaml (>= 4.14)) 53 - odoc)) 54 - 55 - (package 56 - (name odoc-mermaid-extension) 57 - (synopsis "Mermaid diagram support for odoc documentation") 58 - (description "Renders {@mermaid[...]} code blocks as interactive diagrams. 59 - Supports flowcharts, sequence diagrams, class diagrams, and more.") 60 - (depends 61 - (ocaml (>= 4.14)) 62 - odoc)) 63 - 64 - (package 65 - (name odoc-msc-extension) 66 - (synopsis "Message Sequence Chart support for odoc documentation") 67 - (description "Renders {@msc[...]} code blocks as message sequence charts. 68 - Uses the MscGen syntax for defining sequence diagrams.") 69 - (depends 70 - (ocaml (>= 4.14)) 71 - odoc)) 72 - 73 45 (using mdx 0.3) 74 46 75 47 ; Sherlodoc
-292
examples/extensions/dot/src/dot_extension.ml
··· 1 - (** Graphviz/DOT diagram extension for odoc. 2 - 3 - Renders [{@dot[...]}] code blocks as diagrams. By default uses client-side 4 - JavaScript (Viz.js), but can render server-side to PNG/SVG with format option. 5 - 6 - Example: 7 - {[ 8 - {@dot layout=neato[ 9 - digraph G { 10 - a -> b -> c; 11 - b -> d; 12 - } 13 - ]} 14 - ]} 15 - *) 16 - 17 - module Api = Odoc_extension_api 18 - module Block = Odoc_document.Types.Block 19 - module Inline = Odoc_document.Types.Inline 20 - 21 - (** The Viz.js library URL for client-side rendering *) 22 - let viz_js_url = "https://unpkg.com/viz.js@2.1.2/viz.js" 23 - let viz_full_js_url = "https://unpkg.com/viz.js@2.1.2/full.render.js" 24 - 25 - (** Generate a unique ID for each diagram *) 26 - let diagram_counter = ref 0 27 - 28 - let fresh_id () = 29 - incr diagram_counter; 30 - Printf.sprintf "dot-diagram-%d" !diagram_counter 31 - 32 - (** Extract option values *) 33 - let get_layout tags = 34 - Api.get_binding "layout" tags 35 - |> Option.value ~default:"dot" 36 - 37 - let get_format tags = 38 - Api.get_binding "format" tags 39 - 40 - let get_filename tags = 41 - Api.get_binding "filename" tags 42 - 43 - let get_dimensions tags = 44 - let width = Api.get_binding "width" tags in 45 - let height = Api.get_binding "height" tags in 46 - (width, height) 47 - 48 - (** Check if content looks like a complete DOT graph *) 49 - let has_graph_wrapper content = 50 - let trimmed = String.trim content in 51 - String.length trimmed > 0 && 52 - (let starts_with prefix s = 53 - String.length s >= String.length prefix && 54 - String.sub s 0 (String.length prefix) = prefix 55 - in 56 - starts_with "digraph" trimmed || 57 - starts_with "graph" trimmed || 58 - starts_with "strict" trimmed) 59 - 60 - (** Wrap content in a digraph if needed *) 61 - let ensure_graph_wrapper content = 62 - if has_graph_wrapper content then content 63 - else Printf.sprintf "digraph G {\n%s\n}" content 64 - 65 - (** Build inline style string from dimensions *) 66 - let make_style width height = 67 - let parts = [] in 68 - let parts = match width with 69 - | Some w -> Printf.sprintf "width: %s" w :: parts 70 - | None -> parts 71 - in 72 - let parts = match height with 73 - | Some h -> Printf.sprintf "height: %s" h :: parts 74 - | None -> parts 75 - in 76 - match parts with 77 - | [] -> "" 78 - | ps -> String.concat "; " (List.rev ps) 79 - 80 - (** Run the dot command to render to a specific format *) 81 - let run_dot ~layout ~format content = 82 - (* Create temp file for input *) 83 - let tmp_in = Filename.temp_file "odoc_dot_" ".dot" in 84 - let tmp_out = Filename.temp_file "odoc_dot_" ("." ^ format) in 85 - Fun.protect ~finally:(fun () -> 86 - (try Sys.remove tmp_in with _ -> ()); 87 - (try Sys.remove tmp_out with _ -> ()) 88 - ) (fun () -> 89 - (* Write DOT content *) 90 - let oc = open_out tmp_in in 91 - output_string oc content; 92 - close_out oc; 93 - (* Run dot command *) 94 - let cmd = Printf.sprintf "dot -K%s -T%s -o %s %s 2>&1" 95 - layout format (Filename.quote tmp_out) (Filename.quote tmp_in) in 96 - let ic = Unix.open_process_in cmd in 97 - let error_output = Buffer.create 256 in 98 - (try 99 - while true do 100 - Buffer.add_string error_output (input_line ic); 101 - Buffer.add_char error_output '\n' 102 - done 103 - with End_of_file -> ()); 104 - let status = Unix.close_process_in ic in 105 - match status with 106 - | Unix.WEXITED 0 -> 107 - (* Read the output file *) 108 - let ic = open_in_bin tmp_out in 109 - let len = in_channel_length ic in 110 - let data = Bytes.create len in 111 - really_input ic data 0 len; 112 - close_in ic; 113 - Ok data 114 - | _ -> 115 - Error (Buffer.contents error_output) 116 - ) 117 - 118 - (** JavaScript code to render a single diagram (for client-side rendering) *) 119 - let render_script id layout content = 120 - Printf.sprintf {| 121 - (function() { 122 - function renderDot() { 123 - var container = document.getElementById('%s'); 124 - if (!container) return; 125 - 126 - if (typeof Viz === 'undefined') { 127 - container.innerHTML = '<pre style="color: red;">Viz.js not loaded</pre>'; 128 - return; 129 - } 130 - 131 - var viz = new Viz(); 132 - viz.renderSVGElement(%S, { engine: %S }) 133 - .then(function(svg) { 134 - container.innerHTML = ''; 135 - container.appendChild(svg); 136 - }) 137 - .catch(function(error) { 138 - container.innerHTML = '<pre style="color: red;">' + error + '</pre>'; 139 - }); 140 - } 141 - 142 - if (document.readyState === 'loading') { 143 - document.addEventListener('DOMContentLoaded', renderDot); 144 - } else { 145 - renderDot(); 146 - } 147 - })(); 148 - |} id content layout 149 - 150 - module Dot_handler : Api.Code_Block_Extension = struct 151 - let prefix = "dot" 152 - 153 - let to_document meta content = 154 - let id = fresh_id () in 155 - let layout = get_layout meta.Api.tags in 156 - let format = get_format meta.Api.tags in 157 - let filename_opt = get_filename meta.Api.tags in 158 - let (width, height) = get_dimensions meta.Api.tags in 159 - let style = make_style width height in 160 - let style_attr = if style = "" then "" else Printf.sprintf " style=\"%s\"" style in 161 - 162 - (* Auto-wrap in digraph if needed *) 163 - let dot_content = ensure_graph_wrapper content in 164 - 165 - match format with 166 - | Some "png" | Some "svg" -> 167 - (* Server-side rendering *) 168 - let fmt = match format with Some f -> f | None -> "png" in 169 - let base_filename = match filename_opt with 170 - | Some f -> f 171 - | None -> Printf.sprintf "dot-%s.%s" id fmt 172 - in 173 - (match run_dot ~layout ~format:fmt dot_content with 174 - | Ok data -> 175 - let html = Printf.sprintf 176 - {|<div id="%s" class="odoc-dot-diagram"%s><img src="%s" alt="DOT diagram" /></div>|} 177 - id style_attr base_filename 178 - in 179 - let block = Block.[{ 180 - attr = ["odoc-dot"]; 181 - desc = Raw_markup ("html", html) 182 - }] in 183 - Some { 184 - Api.content = block; 185 - overrides = []; 186 - resources = []; 187 - assets = [{ Api.asset_filename = base_filename; asset_content = data }]; 188 - } 189 - | Error err -> 190 - (* Show error message *) 191 - let html = Printf.sprintf 192 - "<div id=\"%s\" class=\"odoc-dot-diagram odoc-dot-error\"><pre style=\"color: red;\">Error rendering DOT diagram (is graphviz installed?):\n%s</pre><pre>%s</pre></div>" 193 - id err content 194 - in 195 - let block = Block.[{ 196 - attr = ["odoc-dot"; "odoc-dot-error"]; 197 - desc = Raw_markup ("html", html) 198 - }] in 199 - Some { 200 - Api.content = block; 201 - overrides = []; 202 - resources = []; 203 - assets = []; 204 - }) 205 - 206 - | Some unknown_format -> 207 - (* Unknown format - show error *) 208 - let html = Printf.sprintf 209 - {|<div class="odoc-dot-error"><pre style="color: red;">Unknown format: %s (supported: png, svg)</pre></div>|} 210 - unknown_format 211 - in 212 - let block = Block.[{ 213 - attr = ["odoc-dot-error"]; 214 - desc = Raw_markup ("html", html) 215 - }] in 216 - Some { 217 - Api.content = block; 218 - overrides = []; 219 - resources = []; 220 - assets = []; 221 - } 222 - 223 - | None -> 224 - (* Default: client-side JavaScript rendering *) 225 - let html = Printf.sprintf 226 - {|<div id="%s" class="odoc-dot-diagram"%s><pre>%s</pre></div>|} 227 - id style_attr content 228 - in 229 - let script = render_script id layout dot_content in 230 - let block = Block.[{ 231 - attr = ["odoc-dot"]; 232 - desc = Raw_markup ("html", html) 233 - }] in 234 - Some { 235 - Api.content = block; 236 - overrides = []; 237 - resources = [ 238 - Api.Js_url viz_js_url; 239 - Api.Js_url viz_full_js_url; 240 - Api.Js_inline script; 241 - ]; 242 - assets = []; 243 - } 244 - end 245 - 246 - (** CSS for dot diagrams *) 247 - let dot_css = {| 248 - .odoc-dot-diagram { 249 - margin: 1em 0; 250 - overflow: auto; 251 - } 252 - 253 - .odoc-dot-diagram svg, 254 - .odoc-dot-diagram img { 255 - max-width: 100%; 256 - height: auto; 257 - } 258 - 259 - .odoc-dot-diagram pre { 260 - background: #f5f5f5; 261 - padding: 1em; 262 - border-radius: 4px; 263 - overflow-x: auto; 264 - } 265 - 266 - .odoc-dot-error pre { 267 - color: #c00; 268 - } 269 - |} 270 - 271 - (** Extension documentation *) 272 - let extension_info : Api.extension_info = { 273 - info_kind = `Code_block; 274 - info_prefix = "dot"; 275 - info_description = "Render Graphviz/DOT diagrams. Uses client-side Viz.js by default, or server-side graphviz with format=png|svg."; 276 - info_options = [ 277 - { opt_name = "format"; opt_description = "Output format: png, svg (server-side), or omit for client-side JS"; opt_default = None }; 278 - { opt_name = "layout"; opt_description = "Graphviz layout engine"; opt_default = Some "dot" }; 279 - { opt_name = "width"; opt_description = "CSS width (e.g., 500px, 100%)"; opt_default = None }; 280 - { opt_name = "height"; opt_description = "CSS height"; opt_default = None }; 281 - { opt_name = "filename"; opt_description = "Output filename for server-side rendering"; opt_default = Some "auto-generated" }; 282 - ]; 283 - info_example = Some "{@dot format=png layout=neato[a -> b -> c]}"; 284 - } 285 - 286 - let () = 287 - Api.Registry.register_code_block (module Dot_handler); 288 - Api.Registry.register_extension_info extension_info; 289 - Api.Registry.register_support_file ~prefix:"dot" { 290 - filename = "extensions/dot.css"; 291 - content = dot_css; 292 - }
-10
examples/extensions/dot/src/dune
··· 1 - (library 2 - (name dot_extension) 3 - (public_name odoc-dot-extension.impl) 4 - (libraries odoc_extension_api odoc_parser unix)) 5 - 6 - (plugin 7 - (name odoc-dot-extension) 8 - (package odoc-dot-extension) 9 - (libraries odoc-dot-extension.impl) 10 - (site (odoc extensions)))
-10
examples/extensions/mermaid/src/dune
··· 1 - (library 2 - (name mermaid_extension) 3 - (public_name odoc-mermaid-extension.impl) 4 - (libraries odoc_extension_api odoc_parser unix)) 5 - 6 - (plugin 7 - (name odoc-mermaid-extension) 8 - (package odoc-mermaid-extension) 9 - (libraries odoc-mermaid-extension.impl) 10 - (site (odoc extensions)))
-266
examples/extensions/mermaid/src/mermaid_extension.ml
··· 1 - (** Mermaid diagram extension for odoc. 2 - 3 - Renders [{@mermaid[...]}] code blocks as diagrams. By default uses client-side 4 - JavaScript, but can render server-side to PNG/SVG with format option 5 - (requires mermaid-cli/mmdc). 6 - 7 - Example: 8 - {[ 9 - {@mermaid theme=forest[ 10 - graph LR 11 - A[Start] --> B{Decision} 12 - B -->|Yes| C[OK] 13 - B -->|No| D[Cancel] 14 - ]} 15 - ]} 16 - *) 17 - 18 - module Api = Odoc_extension_api 19 - module Block = Odoc_document.Types.Block 20 - module Inline = Odoc_document.Types.Inline 21 - 22 - (** Mermaid.js CDN URL *) 23 - let mermaid_js_url = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js" 24 - 25 - (** Generate a unique ID for each diagram *) 26 - let diagram_counter = ref 0 27 - 28 - let fresh_id () = 29 - incr diagram_counter; 30 - Printf.sprintf "mermaid-diagram-%d" !diagram_counter 31 - 32 - (** Extract option values *) 33 - let get_theme tags = 34 - Api.get_binding "theme" tags 35 - |> Option.value ~default:"default" 36 - 37 - let get_format tags = 38 - Api.get_binding "format" tags 39 - 40 - let get_filename tags = 41 - Api.get_binding "filename" tags 42 - 43 - let get_dimensions tags = 44 - let width = Api.get_binding "width" tags in 45 - let height = Api.get_binding "height" tags in 46 - (width, height) 47 - 48 - (** Build inline style string from dimensions *) 49 - let make_style width height = 50 - let parts = [] in 51 - let parts = match width with 52 - | Some w -> Printf.sprintf "width: %s" w :: parts 53 - | None -> parts 54 - in 55 - let parts = match height with 56 - | Some h -> Printf.sprintf "height: %s" h :: parts 57 - | None -> parts 58 - in 59 - match parts with 60 - | [] -> "" 61 - | ps -> String.concat "; " (List.rev ps) 62 - 63 - (** HTML-escape content for safe embedding *) 64 - let html_escape s = 65 - let buf = Buffer.create (String.length s) in 66 - String.iter (fun c -> 67 - match c with 68 - | '<' -> Buffer.add_string buf "&lt;" 69 - | '>' -> Buffer.add_string buf "&gt;" 70 - | '&' -> Buffer.add_string buf "&amp;" 71 - | '"' -> Buffer.add_string buf "&quot;" 72 - | c -> Buffer.add_char buf c 73 - ) s; 74 - Buffer.contents buf 75 - 76 - (** Puppeteer config for environments that need --no-sandbox *) 77 - let puppeteer_config = {|{"args": ["--no-sandbox", "--disable-setuid-sandbox"]}|} 78 - 79 - (** Run mmdc (mermaid-cli) to render to a specific format *) 80 - let run_mmdc ~theme ~format content = 81 - let tmp_in = Filename.temp_file "odoc_mermaid_" ".mmd" in 82 - let tmp_out = Filename.temp_file "odoc_mermaid_" ("." ^ format) in 83 - let tmp_config = Filename.temp_file "odoc_mermaid_" ".json" in 84 - Fun.protect ~finally:(fun () -> 85 - (try Sys.remove tmp_in with _ -> ()); 86 - (try Sys.remove tmp_out with _ -> ()); 87 - (try Sys.remove tmp_config with _ -> ()) 88 - ) (fun () -> 89 - (* Write Mermaid content *) 90 - let oc = open_out tmp_in in 91 - output_string oc content; 92 - close_out oc; 93 - (* Write puppeteer config for --no-sandbox *) 94 - let oc = open_out tmp_config in 95 - output_string oc puppeteer_config; 96 - close_out oc; 97 - (* Run mmdc command with puppeteer config *) 98 - let cmd = Printf.sprintf "mmdc -i %s -o %s -t %s -b transparent -p %s 2>&1" 99 - (Filename.quote tmp_in) (Filename.quote tmp_out) theme (Filename.quote tmp_config) in 100 - let ic = Unix.open_process_in cmd in 101 - let error_output = Buffer.create 256 in 102 - (try 103 - while true do 104 - Buffer.add_string error_output (input_line ic); 105 - Buffer.add_char error_output '\n' 106 - done 107 - with End_of_file -> ()); 108 - let status = Unix.close_process_in ic in 109 - match status with 110 - | Unix.WEXITED 0 -> 111 - (* Read the output file *) 112 - let ic = open_in_bin tmp_out in 113 - let len = in_channel_length ic in 114 - let data = Bytes.create len in 115 - really_input ic data 0 len; 116 - close_in ic; 117 - Ok data 118 - | _ -> 119 - Error (Buffer.contents error_output) 120 - ) 121 - 122 - module Mermaid_handler : Api.Code_Block_Extension = struct 123 - let prefix = "mermaid" 124 - 125 - let to_document meta content = 126 - let id = fresh_id () in 127 - let theme = get_theme meta.Api.tags in 128 - let format = get_format meta.Api.tags in 129 - let filename_opt = get_filename meta.Api.tags in 130 - let (width, height) = get_dimensions meta.Api.tags in 131 - let style = make_style width height in 132 - let style_attr = if style = "" then "" else Printf.sprintf " style=\"%s\"" style in 133 - 134 - match format with 135 - | Some "png" | Some "svg" -> 136 - (* Server-side rendering with mmdc *) 137 - let fmt = match format with Some f -> f | None -> "png" in 138 - let base_filename = match filename_opt with 139 - | Some f -> f 140 - | None -> Printf.sprintf "mermaid-%s.%s" id fmt 141 - in 142 - (match run_mmdc ~theme ~format:fmt content with 143 - | Ok data -> 144 - let html = Printf.sprintf 145 - {|<div id="%s" class="odoc-mermaid-diagram"%s><img src="%s" alt="Mermaid diagram" /></div>|} 146 - id style_attr base_filename 147 - in 148 - let block = Block.[{ 149 - attr = ["odoc-mermaid"]; 150 - desc = Raw_markup ("html", html) 151 - }] in 152 - Some { 153 - Api.content = block; 154 - overrides = []; 155 - resources = []; 156 - assets = [{ Api.asset_filename = base_filename; asset_content = data }]; 157 - } 158 - | Error err -> 159 - (* Show error message *) 160 - let html = Printf.sprintf 161 - "<div id=\"%s\" class=\"odoc-mermaid-diagram odoc-mermaid-error\"><pre style=\"color: red;\">Error rendering Mermaid diagram (is mmdc installed?):\n%s</pre><pre>%s</pre></div>" 162 - id err (html_escape content) 163 - in 164 - let block = Block.[{ 165 - attr = ["odoc-mermaid"; "odoc-mermaid-error"]; 166 - desc = Raw_markup ("html", html) 167 - }] in 168 - Some { 169 - Api.content = block; 170 - overrides = []; 171 - resources = []; 172 - assets = []; 173 - }) 174 - 175 - | Some unknown_format -> 176 - let html = Printf.sprintf 177 - {|<div class="odoc-mermaid-error"><pre style="color: red;">Unknown format: %s (supported: png, svg)</pre></div>|} 178 - unknown_format 179 - in 180 - let block = Block.[{ 181 - attr = ["odoc-mermaid-error"]; 182 - desc = Raw_markup ("html", html) 183 - }] in 184 - Some { 185 - Api.content = block; 186 - overrides = []; 187 - resources = []; 188 - assets = []; 189 - } 190 - 191 - | None -> 192 - (* Default: client-side JavaScript rendering *) 193 - let html = Printf.sprintf 194 - {|<div id="%s" class="odoc-mermaid-diagram"%s><pre class="mermaid">%s</pre></div>|} 195 - id style_attr (html_escape content) 196 - in 197 - let init_script = Printf.sprintf {| 198 - if (typeof window.mermaidInitialized === 'undefined') { 199 - window.mermaidInitialized = true; 200 - mermaid.initialize({ 201 - startOnLoad: true, 202 - theme: '%s', 203 - securityLevel: 'loose' 204 - }); 205 - } 206 - |} theme 207 - in 208 - let block = Block.[{ 209 - attr = ["odoc-mermaid"]; 210 - desc = Raw_markup ("html", html) 211 - }] in 212 - Some { 213 - Api.content = block; 214 - overrides = []; 215 - resources = [ 216 - Api.Js_url mermaid_js_url; 217 - Api.Js_inline init_script; 218 - ]; 219 - assets = []; 220 - } 221 - end 222 - 223 - (** CSS for mermaid diagrams *) 224 - let mermaid_css = {| 225 - .odoc-mermaid-diagram { 226 - margin: 1em 0; 227 - overflow: auto; 228 - } 229 - 230 - .odoc-mermaid-diagram svg, 231 - .odoc-mermaid-diagram img { 232 - max-width: 100%; 233 - height: auto; 234 - } 235 - 236 - .odoc-mermaid-diagram pre.mermaid { 237 - background: transparent; 238 - } 239 - 240 - .odoc-mermaid-error pre { 241 - color: #c00; 242 - } 243 - |} 244 - 245 - (** Extension documentation *) 246 - let extension_info : Api.extension_info = { 247 - info_kind = `Code_block; 248 - info_prefix = "mermaid"; 249 - info_description = "Render Mermaid diagrams (flowcharts, sequence diagrams, etc.). Uses client-side JS by default, or server-side mmdc with format=png|svg."; 250 - info_options = [ 251 - { opt_name = "format"; opt_description = "Output format: png, svg (requires mmdc), or omit for client-side JS"; opt_default = None }; 252 - { opt_name = "theme"; opt_description = "Mermaid theme (default, forest, dark, neutral)"; opt_default = Some "default" }; 253 - { opt_name = "width"; opt_description = "CSS width (e.g., 500px, 100%)"; opt_default = None }; 254 - { opt_name = "height"; opt_description = "CSS height"; opt_default = None }; 255 - { opt_name = "filename"; opt_description = "Output filename for server-side rendering"; opt_default = Some "auto-generated" }; 256 - ]; 257 - info_example = Some "{@mermaid format=png theme=forest[graph LR; A-->B]}"; 258 - } 259 - 260 - let () = 261 - Api.Registry.register_code_block (module Mermaid_handler); 262 - Api.Registry.register_extension_info extension_info; 263 - Api.Registry.register_support_file ~prefix:"mermaid" { 264 - filename = "extensions/mermaid.css"; 265 - content = mermaid_css; 266 - }
-10
examples/extensions/msc/src/dune
··· 1 - (library 2 - (name msc_extension) 3 - (public_name odoc-msc-extension.impl) 4 - (libraries odoc_extension_api odoc_parser unix)) 5 - 6 - (plugin 7 - (name odoc-msc-extension) 8 - (package odoc-msc-extension) 9 - (libraries odoc-msc-extension.impl) 10 - (site (odoc extensions)))
-272
examples/extensions/msc/src/msc_extension.ml
··· 1 - (** Message Sequence Chart extension for odoc. 2 - 3 - Renders [{@msc[...]}] code blocks as sequence diagrams. By default uses 4 - client-side JavaScript (mscgen-inpage), but can render server-side to 5 - PNG/SVG with format option (requires mscgen). 6 - 7 - Example: 8 - {[ 9 - {@msc format=png width=600px[ 10 - msc { 11 - a, b, c; 12 - a -> b [label="request"]; 13 - b -> c [label="forward"]; 14 - c -> b [label="response"]; 15 - b -> a [label="reply"]; 16 - } 17 - ]} 18 - ]} 19 - *) 20 - 21 - module Api = Odoc_extension_api 22 - module Block = Odoc_document.Types.Block 23 - module Inline = Odoc_document.Types.Inline 24 - 25 - (** MscGen.js CDN URL - the inpage version auto-renders on DOMContentLoaded *) 26 - let mscgen_js_url = "https://unpkg.com/mscgenjs-inpage@4/dist/mscgen-inpage.js" 27 - 28 - (** Script to load mscgenjs with defer-like behavior *) 29 - let loader_script = Printf.sprintf {| 30 - (function() { 31 - function loadMscgen() { 32 - var script = document.createElement('script'); 33 - script.src = %S; 34 - script.async = false; 35 - document.head.appendChild(script); 36 - } 37 - if (document.readyState === 'loading') { 38 - document.addEventListener('DOMContentLoaded', loadMscgen); 39 - } else { 40 - loadMscgen(); 41 - } 42 - })(); 43 - |} mscgen_js_url 44 - 45 - 46 - (** Generate a unique ID for each diagram *) 47 - let diagram_counter = ref 0 48 - 49 - let fresh_id () = 50 - incr diagram_counter; 51 - Printf.sprintf "msc-diagram-%d" !diagram_counter 52 - 53 - (** Extract option values *) 54 - let get_style tags = 55 - Api.get_binding "named-style" tags 56 - |> Option.value ~default:"basic" 57 - 58 - let get_format tags = 59 - Api.get_binding "format" tags 60 - 61 - let get_filename tags = 62 - Api.get_binding "filename" tags 63 - 64 - (** Extract CSS dimensions *) 65 - let get_dimensions tags = 66 - let width = Api.get_binding "width" tags in 67 - let height = Api.get_binding "height" tags in 68 - (width, height) 69 - 70 - (** Build inline style string from dimensions *) 71 - let make_style width height = 72 - let parts = [] in 73 - let parts = match width with 74 - | Some w -> Printf.sprintf "width: %s" w :: parts 75 - | None -> parts 76 - in 77 - let parts = match height with 78 - | Some h -> Printf.sprintf "height: %s" h :: parts 79 - | None -> parts 80 - in 81 - match parts with 82 - | [] -> "" 83 - | ps -> String.concat "; " (List.rev ps) 84 - 85 - (** HTML-escape content for safe embedding *) 86 - let html_escape s = 87 - let buf = Buffer.create (String.length s) in 88 - String.iter (fun c -> 89 - match c with 90 - | '<' -> Buffer.add_string buf "&lt;" 91 - | '>' -> Buffer.add_string buf "&gt;" 92 - | '&' -> Buffer.add_string buf "&amp;" 93 - | '"' -> Buffer.add_string buf "&quot;" 94 - | c -> Buffer.add_char buf c 95 - ) s; 96 - Buffer.contents buf 97 - 98 - (** Run mscgen to render to a specific format *) 99 - let run_mscgen ~format content = 100 - let tmp_in = Filename.temp_file "odoc_msc_" ".msc" in 101 - let tmp_out = Filename.temp_file "odoc_msc_" ("." ^ format) in 102 - Fun.protect ~finally:(fun () -> 103 - (try Sys.remove tmp_in with _ -> ()); 104 - (try Sys.remove tmp_out with _ -> ()) 105 - ) (fun () -> 106 - (* Write MSC content *) 107 - let oc = open_out tmp_in in 108 - output_string oc content; 109 - close_out oc; 110 - (* Run mscgen command *) 111 - let cmd = Printf.sprintf "mscgen -T %s -i %s -o %s 2>&1" 112 - format (Filename.quote tmp_in) (Filename.quote tmp_out) in 113 - let ic = Unix.open_process_in cmd in 114 - let error_output = Buffer.create 256 in 115 - (try 116 - while true do 117 - Buffer.add_string error_output (input_line ic); 118 - Buffer.add_char error_output '\n' 119 - done 120 - with End_of_file -> ()); 121 - let status = Unix.close_process_in ic in 122 - match status with 123 - | Unix.WEXITED 0 -> 124 - (* Read the output file *) 125 - let ic = open_in_bin tmp_out in 126 - let len = in_channel_length ic in 127 - let data = Bytes.create len in 128 - really_input ic data 0 len; 129 - close_in ic; 130 - Ok data 131 - | _ -> 132 - Error (Buffer.contents error_output) 133 - ) 134 - 135 - module Msc_handler : Api.Code_Block_Extension = struct 136 - let prefix = "msc" 137 - 138 - let to_document meta content = 139 - let id = fresh_id () in 140 - let style_name = get_style meta.Api.tags in 141 - let format = get_format meta.Api.tags in 142 - let filename_opt = get_filename meta.Api.tags in 143 - let (width, height) = get_dimensions meta.Api.tags in 144 - let style = make_style width height in 145 - let style_attr = if style = "" then "" else Printf.sprintf " style=\"%s\"" style in 146 - 147 - match format with 148 - | Some "png" | Some "svg" -> 149 - (* Server-side rendering with mscgen *) 150 - let fmt = match format with Some f -> f | None -> "png" in 151 - let base_filename = match filename_opt with 152 - | Some f -> f 153 - | None -> Printf.sprintf "msc-%s.%s" id fmt 154 - in 155 - (match run_mscgen ~format:fmt content with 156 - | Ok data -> 157 - let html = Printf.sprintf 158 - {|<div id="%s" class="odoc-msc-diagram"%s><img src="%s" alt="MSC diagram" /></div>|} 159 - id style_attr base_filename 160 - in 161 - let block = Block.[{ 162 - attr = ["odoc-msc"]; 163 - desc = Raw_markup ("html", html) 164 - }] in 165 - Some { 166 - Api.content = block; 167 - overrides = []; 168 - resources = []; 169 - assets = [{ Api.asset_filename = base_filename; asset_content = data }]; 170 - } 171 - | Error err -> 172 - (* Show error message *) 173 - let html = Printf.sprintf 174 - "<div id=\"%s\" class=\"odoc-msc-diagram odoc-msc-error\"><pre style=\"color: red;\">Error rendering MSC diagram (is mscgen installed?):\n%s</pre><pre>%s</pre></div>" 175 - id err (html_escape content) 176 - in 177 - let block = Block.[{ 178 - attr = ["odoc-msc"; "odoc-msc-error"]; 179 - desc = Raw_markup ("html", html) 180 - }] in 181 - Some { 182 - Api.content = block; 183 - overrides = []; 184 - resources = []; 185 - assets = []; 186 - }) 187 - 188 - | Some unknown_format -> 189 - let html = Printf.sprintf 190 - {|<div class="odoc-msc-error"><pre style="color: red;">Unknown format: %s (supported: png, svg)</pre></div>|} 191 - unknown_format 192 - in 193 - let block = Block.[{ 194 - attr = ["odoc-msc-error"]; 195 - desc = Raw_markup ("html", html) 196 - }] in 197 - Some { 198 - Api.content = block; 199 - overrides = []; 200 - resources = []; 201 - assets = []; 202 - } 203 - 204 - | None -> 205 - (* Default: client-side JavaScript rendering *) 206 - let data_style = if style_name = "basic" then "" else Printf.sprintf " data-named-style=\"%s\"" style_name in 207 - let html = Printf.sprintf 208 - {|<div id="%s" class="odoc-msc-diagram"%s><script type="text/x-mscgen"%s>%s</script><noscript><pre>%s</pre></noscript></div>|} 209 - id style_attr data_style content (html_escape content) 210 - in 211 - let block = Block.[{ 212 - attr = ["odoc-msc"]; 213 - desc = Raw_markup ("html", html) 214 - }] in 215 - Some { 216 - Api.content = block; 217 - overrides = []; 218 - resources = [ 219 - Api.Js_inline loader_script; 220 - ]; 221 - assets = []; 222 - } 223 - end 224 - 225 - (** CSS for MSC diagrams *) 226 - let msc_css = {| 227 - .odoc-msc-diagram { 228 - margin: 1em 0; 229 - overflow: auto; 230 - } 231 - 232 - .odoc-msc-diagram svg, 233 - .odoc-msc-diagram img { 234 - max-width: 100%; 235 - height: auto; 236 - } 237 - 238 - /* Fallback for noscript */ 239 - .odoc-msc-diagram noscript pre { 240 - background: #f8f8f8; 241 - padding: 1em; 242 - border-radius: 4px; 243 - overflow-x: auto; 244 - } 245 - 246 - .odoc-msc-error pre { 247 - color: #c00; 248 - } 249 - |} 250 - 251 - (** Extension documentation *) 252 - let extension_info : Api.extension_info = { 253 - info_kind = `Code_block; 254 - info_prefix = "msc"; 255 - info_description = "Render Message Sequence Charts. Uses client-side mscgen-inpage.js by default, or server-side mscgen with format=png|svg."; 256 - info_options = [ 257 - { opt_name = "format"; opt_description = "Output format: png, svg (requires mscgen), or omit for client-side JS"; opt_default = None }; 258 - { opt_name = "named-style"; opt_description = "MscGen style (basic, lazy, classic, etc.)"; opt_default = Some "basic" }; 259 - { opt_name = "width"; opt_description = "CSS width (e.g., 500px, 100%)"; opt_default = None }; 260 - { opt_name = "height"; opt_description = "CSS height"; opt_default = None }; 261 - { opt_name = "filename"; opt_description = "Output filename for server-side rendering"; opt_default = Some "auto-generated" }; 262 - ]; 263 - info_example = Some "{@msc format=png[msc { a,b; a->b; }]}"; 264 - } 265 - 266 - let () = 267 - Api.Registry.register_code_block (module Msc_handler); 268 - Api.Registry.register_extension_info extension_info; 269 - Api.Registry.register_support_file ~prefix:"msc" { 270 - filename = "extensions/msc.css"; 271 - content = msc_css; 272 - }
-29
odoc-dot-extension.opam
··· 1 - opam-version: "2.0" 2 - version: "dev" 3 - synopsis: "Graphviz/DOT diagram support for odoc documentation" 4 - description: """ 5 - Renders {@dot[...]} code blocks as SVG diagrams using Graphviz. 6 - Supports width, height, and layout engine options.""" 7 - maintainer: ["Jon Ludlam <jon@recoil.org>"] 8 - authors: ["Jon Ludlam <jon@recoil.org>"] 9 - license: "ISC" 10 - homepage: "https://github.com/ocaml/odoc" 11 - bug-reports: "https://github.com/ocaml/odoc/issues" 12 - dev-repo: "git+https://github.com/ocaml/odoc.git" 13 - depends: [ 14 - "dune" {>= "3.18"} 15 - "ocaml" {>= "4.14"} 16 - "odoc" {= version} 17 - ] 18 - build: [ 19 - [ 20 - "dune" 21 - "build" 22 - "-p" 23 - name 24 - "-j" 25 - jobs 26 - "@install" 27 - ] 28 - ] 29 - x-maintenance-intent: ["(latest)"]
-29
odoc-mermaid-extension.opam
··· 1 - opam-version: "2.0" 2 - version: "dev" 3 - synopsis: "Mermaid diagram support for odoc documentation" 4 - description: """ 5 - Renders {@mermaid[...]} code blocks as interactive diagrams. 6 - Supports flowcharts, sequence diagrams, class diagrams, and more.""" 7 - maintainer: ["Jon Ludlam <jon@recoil.org>"] 8 - authors: ["Jon Ludlam <jon@recoil.org>"] 9 - license: "ISC" 10 - homepage: "https://github.com/ocaml/odoc" 11 - bug-reports: "https://github.com/ocaml/odoc/issues" 12 - dev-repo: "git+https://github.com/ocaml/odoc.git" 13 - depends: [ 14 - "dune" {>= "3.18"} 15 - "ocaml" {>= "4.14"} 16 - "odoc" {= version} 17 - ] 18 - build: [ 19 - [ 20 - "dune" 21 - "build" 22 - "-p" 23 - name 24 - "-j" 25 - jobs 26 - "@install" 27 - ] 28 - ] 29 - x-maintenance-intent: ["(latest)"]
-29
odoc-msc-extension.opam
··· 1 - opam-version: "2.0" 2 - version: "dev" 3 - synopsis: "Message Sequence Chart support for odoc documentation" 4 - description: """ 5 - Renders {@msc[...]} code blocks as message sequence charts. 6 - Uses the MscGen syntax for defining sequence diagrams.""" 7 - maintainer: ["Jon Ludlam <jon@recoil.org>"] 8 - authors: ["Jon Ludlam <jon@recoil.org>"] 9 - license: "ISC" 10 - homepage: "https://github.com/ocaml/odoc" 11 - bug-reports: "https://github.com/ocaml/odoc/issues" 12 - dev-repo: "git+https://github.com/ocaml/odoc.git" 13 - depends: [ 14 - "dune" {>= "3.18"} 15 - "ocaml" {>= "4.14"} 16 - "odoc" {= version} 17 - ] 18 - build: [ 19 - [ 20 - "dune" 21 - "build" 22 - "-p" 23 - name 24 - "-j" 25 - jobs 26 - "@install" 27 - ] 28 - ] 29 - x-maintenance-intent: ["(latest)"]