Merge pull request 'replace omd with cmarkit' (#167) from cmarkit into main
Reviewed-on: #167
This commit is contained in:
commit
d6f172b777
6 changed files with 68 additions and 124 deletions
|
@ -45,7 +45,7 @@ depends: [
|
||||||
"cmdliner" {>= "1.1.0"}
|
"cmdliner" {>= "1.1.0"}
|
||||||
"uri"
|
"uri"
|
||||||
"fmt" {>= "0.8.7"}
|
"fmt" {>= "0.8.7"}
|
||||||
"omd" {>= "2.0.0~alpha3"}
|
"cmarkit"
|
||||||
"tar"
|
"tar"
|
||||||
"owee"
|
"owee"
|
||||||
"solo5-elftool" {>= "0.3.0"}
|
"solo5-elftool" {>= "0.3.0"}
|
||||||
|
|
2
lib/dune
2
lib/dune
|
@ -11,7 +11,7 @@
|
||||||
caqti-lwt
|
caqti-lwt
|
||||||
opamdiff
|
opamdiff
|
||||||
ptime.clock.os
|
ptime.clock.os
|
||||||
omd
|
cmarkit
|
||||||
tar
|
tar
|
||||||
owee
|
owee
|
||||||
solo5-elftool
|
solo5-elftool
|
||||||
|
|
120
lib/utils.ml
120
lib/utils.ml
|
@ -45,109 +45,29 @@ let compare_pkgs p1 p2 =
|
||||||
in
|
in
|
||||||
diff_map (parse_pkgs p1) (parse_pkgs p2)
|
diff_map (parse_pkgs p1) (parse_pkgs p2)
|
||||||
|
|
||||||
module Omd = struct
|
let md_to_html ?adjust_heading ?(safe = true) data =
|
||||||
|
let open Cmarkit in
|
||||||
let make_safe omd =
|
let doc = Doc.of_string ~heading_auto_ids:true data in
|
||||||
let rec safe_block = function
|
let doc =
|
||||||
| Omd.Paragraph (attr, inline) ->
|
Option.fold ~none:doc
|
||||||
safe_inline inline
|
~some:(fun lvl ->
|
||||||
|> Option.map (fun inline -> Omd.Paragraph (attr, inline))
|
let block _m = function
|
||||||
| Omd.List (attr, typ, spacing, blocks) ->
|
| Block.Heading (h, meta) ->
|
||||||
let blocks = List.filter_map (fun b ->
|
let open Block.Heading in
|
||||||
let b = List.filter_map safe_block b in
|
let level = level h
|
||||||
if b = [] then None else Some b)
|
and id = id h
|
||||||
blocks
|
and layout = layout h
|
||||||
|
and inline = inline h
|
||||||
in
|
in
|
||||||
if blocks = [] then None else
|
let h' = make ?id ~layout ~level:(level + lvl) inline in
|
||||||
Some (Omd.List (attr, typ, spacing, blocks))
|
Mapper.ret (Block.Heading (h', meta))
|
||||||
| Omd.Blockquote (attr, blocks) ->
|
| _ -> Mapper.default
|
||||||
let blocks = List.filter_map safe_block blocks in
|
|
||||||
if blocks = [] then None else
|
|
||||||
Some (Omd.Blockquote (attr, blocks))
|
|
||||||
| Omd.Heading (attr, level, inline) ->
|
|
||||||
safe_inline inline
|
|
||||||
|> Option.map (fun inline -> Omd.Heading (attr, level, inline))
|
|
||||||
| Omd.Html_block _ -> None
|
|
||||||
| Omd.Definition_list (attr, def_elts) ->
|
|
||||||
let def_elts = List.filter_map safe_def_elts def_elts in
|
|
||||||
if def_elts = [] then None else
|
|
||||||
Some (Omd.Definition_list (attr, def_elts))
|
|
||||||
| Omd.Code_block _
|
|
||||||
| Omd.Thematic_break _ as v -> Some v
|
|
||||||
| Omd.Table (attr, header_row, rows) ->
|
|
||||||
let header_row =
|
|
||||||
List.fold_left (fun acc (cell, alignment) ->
|
|
||||||
match acc with
|
|
||||||
| None -> None
|
|
||||||
| Some xs ->
|
|
||||||
Option.map (fun cell -> xs @ [ cell, alignment ])
|
|
||||||
(safe_inline cell))
|
|
||||||
(Some []) header_row
|
|
||||||
in
|
in
|
||||||
Option.map
|
let mapper = Mapper.make ~block () in
|
||||||
(fun header_row ->
|
Mapper.map_doc mapper doc)
|
||||||
let rows =
|
adjust_heading
|
||||||
List.filter_map (fun row ->
|
|
||||||
List.fold_left (fun acc cell ->
|
|
||||||
match acc with
|
|
||||||
| None -> None
|
|
||||||
| Some xs -> Option.map (fun cell -> xs @ [ cell ])
|
|
||||||
(safe_inline cell))
|
|
||||||
(Some []) row)
|
|
||||||
rows
|
|
||||||
in
|
in
|
||||||
Omd.Table (attr, header_row, rows))
|
Cmarkit_html.of_doc ~safe doc
|
||||||
header_row
|
|
||||||
and safe_def_elts { term ; defs } =
|
|
||||||
let defs = List.filter_map safe_inline defs in
|
|
||||||
safe_inline term
|
|
||||||
|> Option.map (fun term -> { Omd.term ; defs })
|
|
||||||
and safe_inline = function
|
|
||||||
| Concat (attr, inline) ->
|
|
||||||
Some (Concat (attr, List.filter_map safe_inline inline))
|
|
||||||
| Emph (attr, inline) ->
|
|
||||||
safe_inline inline
|
|
||||||
|> Option.map (fun inline -> Omd.Emph (attr, inline))
|
|
||||||
| Strong (attr, inline) ->
|
|
||||||
safe_inline inline
|
|
||||||
|> Option.map (fun inline -> Omd.Strong (attr, inline))
|
|
||||||
| Link (attr, link) ->
|
|
||||||
begin match safe_link link with
|
|
||||||
| `No_label | `Relative -> safe_inline link.Omd.label
|
|
||||||
| `Link l -> Some (Omd.Link (attr, l))
|
|
||||||
end
|
|
||||||
| Image (attr, link) ->
|
|
||||||
begin match safe_link link with
|
|
||||||
| `No_label | `Relative -> None
|
|
||||||
| `Link l -> Some (Omd.Image (attr, l))
|
|
||||||
end
|
|
||||||
| Html _ -> None
|
|
||||||
| Text _
|
|
||||||
| Code _
|
|
||||||
| Hard_break _
|
|
||||||
| Soft_break _ as v -> Some v
|
|
||||||
and safe_link ({ label ; destination ; _ } as l) =
|
|
||||||
let absolute_link =
|
|
||||||
String.(length destination >= 2 && equal (sub destination 0 2) "//") ||
|
|
||||||
String.(length destination >= 7 && equal (sub destination 0 7) "http://") ||
|
|
||||||
String.(length destination >= 8 && equal (sub destination 0 8) "https://")
|
|
||||||
in
|
|
||||||
if absolute_link then
|
|
||||||
match safe_inline label with
|
|
||||||
| None -> `No_label
|
|
||||||
| Some label -> `Link { l with label }
|
|
||||||
else
|
|
||||||
`Relative
|
|
||||||
in
|
|
||||||
List.filter_map safe_block omd
|
|
||||||
|
|
||||||
let html_of_string markdown =
|
|
||||||
markdown
|
|
||||||
|> Omd.of_string
|
|
||||||
|> make_safe
|
|
||||||
|> Omd.to_html
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
module Path = struct
|
module Path = struct
|
||||||
|
|
||||||
|
|
|
@ -285,7 +285,7 @@ have questions or suggestions.
|
||||||
|
|
||||||
let make_header =
|
let make_header =
|
||||||
[
|
[
|
||||||
H.Unsafe.data (Utils.Omd.html_of_string data);
|
H.Unsafe.data (Utils.md_to_html data);
|
||||||
H.form ~a:H.[a_action "/hash"; a_method `Get] [
|
H.form ~a:H.[a_action "/hash"; a_method `Get] [
|
||||||
H.label [
|
H.label [
|
||||||
H.txt "Search artifact by SHA256";
|
H.txt "Search artifact by SHA256";
|
||||||
|
@ -383,7 +383,7 @@ module Job = struct
|
||||||
[
|
[
|
||||||
H.h2 ~a:H.[a_id "readme"] [H.txt "README"];
|
H.h2 ~a:H.[a_id "readme"] [H.txt "README"];
|
||||||
H.a ~a:H.[a_href "#builds"] [H.txt "Skip to builds"];
|
H.a ~a:H.[a_href "#builds"] [H.txt "Skip to builds"];
|
||||||
H.Unsafe.data (Utils.Omd.html_of_string data)
|
H.Unsafe.data (Utils.md_to_html ~adjust_heading:2 data)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
(test
|
(test
|
||||||
(name markdown_to_html)
|
(name markdown_to_html)
|
||||||
(modules markdown_to_html)
|
(modules markdown_to_html)
|
||||||
(libraries builder_web alcotest))
|
(libraries builder_web cmarkit alcotest))
|
||||||
|
|
||||||
(test
|
(test
|
||||||
(name router)
|
(name router)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
let markdown_to_html = Builder_web__Utils.Omd.html_of_string
|
let markdown_to_html = Builder_web__Utils.md_to_html
|
||||||
|
|
||||||
let test_simple () =
|
let test_simple () =
|
||||||
let markdown = {|# Hello world|} in
|
let markdown = {|# Hello world|} in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "simple html" "<h1 id=\"hello-world\">Hello world</h1>\n" html)
|
Alcotest.(check string "simple html" "<h1 id=\"hello-world\"><a class=\"anchor\" aria-hidden=\"true\" href=\"#hello-world\"></a>Hello world</h1>\n" html)
|
||||||
|
|
||||||
let test_html_script () =
|
let test_html_script () =
|
||||||
let markdown = {|# <script>Hello world</script>|} in
|
let markdown = {|# <script>Hello world</script>|} in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "html script header" "<h1 id=\"hello-world\">Hello world</h1>\n" html)
|
Alcotest.(check string "html script header" "<h1 id=\"hello-world\"><a class=\"anchor\" aria-hidden=\"true\" href=\"#hello-world\"></a><!-- CommonMark raw HTML omitted -->Hello world<!-- CommonMark raw HTML omitted --></h1>\n" html)
|
||||||
|
|
||||||
let test_preserve_span_content () =
|
let test_preserve_span_content () =
|
||||||
let markdown = {|* <span id="myref">My ref</span>
|
let markdown = {|* <span id="myref">My ref</span>
|
||||||
|
@ -16,10 +16,8 @@ let test_preserve_span_content () =
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "html span content preserved"
|
Alcotest.(check string "html span content preserved"
|
||||||
{|<ul>
|
{|<ul>
|
||||||
<li>My ref
|
<li><!-- CommonMark raw HTML omitted -->My ref<!-- CommonMark raw HTML omitted --></li>
|
||||||
</li>
|
<li><a href="#myref">See my ref</a> for more information</li>
|
||||||
<li>See my ref for more information
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|}
|
|}
|
||||||
html)
|
html)
|
||||||
|
@ -27,20 +25,21 @@ let test_preserve_span_content () =
|
||||||
let test_remove_script () =
|
let test_remove_script () =
|
||||||
let markdown = {|<script>alert(1);</script>|} in
|
let markdown = {|<script>alert(1);</script>|} in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "html script removed" "" html)
|
Alcotest.(check string "html script removed" "<!-- CommonMark HTML block omitted -->\n" html)
|
||||||
|
|
||||||
let test_list_with_html_block_and_markdown () =
|
let test_list_with_html_block_and_markdown () =
|
||||||
let markdown = "* <div> Hello, World!</div> *this is not html*" in
|
let markdown = "* <div> Hello, World!</div> *this is not html*" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "list with html block and markdown"
|
Alcotest.(check string "list with html block and markdown"
|
||||||
(*"<ul>\n<li><em>this is not html</em>\n</li>\n</ul>\n"*) ""
|
(*"<ul>\n<li><em>this is not html</em>\n</li>\n</ul>\n"*)
|
||||||
|
"<ul>\n<li>\n<!-- CommonMark HTML block omitted -->\n</li>\n</ul>\n"
|
||||||
html)
|
html)
|
||||||
|
|
||||||
let test_list_with_inline_html_and_markdown () =
|
let test_list_with_inline_html_and_markdown () =
|
||||||
let markdown = "* <span> Hello, World!</span> *this is not html*" in
|
let markdown = "* <span> Hello, World!</span> *this is not html*" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "list with html block and markdown"
|
Alcotest.(check string "list with html block and markdown"
|
||||||
"<ul>\n<li> Hello, World! <em>this is not html</em>\n</li>\n</ul>\n"
|
"<ul>\n<li><!-- CommonMark raw HTML omitted --> Hello, World!<!-- CommonMark raw HTML omitted --> <em>this is not html</em></li>\n</ul>\n"
|
||||||
html)
|
html)
|
||||||
|
|
||||||
let test_absolute_link () =
|
let test_absolute_link () =
|
||||||
|
@ -51,35 +50,59 @@ let test_absolute_link () =
|
||||||
let test_relative_link () =
|
let test_relative_link () =
|
||||||
let markdown = "[foo](../foo.jpg)" in
|
let markdown = "[foo](../foo.jpg)" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "relative link" "<p>foo</p>\n" html)
|
Alcotest.(check string "relative link" "<p><a href=\"../foo.jpg\">foo</a></p>\n" html)
|
||||||
|
|
||||||
let test_absolute_image () =
|
let test_absolute_image () =
|
||||||
let markdown = "![alttext](https://foo.com/bar.jpg)" in
|
let markdown = "![alttext](https://foo.com/bar.jpg)" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "absolute image"
|
Alcotest.(check string "absolute image"
|
||||||
"<p><img src=\"https://foo.com/bar.jpg\" alt=\"alttext\" /></p>\n" html)
|
"<p><img src=\"https://foo.com/bar.jpg\" alt=\"alttext\" ></p>\n" html)
|
||||||
|
|
||||||
let test_absolute_image_no_alt () =
|
let test_absolute_image_no_alt () =
|
||||||
let markdown = "![](https://foo.com/bar.jpg)" in
|
let markdown = "![](https://foo.com/bar.jpg)" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "absolute image"
|
Alcotest.(check string "absolute image"
|
||||||
"<p><img src=\"https://foo.com/bar.jpg\" alt=\"\" /></p>\n" html)
|
"<p><img src=\"https://foo.com/bar.jpg\" alt=\"\" ></p>\n" html)
|
||||||
|
|
||||||
let test_relative_image () =
|
let test_relative_image () =
|
||||||
let markdown = "![](/bar.jpg)" in
|
let markdown = "![](/bar.jpg)" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "relative image" "" html)
|
Alcotest.(check string "relative image" "<p><img src=\"/bar.jpg\" alt=\"\" ></p>\n" html)
|
||||||
|
|
||||||
let test_absolute_image_script_alt () =
|
let test_absolute_image_script_alt () =
|
||||||
let markdown = "![<script src=\"bla.js\"></script>](https://foo.com/bar.jpg)" in
|
let markdown = "![<script src=\"bla.js\"></script>](https://foo.com/bar.jpg)" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "absolute image with script alt text"
|
Alcotest.(check string "absolute image with script alt text"
|
||||||
"<p><img src=\"https://foo.com/bar.jpg\" alt=\"\" /></p>\n" html)
|
"<p><img src=\"https://foo.com/bar.jpg\" alt=\"\" ></p>\n" html)
|
||||||
|
|
||||||
let test_fragment_link () =
|
let test_fragment_link () =
|
||||||
let markdown = "[fragment](#fragment)" in
|
let markdown = "[fragment](#fragment)" in
|
||||||
let html = markdown_to_html markdown in
|
let html = markdown_to_html markdown in
|
||||||
Alcotest.(check string "fragment link" "<p>fragment</p>\n" html)
|
Alcotest.(check string "fragment link" "<p><a href=\"#fragment\">fragment</a></p>\n" html)
|
||||||
|
|
||||||
|
let test_heading_adjustment () =
|
||||||
|
let markdown = {|# foo
|
||||||
|
## bar
|
||||||
|
# baz
|
||||||
|
## bazbar
|
||||||
|
### bazbarbar
|
||||||
|
#### bazbarbarbar
|
||||||
|
##### bazbarbarbarbar
|
||||||
|
###### bazbarbarbarbarbar
|
||||||
|
|}
|
||||||
|
in
|
||||||
|
let html = markdown_to_html ~adjust_heading:2 markdown in
|
||||||
|
(* NB: the maximum heading is 6 in cmarkit, thus we reduce the structure *)
|
||||||
|
let exp = {|<h3 id="foo"><a class="anchor" aria-hidden="true" href="#foo"></a>foo</h3>
|
||||||
|
<h4 id="bar"><a class="anchor" aria-hidden="true" href="#bar"></a>bar</h4>
|
||||||
|
<h3 id="baz"><a class="anchor" aria-hidden="true" href="#baz"></a>baz</h3>
|
||||||
|
<h4 id="bazbar"><a class="anchor" aria-hidden="true" href="#bazbar"></a>bazbar</h4>
|
||||||
|
<h5 id="bazbarbar"><a class="anchor" aria-hidden="true" href="#bazbarbar"></a>bazbarbar</h5>
|
||||||
|
<h6 id="bazbarbarbar"><a class="anchor" aria-hidden="true" href="#bazbarbarbar"></a>bazbarbarbar</h6>
|
||||||
|
<h6 id="bazbarbarbarbar"><a class="anchor" aria-hidden="true" href="#bazbarbarbarbar"></a>bazbarbarbarbar</h6>
|
||||||
|
<h6 id="bazbarbarbarbarbar"><a class="anchor" aria-hidden="true" href="#bazbarbarbarbarbar"></a>bazbarbarbarbarbar</h6>
|
||||||
|
|} in
|
||||||
|
Alcotest.(check string "header adjustment works fine" exp html)
|
||||||
|
|
||||||
let markdown_tests = [
|
let markdown_tests = [
|
||||||
Alcotest.test_case "Simple" `Quick test_simple;
|
Alcotest.test_case "Simple" `Quick test_simple;
|
||||||
|
@ -95,6 +118,7 @@ let markdown_tests = [
|
||||||
Alcotest.test_case "relative image" `Quick test_relative_image;
|
Alcotest.test_case "relative image" `Quick test_relative_image;
|
||||||
Alcotest.test_case "absolute image with script alt" `Quick test_absolute_image_script_alt;
|
Alcotest.test_case "absolute image with script alt" `Quick test_absolute_image_script_alt;
|
||||||
Alcotest.test_case "fragment link" `Quick test_fragment_link;
|
Alcotest.test_case "fragment link" `Quick test_fragment_link;
|
||||||
|
Alcotest.test_case "heading adjustment" `Quick test_heading_adjustment;
|
||||||
]
|
]
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
|
|
Loading…
Reference in a new issue