···7788 This library provides codecs for the
99 {{:https://citation-file-format.github.io/}Citation File Format (CFF)}
1010- version 1.2.0, a human- and machine-readable format for software and
1111- dataset citation metadata.
1010+ version 1.2.0, a human- and machine-readable format for software and dataset
1111+ citation metadata.
12121313- CFF files are named [CITATION.cff] written in the {{:https://yaml.org/}YAML 1.2}
1414- format. They provide citation metadata for software and datasets, enabling
1515-proper academic credit for research software.
1313+ CFF files are named [CITATION.cff] written in the
1414+ {{:https://yaml.org/}YAML 1.2} format. They provide citation metadata for
1515+ software and datasets, enabling proper academic credit for research
1616+ software.
16171718 {1 Overview}
1819···43444445 Example with [cff.unix]:
4546 {[
4646- match Cff_unix.of_file "CITATION.cff" with
4747- | Ok cff -> Printf.printf "Title: %s\n%!" (Cff.title cff)
4848- | Error msg -> Printf.eprintf "Error: %s\n%!" msg
4747+ match Cff_unix.of_file "CITATION.cff" with
4848+ | Ok cff -> Printf.printf "Title: %s\n%!" (Cff.title cff)
4949+ | Error msg -> Printf.eprintf "Error: %s\n%!" msg
4950 ]}
50515152 {1 CFF Specification}
52535354 This implementation follows the
5454- {{:https://github.com/citation-file-format/citation-file-format}CFF 1.2.0 specification}.
5555- Useful modules include:
5555+ {{:https://github.com/citation-file-format/citation-file-format}CFF 1.2.0
5656+ specification}. Useful modules include:
56575758 - {!module:Author}: Can be persons (with family/given names) or entities
5859 (organizations, identified by a [name] field)
5959- - {!module:Reference}: Bibliography entries that the work cites or depends on
6060- - {!module:Identifier}: Typed identifiers including DOIs, URLs, and
6161- Software Heritage IDs (SWH)
6262- - {!module:License}: SPDX license identifiers where multiple licenses imply "OR"
6060+ - {!module:Reference}: Bibliography entries that the work cites or depends
6161+ on
6262+ - {!module:Identifier}: Typed identifiers including DOIs, URLs, and Software
6363+ Heritage IDs (SWH)
6464+ - {!module:License}: SPDX license identifiers where multiple licenses imply
6565+ "OR"
63666467 {1 Core Types} *)
6568···8083 - {!keywords}: Descriptive keywords
8184 - {!abstract}: Description of the work
82858383- The {!preferred_citation} field allows redirecting citations to
8484- a related work (e.g., a journal article describing the software).
8585- The {!section-references} field lists works that the software cites or
8686- depends upon. *)
8686+ The {!preferred_citation} field allows redirecting citations to a related
8787+ work (e.g., a journal article describing the software). The
8888+ {!section-references} field lists works that the software cites or depends
8989+ upon. *)
87908888-(** The abstract type representing a complete CFF document. *)
8991type t
9292+(** The abstract type representing a complete CFF document. *)
90939494+module Date = Cff_date
9195(** Date representation as [(year, month, day)] tuple.
92969397 CFF uses ISO 8601 dates in [YYYY-MM-DD] format (e.g., ["2024-01-15"]). *)
9494-module Date = Cff_date
95989999+module Country = Cff_country
96100(** ISO 3166-1 alpha-2 country codes (e.g., ["US"], ["DE"], ["GB"]).
9710198102 Used for author and entity addresses. *)
9999-module Country = Cff_country
100103104104+module Address = Cff_address.Address
101105(** Physical address information.
102106103103- Address fields used for persons and entities: street address, city,
104104- region (state/province), postal code, and country code. *)
105105-module Address = Cff_address.Address
107107+ Address fields used for persons and entities: street address, city, region
108108+ (state/province), postal code, and country code. *)
106109110110+module Contact = Cff_address.Contact
107111(** Contact information.
108112109109- Contact fields used for persons and entities: email, telephone, fax,
110110- website URL, and ORCID identifier. *)
111111-module Contact = Cff_address.Contact
113113+ Contact fields used for persons and entities: email, telephone, fax, website
114114+ URL, and ORCID identifier. *)
112115116116+module License = Cff_license
113117(** SPDX license identifiers.
114118115115- CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers} for
116116- the [license] field. Multiple licenses indicate an OR relationship
117117- (the user may choose any of the listed licenses). *)
118118-module License = Cff_license
119119+ CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers} for the
120120+ [license] field. Multiple licenses indicate an OR relationship (the user may
121121+ choose any of the listed licenses). *)
119122123123+module Cff_type = Cff_enums.Cff_type
120124(** CFF file type: [`Software] (default) or [`Dataset]. *)
121121-module Cff_type = Cff_enums.Cff_type
122125123126(** {1 Authors and Entities} *)
124127128128+module Author = Cff_author
125129(** Authors as a discriminated union of {!Person} or {!Entity}.
126130127131 CFF distinguishes between:
128132 - {b Persons}: Individual humans with family names, given names, etc.
129133 - {b Entities}: Organizations, projects, or groups with a [name] field
130134131131- When parsing, the presence of a [name] field indicates an entity;
132132- otherwise, the entry is treated as a person. *)
133133-module Author = Cff_author
135135+ When parsing, the presence of a [name] field indicates an entity; otherwise,
136136+ the entry is treated as a person. *)
134137135135-(** A person (individual author or contributor). *)
136138module Person = Cff_author.Person
139139+(** A person (individual author or contributor). *)
137140138138-(** An entity (organization, institution, project, conference). *)
139141module Entity = Cff_author.Entity
142142+(** An entity (organization, institution, project, conference). *)
140143141144(** {1 Identifiers and References} *)
142145146146+module Identifier = Cff_identifier
143147(** Typed identifiers for DOI, URL, SWH, or other schemes.
144148145149 Each identifier has a type, value, and optional description. Example:
146150 {[
147147- let id = Cff.Identifier.make
148148- ~type_:`Doi
149149- ~value:"10.5281/zenodo.1234567"
150150- ~description:"The concept DOI for all versions"
151151- ()
151151+ let id =
152152+ Cff.Identifier.make ~type_:`Doi ~value:"10.5281/zenodo.1234567"
153153+ ~description:"The concept DOI for all versions" ()
152154 ]} *)
153153-module Identifier = Cff_identifier
154155156156+module Reference = Cff_reference
155157(** Bibliographic references with comprehensive metadata.
156158157159 References can represent any citable work: articles, books, software,
158158- datasets, conference papers, theses, etc. The {!Reference} module
159159- provides 60+ fields organized into logical sub-records:
160160+ datasets, conference papers, theses, etc. The {!Reference} module provides
161161+ 60+ fields organized into logical sub-records:
160162161163 - {!Reference.Core} - Type, title, authors, abstract
162164 - {!Reference.Publication} - Journal, volume, issue, pages
···166168 - {!Reference.Entities} - Editors, publisher, institution
167169 - {!Reference.Metadata} - Keywords, license, notes
168170 - {!Reference.Technical} - Commit, version, format *)
169169-module Reference = Cff_reference
170171171172(** {1 Construction} *)
172173173173-(** The default CFF version used when not specified: ["1.2.0"]. *)
174174val default_cff_version : string
175175+(** The default CFF version used when not specified: ["1.2.0"]. *)
175176177177+val default_message : string
176178(** The default citation message:
177177- ["If you use this software, please cite it using the metadata from this file."] *)
178178-val default_message : string
179179+ ["If you use this software, please cite it using the metadata from this
180180+ file."] *)
179181182182+val make :
183183+ ?cff_version:string ->
184184+ ?message:string ->
185185+ title:string ->
186186+ authors:Author.t list ->
187187+ ?abstract:string ->
188188+ ?commit:string ->
189189+ ?contact:Author.t list ->
190190+ ?date_released:Date.t ->
191191+ ?doi:string ->
192192+ ?identifiers:Identifier.t list ->
193193+ ?keywords:string list ->
194194+ ?license:License.t ->
195195+ ?preferred_citation:Reference.t ->
196196+ ?references:Reference.t list ->
197197+ ?repository:string ->
198198+ ?repository_artifact:string ->
199199+ ?repository_code:string ->
200200+ ?type_:Cff_type.t ->
201201+ ?url:string ->
202202+ ?version:string ->
203203+ unit ->
204204+ t
180205(** [make ~title ~authors ...] constructs a CFF value.
181206182207 @param cff_version The CFF schema version (default: {!default_cff_version})
183183- @param message Instructions for users on how to cite (default: {!default_message})
208208+ @param message
209209+ Instructions for users on how to cite (default: {!default_message})
184210 @param title The name of the software or dataset
185211 @param authors List of persons and/or entities who created the work *)
186186-val make
187187- : ?cff_version:string
188188- -> ?message:string
189189- -> title:string
190190- -> authors:Author.t list
191191- -> ?abstract:string
192192- -> ?commit:string
193193- -> ?contact:Author.t list
194194- -> ?date_released:Date.t
195195- -> ?doi:string
196196- -> ?identifiers:Identifier.t list
197197- -> ?keywords:string list
198198- -> ?license:License.t
199199- -> ?preferred_citation:Reference.t
200200- -> ?references:Reference.t list
201201- -> ?repository:string
202202- -> ?repository_artifact:string
203203- -> ?repository_code:string
204204- -> ?type_:Cff_type.t
205205- -> ?url:string
206206- -> ?version:string
207207- -> unit
208208- -> t
209212210213(** {2 Required Fields} *)
211214215215+val cff_version : t -> string
212216(** The CFF schema version that this file adheres to.
213217214214- For CFF 1.2.0 files, this should be ["1.2.0"]. The version determines
215215- which keys are valid and how they should be interpreted. *)
216216-val cff_version : t -> string
218218+ For CFF 1.2.0 files, this should be ["1.2.0"]. The version determines which
219219+ keys are valid and how they should be interpreted. *)
217220221221+val message : t -> string
218222(** A message to readers explaining how to cite the work.
219223220224 Common examples:
221221- - ["If you use this software, please cite it using the metadata from this file."]
222222- - ["Please cite this software using the metadata from 'preferred-citation'."]
225225+ - ["If you use this software, please cite it using the metadata from this
226226+ file."]
227227+ - ["Please cite this software using the metadata from
228228+ 'preferred-citation'."]
223229224230 The message should guide users toward the preferred citation method. *)
225225-val message : t -> string
226231232232+val title : t -> string
227233(** The name of the software or dataset.
228234229235 This is the title that should appear in citations. For software, it's
230236 typically the project name; for datasets, the dataset title. *)
231231-val title : t -> string
232237238238+val authors : t -> Author.t list
233239(** The creators of the software or dataset.
234240235235- Authors can be persons (individuals) or entities (organizations).
236236- At least one author is required for a valid CFF file. The order
237237- typically reflects contribution significance. *)
238238-val authors : t -> Author.t list
241241+ Authors can be persons (individuals) or entities (organizations). At least
242242+ one author is required for a valid CFF file. The order typically reflects
243243+ contribution significance. *)
239244240245(** {2 Optional Fields} *)
241246247247+val abstract : t -> string option
242248(** A description of the software or dataset.
243249244250 Provides context about what the work does, its purpose, and scope. *)
245245-val abstract : t -> string option
246251252252+val commit : t -> string option
247253(** The commit hash or revision number of the software version.
248254249255 Useful for precise version identification beyond semantic versioning.
250256 Example: ["1ff847d81f29c45a3a1a5ce73d38e45c2f319bba"] *)
251251-val commit : t -> string option
252257258258+val contact : t -> Author.t list option
253259(** Contact persons or entities for the software or dataset.
254260255255- May differ from authors; useful when the primary contact is a
256256- project maintainer rather than the original author. *)
257257-val contact : t -> Author.t list option
261261+ May differ from authors; useful when the primary contact is a project
262262+ maintainer rather than the original author. *)
258263264264+val date_released : t -> Date.t option
259265(** The date when the software or dataset was released.
260266261267 Format is [(year, month, day)], corresponding to ISO 8601 [YYYY-MM-DD]. *)
262262-val date_released : t -> Date.t option
263268269269+val doi : t -> string option
264270(** The Digital Object Identifier for the software or dataset.
265271266266- DOIs provide persistent, citable identifiers. This is a shorthand
267267- for a single DOI; use {!identifiers} for multiple DOIs or other
268268- identifier types. Example: ["10.5281/zenodo.1234567"] *)
269269-val doi : t -> string option
272272+ DOIs provide persistent, citable identifiers. This is a shorthand for a
273273+ single DOI; use {!identifiers} for multiple DOIs or other identifier types.
274274+ Example: ["10.5281/zenodo.1234567"] *)
270275276276+val identifiers : t -> Identifier.t list option
271277(** Additional identifiers beyond the primary DOI.
272278273273- Each identifier has a type (DOI, URL, SWH, other), value, and
274274- optional description. Useful for versioned DOIs, Software Heritage
275275- identifiers, or repository URLs. *)
276276-val identifiers : t -> Identifier.t list option
279279+ Each identifier has a type (DOI, URL, SWH, other), value, and optional
280280+ description. Useful for versioned DOIs, Software Heritage identifiers, or
281281+ repository URLs. *)
277282283283+val keywords : t -> string list option
278284(** Descriptive keywords for the work.
279285280286 Help with discoverability and categorization. Example:
281287 [["machine learning"; "image processing"; "python"]] *)
282282-val keywords : t -> string list option
283288289289+val license : t -> License.t option
284290(** The SPDX license identifier(s) for the work.
285291286286- Uses {{:https://spdx.org/licenses/}SPDX identifiers}. Multiple
287287- licenses imply an OR relationship (user may choose any).
288288- Example: ["MIT"], ["Apache-2.0"], or [["GPL-3.0-only"; "MIT"]]. *)
289289-val license : t -> License.t option
292292+ Uses {{:https://spdx.org/licenses/}SPDX identifiers}. Multiple licenses
293293+ imply an OR relationship (user may choose any). Example: ["MIT"],
294294+ ["Apache-2.0"], or [["GPL-3.0-only"; "MIT"]]. *)
290295296296+val preferred_citation : t -> Reference.t option
291297(** A reference to cite instead of the software itself.
292298293293- Used for "credit redirection" when authors prefer citation of
294294- a related publication (e.g., a methods paper) over the software.
295295- Note: Software citation principles recommend citing software
296296- directly; use this field judiciously. *)
297297-val preferred_citation : t -> Reference.t option
299299+ Used for "credit redirection" when authors prefer citation of a related
300300+ publication (e.g., a methods paper) over the software. Note: Software
301301+ citation principles recommend citing software directly; use this field
302302+ judiciously. *)
298303304304+val references : t -> Reference.t list option
299305(** Works that this software cites or depends upon.
300306301301- Functions like a bibliography, listing dependencies, foundational
302302- works, or related publications. Each reference includes full
303303- bibliographic metadata. *)
304304-val references : t -> Reference.t list option
307307+ Functions like a bibliography, listing dependencies, foundational works, or
308308+ related publications. Each reference includes full bibliographic metadata.
309309+*)
305310311311+val repository : t -> string option
306312(** URL to the repository where the software is developed.
307313308308- Typically a version control system URL. For source code repositories,
309309- prefer {!repository_code}. *)
310310-val repository : t -> string option
314314+ Typically a version control system URL. For source code repositories, prefer
315315+ {!repository_code}. *)
311316317317+val repository_artifact : t -> string option
312318(** URL to the built/compiled artifact repository.
313319314314- For binary distributions, package registries (npm, PyPI, CRAN),
315315- or container registries. *)
316316-val repository_artifact : t -> string option
320320+ For binary distributions, package registries (npm, PyPI, CRAN), or container
321321+ registries. *)
317322323323+val repository_code : t -> string option
318324(** URL to the source code repository.
319325320320- Typically a GitHub, GitLab, or similar URL where the source
321321- code is publicly accessible. *)
322322-val repository_code : t -> string option
326326+ Typically a GitHub, GitLab, or similar URL where the source code is publicly
327327+ accessible. *)
323328329329+val type_ : t -> Cff_type.t option
324330(** The type of work: [`Software] (default) or [`Dataset].
325331326332 Most CFF files describe software; use [`Dataset] for data packages. *)
327327-val type_ : t -> Cff_type.t option
328333334334+val url : t -> string option
329335(** The URL of the software or dataset homepage.
330336331337 A general landing page, documentation site, or project website. *)
332332-val url : t -> string option
333338339339+val version : t -> string option
334340(** The version string of the software or dataset.
335341336336- Can be any version format: semantic versioning (["1.2.3"]),
337337- date-based (["2024.01"]), or other schemes. *)
338338-val version : t -> string option
342342+ Can be any version format: semantic versioning (["1.2.3"]), date-based
343343+ (["2024.01"]), or other schemes. *)
339344340345(** {1 Formatting and Codec} *)
341346342342-(** Pretty-print a CFF value in a human-readable YAML-like format. *)
343347val pp : Format.formatter -> t -> unit
348348+(** Pretty-print a CFF value in a human-readable YAML-like format. *)
344349350350+val jsont : t Json.Codec.t
345351(** JSON/YAML codec for serialization and deserialization.
346352347353 Used internally by the YAML codec functions. *)
348348-val jsont : t Jsont.t
+61-44
lib/cff_address.ml
···7788(** Physical address information. *)
99module Address = struct
1010- type t =
1111- { address : string option
1212- ; city : string option
1313- ; region : string option
1414- ; post_code : string option
1515- ; country : string option (* ISO 3166-1 alpha-2 *)
1616- }
1010+ type t = {
1111+ address : string option;
1212+ city : string option;
1313+ region : string option;
1414+ post_code : string option;
1515+ country : string option (* ISO 3166-1 alpha-2 *);
1616+ }
17171818 let empty =
1919- { address = None; city = None; region = None; post_code = None; country = None }
2020- ;;
1919+ {
2020+ address = None;
2121+ city = None;
2222+ region = None;
2323+ post_code = None;
2424+ country = None;
2525+ }
21262227 let make ?address ?city ?region ?post_code ?country () =
2328 { address; city; region; post_code; country }
2424- ;;
25292630 let of_options ~address ~city ~region ~post_code ~country =
2731 { address; city; region; post_code; country }
2828- ;;
29323033 let address t = t.address
3134 let city t = t.city
···3437 let country t = t.country
35383639 let is_empty t =
3737- t.address = None
3838- && t.city = None
3939- && t.region = None
4040- && t.post_code = None
4040+ t.address = None && t.city = None && t.region = None && t.post_code = None
4141 && t.country = None
4242- ;;
43424443 let pp ppf t =
4544 let parts =
4646- List.filter_map Fun.id [ t.address; t.city; t.region; t.post_code; t.country ]
4545+ List.filter_map Fun.id
4646+ [ t.address; t.city; t.region; t.post_code; t.country ]
4747 in
4848 Format.pp_print_string ppf (String.concat ", " parts)
4949- ;;
50495150 let jsont_fields ~get obj =
5251 obj
5353- |> Jsont.Object.opt_mem "address" Jsont.string ~enc:(fun x -> (get x).address)
5454- |> Jsont.Object.opt_mem "city" Jsont.string ~enc:(fun x -> (get x).city)
5555- |> Jsont.Object.opt_mem "region" Jsont.string ~enc:(fun x -> (get x).region)
5656- |> Jsont.Object.opt_mem "post-code" Jsont.string ~enc:(fun x -> (get x).post_code)
5757- |> Jsont.Object.opt_mem "country" Jsont.string ~enc:(fun x -> (get x).country)
5858- ;;
5252+ |> Json.Codec.Object.opt_member "address" Json.Codec.string ~enc:(fun x ->
5353+ (get x).address)
5454+ |> Json.Codec.Object.opt_member "city" Json.Codec.string ~enc:(fun x ->
5555+ (get x).city)
5656+ |> Json.Codec.Object.opt_member "region" Json.Codec.string ~enc:(fun x ->
5757+ (get x).region)
5858+ |> Json.Codec.Object.opt_member "post-code" Json.Codec.string ~enc:(fun x ->
5959+ (get x).post_code)
6060+ |> Json.Codec.Object.opt_member "country" Json.Codec.string ~enc:(fun x ->
6161+ (get x).country)
5962end
60636164(** Contact information. *)
6265module Contact = struct
6363- type t =
6464- { email : string option
6565- ; tel : string option
6666- ; fax : string option
6767- ; website : string option
6868- ; orcid : string option
6969- }
6666+ type t = {
6767+ email : string option;
6868+ tel : string option;
6969+ fax : string option;
7070+ website : string option;
7171+ orcid : string option;
7272+ }
70737171- let empty = { email = None; tel = None; fax = None; website = None; orcid = None }
7272- let make ?email ?tel ?fax ?website ?orcid () = { email; tel; fax; website; orcid }
7373- let of_options ~email ~tel ~fax ~website ~orcid = { email; tel; fax; website; orcid }
7474+ let empty =
7575+ { email = None; tel = None; fax = None; website = None; orcid = None }
7676+7777+ let make ?email ?tel ?fax ?website ?orcid () =
7878+ { email; tel; fax; website; orcid }
7979+8080+ let of_options ~email ~tel ~fax ~website ~orcid =
8181+ { email; tel; fax; website; orcid }
8282+7483 let email t = t.email
7584 let tel t = t.tel
7685 let fax t = t.fax
···7887 let orcid t = t.orcid
79888089 let is_empty t =
8181- t.email = None && t.tel = None && t.fax = None && t.website = None && t.orcid = None
8282- ;;
9090+ t.email = None && t.tel = None && t.fax = None && t.website = None
9191+ && t.orcid = None
83928493 let pp ppf t =
8594 let parts =
8695 List.filter_map
8796 (fun (k, v) -> Option.map (fun v -> k ^ ": " ^ v) v)
8888- [ "email", t.email; "tel", t.tel; "website", t.website; "orcid", t.orcid ]
9797+ [
9898+ ("email", t.email);
9999+ ("tel", t.tel);
100100+ ("website", t.website);
101101+ ("orcid", t.orcid);
102102+ ]
89103 in
90104 Format.pp_print_string ppf (String.concat ", " parts)
9191- ;;
9210593106 let jsont_fields ~get obj =
94107 obj
9595- |> Jsont.Object.opt_mem "email" Jsont.string ~enc:(fun x -> (get x).email)
9696- |> Jsont.Object.opt_mem "tel" Jsont.string ~enc:(fun x -> (get x).tel)
9797- |> Jsont.Object.opt_mem "fax" Jsont.string ~enc:(fun x -> (get x).fax)
9898- |> Jsont.Object.opt_mem "website" Jsont.string ~enc:(fun x -> (get x).website)
9999- |> Jsont.Object.opt_mem "orcid" Jsont.string ~enc:(fun x -> (get x).orcid)
100100- ;;
108108+ |> Json.Codec.Object.opt_member "email" Json.Codec.string ~enc:(fun x ->
109109+ (get x).email)
110110+ |> Json.Codec.Object.opt_member "tel" Json.Codec.string ~enc:(fun x ->
111111+ (get x).tel)
112112+ |> Json.Codec.Object.opt_member "fax" Json.Codec.string ~enc:(fun x ->
113113+ (get x).fax)
114114+ |> Json.Codec.Object.opt_member "website" Json.Codec.string ~enc:(fun x ->
115115+ (get x).website)
116116+ |> Json.Codec.Object.opt_member "orcid" Json.Codec.string ~enc:(fun x ->
117117+ (get x).orcid)
101118end
+79-79
lib/cff_address.mli
···5566(** Physical address and contact information for CFF.
7788- CFF includes address and contact fields for both persons and entities.
99- This module provides types for these shared fields.
88+ CFF includes address and contact fields for both persons and entities. This
99+ module provides types for these shared fields.
10101111 {1 Address Fields}
1212···48484949 All fields are optional; an empty address is valid. *)
5050module Address : sig
5151- (** Physical address record. *)
5251 type t
5252+ (** Physical address record. *)
53535454+ val empty : t
5455 (** Empty address with all fields [None]. *)
5555- val empty : t
56565757+ val make :
5858+ ?address:string ->
5959+ ?city:string ->
6060+ ?region:string ->
6161+ ?post_code:string ->
6262+ ?country:string ->
6363+ unit ->
6464+ t
5765 (** Create an address with optional fields.
58665967 @param address Street address
···6169 @param region State, province, or administrative region
6270 @param post_code Postal code, ZIP code, or postcode
6371 @param country ISO 3166-1 alpha-2 country code *)
6464- val make
6565- : ?address:string
6666- -> ?city:string
6767- -> ?region:string
6868- -> ?post_code:string
6969- -> ?country:string
7070- -> unit
7171- -> t
72727373+ val of_options :
7474+ address:string option ->
7575+ city:string option ->
7676+ region:string option ->
7777+ post_code:string option ->
7878+ country:string option ->
7979+ t
7380 (** Create an address from option values directly.
74817582 Used internally by jsont decoders where fields are decoded as options. *)
7676- val of_options
7777- : address:string option
7878- -> city:string option
7979- -> region:string option
8080- -> post_code:string option
8181- -> country:string option
8282- -> t
83838484- (** Street address (e.g., ["77 Massachusetts Avenue"]). *)
8584 val address : t -> string option
8585+ (** Street address (e.g., ["77 Massachusetts Avenue"]). *)
86868787- (** City name (e.g., ["Cambridge"], ["London"]). *)
8887 val city : t -> string option
8888+ (** City name (e.g., ["Cambridge"], ["London"]). *)
89899090+ val region : t -> string option
9091 (** State, province, or region (e.g., ["Massachusetts"], ["Bavaria"]). *)
9191- val region : t -> string option
92929393- (** Postal or ZIP code (e.g., ["02139"], ["W1A 1AA"]). *)
9493 val post_code : t -> string option
9494+ (** Postal or ZIP code (e.g., ["02139"], ["W1A 1AA"]). *)
95959696+ val country : t -> string option
9697 (** ISO 3166-1 alpha-2 country code (e.g., ["US"], ["DE"], ["GB"]). *)
9797- val country : t -> string option
98989999- (** [true] if all fields are [None]. *)
10099 val is_empty : t -> bool
100100+ (** [true] if all fields are [None]. *)
101101102102- (** Pretty-print the address. *)
103102 val pp : Format.formatter -> t -> unit
103103+ (** Pretty-print the address. *)
104104105105+ val jsont_fields :
106106+ get:('a -> t) ->
107107+ ( 'a,
108108+ string option ->
109109+ string option ->
110110+ string option ->
111111+ string option ->
112112+ string option ->
113113+ 'b )
114114+ Json.Codec.Object.map ->
115115+ ('a, 'b) Json.Codec.Object.map
105116 (** Add address fields to a jsont object builder.
106117107118 This adds the five address fields (address, city, region, post-code,
···109120 [string option] arguments in that order.
110121111122 @param get Extracts the address from the parent type for encoding *)
112112- val jsont_fields
113113- : get:('a -> t)
114114- -> ( 'a
115115- , string option
116116- -> string option
117117- -> string option
118118- -> string option
119119- -> string option
120120- -> 'b )
121121- Jsont.Object.map
122122- -> ('a, 'b) Jsont.Object.map
123123end
124124125125(** Contact information.
126126127127- Electronic contact details for persons and entities. All fields
128128- are optional. *)
127127+ Electronic contact details for persons and entities. All fields are
128128+ optional. *)
129129module Contact : sig
130130- (** Contact information record. *)
131130 type t
131131+ (** Contact information record. *)
132132133133+ val empty : t
133134 (** Empty contact with all fields [None]. *)
134134- val empty : t
135135136136+ val make :
137137+ ?email:string ->
138138+ ?tel:string ->
139139+ ?fax:string ->
140140+ ?website:string ->
141141+ ?orcid:string ->
142142+ unit ->
143143+ t
136144 (** Create contact information with optional fields.
137145138146 @param email Email address
···140148 @param fax Fax number (any format)
141149 @param website Website URL
142150 @param orcid ORCID identifier URL *)
143143- val make
144144- : ?email:string
145145- -> ?tel:string
146146- -> ?fax:string
147147- -> ?website:string
148148- -> ?orcid:string
149149- -> unit
150150- -> t
151151152152+ val of_options :
153153+ email:string option ->
154154+ tel:string option ->
155155+ fax:string option ->
156156+ website:string option ->
157157+ orcid:string option ->
158158+ t
152159 (** Create contact info from option values directly.
153160154161 Used internally by jsont decoders where fields are decoded as options. *)
155155- val of_options
156156- : email:string option
157157- -> tel:string option
158158- -> fax:string option
159159- -> website:string option
160160- -> orcid:string option
161161- -> t
162162163163- (** Email address (e.g., ["jane.smith\@example.org"]). *)
164163 val email : t -> string option
164164+ (** Email address (e.g., ["jane.smith\@example.org"]). *)
165165166166- (** Telephone number. No specific format is required. *)
167166 val tel : t -> string option
167167+ (** Telephone number. No specific format is required. *)
168168169169+ val fax : t -> string option
169170 (** Fax number. No specific format is required. *)
170170- val fax : t -> string option
171171172172+ val website : t -> string option
172173 (** Website URL (e.g., ["https://example.org/~jsmith"]). *)
173173- val website : t -> string option
174174175175+ val orcid : t -> string option
175176 (** ORCID identifier as a URL.
176177177177- ORCID (Open Researcher and Contributor ID) provides persistent
178178- digital identifiers for researchers.
178178+ ORCID (Open Researcher and Contributor ID) provides persistent digital
179179+ identifiers for researchers.
179180180181 Format: ["https://orcid.org/XXXX-XXXX-XXXX-XXXX"]
181182182183 Example: ["https://orcid.org/0000-0001-2345-6789"] *)
183183- val orcid : t -> string option
184184185185+ val is_empty : t -> bool
185186 (** [true] if all fields are [None]. *)
186186- val is_empty : t -> bool
187187188188- (** Pretty-print the contact information. *)
189188 val pp : Format.formatter -> t -> unit
189189+ (** Pretty-print the contact information. *)
190190191191+ val jsont_fields :
192192+ get:('a -> t) ->
193193+ ( 'a,
194194+ string option ->
195195+ string option ->
196196+ string option ->
197197+ string option ->
198198+ string option ->
199199+ 'b )
200200+ Json.Codec.Object.map ->
201201+ ('a, 'b) Json.Codec.Object.map
191202 (** Add contact fields to a jsont object builder.
192203193193- This adds the five contact fields (email, tel, fax, website, orcid)
194194- to an object codec. The decoder function must accept five
195195- [string option] arguments in that order.
204204+ This adds the five contact fields (email, tel, fax, website, orcid) to an
205205+ object codec. The decoder function must accept five [string option]
206206+ arguments in that order.
196207197208 @param get Extracts the contact from the parent type for encoding *)
198198- val jsont_fields
199199- : get:('a -> t)
200200- -> ( 'a
201201- , string option
202202- -> string option
203203- -> string option
204204- -> string option
205205- -> string option
206206- -> 'b )
207207- Jsont.Object.map
208208- -> ('a, 'b) Jsont.Object.map
209209end
+141-183
lib/cff_author.ml
···7788(** A person (individual author/contributor). *)
99module Person = struct
1010- type t =
1111- { family_names : string option
1212- ; given_names : string option
1313- ; name_particle : string option
1414- ; name_suffix : string option
1515- ; alias : string option
1616- ; affiliation : string option
1717- ; address : Cff_address.Address.t
1818- ; contact : Cff_address.Contact.t
1919- }
1010+ type t = {
1111+ family_names : string option;
1212+ given_names : string option;
1313+ name_particle : string option;
1414+ name_suffix : string option;
1515+ alias : string option;
1616+ affiliation : string option;
1717+ address : Cff_address.Address.t;
1818+ contact : Cff_address.Contact.t;
1919+ }
20202121- let make
2222- ?family_names
2323- ?given_names
2424- ?name_particle
2525- ?name_suffix
2626- ?alias
2727- ?affiliation
2828- ?(address = Cff_address.Address.empty)
2929- ?(contact = Cff_address.Contact.empty)
3030- ()
3131- =
3232- { family_names
3333- ; given_names
3434- ; name_particle
3535- ; name_suffix
3636- ; alias
3737- ; affiliation
3838- ; address
3939- ; contact
2121+ let make ?family_names ?given_names ?name_particle ?name_suffix ?alias
2222+ ?affiliation ?(address = Cff_address.Address.empty)
2323+ ?(contact = Cff_address.Contact.empty) () =
2424+ {
2525+ family_names;
2626+ given_names;
2727+ name_particle;
2828+ name_suffix;
2929+ alias;
3030+ affiliation;
3131+ address;
3232+ contact;
4033 }
4141- ;;
42344335 let family_names t = t.family_names
4436 let given_names t = t.given_names
···5749 match t.name_suffix with
5850 | Some suffix -> base ^ ", " ^ suffix
5951 | None -> base
6060- ;;
61526253 let email t = Cff_address.Contact.email t.contact
6354 let orcid t = Cff_address.Contact.orcid t.contact
···6657 let pp ppf t =
6758 Format.fprintf ppf "%s" (full_name t);
6859 Option.iter (Format.fprintf ppf " (%s)") t.affiliation
6969- ;;
70607161 let jsont =
7272- Jsont.Object.map
7373- ~kind:"Person"
6262+ Json.Codec.Object.map ~kind:"Person"
7463 (fun
7575- family_names
7676- given_names
7777- name_particle
7878- name_suffix
7979- alias
8080- affiliation
8181- address
8282- city
8383- region
8484- post_code
8585- country
8686- email
8787- tel
8888- fax
8989- website
9090- orcid
9191- ->
9292- let address =
9393- Cff_address.Address.of_options ~address ~city ~region ~post_code ~country
9494- in
9595- let contact = Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid in
9696- { family_names
9797- ; given_names
9898- ; name_particle
9999- ; name_suffix
100100- ; alias
101101- ; affiliation
102102- ; address
103103- ; contact
104104- })
105105- |> Jsont.Object.opt_mem "family-names" Jsont.string ~enc:(fun p -> p.family_names)
106106- |> Jsont.Object.opt_mem "given-names" Jsont.string ~enc:(fun p -> p.given_names)
107107- |> Jsont.Object.opt_mem "name-particle" Jsont.string ~enc:(fun p -> p.name_particle)
108108- |> Jsont.Object.opt_mem "name-suffix" Jsont.string ~enc:(fun p -> p.name_suffix)
109109- |> Jsont.Object.opt_mem "alias" Jsont.string ~enc:(fun p -> p.alias)
110110- |> Jsont.Object.opt_mem "affiliation" Jsont.string ~enc:(fun p -> p.affiliation)
6464+ family_names
6565+ given_names
6666+ name_particle
6767+ name_suffix
6868+ alias
6969+ affiliation
7070+ address
7171+ city
7272+ region
7373+ post_code
7474+ country
7575+ email
7676+ tel
7777+ fax
7878+ website
7979+ orcid
8080+ ->
8181+ let address =
8282+ Cff_address.Address.of_options ~address ~city ~region ~post_code
8383+ ~country
8484+ in
8585+ let contact =
8686+ Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid
8787+ in
8888+ {
8989+ family_names;
9090+ given_names;
9191+ name_particle;
9292+ name_suffix;
9393+ alias;
9494+ affiliation;
9595+ address;
9696+ contact;
9797+ })
9898+ |> Json.Codec.Object.opt_member "family-names" Json.Codec.string
9999+ ~enc:(fun p -> p.family_names)
100100+ |> Json.Codec.Object.opt_member "given-names" Json.Codec.string
101101+ ~enc:(fun p -> p.given_names)
102102+ |> Json.Codec.Object.opt_member "name-particle" Json.Codec.string
103103+ ~enc:(fun p -> p.name_particle)
104104+ |> Json.Codec.Object.opt_member "name-suffix" Json.Codec.string
105105+ ~enc:(fun p -> p.name_suffix)
106106+ |> Json.Codec.Object.opt_member "alias" Json.Codec.string ~enc:(fun p ->
107107+ p.alias)
108108+ |> Json.Codec.Object.opt_member "affiliation" Json.Codec.string
109109+ ~enc:(fun p -> p.affiliation)
111110 |> Cff_address.Address.jsont_fields ~get:(fun p -> p.address)
112111 |> Cff_address.Contact.jsont_fields ~get:(fun p -> p.contact)
113113- |> Jsont.Object.skip_unknown
114114- |> Jsont.Object.finish
115115- ;;
112112+ |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal
116113end
117114118115(** An entity (organization, team, conference, etc.). *)
119116module Entity = struct
120120- type t =
121121- { name : string
122122- ; alias : string option
123123- ; address : Cff_address.Address.t
124124- ; contact : Cff_address.Contact.t
125125- ; date_start : Cff_date.t option
126126- ; date_end : Cff_date.t option
127127- ; location : string option
128128- }
117117+ type t = {
118118+ name : string;
119119+ alias : string option;
120120+ address : Cff_address.Address.t;
121121+ contact : Cff_address.Contact.t;
122122+ date_start : Cff_date.t option;
123123+ date_end : Cff_date.t option;
124124+ location : string option;
125125+ }
129126130130- let make
131131- ~name
132132- ?alias
133133- ?(address = Cff_address.Address.empty)
134134- ?(contact = Cff_address.Contact.empty)
135135- ?date_start
136136- ?date_end
137137- ?location
138138- ()
139139- =
127127+ let make ~name ?alias ?(address = Cff_address.Address.empty)
128128+ ?(contact = Cff_address.Contact.empty) ?date_start ?date_end ?location ()
129129+ =
140130 { name; alias; address; contact; date_start; date_end; location }
141141- ;;
142131143132 let name t = t.name
144133 let alias t = t.alias
···154143 let pp ppf t =
155144 Format.pp_print_string ppf t.name;
156145 Option.iter (Format.fprintf ppf " (%s)") t.alias
157157- ;;
158146159147 let jsont =
160160- Jsont.Object.map
161161- ~kind:"Entity"
148148+ Json.Codec.Object.map ~kind:"Entity"
162149 (fun
163163- name
164164- alias
165165- address
166166- city
167167- region
168168- post_code
169169- country
170170- email
171171- tel
172172- fax
173173- website
174174- orcid
175175- date_start
176176- date_end
177177- location
178178- ->
179179- let address =
180180- Cff_address.Address.of_options ~address ~city ~region ~post_code ~country
181181- in
182182- let contact = Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid in
183183- { name; alias; address; contact; date_start; date_end; location })
184184- |> Jsont.Object.mem "name" Jsont.string ~enc:(fun e -> e.name)
185185- |> Jsont.Object.opt_mem "alias" Jsont.string ~enc:(fun e -> e.alias)
150150+ name
151151+ alias
152152+ address
153153+ city
154154+ region
155155+ post_code
156156+ country
157157+ email
158158+ tel
159159+ fax
160160+ website
161161+ orcid
162162+ date_start
163163+ date_end
164164+ location
165165+ ->
166166+ let address =
167167+ Cff_address.Address.of_options ~address ~city ~region ~post_code
168168+ ~country
169169+ in
170170+ let contact =
171171+ Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid
172172+ in
173173+ { name; alias; address; contact; date_start; date_end; location })
174174+ |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun e -> e.name)
175175+ |> Json.Codec.Object.opt_member "alias" Json.Codec.string ~enc:(fun e ->
176176+ e.alias)
186177 |> Cff_address.Address.jsont_fields ~get:(fun e -> e.address)
187178 |> Cff_address.Contact.jsont_fields ~get:(fun e -> e.contact)
188188- |> Jsont.Object.opt_mem "date-start" Cff_date.jsont ~enc:(fun e -> e.date_start)
189189- |> Jsont.Object.opt_mem "date-end" Cff_date.jsont ~enc:(fun e -> e.date_end)
190190- |> Jsont.Object.opt_mem "location" Jsont.string ~enc:(fun e -> e.location)
191191- |> Jsont.Object.skip_unknown
192192- |> Jsont.Object.finish
193193- ;;
179179+ |> Json.Codec.Object.opt_member "date-start" Cff_date.jsont ~enc:(fun e ->
180180+ e.date_start)
181181+ |> Json.Codec.Object.opt_member "date-end" Cff_date.jsont ~enc:(fun e ->
182182+ e.date_end)
183183+ |> Json.Codec.Object.opt_member "location" Json.Codec.string ~enc:(fun e ->
184184+ e.location)
185185+ |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal
194186end
195187188188+type t = [ `Person of Person.t | `Entity of Entity.t ]
196189(** An author can be either a Person or an Entity. *)
197197-type t =
198198- [ `Person of Person.t
199199- | `Entity of Entity.t
200200- ]
201190202202-let person
203203- ?family_names
204204- ?given_names
205205- ?name_particle
206206- ?name_suffix
207207- ?alias
208208- ?affiliation
209209- ?address
210210- ?contact
211211- ()
212212- =
191191+let person ?family_names ?given_names ?name_particle ?name_suffix ?alias
192192+ ?affiliation ?address ?contact () =
213193 `Person
214214- (Person.make
215215- ?family_names
216216- ?given_names
217217- ?name_particle
218218- ?name_suffix
219219- ?alias
220220- ?affiliation
221221- ?address
222222- ?contact
223223- ())
224224-;;
194194+ (Person.make ?family_names ?given_names ?name_particle ?name_suffix ?alias
195195+ ?affiliation ?address ?contact ())
225196226197let entity ~name ?alias ?address ?contact ?date_start ?date_end ?location () =
227227- `Entity (Entity.make ~name ?alias ?address ?contact ?date_start ?date_end ?location ())
228228-;;
198198+ `Entity
199199+ (Entity.make ~name ?alias ?address ?contact ?date_start ?date_end ?location
200200+ ())
229201230202let name = function
231203 | `Person p -> Person.full_name p
232204 | `Entity e -> Entity.name e
233233-;;
234205235235-let orcid = function
236236- | `Person p -> Person.orcid p
237237- | `Entity e -> Entity.orcid e
238238-;;
239239-240240-let email = function
241241- | `Person p -> Person.email p
242242- | `Entity e -> Entity.email e
243243-;;
206206+let orcid = function `Person p -> Person.orcid p | `Entity e -> Entity.orcid e
207207+let email = function `Person p -> Person.email p | `Entity e -> Entity.email e
244208245209let pp ppf = function
246210 | `Person p -> Person.pp ppf p
247211 | `Entity e -> Entity.pp ppf e
248248-;;
249212250250-(* Jsont codec that discriminates based on "name" field presence.
213213+(* JSON codec that discriminates based on "name" field presence.
251214 If "name" is present -> Entity, otherwise -> Person *)
252215let jsont =
253216 (* Check if json object has "name" member *)
254217 let has_name_member = function
255255- | Jsont.Object (members, _) -> Option.is_some (Jsont.Json.find_mem "name" members)
218218+ | Json.Object (members, _) ->
219219+ Option.is_some (Json.Value.member_key "name" members)
256220 | _ -> false
257221 in
258222 let dec_json j =
259259- if has_name_member j
260260- then (
261261- match Jsont.Json.decode' Entity.jsont j with
223223+ if has_name_member j then
224224+ match Json.Codec.decode Entity.jsont j with
262225 | Ok e -> `Entity e
263226 | Error err ->
264264- Jsont.Error.msgf Jsont.Meta.none "Invalid entity: %s" (Jsont.Error.to_string err))
265265- else (
266266- match Jsont.Json.decode' Person.jsont j with
227227+ Json.Error.failf Json.Error.Meta.none "Invalid entity: %s"
228228+ (Json.Error.to_string err)
229229+ else
230230+ match Json.Codec.decode Person.jsont j with
267231 | Ok p -> `Person p
268232 | Error err ->
269269- Jsont.Error.msgf Jsont.Meta.none "Invalid person: %s" (Jsont.Error.to_string err))
233233+ Json.Error.failf Json.Error.Meta.none "Invalid person: %s"
234234+ (Json.Error.to_string err)
270235 in
271236 let enc_author = function
272272- | `Person p ->
273273- (match Jsont.Json.encode' Person.jsont p with
274274- | Ok j -> j
275275- | Error _ -> assert false)
276276- | `Entity e ->
277277- (match Jsont.Json.encode' Entity.jsont e with
278278- | Ok j -> j
279279- | Error _ -> assert false)
237237+ | `Person p -> Json.Codec.encode Person.jsont p
238238+ | `Entity e -> Json.Codec.encode Entity.jsont e
280239 in
281281- Jsont.json |> Jsont.map ~dec:dec_json ~enc:enc_author
282282-;;
240240+ Json.Codec.Value.t |> Json.Codec.map ~dec:dec_json ~enc:enc_author
+100-109
lib/cff_author.mli
···7788 CFF distinguishes between two types of authors:
991010- - {b Persons}: Individual humans identified by name components
1111- (family names, given names, etc.)
1212- - {b Entities}: Organizations, institutions, teams, projects, or
1313- conferences identified by a single [name] field
1010+ - {b Persons}: Individual humans identified by name components (family
1111+ names, given names, etc.)
1212+ - {b Entities}: Organizations, institutions, teams, projects, or conferences
1313+ identified by a single [name] field
14141515- When parsing YAML, the library discriminates based on the presence
1616- of a [name] field: if present, the entry is an entity; otherwise,
1717- it's a person.
1515+ When parsing YAML, the library discriminates based on the presence of a
1616+ [name] field: if present, the entry is an entity; otherwise, it's a person.
18171918 {1 Quick Example}
20192120 {[
2222- (* Create a person author *)
2323- let jane = Cff.Author.person
2424- ~family_names:"Smith"
2525- ~given_names:"Jane"
2626- ~affiliation:"MIT"
2727- ()
2121+ (* Create a person author *)
2222+ let jane =
2323+ Cff.Author.person ~family_names:"Smith" ~given_names:"Jane"
2424+ ~affiliation:"MIT" ()
28252929- (* Create an entity author *)
3030- let mozilla = Cff.Author.entity
3131- ~name:"Mozilla Foundation"
3232- ()
2626+ (* Create an entity author *)
2727+ let mozilla = Cff.Author.entity ~name:"Mozilla Foundation" ()
33283434- (* Pattern match on authors *)
3535- let show_author = function
3636- | `Person p -> Cff.Author.Person.full_name p
3737- | `Entity e -> Cff.Author.Entity.name e
2929+ (* Pattern match on authors *)
3030+ let show_author = function
3131+ | `Person p -> Cff.Author.Person.full_name p
3232+ | `Entity e -> Cff.Author.Entity.name e
3833 ]} *)
39344035(** {1 Person}
···4742 - Optional physical address
4843 - Optional contact information (email, ORCID, website) *)
4944module Person : sig
5050- (** A person record. *)
5145 type t
4646+ (** A person record. *)
52474848+ val make :
4949+ ?family_names:string ->
5050+ ?given_names:string ->
5151+ ?name_particle:string ->
5252+ ?name_suffix:string ->
5353+ ?alias:string ->
5454+ ?affiliation:string ->
5555+ ?address:Cff_address.Address.t ->
5656+ ?contact:Cff_address.Contact.t ->
5757+ unit ->
5858+ t
5359 (** Create a person with optional fields.
54605561 At minimum, provide [family_names] or [given_names].
···6268 @param affiliation Institution or organization name
6369 @param address Physical address
6470 @param contact Contact information (email, ORCID, website, etc.) *)
6565- val make
6666- : ?family_names:string
6767- -> ?given_names:string
6868- -> ?name_particle:string
6969- -> ?name_suffix:string
7070- -> ?alias:string
7171- -> ?affiliation:string
7272- -> ?address:Cff_address.Address.t
7373- -> ?contact:Cff_address.Contact.t
7474- -> unit
7575- -> t
76717772 (** {2 Name Fields} *)
78737979- (** The person's family name (surname, last name). *)
8074 val family_names : t -> string option
7575+ (** The person's family name (surname, last name). *)
81768282- (** The person's given name(s) (first name, forenames). *)
8377 val given_names : t -> string option
7878+ (** The person's given name(s) (first name, forenames). *)
84798585- (** Name connector appearing before family name.
8686- Examples: ["von"] in "Ludwig von Beethoven". *)
8780 val name_particle : t -> string option
8181+ (** Name connector appearing before family name. Examples: ["von"] in "Ludwig
8282+ von Beethoven". *)
88838989- (** Generational or honorary suffix.
9090- Examples: ["Jr."], ["Sr."], ["III"]. *)
9184 val name_suffix : t -> string option
8585+ (** Generational or honorary suffix. Examples: ["Jr."], ["Sr."], ["III"]. *)
92868787+ val alias : t -> string option
9388 (** Nickname, pseudonym, or alternative name. *)
9494- val alias : t -> string option
95899696- (** Format name as "Given Particle Family, Suffix".
9797- Examples: ["Jane Smith"], ["Guido van Rossum"]. *)
9890 val full_name : t -> string
9191+ (** Format name as "Given Particle Family, Suffix". Examples: ["Jane Smith"],
9292+ ["Guido van Rossum"]. *)
999310094 (** {2 Affiliation and Location} *)
10195102102- (** The person's institutional affiliation. *)
10396 val affiliation : t -> string option
9797+ (** The person's institutional affiliation. *)
10498105105- (** Physical address information. *)
10699 val address : t -> Cff_address.Address.t
100100+ (** Physical address information. *)
107101108102 (** {2 Contact Information} *)
109103104104+ val contact : t -> Cff_address.Contact.t
110105 (** Full contact information record. *)
111111- val contact : t -> Cff_address.Contact.t
112106107107+ val email : t -> string option
113108 (** The person's email address. *)
114114- val email : t -> string option
115109116116- (** The person's ORCID identifier URL. *)
117110 val orcid : t -> string option
111111+ (** The person's ORCID identifier URL. *)
118112119119- (** The person's website URL. *)
120113 val website : t -> string option
114114+ (** The person's website URL. *)
121115122116 (** {2 Formatting and Codec} *)
123117124124- (** Pretty-print as "Full Name (affiliation)". *)
125118 val pp : Format.formatter -> t -> unit
119119+ (** Pretty-print as "Full Name (affiliation)". *)
126120121121+ val jsont : t Json.Codec.t
127122 (** JSON/YAML codec for person records. *)
128128- val jsont : t Jsont.t
129123end
130124131125(** {1 Entity}
···138132 - Open source projects (["The Rust Project"])
139133 - Academic conferences (["ICSE 2024"]) with dates
140134141141- Entities are distinguished from persons in YAML by the presence
142142- of a required [name] field. *)
135135+ Entities are distinguished from persons in YAML by the presence of a
136136+ required [name] field. *)
143137module Entity : sig
138138+ type t
144139 (** An entity record. *)
145145- type t
146140141141+ val make :
142142+ name:string ->
143143+ ?alias:string ->
144144+ ?address:Cff_address.Address.t ->
145145+ ?contact:Cff_address.Contact.t ->
146146+ ?date_start:Cff_date.t ->
147147+ ?date_end:Cff_date.t ->
148148+ ?location:string ->
149149+ unit ->
150150+ t
147151 (** Create an entity.
148152149153 @param name The entity's official name (required)
···153157 @param date_start Event start date (for conferences)
154158 @param date_end Event end date (for conferences)
155159 @param location Event location description *)
156156- val make
157157- : name:string
158158- -> ?alias:string
159159- -> ?address:Cff_address.Address.t
160160- -> ?contact:Cff_address.Contact.t
161161- -> ?date_start:Cff_date.t
162162- -> ?date_end:Cff_date.t
163163- -> ?location:string
164164- -> unit
165165- -> t
166160167161 (** {2 Core Fields} *)
168162169169- (** The entity's official name. *)
170163 val name : t -> string
164164+ (** The entity's official name. *)
171165172172- (** Short name, acronym, or alternative name. *)
173166 val alias : t -> string option
167167+ (** Short name, acronym, or alternative name. *)
174168175169 (** {2 Location} *)
176170171171+ val address : t -> Cff_address.Address.t
177172 (** Physical address information. *)
178178- val address : t -> Cff_address.Address.t
179173174174+ val location : t -> string option
180175 (** Event location description (for conferences). *)
181181- val location : t -> string option
182176183177 (** {2 Event Dates} *)
184178185185- (** The start date of the event (for conferences). *)
186179 val date_start : t -> Cff_date.t option
180180+ (** The start date of the event (for conferences). *)
187181188188- (** The end date of the event (for conferences). *)
189182 val date_end : t -> Cff_date.t option
183183+ (** The end date of the event (for conferences). *)
190184191185 (** {2 Contact Information} *)
192186193193- (** Full contact information record. *)
194187 val contact : t -> Cff_address.Contact.t
188188+ (** Full contact information record. *)
195189190190+ val email : t -> string option
196191 (** The entity's contact email. *)
197197- val email : t -> string option
198192193193+ val orcid : t -> string option
199194 (** The entity's ORCID (organizations can have ORCIDs). *)
200200- val orcid : t -> string option
201195202202- (** The entity's official website URL. *)
203196 val website : t -> string option
197197+ (** The entity's official website URL. *)
204198205199 (** {2 Formatting and Codec} *)
206200207207- (** Pretty-print as "Name (alias)". *)
208201 val pp : Format.formatter -> t -> unit
202202+ (** Pretty-print as "Name (alias)". *)
209203204204+ val jsont : t Json.Codec.t
210205 (** JSON/YAML codec for entity records. *)
211211- val jsont : t Jsont.t
212206end
213207214208(** {1 Author Type}
215209216216- The main author type is a polymorphic variant that can hold either
217217- a person or an entity. *)
210210+ The main author type is a polymorphic variant that can hold either a person
211211+ or an entity. *)
218212213213+type t = [ `Person of Person.t | `Entity of Entity.t ]
219214(** An author: either a person or an entity. *)
220220-type t =
221221- [ `Person of Person.t
222222- | `Entity of Entity.t
223223- ]
224215225216(** {2 Smart Constructors} *)
226217218218+val person :
219219+ ?family_names:string ->
220220+ ?given_names:string ->
221221+ ?name_particle:string ->
222222+ ?name_suffix:string ->
223223+ ?alias:string ->
224224+ ?affiliation:string ->
225225+ ?address:Cff_address.Address.t ->
226226+ ?contact:Cff_address.Contact.t ->
227227+ unit ->
228228+ t
227229(** Create a person author directly.
228230229231 Equivalent to [`Person (Person.make ...)]. *)
230230-val person
231231- : ?family_names:string
232232- -> ?given_names:string
233233- -> ?name_particle:string
234234- -> ?name_suffix:string
235235- -> ?alias:string
236236- -> ?affiliation:string
237237- -> ?address:Cff_address.Address.t
238238- -> ?contact:Cff_address.Contact.t
239239- -> unit
240240- -> t
241232233233+val entity :
234234+ name:string ->
235235+ ?alias:string ->
236236+ ?address:Cff_address.Address.t ->
237237+ ?contact:Cff_address.Contact.t ->
238238+ ?date_start:Cff_date.t ->
239239+ ?date_end:Cff_date.t ->
240240+ ?location:string ->
241241+ unit ->
242242+ t
242243(** Create an entity author directly.
243244244245 Equivalent to [`Entity (Entity.make ...)]. *)
245245-val entity
246246- : name:string
247247- -> ?alias:string
248248- -> ?address:Cff_address.Address.t
249249- -> ?contact:Cff_address.Contact.t
250250- -> ?date_start:Cff_date.t
251251- -> ?date_end:Cff_date.t
252252- -> ?location:string
253253- -> unit
254254- -> t
255246256247(** {2 Common Accessors} *)
257248249249+val name : t -> string
258250(** Get the display name.
259251260260- For persons, returns the full formatted name.
261261- For entities, returns the entity name. *)
262262-val name : t -> string
252252+ For persons, returns the full formatted name. For entities, returns the
253253+ entity name. *)
263254255255+val orcid : t -> string option
264256(** Get the ORCID if present. Works for both persons and entities. *)
265265-val orcid : t -> string option
266257267267-(** Get the email if present. Works for both persons and entities. *)
268258val email : t -> string option
259259+(** Get the email if present. Works for both persons and entities. *)
269260270261(** {2 Formatting and Codec} *)
271262272272-(** Pretty-print the author. *)
273263val pp : Format.formatter -> t -> unit
264264+(** Pretty-print the author. *)
274265266266+val jsont : t Json.Codec.t
275267(** JSON/YAML codec that discriminates based on [name] field presence.
276268277269 When decoding:
278270 - If the object has a [name] field -> Entity
279271 - Otherwise -> Person *)
280280-val jsont : t Jsont.t
+8-11
lib/cff_country.ml
···1313 try
1414 let _ = ISO3166.alpha2_of_string s in
1515 Ok s
1616- with
1717- | Invalid_argument _ -> Error (`Invalid_country s)
1818-;;
1616+ with Invalid_argument _ -> Error (`Invalid_country s)
19172018let to_string t = t
21192220let to_iso3166 t =
2323- try Some (ISO3166.alpha2_to_country (ISO3166.alpha2_of_string t)) with
2424- | Invalid_argument _ -> None
2525-;;
2121+ try Some (ISO3166.alpha2_to_country (ISO3166.alpha2_of_string t))
2222+ with Invalid_argument _ -> None
26232724let name t = Option.map ISO3166.Country.name (to_iso3166 t)
2825let equal = String.equal
2926let compare = String.compare
3027let pp ppf t = Format.pp_print_string ppf t
31283232-(* Jsont codec for country codes *)
2929+(* JSON codec for country codes *)
3330let jsont =
3431 let dec s =
3532 match of_string s with
3633 | Ok c -> c
3734 | Error (`Invalid_country s) ->
3838- Jsont.Error.msgf Jsont.Meta.none "Invalid ISO 3166-1 alpha-2 country code: %s" s
3535+ Json.Error.failf Json.Error.Meta.none
3636+ "Invalid ISO 3166-1 alpha-2 country code: %s" s
3937 in
4038 let enc t = to_string t in
4141- Jsont.string |> Jsont.map ~dec ~enc
4242-;;
3939+ Json.Codec.string |> Json.Codec.map ~dec ~enc
43404441(* Lenient codec that accepts any string *)
4545-let jsont_lenient = Jsont.string
4242+let jsont_lenient = Json.Codec.string
+19-19
lib/cff_country.mli
···5566(** ISO 3166-1 alpha-2 country codes for CFF.
7788- CFF uses {{:https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2}
99- ISO 3166-1 alpha-2} two-letter country codes for the [country]
1010- field on persons and entities.
88+ CFF uses
99+ {{:https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2} ISO 3166-1 alpha-2}
1010+ two-letter country codes for the [country] field on persons and entities.
11111212 {1 Format}
1313···26262727 {1 Validation}
28282929- This module validates country codes against the {!ISO3166} library,
3030- which maintains the official list of assigned codes.
2929+ This module validates country codes against the {!ISO3166} library, which
3030+ maintains the official list of assigned codes.
31313232 {1 Example}
3333···3939 country: DE
4040 ]} *)
41414242-(** An ISO 3166-1 alpha-2 country code (two uppercase letters). *)
4342type t = string
4343+(** An ISO 3166-1 alpha-2 country code (two uppercase letters). *)
44444545+val of_string : string -> (t, [> `Invalid_country of string ]) result
4546(** Parse and validate a country code.
46474747- Case-insensitive: ["us"], ["US"], and ["Us"] all produce ["US"].
4848- Returns [Error (`Invalid_country s)] for unknown codes. *)
4949-val of_string : string -> (t, [> `Invalid_country of string ]) result
4848+ Case-insensitive: ["us"], ["US"], and ["Us"] all produce ["US"]. Returns
4949+ [Error (`Invalid_country s)] for unknown codes. *)
50505151-(** Return the uppercase country code. *)
5251val to_string : t -> string
5252+(** Return the uppercase country code. *)
53535454+val to_iso3166 : t -> ISO3166.Country.t option
5455(** Look up the full country record from {!ISO3166}.
55565657 Returns [None] if the code is not in the ISO 3166-1 list. *)
5757-val to_iso3166 : t -> ISO3166.Country.t option
58585959+val name : t -> string option
5960(** Get the country name if the code is valid.
60616162 Examples:
6263 - [name "US" = Some "United States of America"]
6364 - [name "GB" = Some "United Kingdom of Great Britain and Northern Ireland"]
6465 - [name "XX" = None] *)
6565-val name : t -> string option
66666767-(** Country code equality (case-sensitive after normalization). *)
6867val equal : t -> t -> bool
6868+(** Country code equality (case-sensitive after normalization). *)
69697070+val compare : t -> t -> int
7071(** Alphabetical comparison of country codes. *)
7171-val compare : t -> t -> int
72727373+val pp : Format.formatter -> t -> unit
7374(** Pretty-print the country code. *)
7474-val pp : Format.formatter -> t -> unit
75757676+val jsont : t Json.Codec.t
7677(** JSON/YAML codec that validates country codes.
77787879 Returns an error for invalid ISO 3166-1 alpha-2 codes. *)
7979-val jsont : t Jsont.t
80808181+val jsont_lenient : t Json.Codec.t
8182(** JSON/YAML codec that accepts any string.
82838383- Use this when parsing CFF files that may contain non-standard
8484- country codes. *)
8585-val jsont_lenient : t Jsont.t
8484+ Use this when parsing CFF files that may contain non-standard country codes.
8585+*)
+16-14
lib/cff_date.ml
···1010let of_string s =
1111 (* CFF dates are YYYY-MM-DD format *)
1212 match String.split_on_char '-' s with
1313- | [ y; m; d ] ->
1414- (match int_of_string_opt y, int_of_string_opt m, int_of_string_opt d with
1515- | Some year, Some month, Some day ->
1616- (* Validate the date components *)
1717- if year >= 0 && year <= 9999 && month >= 1 && month <= 12 && day >= 1 && day <= 31
1818- then Ok (year, month, day)
1919- else Error (`Invalid_date s)
2020- | _ -> Error (`Invalid_date s))
1313+ | [ y; m; d ] -> (
1414+ match (int_of_string_opt y, int_of_string_opt m, int_of_string_opt d) with
1515+ | Some year, Some month, Some day ->
1616+ (* Validate the date components *)
1717+ if
1818+ year >= 0 && year <= 9999 && month >= 1 && month <= 12 && day >= 1
1919+ && day <= 31
2020+ then Ok (year, month, day)
2121+ else Error (`Invalid_date s)
2222+ | _ -> Error (`Invalid_date s))
2123 | _ -> Error (`Invalid_date s)
2222-;;
23242424-let to_string (year, month, day) = Printf.sprintf "%04d-%02d-%02d" year month day
2525+let to_string (year, month, day) =
2626+ Printf.sprintf "%04d-%02d-%02d" year month day
2727+2528let year (y, _, _) = y
2629let month (_, m, _) = m
2730let day (_, _, d) = d
···2932let compare = Stdlib.compare
3033let pp ppf date = Format.pp_print_string ppf (to_string date)
31343232-(* Jsont codec for dates *)
3535+(* JSON codec for dates *)
3336let jsont =
3437 let dec s =
3538 match of_string s with
3639 | Ok d -> d
3740 | Error (`Invalid_date s) ->
3838- Jsont.Error.msgf Jsont.Meta.none "Invalid date format: %s" s
4141+ Json.Error.failf Json.Error.Meta.none "Invalid date format: %s" s
3942 in
4043 let enc date = to_string date in
4141- Jsont.string |> Jsont.map ~dec ~enc
4242-;;
4444+ Json.Codec.string |> Json.Codec.map ~dec ~enc
+17-17
lib/cff_date.mli
···5566(** Date handling for CFF.
7788- CFF uses ISO 8601 date format ([YYYY-MM-DD]) for all date fields.
99- This module wraps {!Ptime.date} for date representation and provides
1010- parsing and formatting functions.
88+ CFF uses ISO 8601 date format ([YYYY-MM-DD]) for all date fields. This
99+ module wraps {!Ptime.date} for date representation and provides parsing and
1010+ formatting functions.
11111212 {1 Date Fields in CFF}
1313···42424343 {1 Year-Only Dates}
44444545- For historical works or when only the year is known, use the [year]
4646- field (an integer) instead of a full date. *)
4545+ For historical works or when only the year is known, use the [year] field
4646+ (an integer) instead of a full date. *)
47474848+type t = Ptime.date
4849(** A date as [(year, month, day)] tuple.
49505051 The tuple contains:
5152 - [year]: Four-digit year (e.g., [2024])
5253 - [month]: Month number (1-12)
5354 - [day]: Day of month (1-31) *)
5454-type t = Ptime.date
55555656+val of_string : string -> (t, [> `Invalid_date of string ]) result
5657(** Parse a date from [YYYY-MM-DD] format.
57585859 Returns [Error (`Invalid_date s)] if the string is not a valid date.
5960 Validates that the date is a real calendar date (e.g., rejects Feb 30). *)
6060-val of_string : string -> (t, [> `Invalid_date of string ]) result
61616262+val to_string : t -> string
6263(** Format a date as [YYYY-MM-DD]. *)
6363-val to_string : t -> string
64646565-(** Extract the year component. *)
6665val year : t -> int
6666+(** Extract the year component. *)
67676868+val month : t -> int
6869(** Extract the month component (1-12). *)
6969-val month : t -> int
70707171-(** Extract the day component (1-31). *)
7271val day : t -> int
7272+(** Extract the day component (1-31). *)
73737474-(** Date equality. *)
7574val equal : t -> t -> bool
7575+(** Date equality. *)
76767777-(** Date comparison (chronological order). *)
7877val compare : t -> t -> int
7878+(** Date comparison (chronological order). *)
79798080-(** Pretty-print a date in [YYYY-MM-DD] format. *)
8180val pp : Format.formatter -> t -> unit
8181+(** Pretty-print a date in [YYYY-MM-DD] format. *)
82828383+val jsont : t Json.Codec.t
8384(** JSON/YAML codec for dates.
84858585- Parses strings in [YYYY-MM-DD] format and serializes back to the
8686- same format. *)
8787-val jsont : t Jsont.t
8686+ Parses strings in [YYYY-MM-DD] format and serializes back to the same
8787+ format. *)
···5566(** Enumeration types for CFF.
7788- CFF defines several enumerated types using fixed string values.
99- This module represents them as polymorphic variants for type safety
1010- while providing bidirectional conversion to/from strings.
88+ CFF defines several enumerated types using fixed string values. This module
99+ represents them as polymorphic variants for type safety while providing
1010+ bidirectional conversion to/from strings.
11111212 {1 Identifier Types}
1313···33333434 - [`Doi] - Digital Object Identifier ({{:https://doi.org}doi.org})
3535 - [`Url] - Web URL
3636- - [`Swh] - Software Heritage identifier ({{:https://www.softwareheritage.org}softwareheritage.org})
3636+ - [`Swh] - Software Heritage identifier
3737+ ({{:https://www.softwareheritage.org}softwareheritage.org})
3738 - [`Other] - Any other identifier type
38393940 {2 Examples}
···4849 description: Software Heritage archive
4950 ]} *)
5051module Identifier_type : sig
5252+ type t = [ `Doi | `Url | `Swh | `Other ]
5153 (** Identifier types. *)
5252- type t =
5353- [ `Doi
5454- | `Url
5555- | `Swh
5656- | `Other
5757- ]
58545959- (** Parse from YAML string: ["doi"], ["url"], ["swh"], ["other"]. *)
6055 val of_string : string -> t option
5656+ (** Parse from YAML string: ["doi"], ["url"], ["swh"], ["other"]. *)
61576262- (** Convert to YAML string representation. *)
6358 val to_string : t -> string
5959+ (** Convert to YAML string representation. *)
64606561 val equal : t -> t -> bool
6662 val compare : t -> t -> int
6763 val pp : Format.formatter -> t -> unit
68646565+ val jsont : t Json.Codec.t
6966 (** JSON/YAML codec. *)
7070- val jsont : t Jsont.t
7167end
72687369(** Reference type for bibliographic entries.
74707575- CFF 1.2.0 supports 40+ reference types covering virtually all forms
7676- of citable content. The type determines which fields are relevant.
7171+ CFF 1.2.0 supports 40+ reference types covering virtually all forms of
7272+ citable content. The type determines which fields are relevant.
77737874 {2 Academic/Research}
7975···143139 - [`Standard] - Technical standard
144140 - [`Unpublished] - Unpublished work *)
145141module Reference_type : sig
146146- (** All supported reference types. *)
147142 type t =
148143 [ `Art
149144 | `Article
···191186 | `Thesis
192187 | `Unpublished
193188 | `Video
194194- | `Website
195195- ]
189189+ | `Website ]
190190+ (** All supported reference types. *)
196191197197- (** Parse from YAML string. Hyphenated names like ["conference-paper"]
198198- map to underscored variants like [`Conference_paper]. *)
199192 val of_string : string -> t option
193193+ (** Parse from YAML string. Hyphenated names like ["conference-paper"] map to
194194+ underscored variants like [`Conference_paper]. *)
200195201201- (** Convert to YAML string representation.
202202- Underscored variants like [`Conference_paper] become ["conference-paper"]. *)
203196 val to_string : t -> string
197197+ (** Convert to YAML string representation. Underscored variants like
198198+ [`Conference_paper] become ["conference-paper"]. *)
204199205200 val equal : t -> t -> bool
206201 val compare : t -> t -> int
207202 val pp : Format.formatter -> t -> unit
208203204204+ val jsont : t Json.Codec.t
209205 (** JSON/YAML codec. *)
210210- val jsont : t Jsont.t
211206end
212207213208(** Publication status for works in progress.
214209215215- The [status] field indicates the publication stage of a work that
216216- is not yet formally published:
210210+ The [status] field indicates the publication stage of a work that is not yet
211211+ formally published:
217212218213 - [`Abstract] - Only an abstract is available
219214 - [`Advance_online] - Published online ahead of print
···235230 status: submitted
236231 ]} *)
237232module Status : sig
238238- (** Publication status values. *)
239233 type t =
240234 [ `Abstract
241235 | `Advance_online
242236 | `In_preparation
243237 | `In_press
244238 | `Preprint
245245- | `Submitted
246246- ]
239239+ | `Submitted ]
240240+ (** Publication status values. *)
247241248248- (** Parse from YAML string: ["abstract"], ["advance-online"], etc. *)
249242 val of_string : string -> t option
243243+ (** Parse from YAML string: ["abstract"], ["advance-online"], etc. *)
250244251251- (** Convert to YAML string representation. *)
252245 val to_string : t -> string
246246+ (** Convert to YAML string representation. *)
253247254248 val equal : t -> t -> bool
255249 val compare : t -> t -> int
256250 val pp : Format.formatter -> t -> unit
257251252252+ val jsont : t Json.Codec.t
258253 (** JSON/YAML codec. *)
259259- val jsont : t Jsont.t
260254end
261255262256(** CFF file type: software or dataset.
263257264264- The [type] field at the root level indicates whether the CFF file
265265- describes software or a dataset:
258258+ The [type] field at the root level indicates whether the CFF file describes
259259+ software or a dataset:
266260267261 - [`Software] - Software project (default if omitted)
268262 - [`Dataset] - Dataset or data package
···276270 # ...
277271 ]} *)
278272module Cff_type : sig
273273+ type t = [ `Software | `Dataset ]
279274 (** CFF file types. *)
280280- type t =
281281- [ `Software
282282- | `Dataset
283283- ]
284275285285- (** Parse from YAML string: ["software"] or ["dataset"]. *)
286276 val of_string : string -> t option
277277+ (** Parse from YAML string: ["software"] or ["dataset"]. *)
287278288288- (** Convert to YAML string representation. *)
289279 val to_string : t -> string
280280+ (** Convert to YAML string representation. *)
290281291282 val equal : t -> t -> bool
292283 val compare : t -> t -> int
293284 val pp : Format.formatter -> t -> unit
294285286286+ val jsont : t Json.Codec.t
295287 (** JSON/YAML codec. *)
296296- val jsont : t Jsont.t
297288end
+17-17
lib/cff_identifier.ml
···5566(** Identifier type for CFF. *)
7788-type t =
99- { type_ : Cff_enums.Identifier_type.t
1010- ; value : string
1111- ; description : string option
1212- }
88+type t = {
99+ type_ : Cff_enums.Identifier_type.t;
1010+ value : string;
1111+ description : string option;
1212+}
13131414let make ~type_ ~value ?description () = { type_; value; description }
1515let type_ t = t.type_
···1717let description t = t.description
18181919let equal a b =
2020- Cff_enums.Identifier_type.equal a.type_ b.type_ && String.equal a.value b.value
2121-;;
2020+ Cff_enums.Identifier_type.equal a.type_ b.type_
2121+ && String.equal a.value b.value
22222323let compare a b =
2424 match Cff_enums.Identifier_type.compare a.type_ b.type_ with
2525 | 0 -> String.compare a.value b.value
2626 | n -> n
2727-;;
28272929-let pp ppf t = Format.fprintf ppf "%a: %s" Cff_enums.Identifier_type.pp t.type_ t.value
2828+let pp ppf t =
2929+ Format.fprintf ppf "%a: %s" Cff_enums.Identifier_type.pp t.type_ t.value
30303131let jsont =
3232- Jsont.Object.map ~kind:"Identifier" (fun type_ value description ->
3333- { type_; value; description })
3434- |> Jsont.Object.mem "type" Cff_enums.Identifier_type.jsont ~enc:(fun i -> i.type_)
3535- |> Jsont.Object.mem "value" Jsont.string ~enc:(fun i -> i.value)
3636- |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun i -> i.description)
3737- |> Jsont.Object.skip_unknown
3838- |> Jsont.Object.finish
3939-;;
3232+ Json.Codec.Object.map ~kind:"Identifier" (fun type_ value description ->
3333+ { type_; value; description })
3434+ |> Json.Codec.Object.member "type" Cff_enums.Identifier_type.jsont
3535+ ~enc:(fun i -> i.type_)
3636+ |> Json.Codec.Object.member "value" Json.Codec.string ~enc:(fun i -> i.value)
3737+ |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun i ->
3838+ i.description)
3939+ |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal
+21-23
lib/cff_identifier.mli
···5566(** Typed identifiers for CFF.
7788- The [identifiers] field in CFF allows listing multiple typed
99- identifiers for a work. Each identifier has a type, value, and
1010- optional description.
88+ The [identifiers] field in CFF allows listing multiple typed identifiers for
99+ a work. Each identifier has a type, value, and optional description.
11101211 {1 Identifier Types}
13121413 CFF supports four identifier types:
15141616- - {b DOI}: Digital Object Identifier
1717- ({{:https://doi.org}doi.org})
1515+ - {b DOI}: Digital Object Identifier ({{:https://doi.org}doi.org})
1816 - {b URL}: Web URL
1917 - {b SWH}: Software Heritage identifier
2018 ({{:https://www.softwareheritage.org}softwareheritage.org})
···54525553 {1 Software Heritage}
56545757- Software Heritage (SWH) provides persistent identifiers for source
5858- code. SWH identifiers follow the format:
5555+ Software Heritage (SWH) provides persistent identifiers for source code. SWH
5656+ identifiers follow the format:
59576058 [swh:1:<object_type>:<hash>]
6159···6664 - [rel]: Release
6765 - [snp]: Snapshot *)
68666969-(** An identifier with type, value, and optional description. *)
7067type t
6868+(** An identifier with type, value, and optional description. *)
71697070+val make :
7171+ type_:Cff_enums.Identifier_type.t ->
7272+ value:string ->
7373+ ?description:string ->
7474+ unit ->
7575+ t
7276(** Create an identifier.
73777478 @param type_ The identifier type ([`Doi], [`Url], [`Swh], or [`Other])
7579 @param value The identifier value (DOI, URL, SWH ID, etc.)
7680 @param description Optional human-readable description *)
7777-val make
7878- : type_:Cff_enums.Identifier_type.t
7979- -> value:string
8080- -> ?description:string
8181- -> unit
8282- -> t
83818282+val type_ : t -> Cff_enums.Identifier_type.t
8483(** The identifier type. *)
8585-val type_ : t -> Cff_enums.Identifier_type.t
86848585+val value : t -> string
8786(** The identifier value.
88878989- For DOIs, this is just the DOI (e.g., ["10.5281/zenodo.1234567"]),
9090- not the full URL. *)
9191-val value : t -> string
8888+ For DOIs, this is just the DOI (e.g., ["10.5281/zenodo.1234567"]), not the
8989+ full URL. *)
92909191+val description : t -> string option
9392(** Optional description explaining what this identifier refers to.
94939594 Examples:
9695 - ["The concept DOI for all versions"]
9796 - ["Version 1.0.0 archive"]
9897 - ["Release on GitHub"] *)
9999-val description : t -> string option
10098101101-(** Identifier equality (compares all fields). *)
10299val equal : t -> t -> bool
100100+(** Identifier equality (compares all fields). *)
103101102102+val compare : t -> t -> int
104103(** Identifier comparison. *)
105105-val compare : t -> t -> int
106104105105+val pp : Format.formatter -> t -> unit
107106(** Pretty-print as "[type]: value (description)". *)
108108-val pp : Format.formatter -> t -> unit
109107108108+val jsont : t Json.Codec.t
110109(** JSON/YAML codec for identifiers. *)
111111-val jsont : t Jsont.t
+28-49
lib/cff_license.ml
···5566(** SPDX license handling for CFF. *)
7788-type t =
99- [ `Spdx of Spdx_licenses.t
1010- | `Other of string list * string option
1111- ]
88+type t = [ `Spdx of Spdx_licenses.t | `Other of string list * string option ]
1291310let of_spdx spdx = `Spdx spdx
1411···1613 match Spdx_licenses.parse s with
1714 | Ok spdx -> `Spdx spdx
1815 | Error _ -> `Other ([ s ], None)
1919-;;
20162117let of_strings ss =
2218 (* Try to parse as OR combination, fall back to Other *)
2319 let try_parse_all () =
2420 let rec build = function
2521 | [] -> None
2626- | [ s ] ->
2727- (match Spdx_licenses.parse s with
2828- | Ok spdx -> Some spdx
2929- | Error _ -> None)
3030- | s :: rest ->
3131- (match Spdx_licenses.parse s, build rest with
3232- | Ok spdx, Some rest_spdx -> Some (Spdx_licenses.OR (spdx, rest_spdx))
3333- | _ -> None)
2222+ | [ s ] -> (
2323+ match Spdx_licenses.parse s with
2424+ | Ok spdx -> Some spdx
2525+ | Error _ -> None)
2626+ | s :: rest -> (
2727+ match (Spdx_licenses.parse s, build rest) with
2828+ | Ok spdx, Some rest_spdx -> Some (Spdx_licenses.OR (spdx, rest_spdx))
2929+ | _ -> None)
3430 in
3531 build ss
3632 in
3733 match try_parse_all () with
3834 | Some spdx -> `Spdx spdx
3935 | None -> `Other (ss, None)
4040-;;
41364237let with_url url = function
4343- | `Spdx _ as t -> t (* SPDX licenses have well-known URLs, ignore provided URL *)
3838+ | `Spdx _ as t ->
3939+ t (* SPDX licenses have well-known URLs, ignore provided URL *)
4440 | `Other (ids, _) -> `Other (ids, Some url)
4545-;;
46414742let with_url_opt url_opt t =
4848- match url_opt with
4949- | None -> t
5050- | Some url -> with_url url t
5151-;;
4343+ match url_opt with None -> t | Some url -> with_url url t
52445353-let to_spdx = function
5454- | `Spdx spdx -> Some spdx
5555- | `Other _ -> None
5656-;;
4545+let to_spdx = function `Spdx spdx -> Some spdx | `Other _ -> None
57465847let to_strings = function
5948 | `Spdx spdx -> [ Spdx_licenses.to_string spdx ]
6049 | `Other (ss, _) -> ss
6161-;;
62506363-let url = function
6464- | `Spdx _ -> None
6565- | `Other (_, url) -> url
6666-;;
5151+let url = function `Spdx _ -> None | `Other (_, url) -> url
67526853let pp ppf = function
6954 | `Spdx spdx -> Format.pp_print_string ppf (Spdx_licenses.to_string spdx)
7055 | `Other (ss, url_opt) ->
7171- (match ss with
7272- | [ s ] -> Format.pp_print_string ppf s
7373- | _ ->
7474- Format.fprintf
7575- ppf
7676- "[%a]"
7777- (Format.pp_print_list
7878- ~pp_sep:(fun ppf () -> Format.fprintf ppf ", ")
7979- Format.pp_print_string)
8080- ss);
8181- Option.iter (Format.fprintf ppf " <%s>") url_opt
8282-;;
5656+ (match ss with
5757+ | [ s ] -> Format.pp_print_string ppf s
5858+ | _ ->
5959+ Format.fprintf ppf "[%a]"
6060+ (Format.pp_print_list
6161+ ~pp_sep:(fun ppf () -> Format.fprintf ppf ", ")
6262+ Format.pp_print_string)
6363+ ss);
6464+ Option.iter (Format.fprintf ppf " <%s>") url_opt
83658466let jsont =
8567 let string_codec =
8686- Jsont.string
8787- |> Jsont.map ~dec:of_string ~enc:(function
6868+ Json.Codec.string
6969+ |> Json.Codec.map ~dec:of_string ~enc:(function
8870 | `Spdx spdx -> Spdx_licenses.to_string spdx
8971 | `Other ([ s ], _) -> s
9072 | `Other _ -> assert false)
9173 in
9274 let array_codec =
9393- Jsont.(array string)
9494- |> Jsont.map
7575+ Json.Codec.(array string)
7676+ |> Json.Codec.map
9577 ~dec:(fun ss -> of_strings (Array.to_list ss))
9678 ~enc:(fun t -> Array.of_list (to_strings t))
9779 in
9898- Jsont.any
9999- ~dec_string:string_codec
100100- ~dec_array:array_codec
8080+ Json.Codec.any ~dec_string:string_codec ~dec_array:array_codec
10181 ~enc:(function
10282 | `Spdx (Spdx_licenses.Simple _) -> string_codec
10383 | `Other ([ _ ], _) -> string_codec
10484 | _ -> array_codec)
10585 ()
106106-;;
+28-30
lib/cff_license.mli
···5566(** SPDX license expressions for CFF.
7788- CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers}
99- for the [license] field. This module combines license identification
1010- with optional URLs for non-standard licenses.
88+ CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers} for the
99+ [license] field. This module combines license identification with optional
1010+ URLs for non-standard licenses.
11111212 {1 License Representation}
1313···20202121 {1 Why Combined?}
22222323- The CFF spec has separate [license] and [license-url] fields, but they
2424- have a hidden relationship:
2525- - SPDX licenses have well-known URLs (e.g., MIT → https://spdx.org/licenses/MIT.html)
2323+ The CFF spec has separate [license] and [license-url] fields, but they have
2424+ a hidden relationship:
2525+ - SPDX licenses have well-known URLs (e.g., MIT →
2626+ https://spdx.org/licenses/MIT.html)
2627 - [license-url] is only meaningful for non-SPDX licenses
27282828- This type makes that relationship explicit: [`Spdx] licenses don't need
2929- a URL, while [`Other] licenses can optionally include one.
2929+ This type makes that relationship explicit: [`Spdx] licenses don't need a
3030+ URL, while [`Other] licenses can optionally include one.
30313132 {1 Examples}
3233···4748 license: ACME-Proprietary-1.0
4849 license-url: https://acme.com/license
4950 ]}
5050- Parsed as [`Other (["ACME-Proprietary-1.0"], Some "https://acme.com/license")]. *)
5151+ Parsed as
5252+ [`Other (["ACME-Proprietary-1.0"], Some "https://acme.com/license")]. *)
51535454+type t = [ `Spdx of Spdx_licenses.t | `Other of string list * string option ]
5255(** License type: SPDX expression or unknown ID(s) with optional URL. *)
5353-type t =
5454- [ `Spdx of Spdx_licenses.t
5555- | `Other of string list * string option
5656- ]
57565857(** {1 Construction} *)
59586060-(** [of_spdx expr] wraps a valid SPDX expression. *)
6159val of_spdx : Spdx_licenses.t -> t
6060+(** [of_spdx expr] wraps a valid SPDX expression. *)
62616363-(** [of_string s] parses [s] as an SPDX expression.
6464- Returns [`Spdx] on success, [`Other ([s], None)] on parse failure. *)
6562val of_string : string -> t
6363+(** [of_string s] parses [s] as an SPDX expression. Returns [`Spdx] on success,
6464+ [`Other ([s], None)] on parse failure. *)
66656767-(** [of_strings ss] parses a list of license strings.
6868- If all are valid SPDX IDs, returns [`Spdx] with OR combination.
6969- Otherwise returns [`Other (ss, None)]. *)
7066val of_strings : string list -> t
6767+(** [of_strings ss] parses a list of license strings. If all are valid SPDX IDs,
6868+ returns [`Spdx] with OR combination. Otherwise returns [`Other (ss, None)].
6969+*)
71707171+val with_url : string -> t -> t
7272(** [with_url url t] adds a URL to the license.
7373 - For [`Spdx], returns unchanged (SPDX URLs are well-known)
7474 - For [`Other (ids, _)], returns [`Other (ids, Some url)] *)
7575-val with_url : string -> t -> t
76757777-(** [with_url_opt url_opt t] optionally adds a URL.
7878- Convenience for combining during jsont decoding. *)
7976val with_url_opt : string option -> t -> t
7777+(** [with_url_opt url_opt t] optionally adds a URL. Convenience for combining
7878+ during jsont decoding. *)
80798180(** {1 Access} *)
82818282+val to_spdx : t -> Spdx_licenses.t option
8383(** [to_spdx t] returns [Some expr] if valid SPDX, [None] otherwise. *)
8484-val to_spdx : t -> Spdx_licenses.t option
85848686-(** [to_strings t] returns the license identifier(s) as strings. *)
8785val to_strings : t -> string list
8686+(** [to_strings t] returns the license identifier(s) as strings. *)
88878888+val url : t -> string option
8989(** [url t] returns the license URL.
9090 - For [`Spdx], always [None] (use SPDX's well-known URLs)
9191 - For [`Other], returns the URL if provided *)
9292-val url : t -> string option
93929493(** {1 Formatting} *)
95949696-(** Pretty-print the license. *)
9795val pp : Format.formatter -> t -> unit
9696+(** Pretty-print the license. *)
98979998(** {1 Codec} *)
10099100100+val jsont : t Json.Codec.t
101101(** JSON/YAML codec for the license field only.
102102103103- This handles just the [license] field. The [license-url] field
104104- is handled separately by the parent codec which combines them
105105- using {!with_url}.
103103+ This handles just the [license] field. The [license-url] field is handled
104104+ separately by the parent codec which combines them using {!with_url}.
106105107106 Lenient: accepts any string without validation for round-tripping. *)
108108-val jsont : t Jsont.t
···66(** Bibliographic reference type for CFF.
7788 References represent citable works in the [references] and
99- [preferred-citation] fields of a CFF file. They can describe any
1010- type of scholarly output: journal articles, books, conference papers,
1111- software, datasets, theses, patents, and many more.
99+ [preferred-citation] fields of a CFF file. They can describe any type of
1010+ scholarly output: journal articles, books, conference papers, software,
1111+ datasets, theses, patents, and many more.
12121313 {1 Structure}
14141515- CFF references have 60+ possible fields. This module organizes them
1616- into logical sub-records for easier manipulation:
1515+ CFF references have 60+ possible fields. This module organizes them into
1616+ logical sub-records for easier manipulation:
17171818 - {!Core} - Required fields: type, title, authors
1919 - {!Publication} - Journal articles: journal, volume, issue, pages
···26262727 {1 Reference Types}
28282929- The [type] field determines what kind of work is being referenced.
3030- CFF 1.2.0 supports 40+ types including:
2929+ The [type] field determines what kind of work is being referenced. CFF 1.2.0
3030+ supports 40+ types including:
31313232 - Academic: [`Article], [`Book], [`Conference_paper], [`Thesis]
3333 - Software: [`Software], [`Software_code], [`Software_container]
···71717272(** Core identity fields (required for all references).
73737474- Every reference must have a type, title, and at least one author.
7575- The type determines what additional fields are relevant. *)
7474+ Every reference must have a type, title, and at least one author. The type
7575+ determines what additional fields are relevant. *)
7676module Core : sig
7777 type t
78787979+ val make :
8080+ type_:Cff_enums.Reference_type.t ->
8181+ title:string ->
8282+ authors:Cff_author.t list ->
8383+ ?abstract:string ->
8484+ ?abbreviation:string ->
8585+ unit ->
8686+ t
7987 (** Create a core record.
80888189 @param type_ The reference type (article, book, software, etc.)
8290 @param title The title of the work
8391 @param authors List of persons and/or entities *)
8484- val make
8585- : type_:Cff_enums.Reference_type.t
8686- -> title:string
8787- -> authors:Cff_author.t list
8888- -> ?abstract:string
8989- -> ?abbreviation:string
9090- -> unit
9191- -> t
92929393- (** The reference type. Determines which other fields are applicable. *)
9493 val type_ : t -> Cff_enums.Reference_type.t
9494+ (** The reference type. Determines which other fields are applicable. *)
95959696- (** The title of the referenced work. *)
9796 val title : t -> string
9797+ (** The title of the referenced work. *)
98989999+ val authors : t -> Cff_author.t list
99100 (** The authors/creators of the work. *)
100100- val authors : t -> Cff_author.t list
101101102102+ val abstract : t -> string option
102103 (** A description or abstract of the work. *)
103103- val abstract : t -> string option
104104105105- (** Abbreviated form of the title (e.g., for journal names). *)
106105 val abbreviation : t -> string option
106106+ (** Abbreviated form of the title (e.g., for journal names). *)
107107108108 val pp : Format.formatter -> t -> unit
109109end
110110111111(** Publication metadata for journal articles and periodicals.
112112113113- Fields for works published in journals, magazines, or other
114114- serial publications. Page numbers can be specified as a range
115115- ([pages]) or as separate [start] and [end_] values. *)
113113+ Fields for works published in journals, magazines, or other serial
114114+ publications. Page numbers can be specified as a range ([pages]) or as
115115+ separate [start] and [end_] values. *)
116116module Publication : sig
117117 type t
118118119119- (** Empty publication record with all fields as [None]. *)
120119 val empty : t
120120+ (** Empty publication record with all fields as [None]. *)
121121122122- val make
123123- : ?journal:string
124124- -> ?volume:string
125125- -> ?issue:string
126126- -> ?pages:string
127127- -> ?start:string
128128- -> ?end_:string
129129- -> ?edition:string
130130- -> ?section:string
131131- -> ?status:Cff_enums.Status.t
132132- -> unit
133133- -> t
122122+ val make :
123123+ ?journal:string ->
124124+ ?volume:string ->
125125+ ?issue:string ->
126126+ ?pages:string ->
127127+ ?start:string ->
128128+ ?end_:string ->
129129+ ?edition:string ->
130130+ ?section:string ->
131131+ ?status:Cff_enums.Status.t ->
132132+ unit ->
133133+ t
134134135135- (** The name of the journal or magazine. *)
136135 val journal : t -> string option
136136+ (** The name of the journal or magazine. *)
137137138138- (** The volume number of the journal. *)
139138 val volume : t -> string option
139139+ (** The volume number of the journal. *)
140140141141+ val issue : t -> string option
141142 (** The issue number within the volume. *)
142142- val issue : t -> string option
143143144144- (** Page range (e.g., ["123-145"]). Alternative to [start]/[end_]. *)
145144 val pages : t -> string option
145145+ (** Page range (e.g., ["123-145"]). Alternative to [start]/[end_]. *)
146146147147- (** Starting page number. *)
148147 val start : t -> string option
148148+ (** Starting page number. *)
149149150150- (** Ending page number. *)
151150 val end_ : t -> string option
151151+ (** Ending page number. *)
152152153153- (** The edition of the work (e.g., ["2nd edition"]). *)
154153 val edition : t -> string option
154154+ (** The edition of the work (e.g., ["2nd edition"]). *)
155155156156- (** The section of a work (e.g., newspaper section). *)
157156 val section : t -> string option
157157+ (** The section of a work (e.g., newspaper section). *)
158158159159+ val status : t -> Cff_enums.Status.t option
159160 (** Publication status: preprint, in-press, submitted, etc. *)
160160- val status : t -> Cff_enums.Status.t option
161161162162+ val is_empty : t -> bool
162163 (** [true] if all fields are [None]. *)
163163- val is_empty : t -> bool
164164end
165165166166(** Collection metadata for works in edited volumes.
167167168168- Used for book chapters, conference proceedings, and other works
169169- that appear within a larger collection. *)
168168+ Used for book chapters, conference proceedings, and other works that appear
169169+ within a larger collection. *)
170170module Collection : sig
171171 type t
172172173173 val empty : t
174174175175- val make
176176- : ?collection_title:string
177177- -> ?collection_type:string
178178- -> ?collection_doi:string
179179- -> ?volume_title:string
180180- -> ?number_volumes:string
181181- -> unit
182182- -> t
175175+ val make :
176176+ ?collection_title:string ->
177177+ ?collection_type:string ->
178178+ ?collection_doi:string ->
179179+ ?volume_title:string ->
180180+ ?number_volumes:string ->
181181+ unit ->
182182+ t
183183184184- (** Title of the collection (proceedings, book series, etc.). *)
185184 val collection_title : t -> string option
185185+ (** Title of the collection (proceedings, book series, etc.). *)
186186187187- (** Type of collection (e.g., ["proceedings"], ["book series"]). *)
188187 val collection_type : t -> string option
188188+ (** Type of collection (e.g., ["proceedings"], ["book series"]). *)
189189190190- (** DOI of the collection itself (not the individual work). *)
191190 val collection_doi : t -> string option
191191+ (** DOI of the collection itself (not the individual work). *)
192192193193- (** Title of the specific volume within a multi-volume collection. *)
194193 val volume_title : t -> string option
194194+ (** Title of the specific volume within a multi-volume collection. *)
195195196196+ val number_volumes : t -> string option
196197 (** Total number of volumes in the collection. *)
197197- val number_volumes : t -> string option
198198199199 val is_empty : t -> bool
200200end
···207207 - {b date-accessed}: When an online resource was last accessed
208208 - {b date-downloaded}: When a resource was downloaded
209209210210- For older works or when only the year is known, use [year] instead
211211- of a full date. *)
210210+ For older works or when only the year is known, use [year] instead of a full
211211+ date. *)
212212module Dates : sig
213213 type t
214214215215 val empty : t
216216217217- val make
218218- : ?date_accessed:Cff_date.t
219219- -> ?date_downloaded:Cff_date.t
220220- -> ?date_published:Cff_date.t
221221- -> ?date_released:Cff_date.t
222222- -> ?year:int
223223- -> ?year_original:int
224224- -> ?month:int
225225- -> ?issue_date:string
226226- -> unit
227227- -> t
217217+ val make :
218218+ ?date_accessed:Cff_date.t ->
219219+ ?date_downloaded:Cff_date.t ->
220220+ ?date_published:Cff_date.t ->
221221+ ?date_released:Cff_date.t ->
222222+ ?year:int ->
223223+ ?year_original:int ->
224224+ ?month:int ->
225225+ ?issue_date:string ->
226226+ unit ->
227227+ t
228228229229- (** Date when an online resource was accessed for citation. *)
230229 val date_accessed : t -> Cff_date.t option
230230+ (** Date when an online resource was accessed for citation. *)
231231232232+ val date_downloaded : t -> Cff_date.t option
232233 (** Date when a resource was downloaded. *)
233233- val date_downloaded : t -> Cff_date.t option
234234235235+ val date_published : t -> Cff_date.t option
235236 (** Formal publication date. *)
236236- val date_published : t -> Cff_date.t option
237237238238- (** Release date (typically for software). *)
239238 val date_released : t -> Cff_date.t option
239239+ (** Release date (typically for software). *)
240240241241- (** Publication year when full date is unknown. *)
242241 val year : t -> int option
242242+ (** Publication year when full date is unknown. *)
243243244244+ val year_original : t -> int option
244245 (** Year of original publication (for reprints, translations). *)
245245- val year_original : t -> int option
246246247247- (** Publication month (1-12) when only month/year is known. *)
248247 val month : t -> int option
248248+ (** Publication month (1-12) when only month/year is known. *)
249249250250- (** Issue date as a string (for periodicals with specific dates). *)
251250 val issue_date : t -> string option
251251+ (** Issue date as a string (for periodicals with specific dates). *)
252252253253 val is_empty : t -> bool
254254end
···267267268268 val empty : t
269269270270- val make
271271- : ?doi:string
272272- -> ?url:string
273273- -> ?repository:string
274274- -> ?repository_code:string
275275- -> ?repository_artifact:string
276276- -> ?isbn:string
277277- -> ?issn:string
278278- -> ?pmcid:string
279279- -> ?nihmsid:string
280280- -> ?identifiers:Cff_identifier.t list
281281- -> unit
282282- -> t
270270+ val make :
271271+ ?doi:string ->
272272+ ?url:string ->
273273+ ?repository:string ->
274274+ ?repository_code:string ->
275275+ ?repository_artifact:string ->
276276+ ?isbn:string ->
277277+ ?issn:string ->
278278+ ?pmcid:string ->
279279+ ?nihmsid:string ->
280280+ ?identifiers:Cff_identifier.t list ->
281281+ unit ->
282282+ t
283283284284+ val doi : t -> string option
284285 (** Digital Object Identifier (e.g., ["10.1234/example"]). *)
285285- val doi : t -> string option
286286287287+ val url : t -> string option
287288 (** URL where the work can be accessed. *)
288288- val url : t -> string option
289289290290- (** General repository URL. *)
291290 val repository : t -> string option
291291+ (** General repository URL. *)
292292293293- (** Source code repository (GitHub, GitLab, etc.). *)
294293 val repository_code : t -> string option
294294+ (** Source code repository (GitHub, GitLab, etc.). *)
295295296296- (** Built artifact repository (npm, PyPI, Docker Hub, etc.). *)
297296 val repository_artifact : t -> string option
297297+ (** Built artifact repository (npm, PyPI, Docker Hub, etc.). *)
298298299299+ val isbn : t -> string option
299300 (** International Standard Book Number. *)
300300- val isbn : t -> string option
301301302302- (** International Standard Serial Number (for journals). *)
303302 val issn : t -> string option
303303+ (** International Standard Serial Number (for journals). *)
304304305305+ val pmcid : t -> string option
305306 (** PubMed Central identifier. *)
306306- val pmcid : t -> string option
307307308308+ val nihmsid : t -> string option
308309 (** NIH Manuscript Submission System identifier. *)
309309- val nihmsid : t -> string option
310310311311- (** Additional typed identifiers (DOI, URL, SWH, other). *)
312311 val identifiers : t -> Cff_identifier.t list option
312312+ (** Additional typed identifiers (DOI, URL, SWH, other). *)
313313314314 val is_empty : t -> bool
315315end
···326326327327 val empty : t
328328329329- val make
330330- : ?editors:Cff_author.t list
331331- -> ?editors_series:Cff_author.t list
332332- -> ?translators:Cff_author.t list
333333- -> ?recipients:Cff_author.t list
334334- -> ?senders:Cff_author.t list
335335- -> ?contact:Cff_author.t list
336336- -> ?publisher:Cff_author.Entity.t
337337- -> ?institution:Cff_author.Entity.t
338338- -> ?conference:Cff_author.Entity.t
339339- -> ?database_provider:Cff_author.Entity.t
340340- -> ?location:Cff_author.Entity.t
341341- -> unit
342342- -> t
329329+ val make :
330330+ ?editors:Cff_author.t list ->
331331+ ?editors_series:Cff_author.t list ->
332332+ ?translators:Cff_author.t list ->
333333+ ?recipients:Cff_author.t list ->
334334+ ?senders:Cff_author.t list ->
335335+ ?contact:Cff_author.t list ->
336336+ ?publisher:Cff_author.Entity.t ->
337337+ ?institution:Cff_author.Entity.t ->
338338+ ?conference:Cff_author.Entity.t ->
339339+ ?database_provider:Cff_author.Entity.t ->
340340+ ?location:Cff_author.Entity.t ->
341341+ unit ->
342342+ t
343343344344+ val editors : t -> Cff_author.t list option
344345 (** Editors of the work (for edited volumes). *)
345345- val editors : t -> Cff_author.t list option
346346347347+ val editors_series : t -> Cff_author.t list option
347348 (** Series editors (for book series). *)
348348- val editors_series : t -> Cff_author.t list option
349349350350- (** Translators of the work. *)
351350 val translators : t -> Cff_author.t list option
351351+ (** Translators of the work. *)
352352353353- (** Recipients (for personal communications). *)
354353 val recipients : t -> Cff_author.t list option
354354+ (** Recipients (for personal communications). *)
355355356356- (** Senders (for personal communications). *)
357356 val senders : t -> Cff_author.t list option
357357+ (** Senders (for personal communications). *)
358358359359- (** Contact persons for the work. *)
360359 val contact : t -> Cff_author.t list option
360360+ (** Contact persons for the work. *)
361361362362- (** Publishing organization. *)
363362 val publisher : t -> Cff_author.Entity.t option
363363+ (** Publishing organization. *)
364364365365+ val institution : t -> Cff_author.Entity.t option
365366 (** Academic/research institution (for theses, reports). *)
366366- val institution : t -> Cff_author.Entity.t option
367367368368+ val conference : t -> Cff_author.Entity.t option
368369 (** Conference where the work was presented. *)
369369- val conference : t -> Cff_author.Entity.t option
370370371371- (** Provider of a database (for data references). *)
372371 val database_provider : t -> Cff_author.Entity.t option
372372+ (** Provider of a database (for data references). *)
373373374374- (** Location entity (city, venue for conferences). *)
375374 val location : t -> Cff_author.Entity.t option
375375+ (** Location entity (city, venue for conferences). *)
376376377377 val is_empty : t -> bool
378378end
···385385386386 val empty : t
387387388388- val make
389389- : ?keywords:string list
390390- -> ?languages:string list
391391- -> ?license:Cff_license.t
392392- -> ?copyright:string
393393- -> ?scope:string
394394- -> ?notes:string
395395- -> unit
396396- -> t
388388+ val make :
389389+ ?keywords:string list ->
390390+ ?languages:string list ->
391391+ ?license:Cff_license.t ->
392392+ ?copyright:string ->
393393+ ?scope:string ->
394394+ ?notes:string ->
395395+ unit ->
396396+ t
397397398398- (** Descriptive keywords for the work. *)
399398 val keywords : t -> string list option
399399+ (** Descriptive keywords for the work. *)
400400401401- (** Languages the work is available in (ISO 639 codes). *)
402401 val languages : t -> string list option
402402+ (** Languages the work is available in (ISO 639 codes). *)
403403404404+ val license : t -> Cff_license.t option
404405 (** SPDX license identifier(s), or unknown license with optional URL. *)
405405- val license : t -> Cff_license.t option
406406407407- (** Copyright statement. *)
408407 val copyright : t -> string option
408408+ (** Copyright statement. *)
409409410410+ val scope : t -> string option
410411 (** Scope of the reference (what aspect it covers). *)
411411- val scope : t -> string option
412412413413+ val notes : t -> string option
413414 (** Additional notes or comments. *)
414414- val notes : t -> string option
415415416416 val is_empty : t -> bool
417417end
···429429430430 val empty : t
431431432432- val make
433433- : ?commit:string
434434- -> ?version:string
435435- -> ?filename:string
436436- -> ?format:string
437437- -> ?medium:string
438438- -> ?data_type:string
439439- -> ?database:string
440440- -> ?number:string
441441- -> ?patent_states:string list
442442- -> ?thesis_type:string
443443- -> ?term:string
444444- -> ?entry:string
445445- -> ?department:string
446446- -> ?loc_start:string
447447- -> ?loc_end:string
448448- -> unit
449449- -> t
432432+ val make :
433433+ ?commit:string ->
434434+ ?version:string ->
435435+ ?filename:string ->
436436+ ?format:string ->
437437+ ?medium:string ->
438438+ ?data_type:string ->
439439+ ?database:string ->
440440+ ?number:string ->
441441+ ?patent_states:string list ->
442442+ ?thesis_type:string ->
443443+ ?term:string ->
444444+ ?entry:string ->
445445+ ?department:string ->
446446+ ?loc_start:string ->
447447+ ?loc_end:string ->
448448+ unit ->
449449+ t
450450451451+ val commit : t -> string option
451452 (** Git commit hash or VCS revision. *)
452452- val commit : t -> string option
453453454454+ val version : t -> string option
454455 (** Version string of the software/data. *)
455455- val version : t -> string option
456456457457- (** Name of the file being referenced. *)
458457 val filename : t -> string option
458458+ (** Name of the file being referenced. *)
459459460460- (** Format of the work (e.g., ["PDF"], ["HTML"]). *)
461460 val format : t -> string option
461461+ (** Format of the work (e.g., ["PDF"], ["HTML"]). *)
462462463463- (** Physical medium (e.g., ["CD-ROM"], ["print"]). *)
464463 val medium : t -> string option
464464+ (** Physical medium (e.g., ["CD-ROM"], ["print"]). *)
465465466466- (** Type of data (for datasets). *)
467466 val data_type : t -> string option
467467+ (** Type of data (for datasets). *)
468468469469- (** Name of the database. *)
470469 val database : t -> string option
470470+ (** Name of the database. *)
471471472472+ val number : t -> string option
472473 (** Report/patent/standard number. *)
473473- val number : t -> string option
474474475475+ val patent_states : t -> string list option
475476 (** Countries where a patent is held. *)
476476- val patent_states : t -> string list option
477477478478- (** Type of thesis (["PhD"], ["Master's"], etc.). *)
479478 val thesis_type : t -> string option
479479+ (** Type of thesis (["PhD"], ["Master's"], etc.). *)
480480481481- (** Dictionary/encyclopedia term being referenced. *)
482481 val term : t -> string option
482482+ (** Dictionary/encyclopedia term being referenced. *)
483483484484- (** Encyclopedia entry name. *)
485484 val entry : t -> string option
485485+ (** Encyclopedia entry name. *)
486486487487+ val department : t -> string option
487488 (** Academic department (for theses). *)
488488- val department : t -> string option
489489490490- (** Starting line/location in source code. *)
491490 val loc_start : t -> string option
491491+ (** Starting line/location in source code. *)
492492493493+ val loc_end : t -> string option
493494 (** Ending line/location in source code. *)
494494- val loc_end : t -> string option
495495496496 val is_empty : t -> bool
497497end
498498499499(** {1 Reference Type} *)
500500501501-(** The complete reference type combining all sub-records. *)
502501type t
502502+(** The complete reference type combining all sub-records. *)
503503504504+val make :
505505+ core:Core.t ->
506506+ ?publication:Publication.t ->
507507+ ?collection:Collection.t ->
508508+ ?dates:Dates.t ->
509509+ ?identifiers:Identifiers.t ->
510510+ ?entities:Entities.t ->
511511+ ?metadata:Metadata.t ->
512512+ ?technical:Technical.t ->
513513+ unit ->
514514+ t
504515(** Construct a reference from sub-records.
505516506517 Only [core] is required; other sub-records default to empty. *)
507507-val make
508508- : core:Core.t
509509- -> ?publication:Publication.t
510510- -> ?collection:Collection.t
511511- -> ?dates:Dates.t
512512- -> ?identifiers:Identifiers.t
513513- -> ?entities:Entities.t
514514- -> ?metadata:Metadata.t
515515- -> ?technical:Technical.t
516516- -> unit
517517- -> t
518518519519+val make_simple :
520520+ type_:Cff_enums.Reference_type.t ->
521521+ title:string ->
522522+ authors:Cff_author.t list ->
523523+ ?doi:string ->
524524+ ?year:int ->
525525+ ?journal:string ->
526526+ unit ->
527527+ t
519528(** Convenience constructor for simple references.
520529521521- Creates a reference with just the most common fields. Suitable
522522- for quick article or software references. *)
523523-val make_simple
524524- : type_:Cff_enums.Reference_type.t
525525- -> title:string
526526- -> authors:Cff_author.t list
527527- -> ?doi:string
528528- -> ?year:int
529529- -> ?journal:string
530530- -> unit
531531- -> t
530530+ Creates a reference with just the most common fields. Suitable for quick
531531+ article or software references. *)
532532533533(** {2 Sub-record Accessors} *)
534534535535-(** The core identity fields. *)
536535val core : t -> Core.t
536536+(** The core identity fields. *)
537537538538-(** Publication metadata (journal, volume, pages). *)
539538val publication : t -> Publication.t
539539+(** Publication metadata (journal, volume, pages). *)
540540541541-(** Collection metadata (proceedings, book series). *)
542541val collection : t -> Collection.t
542542+(** Collection metadata (proceedings, book series). *)
543543544544+val dates : t -> Dates.t
544545(** Date-related fields. *)
545545-val dates : t -> Dates.t
546546547547+val identifiers : t -> Identifiers.t
547548(** Identifiers and links. *)
548548-val identifiers : t -> Identifiers.t
549549550550-(** Related entities (editors, publisher). *)
551550val entities : t -> Entities.t
551551+(** Related entities (editors, publisher). *)
552552553553-(** Descriptive metadata (keywords, license). *)
554553val metadata : t -> Metadata.t
554554+(** Descriptive metadata (keywords, license). *)
555555556556-(** Technical fields (commit, version, format). *)
557556val technical : t -> Technical.t
557557+(** Technical fields (commit, version, format). *)
558558559559(** {2 Direct Accessors for Common Fields}
560560561561 Convenience accessors that delegate to sub-records. *)
562562563563+val type_ : t -> Cff_enums.Reference_type.t
563564(** Shortcut for [Core.type_ (core t)]. *)
564564-val type_ : t -> Cff_enums.Reference_type.t
565565566566-(** Shortcut for [Core.title (core t)]. *)
567566val title : t -> string
567567+(** Shortcut for [Core.title (core t)]. *)
568568569569+val authors : t -> Cff_author.t list
569570(** Shortcut for [Core.authors (core t)]. *)
570570-val authors : t -> Cff_author.t list
571571572572+val doi : t -> string option
572573(** Shortcut for [Identifiers.doi (identifiers t)]. *)
573573-val doi : t -> string option
574574575575-(** Shortcut for [Dates.year (dates t)]. *)
576575val year : t -> int option
576576+(** Shortcut for [Dates.year (dates t)]. *)
577577578578(** {1 Formatting and Codec} *)
579579580580-(** Pretty-print a reference in a human-readable format. *)
581580val pp : Format.formatter -> t -> unit
581581+(** Pretty-print a reference in a human-readable format. *)
582582583583+val jsont : t Json.Codec.t
583584(** JSON/YAML codec for serialization. *)
584584-val jsont : t Jsont.t
···5566(** Unix file I/O for CFF. *)
7788+let codec = Yaml_json.of_json Cff.jsont
99+810let of_yaml_string s =
99- let reader = Bytesrw.Bytes.Reader.of_string s in
1010- Yamlt.decode ~layout:true Cff.jsont reader
1111-;;
1111+ match Yaml.of_string codec s with
1212+ | Ok [ cff ] -> Ok cff
1313+ | Ok [] -> Error "expected one YAML document, got empty stream"
1414+ | Ok _ -> Error "expected one YAML document, got multiple"
1515+ | Error e -> Error (Format.asprintf "%a" Yaml.Error.pp e)
12161317let to_yaml_string t =
1414- let buf = Buffer.create 1024 in
1515- let writer = Bytesrw.Bytes.Writer.of_buffer buf in
1616- match Yamlt.encode ~format:Yamlt.Block Cff.jsont t ~eod:true writer with
1717- | Ok () -> Ok (Buffer.contents buf)
1818- | Error e -> Error e
1919-;;
1818+ try Ok (Yaml.to_string ~indent:2 codec [ t ])
1919+ with Yaml.Error e -> Error (Format.asprintf "%a" Yaml.Error.pp e)
20202121let of_file path =
2222 match In_channel.with_open_text path In_channel.input_all with
2323 | s -> of_yaml_string s
2424 | exception Sys_error e -> Error e
2525-;;
26252726let to_file path t =
2827 match to_yaml_string t with
2928 | Error e -> Error e
3030- | Ok s ->
3131- (match Out_channel.with_open_text path (fun oc -> Out_channel.output_string oc s) with
3232- | () -> Ok ()
3333- | exception Sys_error e -> Error e)
3434-;;
2929+ | Ok s -> (
3030+ match
3131+ Out_channel.with_open_text path (fun oc ->
3232+ Out_channel.output_string oc s)
3333+ with
3434+ | () -> Ok ()
3535+ | exception Sys_error e -> Error e)
+13-14
lib_unix/cff_unix.mli
···5566(** Unix file I/O for CFF.
7788- This module provides YAML parsing and serialization for CFF using
99- standard Unix file operations ({!In_channel}, {!Out_channel}).
88+ This module provides YAML parsing and serialization for CFF using standard
99+ Unix file operations ({!In_channel}, {!Out_channel}).
10101111 {1 Example}
12121313 {[
1414- match Cff_unix.of_file "CITATION.cff" with
1515- | Ok cff ->
1414+ match Cff_unix.of_file "CITATION.cff" with
1515+ | Ok cff ->
1616 Printf.printf "Title: %s\n" (Cff.title cff);
1717 Printf.printf "Version: %s\n"
1818 (Option.value ~default:"unspecified" (Cff.version cff))
1919- | Error msg ->
2020- Printf.eprintf "Parse error: %s\n" msg
1919+ | Error msg -> Printf.eprintf "Parse error: %s\n" msg
2120 ]}
22212322 {1 Functions} *)
24232424+val of_yaml_string : string -> (Cff.t, string) result
2525(** [of_yaml_string s] parses a CFF from YAML string [s].
26262727- Returns [Ok cff] on success or [Error msg] with a descriptive error
2828- message on failure. *)
2929-val of_yaml_string : string -> (Cff.t, string) result
2727+ Returns [Ok cff] on success or [Error msg] with a descriptive error message
2828+ on failure. *)
30293030+val to_yaml_string : Cff.t -> (string, string) result
3131(** [to_yaml_string cff] serializes [cff] to a YAML string.
32323333 The output uses YAML block style for readability. *)
3434-val to_yaml_string : Cff.t -> (string, string) result
35343535+val of_file : string -> (Cff.t, string) result
3636(** [of_file path] reads and parses a [CITATION.cff] file.
37373838- Returns [Ok cff] on success or [Error msg] if the file cannot be
3939- read or contains invalid CFF data. *)
4040-val of_file : string -> (Cff.t, string) result
3838+ Returns [Ok cff] on success or [Error msg] if the file cannot be read or
3939+ contains invalid CFF data. *)
41404141+val to_file : string -> Cff.t -> (unit, string) result
4242(** [to_file path cff] writes [cff] to a file at [path].
43434444 Creates or overwrites the file. Returns [Error msg] on I/O failure. *)
4545-val to_file : string -> Cff.t -> (unit, string) result