OCaml library for JSONfeed parsing and creation
1(*---------------------------------------------------------------------------
2 Copyright (c) 2024 Anil Madhavapeddy. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6module Unknown = struct
7 type t = Jsont.json
8
9 let empty = Jsont.Object ([], Jsont.Meta.none)
10 let is_empty = function Jsont.Object ([], _) -> true | _ -> false
11end
12
13type content = [ `Html of string | `Text of string | `Both of string * string ]
14
15type t = {
16 id : string;
17 content : content;
18 url : string option;
19 external_url : string option;
20 title : string option;
21 summary : string option;
22 image : string option;
23 banner_image : string option;
24 date_published : Ptime.t option;
25 date_modified : Ptime.t option;
26 authors : Author.t list option;
27 tags : string list option;
28 language : string option;
29 attachments : Attachment.t list option;
30 references : Reference.t list option;
31 unknown : Unknown.t;
32}
33
34let create ~id ~content ?url ?external_url ?title ?summary ?image ?banner_image
35 ?date_published ?date_modified ?authors ?tags ?language ?attachments
36 ?references ?(unknown = Unknown.empty) () =
37 {
38 id;
39 content;
40 url;
41 external_url;
42 title;
43 summary;
44 image;
45 banner_image;
46 date_published;
47 date_modified;
48 authors;
49 tags;
50 language;
51 attachments;
52 references;
53 unknown;
54 }
55
56let id t = t.id
57let content t = t.content
58let url t = t.url
59let external_url t = t.external_url
60let title t = t.title
61let summary t = t.summary
62let image t = t.image
63let banner_image t = t.banner_image
64let date_published t = t.date_published
65let date_modified t = t.date_modified
66let authors t = t.authors
67let tags t = t.tags
68let language t = t.language
69let attachments t = t.attachments
70let references t = t.references
71let unknown t = t.unknown
72
73let content_html t =
74 match t.content with
75 | `Html html -> Some html
76 | `Text _ -> None
77 | `Both (html, _) -> Some html
78
79let content_text t =
80 match t.content with
81 | `Html _ -> None
82 | `Text text -> Some text
83 | `Both (_, text) -> Some text
84
85let equal a b = a.id = b.id
86
87let compare a b =
88 Option.compare Ptime.compare a.date_published b.date_published
89
90let pp ppf t =
91 match (t.date_published, t.title) with
92 | Some date, Some title ->
93 let (y, m, d), _ = Ptime.to_date_time date in
94 Format.fprintf ppf "[%04d-%02d-%02d] %s (%s)" y m d title t.id
95 | Some date, None ->
96 let (y, m, d), _ = Ptime.to_date_time date in
97 Format.fprintf ppf "[%04d-%02d-%02d] %s" y m d t.id
98 | None, Some title -> Format.fprintf ppf "%s (%s)" title t.id
99 | None, None -> Format.fprintf ppf "%s" t.id
100
101let pp_summary ppf t =
102 Format.fprintf ppf "%s" (Option.value ~default:t.id t.title)
103
104(* Jsont type *)
105
106let jsont =
107 let kind = "Item" in
108 let doc = "A JSON Feed item" in
109
110 (* Helper to construct item from JSON fields *)
111 let make_from_json id content_html content_text url external_url title summary
112 image banner_image date_published date_modified authors tags language
113 attachments references _extensions unknown =
114 (* Determine content from content_html and content_text *)
115 let content =
116 match (content_html, content_text) with
117 | Some html, Some text -> `Both (html, text)
118 | Some html, None -> `Html html
119 | None, Some text -> `Text text
120 | None, None ->
121 Jsont.Error.msg Jsont.Meta.none
122 "Item must have at least one of content_html or content_text"
123 in
124 {
125 id;
126 content;
127 url;
128 external_url;
129 title;
130 summary;
131 image;
132 banner_image;
133 date_published;
134 date_modified;
135 authors;
136 tags;
137 language;
138 attachments;
139 references;
140 unknown;
141 }
142 in
143
144 Jsont.Object.map ~kind ~doc make_from_json
145 |> Jsont.Object.mem "id" Jsont.string ~enc:id
146 |> Jsont.Object.opt_mem "content_html" Jsont.string ~enc:content_html
147 |> Jsont.Object.opt_mem "content_text" Jsont.string ~enc:content_text
148 |> Jsont.Object.opt_mem "url" Jsont.string ~enc:url
149 |> Jsont.Object.opt_mem "external_url" Jsont.string ~enc:external_url
150 |> Jsont.Object.opt_mem "title" Jsont.string ~enc:title
151 |> Jsont.Object.opt_mem "summary" Jsont.string ~enc:summary
152 |> Jsont.Object.opt_mem "image" Jsont.string ~enc:image
153 |> Jsont.Object.opt_mem "banner_image" Jsont.string ~enc:banner_image
154 |> Jsont.Object.opt_mem "date_published" Rfc3339.jsont ~enc:date_published
155 |> Jsont.Object.opt_mem "date_modified" Rfc3339.jsont ~enc:date_modified
156 |> Jsont.Object.opt_mem "authors" (Jsont.list Author.jsont) ~enc:authors
157 |> Jsont.Object.opt_mem "tags" (Jsont.list Jsont.string) ~enc:tags
158 |> Jsont.Object.opt_mem "language" Jsont.string ~enc:language
159 |> Jsont.Object.opt_mem "attachments"
160 (Jsont.list Attachment.jsont)
161 ~enc:attachments
162 |> Jsont.Object.opt_mem "_references"
163 (Jsont.list Reference.jsont)
164 ~enc:references
165 |> Jsont.Object.opt_mem "_extensions" Jsont.json_object ~enc:(fun _t -> None)
166 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:unknown
167 |> Jsont.Object.finish