diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..aedf46d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,21 @@ +name: md2mld CI +on: + - push + - pull_request +jobs: + run: + name: Tests + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + # no special functionality to test in macos-latest or windows-latest + operating-system: [ubuntu-latest] + ocaml-version: [ '4.11.1', '4.10.1', '4.09.1', '4.08.1', '4.07.1', '4.06.1', '4.05.0', '4.04.2', '4.03.0', '4.02.3' ] + steps: + - uses: actions/checkout@master + - uses: avsm/setup-ocaml@v1.0 + with: + ocaml-version: ${{ matrix.ocaml-version }} + - run: opam pin add md2mld.dev . --no-action + - run: opam install md2mld --yes --deps-only + - run: opam install -t md2mld \ No newline at end of file diff --git a/.ocamlformat b/.ocamlformat new file mode 100644 index 0000000..6f93117 --- /dev/null +++ b/.ocamlformat @@ -0,0 +1,3 @@ +profile=janestreet +wrap-comments=false +let-binding-spacing=sparse diff --git a/docs/dune b/docs/dune index d2badc8..95ee8f8 100644 --- a/docs/dune +++ b/docs/dune @@ -4,11 +4,7 @@ (action (with-stdout-to %{targets} - (run %{exe:../src/md2mld.exe} -min-header 3 %{deps}) - ) - ) -) + (run %{exe:../src/md2mld.exe} -min-header 3 %{deps})))) (documentation - (mld_files index) -) + (mld_files index)) diff --git a/dune-project b/dune-project index f933729..fd78c5e 100644 --- a/dune-project +++ b/dune-project @@ -1 +1,2 @@ -(lang dune 1.4) +(lang dune 2.0) +(name md2mld) diff --git a/md2mld.opam b/md2mld.opam index 765c75f..603f303 100644 --- a/md2mld.opam +++ b/md2mld.opam @@ -9,9 +9,9 @@ doc: "https://mseri.github.io/md2mld/" bug-reports: "https://github.com/mseri/md2mld/issues" depends: [ "ocaml" - "dune" {build & >= "1.4.0"} + "dune" {>= "2.0"} "base-bytes" "omd" ] -build: ["dune" "build" "-p" name "-j" jobs] +build: ["dune" "build" "-p" name "-j" jobs "@install" "@runtest" {with-test}] dev-repo: "git+https://github.com/mseri/md2mld.git" diff --git a/src/backend.ml b/src/backend.ml index 11e0337..796f844 100644 --- a/src/backend.ml +++ b/src/backend.ml @@ -15,19 +15,21 @@ let string_of_attrs attrs = List.iter (function | a, Some v -> - if not (String.contains v '\'') then Printf.bprintf b " %s='%s'" a v - else if not (String.contains v '"') then - Printf.bprintf b " %s=\"%s\"" a v - else Printf.bprintf b " %s=\"%s\"" a v + if not (String.contains v '\'') + then Printf.bprintf b " %s='%s'" a v + else if not (String.contains v '"') + then Printf.bprintf b " %s=\"%s\"" a v + else Printf.bprintf b " %s=\"%s\"" a v | a, None -> - (* if html4 then *) - (* Printf.bprintf b " %s='%s'" a a *) - (* else *) - Printf.bprintf b " %s=''" a - (* HTML5 *)) - attrs ; + (* if html4 then *) + (* Printf.bprintf b " %s='%s'" a a *) + (* else *) + Printf.bprintf b " %s=''" a + (* HTML5 *)) + attrs; Buffer.contents b + let filter_text_omd_rev l = let rec loop b r = function | [] -> if b then r else l @@ -36,24 +38,27 @@ let filter_text_omd_rev l = in loop false [] l + let rec mld_of_md ~min_header md = let quote ?(indent = 0) s = let b = Buffer.create (String.length s) in let l = String.length s in let rec loop nl i = - if i < l then ( - if nl && i < l - 1 then ( + if i < l + then ( + if nl && i < l - 1 + then ( for _ = 1 to indent do Buffer.add_char b ' ' - done ; - Buffer.add_string b "> " ) ; + done; + Buffer.add_string b "> "); match s.[i] with | '\n' -> - Buffer.add_char b '\n' ; - loop true (succ i) + Buffer.add_char b '\n'; + loop true (succ i) | c -> - Buffer.add_char b c ; - loop false (succ i) ) + Buffer.add_char b c; + loop false (succ i)) else Buffer.contents b in loop true 0 @@ -70,229 +75,228 @@ let rec mld_of_md ~min_header md = (* [list_indent: int] is the indentation level in number of spaces. *) (* [is_in_list: bool] is necessary to know if we are inside a paragraph which is inside a list item because those need to be indented! *) - let loop ?(fst_p_in_li = fst_p_in_li) ?(is_in_list = is_in_list) - list_indent l = + let loop ?(fst_p_in_li = fst_p_in_li) ?(is_in_list = is_in_list) list_indent l = loop ~fst_p_in_li ~is_in_list list_indent l in match l with | X x :: tl -> - ( match x#to_t md with - | Some t -> loop list_indent t - | None -> ( - match x#to_html ~indent:0 Omd_backend.html_of_md md with - | Some s -> Printf.bprintf b "{%%html: %s %%}\n" s - | None -> () ) ) ; - loop list_indent tl + (match x#to_t md with + | Some t -> loop list_indent t + | None -> + (match x#to_html ~indent:0 Omd_backend.html_of_md md with + | Some s -> Printf.bprintf b "{%%html: %s %%}\n" s + | None -> ())); + loop list_indent tl | Blockquote q :: tl -> - Buffer.add_string b - (quote ~indent:list_indent (mld_of_md ~min_header q)) ; - if tl <> [] then Buffer.add_string b "\n" ; - loop list_indent tl + Buffer.add_string b (quote ~indent:list_indent (mld_of_md ~min_header q)); + if tl <> [] then Buffer.add_string b "\n"; + loop list_indent tl (* TODO: we need to accumulate the references separately *) | Ref (rc, _name, _text, fallback) :: tl -> - if !references = None then references := Some rc ; - loop list_indent (Raw fallback#to_string :: tl) + if !references = None then references := Some rc; + loop list_indent (Raw fallback#to_string :: tl) | Img_ref (rc, _name, _alt, fallback) :: tl -> - if !references = None then references := Some rc ; - loop list_indent (Raw fallback#to_string :: tl) + if !references = None then references := Some rc; + loop list_indent (Raw fallback#to_string :: tl) | Paragraph [] :: tl -> loop list_indent tl | Paragraph md :: tl -> - if is_in_list then - if fst_p_in_li then add_spaces (list_indent - 2) - else add_spaces list_indent ; - loop ~fst_p_in_li:false list_indent md ; - Printf.bprintf b "\n\n" ; - loop ~fst_p_in_li:false list_indent tl + if is_in_list + then if fst_p_in_li then add_spaces (list_indent - 2) else add_spaces list_indent; + loop ~fst_p_in_li:false list_indent md; + Printf.bprintf b "\n\n"; + loop ~fst_p_in_li:false list_indent tl | Img (alt, src, title) :: tl -> - Printf.bprintf b "{%%html: %s%%}" src - (if alt = "" then "" else Printf.sprintf " alt=\"%s\"" alt) - title ; - loop list_indent tl + Printf.bprintf + b + "{%%html: %s%%}" + src + (if alt = "" then "" else Printf.sprintf " alt=\"%s\"" alt) + title; + loop list_indent tl | Text t :: tl -> - Printf.bprintf b "%s" (Omd_backend.escape_markdown_characters t) ; - loop list_indent tl + Printf.bprintf b "%s" (Omd_backend.escape_markdown_characters t); + loop list_indent tl | Emph md :: tl -> - Buffer.add_string b "{e " ; - loop list_indent md ; - Buffer.add_string b "}" ; - loop list_indent tl + Buffer.add_string b "{e "; + loop list_indent md; + Buffer.add_string b "}"; + loop list_indent tl | Bold md :: tl -> - Buffer.add_string b "{b " ; - loop list_indent md ; - Buffer.add_string b "}" ; - loop list_indent tl + Buffer.add_string b "{b "; + loop list_indent md; + Buffer.add_string b "}"; + loop list_indent tl | Ol l :: tl | Olp l :: tl -> - if Buffer.length b > 0 && Buffer.nth b (Buffer.length b - 1) <> '\n' - then Buffer.add_char b '\n' ; - add_spaces list_indent ; - Buffer.add_string b "{ol \n" ; - List.iter - (fun li -> - add_spaces list_indent ; - Buffer.add_string b "{+ " ; - loop ~is_in_list:true (list_indent + 4) li ; - Buffer.add_string b "}\n" ) - l ; - add_spaces list_indent ; - Buffer.add_string b "}" ; - if list_indent = 0 then Buffer.add_char b '\n' ; - loop list_indent tl + if Buffer.length b > 0 && Buffer.nth b (Buffer.length b - 1) <> '\n' + then Buffer.add_char b '\n'; + add_spaces list_indent; + Buffer.add_string b "{ol \n"; + List.iter + (fun li -> + add_spaces list_indent; + Buffer.add_string b "{+ "; + loop ~is_in_list:true (list_indent + 4) li; + Buffer.add_string b "}\n") + l; + add_spaces list_indent; + Buffer.add_string b "}"; + if list_indent = 0 then Buffer.add_char b '\n'; + loop list_indent tl | Ul l :: tl | Ulp l :: tl -> - if Buffer.length b > 0 && Buffer.nth b (Buffer.length b - 1) <> '\n' - then Buffer.add_char b '\n' ; - add_spaces list_indent ; - Buffer.add_string b "{ul \n" ; - List.iter - (fun li -> - add_spaces list_indent ; - Buffer.add_string b "{- " ; - loop ~is_in_list:true (list_indent + 4) li ; - Buffer.add_string b "}\n" ) - l ; - add_spaces list_indent ; - Buffer.add_string b "}" ; - if list_indent = 0 then Buffer.add_char b '\n' ; - loop list_indent tl + if Buffer.length b > 0 && Buffer.nth b (Buffer.length b - 1) <> '\n' + then Buffer.add_char b '\n'; + add_spaces list_indent; + Buffer.add_string b "{ul \n"; + List.iter + (fun li -> + add_spaces list_indent; + Buffer.add_string b "{- "; + loop ~is_in_list:true (list_indent + 4) li; + Buffer.add_string b "}\n") + l; + add_spaces list_indent; + Buffer.add_string b "}"; + if list_indent = 0 then Buffer.add_char b '\n'; + loop list_indent tl | Code (_lang, c) :: tl -> - Buffer.add_char b '[' ; - Printf.bprintf b "%s" c ; - Buffer.add_char b ']' ; - loop list_indent tl + Buffer.add_char b '['; + Printf.bprintf b "%s" c; + Buffer.add_char b ']'; + loop list_indent tl | Code_block (_lang, c) :: tl -> - Buffer.add_string b "{[\n" ; - Buffer.add_string b c ; - if not (Buffer.nth b (Buffer.length b - 1) = '\n') then - Buffer.add_char b '\n' ; - Buffer.add_string b "]}\n" ; - loop list_indent tl - | Br :: tl -> Buffer.add_string b "\n\n" ; loop list_indent tl + Buffer.add_string b "{[\n"; + Buffer.add_string b c; + if not (Buffer.nth b (Buffer.length b - 1) = '\n') then Buffer.add_char b '\n'; + Buffer.add_string b "]}\n"; + loop list_indent tl + | Br :: tl -> + Buffer.add_string b "\n\n"; + loop list_indent tl | Hr :: tl -> - Buffer.add_string b "{%html:
%}\n" ; - loop list_indent tl - | Raw s :: tl -> Buffer.add_string b s ; loop list_indent tl + Buffer.add_string b "{%html:
%}\n"; + loop list_indent tl + | Raw s :: tl -> + Buffer.add_string b s; + loop list_indent tl | Raw_block s :: tl -> - Buffer.add_char b '\n' ; - Buffer.add_string b s ; - Buffer.add_char b '\n' ; - loop list_indent tl - | Html (tagname, attrs, []) :: tl - when StringSet.mem tagname html_void_elements -> - Buffer.add_string b "{%html: " ; - Printf.bprintf b "<%s" tagname ; - Buffer.add_string b (string_of_attrs attrs) ; - Buffer.add_string b " />" ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_char b '\n'; + Buffer.add_string b s; + Buffer.add_char b '\n'; + loop list_indent tl + | Html (tagname, attrs, []) :: tl when StringSet.mem tagname html_void_elements -> + Buffer.add_string b "{%html: "; + Printf.bprintf b "<%s" tagname; + Buffer.add_string b (string_of_attrs attrs); + Buffer.add_string b " />"; + Buffer.add_string b "}\n"; + loop list_indent tl | Html (tagname, attrs, body) :: tl -> - Buffer.add_string b "{%html: " ; - let a = filter_text_omd_rev attrs in - Printf.bprintf b "<%s" tagname ; - Buffer.add_string b (string_of_attrs a) ; - Buffer.add_string b ">" ; - if a == attrs then loop list_indent body - else Buffer.add_string b (Omd_backend.html_of_md body) ; - Printf.bprintf b "" tagname ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_string b "{%html: "; + let a = filter_text_omd_rev attrs in + Printf.bprintf b "<%s" tagname; + Buffer.add_string b (string_of_attrs a); + Buffer.add_string b ">"; + if a == attrs + then loop list_indent body + else Buffer.add_string b (Omd_backend.html_of_md body); + Printf.bprintf b "" tagname; + Buffer.add_string b "}\n"; + loop list_indent tl | Html_block (tagname, attrs, body) :: tl -> - let needs_newlines = - match tl with - | NL :: Paragraph p :: _ | Paragraph p :: _ -> p <> [] - | ( H1 _ | H2 _ | H3 _ | H4 _ | H5 _ | H6 _ | Ul _ | Ol _ | Ulp _ - | Olp _ - | Code (_, _) - | Code_block (_, _) - | Text _ | Emph _ | Bold _ | Br | Hr - | Url (_, _, _) - | Ref (_, _, _, _) - | Img_ref (_, _, _, _) - | Html (_, _, _) - | Blockquote _ - | Img (_, _, _) ) - :: _ -> - true - | (Html_block (_, _, _) | Html_comment _ | Raw _ | Raw_block _) :: _ - -> - false - | X _ :: _ -> false - | NL :: _ -> false - | [] -> false - in - Buffer.add_string b "{%html: " ; - if body = [] && StringSet.mem tagname html_void_elements then ( - Printf.bprintf b "<%s" tagname ; - Buffer.add_string b (string_of_attrs attrs) ; - Buffer.add_string b " />" ; - Buffer.add_string b " %}" ; - if needs_newlines then Buffer.add_string b "\n\n" ; - loop list_indent tl ) - else - let a = filter_text_omd_rev attrs in - Printf.bprintf b "<%s" tagname ; - Buffer.add_string b (string_of_attrs a) ; - Buffer.add_string b ">" ; - if a == attrs then loop list_indent body - else Buffer.add_string b (Omd_backend.html_of_md body) ; - Printf.bprintf b "" tagname ; - Buffer.add_string b " %}" ; - if needs_newlines then Buffer.add_string b "\n\n" ; - loop list_indent tl + let needs_newlines = + match tl with + | NL :: Paragraph p :: _ | Paragraph p :: _ -> p <> [] + | ( H1 _ | H2 _ | H3 _ | H4 _ | H5 _ | H6 _ | Ul _ | Ol _ | Ulp _ | Olp _ + | Code (_, _) + | Code_block (_, _) + | Text _ | Emph _ | Bold _ | Br | Hr + | Url (_, _, _) + | Ref (_, _, _, _) + | Img_ref (_, _, _, _) + | Html (_, _, _) + | Blockquote _ + | Img (_, _, _) ) + :: _ -> true + | (Html_block (_, _, _) | Html_comment _ | Raw _ | Raw_block _) :: _ -> false + | X _ :: _ -> false + | NL :: _ -> false + | [] -> false + in + Buffer.add_string b "{%html: "; + if body = [] && StringSet.mem tagname html_void_elements + then ( + Printf.bprintf b "<%s" tagname; + Buffer.add_string b (string_of_attrs attrs); + Buffer.add_string b " />"; + Buffer.add_string b " %}"; + if needs_newlines then Buffer.add_string b "\n\n"; + loop list_indent tl) + else ( + let a = filter_text_omd_rev attrs in + Printf.bprintf b "<%s" tagname; + Buffer.add_string b (string_of_attrs a); + Buffer.add_string b ">"; + if a == attrs + then loop list_indent body + else Buffer.add_string b (Omd_backend.html_of_md body); + Printf.bprintf b "" tagname; + Buffer.add_string b " %}"; + if needs_newlines then Buffer.add_string b "\n\n"; + loop list_indent tl) | Html_comment s :: tl -> - Printf.bprintf b "{%%html: %s %%}" s ; - loop list_indent tl + Printf.bprintf b "{%%html: %s %%}" s; + loop list_indent tl | Url (href, s, _title) :: tl -> - Printf.bprintf b "{{: %s} %s}" href (mld_of_md ~min_header s) ; - loop list_indent tl + Printf.bprintf b "{{: %s} %s}" href (mld_of_md ~min_header s); + loop list_indent tl | H1 md :: tl -> - Buffer.add_string b (init_header 0) ; - loop list_indent md ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_string b (init_header 0); + loop list_indent md; + Buffer.add_string b "}\n"; + loop list_indent tl | H2 md :: tl -> - Buffer.add_string b (init_header 1) ; - loop list_indent md ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_string b (init_header 1); + loop list_indent md; + Buffer.add_string b "}\n"; + loop list_indent tl | H3 md :: tl -> - Buffer.add_string b (init_header 2) ; - loop list_indent md ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_string b (init_header 2); + loop list_indent md; + Buffer.add_string b "}\n"; + loop list_indent tl | H4 md :: tl -> - Buffer.add_string b (init_header 3) ; - loop list_indent md ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_string b (init_header 3); + loop list_indent md; + Buffer.add_string b "}\n"; + loop list_indent tl | H5 md :: tl -> - Buffer.add_string b (init_header 4) ; - loop list_indent md ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_string b (init_header 4); + loop list_indent md; + Buffer.add_string b "}\n"; + loop list_indent tl | H6 md :: tl -> - Buffer.add_string b (init_header 5) ; - loop list_indent md ; - Buffer.add_string b "}\n" ; - loop list_indent tl + Buffer.add_string b (init_header 5); + loop list_indent md; + Buffer.add_string b "}\n"; + loop list_indent tl | NL :: tl -> - if - Buffer.length b = 1 - || Buffer.length b > 1 - && not - ( Buffer.nth b (Buffer.length b - 1) = '\n' - && Buffer.nth b (Buffer.length b - 2) = '\n' ) - then Buffer.add_string b "\n" ; - loop list_indent tl + if Buffer.length b = 1 + || (Buffer.length b > 1 + && not + (Buffer.nth b (Buffer.length b - 1) = '\n' + && Buffer.nth b (Buffer.length b - 2) = '\n')) + then Buffer.add_string b "\n"; + loop list_indent tl | [] -> () in - loop 0 md ; - ( match !references with + loop 0 md; + (match !references with | None -> () | Some r -> - Buffer.add_char b '\n' ; - List.iter - (fun (name, (url, title)) -> - Printf.bprintf b "%s: {{: %s} %s}\n" name url - (if title = "" then url else title) ) - r#get_all ) ; + Buffer.add_char b '\n'; + List.iter + (fun (name, (url, title)) -> + Printf.bprintf b "%s: {{: %s} %s}\n" name url (if title = "" then url else title)) + r#get_all); let res = Buffer.contents b in res diff --git a/src/backend.mli b/src/backend.mli index ac66d50..051357d 100644 --- a/src/backend.mli +++ b/src/backend.mli @@ -5,9 +5,9 @@ (* http://www.isc.org/downloads/software-support-policy/isc-license/ *) (***********************************************************************) -val mld_of_md : min_header:int -> Omd_representation.t -> string (** [mld_of_md ~min_header md] returns a string containing the mld version of [md]. Note that [md] uses the {!Omd} internal representation of Markdown. The ~min_header parameters allows you to choose the value for the section heading associated to

