this repo has no description
at main 554 lines 19 kB view raw
1open Odoc_document.Types 2open Types 3module Doctree = Odoc_document.Doctree 4module Url = Odoc_document.Url 5 6type config = { 7 with_children : bool; 8 shorten_beyond_depth : int option; 9 remove_functor_arg_link : bool; 10} 11 12module Link = struct 13 let rec flatten_path ppf (x : Odoc_document.Url.Path.t) = 14 let pp_parent ppf = function 15 | Some p -> Format.fprintf ppf "%a-" flatten_path p 16 | None -> () 17 in 18 Format.fprintf ppf "%a%a%s" pp_parent x.parent 19 Url.Path.pp_disambiguating_prefix x.kind x.name 20 21 let page p = Format.asprintf "%a" flatten_path p 22 23 let anchor p a = Format.asprintf "%a--%s" flatten_path p a 24 25 let label (x : Odoc_document.Url.t) = 26 match x.anchor with "" -> page x.page | a -> anchor x.page a 27 28 let rec is_inside_param (x : Odoc_document.Url.Path.t) = 29 match (x.kind, x.parent) with 30 | `Parameter _, _ -> true 31 | _, None -> false 32 | _, Some p -> is_inside_param p 33 34 let ref config (x : Odoc_document.Url.t) = 35 if config.remove_functor_arg_link && is_inside_param x.page then "" 36 else label x 37 38 let get_dir_and_file url = 39 let open Odoc_document in 40 let l = Url.Path.to_list url in 41 let is_dir = function `Page -> `IfNotLast | _ -> `Never in 42 let dir, file = Url.Path.split ~is_dir l in 43 let segment_to_string (_kind, name) = name in 44 ( List.map segment_to_string dir, 45 String.concat "." (List.map segment_to_string file) ) 46 47 let filename ?(add_ext = true) url = 48 let dir, file = get_dir_and_file url in 49 let file = Fpath.(v (String.concat dir_sep (dir @ [ file ]))) in 50 if add_ext then Fpath.add_ext "tex" file else file 51end 52 53module Expansion = struct 54 let is_class_or_module (url : Odoc_document.Url.Path.t) = 55 match url.kind with 56 | `Module | `LeafPage | `Class | `Page -> true 57 | _ -> false 58 59 let shortened config status url = 60 let depth x = List.length Odoc_document.Url.(Path.to_list x) in 61 match (config.shorten_beyond_depth, status) with 62 | None, _ | _, (`Inline | `Open | `Closed) -> false 63 | Some d, `Default -> depth url >= d 64 65 let should_inline status url = 66 match status with 67 | `Inline | `Open -> true 68 | `Closed -> false 69 | `Default -> 70 (* we don't inline contents that should appear in their own page.*) 71 not (is_class_or_module url) 72 73 let remove_subpage config status url = 74 shortened config status url || should_inline status url 75end 76 77let style = function 78 | `Emphasis | `Italic -> Raw.emph 79 | `Bold -> Raw.bold 80 | `Subscript -> Raw.subscript 81 | `Superscript -> Raw.superscript 82 83let gen_hyperref pp r ppf = 84 match (r.target, r.text) with 85 | "", None -> () 86 | "", Some content -> Raw.inline_code pp ppf content 87 | s, None -> Raw.ref ppf s 88 | s, Some content -> 89 let pp = 90 if r.short then Raw.inline_code pp 91 else fun ppf x -> 92 Fmt.pf ppf "%a[p%a]" (Raw.inline_code pp) x Raw.pageref_star s 93 in 94 Raw.hyperref s pp ppf content 95 96let label = function None -> [] | Some x -> [ Label (Link.label x) ] 97 98let level_macro = function 99 | 0 -> Raw.section 100 | 1 -> Raw.subsection 101 | 2 -> Raw.subsubsection 102 | 3 | _ -> Raw.subsubsection 103 104let none _ppf () = () 105 106let list kind pp ppf x = 107 let list = 108 match kind with Block.Ordered -> Raw.enumerate | Unordered -> Raw.itemize 109 in 110 let elt ppf = Raw.item pp ppf in 111 match x with 112 | [] -> (* empty list are not supported *) () 113 | _ -> list (Fmt.list ~sep:(fun ppf () -> Raw.break ppf Aesthetic) elt) ppf x 114 115let escape_entity = function "#45" -> "-" | "gt" -> ">" | s -> s 116 117let elt_size (x : elt) = 118 match x with 119 | Txt _ | Internal_ref _ | External_ref _ | Label _ | Style _ | Inlined_code _ 120 | Code_fragment _ | Tag _ | Break _ | Ligaturable _ -> 121 Small 122 | List _ | Section _ | Verbatim _ | Raw _ | Code_block _ | Indented _ 123 | Description _ | Image _ -> 124 Large 125 | Table _ | Layout_table _ -> Huge 126 127let layout_table = function 128 | [] -> [] 129 | a :: _ as m -> 130 let start = List.map (fun _ -> Empty) a in 131 let content_size l = 132 List.fold_left (fun s x -> max s (elt_size x)) Empty l 133 in 134 let row mask l = List.map2 (fun x y -> max x @@ content_size y) mask l in 135 let mask = List.fold_left row start m in 136 let filter_empty = function 137 | Empty, _ -> None 138 | (Small | Large | Huge), x -> Some x 139 in 140 let filter_row row = 141 Odoc_utils.List.filter_map filter_empty @@ List.combine mask row 142 in 143 let row_size = List.fold_left max Empty mask in 144 [ Layout_table { row_size; tbl = List.map filter_row m } ] 145 146let txt ~verbatim ~in_source ws = 147 if verbatim then [ Txt ws ] 148 else 149 let escaped = List.map (Raw.Escape.text ~code_hyphenation:in_source) ws in 150 match List.filter (( <> ) "") escaped with [] -> [] | l -> [ Txt l ] 151 152let entity ~in_source ~verbatim x = 153 if in_source && not verbatim then Ligaturable (escape_entity x) 154 else Txt [ escape_entity x ] 155 156(** Tables with too many rows are hard to typeset correctly on the same page. 157 Splitting tables on multiple pages is unreliable with longtable + hyperref. 158 Thus we limit the height of the tables that we render as latex tables. This 159 variable is kept separated because we may want to make it tunable by the 160 user. *) 161let small_table_height_limit = 10 162 163let rec pp_elt ppf = function 164 | Txt words -> Fmt.list Fmt.string ~sep:none ppf words 165 | Section { level; label; content } -> 166 let with_label ppf (label, content) = 167 pp ppf content; 168 match label with None -> () | Some label -> Raw.label ppf label 169 in 170 level_macro level with_label ppf (label, content) 171 | Break lvl -> Raw.break ppf lvl 172 | Raw s -> Fmt.string ppf s 173 | Verbatim s -> Raw.verbatim ppf s 174 | Internal_ref r -> hyperref ppf r 175 | External_ref (l, x) -> href ppf (l, x) 176 | Style (s, x) -> style s pp ppf x 177 | Code_block [] -> () 178 | Code_block x -> Raw.code_block pp ppf x 179 | Inlined_code x -> Raw.inline_code pp ppf x 180 | Code_fragment x -> Raw.code_fragment pp ppf x 181 | List { typ; items } -> list typ pp ppf items 182 | Description items -> Raw.description pp ppf items 183 | Table { align; data } -> Raw.small_table pp ppf (Some align, data) 184 | Layout_table { row_size = Large | Huge; tbl } -> large_table ppf tbl 185 | Layout_table { row_size = Small | Empty; tbl } -> 186 if List.length tbl <= small_table_height_limit then 187 Raw.small_table pp ppf (None, tbl) 188 else large_table ppf tbl 189 | Label x -> Raw.label ppf x 190 | Indented x -> Raw.indent pp ppf x 191 | Ligaturable s -> Fmt.string ppf s 192 | Tag (s, t) -> tag s ppf t 193 | Image target -> Raw.includegraphics Fpath.pp ppf target 194 195and pp ppf = function 196 | [] -> () 197 | Break _ :: ((Layout_table _ | Table _) :: _ as q) -> pp ppf q 198 | ((Layout_table _ | Table _) as t) :: Break _ :: q -> pp ppf (t :: q) 199 | Break a :: Break b :: q -> pp ppf (Break (max a b) :: q) 200 | Ligaturable "-" :: Ligaturable ">" :: q -> 201 Raw.rightarrow ppf; 202 pp ppf q 203 | a :: q -> 204 pp_elt ppf a; 205 pp ppf q 206 207and hyperref ppf r = gen_hyperref pp r ppf 208 209and href ppf (l, txt) = 210 match txt with 211 | Some txt -> 212 Raw.href l pp ppf txt; 213 Raw.footnote ppf l 214 | None -> Raw.url ppf l 215 216and large_table ppf tbl = 217 let rec row ppf = function 218 | [] -> Raw.break ppf Line 219 | [ a ] -> 220 pp ppf a; 221 Raw.break ppf Line 222 | [ a; b ] -> Fmt.pf ppf "%a%a%a" pp a Raw.break Aesthetic (Raw.indent pp) b 223 | a :: (_ :: _ as q) -> 224 Fmt.pf ppf "%a%a%a" pp a Raw.break Aesthetic (Raw.indent row) q 225 in 226 let matrix ppf m = List.iter (row ppf) m in 227 Raw.indent matrix ppf tbl 228 229and tag s ppf x = Raw.ocamltag s pp ppf x 230 231let raw_markup (t : Raw_markup.t) = 232 let target, content = t in 233 match Astring.String.Ascii.lowercase target with 234 | "latex" | "tex" -> [ Raw content ] 235 | _ -> [] 236 237let source k (t : Source.t) = 238 let rec token (x : Source.token) = 239 match x with 240 | Elt i -> k i 241 | Tag (None, l) -> tokens l 242 | Tag (Some s, l) -> [ Tag (s, tokens l) ] 243 and tokens t = Odoc_utils.List.concat_map token t in 244 tokens t 245 246let rec internalref ~config ~verbatim ~in_source (t : Target.internal) 247 (c : Inline.t) = 248 let target = 249 match t with 250 | Target.Resolved uri -> Link.ref config uri 251 | Unresolved -> "xref-unresolved" 252 in 253 let text = inline ~config ~verbatim ~in_source c in 254 let short = in_source in 255 Internal_ref { short; target; text = Some text } 256 257and inline ~config ~in_source ~verbatim (l : Inline.t) = 258 let one (t : Inline.one) = 259 match t.desc with 260 | Text _s -> assert false 261 | Linebreak -> [ Break Line ] 262 | Styled (style, c) -> 263 [ Style (style, inline ~config ~verbatim ~in_source c) ] 264 | Link { target = External ext; content = c; _ } -> 265 let content = inline ~config ~verbatim:false ~in_source:false c in 266 [ External_ref (ext, Some content) ] 267 | Link { target = Internal ref_; content = c; _ } -> 268 [ internalref ~config ~in_source ~verbatim ref_ c ] 269 | Source c -> 270 [ 271 Inlined_code 272 (source (inline ~config ~verbatim:false ~in_source:true) c); 273 ] 274 | Math s -> [ Raw (Format.asprintf "%a" Raw.math s) ] 275 | Raw_markup r -> raw_markup r 276 | Entity s -> [ entity ~in_source ~verbatim s ] 277 in 278 279 let take_text (l : Inline.t) = 280 Doctree.Take.until l ~classify:(function 281 | { Inline.desc = Text code; _ } -> Accum [ code ] 282 | { desc = Entity e; _ } -> Accum [ escape_entity e ] 283 | _ -> Stop_and_keep) 284 in 285 (* if in_source then block_code_txt s else if_not_empty (fun x -> Txt x) s *) 286 let rec prettify = function 287 | { Inline.desc = Inline.Text _; _ } :: _ as l -> 288 let words, _, rest = take_text l in 289 txt ~in_source ~verbatim words @ prettify rest 290 | o :: q -> one o @ prettify q 291 | [] -> [] 292 in 293 prettify l 294 295let heading ~config p (h : Heading.t) = 296 let content = inline ~config ~in_source:false ~verbatim:false h.title in 297 [ 298 Section 299 { label = Option.map (Link.anchor p) h.label; level = h.level; content }; 300 Break Aesthetic; 301 ] 302 303let non_empty_block_code ~config c = 304 let s = source (inline ~config ~verbatim:true ~in_source:true) c in 305 match s with 306 | [] -> [] 307 | _ :: _ as l -> [ Break Separation; Code_block l; Break Separation ] 308 309let non_empty_code_fragment ~config c = 310 let s = source (inline ~config ~verbatim:false ~in_source:true) c in 311 match s with [] -> [] | _ :: _ as l -> [ Code_fragment l ] 312 313let alt_text ~in_source (target : Target.t) alt = 314 let text = txt ~verbatim:false ~in_source:false [ alt ] in 315 let break = if in_source then [] else [ Break Paragraph ] in 316 match target with 317 | Internal _ -> text @ break 318 | External l -> [ External_ref (l, Some text) ] @ break 319 320let image ~in_source (internal_url : Url.t) alt = 321 let dir, file = Link.get_dir_and_file internal_url.page in 322 match Fpath.(get_ext @@ v file) with 323 (* list imported from pdftex.def *) 324 | "" | ".pdf" | ".png" | ".jpg" | ".mps" | ".jpeg" | ".jbig2" | ".jb2" 325 | ".PDF" | ".PNG" | ".JPG" | ".JPEG" | ".JBIG2" | ".JB2" -> 326 let fpath = Fpath.v (String.concat Fpath.dir_sep (dir @ [ file ])) in 327 [ Image fpath ] 328 | _ -> alt_text ~in_source (Internal (Resolved internal_url)) alt 329 330let rec block ~config ~in_source (l : Block.t) = 331 let one (t : Block.one) = 332 match t.desc with 333 | Inline i -> inline ~config ~verbatim:false ~in_source:false i 334 | Image (Internal (Resolved x), alt) -> image ~in_source x alt 335 | Image (t, alt) | Audio (t, alt) | Video (t, alt) -> 336 alt_text ~in_source t alt 337 | Paragraph i -> 338 inline ~config ~in_source:false ~verbatim:false i 339 @ if in_source then [] else [ Break Paragraph ] 340 | List (typ, l) -> 341 [ List { typ; items = List.map (block ~config ~in_source:false) l } ] 342 | Table t -> table_block ~config t 343 | Description l -> 344 [ 345 (let item i = 346 ( inline ~config ~in_source ~verbatim:false i.Description.key, 347 block ~config ~in_source i.Description.definition ) 348 in 349 Description (List.map item l)); 350 ] 351 | Raw_markup r -> raw_markup r 352 | Verbatim s -> [ Verbatim s ] 353 | Source (_, _, _, c, _) -> non_empty_block_code ~config c 354 | Math s -> 355 [ 356 Break Paragraph; 357 Raw (Format.asprintf "%a" Raw.equation s); 358 Break Paragraph; 359 ] 360 in 361 Odoc_utils.List.concat_map one l 362 363and table_block ~config { Table.data; align } = 364 let data = 365 List.map 366 (List.map (fun (cell, cell_type) -> 367 let content = block ~config ~in_source:false cell in 368 match cell_type with 369 | `Header -> [ Style (`Bold, content) ] 370 | `Data -> content)) 371 data 372 in 373 [ Table { align; data } ] 374 375let rec is_only_text l = 376 let is_text : Item.t -> _ = function 377 | Heading _ | Text _ -> true 378 | Declaration _ -> false 379 | Include { content = items; _ } -> is_only_text items.content 380 in 381 List.for_all is_text l 382 383let rec documentedSrc ~config (t : DocumentedSrc.t) = 384 let open DocumentedSrc in 385 let rec to_latex t = 386 match t with 387 | [] -> [] 388 | Code _ :: _ -> 389 let take_code l = 390 Doctree.Take.until l ~classify:(function 391 | Code code -> Accum code 392 | _ -> Stop_and_keep) 393 in 394 let code, _, rest = take_code t in 395 non_empty_code_fragment ~config code @ to_latex rest 396 | Alternative (Expansion e) :: rest -> 397 let elt = 398 (* In the [should_inline] or [shortened], we are replacing the 399 independent page by the inlined contents, thus we need to redirect 400 the links to the missing page to the inlined contents. 401 redirect the *) 402 if Expansion.should_inline e.status e.url then 403 Label (Link.page e.url) :: to_latex e.expansion 404 else if Expansion.shortened config e.status e.url then 405 Label (Link.page e.url) :: non_empty_code_fragment ~config e.summary 406 else non_empty_code_fragment ~config e.summary 407 in 408 elt @ to_latex rest 409 | Subpage subp :: rest -> 410 Indented (items ~config subp.content.url subp.content.items) 411 :: to_latex rest 412 | (Documented _ | Nested _) :: _ -> 413 let take_descr l = 414 Doctree.Take.until l ~classify:(function 415 | Documented { attrs; anchor; code; doc; markers } -> 416 Accum 417 [ 418 { 419 DocumentedSrc.attrs; 420 anchor; 421 code = `D code; 422 doc; 423 markers; 424 }; 425 ] 426 | Nested { attrs; anchor; code; doc; markers } -> 427 Accum 428 [ 429 { 430 DocumentedSrc.attrs; 431 anchor; 432 code = `N code; 433 doc; 434 markers; 435 }; 436 ] 437 | _ -> Stop_and_keep) 438 in 439 let l, _, rest = take_descr t in 440 let one dsrc = 441 let content = 442 match dsrc.code with 443 | `D code -> inline ~config ~verbatim:false ~in_source:true code 444 | `N n -> to_latex n 445 in 446 let doc = [ block ~config ~in_source:true dsrc.doc ] in 447 (content @ label dsrc.anchor) :: doc 448 in 449 layout_table (List.map one l) @ to_latex rest 450 in 451 to_latex t 452 453and items ~config page_url l = 454 let rec walk_items ~page_url ~only_text acc (t : Item.t list) = 455 let continue_with rest elts = 456 walk_items ~page_url ~only_text (List.rev_append elts acc) rest 457 in 458 match t with 459 | [] -> List.rev acc 460 | Text _ :: _ as t -> 461 let text, _, rest = 462 Doctree.Take.until t ~classify:(function 463 | Item.Text text -> Accum text 464 | _ -> Stop_and_keep) 465 in 466 let content = block ~config ~in_source:false text in 467 let elts = content in 468 elts |> continue_with rest 469 | Heading h :: rest -> heading ~config page_url h |> continue_with rest 470 | Include 471 { 472 attr = _; 473 source_anchor = _; 474 anchor; 475 doc; 476 content = { summary; status = _; content }; 477 } 478 :: rest -> 479 let included = items page_url content in 480 let docs = block ~config ~in_source:true doc in 481 let summary = 482 source (inline ~config ~verbatim:false ~in_source:true) summary 483 in 484 let content = included in 485 label anchor @ docs @ summary @ content |> continue_with rest 486 | Declaration { Item.attr = _; source_anchor = _; anchor; content; doc } 487 :: rest -> 488 let content = label anchor @ documentedSrc ~config content in 489 let elts = 490 match doc with 491 | [] -> content @ [ Break Line ] 492 | docs -> 493 content 494 @ [ 495 Indented (block ~config ~in_source:true docs); 496 Break Separation; 497 ] 498 in 499 continue_with rest elts 500 and items page_url l = 501 walk_items ~page_url ~only_text:(is_only_text l) [] l 502 in 503 items page_url l 504 505module Doc = struct 506 let link_children ppf children = 507 let input_child ppf child = 508 Raw.input ppf child.Odoc_document.Renderer.filename 509 in 510 Fmt.list input_child ppf children 511 512 let make ~config url content children = 513 let filename = Link.filename url in 514 let label = Label (Link.page url) in 515 let content = 516 match content with 517 | [] -> [ label ] 518 | (Section _ as s) :: q -> s :: label :: q 519 | q -> label :: q 520 in 521 let children_input ppf = 522 if config.with_children then link_children ppf children else () 523 in 524 let content ppf = Fmt.pf ppf "@[<v>%a@,%t@]@." pp content children_input in 525 { Odoc_document.Renderer.filename; content; children; path = url; assets = [] } 526end 527 528module Page = struct 529 let on_sub = function `Page _ -> Some 1 | `Include _ -> None 530 531 let rec subpage ~config (p : Subpage.t) = 532 if Expansion.remove_subpage config p.status p.content.url then [] 533 else [ page ~config p.content ] 534 535 and subpages ~config subpages = 536 List.flatten @@ List.map (subpage ~config) subpages 537 538 and page ~config p = 539 let { Page.items = i; url; _ } = 540 Doctree.Labels.disambiguate_page ~enter_subpages:true p 541 and subpages = subpages ~config @@ Doctree.Subpages.compute p in 542 let i = Doctree.Shift.compute ~on_sub i in 543 let header, preamble = Doctree.PageTitle.render_title p in 544 let header = items ~config url (header @ preamble) in 545 let content = items ~config url i in 546 let page = Doc.make ~config url (header @ content) subpages in 547 page 548end 549 550let render ~config = function 551 | Document.Page page -> [ Page.page ~config page ] 552 | Source_page _ -> [] 553 554let filepath url = Link.filename ~add_ext:false url