(*--------------------------------------------------------------------------- Copyright (c) 2026 The ocaml-cff programmers. All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Person, Entity, and Author types for CFF. *) (** A person (individual author/contributor). *) module Person = struct type t = { family_names : string option ; given_names : string option ; name_particle : string option ; name_suffix : string option ; alias : string option ; affiliation : string option ; address : Cff_address.Address.t ; contact : Cff_address.Contact.t } let make ?family_names ?given_names ?name_particle ?name_suffix ?alias ?affiliation ?(address = Cff_address.Address.empty) ?(contact = Cff_address.Contact.empty) () = { family_names ; given_names ; name_particle ; name_suffix ; alias ; affiliation ; address ; contact } ;; let family_names t = t.family_names let given_names t = t.given_names let name_particle t = t.name_particle let name_suffix t = t.name_suffix let alias t = t.alias let affiliation t = t.affiliation let address t = t.address let contact t = t.contact let full_name t = let parts = List.filter_map Fun.id [ t.given_names; t.name_particle; t.family_names ] in let base = String.concat " " parts in match t.name_suffix with | Some suffix -> base ^ ", " ^ suffix | None -> base ;; let email t = Cff_address.Contact.email t.contact let orcid t = Cff_address.Contact.orcid t.contact let website t = Cff_address.Contact.website t.contact let pp ppf t = Format.fprintf ppf "%s" (full_name t); Option.iter (Format.fprintf ppf " (%s)") t.affiliation ;; let jsont = Jsont.Object.map ~kind:"Person" (fun family_names given_names name_particle name_suffix alias affiliation address city region post_code country email tel fax website orcid -> let address = Cff_address.Address.of_options ~address ~city ~region ~post_code ~country in let contact = Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid in { family_names ; given_names ; name_particle ; name_suffix ; alias ; affiliation ; address ; contact }) |> Jsont.Object.opt_mem "family-names" Jsont.string ~enc:(fun p -> p.family_names) |> Jsont.Object.opt_mem "given-names" Jsont.string ~enc:(fun p -> p.given_names) |> Jsont.Object.opt_mem "name-particle" Jsont.string ~enc:(fun p -> p.name_particle) |> Jsont.Object.opt_mem "name-suffix" Jsont.string ~enc:(fun p -> p.name_suffix) |> Jsont.Object.opt_mem "alias" Jsont.string ~enc:(fun p -> p.alias) |> Jsont.Object.opt_mem "affiliation" Jsont.string ~enc:(fun p -> p.affiliation) |> Cff_address.Address.jsont_fields ~get:(fun p -> p.address) |> Cff_address.Contact.jsont_fields ~get:(fun p -> p.contact) |> Jsont.Object.skip_unknown |> Jsont.Object.finish ;; end (** An entity (organization, team, conference, etc.). *) module Entity = struct type t = { name : string ; alias : string option ; address : Cff_address.Address.t ; contact : Cff_address.Contact.t ; date_start : Cff_date.t option ; date_end : Cff_date.t option ; location : string option } let make ~name ?alias ?(address = Cff_address.Address.empty) ?(contact = Cff_address.Contact.empty) ?date_start ?date_end ?location () = { name; alias; address; contact; date_start; date_end; location } ;; let name t = t.name let alias t = t.alias let address t = t.address let contact t = t.contact let date_start t = t.date_start let date_end t = t.date_end let location t = t.location let email t = Cff_address.Contact.email t.contact let orcid t = Cff_address.Contact.orcid t.contact let website t = Cff_address.Contact.website t.contact let pp ppf t = Format.pp_print_string ppf t.name; Option.iter (Format.fprintf ppf " (%s)") t.alias ;; let jsont = Jsont.Object.map ~kind:"Entity" (fun name alias address city region post_code country email tel fax website orcid date_start date_end location -> let address = Cff_address.Address.of_options ~address ~city ~region ~post_code ~country in let contact = Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid in { name; alias; address; contact; date_start; date_end; location }) |> Jsont.Object.mem "name" Jsont.string ~enc:(fun e -> e.name) |> Jsont.Object.opt_mem "alias" Jsont.string ~enc:(fun e -> e.alias) |> Cff_address.Address.jsont_fields ~get:(fun e -> e.address) |> Cff_address.Contact.jsont_fields ~get:(fun e -> e.contact) |> Jsont.Object.opt_mem "date-start" Cff_date.jsont ~enc:(fun e -> e.date_start) |> Jsont.Object.opt_mem "date-end" Cff_date.jsont ~enc:(fun e -> e.date_end) |> Jsont.Object.opt_mem "location" Jsont.string ~enc:(fun e -> e.location) |> Jsont.Object.skip_unknown |> Jsont.Object.finish ;; end (** An author can be either a Person or an Entity. *) type t = [ `Person of Person.t | `Entity of Entity.t ] let person ?family_names ?given_names ?name_particle ?name_suffix ?alias ?affiliation ?address ?contact () = `Person (Person.make ?family_names ?given_names ?name_particle ?name_suffix ?alias ?affiliation ?address ?contact ()) ;; let entity ~name ?alias ?address ?contact ?date_start ?date_end ?location () = `Entity (Entity.make ~name ?alias ?address ?contact ?date_start ?date_end ?location ()) ;; let name = function | `Person p -> Person.full_name p | `Entity e -> Entity.name e ;; let orcid = function | `Person p -> Person.orcid p | `Entity e -> Entity.orcid e ;; let email = function | `Person p -> Person.email p | `Entity e -> Entity.email e ;; let pp ppf = function | `Person p -> Person.pp ppf p | `Entity e -> Entity.pp ppf e ;; (* Jsont codec that discriminates based on "name" field presence. If "name" is present -> Entity, otherwise -> Person *) let jsont = (* Check if json object has "name" member *) let has_name_member = function | Jsont.Object (members, _) -> Option.is_some (Jsont.Json.find_mem "name" members) | _ -> false in let dec_json j = if has_name_member j then ( match Jsont.Json.decode' Entity.jsont j with | Ok e -> `Entity e | Error err -> Jsont.Error.msgf Jsont.Meta.none "Invalid entity: %s" (Jsont.Error.to_string err)) else ( match Jsont.Json.decode' Person.jsont j with | Ok p -> `Person p | Error err -> Jsont.Error.msgf Jsont.Meta.none "Invalid person: %s" (Jsont.Error.to_string err)) in let enc_author = function | `Person p -> (match Jsont.Json.encode' Person.jsont p with | Ok j -> j | Error _ -> assert false) | `Entity e -> (match Jsont.Json.encode' Entity.jsont e with | Ok j -> j | Error _ -> assert false) in Jsont.json |> Jsont.map ~dec:dec_json ~enc:enc_author ;;