package mdx
Executable code blocks inside markdown files
Install
Dune Dependency
Authors
Maintainers
Sources
mdx-2.2.1.tbz
sha256=f09ed733fe4461605f029773748a6353d01b95be65eb986bcce17d6ddaf509d8
sha512=5f918fbafcb55c3959f6cce69b8d8e640883dea5c21d159b5ebb6ead31230569100813555d2637f74028d51f79f23a226dc69811524d9cf1c2ca50b87a93dbf2
doc/src/mdx/part.ml.html
Source file part.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
(* * Copyright (c) 2018 Thomas Gazagnaire <thomas@gazagnaire.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) module Part = struct type t = { name : string; sep_indent : string; (** Whitespaces before the [@@@part] separator *) body : string; } let v ~name ~sep_indent ~body = { name; sep_indent; body } let name { name; _ } = name let sep_indent { sep_indent; _ } = sep_indent let body { body; _ } = body end (** Remove empty strings at the beginning of a list *) let rec remove_empty_heads = function | "" :: tl -> remove_empty_heads tl | l -> l let trim_empty_rev l = remove_empty_heads (List.rev (remove_empty_heads l)) module Parse_parts = struct type part_meta = { sep_indent : string; name : string } type t = | Content of string | Compat_attr of part_meta (* ^^^^ This is for compat with the [[@@@part name]] delimiters *) | Part_begin of part_meta | Part_end module Regexp = struct let marker = Re.str "$MDX" let spaces = Re.rep1 Re.space let id = Re.(rep1 (alt [ alnum; char '_'; char '-'; char '=' ])) let ws = Re.(rep space) let cmt = let open Re in compile @@ seq [ group (non_greedy (rep any)); group ws; str "(*"; spaces; marker; spaces; group id; spaces; str "*)"; ] let attribute = let open Re in compile @@ whole_string @@ seq [ group ws; str "[@@@"; ws; group id; ws; str "\""; group id; str "\""; ws; str "]"; ws; opt (str ";;"); ws; ] end let parse_attr line = match Re.exec_opt Regexp.attribute line with | Some g -> ( let sep_indent = Re.Group.get g 1 in let name = Re.Group.get g 2 in let payload = Re.Group.get g 3 in match name with | "part" -> [ Compat_attr { sep_indent; name = payload } ] | _ -> []) | None -> [] let parse_cmt line = match Re.exec_opt Regexp.cmt line with | Some g -> ( let sep_indent = Re.Group.get g 2 in match Re.Group.get g 3 with | "part-end" -> let entries = match Re.Group.get g 1 with | "" -> [ Part_end ] | s -> [ Content s; Part_end ] in Ok entries | s -> ( match Astring.String.cut ~sep:"=" s with | Some ("part-begin", name) -> Ok [ Part_begin { sep_indent; name } ] | Some ("part-end", _) -> Util.Result.errorf "'part-end' delimiter does not accept a value. Please write \ '(* $MDX part-end *)' instead." | _ -> Util.Result.errorf "'%s' is not a valid ocaml delimiter for mdx." line)) | None -> Ok [] let parse line = match parse_attr line with | [] -> ( let open Util.Result.Infix in let* delimiters = parse_cmt line in match delimiters with | [] -> Ok [ Content line ] | delimiters -> Ok delimiters) | delimiters -> Ok delimiters let next_part { name; sep_indent } ~is_begin_end_part lines_rev = let body = if is_begin_end_part then String.concat "\n" (List.rev lines_rev) else "\n" ^ String.concat "\n" (trim_empty_rev lines_rev) in Part.v ~name ~sep_indent ~body let anonymous_part = next_part { name = ""; sep_indent = "" } let parse_line line = match parse line with | Ok content -> content | Error (`Msg msg) -> Fmt.epr "Warning: %s\n" msg; [ Content line ] let parsed_input_line i = match input_line i with | exception End_of_file -> None | line -> Some (parse_line line) let parsed_seq i = let rec loop seq = match parsed_input_line i with | None -> seq | Some inputs -> let inputs = List.to_seq inputs in let tail = loop seq in Util.Seq.append inputs tail in loop Seq.empty let parse_parts input = let open Util.Result.Infix in let* parts, make_part, current_part, part_lines, lineno = Seq.fold_left (fun acc parse_part -> let* parts, make_part, current_part, part_lines, lineno = acc in let lineno = lineno + 1 in match (parse_part, current_part) with | Content line, _ -> Ok (parts, make_part, current_part, line :: part_lines, lineno) | Part_end, Some _ -> let part = make_part ~is_begin_end_part:true part_lines in Ok (part :: parts, anonymous_part, None, [], lineno) | Part_end, None -> Error ("There is no part to end.", lineno) | Part_begin meta, None -> let named_part = next_part meta in let parts = match part_lines with | [] -> (* Ignore empty anonymous parts: needed for legacy support *) parts | _ -> let part = make_part ~is_begin_end_part:true part_lines in part :: parts in Ok (parts, named_part, Some meta.name, [], lineno) | Compat_attr meta, None -> let named_part = next_part meta in let part = make_part ~is_begin_end_part:false part_lines in Ok (part :: parts, named_part, None, [], lineno) | Part_begin _, Some p | Compat_attr _, Some p -> let msg = Printf.sprintf "Part %s has no end." p in Error (msg, lineno)) (Ok ([], anonymous_part, None, [], 0)) input in let* part = match current_part with | Some part -> let msg = Printf.sprintf "File ended before part %s ended." part in Error (msg, lineno + 1) | None -> Ok (make_part ~is_begin_end_part:true part_lines) in part :: parts |> List.rev |> Result.ok let of_file name = let channel = open_in name in let input = parsed_seq channel in match parse_parts input with | Ok parts -> parts | Error (msg, line) -> Fmt.failwith "In file %s, line %d: %s" name line msg end type file = Part.t list let read file = Parse_parts.of_file file let find file ~part = match part with | Some part -> ( match List.find_opt (fun p -> String.equal (Part.name p) part) file with | Some p -> Some [ Part.body p ] | None -> None) | None -> List.fold_left (fun acc p -> Part.body p :: acc) [] file |> List.rev |> fun x -> Some x let rec replace_or_append part_name body = function | p :: tl when String.equal (Part.name p) part_name -> { p with body } :: tl | p :: tl -> p :: replace_or_append part_name body tl | [] -> [ { name = part_name; sep_indent = ""; body } ] let replace file ~part ~lines = let part = match part with None -> "" | Some p -> p in replace_or_append part (String.concat "\n" lines) file let contents file = let lines = List.fold_left (fun acc p -> let body = Part.body p in match Part.name p with | "" -> body :: acc | n -> let indent = Part.sep_indent p in body :: ("\n" ^ indent ^ "[@@@part \"" ^ n ^ "\"] ;;\n") :: acc) [] file in let lines = List.rev lines in let lines = String.concat "\n" lines in String.trim lines ^ "\n" module Internal = struct module Parse_parts = Parse_parts end
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>