source: code/trunk/src/core/meta.ml@ 1

Last change on this file since 1 was 1, checked in by fox, 9 years ago

initial simple example with omd

File size: 7.4 KB
Line 
1module Date = struct
2 type t = {
3 created: Ptime.t option;
4 published: Ptime.t option;
5 edited: Ptime.t option;
6 } [@@deriving lens { submodule = true }]
7
8 let rfc_string date = match date with Some t -> Ptime.to_rfc3339 t | None -> ""
9
10 let of_string (rfc : string) = match Ptime.of_rfc3339 rfc with
11 Ok (t,_,_) -> Some t | Error _ -> None
12
13 let listing date = match date.published, date.created with
14 | Some _, _ -> date.published
15 | None, Some _ -> date.created
16 | None, None -> None
17
18 let compare = compare
19
20 let pretty_date = function
21 | Some t -> Ptime.to_date t |> fun (y, m, d) -> Printf.sprintf "%04d-%02d-%02d" y m d
22 | None -> ""
23end
24
25module Id = struct
26 let random_state = Random.State.make_self_init ()
27 type t = Uuidm.t
28 let compare = Uuidm.compare
29 let to_string = Uuidm.to_string
30 let of_string = Uuidm.of_string
31 let generate ?(random_state=random_state) = Uuidm.v4_gen random_state
32end
33
34module Author = struct
35 type name_t = string
36 type address_t = Uri.t
37 type t = {
38 name: name_t;
39 address: address_t;
40 } [@@deriving lens { submodule = true } ]
41
42 let empty = { name = ""; address = Uri.empty }
43
44 let compare = Pervasives.compare
45
46end
47
48module AuthorSet = struct
49 include Set.Make(Author)
50
51 let to_string authors =
52 let to_string a = a.Author.name ^ " <" ^ Uri.to_string a.Author.address ^ ">" in
53 let f elt acc = if String.length acc > 1 then acc ^ ", " ^ to_string elt else to_string elt in
54 fold f authors ""
55
56 let of_string s =
57 match Emile.List.of_string s with
58 | Error _ -> prerr_endline @@ "Error parsing: " ^ s; empty
59 | Ok emails ->
60 let to_author =
61 let module L = List in
62 let open Emile in
63 function
64 | `Group _ -> prerr_endline @@ "Can't deal with groups in author: " ^ s; Author.empty
65 | `Mailbox { name; local; _ } ->
66 let s_of_phrase = function `Dot -> "" | `Word w -> (match w with `Atom a -> a | `String s -> s) | `Encoded _ -> "" in
67 let name = match name with None -> "" | Some phrase -> L.fold_left (fun a e -> a ^ s_of_phrase e) "" phrase in
68 let address =
69 L.fold_left (fun a e -> a ^ match e with `Atom a -> a | `String s -> s) "" local ^ "@" (* TODO: Author address unimplemented *)
70 in
71 Author.{ name; address = Uri.of_string address }
72 in
73 of_list @@ List.map to_author emails
74end
75
76module Category = struct
77 type t = Draft | Unlisted | Published | Custom of string
78
79 let compare = Pervasives.compare
80
81 let of_string = function
82 | "draft" -> Draft
83 | "unlisted" -> Unlisted
84 | "published" -> Published
85 | c -> Custom c
86
87 let to_string = function
88 | Draft -> "draft"
89 | Unlisted -> "unlisted"
90 | Published -> "published"
91 | Custom c -> c
92end
93
94module CategorySet = struct
95 include Set.Make(Category)
96 let to_csv set =
97 let f elt a =
98 let s = Category.to_string elt in
99 if a <> "" then a ^ ", " ^ s else s
100 in
101 fold f set ""
102 let categorised categs cs = of_list categs |> (fun s -> subset s cs)
103 let published = categorised [Category.Published]
104 let listed cs = not @@ categorised [Category.Unlisted] cs
105end
106
107module StringSet = Set.Make(String)
108
109let stringset_csv set =
110 let f elt a = if a <> "" then a ^ ", " ^ elt else elt in
111 StringSet.fold f set ""
112
113let string_alias t =
114 let is_reserved = function
115 | '!' | '*' | '\'' | '(' | ')' | ';' | ':' | '@' | '&' | '=' | '+' | '$'
116 | ',' | '/' | '?' | '#' | '[' | ']' | ' ' | '\t' | '\x00' -> true
117 | _ -> false
118 in
119 let b = Buffer.create (String.length t) in
120 let filter char =
121 let open Buffer in
122 if is_reserved char then (try (if nth b (pred (length b)) <> '-' then add_char b '-') with Invalid_argument _ -> prerr_endline "reserved")
123 else add_char b char
124 in
125 String.(iter filter (lowercase_ascii t));
126 Buffer.contents b
127
128type t = {
129 title: string;
130 authors: AuthorSet.t;
131 date: Date.t;
132 categories: CategorySet.t;
133 topics: StringSet.t;
134 keywords: StringSet.t;
135 series: StringSet.t;
136 abstract: string;
137 uuid: Id.t;
138 alias: string;
139 } [@@deriving lens { submodule = true }]
140
141let blank ?(uuid=(Id.generate ())) () = {
142 title = "";
143 authors = AuthorSet.empty;
144 date = Date.({ created = None; edited = None; published = None });
145 categories = CategorySet.empty;
146 topics = StringSet.empty;
147 keywords = StringSet.empty;
148 series = StringSet.empty;
149 abstract = "";
150 uuid;
151 alias = "";
152 }
153
154let listed e = CategorySet.listed e.categories
155let published e = CategorySet.published e.categories
156let unique_topics ts x = StringSet.union ts x.topics
157
158module AliasMap = Map.Make(String)
159module IdMap = Map.Make(Id)
160
161let alias meta = if meta.alias = "" then string_alias meta.title else meta.alias
162
163let value_with_name (_meta as m) = function
164 | "Title" -> m.title
165 | "Abstract" -> m.abstract
166 | "Authors" -> AuthorSet.to_string m.authors
167 | "Date" -> Date.(rfc_string m.date.created)
168 | "Edited" -> Date.(rfc_string m.date.edited)
169 | "Published"-> Date.(rfc_string m.date.published)
170 | "Human" -> Date.(pretty_date @@ listing m.date)
171 | "Topics" -> stringset_csv m.topics;
172 | "Categories" -> CategorySet.to_csv m.categories;
173 | "Keywords" -> stringset_csv m.keywords;
174 | "Series" -> stringset_csv m.series;
175 | "ID" -> Id.to_string m.uuid
176 | "Alias" -> alias m
177 | e -> invalid_arg e
178
179let with_kv meta (k,v) =
180 let list_of_csv = Re.Str.(split (regexp " *, *")) in
181 let trim = String.trim in
182 match k with
183 | "Title" -> { meta with title = trim v }
184 | "Author"
185 | "Authors" -> { meta with authors = AuthorSet.of_string (trim v)}
186 | "Abstract" -> { meta with abstract = trim v }
187 | "Date" -> { meta with date = Date.{ meta.date with created = Date.of_string v }}
188 | "Published" -> { meta with date = Date.{ meta.date with published = Date.of_string v }}
189 | "Edited" -> { meta with date = Date.{ meta.date with edited = Date.of_string v }}
190 | "Topics" -> { meta with topics = trim v |> list_of_csv |> StringSet.of_list }
191 | "Keywords" -> { meta with keywords = trim v |> list_of_csv |> StringSet.of_list }
192 | "Categories"->
193 let categories = trim v |> list_of_csv |> List.map Category.of_string |> CategorySet.of_list in
194 { meta with categories }
195 | "Series" -> { meta with series = trim v |> list_of_csv |> StringSet.of_list }
196 | "ID" -> (match Id.of_string v with Some id -> { meta with uuid = id } | None -> meta)
197 | "Alias" -> { meta with alias = v }
198 | k -> prerr_endline ("Unknown key: " ^ k ^ ", with value: " ^ v ); meta
199
200let to_string (_meta as m) =
201 let has_len v = String.length v > 0 in
202 let s field value = if has_len value then field ^ ": " ^ value ^ "\n" else "" in
203 let a value = if AuthorSet.is_empty value then "" else "Authors: " ^ AuthorSet.to_string value ^ "\n" in
204 let d field value = match value with
205 | Some _ -> field ^ ": " ^ Date.rfc_string value ^ "\n" | None -> ""
206 in
207 let rows =
208 [ s "Title" m.title;
209 a m.authors;
210 d "Date" m.date.Date.created;
211 d "Edited" m.date.Date.edited;
212 d "Published" m.date.Date.published;
213 s "Topics" (stringset_csv m.topics);
214 s "Categories" (CategorySet.to_csv m.categories);
215 s "Keywords" (stringset_csv m.keywords);
216 s "Series" (stringset_csv m.series);
217 s "Abstract" m.abstract;
218 s "ID" (Uuidm.to_string m.uuid);
219 s "Alias" m.alias
220 ]
221 in
222 String.concat "" rows
Note: See TracBrowser for help on using the repository browser.