···1919 unknown : Unknown.t;
2020}
21212222-let create ~url ~mime_type ?title ?size_in_bytes ?duration_in_seconds ?(unknown = Unknown.empty) () =
2222+let create ~url ~mime_type ?title ?size_in_bytes ?duration_in_seconds
2323+ ?(unknown = Unknown.empty) () =
2324 { url; mime_type; title; size_in_bytes; duration_in_seconds; unknown }
24252526let url t = t.url
···3031let unknown t = t.unknown
31323233let equal a b =
3333- a.url = b.url &&
3434- a.mime_type = b.mime_type &&
3535- a.title = b.title &&
3636- a.size_in_bytes = b.size_in_bytes &&
3737- a.duration_in_seconds = b.duration_in_seconds
3434+ a.url = b.url && a.mime_type = b.mime_type && a.title = b.title
3535+ && a.size_in_bytes = b.size_in_bytes
3636+ && a.duration_in_seconds = b.duration_in_seconds
38373938let pp ppf t =
4039 (* Extract filename from URL *)
···4847 Format.fprintf ppf "%s (%s" filename t.mime_type;
49485049 (match t.size_in_bytes with
5151- | Some size ->
5252- let mb = Int64.to_float size /. (1024. *. 1024.) in
5353- Format.fprintf ppf ", %.1f MB" mb
5454- | None -> ());
5050+ | Some size ->
5151+ let mb = Int64.to_float size /. (1024. *. 1024.) in
5252+ Format.fprintf ppf ", %.1f MB" mb
5353+ | None -> ());
55545655 (match t.duration_in_seconds with
5757- | Some duration ->
5858- let mins = duration / 60 in
5959- let secs = duration mod 60 in
6060- Format.fprintf ppf ", %dm%ds" mins secs
6161- | None -> ());
5656+ | Some duration ->
5757+ let mins = duration / 60 in
5858+ let secs = duration mod 60 in
5959+ Format.fprintf ppf ", %dm%ds" mins secs
6060+ | None -> ());
62616362 Format.fprintf ppf ")"
64636564let jsont =
6665 let kind = "Attachment" in
6766 let doc = "An attachment object" in
6868- let unknown_mems : (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
6767+ let unknown_mems :
6868+ (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
6969 let open Jsont.Object.Mems in
7070 let dec_empty () = [] in
7171 let dec_add _meta (name : string) value acc =
7272 ((name, Jsont.Meta.none), value) :: acc
7373 in
7474 let dec_finish _meta mems =
7575- List.rev_map (fun ((name, _meta), value) -> (name, value)) mems in
7676- let enc = {
7777- enc = fun (type acc) (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc) unknown (acc : acc) ->
7878- List.fold_left (fun acc (name, value) ->
7979-8080- f Jsont.Meta.none name value acc
8181- ) acc unknown
8282- } in
7575+ List.rev_map (fun ((name, _meta), value) -> (name, value)) mems
7676+ in
7777+ let enc =
7878+ {
7979+ enc =
8080+ (fun (type acc)
8181+ (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc)
8282+ unknown
8383+ (acc : acc)
8484+ ->
8585+ List.fold_left
8686+ (fun acc (name, value) -> f Jsont.Meta.none name value acc)
8787+ acc unknown);
8888+ }
8989+ in
8390 map ~kind:"Unknown members" Jsont.json ~dec_empty ~dec_add ~dec_finish ~enc
8491 in
8592 let create_obj url mime_type title size_in_bytes duration_in_seconds unknown =
8686- create ~url ~mime_type ?title ?size_in_bytes ?duration_in_seconds ~unknown ()
9393+ create ~url ~mime_type ?title ?size_in_bytes ?duration_in_seconds ~unknown
9494+ ()
8795 in
8896 Jsont.Object.map ~kind ~doc create_obj
8997 |> Jsont.Object.mem "url" Jsont.string ~enc:url
9098 |> Jsont.Object.mem "mime_type" Jsont.string ~enc:mime_type
9199 |> Jsont.Object.opt_mem "title" Jsont.string ~enc:title
92100 |> Jsont.Object.opt_mem "size_in_bytes" Jsont.int64 ~enc:size_in_bytes
9393- |> Jsont.Object.opt_mem "duration_in_seconds" Jsont.int ~enc:duration_in_seconds
101101+ |> Jsont.Object.opt_mem "duration_in_seconds" Jsont.int
102102+ ~enc:duration_in_seconds
94103 |> Jsont.Object.keep_unknown unknown_mems ~enc:unknown
95104 |> Jsont.Object.finish
+25-30
lib/attachment.mli
···5566(** Attachments for JSON Feed items.
7788- An attachment represents an external resource related to a feed item,
99- such as audio files for podcasts, video files, or other downloadable content.
1010- Attachments with identical titles indicate alternate formats of the same resource.
88+ An attachment represents an external resource related to a feed item, such
99+ as audio files for podcasts, video files, or other downloadable content.
1010+ Attachments with identical titles indicate alternate formats of the same
1111+ resource.
11121213 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
13141414-1515-(** The type representing an attachment. *)
1615type t
1717-1616+(** The type representing an attachment. *)
18171918(** {1 Unknown Fields} *)
20192120module Unknown : sig
2221 type t = (string * Jsont.json) list
2323- (** Unknown/unrecognized JSON object members.
2424- Useful for preserving fields from custom extensions or future spec versions. *)
2222+ (** Unknown/unrecognized JSON object members. Useful for preserving fields
2323+ from custom extensions or future spec versions. *)
25242625 val empty : t
2726 (** [empty] is the empty list of unknown fields. *)
···3029 (** [is_empty u] returns [true] if there are no unknown fields. *)
3130end
32313333-3432(** {1 Jsont Type} *)
35333634val jsont : t Jsont.t
3735(** Declarative JSON type for attachments.
38363939- Maps JSON objects with "url" (required), "mime_type" (required),
4040- and optional "title", "size_in_bytes", "duration_in_seconds" fields. *)
4141-3737+ Maps JSON objects with "url" (required), "mime_type" (required), and
3838+ optional "title", "size_in_bytes", "duration_in_seconds" fields. *)
42394340(** {1 Construction} *)
4441···5148 ?unknown:Unknown.t ->
5249 unit ->
5350 t
5454-(** [create ~url ~mime_type ?title ?size_in_bytes ?duration_in_seconds ?unknown ()]
5555- creates an attachment object.
5151+(** [create ~url ~mime_type ?title ?size_in_bytes ?duration_in_seconds ?unknown
5252+ ()] creates an attachment object.
56535754 @param url The location of the attachment (required)
5858- @param mime_type The MIME type of the attachment, e.g. ["audio/mpeg"] (required)
5959- @param title The name of the attachment; identical titles indicate alternate formats
6060- of the same resource
5555+ @param mime_type
5656+ The MIME type of the attachment, e.g. ["audio/mpeg"] (required)
5757+ @param title
5858+ The name of the attachment; identical titles indicate alternate formats of
5959+ the same resource
6160 @param size_in_bytes The size of the attachment file in bytes
6262- @param duration_in_seconds The duration of the attachment in seconds (for audio/video)
6161+ @param duration_in_seconds
6262+ The duration of the attachment in seconds (for audio/video)
6363 @param unknown Unknown/custom fields for extensions (default: empty)
64646565 {b Examples:}
6666 {[
6767 (* Simple attachment *)
6868- let att = Attachment.create
6969- ~url:"https://example.com/episode.mp3"
7070- ~mime_type:"audio/mpeg" ()
6868+ let att =
6969+ Attachment.create ~url:"https://example.com/episode.mp3"
7070+ ~mime_type:"audio/mpeg" ()
71717272 (* Podcast episode with metadata *)
7373- let att = Attachment.create
7474- ~url:"https://example.com/episode.mp3"
7575- ~mime_type:"audio/mpeg"
7676- ~title:"Episode 42"
7777- ~size_in_bytes:15_728_640L
7878- ~duration_in_seconds:1800 ()
7373+ let att =
7474+ Attachment.create ~url:"https://example.com/episode.mp3"
7575+ ~mime_type:"audio/mpeg" ~title:"Episode 42" ~size_in_bytes:15_728_640L
7676+ ~duration_in_seconds:1800 ()
7977 ]} *)
8080-81788279(** {1 Accessors} *)
8380···9996val unknown : t -> Unknown.t
10097(** [unknown t] returns unrecognized fields from the JSON. *)
10198102102-10399(** {1 Comparison} *)
104100105101val equal : t -> t -> bool
106102(** [equal a b] tests equality between two attachments. *)
107107-108103109104(** {1 Pretty Printing} *)
110105
+26-20
lib/author.ml
···19192020let create ?name ?url ?avatar ?(unknown = Unknown.empty) () =
2121 if name = None && url = None && avatar = None then
2222- invalid_arg "Author.create: at least one field (name, url, or avatar) must be provided";
2222+ invalid_arg
2323+ "Author.create: at least one field (name, url, or avatar) must be \
2424+ provided";
2325 { name; url; avatar; unknown }
24262527let name t = t.name
2628let url t = t.url
2729let avatar t = t.avatar
2830let unknown t = t.unknown
2929-3030-let is_valid t =
3131- t.name <> None || t.url <> None || t.avatar <> None
3232-3333-let equal a b =
3434- a.name = b.name &&
3535- a.url = b.url &&
3636- a.avatar = b.avatar
3131+let is_valid t = t.name <> None || t.url <> None || t.avatar <> None
3232+let equal a b = a.name = b.name && a.url = b.url && a.avatar = b.avatar
37333834let pp ppf t =
3939- match t.name, t.url with
3535+ match (t.name, t.url) with
4036 | Some name, Some url -> Format.fprintf ppf "%s <%s>" name url
4137 | Some name, None -> Format.fprintf ppf "%s" name
4238 | None, Some url -> Format.fprintf ppf "<%s>" url
4343- | None, None ->
3939+ | None, None -> (
4440 match t.avatar with
4541 | Some avatar -> Format.fprintf ppf "(avatar: %s)" avatar
4646- | None -> Format.fprintf ppf "(empty author)"
4242+ | None -> Format.fprintf ppf "(empty author)")
47434844let jsont =
4945 let kind = "Author" in
5046 let doc = "An author object with at least one field set" in
5147 (* Custom mems map for Unknown.t that strips metadata from names *)
5252- let unknown_mems : (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
4848+ let unknown_mems :
4949+ (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
5350 let open Jsont.Object.Mems in
5451 let dec_empty () = [] in
5552 let dec_add _meta (name : string) value acc =
···5855 let dec_finish _meta mems =
5956 List.rev_map (fun ((name, _meta), value) -> (name, value)) mems
6057 in
6161- let enc = {
6262- enc = fun (type acc) (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc) unknown (acc : acc) ->
6363- List.fold_left (fun acc (name, value) ->
6464- f Jsont.Meta.none name value acc
6565- ) acc unknown
6666- } in
5858+ let enc =
5959+ {
6060+ enc =
6161+ (fun (type acc)
6262+ (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc)
6363+ unknown
6464+ (acc : acc)
6565+ ->
6666+ List.fold_left
6767+ (fun acc (name, value) -> f Jsont.Meta.none name value acc)
6868+ acc unknown);
6969+ }
7070+ in
6771 map ~kind:"Unknown members" Jsont.json ~dec_empty ~dec_add ~dec_finish ~enc
6872 in
6973 (* Constructor that matches the jsont object map pattern *)
7070- let create_obj name url avatar unknown = create ?name ?url ?avatar ~unknown () in
7474+ let create_obj name url avatar unknown =
7575+ create ?name ?url ?avatar ~unknown ()
7676+ in
7177 Jsont.Object.map ~kind ~doc create_obj
7278 |> Jsont.Object.opt_mem "name" Jsont.string ~enc:name
7379 |> Jsont.Object.opt_mem "url" Jsont.string ~enc:url
+21-24
lib/author.mli
···11111212 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
13131414-1515-(** The type representing an author. *)
1614type t
1717-1515+(** The type representing an author. *)
18161917(** {1 Unknown Fields} *)
20182119module Unknown : sig
2220 type t = (string * Jsont.json) list
2323- (** Unknown/unrecognized JSON object members.
2424- Useful for preserving fields from custom extensions or future spec versions. *)
2121+ (** Unknown/unrecognized JSON object members. Useful for preserving fields
2222+ from custom extensions or future spec versions. *)
25232624 val empty : t
2725 (** [empty] is the empty list of unknown fields. *)
···2927 val is_empty : t -> bool
3028 (** [is_empty u] returns [true] if there are no unknown fields. *)
3129end
3232-33303431(** {1 Jsont Type} *)
35323633val jsont : t Jsont.t
3734(** Declarative JSON type for authors.
38353939- Maps JSON objects with optional "name", "url", and "avatar" fields.
4040- At least one field must be present during decoding. *)
4141-3636+ Maps JSON objects with optional "name", "url", and "avatar" fields. At least
3737+ one field must be present during decoding. *)
42384339(** {1 Construction} *)
44404541val create :
4646- ?name:string -> ?url:string -> ?avatar:string ->
4747- ?unknown:Unknown.t -> unit -> t
4242+ ?name:string ->
4343+ ?url:string ->
4444+ ?avatar:string ->
4545+ ?unknown:Unknown.t ->
4646+ unit ->
4747+ t
4848(** [create ?name ?url ?avatar ?unknown ()] creates an author.
49495050- At least one of the optional parameters must be provided, otherwise
5151- the function will raise [Invalid_argument].
5050+ At least one of the optional parameters must be provided, otherwise the
5151+ function will raise [Invalid_argument].
52525353 @param name The author's name
5454 @param url URL of the author's website or profile
5555- @param avatar URL of the author's avatar image (should be square, 512x512 or larger)
5555+ @param avatar
5656+ URL of the author's avatar image (should be square, 512x512 or larger)
5657 @param unknown Unknown/custom fields for extensions (default: empty)
57585859 {b Examples:}
5960 {[
6061 let author = Author.create ~name:"Jane Doe" ()
6162 let author = Author.create ~name:"Jane Doe" ~url:"https://janedoe.com" ()
6262- let author = Author.create
6363- ~name:"Jane Doe"
6464- ~url:"https://janedoe.com"
6565- ~avatar:"https://janedoe.com/avatar.png" ()
6666- ]} *)
67636464+ let author =
6565+ Author.create ~name:"Jane Doe" ~url:"https://janedoe.com"
6666+ ~avatar:"https://janedoe.com/avatar.png" ()
6767+ ]} *)
68686969(** {1 Accessors} *)
7070···8080val unknown : t -> Unknown.t
8181(** [unknown t] returns unrecognized fields from the JSON. *)
82828383-8483(** {1 Predicates} *)
85848685val is_valid : t -> bool
8786(** [is_valid t] checks if the author has at least one field set.
88878989- This should always return [true] for authors created via {!create},
9090- but may be useful when parsing from external sources. *)
9191-8888+ This should always return [true] for authors created via {!create}, but may
8989+ be useful when parsing from external sources. *)
92909391(** {1 Comparison} *)
94929593val equal : t -> t -> bool
9694(** [equal a b] tests equality between two authors. *)
9797-98959996(** {1 Pretty Printing} *)
10097
+4-9
lib/cito.ml
···11-type t = [
22- | `Cites
11+type t =
22+ [ `Cites
33 | `CitesAsAuthority
44 | `CitesAsDataSource
55 | `CitesAsEvidence
···4747 | `SharesPublicationVenueWith
4848 | `SharesFundingAgencyWith
4949 | `SharesAuthorInstitutionWith
5050- | `Other of string
5151-]
5050+ | `Other of string ]
52515352let to_string = function
5453 | `Cites -> "cites"
···153152 | "sharesauthorinstitutionwith" -> `SharesAuthorInstitutionWith
154153 | _ -> `Other s
155154156156-let equal a b =
157157- match a, b with
158158- | `Other sa, `Other sb -> sa = sb
159159- | _ -> a = b
160160-155155+let equal a b = match (a, b) with `Other sa, `Other sb -> sa = sb | _ -> a = b
161156let pp ppf t = Format.fprintf ppf "%s" (to_string t)
162157163158let jsont =
+77-79
lib/cito.mli
···11(** Citation Typing Ontology (CiTO) intent annotations.
2233- CiTO provides a structured vocabulary for describing the nature of citations.
44- This module implements support for CiTO annotations as used in the references extension.
33+ CiTO provides a structured vocabulary for describing the nature of
44+ citations. This module implements support for CiTO annotations as used in
55+ the references extension.
5667 @see <https://purl.archive.org/spar/cito> Citation Typing Ontology
77- @see <https://sparontologies.github.io/cito/current/cito.html> CiTO Specification *)
88-99-1010-(** CiTO citation intent annotation.
1111-1212- Represents the intent or nature of a citation using the Citation Typing Ontology.
1313- Each variant corresponds to a specific CiTO property. The [`Other] variant allows
1414- for custom or future CiTO terms not yet included in this library.
88+ @see <https://sparontologies.github.io/cito/current/cito.html>
99+ CiTO Specification *)
15101616- {b Categories:}
1717- - Factual: Citing for data, methods, evidence, or information
1818- - Critical: Agreement, disagreement, correction, or qualification
1919- - Rhetorical: Style-based citations (parody, ridicule, etc.)
2020- - Relational: Document relationships and compilations
2121- - Support: Providing or obtaining backing and context
2222- - Exploratory: Speculation and recommendations
2323- - Quotation: Direct quotes and excerpts
2424- - Dialogue: Replies and responses
2525- - Sharing: Common attributes between works *)
2626-type t = [
2727- | `Cites (** The base citation property *)
2828-2929- (* Factual citation intents *)
3030- | `CitesAsAuthority (** Cites as authoritative source *)
1111+type t =
1212+ [ `Cites (** The base citation property *)
1313+ | (* Factual citation intents *)
1414+ `CitesAsAuthority
1515+ (** Cites as authoritative source *)
3116 | `CitesAsDataSource (** Cites as origin of data *)
3217 | `CitesAsEvidence (** Cites for factual evidence *)
3318 | `CitesForInformation (** Cites as information source *)
3419 | `UsesDataFrom (** Uses data from cited work *)
3520 | `UsesMethodIn (** Uses methodology from cited work *)
3621 | `UsesConclusionsFrom (** Applies conclusions from cited work *)
3737-3838- (* Agreement/disagreement *)
3939- | `AgreesWith (** Concurs with cited statements *)
2222+ | (* Agreement/disagreement *)
2323+ `AgreesWith
2424+ (** Concurs with cited statements *)
4025 | `DisagreesWith (** Rejects cited statements *)
4126 | `Confirms (** Validates facts in cited work *)
4227 | `Refutes (** Disproves cited statements *)
4328 | `Disputes (** Contests without definitive refutation *)
4444-4545- (* Critical engagement *)
4646- | `Critiques (** Analyzes and finds fault *)
2929+ | (* Critical engagement *)
3030+ `Critiques
3131+ (** Analyzes and finds fault *)
4732 | `Qualifies (** Places conditions on statements *)
4833 | `Corrects (** Fixes errors in cited work *)
4934 | `Updates (** Advances understanding beyond cited work *)
5035 | `Extends (** Builds upon cited facts *)
5151-5252- (* Rhetorical/stylistic *)
5353- | `Parodies (** Imitates for comic effect *)
3636+ | (* Rhetorical/stylistic *)
3737+ `Parodies
3838+ (** Imitates for comic effect *)
5439 | `Plagiarizes (** Uses without acknowledgment *)
5540 | `Derides (** Expresses contempt *)
5641 | `Ridicules (** Mocks cited work *)
5757-5858- (* Document relationships *)
5959- | `Describes (** Characterizes cited entity *)
4242+ | (* Document relationships *)
4343+ `Describes
4444+ (** Characterizes cited entity *)
6045 | `Documents (** Records information about source *)
6146 | `CitesAsSourceDocument (** Cites as foundational source *)
6247 | `CitesAsMetadataDocument (** Cites containing metadata *)
6348 | `Compiles (** Uses to create new work *)
6449 | `Reviews (** Examines cited statements *)
6550 | `Retracts (** Formally withdraws *)
6666-6767- (* Support/context *)
6868- | `Supports (** Provides intellectual backing *)
5151+ | (* Support/context *)
5252+ `Supports
5353+ (** Provides intellectual backing *)
6954 | `GivesSupportTo (** Provides support to citing entity *)
7055 | `ObtainsSupportFrom (** Obtains backing from cited work *)
7156 | `GivesBackgroundTo (** Provides context *)
7257 | `ObtainsBackgroundFrom (** Obtains context from cited work *)
7373-7474- (* Exploratory *)
7575- | `SpeculatesOn (** Theorizes without firm evidence *)
5858+ | (* Exploratory *)
5959+ `SpeculatesOn
6060+ (** Theorizes without firm evidence *)
7661 | `CitesAsPotentialSolution (** Offers possible resolution *)
7762 | `CitesAsRecommendedReading (** Suggests as further reading *)
7863 | `CitesAsRelated (** Identifies as thematically connected *)
7979-8080- (* Quotation/excerpting *)
8181- | `IncludesQuotationFrom (** Incorporates direct quotes *)
6464+ | (* Quotation/excerpting *)
6565+ `IncludesQuotationFrom
6666+ (** Incorporates direct quotes *)
8267 | `IncludesExcerptFrom (** Uses non-quoted passages *)
8383-8484- (* Dialogue *)
8585- | `RepliesTo (** Responds to cited statements *)
6868+ | (* Dialogue *)
6969+ `RepliesTo
7070+ (** Responds to cited statements *)
8671 | `HasReplyFrom (** Evokes response *)
8787-8888- (* Linking *)
8989- | `LinksTo (** Provides URL hyperlink *)
9090-9191- (* Shared attribution *)
9292- | `SharesAuthorWith (** Common authorship *)
7272+ | (* Linking *)
7373+ `LinksTo
7474+ (** Provides URL hyperlink *)
7575+ | (* Shared attribution *)
7676+ `SharesAuthorWith
7777+ (** Common authorship *)
9378 | `SharesJournalWith (** Published in same journal *)
9479 | `SharesPublicationVenueWith (** Published in same venue *)
9580 | `SharesFundingAgencyWith (** Funded by same agency *)
9681 | `SharesAuthorInstitutionWith (** Authors share affiliation *)
8282+ | (* Extensibility *)
8383+ `Other of string
8484+ (** Custom or future CiTO term *) ]
8585+(** CiTO citation intent annotation.
97869898- (* Extensibility *)
9999- | `Other of string (** Custom or future CiTO term *)
100100-]
8787+ Represents the intent or nature of a citation using the Citation Typing
8888+ Ontology. Each variant corresponds to a specific CiTO property. The [`Other]
8989+ variant allows for custom or future CiTO terms not yet included in this
9090+ library.
101919292+ {b Categories:}
9393+ - Factual: Citing for data, methods, evidence, or information
9494+ - Critical: Agreement, disagreement, correction, or qualification
9595+ - Rhetorical: Style-based citations (parody, ridicule, etc.)
9696+ - Relational: Document relationships and compilations
9797+ - Support: Providing or obtaining backing and context
9898+ - Exploratory: Speculation and recommendations
9999+ - Quotation: Direct quotes and excerpts
100100+ - Dialogue: Replies and responses
101101+ - Sharing: Common attributes between works *)
102102103103(** {1 Conversion} *)
104104105105+val of_string : string -> t
105106(** [of_string s] converts a CiTO term string to its variant representation.
106107107108 Recognized CiTO terms are converted to their corresponding variants.
108109 Unrecognized terms are wrapped in [`Other].
109110110110- The comparison is case-insensitive for standard CiTO terms but preserves
111111- the original case in [`Other] variants.
111111+ The comparison is case-insensitive for standard CiTO terms but preserves the
112112+ original case in [`Other] variants.
112113113114 {b Examples:}
114115 {[
115115- of_string "cites" (* returns `Cites *)
116116- of_string "usesMethodIn" (* returns `UsesMethodIn *)
117117- of_string "citesAsRecommendedReading" (* returns `CitesAsRecommendedReading *)
118118- of_string "customTerm" (* returns `Other "customTerm" *)
116116+ of_string "cites" (* returns `Cites *) of_string "usesMethodIn"
117117+ (* returns `UsesMethodIn *) of_string
118118+ "citesAsRecommendedReading" (* returns `CitesAsRecommendedReading *)
119119+ of_string "customTerm" (* returns `Other "customTerm" *)
119120 ]} *)
120120-val of_string : string -> t
121121122122-(** [to_string t] converts a CiTO variant to its canonical string representation.
122122+val to_string : t -> string
123123+(** [to_string t] converts a CiTO variant to its canonical string
124124+ representation.
123125124126 Standard CiTO terms use their official CiTO local names (camelCase).
125127 [`Other] variants return the wrapped string unchanged.
126128127129 {b Examples:}
128130 {[
129129- to_string `Cites (* returns "cites" *)
130130- to_string `UsesMethodIn (* returns "usesMethodIn" *)
131131- to_string (`Other "customTerm") (* returns "customTerm" *)
131131+ to_string `Cites (* returns "cites" *) to_string `UsesMethodIn
132132+ (* returns "usesMethodIn" *) to_string (`Other "customTerm")
133133+ (* returns "customTerm" *)
132134 ]} *)
133133-val to_string : t -> string
134134-135135136136(** {1 Comparison} *)
137137138138+val equal : t -> t -> bool
138139(** [equal a b] tests equality between two CiTO annotations.
139140140140- Two annotations are equal if they represent the same CiTO term.
141141- For [`Other] variants, string comparison is case-sensitive. *)
142142-val equal : t -> t -> bool
143143-141141+ Two annotations are equal if they represent the same CiTO term. For [`Other]
142142+ variants, string comparison is case-sensitive. *)
144143145144(** {1 Jsont Type} *)
146145147146val jsont : t Jsont.t
148147(** Declarative JSON type for CiTO annotations.
149148150150- Maps CiTO intent strings to the corresponding variants.
151151- Unknown intents are mapped to [`Other s]. *)
152152-149149+ Maps CiTO intent strings to the corresponding variants. Unknown intents are
150150+ mapped to [`Other s]. *)
153151154152(** {1 Pretty Printing} *)
155153154154+val pp : Format.formatter -> t -> unit
156155(** [pp ppf t] pretty prints a CiTO annotation to the formatter.
157156158157 {b Example output:}
159158 {v citesAsRecommendedReading v} *)
160160-val pp : Format.formatter -> t -> unit
+21-22
lib/hub.ml
···1010 let is_empty = function [] -> true | _ -> false
1111end
12121313-type t = {
1414- type_ : string;
1515- url : string;
1616- unknown : Unknown.t;
1717-}
1313+type t = { type_ : string; url : string; unknown : Unknown.t }
18141919-let create ~type_ ~url ?(unknown = Unknown.empty) () =
2020- { type_; url; unknown }
2121-1515+let create ~type_ ~url ?(unknown = Unknown.empty) () = { type_; url; unknown }
2216let type_ t = t.type_
2317let url t = t.url
2418let unknown t = t.unknown
2525-2626-let equal a b =
2727- a.type_ = b.type_ && a.url = b.url
2828-2929-let pp ppf t =
3030- Format.fprintf ppf "%s: %s" t.type_ t.url
1919+let equal a b = a.type_ = b.type_ && a.url = b.url
2020+let pp ppf t = Format.fprintf ppf "%s: %s" t.type_ t.url
31213222let jsont =
3323 let kind = "Hub" in
3424 let doc = "A hub endpoint" in
3535- let unknown_mems : (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
2525+ let unknown_mems :
2626+ (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
3627 let open Jsont.Object.Mems in
3728 let dec_empty () = [] in
3829 let dec_add _meta (name : string) value acc =
3930 ((name, Jsont.Meta.none), value) :: acc
4031 in
4132 let dec_finish _meta mems =
4242- List.rev_map (fun ((name, _meta), value) -> (name, value)) mems in
4343- let enc = {
4444- enc = fun (type acc) (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc) unknown (acc : acc) ->
4545- List.fold_left (fun acc (name, value) ->
4646- f Jsont.Meta.none name value acc
4747- ) acc unknown
4848- } in
3333+ List.rev_map (fun ((name, _meta), value) -> (name, value)) mems
3434+ in
3535+ let enc =
3636+ {
3737+ enc =
3838+ (fun (type acc)
3939+ (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc)
4040+ unknown
4141+ (acc : acc)
4242+ ->
4343+ List.fold_left
4444+ (fun acc (name, value) -> f Jsont.Meta.none name value acc)
4545+ acc unknown);
4646+ }
4747+ in
4948 map ~kind:"Unknown members" Jsont.json ~dec_empty ~dec_add ~dec_finish ~enc
5049 in
5150 let create_obj type_ url unknown = create ~type_ ~url ~unknown () in
+6-16
lib/hub.mli
···11111212 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
13131414-1515-(** The type representing a hub endpoint. *)
1614type t
1717-1515+(** The type representing a hub endpoint. *)
18161917(** {1 Unknown Fields} *)
20182119module Unknown : sig
2220 type t = (string * Jsont.json) list
2323- (** Unknown/unrecognized JSON object members.
2424- Useful for preserving fields from custom extensions or future spec versions. *)
2121+ (** Unknown/unrecognized JSON object members. Useful for preserving fields
2222+ from custom extensions or future spec versions. *)
25232624 val empty : t
2725 (** [empty] is the empty list of unknown fields. *)
···2927 val is_empty : t -> bool
3028 (** [is_empty u] returns [true] if there are no unknown fields. *)
3129end
3232-33303431(** {1 Jsont Type} *)
3532···38353936 Maps JSON objects with "type" and "url" fields (both required). *)
40374141-4238(** {1 Construction} *)
43394444-val create :
4545- type_:string -> url:string ->
4646- ?unknown:Unknown.t -> unit -> t
4040+val create : type_:string -> url:string -> ?unknown:Unknown.t -> unit -> t
4741(** [create ~type_ ~url ?unknown ()] creates a hub object.
48424943 @param type_ The type of hub protocol (e.g., ["rssCloud"], ["WebSub"])
···52465347 {b Example:}
5448 {[
5555- let hub = Hub.create
5656- ~type_:"WebSub"
5757- ~url:"https://pubsubhubbub.appspot.com/" ()
4949+ let hub =
5050+ Hub.create ~type_:"WebSub" ~url:"https://pubsubhubbub.appspot.com/" ()
5851 ]} *)
5959-60526153(** {1 Accessors} *)
6254···6961val unknown : t -> Unknown.t
7062(** [unknown t] returns unrecognized fields from the JSON. *)
71637272-7364(** {1 Comparison} *)
74657566val equal : t -> t -> bool
7667(** [equal a b] tests equality between two hubs. *)
7777-78687969(** {1 Pretty Printing} *)
8070
+65-30
lib/item.ml
···1010 let is_empty = function [] -> true | _ -> false
1111end
12121313-type content = [
1414- | `Html of string
1515- | `Text of string
1616- | `Both of string * string
1717-]
1313+type content = [ `Html of string | `Text of string | `Both of string * string ]
18141915type t = {
2016 id : string;
···3632}
37333834let create ~id ~content ?url ?external_url ?title ?summary ?image ?banner_image
3939- ?date_published ?date_modified ?authors ?tags ?language ?attachments ?references
4040- ?(unknown = Unknown.empty) () =
3535+ ?date_published ?date_modified ?authors ?tags ?language ?attachments
3636+ ?references ?(unknown = Unknown.empty) () =
4137 {
4242- id; content; url; external_url; title; summary; image; banner_image;
4343- date_published; date_modified; authors; tags; language; attachments; references;
3838+ id;
3939+ content;
4040+ url;
4141+ external_url;
4242+ title;
4343+ summary;
4444+ image;
4545+ banner_image;
4646+ date_published;
4747+ date_modified;
4848+ authors;
4949+ tags;
5050+ language;
5151+ attachments;
5252+ references;
4453 unknown;
4554 }
4655···7685let equal a b = a.id = b.id
77867887let compare a b =
7979- match a.date_published, b.date_published with
8888+ match (a.date_published, b.date_published) with
8089 | None, None -> 0
8190 | None, Some _ -> -1
8291 | Some _, None -> 1
8392 | Some da, Some db -> Ptime.compare da db
84938594let pp ppf t =
8686- match t.date_published, t.title with
9595+ match (t.date_published, t.title) with
8796 | Some date, Some title ->
8897 let (y, m, d), _ = Ptime.to_date_time date in
8998 Format.fprintf ppf "[%04d-%02d-%02d] %s (%s)" y m d title t.id
9099 | Some date, None ->
91100 let (y, m, d), _ = Ptime.to_date_time date in
92101 Format.fprintf ppf "[%04d-%02d-%02d] %s" y m d t.id
9393- | None, Some title ->
9494- Format.fprintf ppf "%s (%s)" title t.id
9595- | None, None ->
9696- Format.fprintf ppf "%s" t.id
102102+ | None, Some title -> Format.fprintf ppf "%s (%s)" title t.id
103103+ | None, None -> Format.fprintf ppf "%s" t.id
9710498105let pp_summary ppf t =
99106 match t.title with
···111118 image banner_image date_published date_modified authors tags language
112119 attachments references _extensions unknown =
113120 (* Determine content from content_html and content_text *)
114114- let content = match content_html, content_text with
121121+ let content =
122122+ match (content_html, content_text) with
115123 | Some html, Some text -> `Both (html, text)
116124 | Some html, None -> `Html html
117125 | None, Some text -> `Text text
···119127 Jsont.Error.msg Jsont.Meta.none
120128 "Item must have at least one of content_html or content_text"
121129 in
122122- { id; content; url; external_url; title; summary; image; banner_image;
123123- date_published; date_modified; authors; tags; language; attachments;
124124- references; unknown }
130130+ {
131131+ id;
132132+ content;
133133+ url;
134134+ external_url;
135135+ title;
136136+ summary;
137137+ image;
138138+ banner_image;
139139+ date_published;
140140+ date_modified;
141141+ authors;
142142+ tags;
143143+ language;
144144+ attachments;
145145+ references;
146146+ unknown;
147147+ }
125148 in
126149127150 (* Encoders to extract fields from item *)
···143166 let enc_references t = t.references in
144167 let enc_unknown t = t.unknown in
145168146146- let unknown_mems : (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
169169+ let unknown_mems :
170170+ (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
147171 let open Jsont.Object.Mems in
148172 let dec_empty () = [] in
149173 let dec_add _meta (name : string) value acc =
150174 ((name, Jsont.Meta.none), value) :: acc
151175 in
152176 let dec_finish _meta mems =
153153- List.rev_map (fun ((name, _meta), value) -> (name, value)) mems in
154154- let enc = {
155155- enc = fun (type acc) (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc) unknown (acc : acc) ->
156156- List.fold_left (fun acc (name, value) ->
157157-158158- f Jsont.Meta.none name value acc
159159- ) acc unknown
160160- } in
177177+ List.rev_map (fun ((name, _meta), value) -> (name, value)) mems
178178+ in
179179+ let enc =
180180+ {
181181+ enc =
182182+ (fun (type acc)
183183+ (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc)
184184+ unknown
185185+ (acc : acc)
186186+ ->
187187+ List.fold_left
188188+ (fun acc (name, value) -> f Jsont.Meta.none name value acc)
189189+ acc unknown);
190190+ }
191191+ in
161192 map ~kind:"Unknown members" Jsont.json ~dec_empty ~dec_add ~dec_finish ~enc
162193 in
163194···176207 |> Jsont.Object.opt_mem "authors" (Jsont.list Author.jsont) ~enc:enc_authors
177208 |> Jsont.Object.opt_mem "tags" (Jsont.list Jsont.string) ~enc:enc_tags
178209 |> Jsont.Object.opt_mem "language" Jsont.string ~enc:enc_language
179179- |> Jsont.Object.opt_mem "attachments" (Jsont.list Attachment.jsont) ~enc:enc_attachments
180180- |> Jsont.Object.opt_mem "_references" (Jsont.list Reference.jsont) ~enc:enc_references
210210+ |> Jsont.Object.opt_mem "attachments"
211211+ (Jsont.list Attachment.jsont)
212212+ ~enc:enc_attachments
213213+ |> Jsont.Object.opt_mem "_references"
214214+ (Jsont.list Reference.jsont)
215215+ ~enc:enc_references
181216 |> Jsont.Object.opt_mem "_extensions" Jsont.json_object ~enc:(fun _t -> None)
182217 |> Jsont.Object.keep_unknown unknown_mems ~enc:enc_unknown
183218 |> Jsont.Object.finish
+12-21
lib/item.mli
···5566(** Feed items in a JSON Feed.
7788- An item represents a single entry in a feed, such as a blog post, podcast episode,
99- or microblog entry. Each item must have a unique identifier and content.
88+ An item represents a single entry in a feed, such as a blog post, podcast
99+ episode, or microblog entry. Each item must have a unique identifier and
1010+ content.
10111112 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
12131313-1414-(** The type representing a feed item. *)
1514type t
1515+(** The type representing a feed item. *)
16161717+type content = [ `Html of string | `Text of string | `Both of string * string ]
1718(** Content representation for an item.
18191919- The JSON Feed specification requires that each item has at least one
2020- form of content. This type enforces that requirement at compile time.
2020+ The JSON Feed specification requires that each item has at least one form of
2121+ content. This type enforces that requirement at compile time.
21222223 - [`Html s]: Item has HTML content only
2324 - [`Text s]: Item has plain text content only
2425 - [`Both (html, text)]: Item has both HTML and plain text versions *)
2525-type content = [
2626- | `Html of string
2727- | `Text of string
2828- | `Both of string * string
2929-]
3030-31263227(** {1 Unknown Fields} *)
33283429module Unknown : sig
3530 type t = (string * Jsont.json) list
3636- (** Unknown/unrecognized JSON object members.
3737- Useful for preserving fields from custom extensions or future spec versions. *)
3131+ (** Unknown/unrecognized JSON object members. Useful for preserving fields
3232+ from custom extensions or future spec versions. *)
38333934 val empty : t
4035 (** [empty] is the empty list of unknown fields. *)
···4338 (** [is_empty u] returns [true] if there are no unknown fields. *)
4439end
45404646-4741(** {1 Jsont Type} *)
48424943val jsont : t Jsont.t
5044(** Declarative JSON type for feed items.
51455252- Maps JSON objects with "id" (required), content fields, and various optional metadata.
5353- The content must have at least one of "content_html" or "content_text". *)
5454-4646+ Maps JSON objects with "id" (required), content fields, and various optional
4747+ metadata. The content must have at least one of "content_html" or
4848+ "content_text". *)
55495650(** {1 Construction} *)
5751···7468 ?unknown:Unknown.t ->
7569 unit ->
7670 t
7777-78717972(** {1 Accessors} *)
8073···9790val references : t -> Reference.t list option
9891val unknown : t -> Unknown.t
9992100100-10193(** {1 Comparison} *)
1029410395val equal : t -> t -> bool
10496val compare : t -> t -> int
105105-1069710798(** {1 Pretty Printing} *)
10899
+50-43
lib/jsonfeed.ml
···3636 unknown : Unknown.t;
3737}
38383939-let create ~title ?home_page_url ?feed_url ?description ?user_comment
4040- ?next_url ?icon ?favicon ?authors ?language ?expired ?hubs ~items
3939+let create ~title ?home_page_url ?feed_url ?description ?user_comment ?next_url
4040+ ?icon ?favicon ?authors ?language ?expired ?hubs ~items
4141 ?(unknown = Unknown.empty) () =
4242 {
4343 version = "https://jsonfeed.org/version/1.1";
···7272let hubs t = t.hubs
7373let items t = t.items
7474let unknown t = t.unknown
7575-7676-let equal a b =
7777- a.title = b.title &&
7878- a.items = b.items
7575+let equal a b = a.title = b.title && a.items = b.items
79768077let pp ppf t =
8178 Format.fprintf ppf "Feed: %s (%d items)" t.title (List.length t.items)
···8885let jsont =
8986 let kind = "JSON Feed" in
9087 let doc = "A JSON Feed document" in
9191- let unknown_mems : (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
8888+ let unknown_mems :
8989+ (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
9290 let open Jsont.Object.Mems in
9391 let dec_empty () = [] in
9492 let dec_add _meta (name : string) value acc =
9593 ((name, Jsont.Meta.none), value) :: acc
9694 in
9795 let dec_finish _meta mems =
9898- List.rev_map (fun ((name, _meta), value) -> (name, value)) mems in
9999- let enc = {
100100- enc = fun (type acc) (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc) unknown (acc : acc) ->
101101- List.fold_left (fun acc (name, value) ->
102102-103103- f Jsont.Meta.none name value acc
104104- ) acc unknown
105105- } in
9696+ List.rev_map (fun ((name, _meta), value) -> (name, value)) mems
9797+ in
9898+ let enc =
9999+ {
100100+ enc =
101101+ (fun (type acc)
102102+ (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc)
103103+ unknown
104104+ (acc : acc)
105105+ ->
106106+ List.fold_left
107107+ (fun acc (name, value) -> f Jsont.Meta.none name value acc)
108108+ acc unknown);
109109+ }
110110+ in
106111 map ~kind:"Unknown members" Jsont.json ~dec_empty ~dec_add ~dec_finish ~enc
107112 in
108113109114 (* Helper constructor that sets version automatically *)
110110- let make_from_json _version title home_page_url feed_url description user_comment
111111- next_url icon favicon authors language expired hubs items unknown =
115115+ let make_from_json _version title home_page_url feed_url description
116116+ user_comment next_url icon favicon authors language expired hubs items
117117+ unknown =
112118 {
113119 version = "https://jsonfeed.org/version/1.1";
114120 title;
···160166let encode_string ?format ?number_format feed =
161167 Jsont_bytesrw.encode_string' ?format ?number_format jsont feed
162168163163-let of_string s =
164164- decode_string s
169169+let of_string s = decode_string s
165170166166-let to_string ?(minify=false) feed =
171171+let to_string ?(minify = false) feed =
167172 let format = if minify then Jsont.Minify else Jsont.Indent in
168173 encode_string ~format feed
169174···174179 let add_error msg = errors := msg :: !errors in
175180176181 (* Check required fields *)
177177- if feed.title = "" then
178178- add_error "title is required and cannot be empty";
182182+ if feed.title = "" then add_error "title is required and cannot be empty";
179183180184 (* Check items have unique IDs *)
181185 let ids = List.map Item.id feed.items in
···185189186190 (* Validate authors *)
187191 (match feed.authors with
188188- | Some authors ->
189189- List.iteri (fun i author ->
190190- if not (Author.is_valid author) then
191191- add_error (Printf.sprintf "feed author %d is invalid (needs at least one field)" i)
192192- ) authors
193193- | None -> ());
192192+ | Some authors ->
193193+ List.iteri
194194+ (fun i author ->
195195+ if not (Author.is_valid author) then
196196+ add_error
197197+ (Printf.sprintf
198198+ "feed author %d is invalid (needs at least one field)" i))
199199+ authors
200200+ | None -> ());
194201195202 (* Validate items *)
196196- List.iteri (fun i item ->
197197- if Item.id item = "" then
198198- add_error (Printf.sprintf "item %d has empty ID" i);
203203+ List.iteri
204204+ (fun i item ->
205205+ if Item.id item = "" then
206206+ add_error (Printf.sprintf "item %d has empty ID" i);
199207200200- (* Validate item authors *)
201201- (match Item.authors item with
202202- | Some authors ->
203203- List.iteri (fun j author ->
204204- if not (Author.is_valid author) then
205205- add_error (Printf.sprintf "item %d author %d is invalid" i j)
206206- ) authors
207207- | None -> ())
208208- ) feed.items;
208208+ (* Validate item authors *)
209209+ match Item.authors item with
210210+ | Some authors ->
211211+ List.iteri
212212+ (fun j author ->
213213+ if not (Author.is_valid author) then
214214+ add_error (Printf.sprintf "item %d author %d is invalid" i j))
215215+ authors
216216+ | None -> ())
217217+ feed.items;
209218210210- match !errors with
211211- | [] -> Ok ()
212212- | errs -> Error (List.rev errs)
219219+ match !errors with [] -> Ok () | errs -> Error (List.rev errs)
+29-22
lib/jsonfeed.mli
···7788 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
991010-1111-(** The type representing a complete JSON Feed. *)
1210type t
1313-1111+(** The type representing a complete JSON Feed. *)
14121513(** {1 Jsont Type} *)
16141715val jsont : t Jsont.t
1816(** Declarative JSON type for JSON feeds.
19172020- Maps the complete JSON Feed 1.1 specification including all required
2121- and optional fields. *)
1818+ Maps the complete JSON Feed 1.1 specification including all required and
1919+ optional fields. *)
22202321module Unknown : sig
2422 type t = (string * Jsont.json) list
2525- (** Unknown/unrecognized JSON object members.
2626- Useful for preserving fields from custom extensions or future spec versions. *)
2323+ (** Unknown/unrecognized JSON object members. Useful for preserving fields
2424+ from custom extensions or future spec versions. *)
27252826 val empty : t
2927 (** [empty] is the empty list of unknown fields. *)
···6967(** {1 Encoding and Decoding} *)
70687169val decode :
7272- ?layout:bool -> ?locs:bool -> ?file:string ->
7373- Bytesrw.Bytes.Reader.t -> (t, Jsont.Error.t) result
7070+ ?layout:bool ->
7171+ ?locs:bool ->
7272+ ?file:string ->
7373+ Bytesrw.Bytes.Reader.t ->
7474+ (t, Jsont.Error.t) result
7475(** [decode r] decodes a JSON Feed from bytesrw reader [r].
75767677 @param layout Preserve whitespace for round-tripping (default: false)
···7879 @param file Source file name for error reporting *)
79808081val decode_string :
8181- ?layout:bool -> ?locs:bool -> ?file:string ->
8282- string -> (t, Jsont.Error.t) result
8282+ ?layout:bool ->
8383+ ?locs:bool ->
8484+ ?file:string ->
8585+ string ->
8686+ (t, Jsont.Error.t) result
8387(** [decode_string s] decodes a JSON Feed from string [s]. *)
84888589val encode :
8686- ?format:Jsont.format -> ?number_format:Jsont.number_format ->
8787- t -> eod:bool -> Bytesrw.Bytes.Writer.t -> (unit, Jsont.Error.t) result
9090+ ?format:Jsont.format ->
9191+ ?number_format:Jsont.number_format ->
9292+ t ->
9393+ eod:bool ->
9494+ Bytesrw.Bytes.Writer.t ->
9595+ (unit, Jsont.Error.t) result
8896(** [encode feed w] encodes [feed] to bytesrw writer [w].
89979090- @param format Output formatting: [Jsont.Minify] or [Jsont.Indent] (default: Minify)
9898+ @param format
9999+ Output formatting: [Jsont.Minify] or [Jsont.Indent] (default: Minify)
91100 @param number_format Printf format for numbers (default: "%.16g")
92101 @param eod Write end-of-data marker *)
9310294103val encode_string :
9595- ?format:Jsont.format -> ?number_format:Jsont.number_format ->
9696- t -> (string, Jsont.Error.t) result
104104+ ?format:Jsont.format ->
105105+ ?number_format:Jsont.number_format ->
106106+ t ->
107107+ (string, Jsont.Error.t) result
97108(** [encode_string feed] encodes [feed] to a string. *)
9898-99109100110val of_string : string -> (t, Jsont.Error.t) result
101111(** Alias for [decode_string] with default options. *)
···103113val to_string : ?minify:bool -> t -> (string, Jsont.Error.t) result
104114(** [to_string feed] encodes [feed] to string.
105115 @param minify Use compact format (true) or indented (false, default) *)
106106-107116108117(** {1 Validation} *)
109118110119val validate : t -> (unit, string list) result
111111-(** [validate feed] validates the feed structure.
112112- Checks for unique item IDs, valid content, etc. *)
113113-120120+(** [validate feed] validates the feed structure. Checks for unique item IDs,
121121+ valid content, etc. *)
114122115123(** {1 Comparison} *)
116124117125val equal : t -> t -> bool
118126(** [equal a b] tests equality between two feeds. *)
119119-120127121128(** {1 Pretty Printing} *)
122129
+18-13
lib/reference.ml
···2424let doi t = t.doi
2525let cito t = t.cito
2626let unknown t = t.unknown
2727-2827let equal a b = String.equal a.url b.url
29283029let pp ppf t =
3130 let open Format in
3231 fprintf ppf "%s" t.url;
3333- match t.doi with
3434- | Some d -> fprintf ppf " [DOI: %s]" d
3535- | None -> ()
3232+ match t.doi with Some d -> fprintf ppf " [DOI: %s]" d | None -> ()
36333734let jsont =
3835 let kind = "Reference" in
3936 let doc = "A reference to a cited source" in
4040- let unknown_mems : (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
3737+ let unknown_mems :
3838+ (Unknown.t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
4139 let open Jsont.Object.Mems in
4240 let dec_empty () = [] in
4341 let dec_add _meta (name : string) value acc =
4442 ((name, Jsont.Meta.none), value) :: acc
4543 in
4644 let dec_finish _meta mems =
4747- List.rev_map (fun ((name, _meta), value) -> (name, value)) mems in
4848- let enc = {
4949- enc = fun (type acc) (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc) unknown (acc : acc) ->
5050- List.fold_left (fun acc (name, value) ->
5151-5252- f Jsont.Meta.none name value acc
5353- ) acc unknown
5454- } in
4545+ List.rev_map (fun ((name, _meta), value) -> (name, value)) mems
4646+ in
4747+ let enc =
4848+ {
4949+ enc =
5050+ (fun (type acc)
5151+ (f : Jsont.Meta.t -> string -> Jsont.json -> acc -> acc)
5252+ unknown
5353+ (acc : acc)
5454+ ->
5555+ List.fold_left
5656+ (fun acc (name, value) -> f Jsont.Meta.none name value acc)
5757+ acc unknown);
5858+ }
5959+ in
5560 map ~kind:"Unknown members" Jsont.json ~dec_empty ~dec_add ~dec_finish ~enc
5661 in
5762 let create_obj url doi cito unknown = create ~url ?doi ?cito ~unknown () in
+24-28
lib/reference.mli
···66(** References extension for JSON Feed items.
7788 This implements the references extension that allows items to cite sources.
99- Each reference represents a cited resource with optional DOI and CiTO annotations.
99+ Each reference represents a cited resource with optional DOI and CiTO
1010+ annotations.
10111111- @see <https://github.com/egonw/JSONFeed-extensions/blob/main/references.md> References Extension Specification
1212+ @see <https://github.com/egonw/JSONFeed-extensions/blob/main/references.md>
1313+ References Extension Specification
1214 @see <https://purl.archive.org/spar/cito> Citation Typing Ontology *)
13151414-1616+type t
1517(** The type representing a reference to a cited source. *)
1616-type t
1717-18181919(** {1 Unknown Fields} *)
20202121module Unknown : sig
2222 type t = (string * Jsont.json) list
2323- (** Unknown/unrecognized JSON object members.
2424- Useful for preserving fields from custom extensions or future spec versions. *)
2323+ (** Unknown/unrecognized JSON object members. Useful for preserving fields
2424+ from custom extensions or future spec versions. *)
25252626 val empty : t
2727 (** [empty] is the empty list of unknown fields. *)
···3030 (** [is_empty u] returns [true] if there are no unknown fields. *)
3131end
32323333-3433(** {1 Jsont Type} *)
35343635val jsont : t Jsont.t
3736(** Declarative JSON type for references.
38373939- Maps JSON objects with "url" (required) and optional "doi" and "cito" fields. *)
4040-3838+ Maps JSON objects with "url" (required) and optional "doi" and "cito"
3939+ fields. *)
41404241(** {1 Construction} *)
4342···5049 t
5150(** [create ~url ?doi ?cito ?unknown ()] creates a reference.
52515353- @param url Unique URL for the reference (required).
5454- A URL based on a persistent unique identifier (like DOI) is recommended.
5252+ @param url
5353+ Unique URL for the reference (required). A URL based on a persistent
5454+ unique identifier (like DOI) is recommended.
5555 @param doi Digital Object Identifier for the reference
5656 @param cito Citation Typing Ontology intent annotations
5757 @param unknown Unknown/custom fields for extensions (default: empty)
···5959 {b Examples:}
6060 {[
6161 (* Simple reference with just a URL *)
6262- let ref1 = Reference.create
6363- ~url:"https://doi.org/10.5281/zenodo.16755947"
6464- ()
6262+ let ref1 =
6363+ Reference.create ~url:"https://doi.org/10.5281/zenodo.16755947" ()
65646665 (* Reference with DOI *)
6767- let ref2 = Reference.create
6868- ~url:"https://doi.org/10.5281/zenodo.16755947"
6969- ~doi:"10.5281/zenodo.16755947"
7070- ()
6666+ let ref2 =
6767+ Reference.create ~url:"https://doi.org/10.5281/zenodo.16755947"
6868+ ~doi:"10.5281/zenodo.16755947" ()
71697270 (* Reference with CiTO annotations *)
7373- let ref3 = Reference.create
7474- ~url:"https://doi.org/10.5281/zenodo.16755947"
7575- ~doi:"10.5281/zenodo.16755947"
7676- ~cito:[`CitesAsRecommendedReading; `UsesMethodIn]
7777- ()
7171+ let ref3 =
7272+ Reference.create ~url:"https://doi.org/10.5281/zenodo.16755947"
7373+ ~doi:"10.5281/zenodo.16755947"
7474+ ~cito:[ `CitesAsRecommendedReading; `UsesMethodIn ]
7575+ ()
7876 ]} *)
79778080-8178(** {1 Accessors} *)
82798380val url : t -> string
···9289val unknown : t -> Unknown.t
9390(** [unknown t] returns unrecognized fields from the JSON. *)
94919595-9692(** {1 Comparison} *)
97939894val equal : t -> t -> bool
9995(** [equal a b] tests equality between two references.
1009610197 References are considered equal if they have the same URL. *)
102102-1039810499(** {1 Pretty Printing} *)
105100···107102(** [pp ppf t] pretty prints a reference to the formatter.
108103109104 {b Example output:}
110110- {v https://doi.org/10.5281/zenodo.16755947 [DOI: 10.5281/zenodo.16755947] v} *)
105105+ {v https://doi.org/10.5281/zenodo.16755947 [DOI: 10.5281/zenodo.16755947] v}
106106+*)
+8-10
lib/rfc3339.ml
···44 ---------------------------------------------------------------------------*)
5566let parse s =
77- match Ptime.of_rfc3339 s with
88- | Ok (t, _, _) -> Some t
99- | Error _ -> None
77+ match Ptime.of_rfc3339 s with Ok (t, _, _) -> Some t | Error _ -> None
1081111-let format t =
1212- Ptime.to_rfc3339 ~frac_s:6 ~tz_offset_s:0 t
1313-1414-let pp ppf t =
1515- Format.pp_print_string ppf (format t)
99+let format t = Ptime.to_rfc3339 ~frac_s:6 ~tz_offset_s:0 t
1010+let pp ppf t = Format.pp_print_string ppf (format t)
16111712let jsont =
1813 let kind = "RFC 3339 timestamp" in
1914 let doc = "An RFC 3339 date-time string" in
2020- let dec s = match parse s with
1515+ let dec s =
1616+ match parse s with
2117 | Some t -> t
2222- | None -> Jsont.Error.msgf Jsont.Meta.none "%s: invalid RFC 3339 timestamp: %S" kind s
1818+ | None ->
1919+ Jsont.Error.msgf Jsont.Meta.none "%s: invalid RFC 3339 timestamp: %S"
2020+ kind s
2321 in
2422 let enc = format in
2523 Jsont.map ~kind ~doc ~dec ~enc Jsont.string
+4-5
lib/rfc3339.mli
···10101111 @see <https://www.rfc-editor.org/rfc/rfc3339> RFC 3339 *)
12121313-1413val jsont : Ptime.t Jsont.t
1514(** [jsont] is a bidirectional JSON type for RFC 3339 timestamps.
16151717- On decode: accepts JSON strings in RFC 3339 format (e.g., "2024-11-03T10:30:00Z")
1818- On encode: produces UTC timestamps with 'Z' suffix
1616+ On decode: accepts JSON strings in RFC 3339 format (e.g.,
1717+ "2024-11-03T10:30:00Z") On encode: produces UTC timestamps with 'Z' suffix
19182019 {b Example:}
2120 {[
···3635val format : Ptime.t -> string
3736(** [format t] formats a timestamp as RFC 3339.
38373939- Always uses UTC timezone (Z suffix) and includes fractional seconds
4040- if the timestamp has sub-second precision.
3838+ Always uses UTC timezone (Z suffix) and includes fractional seconds if the
3939+ timestamp has sub-second precision.
41404241 {b Example output:} ["2024-11-03T10:30:45.123Z"] *)
4342