···11+opam-version: "2.0"
22+33+version: "dev"
44+homepage: "https://github.com/ocaml/odoc"
55+doc: "https://ocaml.github.io/odoc/"
66+bug-reports: "https://github.com/ocaml/odoc/issues"
77+license: "ISC"
88+99+maintainer: [
1010+ "Jon Ludlam <jon@recoil.org>"
1111+ "Jules Aguillon <juloo.dsi@gmail.com>"
1212+ "Paul-Elliot Anglès d'Auriac <paul-elliot@tarides.com>"
1313+]
1414+authors: [
1515+ "Daniel Bünzli <daniel.buenzli@erratique.ch>"
1616+ "Paul-Elliot Anglès d'Auriac <paul-elliot@tarides.com>"
1717+ "Jon Ludlam <jon@recoil.org>"
1818+]
1919+dev-repo: "git+https://github.com/ocaml/odoc.git"
2020+2121+synopsis: "OCaml Documentation Generator - Markdown support"
2222+description: """
2323+Odoc-md is part of the odoc suite of tools for generating documentation for OCaml packages.
2424+2525+This package provides support for generating documentation from Markdown files.
2626+"""
2727+2828+depends: [
2929+ "ocaml" {>= "4.14.0"}
3030+ "odoc" {= version}
3131+ "cmarkit"
3232+]
3333+3434+build: [
3535+ ["dune" "subst"] {dev}
3636+ [
3737+ "dune"
3838+ "build"
3939+ "-p"
4040+ name
4141+ "-j"
4242+ jobs
4343+ "@install"
4444+ "@runtest" {with-test}
4545+ "@doc" {with-doc}
4646+ ]
4747+]
+454
src/markdown/doc_of_md.ml
···11+let strf = Printf.sprintf
22+33+(* ocamlmark parsing *)
44+55+open Odoc_parser
66+open Cmarkit
77+88+(* Text location and comment massaging.
99+1010+ One slight annoyance is that CommonMark is sensitive to leading
1111+ blanks on lines and ocamldoc comments are usually indented by [n]
1212+ spaces up the … of (** … *). So we can't just feed it the comment
1313+ text: we would mostly get CommonMark indented code blocks.
1414+1515+ So we massage the comment to trim up to [n] initial spaces after
1616+ newlines. [n] being the number of columns until … in (** … *). We
1717+ need to remember how much we trimmed on each line in order to patch
1818+ the locations reported by cmarkit. Below we keep pass that info
1919+ around using the [~locator] argument.
2020+2121+ This is not needed in [md] files, but the code is kept in case we
2222+ add support for markdown in docstrings. *)
2323+2424+let comment_col ~location = location.Lexing.pos_cnum - location.Lexing.pos_bol
2525+2626+let massage_comment ~location b s =
2727+ let rec next_non_space s ~max i =
2828+ if i > max || not (s.[i] = ' ') then i else next_non_space s ~max (i + 1)
2929+ in
3030+ let rec find_after_trim ~max_trim s max ~start i =
3131+ if i - start + 1 > max_trim || i > max || s.[i] <> ' ' then i
3232+ else find_after_trim ~max_trim s max ~start (i + 1)
3333+ in
3434+ let flush b s start last =
3535+ Buffer.add_substring b s start (last - start + 1)
3636+ in
3737+ let rec loop b s acc ~max_trim max start k =
3838+ if k > max then (
3939+ flush b s start max;
4040+ ((location, Array.of_list (List.rev acc)), Buffer.contents b))
4141+ else if not (s.[k] = '\n' || s.[k] = '\r') then
4242+ loop b s acc ~max_trim max start (k + 1)
4343+ else
4444+ let next = k + 1 in
4545+ let next =
4646+ if s.[k] = '\r' && next <= max && s.[next] = '\n' then next + 1
4747+ else next
4848+ in
4949+ let after_trim = find_after_trim ~max_trim s max ~start:next next in
5050+ let trim = after_trim - next in
5151+ flush b s start (next - 1);
5252+ loop b s (trim :: acc) ~max_trim max after_trim after_trim
5353+ in
5454+ if s = "" then ((location, [| 0 |]), s)
5555+ else
5656+ let max = String.length s - 1 in
5757+ let nsp = next_non_space s ~max 0 in
5858+ let max_trim = comment_col ~location + nsp in
5959+ loop b s [ nsp (* trim *) ] ~max_trim max nsp nsp
6060+6161+let textloc_to_loc ~locator textloc =
6262+ (* Note: if you get an [Invalid_argument] from this function suspect a bug
6363+ in cmarkit's location computation. *)
6464+ let point_of_line_and_byte_pos ~locator:(location, line_trim_counts) l pos =
6565+ let line_num, line_pos = l in
6666+ let line = location.Lexing.pos_lnum + line_num - 1 in
6767+ let column = line_trim_counts.(line_num - 1) + (pos - line_pos) in
6868+ let column =
6969+ match line_num with 1 -> comment_col ~location + column | _ -> column
7070+ in
7171+ { Loc.line; column }
7272+ in
7373+ let file = Textloc.file textloc in
7474+ let first_line = Textloc.first_line textloc in
7575+ let first_byte = Textloc.first_byte textloc in
7676+ let last_line = Textloc.last_line textloc in
7777+ let last_byte = Textloc.last_byte textloc + 1 in
7878+ let start = point_of_line_and_byte_pos ~locator first_line first_byte in
7979+ let end_ = point_of_line_and_byte_pos ~locator last_line last_byte in
8080+ { Loc.file; start; end_ }
8181+8282+let meta_to_loc ~locator meta = textloc_to_loc ~locator (Meta.textloc meta)
8383+8484+(* Sometimes we need to munge a bit the cmarkit metas and textlocs.
8585+ These function do that. They are not general and make assumptions
8686+ about the nature of data they apply to. E.g. most assume the
8787+ textloc is on the same line. *)
8888+8989+let split_info_string_locs ~left_count ~right_count m =
9090+ if right_count = 0 then (Meta.textloc m, Textloc.none)
9191+ else
9292+ let textloc = Meta.textloc m in
9393+ let line = Textloc.first_line textloc in
9494+ let last_byte = Textloc.first_byte textloc + left_count - 1 in
9595+ let first_byte = Textloc.last_byte textloc - right_count + 1 in
9696+ ( Textloc.set_last textloc ~last_byte ~last_line:line,
9797+ Textloc.set_first textloc ~first_byte ~first_line:line )
9898+9999+let textloc_of_sub textloc ~first ~last (* in textloc relative space *) =
100100+ let file = Textloc.file textloc in
101101+ let line = Textloc.first_line textloc in
102102+ let first_byte = Textloc.first_byte textloc + first in
103103+ let last_byte = Textloc.first_byte textloc + last in
104104+ Textloc.v ~file ~first_byte ~last_byte ~first_line:line ~last_line:line
105105+106106+(* Warnings *)
107107+108108+let warn_unsupported_hard_break =
109109+ "Hard breaks are unsupported in ocamlmark, using a soft break."
110110+111111+let warn_unsupported_header_nesting =
112112+ "Headers in list items are unsupported in ocamlmark, dropped."
113113+114114+let warn_heading_level_6 =
115115+ "Heading level 6 is unsupported in ocamlmark, using 5."
116116+117117+let warn_unsupported_list_start_number start =
118118+ strf "List start numbers are unsupported in ocamlmark, replacing %d with 1."
119119+ start
120120+121121+let warn_unsupported_cmark kind =
122122+ strf "%s are unsupported in ocamlmark, dropped." kind
123123+124124+let warn_unsupported_link_title =
125125+ "Link titles are unsupported in ocamlmark, dropped."
126126+127127+let warn ~loc:location message warns = { Warning.location; message } :: warns
128128+129129+let warn_unsupported_cmark ~locator kind meta (acc, warns) =
130130+ let msg = warn_unsupported_cmark kind in
131131+ (acc, warn ~loc:(meta_to_loc ~locator meta) msg warns)
132132+133133+let warn_unsupported_header_nesting ~locator meta (acc, warns) =
134134+ let msg = warn_unsupported_header_nesting in
135135+ (acc, warn ~loc:(meta_to_loc ~locator meta) msg warns)
136136+137137+let is_blank = function ' ' | '\t' -> true | _ -> false
138138+let rec next_blank s ~max i =
139139+ if i > max || is_blank s.[i] then i else next_blank s ~max (i + 1)
140140+141141+let rec next_nonblank s ~max i =
142142+ if i > max || not (is_blank s.[i]) then i else next_nonblank s ~max (i + 1)
143143+144144+(* Translating blocks and inlines. *)
145145+146146+(* A few type definitions for better variant typing. *)
147147+148148+type inlines_acc = Ast.inline_element Ast.with_location list * Warning.t list
149149+type ast_acc = Ast.t * Warning.t list
150150+type nestable_ast_acc =
151151+ Ast.nestable_block_element Ast.with_location list * Warning.t list
152152+153153+(* Inline translations *)
154154+155155+let link_definition defs l =
156156+ match Inline.Link.reference_definition defs l with
157157+ | Some (Link_definition.Def (ld, _)) -> ld
158158+ | Some _ -> assert false (* if we parse without cmarkit extensions *)
159159+ | None -> assert false (* assert [l]'s referenced label is not synthetic *)
160160+161161+let autolink_to_inline_element ~locator a m (is, warns) =
162162+ let loc = meta_to_loc ~locator m in
163163+ let link, link_loc = Inline.Autolink.link a in
164164+ let link_loc = meta_to_loc ~locator link_loc in
165165+ let text = [ Loc.at link_loc (`Word link) ] in
166166+ (Loc.at loc (`Link (link, text)) :: is, warns)
167167+168168+let break_to_inline_element ~locator br m (is, warns) =
169169+ let loc = meta_to_loc ~locator m in
170170+ let warns =
171171+ match Inline.Break.type' br with
172172+ | `Soft -> warns
173173+ | `Hard -> warn ~loc warn_unsupported_hard_break warns
174174+ in
175175+ (Loc.at loc (`Space "\n") :: is, warns)
176176+177177+let code_span_to_inline_element ~locator cs m (is, warns) =
178178+ let loc = meta_to_loc ~locator m in
179179+ let code = Inline.Code_span.code cs in
180180+ (Loc.at loc (`Code_span code) :: is, warns)
181181+182182+let raw_html_to_inline_element ~locator html m (is, warns) =
183183+ let loc = meta_to_loc ~locator m in
184184+ let html = String.concat "\n" (List.map Block_line.tight_to_string html) in
185185+ (Loc.at loc (`Raw_markup (Some "html", html)) :: is, warns)
186186+187187+let image_to_inline_element ~locator defs i m (is, warns) =
188188+ (* We map to raw html, ocamldoc's ast should have a case for that. *)
189189+ let escape esc b s =
190190+ Buffer.clear b;
191191+ esc b s;
192192+ Buffer.contents b
193193+ in
194194+ let pct_esc = escape Cmarkit_html.buffer_add_pct_encoded_string in
195195+ let html_esc = escape Cmarkit_html.buffer_add_html_escaped_string in
196196+ let loc = meta_to_loc ~locator m in
197197+ let b = Buffer.create 255 in
198198+ let ld = link_definition defs i in
199199+ let link =
200200+ match Link_definition.dest ld with
201201+ | None -> ""
202202+ | Some (link, _) -> pct_esc b link
203203+ in
204204+ let title =
205205+ match Link_definition.title ld with
206206+ | None -> ""
207207+ | Some title ->
208208+ let title = List.map Block_line.tight_to_string title in
209209+ html_esc b (String.concat "\n" title)
210210+ in
211211+ let alt =
212212+ let ls = Inline.to_plain_text ~break_on_soft:false (Inline.Link.text i) in
213213+ html_esc b (String.concat "\n" (List.map (String.concat "") ls))
214214+ in
215215+ let img =
216216+ String.concat ""
217217+ [ {|<img src="|}; link; {|" alt="|}; alt; {|" title="|}; title; {|" >"|} ]
218218+ in
219219+ (Loc.at loc (`Raw_markup (Some "html", img)) :: is, warns)
220220+221221+let text_to_inline_elements ~locator s meta ((is, warns) as acc) =
222222+ (* [s] is on a single source line (but may have newlines because of
223223+ character references) we need to tokenize it for ocamldoc's ast. *)
224224+ let flush_tok s meta acc is_space first last =
225225+ let textloc = textloc_of_sub (Meta.textloc meta) ~first ~last in
226226+ let loc = textloc_to_loc ~locator textloc in
227227+ let s = String.sub s first (last - first + 1) in
228228+ Loc.at loc (if is_space then `Space s else `Word s) :: acc
229229+ in
230230+ let rec tokenize s meta acc max start is_space =
231231+ if start > max then (List.rev_append acc is, warns)
232232+ else
233233+ let next_start =
234234+ if is_space then next_nonblank s ~max start else next_blank s ~max start
235235+ in
236236+ let acc = flush_tok s meta acc is_space start (next_start - 1) in
237237+ tokenize s meta acc max next_start (not is_space)
238238+ in
239239+ let max = String.length s - 1 in
240240+ if max < 0 then acc else tokenize s meta [] max 0 (is_blank s.[0])
241241+242242+let rec link_reference_to_inline_element ~locator defs l m (is, warns) =
243243+ let loc = meta_to_loc ~locator m in
244244+ let ld = link_definition defs l in
245245+ let link =
246246+ match Link_definition.dest ld with None -> "" | Some (l, _) -> l
247247+ in
248248+ let warns =
249249+ match Link_definition.title ld with
250250+ | None -> warns
251251+ | Some title ->
252252+ let textloc = Block_line.tight_list_textloc title in
253253+ let loc = textloc_to_loc ~locator textloc in
254254+ warn ~loc warn_unsupported_link_title warns
255255+ in
256256+ let text, warns =
257257+ inline_to_inline_elements ~locator defs ([], warns) (Inline.Link.text l)
258258+ in
259259+ (Loc.at loc (`Link (link, text)) :: is, warns)
260260+261261+and link_to_inline_element ~locator defs l m acc =
262262+ link_reference_to_inline_element ~locator defs l m acc
263263+264264+and emphasis_to_inline_element ~locator defs style e m (is, warns) =
265265+ let loc = meta_to_loc ~locator m in
266266+ let i = Inline.Emphasis.inline e in
267267+ let inlines, warns = inline_to_inline_elements ~locator defs ([], warns) i in
268268+ (Loc.at loc (`Styled (style, inlines)) :: is, warns)
269269+270270+and inline_to_inline_elements ~locator defs acc i : inlines_acc =
271271+ match i with
272272+ | Inline.Autolink (a, m) -> autolink_to_inline_element ~locator a m acc
273273+ | Inline.Break (b, m) -> break_to_inline_element ~locator b m acc
274274+ | Inline.Code_span (cs, m) -> code_span_to_inline_element ~locator cs m acc
275275+ | Inline.Emphasis (e, m) ->
276276+ emphasis_to_inline_element ~locator defs `Emphasis e m acc
277277+ | Inline.Image (i, m) -> image_to_inline_element ~locator defs i m acc
278278+ | Inline.Inlines (is, _m) ->
279279+ let inline = inline_to_inline_elements ~locator defs in
280280+ List.fold_left inline acc (List.rev is)
281281+ | Inline.Link (l, m) -> link_to_inline_element ~locator defs l m acc
282282+ | Inline.Raw_html (html, m) -> raw_html_to_inline_element ~locator html m acc
283283+ | Inline.Strong_emphasis (e, m) ->
284284+ emphasis_to_inline_element ~locator defs `Bold e m acc
285285+ | Inline.Text (t, m) -> text_to_inline_elements ~locator t m acc
286286+ | _ -> assert false
287287+288288+(* Block translations *)
289289+290290+let raw_paragraph ~loc ~raw_loc backend raw =
291291+ Loc.at loc (`Paragraph [ Loc.at raw_loc (`Raw_markup (Some backend, raw)) ])
292292+293293+let code_block_to_nestable_block_element ~locator cb m (bs, warns) =
294294+ let loc = meta_to_loc ~locator m in
295295+ let code = Block.Code_block.code cb in
296296+ let code_loc = textloc_to_loc ~locator (Block_line.list_textloc code) in
297297+ let code = String.concat "\n" (List.map Block_line.to_string code) in
298298+ match Block.Code_block.info_string cb with
299299+ | None ->
300300+ let code_block =
301301+ {
302302+ Ast.meta = None;
303303+ delimiter = None;
304304+ content = Loc.at code_loc code;
305305+ output = None;
306306+ }
307307+ (* (None, Loc.at code_loc code) *)
308308+ in
309309+ (Loc.at loc (`Code_block code_block) :: bs, warns)
310310+ | Some (info, im) -> (
311311+ match Block.Code_block.language_of_info_string info with
312312+ | None ->
313313+ let code_block =
314314+ {
315315+ Ast.meta = None;
316316+ delimiter = None;
317317+ content = Loc.at code_loc code;
318318+ output = None;
319319+ }
320320+ in
321321+ (* (None, Loc.at code_loc code) *)
322322+ (Loc.at loc (`Code_block code_block) :: bs, warns)
323323+ | Some ("verb", _) -> (Loc.at loc (`Verbatim code) :: bs, warns)
324324+ | Some ("=html", _) ->
325325+ (raw_paragraph ~loc ~raw_loc:code_loc "html" code :: bs, warns)
326326+ | Some ("=latex", _) ->
327327+ (raw_paragraph ~loc ~raw_loc:code_loc "latex" code :: bs, warns)
328328+ | Some ("=texi", _) ->
329329+ (raw_paragraph ~loc ~raw_loc:code_loc "texi" code :: bs, warns)
330330+ | Some ("=man", _) ->
331331+ (raw_paragraph ~loc ~raw_loc:code_loc "man" code :: bs, warns)
332332+ | Some (lang, env) ->
333333+ let left_count = String.length lang in
334334+ let right_count = String.length env in
335335+ let lang_loc, env_loc =
336336+ split_info_string_locs ~left_count ~right_count im
337337+ in
338338+ let env =
339339+ if env = "" then None
340340+ else Some (Loc.at (textloc_to_loc ~locator env_loc) env)
341341+ in
342342+ let lang = Loc.at (textloc_to_loc ~locator lang_loc) lang in
343343+ let metadata = Some { Ast.language = lang; tags = env } in
344344+ let code_block =
345345+ {
346346+ Ast.meta = metadata;
347347+ delimiter = None;
348348+ content = Loc.at code_loc code;
349349+ output = None;
350350+ }
351351+ (* (metadata, Loc.at code_loc code) *)
352352+ in
353353+ (Loc.at loc (`Code_block code_block) :: bs, warns))
354354+355355+let html_block_to_nestable_block_element ~locator html m (bs, warns) =
356356+ let loc = meta_to_loc ~locator m in
357357+ let html = String.concat "\n" (List.map fst html) in
358358+ (raw_paragraph ~loc ~raw_loc:loc "html" html :: bs, warns)
359359+360360+let heading_to_block_element ~locator defs h m (bs, warns) =
361361+ let loc = meta_to_loc ~locator m in
362362+ let level, warns =
363363+ match Block.Heading.level h with
364364+ | 6 -> (5, warn ~loc warn_heading_level_6 warns)
365365+ | level -> (level, warns)
366366+ in
367367+ let inline =
368368+ (* cmarkit claims it's already normalized but let's be defensive :-) *)
369369+ Inline.normalize (Block.Heading.inline h)
370370+ in
371371+ let inlines, warns =
372372+ inline_to_inline_elements ~locator defs ([], warns) inline
373373+ in
374374+ (Loc.at loc (`Heading (level, None, inlines)) :: bs, warns)
375375+376376+let paragraph_to_nestable_block_element ~locator defs p m (bs, warns) =
377377+ (* TODO Parse inlines for @tags support. *)
378378+ let loc = meta_to_loc ~locator m in
379379+ let i = Block.Paragraph.inline p in
380380+ let is, warns = inline_to_inline_elements ~locator defs ([], warns) i in
381381+ (Loc.at loc (`Paragraph is) :: bs, warns)
382382+383383+let thematic_break_to_nestable_block_element ~locator m (bs, warns) =
384384+ let loc = meta_to_loc ~locator m in
385385+ (raw_paragraph ~loc ~raw_loc:loc "html" "<hr>" :: bs, warns)
386386+387387+let rec list_to_nestable_block_element ~locator defs l m (bs, warns) =
388388+ let loc = meta_to_loc ~locator m in
389389+ let style = `Heavy (* Note this is a layout property of ocamldoc *) in
390390+ let kind, warns =
391391+ match Block.List'.type' l with
392392+ | `Unordered _ -> (`Unordered, warns)
393393+ | `Ordered (start, _) ->
394394+ ( `Ordered,
395395+ if start = 1 then warns
396396+ else warn ~loc (warn_unsupported_list_start_number start) warns )
397397+ in
398398+ let add_item ~locator (acc, warns) (i, _meta) =
399399+ let b = Block.List_item.block i in
400400+ let bs, warns =
401401+ block_to_nestable_block_elements ~locator defs ([], warns) b
402402+ in
403403+ (bs :: acc, warns)
404404+ in
405405+ let ritems = List.rev (Block.List'.items l) in
406406+ let items, warns = List.fold_left (add_item ~locator) ([], warns) ritems in
407407+ (Loc.at loc (`List (kind, style, items)) :: bs, warns)
408408+409409+and block_to_nestable_block_elements ~locator defs acc b : nestable_ast_acc =
410410+ match b with
411411+ | Block.Blocks (bs, _) ->
412412+ let block = block_to_nestable_block_elements ~locator defs in
413413+ List.fold_left block acc (List.rev bs)
414414+ | Block.Code_block (c, m) ->
415415+ code_block_to_nestable_block_element ~locator c m acc
416416+ | Block.Heading (_, m) -> warn_unsupported_header_nesting ~locator m acc
417417+ | Block.Html_block (html, m) ->
418418+ html_block_to_nestable_block_element ~locator html m acc
419419+ | Block.List (l, m) -> list_to_nestable_block_element ~locator defs l m acc
420420+ | Block.Paragraph (p, m) ->
421421+ paragraph_to_nestable_block_element ~locator defs p m acc
422422+ | Block.Block_quote (_, m) ->
423423+ warn_unsupported_cmark ~locator "Block quotes" m acc
424424+ | Block.Thematic_break (_, m) ->
425425+ thematic_break_to_nestable_block_element ~locator m acc
426426+ | Block.Blank_line _ | Block.Link_reference_definition _ ->
427427+ (* layout cases *) acc
428428+ | _ -> assert false
429429+430430+let rec block_to_ast ~locator defs acc b : ast_acc =
431431+ match b with
432432+ | Block.Heading (h, m) -> heading_to_block_element ~locator defs h m acc
433433+ | Block.Blocks (bs, _) ->
434434+ List.fold_left (block_to_ast ~locator defs) acc (List.rev bs)
435435+ | b ->
436436+ (* We can't go directy with acc because of nestable typing. *)
437437+ let bs, ws = acc in
438438+ let bs', ws = block_to_nestable_block_elements ~locator defs ([], ws) b in
439439+ (List.rev_append (List.rev (bs' :> Ast.t)) bs, ws)
440440+441441+(* Parsing comments *)
442442+443443+let parse_comment ?buffer:b ~location ~text:s () : Ast.t * Warning.t list =
444444+ let b =
445445+ match b with
446446+ | None -> Buffer.create (String.length s)
447447+ | Some b ->
448448+ Buffer.reset b;
449449+ b
450450+ in
451451+ let locator, text = massage_comment ~location b s in
452452+ let warns = ref [] and file = location.Lexing.pos_fname in
453453+ let doc = Doc.of_string ~file ~locs:true ~strict:true text in
454454+ block_to_ast ~locator (Doc.defs doc) ([], !warns) (Doc.block doc)
+12
src/markdown/doc_of_md.mli
···11+(** [ocamlmark] support. *)
22+33+(** {1:parsing ocamlmark parsing} *)
44+55+val parse_comment :
66+ ?buffer:Buffer.t ->
77+ location:Lexing.position ->
88+ text:string ->
99+ unit ->
1010+ Odoc_parser.Ast.t * Odoc_parser.Warning.t list
1111+(** [parse_comment ~location ~text] parses the ocamlmark [text] assuming it
1212+ corresponds to [location]. [buffer] is used as a scratch buffer. *)
···11+(* This exe will compile a markdown file, outputting a compiled `page-x.odoc` file.
22+ This is tightly coupled with the internal representation of odoc files and thus needs
33+ to be run with the exact same version of odoc that it is compiled with. *)
44+55+open Odoc_model
66+77+let parse id input_s =
88+ let location =
99+ Lexing.{ pos_fname = input_s; pos_lnum = 1; pos_cnum = 0; pos_bol = 0 }
1010+ in
1111+ let str = In_channel.(with_open_bin input_s input_all) in
1212+ let content, _warnings = Doc_of_md.parse_comment ~location ~text:str () in
1313+ let (content, ()) = Semantics.ast_to_comment ~internal_tags:Expect_none
1414+ ~sections_allowed:`All ~tags_allowed:true
1515+ ~parent_of_sections:(id :> Paths.Identifier.LabelParent.t) content []
1616+ |> Error.raise_warnings in
1717+ content
1818+1919+let mk_page input_s id content =
2020+ (* Construct the output file representation *)
2121+ let zero_heading = Comment.find_zero_heading content in
2222+ let frontmatter, content = Comment.extract_frontmatter content in
2323+ let digest = Digest.file input_s in
2424+ let root =
2525+ let file =
2626+ Root.Odoc_file.create_page input_s zero_heading frontmatter
2727+ in
2828+ { Root.id = (id :> Paths.Identifier.OdocId.t); file; digest }
2929+ in
3030+ let children=[] in
3131+ { Lang.Page.name=id; root; children; content; digest; linked = false; frontmatter }
3232+3333+let run input_s parent_id_str odoc_dir =
3434+ (* Construct the id of this page *)
3535+ let page_name =
3636+ Filename.basename input_s |> Filename.chop_extension
3737+ in
3838+ let parent_id = Odoc_odoc.Compile.mk_id parent_id_str in
3939+ let id = Odoc_model.Paths.Identifier.Mk.leaf_page (parent_id, Odoc_model.Names.PageName.make_std page_name) in
4040+4141+ let content = parse id input_s in
4242+ let page = mk_page input_s id content in
4343+4444+ let output = Fpath.(v odoc_dir // v parent_id_str / ("page-" ^ page_name ^ ".odoc")) in
4545+ Odoc_odoc.Odoc_file.save_page output ~warnings:[] page
4646+4747+open Cmdliner
4848+4949+let input =
5050+ let doc = "Input markdown file." in
5151+ Arg.(required & pos 0 (some file) None & info ~doc ~docv:"FILE" [])
5252+5353+let parent_id =
5454+ let doc = "Parent id. This defines both the location of the resulting odoc file as well as the \
5555+ location of the eventual html or other file." in
5656+ Arg.(
5757+ required
5858+ & opt (some string) None
5959+ & info ~docv:"PARENT" ~doc [ "parent-id" ])
6060+6161+let output_dir =
6262+ let doc = "Output file directory. The output file will be put in the parent-id path below this." in
6363+ Arg.(
6464+ required
6565+ & opt (some string) None
6666+ & info ~docv:"PATH" ~doc [ "output-dir" ])
6767+6868+let cmd =
6969+ let doc = "Compile a markdown file to an odoc page-*.odoc file." in
7070+ let info = Cmd.info "odoc-md" ~doc in
7171+ Cmd.v info
7272+ Term.(const run $ input $ parent_id $ output_dir)
7373+7474+let () = Cmdliner.(exit @@ Cmd.eval cmd)
+6-5
src/odoc/compile.ml
···244244 Ok (Paths.Identifier.Mk.page (parent_id, page_name))
245245 | None -> Ok (Paths.Identifier.Mk.page (parent_id, page_name)))
246246 >>= fun id -> Ok (id :> Paths.Identifier.Page.t))
247247- >>= fun name ->
247247+ >>= fun id ->
248248 let resolve content =
249249 let zero_heading = Comment.find_zero_heading content in
250250 let frontmatter, content = Comment.extract_frontmatter content in
251251- if (not (is_index_page name)) && has_children_order frontmatter then
251251+ if (not (is_index_page id)) && has_children_order frontmatter then
252252 Error.raise_warning
253253 (Error.filename_only
254254 "Non-index page cannot specify (children _) in the frontmatter."
···257257 let file =
258258 Root.Odoc_file.create_page root_name zero_heading frontmatter
259259 in
260260- { Root.id = (name :> Paths.Identifier.OdocId.t); file; digest }
260260+ { Root.id = (id :> Paths.Identifier.OdocId.t); file; digest }
261261 in
262262 let page =
263263 Lang.Page.
264264- { name; root; children; content; digest; linked = false; frontmatter }
264264+ { name=id; root; children; content; digest; linked = false; frontmatter }
265265 in
266266 Odoc_file.save_page output ~warnings:[] page;
267267 ()
···270270 Error.handle_errors_and_warnings ~warnings_options
271271 @@ Error.catch_errors_and_warnings
272272 @@ fun () ->
273273- Odoc_loader.read_string (name :> Paths.Identifier.LabelParent.t) input_s str
273273+ Odoc_loader.read_string (id :> Paths.Identifier.LabelParent.t) input_s str
274274 |> Error.raise_errors_and_warnings
275275 |> function
276276 | `Stop -> resolve [] (* TODO: Error? *)
277277 | `Docs content -> resolve content
278278+278279279280let handle_file_ext ext =
280281 match ext with