, all the other values will be determined incrementally. *) +val mld_of_md : min_header:int -> Omd_representation.t -> string diff --git a/src/dune b/src/dune index 0dde737..f13a2ab 100644 --- a/src/dune +++ b/src/dune @@ -1,5 +1,4 @@ (executable (name md2mld) (public_name md2mld) - (libraries omd) -) + (libraries omd)) diff --git a/src/md2mld.ml b/src/md2mld.ml index afb3af2..9141a19 100644 --- a/src/md2mld.ml +++ b/src/md2mld.ml @@ -10,8 +10,14 @@ let readall filename = try let n = in_channel_length ic in let b = Bytes.create n in - really_input ic b 0 n ; close_in ic ; Bytes.to_string b - with exn -> close_in ic ; raise exn + really_input ic b 0 n; + close_in ic; + Bytes.to_string b + with + | exn -> + close_in ic; + raise exn + (* let writeall filename content = @@ -28,26 +34,27 @@ let _ = let speclist = [ ( "-min-header" , Arg.Set_int min_header - , "Minimal section header level. Defaults to 0." ) ] + , "Minimal section header level. Defaults to 0." ) + ] in let usage_msg = "Usage: md2mld [OPTIONS] FILENAME\n\ - md2mld converts a Markdown-format file into the mld format used by odoc \ - to render HTML documentation or OCaml libraries.\n\n\ + md2mld converts a Markdown-format file into the mld format used by odoc to render \ + HTML documentation or OCaml libraries.\n\n\ Options:" in - Arg.parse speclist - (fun filename -> input_files := filename :: !input_files) - usage_msg ; + Arg.parse speclist (fun filename -> input_files := filename :: !input_files) usage_msg; match !input_files with | [] | _ :: _ :: _ -> - Arg.usage speclist usage_msg ; - exit 1 - | [filename] when not (Sys.file_exists filename) -> - Printf.eprintf "error: file %s not found\n%!" filename ; - exit 2 - | [filename] -> - let min_header = !min_header in - readall filename |> Omd.of_string - |> Backend.mld_of_md ~min_header - |> String.trim |> print_endline + Arg.usage speclist usage_msg; + exit 1 + | [ filename ] when not (Sys.file_exists filename) -> + Printf.eprintf "error: file %s not found\n%!" filename; + exit 2 + | [ filename ] -> + let min_header = !min_header in + readall filename + |> Omd.of_string + |> Backend.mld_of_md ~min_header + |> String.trim + |> print_endline diff --git a/tests/dune b/tests/dune index fb8a683..5fa63f5 100644 --- a/tests/dune +++ b/tests/dune @@ -4,10 +4,7 @@ (action (with-stdout-to %{targets} - (run %{exe:../src/md2mld.exe} %{deps}) - ) - ) -) + (run %{exe:../src/md2mld.exe} %{deps})))) (rule (targets readme-min-header-3.output) @@ -15,21 +12,14 @@ (action (with-stdout-to %{targets} - (run %{exe:../src/md2mld.exe} -min-header 3 %{deps}) - ) - ) -) + (run %{exe:../src/md2mld.exe} -min-header 3 %{deps})))) -(alias - (name runtest) +(rule + (alias runtest) (action - (diff readme.expected readme.output) - ) -) + (diff readme.expected readme.output))) -(alias - (name runtest) +(rule + (alias runtest) (action - (diff readme-min-header-3.expected readme-min-header-3.output) - ) -) + (diff readme-min-header-3.expected readme-min-header-3.output)))