[2] | 1 | module String_map = Map.Make (String)
|
---|
| 2 | type t = {
|
---|
[3] | 3 | id: Id.t;
|
---|
| 4 | title: string;
|
---|
| 5 | authors: Person.Set.t;
|
---|
| 6 | date: Date.t;
|
---|
| 7 | string_map: string String_map.t;
|
---|
| 8 | stringset_map: String_set.t String_map.t;
|
---|
| 9 | body: string;
|
---|
| 10 | }
|
---|
[2] | 11 |
|
---|
[3] | 12 | let blank ?(id=(Id.generate ())) () = {
|
---|
| 13 | id;
|
---|
| 14 | title = "";
|
---|
| 15 | authors = Person.Set.empty;
|
---|
| 16 | date = Date.({ created = now (); edited = ""});
|
---|
| 17 | string_map = String_map.empty;
|
---|
| 18 | stringset_map = String_map.empty;
|
---|
| 19 | body = "";
|
---|
| 20 | }
|
---|
[2] | 21 |
|
---|
| 22 | let compare = Stdlib.compare
|
---|
| 23 | let newest a b = Date.(compare a.date b.date)
|
---|
| 24 | let oldest a b = Date.(compare b.date a.date)
|
---|
| 25 | let str key m = try String_map.find (String.lowercase_ascii key) m.string_map with Not_found -> ""
|
---|
| 26 | let set key m = try String_map.find (String.lowercase_ascii key) m.stringset_map with Not_found -> String_set.empty
|
---|
| 27 | let str_set key m = String_set.to_string @@ set key m
|
---|
[39] | 28 | let with_str_set ?(separator=String_set.of_csv_string) m key str = { m with
|
---|
| 29 | stringset_map = String_map.add (String.lowercase_ascii key) (separator str) m.stringset_map
|
---|
[38] | 30 | }
|
---|
[2] | 31 |
|
---|
| 32 | let with_kv x (k,v) =
|
---|
[3] | 33 | let trim = String.trim in
|
---|
| 34 | match String.lowercase_ascii k with
|
---|
| 35 | | "body" -> { x with body = String.trim v }
|
---|
| 36 | | "title"-> { x with title = trim v }
|
---|
| 37 | | "id" -> (match v with "" -> x | s -> { x with id = s })
|
---|
| 38 | | "author"
|
---|
| 39 | | "authors" -> { x with authors = Person.Set.of_string (trim v)}
|
---|
| 40 | | "date" -> { x with date = Date.{ x.date with created = Date.of_string v }}
|
---|
| 41 | | "date-edited"-> { x with date = Date.{ x.date with edited = Date.of_string v }}
|
---|
[39] | 42 | | "licences" | "topics" | "keywords" | "series" as k -> with_str_set x k v
|
---|
| 43 | | "references" -> with_str_set
|
---|
| 44 | ~separator:(fun x -> String_set.map (fun x -> String.(sub x 1 (length x-2)))
|
---|
| 45 | (String_set.of_ssv_string x))
|
---|
| 46 | x k v
|
---|
[3] | 47 | | k -> { x with string_map = String_map.add k (trim v) x.string_map }
|
---|
[2] | 48 |
|
---|
[3] | 49 | let kv_of_string line = match Str.(bounded_split (regexp ": *")) line 2 with
|
---|
| 50 | | [ key; value ] -> Str.(replace_first (regexp "^#\\+") "" key), value
|
---|
| 51 | | [ key ] -> Str.(replace_first (regexp "^#\\+") "" key), ""
|
---|
| 52 | | _ -> "",""
|
---|
[2] | 53 |
|
---|
| 54 | let of_header front_matter =
|
---|
[3] | 55 | let fields = List.map kv_of_string (Str.(split (regexp "\n")) front_matter) in
|
---|
| 56 | List.fold_left with_kv (blank ~id:Id.nil ()) fields
|
---|
[2] | 57 |
|
---|
| 58 | let front_matter_body_split s =
|
---|
[3] | 59 | if Str.(string_match (regexp ".*:.*")) s 0
|
---|
| 60 | then match Str.(bounded_split (regexp "^$")) s 2 with
|
---|
| 61 | | front::body::[] -> (front, body)
|
---|
| 62 | | _ -> ("", s)
|
---|
| 63 | else ("", s)
|
---|
[2] | 64 |
|
---|
| 65 | let of_string s =
|
---|
[3] | 66 | let front_matter, body = front_matter_body_split s in
|
---|
| 67 | try
|
---|
| 68 | let note = { (of_header front_matter) with body } in
|
---|
| 69 | if note.id <> Id.nil then Ok note else Error "Missing ID header"
|
---|
| 70 | with _ -> Error ("Failed parsing" ^ s)
|
---|
[2] | 71 |
|
---|
| 72 | let to_string x =
|
---|
[3] | 73 | let has_len v = String.length v > 0 in
|
---|
| 74 | let s field value = if has_len value then field ^ ": " ^ value ^ "\n" else "" in
|
---|
| 75 | let a value = if Person.Set.is_empty value then "" else "Authors: " ^ Person.Set.to_string value ^ "\n" in
|
---|
| 76 | let d field value = match value with "" -> "" | s -> field ^ ": " ^ Date.rfc_string s ^ "\n" in
|
---|
| 77 | let rows = [
|
---|
| 78 | s "ID" x.id;
|
---|
| 79 | d "Date" x.date.Date.created;
|
---|
| 80 | d "Edited" x.date.Date.edited;
|
---|
| 81 | s "Title" x.title;
|
---|
| 82 | a x.authors;
|
---|
| 83 | s "Licences" (str_set "licences" x);
|
---|
| 84 | s "Topics" (str_set "topics" x);
|
---|
| 85 | s "Keywords" (str_set "keywords" x);
|
---|
[38] | 86 | s "References"(str_set "references" x);
|
---|
[3] | 87 | s "Series" (str_set "series" x);
|
---|
| 88 | s "Abstract" (str "abstract" x);
|
---|
| 89 | s "Alias" (str "Alias" x)
|
---|
| 90 | ] in
|
---|
| 91 | String.concat "" rows ^ "\n" ^ x.body
|
---|
[2] | 92 |
|
---|
| 93 | let string_alias t =
|
---|
[3] | 94 | let is_reserved = function
|
---|
| 95 | | '!' | '*' | '\'' | '(' | ')' | ';' | ':' | '@' | '&' | '=' | '+' | '$'
|
---|
| 96 | | ',' | '/' | '?' | '#' | '[' | ']' | ' ' | '\t' | '\x00' -> true
|
---|
| 97 | | _ -> false
|
---|
| 98 | in
|
---|
| 99 | let b = Buffer.create (String.length t) in
|
---|
| 100 | let filter char =
|
---|
| 101 | let open Buffer in
|
---|
| 102 | if is_reserved char then (try (if nth b (pred (length b)) <> '-' then add_char b '-') with Invalid_argument _ -> prerr_endline "reserved")
|
---|
| 103 | else add_char b char
|
---|
| 104 | in
|
---|
| 105 | String.(iter filter (lowercase_ascii t));
|
---|
| 106 | Buffer.contents b
|
---|
[2] | 107 |
|
---|
| 108 | let alias t = match str "alias" t with "" -> string_alias t.title | x -> x
|
---|
[3] | 109 | let short_id t = Id.short t.id
|
---|