···66module Url = Odoc_document.Url
77module Link = HLink
8899+(* TODO: Remove Md module *)
910module Md = struct
1010- include Cmarkit
1111- let meta = Cmarkit.Meta.none
1111+ include Renderer
1212+ let meta = Renderer.Meta.none
1213end
13141415let source fn (t : Types.Source.t) =
+2-2
src/markdown2/generator.mli
···99 config:Config.t ->
1010 resolve:Link.resolve ->
1111 Odoc_document.Types.Item.t list ->
1212- Cmarkit.Block.t list
1212+ Renderer.Block.t list
13131414val inline :
1515 config:Config.t ->
1616 xref_base_uri:string ->
1717 Odoc_document.Types.Inline.t ->
1818- Cmarkit.Inline.t list
1818+ Renderer.Inline.t list
+6-6
src/markdown2/markdown_page.ml
···1919let make ~config ~url doc children =
2020 let filename = Link.Path.as_filename ~config url in
2121 let content ppf =
2222- let renderer = Cmarkit_commonmark.renderer () in
2323- Format.fprintf ppf "%s" (Cmarkit_renderer.doc_to_string renderer doc)
2222+ let renderer = Renderer.renderer () in
2323+ Format.fprintf ppf "%s" (Renderer.doc_to_string renderer doc)
2424 in
2525 { Odoc_document.Renderer.filename; content; children; path = url }
26262727let make_src ~config ~url _title block_list =
2828 let filename = Link.Path.as_filename ~config url in
2929 let content (ppf : Format.formatter) =
3030- let renderer = Cmarkit_commonmark.renderer () in
3131- let root_block = Cmarkit.Block.Blocks (block_list, Cmarkit.Meta.none) in
3232- let doc = Cmarkit.Doc.make root_block in
3333- Format.fprintf ppf "%s" (Cmarkit_renderer.doc_to_string renderer doc)
3030+ let renderer = Renderer.renderer () in
3131+ let root_block = Renderer.Block.Blocks (block_list, Renderer.Meta.none) in
3232+ let doc = Renderer.Doc.make root_block in
3333+ Format.fprintf ppf "%s" (Renderer.doc_to_string renderer doc)
3434 in
3535 { Odoc_document.Renderer.filename; content; children = []; path = url }
+2-2
src/markdown2/markdown_page.mli
···2121val make :
2222 config:Config.t ->
2323 url:Odoc_document.Url.Path.t ->
2424- Cmarkit.Doc.t ->
2424+ Renderer.Doc.t ->
2525 Odoc_document.Renderer.page list ->
2626 Odoc_document.Renderer.page
2727···2929 config:Config.t ->
3030 url:Odoc_document.Url.Path.t ->
3131 string ->
3232- Cmarkit.Block.t list ->
3232+ Renderer.Block.t list ->
3333 Odoc_document.Renderer.page
+1797
src/markdown2/renderer.ml
···11+module Cmarkit_data = struct
22+ module Uset = struct
33+ include Set.Make (Uchar)
44+ let of_array =
55+ let add acc u = add (Uchar.unsafe_of_int u) acc in
66+ Array.fold_left add empty
77+ end
88+99+ module Umap = struct
1010+ include Map.Make (Uchar)
1111+ let of_array =
1212+ let add acc (u, f) = add (Uchar.unsafe_of_int u) f acc in
1313+ Array.fold_left add empty
1414+ end
1515+1616+ let whitespace_uset = Uset.of_array Data_uchar.whitespace
1717+ let punctuation_uset = Uset.of_array Data_uchar.punctuation
1818+ let case_fold_umap = Umap.of_array Data_uchar.case_fold
1919+2020+ let unicode_version = Data_uchar.unicode_version
2121+ let is_unicode_whitespace u = Uset.mem u whitespace_uset
2222+ let is_unicode_punctuation u = Uset.mem u punctuation_uset
2323+ let unicode_case_fold u = Umap.find_opt u case_fold_umap
2424+2525+ (* HTML entity data. *)
2626+2727+ module String_map = Map.Make (String)
2828+end
2929+3030+(* TODO: Remove Meta module *)
3131+module Meta = struct
3232+ type t = unit
3333+ let none = ()
3434+end
3535+3636+(* TODO: Remove Meta.t from node *)
3737+type 'a node = 'a * Meta.t
3838+3939+module Ascii = struct
4040+ let is_control = function '\x00' .. '\x1F' | '\x7F' -> true | _ -> false
4141+ let is_letter = function 'a' .. 'z' | 'A' .. 'Z' -> true | _ -> false
4242+ let is_upper = function 'A' .. 'Z' -> true | _ -> false
4343+ let is_lower = function 'a' .. 'z' -> true | _ -> false
4444+ let is_digit = function '0' .. '9' -> true | _ -> false
4545+ let is_hex_digit = function
4646+ | '0' .. '9' | 'A' .. 'F' | 'a' .. 'f' -> true
4747+ | _ -> false
4848+4949+ let hex_digit_to_int = function
5050+ | '0' .. '9' as c -> Char.code c - 0x30
5151+ | 'A' .. 'F' as c -> Char.code c - 0x37
5252+ | 'a' .. 'f' as c -> Char.code c - 0x57
5353+ | _ -> assert false
5454+5555+ let is_alphanum = function
5656+ | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> true
5757+ | _ -> false
5858+5959+ let is_white = function
6060+ | '\x20' | '\x09' | '\x0A' | '\x0B' | '\x0C' | '\x0D' -> true
6161+ | _ -> false
6262+6363+ let is_punct = function
6464+ (* https://spec.commonmark.org/current/#ascii-punctuation-character *)
6565+ | '!' | '\"' | '#' | '$' | '%' | '&' | '\'' | '(' | ')' | '*' | '+' | ','
6666+ | '-' | '.' | '/' | ':' | ';' | '<' | '=' | '>' | '?' | '@' | '[' | '\\'
6767+ | ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~' ->
6868+ true
6969+ | _ -> false
7070+7171+ let is_blank = function ' ' | '\t' -> true | _ -> false
7272+7373+ let caseless_starts_with ~prefix s =
7474+ let get = String.get in
7575+ let len_a = String.length prefix in
7676+ let len_s = String.length s in
7777+ if len_a > len_s then false
7878+ else
7979+ let max_idx_a = len_a - 1 in
8080+ let rec loop s i max =
8181+ if i > max then true
8282+ else
8383+ let c =
8484+ match get s i with
8585+ | 'A' .. 'Z' as c -> Char.(unsafe_chr (code c + 32))
8686+ | c -> c
8787+ in
8888+ if get prefix i <> c then false else loop s (i + 1) max
8989+ in
9090+ loop s 0 max_idx_a
9191+9292+ let match' ~sub s ~start =
9393+ (* assert (start + String.length sub - 1 < String.length s) *)
9494+ try
9595+ for i = 0 to String.length sub - 1 do
9696+ if s.[start + i] <> sub.[i] then raise_notrace Exit
9797+ done;
9898+ true
9999+ with Exit -> false
100100+101101+ let caseless_match ~sub s ~start =
102102+ (* assert (start + String.length sub - 1 < String.length s) *)
103103+ try
104104+ for i = 0 to String.length sub - 1 do
105105+ let c =
106106+ match s.[start + i] with
107107+ | 'A' .. 'Z' as c -> Char.(unsafe_chr (code c + 32))
108108+ | c -> c
109109+ in
110110+ if c <> sub.[i] then raise_notrace Exit
111111+ done;
112112+ true
113113+ with Exit -> false
114114+115115+ let lowercase_sub s first len =
116116+ let b = Bytes.create len in
117117+ for i = 0 to len - 1 do
118118+ let c =
119119+ match s.[first + i] with
120120+ | 'A' .. 'Z' as c -> Char.(unsafe_chr (code c + 32))
121121+ | c -> c
122122+ in
123123+ Bytes.set b i c
124124+ done;
125125+ Bytes.unsafe_to_string b
126126+end
127127+128128+module Match = struct
129129+ let rec first_non_blank s ~last ~start =
130130+ if start > last then last + 1
131131+ else
132132+ match s.[start] with
133133+ | ' ' | '\t' -> first_non_blank s ~last ~start:(start + 1)
134134+ | _ -> start
135135+136136+ let autolink_email s ~last ~start =
137137+ (* https://spec.commonmark.org/current/#email-address
138138+ Via the ABNF "<" email ">" with email defined by:
139139+ https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address *)
140140+ let is_atext_plus_dot = function
141141+ | 'a' .. 'z'
142142+ | 'A' .. 'Z'
143143+ | '0' .. '9'
144144+ | '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?'
145145+ | '^' | '_' | '`' | '{' | '|' | '}' | '~' | '.' ->
146146+ true
147147+ | _ -> false
148148+ in
149149+ let is_let_dig = Ascii.is_alphanum in
150150+ let is_let_dig_hyp c = Ascii.is_alphanum c || c = '-' in
151151+ let rec label_seq s last k =
152152+ let rec loop s last c k =
153153+ if k > last then None
154154+ else if is_let_dig_hyp s.[k] && c <= 63 then loop s last (c + 1) (k + 1)
155155+ else if c > 63 || not (is_let_dig s.[k - 1]) then None
156156+ else
157157+ match s.[k] with
158158+ | '>' -> Some k
159159+ | '.' -> label_seq s last (k + 1)
160160+ | _ -> None
161161+ in
162162+ if k > last || not (is_let_dig s.[k]) then None else loop s last 1 (k + 1)
163163+ in
164164+ let rec atext_seq s last k =
165165+ if k > last then None
166166+ else if is_atext_plus_dot s.[k] then atext_seq s last (k + 1)
167167+ else if s.[k] = '@' && is_atext_plus_dot s.[k - 1] then
168168+ label_seq s last (k + 1)
169169+ else None
170170+ in
171171+ if start > last || s.[start] <> '<' then None
172172+ else atext_seq s last (start + 1)
173173+end
174174+175175+module Layout = struct
176176+ type blanks = string
177177+ type nonrec string = string
178178+ type nonrec char = char
179179+ type count = int
180180+ type indent = int
181181+ let string ?(meta = Meta.none) s = (s, meta)
182182+ let empty = string ""
183183+end
184184+185185+module Block_line = struct
186186+ let _list_of_string flush s =
187187+ (* cuts [s] on newlines *)
188188+ let rec loop s acc max start k =
189189+ if k > max then List.rev (flush s start max acc)
190190+ else if not (s.[k] = '\n' || s.[k] = '\r') then
191191+ loop s acc max start (k + 1)
192192+ else
193193+ let acc = flush s start (k - 1) acc in
194194+ let next = k + 1 in
195195+ let start =
196196+ if s.[k] = '\r' && next <= max && s.[next] = '\n' then next + 1
197197+ else next
198198+ in
199199+ loop s acc max start start
200200+ in
201201+ loop s [] (String.length s - 1) 0 0
202202+203203+ let flush ?(meta = Meta.none) s start last acc =
204204+ let sub = String.sub s start (last - start + 1) in
205205+ (sub, meta) :: acc
206206+207207+ let flush_tight ?(meta = Meta.none) s start last acc =
208208+ (* If [s] has newlines, blanks after newlines are layout *)
209209+ if start > last then ("", ("", meta)) :: acc
210210+ else
211211+ match acc with
212212+ | [] (* On the first line the blanks are legit *) ->
213213+ ("", (String.sub s start (last - start + 1), meta)) :: acc
214214+ | acc ->
215215+ let nb = Match.first_non_blank s ~last ~start in
216216+ ( String.sub s start (nb - 1 - start + 1),
217217+ (String.sub s nb (last - nb + 1), meta) )
218218+ :: acc
219219+220220+ (* Block lines *)
221221+222222+ type t = string node
223223+224224+ let to_string = fst
225225+ let list_of_string ?meta s = _list_of_string (flush ?meta) s
226226+227227+ (* Tight lines *)
228228+229229+ type tight = Layout.blanks * t
230230+231231+ let tight_to_string l = fst (snd l)
232232+ let tight_list_of_string ?meta s = _list_of_string (flush_tight ?meta) s
233233+234234+ (* Blank lines *)
235235+236236+ type blank = Layout.blanks node
237237+end
238238+239239+module Label = struct
240240+ type key = string
241241+ type t = { meta : Meta.t; key : key; text : Block_line.tight list }
242242+ let make ?(meta = Meta.none) ~key text = { key; text; meta }
243243+ let with_meta meta l = { l with meta }
244244+ let meta t = t.meta
245245+ let key t = t.key
246246+ let text t = t.text
247247+ let text_to_string t =
248248+ String.concat " " (List.map Block_line.tight_to_string t.text)
249249+250250+ let compare l0 l1 = String.compare l0.key l1.key
251251+252252+ (* Definitions *)
253253+254254+ module Map = Map.Make (String)
255255+ type def = ..
256256+ type defs = def Map.t
257257+258258+ (* Resolvers *)
259259+260260+ type context =
261261+ [ `Def of t option * t | `Ref of [ `Link | `Image ] * t * t option ]
262262+263263+ type resolver = context -> t option
264264+ let default_resolver = function
265265+ | `Def (None, k) -> Some k
266266+ | `Def (Some _, _k) -> None
267267+ | `Ref (_, _, k) -> k
268268+end
269269+270270+module Link_definition = struct
271271+ type layout = {
272272+ indent : Layout.indent;
273273+ angled_dest : bool;
274274+ before_dest : Block_line.blank list;
275275+ after_dest : Block_line.blank list;
276276+ title_open_delim : Layout.char;
277277+ after_title : Block_line.blank list;
278278+ }
279279+280280+ let layout_for_dest dest =
281281+ let needs_angles c = Ascii.is_control c || c = ' ' in
282282+ let angled_dest = String.exists needs_angles dest in
283283+ {
284284+ indent = 0;
285285+ angled_dest;
286286+ before_dest = [];
287287+ after_dest = [];
288288+ title_open_delim = '\"';
289289+ after_title = [];
290290+ }
291291+292292+ let default_layout =
293293+ {
294294+ indent = 0;
295295+ angled_dest = false;
296296+ before_dest = [];
297297+ after_dest = [];
298298+ title_open_delim = '\"';
299299+ after_title = [];
300300+ }
301301+302302+ type t = {
303303+ layout : layout;
304304+ label : Label.t option;
305305+ defined_label : Label.t option;
306306+ dest : string node option;
307307+ title : Block_line.tight list option;
308308+ }
309309+310310+ let make ?defined_label ?label ?dest ?title () =
311311+ let layout =
312312+ match dest with
313313+ | None -> default_layout
314314+ | Some (d, _) -> layout_for_dest d
315315+ in
316316+ let defined_label =
317317+ match defined_label with None -> label | Some d -> d
318318+ in
319319+ { layout; label; defined_label; dest; title }
320320+321321+ let layout ld = ld.layout
322322+ let label ld = ld.label
323323+ let defined_label ld = ld.defined_label
324324+ let dest ld = ld.dest
325325+ let title ld = ld.title
326326+327327+ type Label.def += Def of t node
328328+end
329329+330330+module Inline = struct
331331+ type t = ..
332332+333333+ module Autolink = struct
334334+ type t = { is_email : bool; link : string node }
335335+ let is_email a = a.is_email
336336+ let link a = a.link
337337+ let make link =
338338+ let is_email =
339339+ let l = String.concat "" [ "<"; fst link; ">" ] in
340340+ match Match.autolink_email l ~last:(String.length l - 1) ~start:0 with
341341+ | None -> false
342342+ | Some _ -> true
343343+ in
344344+ { is_email; link }
345345+ end
346346+347347+ module Break = struct
348348+ type type' = [ `Hard | `Soft ]
349349+ type t = {
350350+ layout_before : Layout.blanks node;
351351+ type' : type';
352352+ layout_after : Layout.blanks node;
353353+ }
354354+355355+ let make ?(layout_before = Layout.empty) ?(layout_after = Layout.empty)
356356+ type' =
357357+ { layout_before; type'; layout_after }
358358+359359+ let type' b = b.type'
360360+ let layout_before b = b.layout_before
361361+ let layout_after b = b.layout_after
362362+ end
363363+364364+ module Code_span = struct
365365+ type t = {
366366+ backtick_count : Layout.count;
367367+ code_layout : Block_line.tight list;
368368+ }
369369+370370+ let make ~backtick_count code_layout = { backtick_count; code_layout }
371371+372372+ let min_backtick_count ~min counts =
373373+ let rec loop min = function
374374+ | c :: cs -> if min <> c then min else loop (c + 1) cs
375375+ | [] -> min
376376+ in
377377+ loop min (List.sort Int.compare counts)
378378+379379+ let of_string ?(meta = Meta.none) = function
380380+ | "" -> { backtick_count = 1; code_layout = [ ("", ("", meta)) ] }
381381+ | s ->
382382+ (* This finds out the needed backtick count, whether spaces are needed,
383383+ and treats blanks after newline as layout *)
384384+ let max = String.length s - 1 in
385385+ let need_sp = s.[0] = '`' || s.[max] = '`' in
386386+ let s = if need_sp then String.concat "" [ " "; s; " " ] else s in
387387+ let backtick_counts, code_layout =
388388+ let rec loop bt_counts acc max btc start k =
389389+ match k > max with
390390+ | true ->
391391+ (* assert (btc = 0) because of [need_sp] *)
392392+ ( bt_counts,
393393+ if acc = [] then [ ("", (s, meta)) ]
394394+ else List.rev (Block_line.flush_tight ~meta s start max acc)
395395+ )
396396+ | false ->
397397+ if s.[k] = '`' then
398398+ loop bt_counts acc max (btc + 1) start (k + 1)
399399+ else
400400+ let bt_counts =
401401+ if btc > 0 then btc :: bt_counts else bt_counts
402402+ in
403403+ if not (s.[k] = '\n' || s.[k] = '\r') then
404404+ loop bt_counts acc max 0 start (k + 1)
405405+ else
406406+ let acc =
407407+ Block_line.flush_tight ~meta s start (k - 1) acc
408408+ in
409409+ let start =
410410+ if k + 1 <= max && s.[k] = '\r' && s.[k + 1] = '\n' then
411411+ k + 2
412412+ else k + 1
413413+ in
414414+ loop bt_counts acc max 0 start start
415415+ in
416416+ loop [] [] max 0 0 0
417417+ in
418418+ let backtick_count = min_backtick_count ~min:1 backtick_counts in
419419+ { backtick_count; code_layout }
420420+421421+ let backtick_count cs = cs.backtick_count
422422+ let code_layout cs = cs.code_layout
423423+ let code cs =
424424+ (* Extract code, see https://spec.commonmark.org/0.30/#code-spans *)
425425+ let sp c = Char.equal c ' ' in
426426+ let s = List.map Block_line.tight_to_string cs.code_layout in
427427+ let s = String.concat " " s in
428428+ if s = "" then ""
429429+ else if
430430+ s.[0] = ' '
431431+ && s.[String.length s - 1] = ' '
432432+ && not (String.for_all sp s)
433433+ then String.sub s 1 (String.length s - 2)
434434+ else s
435435+ end
436436+437437+ module Emphasis = struct
438438+ type inline = t
439439+ type t = { delim : Layout.char; inline : inline }
440440+ let make ?(delim = '*') inline = { delim; inline }
441441+ let inline e = e.inline
442442+ let delim e = e.delim
443443+ end
444444+445445+ module Link = struct
446446+ type inline = t
447447+448448+ type reference_layout = [ `Collapsed | `Full | `Shortcut ]
449449+ type reference =
450450+ [ `Inline of Link_definition.t node
451451+ | `Ref of reference_layout * Label.t * Label.t ]
452452+453453+ type t = { text : inline; reference : reference }
454454+455455+ let make text reference = { text; reference }
456456+ let text l = l.text
457457+ let reference l = l.reference
458458+ let referenced_label l =
459459+ match l.reference with `Inline _ -> None | `Ref (_, _, k) -> Some k
460460+461461+ let reference_definition defs l =
462462+ match l.reference with
463463+ | `Inline ld -> Some (Link_definition.Def ld)
464464+ | `Ref (_, _, def) -> Label.Map.find_opt (Label.key def) defs
465465+466466+ let is_unsafe l =
467467+ let allowed_data_url l =
468468+ let allowed =
469469+ [ "image/gif"; "image/png"; "image/jpeg"; "image/webp" ]
470470+ in
471471+ (* Extract mediatype from data:[<mediatype>][;base64],<data> *)
472472+ match String.index_from_opt l 4 ',' with
473473+ | None -> false
474474+ | Some j ->
475475+ let k =
476476+ match String.index_from_opt l 4 ';' with None -> j | Some k -> k
477477+ in
478478+ let t = String.sub l 5 (min j k - 5) in
479479+ List.mem t allowed
480480+ in
481481+ Ascii.caseless_starts_with ~prefix:"javascript:" l
482482+ || Ascii.caseless_starts_with ~prefix:"vbscript:" l
483483+ || Ascii.caseless_starts_with ~prefix:"file:" l
484484+ || Ascii.caseless_starts_with ~prefix:"data:" l
485485+ && not (allowed_data_url l)
486486+ end
487487+488488+ module Raw_html = struct
489489+ type t = Block_line.tight list
490490+ end
491491+492492+ module Text = struct
493493+ type t = string
494494+ end
495495+496496+ type t +=
497497+ | Autolink of Autolink.t node
498498+ | Break of Break.t node
499499+ | Code_span of Code_span.t node
500500+ | Emphasis of Emphasis.t node
501501+ | Image of Link.t node
502502+ | Inlines of t list node
503503+ | Link of Link.t node
504504+ | Raw_html of Raw_html.t node
505505+ | Strong_emphasis of Emphasis.t node
506506+ | Text of Text.t node
507507+508508+ let empty = Inlines ([], Meta.none)
509509+510510+ let err_unknown = "Unknown Cmarkit.Inline.t type extension"
511511+512512+ (* Extensions *)
513513+514514+ module Strikethrough = struct
515515+ type nonrec t = t
516516+ let make = Fun.id
517517+ let inline = Fun.id
518518+ end
519519+520520+ module Math_span = struct
521521+ type t = { display : bool; tex_layout : Block_line.tight list }
522522+ let make ~display tex_layout = { display; tex_layout }
523523+ let display ms = ms.display
524524+ let tex_layout ms = ms.tex_layout
525525+ let tex ms =
526526+ let s = List.map Block_line.tight_to_string ms.tex_layout in
527527+ String.concat " " s
528528+ end
529529+530530+ type t +=
531531+ | Ext_strikethrough of Strikethrough.t node
532532+ | Ext_math_span of Math_span.t node
533533+534534+ (* Functions on inlines *)
535535+536536+ let is_empty = function Text ("", _) | Inlines ([], _) -> true | _ -> false
537537+538538+ let ext_none _ = invalid_arg err_unknown
539539+ let meta ?(ext = ext_none) = function
540540+ | Autolink (_, m)
541541+ | Break (_, m)
542542+ | Code_span (_, m)
543543+ | Emphasis (_, m)
544544+ | Image (_, m)
545545+ | Inlines (_, m)
546546+ | Link (_, m)
547547+ | Raw_html (_, m)
548548+ | Strong_emphasis (_, m)
549549+ | Text (_, m) ->
550550+ m
551551+ | Ext_strikethrough (_, m) -> m
552552+ | Ext_math_span (_, m) -> m
553553+ | i -> ext i
554554+555555+ let rec normalize ?(ext = ext_none) = function
556556+ | ( Autolink _ | Break _ | Code_span _ | Raw_html _ | Text _
557557+ | Inlines ([], _)
558558+ | Ext_math_span _ ) as i ->
559559+ i
560560+ | Image (l, m) -> Image ({ l with text = normalize ~ext l.text }, m)
561561+ | Link (l, m) -> Link ({ l with text = normalize ~ext l.text }, m)
562562+ | Inlines ([ i ], _) -> i
563563+ | Emphasis (e, m) ->
564564+ Emphasis ({ e with inline = normalize ~ext e.inline }, m)
565565+ | Strong_emphasis (e, m) ->
566566+ Strong_emphasis ({ e with inline = normalize ~ext e.inline }, m)
567567+ | Inlines (i :: is, m) -> (
568568+ let rec loop acc = function
569569+ | Inlines (is', _) :: is ->
570570+ loop acc (List.rev_append (List.rev is') is)
571571+ | (Text (t', _) as i') :: is -> (
572572+ match acc with
573573+ | Text (t, _) :: acc ->
574574+ let i = Text (t ^ t', ()) in
575575+ loop (i :: acc) is
576576+ | _ -> loop (normalize ~ext i' :: acc) is)
577577+ | i :: is -> loop (normalize ~ext i :: acc) is
578578+ | [] -> List.rev acc
579579+ in
580580+ let is = loop [ normalize ~ext i ] is in
581581+ match is with [ i ] -> i | _ -> Inlines (is, m))
582582+ | Ext_strikethrough (i, m) -> Ext_strikethrough (normalize ~ext i, m)
583583+ | i -> ext i
584584+585585+ let ext_none = ext_none
586586+ let to_plain_text ?(ext = ext_none) ~break_on_soft i =
587587+ let push s acc = (s :: List.hd acc) :: List.tl acc in
588588+ let newline acc = [] :: List.rev (List.hd acc) :: List.tl acc in
589589+ let rec loop ~break_on_soft acc = function
590590+ | Autolink (a, _) :: is ->
591591+ let acc = push (String.concat "" [ "<"; fst a.link; ">" ]) acc in
592592+ loop ~break_on_soft acc is
593593+ | Break ({ type' = `Hard; _ }, _) :: is ->
594594+ loop ~break_on_soft (newline acc) is
595595+ | Break ({ type' = `Soft; _ }, _) :: is ->
596596+ let acc = if break_on_soft then newline acc else push " " acc in
597597+ loop ~break_on_soft acc is
598598+ | Code_span (cs, _) :: is ->
599599+ loop ~break_on_soft (push (Code_span.code cs) acc) is
600600+ | Emphasis ({ inline; _ }, _) :: is
601601+ | Strong_emphasis ({ inline; _ }, _) :: is ->
602602+ loop ~break_on_soft acc (inline :: is)
603603+ | Inlines (is', _) :: is ->
604604+ loop ~break_on_soft acc (List.rev_append (List.rev is') is)
605605+ | Link (l, _) :: is | Image (l, _) :: is ->
606606+ loop ~break_on_soft acc (l.text :: is)
607607+ | Raw_html _ :: is -> loop ~break_on_soft acc is
608608+ | Text (t, _) :: is -> loop ~break_on_soft (push t acc) is
609609+ | Ext_strikethrough (i, _) :: is -> loop ~break_on_soft acc (i :: is)
610610+ | Ext_math_span (m, _) :: is ->
611611+ loop ~break_on_soft (push (Math_span.tex m) acc) is
612612+ | i :: is -> loop ~break_on_soft acc (ext ~break_on_soft i :: is)
613613+ | [] -> List.rev (List.rev (List.hd acc) :: List.tl acc)
614614+ in
615615+ loop ~break_on_soft ([] :: []) [ i ]
616616+617617+ let id ?buf ?ext i =
618618+ let text = to_plain_text ?ext ~break_on_soft:false i in
619619+ let s = String.concat "\n" (List.map (String.concat "") text) in
620620+ let b =
621621+ match buf with
622622+ | Some b ->
623623+ Buffer.reset b;
624624+ b
625625+ | None -> Buffer.create 256
626626+ in
627627+ let[@inline] collapse_blanks b ~prev_byte =
628628+ (* Collapses non initial white *)
629629+ if Ascii.is_blank prev_byte && Buffer.length b <> 0 then
630630+ Buffer.add_char b '-'
631631+ in
632632+ let rec loop b s max ~prev_byte k =
633633+ if k > max then Buffer.contents b
634634+ else
635635+ match s.[k] with
636636+ | (' ' | '\t') as prev_byte -> loop b s max ~prev_byte (k + 1)
637637+ | ('_' | '-') as c ->
638638+ collapse_blanks b ~prev_byte;
639639+ Buffer.add_char b c;
640640+ loop b s max ~prev_byte:c (k + 1)
641641+ | _ ->
642642+ let () = collapse_blanks b ~prev_byte in
643643+ let d = String.get_utf_8_uchar s k in
644644+ let u = Uchar.utf_decode_uchar d in
645645+ let u = match Uchar.to_int u with 0x0000 -> Uchar.rep | _ -> u in
646646+ let k' = k + Uchar.utf_decode_length d in
647647+ if Cmarkit_data.is_unicode_punctuation u then
648648+ loop b s max ~prev_byte:'\x00' k'
649649+ else
650650+ let () =
651651+ match Cmarkit_data.unicode_case_fold u with
652652+ | None -> Buffer.add_utf_8_uchar b u
653653+ | Some fold -> Buffer.add_string b fold
654654+ in
655655+ let prev_byte = s.[k] in
656656+ loop b s max ~prev_byte k'
657657+ in
658658+ loop b s (String.length s - 1) ~prev_byte:'\x00' 0
659659+end
660660+661661+module Block = struct
662662+ type t = ..
663663+664664+ module Blank_line = struct
665665+ type t = Layout.blanks
666666+ end
667667+668668+ module Block_quote = struct
669669+ type nonrec t = { indent : Layout.indent; block : t }
670670+ let make ?(indent = 0) block = { indent; block }
671671+ let indent bq = bq.indent
672672+ let block bq = bq.block
673673+ end
674674+675675+ module Code_block = struct
676676+ type fenced_layout = {
677677+ indent : Layout.indent;
678678+ opening_fence : Layout.string node;
679679+ closing_fence : Layout.string node option;
680680+ }
681681+682682+ let default_fenced_layout =
683683+ {
684684+ indent = 0;
685685+ opening_fence = Layout.empty;
686686+ closing_fence = Some Layout.empty;
687687+ }
688688+689689+ type layout = [ `Indented | `Fenced of fenced_layout ]
690690+ type t = {
691691+ layout : layout;
692692+ info_string : string node option;
693693+ code : string node list;
694694+ }
695695+696696+ let make ?(layout = `Fenced default_fenced_layout) ?info_string code =
697697+ let layout =
698698+ match (info_string, layout) with
699699+ | Some _, `Indented -> `Fenced default_fenced_layout
700700+ | _, layout -> layout
701701+ in
702702+ { layout; info_string; code }
703703+704704+ let layout cb = cb.layout
705705+ let info_string cb = cb.info_string
706706+ let code cb = cb.code
707707+708708+ let make_fence cb =
709709+ let rec loop char counts = function
710710+ | [] -> counts
711711+ | (c, _) :: cs ->
712712+ let max = String.length c - 1 in
713713+ let k = ref 0 in
714714+ while !k <= max && c.[!k] = char do
715715+ incr k
716716+ done;
717717+ loop char (if !k <> 0 then !k :: counts else counts) cs
718718+ in
719719+ let char =
720720+ match cb.info_string with
721721+ | Some (i, _) when String.exists (Char.equal '`') i -> '~'
722722+ | None | Some _ -> '`'
723723+ in
724724+ let counts = loop char [] cb.code in
725725+ ( char,
726726+ Inline.Code_span.min_backtick_count (* not char specific *)
727727+ ~min:3 counts )
728728+729729+ let language_of_info_string s =
730730+ let rec next_white s max i =
731731+ if i > max || Ascii.is_white s.[i] then i else next_white s max (i + 1)
732732+ in
733733+ if s = "" then None
734734+ else
735735+ let max = String.length s - 1 in
736736+ let white = next_white s max 0 in
737737+ let rem_first = Match.first_non_blank s ~last:max ~start:white in
738738+ let lang = String.sub s 0 white in
739739+ if lang = "" then None
740740+ else Some (lang, String.sub s rem_first (max - rem_first + 1))
741741+742742+ let is_math_block = function
743743+ | None -> false
744744+ | Some (i, _) -> (
745745+ match language_of_info_string i with
746746+ | Some ("math", _) -> true
747747+ | Some _ | None -> false)
748748+ end
749749+750750+ module Heading = struct
751751+ type atx_layout = {
752752+ indent : Layout.indent;
753753+ after_opening : Layout.blanks;
754754+ closing : Layout.string;
755755+ }
756756+757757+ let default_atx_layout = { indent = 0; after_opening = ""; closing = "" }
758758+759759+ type setext_layout = {
760760+ leading_indent : Layout.indent;
761761+ trailing_blanks : Layout.blanks;
762762+ underline_indent : Layout.indent;
763763+ underline_count : Layout.count node;
764764+ underline_blanks : Layout.blanks;
765765+ }
766766+767767+ type layout = [ `Atx of atx_layout | `Setext of setext_layout ]
768768+ type id = [ `Auto of string | `Id of string ]
769769+ type t = { layout : layout; level : int; inline : Inline.t; id : id option }
770770+771771+ let make ?id ?(layout = `Atx default_atx_layout) ~level inline =
772772+ let max = match layout with `Atx _ -> 6 | `Setext _ -> 2 in
773773+ let level = Int.max 1 (Int.min level max) in
774774+ { layout; level; inline; id }
775775+776776+ let layout h = h.layout
777777+ let level h = h.level
778778+ let inline h = h.inline
779779+ let id h = h.id
780780+ end
781781+782782+ module Html_block = struct
783783+ type t = string node list
784784+ end
785785+786786+ module List_item = struct
787787+ type block = t
788788+ type t = {
789789+ before_marker : Layout.indent;
790790+ marker : Layout.string node;
791791+ after_marker : Layout.indent;
792792+ block : block;
793793+ ext_task_marker : Uchar.t node option;
794794+ }
795795+796796+ let make ?(before_marker = 0) ?(marker = Layout.empty) ?(after_marker = 1)
797797+ ?ext_task_marker block =
798798+ { before_marker; marker; after_marker; block; ext_task_marker }
799799+800800+ let block i = i.block
801801+ let before_marker i = i.before_marker
802802+ let marker i = i.marker
803803+ let after_marker i = i.after_marker
804804+ let ext_task_marker i = i.ext_task_marker
805805+ let task_status_of_task_marker u =
806806+ match Uchar.to_int u with
807807+ | 0x0020 -> `Unchecked
808808+ | 0x0078 (* x *)
809809+ | 0x0058 (* X *)
810810+ | 0x2713 (* ✓ *)
811811+ | 0x2714 (* ✔ *)
812812+ | 0x10102 (* 𐄂 *)
813813+ | 0x1F5F8 (* 🗸*) ->
814814+ `Checked
815815+ | 0x007E (* ~ *) -> `Cancelled
816816+ | _ -> `Other u
817817+ end
818818+819819+ module List' = struct
820820+ type type' = [ `Unordered of Layout.char | `Ordered of int * Layout.char ]
821821+ type t = { type' : type'; tight : bool; items : List_item.t node list }
822822+823823+ let make ?(tight = true) type' items = { type'; tight; items }
824824+825825+ let type' l = l.type'
826826+ let tight l = l.tight
827827+ let items l = l.items
828828+ end
829829+830830+ module Paragraph = struct
831831+ type t = {
832832+ leading_indent : Layout.indent;
833833+ inline : Inline.t;
834834+ trailing_blanks : Layout.blanks;
835835+ }
836836+837837+ let make ?(leading_indent = 0) ?(trailing_blanks = "") inline =
838838+ { leading_indent; inline; trailing_blanks }
839839+840840+ let inline p = p.inline
841841+ let leading_indent p = p.leading_indent
842842+ let trailing_blanks p = p.trailing_blanks
843843+ end
844844+845845+ module Thematic_break = struct
846846+ type t = { indent : Layout.indent; layout : Layout.string }
847847+ let make ?(indent = 0) ?(layout = "---") () = { indent; layout }
848848+ let indent t = t.indent
849849+ let layout t = t.layout
850850+ end
851851+852852+ type t +=
853853+ | Blank_line of Layout.blanks node
854854+ | Block_quote of Block_quote.t node
855855+ | Blocks of t list node
856856+ | Code_block of Code_block.t node
857857+ | Heading of Heading.t node
858858+ | Html_block of Html_block.t node
859859+ | Link_reference_definition of Link_definition.t node
860860+ | List of List'.t node
861861+ | Paragraph of Paragraph.t node
862862+ | Thematic_break of Thematic_break.t node
863863+864864+ let empty = Blocks ([], Meta.none)
865865+866866+ (* Extensions *)
867867+868868+ module Table = struct
869869+ type align = [ `Left | `Center | `Right ]
870870+ type sep = align option * Layout.count
871871+ type cell_layout = Layout.blanks * Layout.blanks
872872+ type row =
873873+ [ `Header of (Inline.t * cell_layout) list
874874+ | `Sep of sep node list
875875+ | `Data of (Inline.t * cell_layout) list ]
876876+877877+ type t = {
878878+ indent : Layout.indent;
879879+ col_count : int;
880880+ rows : (row node * Layout.blanks) list;
881881+ }
882882+883883+ let col_count rows =
884884+ let rec loop c = function
885885+ | (((`Header cols | `Data cols), _), _) :: rs ->
886886+ loop (Int.max (List.length cols) c) rs
887887+ | ((`Sep cols, _), _) :: rs -> loop (Int.max (List.length cols) c) rs
888888+ | [] -> c
889889+ in
890890+ loop 0 rows
891891+892892+ let make ?(indent = 0) rows = { indent; col_count = col_count rows; rows }
893893+ let indent t = t.indent
894894+ let col_count t = t.col_count
895895+ let rows t = t.rows
896896+897897+ let parse_sep_row cs =
898898+ let rec loop acc = function
899899+ | [] -> Some (List.rev acc)
900900+ | (Inline.Text (s, meta), ("", "")) :: cs -> (
901901+ if s = "" then None
902902+ else
903903+ let max = String.length s - 1 in
904904+ let first_colon = s.[0] = ':' and last_colon = s.[max] = ':' in
905905+ let first = if first_colon then 1 else 0 in
906906+ let last = if last_colon then max - 1 else max in
907907+ match
908908+ for i = first to last do
909909+ if s.[i] <> '-' then raise Exit
910910+ done
911911+ with
912912+ | exception Exit -> None
913913+ | () ->
914914+ let count = last - first + 1 in
915915+ let sep =
916916+ match (first_colon, last_colon) with
917917+ | false, false -> None
918918+ | true, true -> Some `Center
919919+ | true, false -> Some `Left
920920+ | false, true -> Some `Right
921921+ in
922922+ loop (((sep, count), meta) :: acc) cs)
923923+ | _ -> None
924924+ in
925925+ loop [] cs
926926+ end
927927+928928+ module Footnote = struct
929929+ type nonrec t = {
930930+ indent : Layout.indent;
931931+ label : Label.t;
932932+ defined_label : Label.t option;
933933+ block : t;
934934+ }
935935+936936+ let make ?(indent = 0) ?defined_label:d label block =
937937+ let defined_label = match d with None -> Some label | Some d -> d in
938938+ { indent; label; defined_label; block }
939939+940940+ let indent fn = fn.indent
941941+ let label fn = fn.label
942942+ let defined_label fn = fn.defined_label
943943+ let block fn = fn.block
944944+945945+ type Label.def += Def of t node
946946+ let stub label defined_label =
947947+ Def ({ indent = 0; label; defined_label; block = empty }, Meta.none)
948948+ end
949949+950950+ type t +=
951951+ | Ext_math_block of Code_block.t node
952952+ | Ext_table of Table.t node
953953+ | Ext_footnote_definition of Footnote.t node
954954+955955+ (* Functions on blocks *)
956956+957957+ let err_unknown = "Unknown Cmarkit.Block.t type extension"
958958+959959+ let ext_none _ = invalid_arg err_unknown
960960+ let meta ?(ext = ext_none) = function
961961+ | Blank_line (_, m)
962962+ | Block_quote (_, m)
963963+ | Blocks (_, m)
964964+ | Code_block (_, m)
965965+ | Heading (_, m)
966966+ | Html_block (_, m)
967967+ | Link_reference_definition (_, m)
968968+ | List (_, m)
969969+ | Paragraph (_, m)
970970+ | Thematic_break (_, m)
971971+ | Ext_math_block (_, m)
972972+ | Ext_table (_, m)
973973+ | Ext_footnote_definition (_, m) ->
974974+ m
975975+ | b -> ext b
976976+977977+ let rec normalize ?(ext = ext_none) = function
978978+ | ( Blank_line _ | Code_block _ | Heading _ | Html_block _
979979+ | Link_reference_definition _ | Paragraph _ | Thematic_break _
980980+ | Blocks ([], _)
981981+ | Ext_math_block _ | Ext_table _ ) as b ->
982982+ b
983983+ | Block_quote (b, m) ->
984984+ let b = { b with block = normalize ~ext b.block } in
985985+ Block_quote (b, m)
986986+ | List (l, m) ->
987987+ let item (i, meta) =
988988+ let block = List_item.block i in
989989+ ({ i with List_item.block = normalize ~ext block }, meta)
990990+ in
991991+ List ({ l with items = List.map item l.items }, m)
992992+ | Blocks (b :: bs, m) -> (
993993+ let rec loop acc = function
994994+ | Blocks (bs', _) :: bs ->
995995+ loop acc (List.rev_append (List.rev bs') bs)
996996+ | b :: bs -> loop (normalize ~ext b :: acc) bs
997997+ | [] -> List.rev acc
998998+ in
999999+ let bs = loop [ normalize ~ext b ] bs in
10001000+ match bs with [ b ] -> b | _ -> Blocks (bs, m))
10011001+ | Ext_footnote_definition (fn, m) ->
10021002+ let fn = { fn with block = normalize ~ext fn.block } in
10031003+ Ext_footnote_definition (fn, m)
10041004+ | b -> ext b
10051005+10061006+ let rec defs ?(ext = fun _b _defs -> invalid_arg err_unknown)
10071007+ ?(init = Label.Map.empty) = function
10081008+ | Blank_line _ | Code_block _ | Heading _ | Html_block _ | Paragraph _
10091009+ | Thematic_break _ | Ext_math_block _ | Ext_table _ ->
10101010+ init
10111011+ | Block_quote (b, _) -> defs ~ext ~init (Block_quote.block b)
10121012+ | Blocks (bs, _) -> List.fold_left (fun init b -> defs ~ext ~init b) init bs
10131013+ | List (l, _) ->
10141014+ let add init (i, _) = defs ~ext ~init (List_item.block i) in
10151015+ List.fold_left add init l.items
10161016+ | Link_reference_definition ld -> (
10171017+ match Link_definition.defined_label (fst ld) with
10181018+ | None -> init
10191019+ | Some def ->
10201020+ Label.Map.add (Label.key def) (Link_definition.Def ld) init)
10211021+ | Ext_footnote_definition fn ->
10221022+ let init =
10231023+ match Footnote.defined_label (fst fn) with
10241024+ | None -> init
10251025+ | Some def -> Label.Map.add (Label.key def) (Footnote.Def fn) init
10261026+ in
10271027+ defs ~ext ~init (Footnote.block (fst fn))
10281028+ | b -> ext init b
10291029+end
10301030+10311031+module Doc = struct
10321032+ type t = { nl : Layout.string; block : Block.t; defs : Label.defs }
10331033+ let make ?(nl = "\n") ?(defs = Label.Map.empty) block = { nl; block; defs }
10341034+ let empty = make (Block.Blocks ([], Meta.none))
10351035+ let nl d = d.nl
10361036+ let block d = d.block
10371037+ let defs d = d.defs
10381038+ let unicode_version = Data_uchar.unicode_version
10391039+ let commonmark_version = "0.30"
10401040+end
10411041+10421042+(* Heterogeneous dictionaries *)
10431043+10441044+module Dict = struct
10451045+ (* Type identifiers, can be deleted once we require 5.1 *)
10461046+ module Type = struct
10471047+ type (_, _) eq = Equal : ('a, 'a) eq
10481048+ module Id = struct
10491049+ type _ id = ..
10501050+ module type ID = sig
10511051+ type t
10521052+ type _ id += Id : t id
10531053+ end
10541054+ type 'a t = (module ID with type t = 'a)
10551055+10561056+ let make (type a) () : a t =
10571057+ (module struct
10581058+ type t = a
10591059+ type _ id += Id : t id
10601060+ end)
10611061+10621062+ let provably_equal (type a b) ((module A) : a t) ((module B) : b t) :
10631063+ (a, b) eq option =
10641064+ match A.Id with B.Id -> Some Equal | _ -> None
10651065+10661066+ let uid (type a) ((module A) : a t) =
10671067+ Obj.Extension_constructor.id (Obj.Extension_constructor.of_val A.Id)
10681068+ end
10691069+ end
10701070+10711071+ module M = Map.Make (Int)
10721072+ type 'a key = 'a Type.Id.t
10731073+ type binding = B : 'a key * 'a -> binding
10741074+ type t = binding M.t
10751075+10761076+ let key = Type.Id.make
10771077+ let empty = M.empty
10781078+ let mem k m = M.mem (Type.Id.uid k) m
10791079+ let add k v m = M.add (Type.Id.uid k) (B (k, v)) m
10801080+ let tag k m = add k () m
10811081+ let remove k m = M.remove (Type.Id.uid k) m
10821082+ let find : type a. a key -> t -> a option =
10831083+ fun k m ->
10841084+ match M.find_opt (Type.Id.uid k) m with
10851085+ | None -> None
10861086+ | Some (B (k', v)) -> (
10871087+ match Type.Id.provably_equal k k' with
10881088+ | None -> assert false
10891089+ | Some Type.Equal -> Some v)
10901090+end
10911091+10921092+type t = {
10931093+ init_context : context -> Doc.t -> unit;
10941094+ inline : inline;
10951095+ block : block;
10961096+ doc : doc;
10971097+}
10981098+10991099+and context = {
11001100+ renderer : t;
11011101+ mutable state : Dict.t;
11021102+ b : Buffer.t;
11031103+ mutable document : Doc.t;
11041104+}
11051105+11061106+and inline = context -> Inline.t -> bool
11071107+and block = context -> Block.t -> bool
11081108+and doc = context -> Doc.t -> bool
11091109+11101110+let nop _ _ = ()
11111111+let none _ _ = false
11121112+11131113+let make ?(init_context = nop) ?(inline = none) ?(block = none) ?(doc = none) ()
11141114+ =
11151115+ { init_context; inline; block; doc }
11161116+11171117+let compose g f =
11181118+ let init_context c d =
11191119+ g.init_context c d;
11201120+ f.init_context c d
11211121+ in
11221122+ let block c b = f.block c b || g.block c b in
11231123+ let inline c i = f.inline c i || g.inline c i in
11241124+ let doc c d = f.doc c d || g.doc c d in
11251125+ { init_context; inline; block; doc }
11261126+11271127+let _init_context r = r.init_context
11281128+let _inline r = r.inline
11291129+let _block r = r.block
11301130+let _doc r = r.doc
11311131+11321132+module Context = struct
11331133+ type t = context
11341134+ let make renderer b =
11351135+ { renderer; b; state = Dict.empty; document = Doc.empty }
11361136+11371137+ let buffer c = c.b
11381138+ let renderer c = c.renderer
11391139+ let get_document (c : context) = c.document
11401140+ let get_defs (c : context) = Doc.defs c.document
11411141+11421142+ module State = struct
11431143+ type 'a t = 'a Dict.key
11441144+ let make = Dict.key
11451145+ let find c st = Dict.find st c.state
11461146+ let get c st = Option.get (Dict.find st c.state)
11471147+ let set c st = function
11481148+ | None -> c.state <- Dict.remove st c.state
11491149+ | Some s -> c.state <- Dict.add st s c.state
11501150+ end
11511151+11521152+ let init c d = c.renderer.init_context c d
11531153+11541154+ let invalid_inline _ = invalid_arg "Unknown Inline.t case"
11551155+ let invalid_block _ = invalid_arg "Unknown Block.t case"
11561156+ let unhandled_doc _ = invalid_arg "Unhandled Doc.t"
11571157+11581158+ let byte r c = Buffer.add_char r.b c
11591159+ let utf_8_uchar r u = Buffer.add_utf_8_uchar r.b u
11601160+ let string c s = Buffer.add_string c.b s
11611161+ let inline c i = ignore (c.renderer.inline c i || invalid_inline i)
11621162+ let block c b = ignore (c.renderer.block c b || invalid_block b)
11631163+ let doc (c : context) d =
11641164+ c.document <- d;
11651165+ init c d;
11661166+ ignore (c.renderer.doc c d || unhandled_doc d);
11671167+ c.document <- Doc.empty
11681168+end
11691169+11701170+let doc_to_string r d =
11711171+ let b = Buffer.create 1024 in
11721172+ let c = Context.make r b in
11731173+ Context.doc c d;
11741174+ Buffer.contents b
11751175+11761176+let buffer_add_doc r b d = Context.doc (Context.make r b) d
11771177+11781178+type indent =
11791179+ [ `I of int
11801180+ | `L of int * string * int * Uchar.t option
11811181+ | `Q of int
11821182+ | `Fn of int * Label.t ]
11831183+11841184+type state = {
11851185+ nl : string; (* newline to output. *)
11861186+ mutable sot : bool; (* start of text *)
11871187+ mutable indents : indent list; (* indentation stack. *)
11881188+}
11891189+11901190+let state : state Context.State.t = Context.State.make ()
11911191+let get_state c = Context.State.get c state
11921192+let init_context c d =
11931193+ Context.State.set c state (Some { nl = Doc.nl d; sot = true; indents = [] })
11941194+11951195+module Char_set = Set.Make (Char)
11961196+11971197+let esc_angles = Char_set.of_list [ '<'; '>' ]
11981198+let esc_parens = Char_set.of_list [ '('; ')' ]
11991199+let esc_quote = Char_set.singleton '\''
12001200+let esc_dquote = Char_set.singleton '\"'
12011201+let esc_link_label = Char_set.of_list [ '['; ']'; '\\' ]
12021202+12031203+let buffer_add_dec_esc b c =
12041204+ Buffer.add_string b "&#";
12051205+ Buffer.add_string b (Int.to_string (Char.code c));
12061206+ Buffer.add_char b ';'
12071207+12081208+let buffer_add_bslash_esc b c =
12091209+ Buffer.add_char b '\\';
12101210+ Buffer.add_char b c
12111211+12121212+let buffer_add_escaped_string ?(esc_ctrl = true) b cs s =
12131213+ let flush b max start i =
12141214+ if start <= max then Buffer.add_substring b s start (i - start)
12151215+ in
12161216+ let rec loop b s max start i =
12171217+ if i > max then flush b max start i
12181218+ else
12191219+ let next = i + 1 in
12201220+ let c = String.get s i in
12211221+ if Char_set.mem c cs then (
12221222+ flush b max start i;
12231223+ buffer_add_bslash_esc b c;
12241224+ loop b s max next next)
12251225+ else if esc_ctrl && Ascii.is_control c then (
12261226+ flush b max start i;
12271227+ buffer_add_dec_esc b c;
12281228+ loop b s max next next)
12291229+ else loop b s max start next
12301230+ in
12311231+ loop b s (String.length s - 1) 0 0
12321232+12331233+let escaped_string ?esc_ctrl c cs s =
12341234+ buffer_add_escaped_string ?esc_ctrl (Context.buffer c) cs s
12351235+12361236+let buffer_add_escaped_text b s =
12371237+ let esc_first b s =
12381238+ match s.[0] with
12391239+ | ('-' | '+' | '_' | '=') as c ->
12401240+ Buffer.add_char b '\\';
12411241+ Buffer.add_char b c;
12421242+ true
12431243+ | _ -> false
12441244+ in
12451245+ let esc_amp s max next =
12461246+ next <= max && (Ascii.is_letter s.[next] || s.[next] = '#')
12471247+ in
12481248+ let esc_tilde s max prev next =
12491249+ (not (Char.equal prev '~')) && next <= max && s.[next] = '~'
12501250+ in
12511251+ let esc_item_marker s i =
12521252+ if i = 0 || i > 9 (* marker has from 1-9 digits *) then false
12531253+ else
12541254+ let k = ref (i - 1) in
12551255+ while !k >= 0 && Ascii.is_digit s.[!k] do
12561256+ decr k
12571257+ done;
12581258+ !k < 0
12591259+ in
12601260+ let flush b max start i =
12611261+ if start <= max then Buffer.add_substring b s start (i - start)
12621262+ in
12631263+ let rec loop b s max start prev i =
12641264+ if i > max then flush b max start i
12651265+ else
12661266+ let next = i + 1 in
12671267+ let c = String.get s i in
12681268+ if Ascii.is_control c then (
12691269+ flush b max start i;
12701270+ buffer_add_dec_esc b c;
12711271+ loop b s max next c next)
12721272+ else
12731273+ match c with
12741274+ | ('#' | '`') when not (Char.equal prev c) ->
12751275+ flush b max start i;
12761276+ buffer_add_bslash_esc b c;
12771277+ loop b s max next c next
12781278+ | '~' when esc_tilde s max prev next ->
12791279+ flush b max start i;
12801280+ buffer_add_bslash_esc b c;
12811281+ loop b s max next c next
12821282+ | '&' when esc_amp s max next ->
12831283+ flush b max start i;
12841284+ buffer_add_bslash_esc b c;
12851285+ loop b s max next c next
12861286+ | '!' when i = max ->
12871287+ flush b max start i;
12881288+ buffer_add_bslash_esc b c;
12891289+ loop b s max next c next
12901290+ | ('.' | ')') when esc_item_marker s i ->
12911291+ flush b max start i;
12921292+ buffer_add_bslash_esc b c;
12931293+ loop b s max next c next
12941294+ | '\\' | '<' | '>' | '[' | ']' | '*' | '_' | '$' | '|' ->
12951295+ flush b max start i;
12961296+ buffer_add_bslash_esc b c;
12971297+ loop b s max next c next
12981298+ | _ -> loop b s max start c next
12991299+ in
13001300+ let max = String.length s - 1 in
13011301+ if max < 0 then ()
13021302+ else if esc_first b s then loop b s max 1 s.[0] 1
13031303+ else loop b s max 0 '\x00' 0
13041304+13051305+let escaped_text c s = buffer_add_escaped_text (Context.buffer c) s
13061306+13071307+let string_node_option c = function
13081308+ | None -> ()
13091309+ | Some (s, _) -> Context.string c s
13101310+let nchars c n char =
13111311+ for _i = 1 to n do
13121312+ Context.byte c char
13131313+ done
13141314+13151315+let newline c =
13161316+ (* Block generally introduce newlines, except the first one. *)
13171317+ let st = get_state c in
13181318+ if st.sot then st.sot <- false else Context.string c st.nl
13191319+13201320+let push_indent c n =
13211321+ let st = get_state c in
13221322+ st.indents <- n :: st.indents
13231323+let pop_indent c =
13241324+ let st = get_state c in
13251325+ match st.indents with [] -> () | ns -> st.indents <- List.tl ns
13261326+13271327+let rec indent c =
13281328+ let rec loop c acc = function
13291329+ | [] -> acc
13301330+ | (`I n as i) :: is ->
13311331+ nchars c n ' ';
13321332+ loop c (i :: acc) is
13331333+ | (`Q n as i) :: is ->
13341334+ nchars c n ' ';
13351335+ Context.byte c '>';
13361336+ Context.byte c ' ';
13371337+ loop c (i :: acc) is
13381338+ | `L (before, m, after, task) :: is ->
13391339+ nchars c before ' ';
13401340+ Context.string c m;
13411341+ nchars c after ' ';
13421342+ let after =
13431343+ match task with
13441344+ | None -> after
13451345+ | Some u ->
13461346+ Context.byte c '[';
13471347+ Context.utf_8_uchar c u;
13481348+ Context.string c "] ";
13491349+ after + 4
13501350+ in
13511351+ (* On the next call we'll just indent for the list item *)
13521352+ loop c (`I (before + String.length m + after) :: acc) is
13531353+ | `Fn (before, label) :: is ->
13541354+ nchars c before ' ';
13551355+ Context.byte c '[';
13561356+ link_label_lines c (Label.text label);
13571357+ Context.string c "]:";
13581358+ (* On the next call we'll just indent to ^ for the footnote *)
13591359+ loop c (`I (before + 1) :: acc) is
13601360+ in
13611361+ let st = get_state c in
13621362+ st.indents <- loop c [] (List.rev st.indents)
13631363+13641364+and link_label_lines c lines = escaped_tight_block_lines c esc_link_label lines
13651365+13661366+and escaped_tight_block_lines c cs = function
13671367+ | [] -> ()
13681368+ | l :: ls ->
13691369+ let tight c (blanks, (l, _)) =
13701370+ Context.string c blanks;
13711371+ escaped_string c cs l
13721372+ in
13731373+ let line c l =
13741374+ newline c;
13751375+ indent c;
13761376+ tight c l
13771377+ in
13781378+ tight c l;
13791379+ List.iter (line c) ls
13801380+13811381+let block_lines c = function
13821382+ | [] -> ()
13831383+ | (l, _) :: ls ->
13841384+ let line c (l, _) =
13851385+ newline c;
13861386+ indent c;
13871387+ Context.string c l
13881388+ in
13891389+ Context.string c l;
13901390+ List.iter (line c) ls
13911391+13921392+let tight_block_lines c = function
13931393+ | [] -> ()
13941394+ | l :: ls ->
13951395+ let tight c (blanks, (l, _)) =
13961396+ Context.string c blanks;
13971397+ Context.string c l
13981398+ in
13991399+ let line c l =
14001400+ newline c;
14011401+ indent c;
14021402+ tight c l
14031403+ in
14041404+ tight c l;
14051405+ List.iter (line c) ls
14061406+14071407+let autolink c a =
14081408+ Context.byte c '<';
14091409+ Context.string c (fst (Inline.Autolink.link a));
14101410+ Context.byte c '>'
14111411+14121412+let break c b =
14131413+ let layout_before = fst (Inline.Break.layout_before b) in
14141414+ let layout_after = fst (Inline.Break.layout_after b) in
14151415+ let before, after =
14161416+ match Inline.Break.type' b with
14171417+ | `Soft -> (layout_before, layout_after)
14181418+ | `Hard ->
14191419+ ((if layout_before = "" then " " else layout_before), layout_after)
14201420+ in
14211421+ Context.string c before;
14221422+ newline c;
14231423+ indent c;
14241424+ Context.string c after
14251425+14261426+let code_span c cs =
14271427+ nchars c (Inline.Code_span.backtick_count cs) '`';
14281428+ tight_block_lines c (Inline.Code_span.code_layout cs);
14291429+ nchars c (Inline.Code_span.backtick_count cs) '`'
14301430+14311431+let emphasis c e =
14321432+ let delim = Inline.Emphasis.delim e and i = Inline.Emphasis.inline e in
14331433+ let delim = if not (delim = '*' || delim = '_') then '*' else delim in
14341434+ Context.byte c delim;
14351435+ Context.inline c i;
14361436+ Context.byte c delim
14371437+14381438+let strong_emphasis c e =
14391439+ let delim = Inline.Emphasis.delim e and i = Inline.Emphasis.inline e in
14401440+ let delim = if not (delim = '*' || delim = '_') then '*' else delim in
14411441+ Context.byte c delim;
14421442+ Context.byte c delim;
14431443+ Context.inline c i;
14441444+ Context.byte c delim;
14451445+ Context.byte c delim
14461446+14471447+let link_title c open_delim title =
14481448+ match title with
14491449+ | None -> ()
14501450+ | Some lines ->
14511451+ let open', close, escapes =
14521452+ match open_delim with
14531453+ | '\"' as delim -> (delim, delim, esc_dquote)
14541454+ | '\'' as delim -> (delim, delim, esc_quote)
14551455+ | '(' -> ('(', ')', esc_parens)
14561456+ | _ -> ('\"', '\"', esc_dquote)
14571457+ in
14581458+ Context.byte c open';
14591459+ escaped_tight_block_lines c escapes lines;
14601460+ Context.byte c close
14611461+14621462+let link_definition c ld =
14631463+ let layout = Link_definition.layout ld in
14641464+ block_lines c layout.before_dest;
14651465+ (match Link_definition.dest ld with
14661466+ | None -> ()
14671467+ | Some (dest, _) ->
14681468+ if layout.angled_dest then (
14691469+ Context.byte c '<';
14701470+ escaped_string c esc_angles dest;
14711471+ Context.byte c '>')
14721472+ else escaped_string c esc_parens dest);
14731473+ if
14741474+ layout.after_dest = []
14751475+ && Option.is_some (Link_definition.dest ld)
14761476+ && Option.is_some (Link_definition.title ld)
14771477+ then Context.byte c ' ' (* at least a space is needed *);
14781478+ block_lines c layout.after_dest;
14791479+ link_title c layout.title_open_delim (Link_definition.title ld);
14801480+ block_lines c layout.after_title
14811481+14821482+let link c l =
14831483+ match Inline.Link.reference l with
14841484+ | `Inline (ld, _) ->
14851485+ Context.byte c '[';
14861486+ Context.inline c (Inline.Link.text l);
14871487+ Context.byte c ']';
14881488+ Context.byte c '(';
14891489+ link_definition c ld;
14901490+ Context.byte c ')'
14911491+ | `Ref (`Shortcut, label, _) ->
14921492+ Context.byte c '[';
14931493+ link_label_lines c (Label.text label);
14941494+ Context.byte c ']'
14951495+ | `Ref (`Collapsed, label, _) ->
14961496+ Context.byte c '[';
14971497+ link_label_lines c (Label.text label);
14981498+ Context.byte c ']';
14991499+ Context.string c "[]"
15001500+ | `Ref (`Full, label, _) ->
15011501+ Context.byte c '[';
15021502+ Context.inline c (Inline.Link.text l);
15031503+ Context.byte c ']';
15041504+ Context.byte c '[';
15051505+ link_label_lines c (Label.text label);
15061506+ Context.byte c ']'
15071507+15081508+let inlines c is = List.iter (Context.inline c) is
15091509+let image c l =
15101510+ Context.byte c '!';
15111511+ link c l
15121512+let raw_html c h = tight_block_lines c h
15131513+let text c t = escaped_text c t
15141514+15151515+let strikethrough c s =
15161516+ let i = Inline.Strikethrough.inline s in
15171517+ Context.string c "~~";
15181518+ Context.inline c i;
15191519+ Context.string c "~~"
15201520+15211521+let math_span c ms =
15221522+ let sep = if Inline.Math_span.display ms then "$$" else "$" in
15231523+ Context.string c sep;
15241524+ tight_block_lines c (Inline.Math_span.tex_layout ms);
15251525+ Context.string c sep
15261526+15271527+let inline c = function
15281528+ | Inline.Autolink (a, _) ->
15291529+ autolink c a;
15301530+ true
15311531+ | Inline.Break (b, _) ->
15321532+ break c b;
15331533+ true
15341534+ | Inline.Code_span (cs, _) ->
15351535+ code_span c cs;
15361536+ true
15371537+ | Inline.Emphasis (e, _) ->
15381538+ emphasis c e;
15391539+ true
15401540+ | Inline.Image (i, _) ->
15411541+ image c i;
15421542+ true
15431543+ | Inline.Inlines (is, _) ->
15441544+ inlines c is;
15451545+ true
15461546+ | Inline.Link (l, _) ->
15471547+ link c l;
15481548+ true
15491549+ | Inline.Raw_html (html, _) ->
15501550+ raw_html c html;
15511551+ true
15521552+ | Inline.Strong_emphasis (e, _) ->
15531553+ strong_emphasis c e;
15541554+ true
15551555+ | Inline.Text (t, _) ->
15561556+ text c t;
15571557+ true
15581558+ | Inline.Ext_strikethrough (s, _) ->
15591559+ strikethrough c s;
15601560+ true
15611561+ | Inline.Ext_math_span (m, _) ->
15621562+ math_span c m;
15631563+ true
15641564+ | _ ->
15651565+ Context.string c "<!-- Unknown Cmarkit inline -->";
15661566+ true
15671567+15681568+let blank_line c l =
15691569+ newline c;
15701570+ indent c;
15711571+ Context.string c l
15721572+15731573+let block_quote c bq =
15741574+ push_indent c (`Q (Block.Block_quote.indent bq));
15751575+ Context.block c (Block.Block_quote.block bq);
15761576+ pop_indent c
15771577+15781578+let code_block c cb =
15791579+ match Block.Code_block.layout cb with
15801580+ | `Indented ->
15811581+ newline c;
15821582+ push_indent c (`I 4);
15831583+ indent c;
15841584+ block_lines c (Block.Code_block.code cb);
15851585+ pop_indent c
15861586+ | `Fenced f ->
15871587+ let opening, closing =
15881588+ match fst f.opening_fence with
15891589+ | "" ->
15901590+ let char, len = Block.Code_block.make_fence cb in
15911591+ let f = String.make len char in
15921592+ (f, Some f)
15931593+ | opening -> (opening, Option.map fst f.closing_fence)
15941594+ in
15951595+ let info_string = Block.Code_block.info_string cb in
15961596+ let code = Block.Code_block.code cb in
15971597+ newline c;
15981598+ push_indent c (`I f.indent);
15991599+ indent c;
16001600+ Context.string c opening;
16011601+ string_node_option c info_string;
16021602+ if code <> [] then (
16031603+ newline c;
16041604+ indent c;
16051605+ block_lines c code);
16061606+ (match closing with
16071607+ | None -> ()
16081608+ | Some close ->
16091609+ newline c;
16101610+ indent c;
16111611+ Context.string c close);
16121612+ pop_indent c
16131613+16141614+let heading c h =
16151615+ newline c;
16161616+ indent c;
16171617+ match Block.Heading.layout h with
16181618+ | `Atx { indent; after_opening; closing } ->
16191619+ let inline = Block.Heading.inline h in
16201620+ nchars c indent ' ';
16211621+ nchars c (Block.Heading.level h) '#';
16221622+ if after_opening = "" && not (Inline.is_empty inline) then
16231623+ Context.byte c ' '
16241624+ else Context.string c after_opening;
16251625+ Context.inline c inline;
16261626+ Context.string c closing
16271627+ | `Setext l ->
16281628+ let u =
16291629+ match Block.Heading.level h with 1 -> '=' | 2 -> '-' | _ -> '-'
16301630+ in
16311631+ nchars c l.leading_indent ' ';
16321632+ Context.inline c (Block.Heading.inline h);
16331633+ Context.string c l.trailing_blanks;
16341634+ newline c;
16351635+ indent c;
16361636+ nchars c l.underline_indent ' ';
16371637+ nchars c (fst l.underline_count) u;
16381638+ Context.string c l.underline_blanks
16391639+16401640+let html_block c h =
16411641+ newline c;
16421642+ indent c;
16431643+ block_lines c h
16441644+16451645+let link_reference_definition c ld =
16461646+ newline c;
16471647+ indent c;
16481648+ nchars c (Link_definition.layout ld).indent ' ';
16491649+ Context.byte c '[';
16501650+ (match Link_definition.label ld with
16511651+ | None -> ()
16521652+ | Some label -> escaped_tight_block_lines c esc_link_label (Label.text label));
16531653+ Context.string c "]:";
16541654+ link_definition c ld
16551655+16561656+let unordered_item c marker (i, _) =
16571657+ let before = Block.List_item.before_marker i in
16581658+ let after = Block.List_item.after_marker i in
16591659+ let task = Option.map fst (Block.List_item.ext_task_marker i) in
16601660+ push_indent c (`L (before, marker, after, task));
16611661+ Context.block c (Block.List_item.block i);
16621662+ pop_indent c
16631663+16641664+let ordered_item c sep num (i, _) =
16651665+ let before = Block.List_item.before_marker i in
16661666+ let marker = fst (Block.List_item.marker i) in
16671667+ let marker = if marker = "" then Int.to_string num ^ sep else marker in
16681668+ let after = Block.List_item.after_marker i in
16691669+ let task = Option.map fst (Block.List_item.ext_task_marker i) in
16701670+ push_indent c (`L (before, marker, after, task));
16711671+ Context.block c (Block.List_item.block i);
16721672+ pop_indent c;
16731673+ num + 1
16741674+16751675+let list c l =
16761676+ match Block.List'.type' l with
16771677+ | `Unordered marker ->
16781678+ let marker = match marker with '*' | '-' | '+' -> marker | _ -> '*' in
16791679+ let marker = String.make 1 marker in
16801680+ List.iter (unordered_item c marker) (Block.List'.items l)
16811681+ | `Ordered (start, sep) ->
16821682+ let sep = if sep <> '.' && sep <> ')' then '.' else sep in
16831683+ let sep = String.make 1 sep in
16841684+ ignore (List.fold_left (ordered_item c sep) start (Block.List'.items l))
16851685+16861686+let paragraph c p =
16871687+ newline c;
16881688+ indent c;
16891689+ nchars c (Block.Paragraph.leading_indent p) ' ';
16901690+ Context.inline c (Block.Paragraph.inline p);
16911691+ Context.string c (Block.Paragraph.trailing_blanks p)
16921692+16931693+let thematic_break c t =
16941694+ let ind = Block.Thematic_break.indent t in
16951695+ let break = Block.Thematic_break.layout t in
16961696+ let break = if break = "" then "---" else break in
16971697+ newline c;
16981698+ indent c;
16991699+ nchars c ind ' ';
17001700+ Context.string c break
17011701+17021702+let table c t =
17031703+ let col c (i, (before, after)) =
17041704+ Context.byte c '|';
17051705+ Context.string c before;
17061706+ Context.inline c i;
17071707+ Context.string c after
17081708+ in
17091709+ let sep c ((align, len), _) =
17101710+ Context.byte c '|';
17111711+ match align with
17121712+ | None -> nchars c len '-'
17131713+ | Some `Left ->
17141714+ Context.byte c ':';
17151715+ nchars c len '-'
17161716+ | Some `Center ->
17171717+ Context.byte c ':';
17181718+ nchars c len '-';
17191719+ Context.byte c ':'
17201720+ | Some `Right ->
17211721+ nchars c len '-';
17221722+ Context.byte c ':'
17231723+ in
17241724+ let row c = function
17251725+ | (`Header cols, _), blanks | (`Data cols, _), blanks ->
17261726+ newline c;
17271727+ indent c;
17281728+ if cols = [] then Context.byte c '|' else List.iter (col c) cols;
17291729+ Context.byte c '|';
17301730+ Context.string c blanks
17311731+ | (`Sep seps, _), blanks ->
17321732+ newline c;
17331733+ indent c;
17341734+ if seps = [] then Context.byte c '|' else List.iter (sep c) seps;
17351735+ Context.byte c '|';
17361736+ Context.string c blanks
17371737+ in
17381738+ push_indent c (`I (Block.Table.indent t));
17391739+ List.iter (row c) (Block.Table.rows t);
17401740+ pop_indent c
17411741+17421742+let footnote c fn =
17431743+ push_indent c (`Fn (Block.Footnote.indent fn, Block.Footnote.label fn));
17441744+ Context.block c (Block.Footnote.block fn);
17451745+ pop_indent c
17461746+17471747+let block c = function
17481748+ | Block.Blank_line (l, _) ->
17491749+ blank_line c l;
17501750+ true
17511751+ | Block.Block_quote (b, _) ->
17521752+ block_quote c b;
17531753+ true
17541754+ | Block.Blocks (bs, _) ->
17551755+ List.iter (Context.block c) bs;
17561756+ true
17571757+ | Block.Code_block (cb, _) ->
17581758+ code_block c cb;
17591759+ true
17601760+ | Block.Heading (h, _) ->
17611761+ heading c h;
17621762+ true
17631763+ | Block.Html_block (h, _) ->
17641764+ html_block c h;
17651765+ true
17661766+ | Block.Link_reference_definition (ld, _) ->
17671767+ link_reference_definition c ld;
17681768+ true
17691769+ | Block.List (l, _) ->
17701770+ list c l;
17711771+ true
17721772+ | Block.Paragraph (p, _) ->
17731773+ paragraph c p;
17741774+ true
17751775+ | Block.Thematic_break (t, _) ->
17761776+ thematic_break c t;
17771777+ true
17781778+ | Block.Ext_math_block (cb, _) ->
17791779+ code_block c cb;
17801780+ true
17811781+ | Block.Ext_table (t, _) ->
17821782+ table c t;
17831783+ true
17841784+ | Block.Ext_footnote_definition (t, _) ->
17851785+ footnote c t;
17861786+ true
17871787+ | _ ->
17881788+ newline c;
17891789+ indent c;
17901790+ Context.string c "<!-- Unknown Cmarkit block -->";
17911791+ true
17921792+17931793+let doc c d =
17941794+ Context.block c (Doc.block d);
17951795+ true
17961796+17971797+let renderer () = make ~init_context ~inline ~block ~doc ()
+2-1
src/odoc/bin/main.ml
···13161316 (* QUESTION: Where is this being used? *)
13171317 let filepath config url = Odoc_markdown.Generator.filepath ~config url
1318131813191319- let extra_args = Term.const { Odoc_markdown.Config.root_url = None }
13191319+ let extra_args =
13201320+ Term.const { Odoc_markdown.Config.root_url = None; allow_html = true }
13201321 let renderer = { Odoc_document.Renderer.name = "markdown"; render; filepath }
13211322end)
13221323