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 match (a.date_published, b.date_published) with 89 | None, None -> 0 90 | None, Some _ -> -1 91 | Some _, None -> 1 92 | Some da, Some db -> Ptime.compare da db 93 94let pp ppf t = 95 match (t.date_published, t.title) with 96 | Some date, Some title -> 97 let (y, m, d), _ = Ptime.to_date_time date in 98 Format.fprintf ppf "[%04d-%02d-%02d] %s (%s)" y m d title t.id 99 | Some date, None -> 100 let (y, m, d), _ = Ptime.to_date_time date in 101 Format.fprintf ppf "[%04d-%02d-%02d] %s" y m d t.id 102 | None, Some title -> Format.fprintf ppf "%s (%s)" title t.id 103 | None, None -> Format.fprintf ppf "%s" t.id 104 105let pp_summary ppf t = 106 match t.title with 107 | Some title -> Format.fprintf ppf "%s" title 108 | None -> Format.fprintf ppf "%s" t.id 109 110(* Jsont type *) 111 112let jsont = 113 let kind = "Item" in 114 let doc = "A JSON Feed item" in 115 116 (* Helper to construct item from JSON fields *) 117 let make_from_json id content_html content_text url external_url title summary 118 image banner_image date_published date_modified authors tags language 119 attachments references _extensions unknown = 120 (* Determine content from content_html and content_text *) 121 let content = 122 match (content_html, content_text) with 123 | Some html, Some text -> `Both (html, text) 124 | Some html, None -> `Html html 125 | None, Some text -> `Text text 126 | None, None -> 127 Jsont.Error.msg Jsont.Meta.none 128 "Item must have at least one of content_html or content_text" 129 in 130 { 131 id; 132 content; 133 url; 134 external_url; 135 title; 136 summary; 137 image; 138 banner_image; 139 date_published; 140 date_modified; 141 authors; 142 tags; 143 language; 144 attachments; 145 references; 146 unknown; 147 } 148 in 149 150 (* Encoders to extract fields from item *) 151 let enc_id t = t.id in 152 let enc_content_html t = content_html t in 153 let enc_content_text t = content_text t in 154 let enc_url t = t.url in 155 let enc_external_url t = t.external_url in 156 let enc_title t = t.title in 157 let enc_summary t = t.summary in 158 let enc_image t = t.image in 159 let enc_banner_image t = t.banner_image in 160 let enc_date_published t = t.date_published in 161 let enc_date_modified t = t.date_modified in 162 let enc_authors t = t.authors in 163 let enc_tags t = t.tags in 164 let enc_language t = t.language in 165 let enc_attachments t = t.attachments in 166 let enc_references t = t.references in 167 let enc_unknown t = t.unknown in 168 169 Jsont.Object.map ~kind ~doc make_from_json 170 |> Jsont.Object.mem "id" Jsont.string ~enc:enc_id 171 |> Jsont.Object.opt_mem "content_html" Jsont.string ~enc:enc_content_html 172 |> Jsont.Object.opt_mem "content_text" Jsont.string ~enc:enc_content_text 173 |> Jsont.Object.opt_mem "url" Jsont.string ~enc:enc_url 174 |> Jsont.Object.opt_mem "external_url" Jsont.string ~enc:enc_external_url 175 |> Jsont.Object.opt_mem "title" Jsont.string ~enc:enc_title 176 |> Jsont.Object.opt_mem "summary" Jsont.string ~enc:enc_summary 177 |> Jsont.Object.opt_mem "image" Jsont.string ~enc:enc_image 178 |> Jsont.Object.opt_mem "banner_image" Jsont.string ~enc:enc_banner_image 179 |> Jsont.Object.opt_mem "date_published" Rfc3339.jsont ~enc:enc_date_published 180 |> Jsont.Object.opt_mem "date_modified" Rfc3339.jsont ~enc:enc_date_modified 181 |> Jsont.Object.opt_mem "authors" (Jsont.list Author.jsont) ~enc:enc_authors 182 |> Jsont.Object.opt_mem "tags" (Jsont.list Jsont.string) ~enc:enc_tags 183 |> Jsont.Object.opt_mem "language" Jsont.string ~enc:enc_language 184 |> Jsont.Object.opt_mem "attachments" 185 (Jsont.list Attachment.jsont) 186 ~enc:enc_attachments 187 |> Jsont.Object.opt_mem "_references" 188 (Jsont.list Reference.jsont) 189 ~enc:enc_references 190 |> Jsont.Object.opt_mem "_extensions" Jsont.json_object ~enc:(fun _t -> None) 191 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:enc_unknown 192 |> Jsont.Object.finish