OCaml codecs for the Citation File Format (CFF)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

port to nox-yaml and friends

+2188 -2343
+1 -2
.ocamlformat
··· 1 - version=0.28.1 2 - profile=janestreet 1 + version=0.29.0
+2 -4
cff.opam
··· 14 14 "ptime" 15 15 "ISO3166" 16 16 "spdx_licenses" 17 - "jsont" 18 - "yamlt" 19 - "bytesrw" 17 + "nox-json" 18 + "nox-yaml" 20 19 "eio" 21 - "bytesrw-eio" 22 20 "odoc" {with-doc} 23 21 "alcotest" {with-test & >= "1.7.0"} 24 22 "eio_main" {with-test}
+2 -4
dune-project
··· 20 20 ptime 21 21 ISO3166 22 22 spdx_licenses 23 - jsont 24 - yamlt 25 - bytesrw 23 + nox-json 24 + nox-yaml 26 25 eio 27 - bytesrw-eio 28 26 (odoc :with-doc) 29 27 (alcotest (and :with-test (>= 1.7.0))) 30 28 (eio_main :with-test)))
+148 -154
lib/cff.ml
··· 23 23 24 24 (* Root CFF type - integrated directly *) 25 25 26 - type t = 27 - { cff_version : string 28 - ; message : string 29 - ; title : string 30 - ; authors : Cff_author.t list 31 - ; abstract : string option 32 - ; commit : string option 33 - ; contact : Cff_author.t list option 34 - ; date_released : Cff_date.t option 35 - ; doi : string option 36 - ; identifiers : Cff_identifier.t list option 37 - ; keywords : string list option 38 - ; license : Cff_license.t option 39 - ; preferred_citation : Cff_reference.t option 40 - ; references : Cff_reference.t list option 41 - ; repository : string option 42 - ; repository_artifact : string option 43 - ; repository_code : string option 44 - ; type_ : Cff_enums.Cff_type.t option 45 - ; url : string option 46 - ; version : string option 47 - } 26 + type t = { 27 + cff_version : string; 28 + message : string; 29 + title : string; 30 + authors : Cff_author.t list; 31 + abstract : string option; 32 + commit : string option; 33 + contact : Cff_author.t list option; 34 + date_released : Cff_date.t option; 35 + doi : string option; 36 + identifiers : Cff_identifier.t list option; 37 + keywords : string list option; 38 + license : Cff_license.t option; 39 + preferred_citation : Cff_reference.t option; 40 + references : Cff_reference.t list option; 41 + repository : string option; 42 + repository_artifact : string option; 43 + repository_code : string option; 44 + type_ : Cff_enums.Cff_type.t option; 45 + url : string option; 46 + version : string option; 47 + } 48 48 49 49 let default_cff_version = "1.2.0" 50 50 51 51 let default_message = 52 52 "If you use this software, please cite it using the metadata from this file." 53 - ;; 54 53 55 - let make 56 - ?(cff_version = default_cff_version) 57 - ?(message = default_message) 58 - ~title 59 - ~authors 60 - ?abstract 61 - ?commit 62 - ?contact 63 - ?date_released 64 - ?doi 65 - ?identifiers 66 - ?keywords 67 - ?license 68 - ?preferred_citation 69 - ?references 70 - ?repository 71 - ?repository_artifact 72 - ?repository_code 73 - ?type_ 74 - ?url 75 - ?version 76 - () 77 - = 78 - { cff_version 79 - ; message 80 - ; title 81 - ; authors 82 - ; abstract 83 - ; commit 84 - ; contact 85 - ; date_released 86 - ; doi 87 - ; identifiers 88 - ; keywords 89 - ; license 90 - ; preferred_citation 91 - ; references 92 - ; repository 93 - ; repository_artifact 94 - ; repository_code 95 - ; type_ 96 - ; url 97 - ; version 54 + let make ?(cff_version = default_cff_version) ?(message = default_message) 55 + ~title ~authors ?abstract ?commit ?contact ?date_released ?doi ?identifiers 56 + ?keywords ?license ?preferred_citation ?references ?repository 57 + ?repository_artifact ?repository_code ?type_ ?url ?version () = 58 + { 59 + cff_version; 60 + message; 61 + title; 62 + authors; 63 + abstract; 64 + commit; 65 + contact; 66 + date_released; 67 + doi; 68 + identifiers; 69 + keywords; 70 + license; 71 + preferred_citation; 72 + references; 73 + repository; 74 + repository_artifact; 75 + repository_code; 76 + type_; 77 + url; 78 + version; 98 79 } 99 - ;; 100 80 101 81 (* Required field accessors *) 102 82 let cff_version t = t.cff_version ··· 134 114 Option.iter 135 115 (fun v -> Format.fprintf ppf "date-released: %a@," Cff_date.pp v) 136 116 t.date_released; 137 - Option.iter (fun v -> Format.fprintf ppf "license: %a@," Cff_license.pp v) t.license; 117 + Option.iter 118 + (fun v -> Format.fprintf ppf "license: %a@," Cff_license.pp v) 119 + t.license; 138 120 Option.iter (fun v -> Format.fprintf ppf "url: %s@," v) t.url; 139 121 Option.iter (fun v -> Format.fprintf ppf "repository: %s@," v) t.repository; 140 - Option.iter (fun v -> Format.fprintf ppf "repository-code: %s@," v) t.repository_code; 122 + Option.iter 123 + (fun v -> Format.fprintf ppf "repository-code: %s@," v) 124 + t.repository_code; 141 125 Option.iter (fun v -> Format.fprintf ppf "abstract: %s@," v) t.abstract; 142 126 Option.iter (fun v -> Format.fprintf ppf "commit: %s@," v) t.commit; 143 - Option.iter (fun v -> Format.fprintf ppf "type: %a@," Cff_enums.Cff_type.pp v) t.type_; 127 + Option.iter 128 + (fun v -> Format.fprintf ppf "type: %a@," Cff_enums.Cff_type.pp v) 129 + t.type_; 144 130 Option.iter 145 131 (fun kws -> 146 - Format.fprintf ppf "keywords:@,"; 147 - List.iter (fun k -> Format.fprintf ppf " - %s@," k) kws) 132 + Format.fprintf ppf "keywords:@,"; 133 + List.iter (fun k -> Format.fprintf ppf " - %s@," k) kws) 148 134 t.keywords; 149 135 Option.iter 150 136 (fun ids -> 151 - Format.fprintf ppf "identifiers:@,"; 152 - List.iter (fun id -> Format.fprintf ppf " - %a@," Cff_identifier.pp id) ids) 137 + Format.fprintf ppf "identifiers:@,"; 138 + List.iter 139 + (fun id -> Format.fprintf ppf " - %a@," Cff_identifier.pp id) 140 + ids) 153 141 t.identifiers; 154 142 Option.iter 155 143 (fun contacts -> 156 - Format.fprintf ppf "contact:@,"; 157 - List.iter (fun c -> Format.fprintf ppf " - %a@," Cff_author.pp c) contacts) 144 + Format.fprintf ppf "contact:@,"; 145 + List.iter 146 + (fun c -> Format.fprintf ppf " - %a@," Cff_author.pp c) 147 + contacts) 158 148 t.contact; 159 149 Option.iter 160 150 (fun refs -> 161 - Format.fprintf ppf "references:@,"; 162 - List.iter (fun r -> Format.fprintf ppf " - %a@," Cff_reference.pp r) refs) 151 + Format.fprintf ppf "references:@,"; 152 + List.iter (fun r -> Format.fprintf ppf " - %a@," Cff_reference.pp r) refs) 163 153 t.references; 164 154 Option.iter 165 - (fun pc -> Format.fprintf ppf "preferred-citation:@, %a@," Cff_reference.pp pc) 155 + (fun pc -> 156 + Format.fprintf ppf "preferred-citation:@, %a@," Cff_reference.pp pc) 166 157 t.preferred_citation; 167 158 Format.fprintf ppf "@]" 168 - ;; 169 159 170 160 let list_jsont elt = 171 - Jsont.(array elt |> map ~dec:Stdlib.Array.to_list ~enc:Stdlib.Array.of_list) 172 - ;; 161 + Json.Codec.( 162 + array elt |> map ~dec:Stdlib.Array.to_list ~enc:Stdlib.Array.of_list) 173 163 174 164 let jsont = 175 - let open Jsont in 165 + let open Json.Codec in 176 166 let authors_jsont = list_jsont Cff_author.jsont in 177 167 let identifiers_jsont = list_jsont Cff_identifier.jsont in 178 168 let references_jsont = list_jsont Cff_reference.jsont in 179 169 let keywords_jsont = list_jsont string in 180 - Object.map 181 - ~kind:"CFF" 170 + Object.map ~kind:"CFF" 182 171 (fun 183 - cff_version 184 - message 185 - title 186 - authors 187 - abstract 188 - commit 189 - contact 190 - date_released 191 - doi 192 - identifiers 193 - keywords 194 - license 195 - license_url 196 - preferred_citation 197 - references 198 - repository 199 - repository_artifact 200 - repository_code 201 - type_ 202 - url 203 - version 204 - -> 205 - let license = Option.map (Cff_license.with_url_opt license_url) license in 206 - { cff_version 207 - ; message 208 - ; title 209 - ; authors 210 - ; abstract 211 - ; commit 212 - ; contact 213 - ; date_released 214 - ; doi 215 - ; identifiers 216 - ; keywords 217 - ; license 218 - ; preferred_citation 219 - ; references 220 - ; repository 221 - ; repository_artifact 222 - ; repository_code 223 - ; type_ 224 - ; url 225 - ; version 226 - }) 227 - |> Object.mem "cff-version" string ~enc:(fun t -> t.cff_version) 228 - |> Object.mem "message" string ~enc:(fun t -> t.message) 229 - |> Object.mem "title" string ~enc:(fun t -> t.title) 230 - |> Object.mem "authors" authors_jsont ~enc:(fun t -> t.authors) 231 - |> Object.opt_mem "abstract" string ~enc:(fun t -> t.abstract) 232 - |> Object.opt_mem "commit" string ~enc:(fun t -> t.commit) 233 - |> Object.opt_mem "contact" authors_jsont ~enc:(fun t -> t.contact) 234 - |> Object.opt_mem "date-released" Cff_date.jsont ~enc:(fun t -> t.date_released) 235 - |> Object.opt_mem "doi" string ~enc:(fun t -> t.doi) 236 - |> Object.opt_mem "identifiers" identifiers_jsont ~enc:(fun t -> t.identifiers) 237 - |> Object.opt_mem "keywords" keywords_jsont ~enc:(fun t -> t.keywords) 238 - |> Object.opt_mem "license" Cff_license.jsont ~enc:(fun t -> t.license) 239 - |> Object.opt_mem "license-url" string ~enc:(fun t -> 240 - Option.bind t.license Cff_license.url) 241 - |> Object.opt_mem "preferred-citation" Cff_reference.jsont ~enc:(fun t -> 242 - t.preferred_citation) 243 - |> Object.opt_mem "references" references_jsont ~enc:(fun t -> t.references) 244 - |> Object.opt_mem "repository" string ~enc:(fun t -> t.repository) 245 - |> Object.opt_mem "repository-artifact" string ~enc:(fun t -> t.repository_artifact) 246 - |> Object.opt_mem "repository-code" string ~enc:(fun t -> t.repository_code) 247 - |> Object.opt_mem "type" Cff_enums.Cff_type.jsont ~enc:(fun t -> t.type_) 248 - |> Object.opt_mem "url" string ~enc:(fun t -> t.url) 249 - |> Object.opt_mem "version" string ~enc:(fun t -> t.version) 250 - |> Object.finish 251 - ;; 172 + cff_version 173 + message 174 + title 175 + authors 176 + abstract 177 + commit 178 + contact 179 + date_released 180 + doi 181 + identifiers 182 + keywords 183 + license 184 + license_url 185 + preferred_citation 186 + references 187 + repository 188 + repository_artifact 189 + repository_code 190 + type_ 191 + url 192 + version 193 + -> 194 + let license = Option.map (Cff_license.with_url_opt license_url) license in 195 + { 196 + cff_version; 197 + message; 198 + title; 199 + authors; 200 + abstract; 201 + commit; 202 + contact; 203 + date_released; 204 + doi; 205 + identifiers; 206 + keywords; 207 + license; 208 + preferred_citation; 209 + references; 210 + repository; 211 + repository_artifact; 212 + repository_code; 213 + type_; 214 + url; 215 + version; 216 + }) 217 + |> Object.member "cff-version" string ~enc:(fun t -> t.cff_version) 218 + |> Object.member "message" string ~enc:(fun t -> t.message) 219 + |> Object.member "title" string ~enc:(fun t -> t.title) 220 + |> Object.member "authors" authors_jsont ~enc:(fun t -> t.authors) 221 + |> Object.opt_member "abstract" string ~enc:(fun t -> t.abstract) 222 + |> Object.opt_member "commit" string ~enc:(fun t -> t.commit) 223 + |> Object.opt_member "contact" authors_jsont ~enc:(fun t -> t.contact) 224 + |> Object.opt_member "date-released" Cff_date.jsont ~enc:(fun t -> 225 + t.date_released) 226 + |> Object.opt_member "doi" string ~enc:(fun t -> t.doi) 227 + |> Object.opt_member "identifiers" identifiers_jsont ~enc:(fun t -> 228 + t.identifiers) 229 + |> Object.opt_member "keywords" keywords_jsont ~enc:(fun t -> t.keywords) 230 + |> Object.opt_member "license" Cff_license.jsont ~enc:(fun t -> t.license) 231 + |> Object.opt_member "license-url" string ~enc:(fun t -> 232 + Option.bind t.license Cff_license.url) 233 + |> Object.opt_member "preferred-citation" Cff_reference.jsont ~enc:(fun t -> 234 + t.preferred_citation) 235 + |> Object.opt_member "references" references_jsont ~enc:(fun t -> 236 + t.references) 237 + |> Object.opt_member "repository" string ~enc:(fun t -> t.repository) 238 + |> Object.opt_member "repository-artifact" string ~enc:(fun t -> 239 + t.repository_artifact) 240 + |> Object.opt_member "repository-code" string ~enc:(fun t -> 241 + t.repository_code) 242 + |> Object.opt_member "type" Cff_enums.Cff_type.jsont ~enc:(fun t -> t.type_) 243 + |> Object.opt_member "url" string ~enc:(fun t -> t.url) 244 + |> Object.opt_member "version" string ~enc:(fun t -> t.version) 245 + |> Object.seal
+133 -128
lib/cff.mli
··· 7 7 8 8 This library provides codecs for the 9 9 {{:https://citation-file-format.github.io/}Citation File Format (CFF)} 10 - version 1.2.0, a human- and machine-readable format for software and 11 - dataset citation metadata. 10 + version 1.2.0, a human- and machine-readable format for software and dataset 11 + citation metadata. 12 12 13 - CFF files are named [CITATION.cff] written in the {{:https://yaml.org/}YAML 1.2} 14 - format. They provide citation metadata for software and datasets, enabling 15 - proper academic credit for research software. 13 + CFF files are named [CITATION.cff] written in the 14 + {{:https://yaml.org/}YAML 1.2} format. They provide citation metadata for 15 + software and datasets, enabling proper academic credit for research 16 + software. 16 17 17 18 {1 Overview} 18 19 ··· 43 44 44 45 Example with [cff.unix]: 45 46 {[ 46 - match Cff_unix.of_file "CITATION.cff" with 47 - | Ok cff -> Printf.printf "Title: %s\n%!" (Cff.title cff) 48 - | Error msg -> Printf.eprintf "Error: %s\n%!" msg 47 + match Cff_unix.of_file "CITATION.cff" with 48 + | Ok cff -> Printf.printf "Title: %s\n%!" (Cff.title cff) 49 + | Error msg -> Printf.eprintf "Error: %s\n%!" msg 49 50 ]} 50 51 51 52 {1 CFF Specification} 52 53 53 54 This implementation follows the 54 - {{:https://github.com/citation-file-format/citation-file-format}CFF 1.2.0 specification}. 55 - Useful modules include: 55 + {{:https://github.com/citation-file-format/citation-file-format}CFF 1.2.0 56 + specification}. Useful modules include: 56 57 57 58 - {!module:Author}: Can be persons (with family/given names) or entities 58 59 (organizations, identified by a [name] field) 59 - - {!module:Reference}: Bibliography entries that the work cites or depends on 60 - - {!module:Identifier}: Typed identifiers including DOIs, URLs, and 61 - Software Heritage IDs (SWH) 62 - - {!module:License}: SPDX license identifiers where multiple licenses imply "OR" 60 + - {!module:Reference}: Bibliography entries that the work cites or depends 61 + on 62 + - {!module:Identifier}: Typed identifiers including DOIs, URLs, and Software 63 + Heritage IDs (SWH) 64 + - {!module:License}: SPDX license identifiers where multiple licenses imply 65 + "OR" 63 66 64 67 {1 Core Types} *) 65 68 ··· 80 83 - {!keywords}: Descriptive keywords 81 84 - {!abstract}: Description of the work 82 85 83 - The {!preferred_citation} field allows redirecting citations to 84 - a related work (e.g., a journal article describing the software). 85 - The {!section-references} field lists works that the software cites or 86 - depends upon. *) 86 + The {!preferred_citation} field allows redirecting citations to a related 87 + work (e.g., a journal article describing the software). The 88 + {!section-references} field lists works that the software cites or depends 89 + upon. *) 87 90 88 - (** The abstract type representing a complete CFF document. *) 89 91 type t 92 + (** The abstract type representing a complete CFF document. *) 90 93 94 + module Date = Cff_date 91 95 (** Date representation as [(year, month, day)] tuple. 92 96 93 97 CFF uses ISO 8601 dates in [YYYY-MM-DD] format (e.g., ["2024-01-15"]). *) 94 - module Date = Cff_date 95 98 99 + module Country = Cff_country 96 100 (** ISO 3166-1 alpha-2 country codes (e.g., ["US"], ["DE"], ["GB"]). 97 101 98 102 Used for author and entity addresses. *) 99 - module Country = Cff_country 100 103 104 + module Address = Cff_address.Address 101 105 (** Physical address information. 102 106 103 - Address fields used for persons and entities: street address, city, 104 - region (state/province), postal code, and country code. *) 105 - module Address = Cff_address.Address 107 + Address fields used for persons and entities: street address, city, region 108 + (state/province), postal code, and country code. *) 106 109 110 + module Contact = Cff_address.Contact 107 111 (** Contact information. 108 112 109 - Contact fields used for persons and entities: email, telephone, fax, 110 - website URL, and ORCID identifier. *) 111 - module Contact = Cff_address.Contact 113 + Contact fields used for persons and entities: email, telephone, fax, website 114 + URL, and ORCID identifier. *) 112 115 116 + module License = Cff_license 113 117 (** SPDX license identifiers. 114 118 115 - CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers} for 116 - the [license] field. Multiple licenses indicate an OR relationship 117 - (the user may choose any of the listed licenses). *) 118 - module License = Cff_license 119 + CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers} for the 120 + [license] field. Multiple licenses indicate an OR relationship (the user may 121 + choose any of the listed licenses). *) 119 122 123 + module Cff_type = Cff_enums.Cff_type 120 124 (** CFF file type: [`Software] (default) or [`Dataset]. *) 121 - module Cff_type = Cff_enums.Cff_type 122 125 123 126 (** {1 Authors and Entities} *) 124 127 128 + module Author = Cff_author 125 129 (** Authors as a discriminated union of {!Person} or {!Entity}. 126 130 127 131 CFF distinguishes between: 128 132 - {b Persons}: Individual humans with family names, given names, etc. 129 133 - {b Entities}: Organizations, projects, or groups with a [name] field 130 134 131 - When parsing, the presence of a [name] field indicates an entity; 132 - otherwise, the entry is treated as a person. *) 133 - module Author = Cff_author 135 + When parsing, the presence of a [name] field indicates an entity; otherwise, 136 + the entry is treated as a person. *) 134 137 135 - (** A person (individual author or contributor). *) 136 138 module Person = Cff_author.Person 139 + (** A person (individual author or contributor). *) 137 140 138 - (** An entity (organization, institution, project, conference). *) 139 141 module Entity = Cff_author.Entity 142 + (** An entity (organization, institution, project, conference). *) 140 143 141 144 (** {1 Identifiers and References} *) 142 145 146 + module Identifier = Cff_identifier 143 147 (** Typed identifiers for DOI, URL, SWH, or other schemes. 144 148 145 149 Each identifier has a type, value, and optional description. Example: 146 150 {[ 147 - let id = Cff.Identifier.make 148 - ~type_:`Doi 149 - ~value:"10.5281/zenodo.1234567" 150 - ~description:"The concept DOI for all versions" 151 - () 151 + let id = 152 + Cff.Identifier.make ~type_:`Doi ~value:"10.5281/zenodo.1234567" 153 + ~description:"The concept DOI for all versions" () 152 154 ]} *) 153 - module Identifier = Cff_identifier 154 155 156 + module Reference = Cff_reference 155 157 (** Bibliographic references with comprehensive metadata. 156 158 157 159 References can represent any citable work: articles, books, software, 158 - datasets, conference papers, theses, etc. The {!Reference} module 159 - provides 60+ fields organized into logical sub-records: 160 + datasets, conference papers, theses, etc. The {!Reference} module provides 161 + 60+ fields organized into logical sub-records: 160 162 161 163 - {!Reference.Core} - Type, title, authors, abstract 162 164 - {!Reference.Publication} - Journal, volume, issue, pages ··· 166 168 - {!Reference.Entities} - Editors, publisher, institution 167 169 - {!Reference.Metadata} - Keywords, license, notes 168 170 - {!Reference.Technical} - Commit, version, format *) 169 - module Reference = Cff_reference 170 171 171 172 (** {1 Construction} *) 172 173 173 - (** The default CFF version used when not specified: ["1.2.0"]. *) 174 174 val default_cff_version : string 175 + (** The default CFF version used when not specified: ["1.2.0"]. *) 175 176 177 + val default_message : string 176 178 (** The default citation message: 177 - ["If you use this software, please cite it using the metadata from this file."] *) 178 - val default_message : string 179 + ["If you use this software, please cite it using the metadata from this 180 + file."] *) 179 181 182 + val make : 183 + ?cff_version:string -> 184 + ?message:string -> 185 + title:string -> 186 + authors:Author.t list -> 187 + ?abstract:string -> 188 + ?commit:string -> 189 + ?contact:Author.t list -> 190 + ?date_released:Date.t -> 191 + ?doi:string -> 192 + ?identifiers:Identifier.t list -> 193 + ?keywords:string list -> 194 + ?license:License.t -> 195 + ?preferred_citation:Reference.t -> 196 + ?references:Reference.t list -> 197 + ?repository:string -> 198 + ?repository_artifact:string -> 199 + ?repository_code:string -> 200 + ?type_:Cff_type.t -> 201 + ?url:string -> 202 + ?version:string -> 203 + unit -> 204 + t 180 205 (** [make ~title ~authors ...] constructs a CFF value. 181 206 182 207 @param cff_version The CFF schema version (default: {!default_cff_version}) 183 - @param message Instructions for users on how to cite (default: {!default_message}) 208 + @param message 209 + Instructions for users on how to cite (default: {!default_message}) 184 210 @param title The name of the software or dataset 185 211 @param authors List of persons and/or entities who created the work *) 186 - val make 187 - : ?cff_version:string 188 - -> ?message:string 189 - -> title:string 190 - -> authors:Author.t list 191 - -> ?abstract:string 192 - -> ?commit:string 193 - -> ?contact:Author.t list 194 - -> ?date_released:Date.t 195 - -> ?doi:string 196 - -> ?identifiers:Identifier.t list 197 - -> ?keywords:string list 198 - -> ?license:License.t 199 - -> ?preferred_citation:Reference.t 200 - -> ?references:Reference.t list 201 - -> ?repository:string 202 - -> ?repository_artifact:string 203 - -> ?repository_code:string 204 - -> ?type_:Cff_type.t 205 - -> ?url:string 206 - -> ?version:string 207 - -> unit 208 - -> t 209 212 210 213 (** {2 Required Fields} *) 211 214 215 + val cff_version : t -> string 212 216 (** The CFF schema version that this file adheres to. 213 217 214 - For CFF 1.2.0 files, this should be ["1.2.0"]. The version determines 215 - which keys are valid and how they should be interpreted. *) 216 - val cff_version : t -> string 218 + For CFF 1.2.0 files, this should be ["1.2.0"]. The version determines which 219 + keys are valid and how they should be interpreted. *) 217 220 221 + val message : t -> string 218 222 (** A message to readers explaining how to cite the work. 219 223 220 224 Common examples: 221 - - ["If you use this software, please cite it using the metadata from this file."] 222 - - ["Please cite this software using the metadata from 'preferred-citation'."] 225 + - ["If you use this software, please cite it using the metadata from this 226 + file."] 227 + - ["Please cite this software using the metadata from 228 + 'preferred-citation'."] 223 229 224 230 The message should guide users toward the preferred citation method. *) 225 - val message : t -> string 226 231 232 + val title : t -> string 227 233 (** The name of the software or dataset. 228 234 229 235 This is the title that should appear in citations. For software, it's 230 236 typically the project name; for datasets, the dataset title. *) 231 - val title : t -> string 232 237 238 + val authors : t -> Author.t list 233 239 (** The creators of the software or dataset. 234 240 235 - Authors can be persons (individuals) or entities (organizations). 236 - At least one author is required for a valid CFF file. The order 237 - typically reflects contribution significance. *) 238 - val authors : t -> Author.t list 241 + Authors can be persons (individuals) or entities (organizations). At least 242 + one author is required for a valid CFF file. The order typically reflects 243 + contribution significance. *) 239 244 240 245 (** {2 Optional Fields} *) 241 246 247 + val abstract : t -> string option 242 248 (** A description of the software or dataset. 243 249 244 250 Provides context about what the work does, its purpose, and scope. *) 245 - val abstract : t -> string option 246 251 252 + val commit : t -> string option 247 253 (** The commit hash or revision number of the software version. 248 254 249 255 Useful for precise version identification beyond semantic versioning. 250 256 Example: ["1ff847d81f29c45a3a1a5ce73d38e45c2f319bba"] *) 251 - val commit : t -> string option 252 257 258 + val contact : t -> Author.t list option 253 259 (** Contact persons or entities for the software or dataset. 254 260 255 - May differ from authors; useful when the primary contact is a 256 - project maintainer rather than the original author. *) 257 - val contact : t -> Author.t list option 261 + May differ from authors; useful when the primary contact is a project 262 + maintainer rather than the original author. *) 258 263 264 + val date_released : t -> Date.t option 259 265 (** The date when the software or dataset was released. 260 266 261 267 Format is [(year, month, day)], corresponding to ISO 8601 [YYYY-MM-DD]. *) 262 - val date_released : t -> Date.t option 263 268 269 + val doi : t -> string option 264 270 (** The Digital Object Identifier for the software or dataset. 265 271 266 - DOIs provide persistent, citable identifiers. This is a shorthand 267 - for a single DOI; use {!identifiers} for multiple DOIs or other 268 - identifier types. Example: ["10.5281/zenodo.1234567"] *) 269 - val doi : t -> string option 272 + DOIs provide persistent, citable identifiers. This is a shorthand for a 273 + single DOI; use {!identifiers} for multiple DOIs or other identifier types. 274 + Example: ["10.5281/zenodo.1234567"] *) 270 275 276 + val identifiers : t -> Identifier.t list option 271 277 (** Additional identifiers beyond the primary DOI. 272 278 273 - Each identifier has a type (DOI, URL, SWH, other), value, and 274 - optional description. Useful for versioned DOIs, Software Heritage 275 - identifiers, or repository URLs. *) 276 - val identifiers : t -> Identifier.t list option 279 + Each identifier has a type (DOI, URL, SWH, other), value, and optional 280 + description. Useful for versioned DOIs, Software Heritage identifiers, or 281 + repository URLs. *) 277 282 283 + val keywords : t -> string list option 278 284 (** Descriptive keywords for the work. 279 285 280 286 Help with discoverability and categorization. Example: 281 287 [["machine learning"; "image processing"; "python"]] *) 282 - val keywords : t -> string list option 283 288 289 + val license : t -> License.t option 284 290 (** The SPDX license identifier(s) for the work. 285 291 286 - Uses {{:https://spdx.org/licenses/}SPDX identifiers}. Multiple 287 - licenses imply an OR relationship (user may choose any). 288 - Example: ["MIT"], ["Apache-2.0"], or [["GPL-3.0-only"; "MIT"]]. *) 289 - val license : t -> License.t option 292 + Uses {{:https://spdx.org/licenses/}SPDX identifiers}. Multiple licenses 293 + imply an OR relationship (user may choose any). Example: ["MIT"], 294 + ["Apache-2.0"], or [["GPL-3.0-only"; "MIT"]]. *) 290 295 296 + val preferred_citation : t -> Reference.t option 291 297 (** A reference to cite instead of the software itself. 292 298 293 - Used for "credit redirection" when authors prefer citation of 294 - a related publication (e.g., a methods paper) over the software. 295 - Note: Software citation principles recommend citing software 296 - directly; use this field judiciously. *) 297 - val preferred_citation : t -> Reference.t option 299 + Used for "credit redirection" when authors prefer citation of a related 300 + publication (e.g., a methods paper) over the software. Note: Software 301 + citation principles recommend citing software directly; use this field 302 + judiciously. *) 298 303 304 + val references : t -> Reference.t list option 299 305 (** Works that this software cites or depends upon. 300 306 301 - Functions like a bibliography, listing dependencies, foundational 302 - works, or related publications. Each reference includes full 303 - bibliographic metadata. *) 304 - val references : t -> Reference.t list option 307 + Functions like a bibliography, listing dependencies, foundational works, or 308 + related publications. Each reference includes full bibliographic metadata. 309 + *) 305 310 311 + val repository : t -> string option 306 312 (** URL to the repository where the software is developed. 307 313 308 - Typically a version control system URL. For source code repositories, 309 - prefer {!repository_code}. *) 310 - val repository : t -> string option 314 + Typically a version control system URL. For source code repositories, prefer 315 + {!repository_code}. *) 311 316 317 + val repository_artifact : t -> string option 312 318 (** URL to the built/compiled artifact repository. 313 319 314 - For binary distributions, package registries (npm, PyPI, CRAN), 315 - or container registries. *) 316 - val repository_artifact : t -> string option 320 + For binary distributions, package registries (npm, PyPI, CRAN), or container 321 + registries. *) 317 322 323 + val repository_code : t -> string option 318 324 (** URL to the source code repository. 319 325 320 - Typically a GitHub, GitLab, or similar URL where the source 321 - code is publicly accessible. *) 322 - val repository_code : t -> string option 326 + Typically a GitHub, GitLab, or similar URL where the source code is publicly 327 + accessible. *) 323 328 329 + val type_ : t -> Cff_type.t option 324 330 (** The type of work: [`Software] (default) or [`Dataset]. 325 331 326 332 Most CFF files describe software; use [`Dataset] for data packages. *) 327 - val type_ : t -> Cff_type.t option 328 333 334 + val url : t -> string option 329 335 (** The URL of the software or dataset homepage. 330 336 331 337 A general landing page, documentation site, or project website. *) 332 - val url : t -> string option 333 338 339 + val version : t -> string option 334 340 (** The version string of the software or dataset. 335 341 336 - Can be any version format: semantic versioning (["1.2.3"]), 337 - date-based (["2024.01"]), or other schemes. *) 338 - val version : t -> string option 342 + Can be any version format: semantic versioning (["1.2.3"]), date-based 343 + (["2024.01"]), or other schemes. *) 339 344 340 345 (** {1 Formatting and Codec} *) 341 346 342 - (** Pretty-print a CFF value in a human-readable YAML-like format. *) 343 347 val pp : Format.formatter -> t -> unit 348 + (** Pretty-print a CFF value in a human-readable YAML-like format. *) 344 349 350 + val jsont : t Json.Codec.t 345 351 (** JSON/YAML codec for serialization and deserialization. 346 352 347 353 Used internally by the YAML codec functions. *) 348 - val jsont : t Jsont.t
+61 -44
lib/cff_address.ml
··· 7 7 8 8 (** Physical address information. *) 9 9 module Address = struct 10 - type t = 11 - { address : string option 12 - ; city : string option 13 - ; region : string option 14 - ; post_code : string option 15 - ; country : string option (* ISO 3166-1 alpha-2 *) 16 - } 10 + type t = { 11 + address : string option; 12 + city : string option; 13 + region : string option; 14 + post_code : string option; 15 + country : string option (* ISO 3166-1 alpha-2 *); 16 + } 17 17 18 18 let empty = 19 - { address = None; city = None; region = None; post_code = None; country = None } 20 - ;; 19 + { 20 + address = None; 21 + city = None; 22 + region = None; 23 + post_code = None; 24 + country = None; 25 + } 21 26 22 27 let make ?address ?city ?region ?post_code ?country () = 23 28 { address; city; region; post_code; country } 24 - ;; 25 29 26 30 let of_options ~address ~city ~region ~post_code ~country = 27 31 { address; city; region; post_code; country } 28 - ;; 29 32 30 33 let address t = t.address 31 34 let city t = t.city ··· 34 37 let country t = t.country 35 38 36 39 let is_empty t = 37 - t.address = None 38 - && t.city = None 39 - && t.region = None 40 - && t.post_code = None 40 + t.address = None && t.city = None && t.region = None && t.post_code = None 41 41 && t.country = None 42 - ;; 43 42 44 43 let pp ppf t = 45 44 let parts = 46 - List.filter_map Fun.id [ t.address; t.city; t.region; t.post_code; t.country ] 45 + List.filter_map Fun.id 46 + [ t.address; t.city; t.region; t.post_code; t.country ] 47 47 in 48 48 Format.pp_print_string ppf (String.concat ", " parts) 49 - ;; 50 49 51 50 let jsont_fields ~get obj = 52 51 obj 53 - |> Jsont.Object.opt_mem "address" Jsont.string ~enc:(fun x -> (get x).address) 54 - |> Jsont.Object.opt_mem "city" Jsont.string ~enc:(fun x -> (get x).city) 55 - |> Jsont.Object.opt_mem "region" Jsont.string ~enc:(fun x -> (get x).region) 56 - |> Jsont.Object.opt_mem "post-code" Jsont.string ~enc:(fun x -> (get x).post_code) 57 - |> Jsont.Object.opt_mem "country" Jsont.string ~enc:(fun x -> (get x).country) 58 - ;; 52 + |> Json.Codec.Object.opt_member "address" Json.Codec.string ~enc:(fun x -> 53 + (get x).address) 54 + |> Json.Codec.Object.opt_member "city" Json.Codec.string ~enc:(fun x -> 55 + (get x).city) 56 + |> Json.Codec.Object.opt_member "region" Json.Codec.string ~enc:(fun x -> 57 + (get x).region) 58 + |> Json.Codec.Object.opt_member "post-code" Json.Codec.string ~enc:(fun x -> 59 + (get x).post_code) 60 + |> Json.Codec.Object.opt_member "country" Json.Codec.string ~enc:(fun x -> 61 + (get x).country) 59 62 end 60 63 61 64 (** Contact information. *) 62 65 module Contact = struct 63 - type t = 64 - { email : string option 65 - ; tel : string option 66 - ; fax : string option 67 - ; website : string option 68 - ; orcid : string option 69 - } 66 + type t = { 67 + email : string option; 68 + tel : string option; 69 + fax : string option; 70 + website : string option; 71 + orcid : string option; 72 + } 70 73 71 - let empty = { email = None; tel = None; fax = None; website = None; orcid = None } 72 - let make ?email ?tel ?fax ?website ?orcid () = { email; tel; fax; website; orcid } 73 - let of_options ~email ~tel ~fax ~website ~orcid = { email; tel; fax; website; orcid } 74 + let empty = 75 + { email = None; tel = None; fax = None; website = None; orcid = None } 76 + 77 + let make ?email ?tel ?fax ?website ?orcid () = 78 + { email; tel; fax; website; orcid } 79 + 80 + let of_options ~email ~tel ~fax ~website ~orcid = 81 + { email; tel; fax; website; orcid } 82 + 74 83 let email t = t.email 75 84 let tel t = t.tel 76 85 let fax t = t.fax ··· 78 87 let orcid t = t.orcid 79 88 80 89 let is_empty t = 81 - t.email = None && t.tel = None && t.fax = None && t.website = None && t.orcid = None 82 - ;; 90 + t.email = None && t.tel = None && t.fax = None && t.website = None 91 + && t.orcid = None 83 92 84 93 let pp ppf t = 85 94 let parts = 86 95 List.filter_map 87 96 (fun (k, v) -> Option.map (fun v -> k ^ ": " ^ v) v) 88 - [ "email", t.email; "tel", t.tel; "website", t.website; "orcid", t.orcid ] 97 + [ 98 + ("email", t.email); 99 + ("tel", t.tel); 100 + ("website", t.website); 101 + ("orcid", t.orcid); 102 + ] 89 103 in 90 104 Format.pp_print_string ppf (String.concat ", " parts) 91 - ;; 92 105 93 106 let jsont_fields ~get obj = 94 107 obj 95 - |> Jsont.Object.opt_mem "email" Jsont.string ~enc:(fun x -> (get x).email) 96 - |> Jsont.Object.opt_mem "tel" Jsont.string ~enc:(fun x -> (get x).tel) 97 - |> Jsont.Object.opt_mem "fax" Jsont.string ~enc:(fun x -> (get x).fax) 98 - |> Jsont.Object.opt_mem "website" Jsont.string ~enc:(fun x -> (get x).website) 99 - |> Jsont.Object.opt_mem "orcid" Jsont.string ~enc:(fun x -> (get x).orcid) 100 - ;; 108 + |> Json.Codec.Object.opt_member "email" Json.Codec.string ~enc:(fun x -> 109 + (get x).email) 110 + |> Json.Codec.Object.opt_member "tel" Json.Codec.string ~enc:(fun x -> 111 + (get x).tel) 112 + |> Json.Codec.Object.opt_member "fax" Json.Codec.string ~enc:(fun x -> 113 + (get x).fax) 114 + |> Json.Codec.Object.opt_member "website" Json.Codec.string ~enc:(fun x -> 115 + (get x).website) 116 + |> Json.Codec.Object.opt_member "orcid" Json.Codec.string ~enc:(fun x -> 117 + (get x).orcid) 101 118 end
+79 -79
lib/cff_address.mli
··· 5 5 6 6 (** Physical address and contact information for CFF. 7 7 8 - CFF includes address and contact fields for both persons and entities. 9 - This module provides types for these shared fields. 8 + CFF includes address and contact fields for both persons and entities. This 9 + module provides types for these shared fields. 10 10 11 11 {1 Address Fields} 12 12 ··· 48 48 49 49 All fields are optional; an empty address is valid. *) 50 50 module Address : sig 51 - (** Physical address record. *) 52 51 type t 52 + (** Physical address record. *) 53 53 54 + val empty : t 54 55 (** Empty address with all fields [None]. *) 55 - val empty : t 56 56 57 + val make : 58 + ?address:string -> 59 + ?city:string -> 60 + ?region:string -> 61 + ?post_code:string -> 62 + ?country:string -> 63 + unit -> 64 + t 57 65 (** Create an address with optional fields. 58 66 59 67 @param address Street address ··· 61 69 @param region State, province, or administrative region 62 70 @param post_code Postal code, ZIP code, or postcode 63 71 @param country ISO 3166-1 alpha-2 country code *) 64 - val make 65 - : ?address:string 66 - -> ?city:string 67 - -> ?region:string 68 - -> ?post_code:string 69 - -> ?country:string 70 - -> unit 71 - -> t 72 72 73 + val of_options : 74 + address:string option -> 75 + city:string option -> 76 + region:string option -> 77 + post_code:string option -> 78 + country:string option -> 79 + t 73 80 (** Create an address from option values directly. 74 81 75 82 Used internally by jsont decoders where fields are decoded as options. *) 76 - val of_options 77 - : address:string option 78 - -> city:string option 79 - -> region:string option 80 - -> post_code:string option 81 - -> country:string option 82 - -> t 83 83 84 - (** Street address (e.g., ["77 Massachusetts Avenue"]). *) 85 84 val address : t -> string option 85 + (** Street address (e.g., ["77 Massachusetts Avenue"]). *) 86 86 87 - (** City name (e.g., ["Cambridge"], ["London"]). *) 88 87 val city : t -> string option 88 + (** City name (e.g., ["Cambridge"], ["London"]). *) 89 89 90 + val region : t -> string option 90 91 (** State, province, or region (e.g., ["Massachusetts"], ["Bavaria"]). *) 91 - val region : t -> string option 92 92 93 - (** Postal or ZIP code (e.g., ["02139"], ["W1A 1AA"]). *) 94 93 val post_code : t -> string option 94 + (** Postal or ZIP code (e.g., ["02139"], ["W1A 1AA"]). *) 95 95 96 + val country : t -> string option 96 97 (** ISO 3166-1 alpha-2 country code (e.g., ["US"], ["DE"], ["GB"]). *) 97 - val country : t -> string option 98 98 99 - (** [true] if all fields are [None]. *) 100 99 val is_empty : t -> bool 100 + (** [true] if all fields are [None]. *) 101 101 102 - (** Pretty-print the address. *) 103 102 val pp : Format.formatter -> t -> unit 103 + (** Pretty-print the address. *) 104 104 105 + val jsont_fields : 106 + get:('a -> t) -> 107 + ( 'a, 108 + string option -> 109 + string option -> 110 + string option -> 111 + string option -> 112 + string option -> 113 + 'b ) 114 + Json.Codec.Object.map -> 115 + ('a, 'b) Json.Codec.Object.map 105 116 (** Add address fields to a jsont object builder. 106 117 107 118 This adds the five address fields (address, city, region, post-code, ··· 109 120 [string option] arguments in that order. 110 121 111 122 @param get Extracts the address from the parent type for encoding *) 112 - val jsont_fields 113 - : get:('a -> t) 114 - -> ( 'a 115 - , string option 116 - -> string option 117 - -> string option 118 - -> string option 119 - -> string option 120 - -> 'b ) 121 - Jsont.Object.map 122 - -> ('a, 'b) Jsont.Object.map 123 123 end 124 124 125 125 (** Contact information. 126 126 127 - Electronic contact details for persons and entities. All fields 128 - are optional. *) 127 + Electronic contact details for persons and entities. All fields are 128 + optional. *) 129 129 module Contact : sig 130 - (** Contact information record. *) 131 130 type t 131 + (** Contact information record. *) 132 132 133 + val empty : t 133 134 (** Empty contact with all fields [None]. *) 134 - val empty : t 135 135 136 + val make : 137 + ?email:string -> 138 + ?tel:string -> 139 + ?fax:string -> 140 + ?website:string -> 141 + ?orcid:string -> 142 + unit -> 143 + t 136 144 (** Create contact information with optional fields. 137 145 138 146 @param email Email address ··· 140 148 @param fax Fax number (any format) 141 149 @param website Website URL 142 150 @param orcid ORCID identifier URL *) 143 - val make 144 - : ?email:string 145 - -> ?tel:string 146 - -> ?fax:string 147 - -> ?website:string 148 - -> ?orcid:string 149 - -> unit 150 - -> t 151 151 152 + val of_options : 153 + email:string option -> 154 + tel:string option -> 155 + fax:string option -> 156 + website:string option -> 157 + orcid:string option -> 158 + t 152 159 (** Create contact info from option values directly. 153 160 154 161 Used internally by jsont decoders where fields are decoded as options. *) 155 - val of_options 156 - : email:string option 157 - -> tel:string option 158 - -> fax:string option 159 - -> website:string option 160 - -> orcid:string option 161 - -> t 162 162 163 - (** Email address (e.g., ["jane.smith\@example.org"]). *) 164 163 val email : t -> string option 164 + (** Email address (e.g., ["jane.smith\@example.org"]). *) 165 165 166 - (** Telephone number. No specific format is required. *) 167 166 val tel : t -> string option 167 + (** Telephone number. No specific format is required. *) 168 168 169 + val fax : t -> string option 169 170 (** Fax number. No specific format is required. *) 170 - val fax : t -> string option 171 171 172 + val website : t -> string option 172 173 (** Website URL (e.g., ["https://example.org/~jsmith"]). *) 173 - val website : t -> string option 174 174 175 + val orcid : t -> string option 175 176 (** ORCID identifier as a URL. 176 177 177 - ORCID (Open Researcher and Contributor ID) provides persistent 178 - digital identifiers for researchers. 178 + ORCID (Open Researcher and Contributor ID) provides persistent digital 179 + identifiers for researchers. 179 180 180 181 Format: ["https://orcid.org/XXXX-XXXX-XXXX-XXXX"] 181 182 182 183 Example: ["https://orcid.org/0000-0001-2345-6789"] *) 183 - val orcid : t -> string option 184 184 185 + val is_empty : t -> bool 185 186 (** [true] if all fields are [None]. *) 186 - val is_empty : t -> bool 187 187 188 - (** Pretty-print the contact information. *) 189 188 val pp : Format.formatter -> t -> unit 189 + (** Pretty-print the contact information. *) 190 190 191 + val jsont_fields : 192 + get:('a -> t) -> 193 + ( 'a, 194 + string option -> 195 + string option -> 196 + string option -> 197 + string option -> 198 + string option -> 199 + 'b ) 200 + Json.Codec.Object.map -> 201 + ('a, 'b) Json.Codec.Object.map 191 202 (** Add contact fields to a jsont object builder. 192 203 193 - This adds the five contact fields (email, tel, fax, website, orcid) 194 - to an object codec. The decoder function must accept five 195 - [string option] arguments in that order. 204 + This adds the five contact fields (email, tel, fax, website, orcid) to an 205 + object codec. The decoder function must accept five [string option] 206 + arguments in that order. 196 207 197 208 @param get Extracts the contact from the parent type for encoding *) 198 - val jsont_fields 199 - : get:('a -> t) 200 - -> ( 'a 201 - , string option 202 - -> string option 203 - -> string option 204 - -> string option 205 - -> string option 206 - -> 'b ) 207 - Jsont.Object.map 208 - -> ('a, 'b) Jsont.Object.map 209 209 end
+141 -183
lib/cff_author.ml
··· 7 7 8 8 (** A person (individual author/contributor). *) 9 9 module Person = struct 10 - type t = 11 - { family_names : string option 12 - ; given_names : string option 13 - ; name_particle : string option 14 - ; name_suffix : string option 15 - ; alias : string option 16 - ; affiliation : string option 17 - ; address : Cff_address.Address.t 18 - ; contact : Cff_address.Contact.t 19 - } 10 + type t = { 11 + family_names : string option; 12 + given_names : string option; 13 + name_particle : string option; 14 + name_suffix : string option; 15 + alias : string option; 16 + affiliation : string option; 17 + address : Cff_address.Address.t; 18 + contact : Cff_address.Contact.t; 19 + } 20 20 21 - let make 22 - ?family_names 23 - ?given_names 24 - ?name_particle 25 - ?name_suffix 26 - ?alias 27 - ?affiliation 28 - ?(address = Cff_address.Address.empty) 29 - ?(contact = Cff_address.Contact.empty) 30 - () 31 - = 32 - { family_names 33 - ; given_names 34 - ; name_particle 35 - ; name_suffix 36 - ; alias 37 - ; affiliation 38 - ; address 39 - ; contact 21 + let make ?family_names ?given_names ?name_particle ?name_suffix ?alias 22 + ?affiliation ?(address = Cff_address.Address.empty) 23 + ?(contact = Cff_address.Contact.empty) () = 24 + { 25 + family_names; 26 + given_names; 27 + name_particle; 28 + name_suffix; 29 + alias; 30 + affiliation; 31 + address; 32 + contact; 40 33 } 41 - ;; 42 34 43 35 let family_names t = t.family_names 44 36 let given_names t = t.given_names ··· 57 49 match t.name_suffix with 58 50 | Some suffix -> base ^ ", " ^ suffix 59 51 | None -> base 60 - ;; 61 52 62 53 let email t = Cff_address.Contact.email t.contact 63 54 let orcid t = Cff_address.Contact.orcid t.contact ··· 66 57 let pp ppf t = 67 58 Format.fprintf ppf "%s" (full_name t); 68 59 Option.iter (Format.fprintf ppf " (%s)") t.affiliation 69 - ;; 70 60 71 61 let jsont = 72 - Jsont.Object.map 73 - ~kind:"Person" 62 + Json.Codec.Object.map ~kind:"Person" 74 63 (fun 75 - family_names 76 - given_names 77 - name_particle 78 - name_suffix 79 - alias 80 - affiliation 81 - address 82 - city 83 - region 84 - post_code 85 - country 86 - email 87 - tel 88 - fax 89 - website 90 - orcid 91 - -> 92 - let address = 93 - Cff_address.Address.of_options ~address ~city ~region ~post_code ~country 94 - in 95 - let contact = Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid in 96 - { family_names 97 - ; given_names 98 - ; name_particle 99 - ; name_suffix 100 - ; alias 101 - ; affiliation 102 - ; address 103 - ; contact 104 - }) 105 - |> Jsont.Object.opt_mem "family-names" Jsont.string ~enc:(fun p -> p.family_names) 106 - |> Jsont.Object.opt_mem "given-names" Jsont.string ~enc:(fun p -> p.given_names) 107 - |> Jsont.Object.opt_mem "name-particle" Jsont.string ~enc:(fun p -> p.name_particle) 108 - |> Jsont.Object.opt_mem "name-suffix" Jsont.string ~enc:(fun p -> p.name_suffix) 109 - |> Jsont.Object.opt_mem "alias" Jsont.string ~enc:(fun p -> p.alias) 110 - |> Jsont.Object.opt_mem "affiliation" Jsont.string ~enc:(fun p -> p.affiliation) 64 + family_names 65 + given_names 66 + name_particle 67 + name_suffix 68 + alias 69 + affiliation 70 + address 71 + city 72 + region 73 + post_code 74 + country 75 + email 76 + tel 77 + fax 78 + website 79 + orcid 80 + -> 81 + let address = 82 + Cff_address.Address.of_options ~address ~city ~region ~post_code 83 + ~country 84 + in 85 + let contact = 86 + Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid 87 + in 88 + { 89 + family_names; 90 + given_names; 91 + name_particle; 92 + name_suffix; 93 + alias; 94 + affiliation; 95 + address; 96 + contact; 97 + }) 98 + |> Json.Codec.Object.opt_member "family-names" Json.Codec.string 99 + ~enc:(fun p -> p.family_names) 100 + |> Json.Codec.Object.opt_member "given-names" Json.Codec.string 101 + ~enc:(fun p -> p.given_names) 102 + |> Json.Codec.Object.opt_member "name-particle" Json.Codec.string 103 + ~enc:(fun p -> p.name_particle) 104 + |> Json.Codec.Object.opt_member "name-suffix" Json.Codec.string 105 + ~enc:(fun p -> p.name_suffix) 106 + |> Json.Codec.Object.opt_member "alias" Json.Codec.string ~enc:(fun p -> 107 + p.alias) 108 + |> Json.Codec.Object.opt_member "affiliation" Json.Codec.string 109 + ~enc:(fun p -> p.affiliation) 111 110 |> Cff_address.Address.jsont_fields ~get:(fun p -> p.address) 112 111 |> Cff_address.Contact.jsont_fields ~get:(fun p -> p.contact) 113 - |> Jsont.Object.skip_unknown 114 - |> Jsont.Object.finish 115 - ;; 112 + |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal 116 113 end 117 114 118 115 (** An entity (organization, team, conference, etc.). *) 119 116 module Entity = struct 120 - type t = 121 - { name : string 122 - ; alias : string option 123 - ; address : Cff_address.Address.t 124 - ; contact : Cff_address.Contact.t 125 - ; date_start : Cff_date.t option 126 - ; date_end : Cff_date.t option 127 - ; location : string option 128 - } 117 + type t = { 118 + name : string; 119 + alias : string option; 120 + address : Cff_address.Address.t; 121 + contact : Cff_address.Contact.t; 122 + date_start : Cff_date.t option; 123 + date_end : Cff_date.t option; 124 + location : string option; 125 + } 129 126 130 - let make 131 - ~name 132 - ?alias 133 - ?(address = Cff_address.Address.empty) 134 - ?(contact = Cff_address.Contact.empty) 135 - ?date_start 136 - ?date_end 137 - ?location 138 - () 139 - = 127 + let make ~name ?alias ?(address = Cff_address.Address.empty) 128 + ?(contact = Cff_address.Contact.empty) ?date_start ?date_end ?location () 129 + = 140 130 { name; alias; address; contact; date_start; date_end; location } 141 - ;; 142 131 143 132 let name t = t.name 144 133 let alias t = t.alias ··· 154 143 let pp ppf t = 155 144 Format.pp_print_string ppf t.name; 156 145 Option.iter (Format.fprintf ppf " (%s)") t.alias 157 - ;; 158 146 159 147 let jsont = 160 - Jsont.Object.map 161 - ~kind:"Entity" 148 + Json.Codec.Object.map ~kind:"Entity" 162 149 (fun 163 - name 164 - alias 165 - address 166 - city 167 - region 168 - post_code 169 - country 170 - email 171 - tel 172 - fax 173 - website 174 - orcid 175 - date_start 176 - date_end 177 - location 178 - -> 179 - let address = 180 - Cff_address.Address.of_options ~address ~city ~region ~post_code ~country 181 - in 182 - let contact = Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid in 183 - { name; alias; address; contact; date_start; date_end; location }) 184 - |> Jsont.Object.mem "name" Jsont.string ~enc:(fun e -> e.name) 185 - |> Jsont.Object.opt_mem "alias" Jsont.string ~enc:(fun e -> e.alias) 150 + name 151 + alias 152 + address 153 + city 154 + region 155 + post_code 156 + country 157 + email 158 + tel 159 + fax 160 + website 161 + orcid 162 + date_start 163 + date_end 164 + location 165 + -> 166 + let address = 167 + Cff_address.Address.of_options ~address ~city ~region ~post_code 168 + ~country 169 + in 170 + let contact = 171 + Cff_address.Contact.of_options ~email ~tel ~fax ~website ~orcid 172 + in 173 + { name; alias; address; contact; date_start; date_end; location }) 174 + |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun e -> e.name) 175 + |> Json.Codec.Object.opt_member "alias" Json.Codec.string ~enc:(fun e -> 176 + e.alias) 186 177 |> Cff_address.Address.jsont_fields ~get:(fun e -> e.address) 187 178 |> Cff_address.Contact.jsont_fields ~get:(fun e -> e.contact) 188 - |> Jsont.Object.opt_mem "date-start" Cff_date.jsont ~enc:(fun e -> e.date_start) 189 - |> Jsont.Object.opt_mem "date-end" Cff_date.jsont ~enc:(fun e -> e.date_end) 190 - |> Jsont.Object.opt_mem "location" Jsont.string ~enc:(fun e -> e.location) 191 - |> Jsont.Object.skip_unknown 192 - |> Jsont.Object.finish 193 - ;; 179 + |> Json.Codec.Object.opt_member "date-start" Cff_date.jsont ~enc:(fun e -> 180 + e.date_start) 181 + |> Json.Codec.Object.opt_member "date-end" Cff_date.jsont ~enc:(fun e -> 182 + e.date_end) 183 + |> Json.Codec.Object.opt_member "location" Json.Codec.string ~enc:(fun e -> 184 + e.location) 185 + |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal 194 186 end 195 187 188 + type t = [ `Person of Person.t | `Entity of Entity.t ] 196 189 (** An author can be either a Person or an Entity. *) 197 - type t = 198 - [ `Person of Person.t 199 - | `Entity of Entity.t 200 - ] 201 190 202 - let person 203 - ?family_names 204 - ?given_names 205 - ?name_particle 206 - ?name_suffix 207 - ?alias 208 - ?affiliation 209 - ?address 210 - ?contact 211 - () 212 - = 191 + let person ?family_names ?given_names ?name_particle ?name_suffix ?alias 192 + ?affiliation ?address ?contact () = 213 193 `Person 214 - (Person.make 215 - ?family_names 216 - ?given_names 217 - ?name_particle 218 - ?name_suffix 219 - ?alias 220 - ?affiliation 221 - ?address 222 - ?contact 223 - ()) 224 - ;; 194 + (Person.make ?family_names ?given_names ?name_particle ?name_suffix ?alias 195 + ?affiliation ?address ?contact ()) 225 196 226 197 let entity ~name ?alias ?address ?contact ?date_start ?date_end ?location () = 227 - `Entity (Entity.make ~name ?alias ?address ?contact ?date_start ?date_end ?location ()) 228 - ;; 198 + `Entity 199 + (Entity.make ~name ?alias ?address ?contact ?date_start ?date_end ?location 200 + ()) 229 201 230 202 let name = function 231 203 | `Person p -> Person.full_name p 232 204 | `Entity e -> Entity.name e 233 - ;; 234 205 235 - let orcid = function 236 - | `Person p -> Person.orcid p 237 - | `Entity e -> Entity.orcid e 238 - ;; 239 - 240 - let email = function 241 - | `Person p -> Person.email p 242 - | `Entity e -> Entity.email e 243 - ;; 206 + let orcid = function `Person p -> Person.orcid p | `Entity e -> Entity.orcid e 207 + let email = function `Person p -> Person.email p | `Entity e -> Entity.email e 244 208 245 209 let pp ppf = function 246 210 | `Person p -> Person.pp ppf p 247 211 | `Entity e -> Entity.pp ppf e 248 - ;; 249 212 250 - (* Jsont codec that discriminates based on "name" field presence. 213 + (* JSON codec that discriminates based on "name" field presence. 251 214 If "name" is present -> Entity, otherwise -> Person *) 252 215 let jsont = 253 216 (* Check if json object has "name" member *) 254 217 let has_name_member = function 255 - | Jsont.Object (members, _) -> Option.is_some (Jsont.Json.find_mem "name" members) 218 + | Json.Object (members, _) -> 219 + Option.is_some (Json.Value.member_key "name" members) 256 220 | _ -> false 257 221 in 258 222 let dec_json j = 259 - if has_name_member j 260 - then ( 261 - match Jsont.Json.decode' Entity.jsont j with 223 + if has_name_member j then 224 + match Json.Codec.decode Entity.jsont j with 262 225 | Ok e -> `Entity e 263 226 | Error err -> 264 - Jsont.Error.msgf Jsont.Meta.none "Invalid entity: %s" (Jsont.Error.to_string err)) 265 - else ( 266 - match Jsont.Json.decode' Person.jsont j with 227 + Json.Error.failf Json.Error.Meta.none "Invalid entity: %s" 228 + (Json.Error.to_string err) 229 + else 230 + match Json.Codec.decode Person.jsont j with 267 231 | Ok p -> `Person p 268 232 | Error err -> 269 - Jsont.Error.msgf Jsont.Meta.none "Invalid person: %s" (Jsont.Error.to_string err)) 233 + Json.Error.failf Json.Error.Meta.none "Invalid person: %s" 234 + (Json.Error.to_string err) 270 235 in 271 236 let enc_author = function 272 - | `Person p -> 273 - (match Jsont.Json.encode' Person.jsont p with 274 - | Ok j -> j 275 - | Error _ -> assert false) 276 - | `Entity e -> 277 - (match Jsont.Json.encode' Entity.jsont e with 278 - | Ok j -> j 279 - | Error _ -> assert false) 237 + | `Person p -> Json.Codec.encode Person.jsont p 238 + | `Entity e -> Json.Codec.encode Entity.jsont e 280 239 in 281 - Jsont.json |> Jsont.map ~dec:dec_json ~enc:enc_author 282 - ;; 240 + Json.Codec.Value.t |> Json.Codec.map ~dec:dec_json ~enc:enc_author
+100 -109
lib/cff_author.mli
··· 7 7 8 8 CFF distinguishes between two types of authors: 9 9 10 - - {b Persons}: Individual humans identified by name components 11 - (family names, given names, etc.) 12 - - {b Entities}: Organizations, institutions, teams, projects, or 13 - conferences identified by a single [name] field 10 + - {b Persons}: Individual humans identified by name components (family 11 + names, given names, etc.) 12 + - {b Entities}: Organizations, institutions, teams, projects, or conferences 13 + identified by a single [name] field 14 14 15 - When parsing YAML, the library discriminates based on the presence 16 - of a [name] field: if present, the entry is an entity; otherwise, 17 - it's a person. 15 + When parsing YAML, the library discriminates based on the presence of a 16 + [name] field: if present, the entry is an entity; otherwise, it's a person. 18 17 19 18 {1 Quick Example} 20 19 21 20 {[ 22 - (* Create a person author *) 23 - let jane = Cff.Author.person 24 - ~family_names:"Smith" 25 - ~given_names:"Jane" 26 - ~affiliation:"MIT" 27 - () 21 + (* Create a person author *) 22 + let jane = 23 + Cff.Author.person ~family_names:"Smith" ~given_names:"Jane" 24 + ~affiliation:"MIT" () 28 25 29 - (* Create an entity author *) 30 - let mozilla = Cff.Author.entity 31 - ~name:"Mozilla Foundation" 32 - () 26 + (* Create an entity author *) 27 + let mozilla = Cff.Author.entity ~name:"Mozilla Foundation" () 33 28 34 - (* Pattern match on authors *) 35 - let show_author = function 36 - | `Person p -> Cff.Author.Person.full_name p 37 - | `Entity e -> Cff.Author.Entity.name e 29 + (* Pattern match on authors *) 30 + let show_author = function 31 + | `Person p -> Cff.Author.Person.full_name p 32 + | `Entity e -> Cff.Author.Entity.name e 38 33 ]} *) 39 34 40 35 (** {1 Person} ··· 47 42 - Optional physical address 48 43 - Optional contact information (email, ORCID, website) *) 49 44 module Person : sig 50 - (** A person record. *) 51 45 type t 46 + (** A person record. *) 52 47 48 + val make : 49 + ?family_names:string -> 50 + ?given_names:string -> 51 + ?name_particle:string -> 52 + ?name_suffix:string -> 53 + ?alias:string -> 54 + ?affiliation:string -> 55 + ?address:Cff_address.Address.t -> 56 + ?contact:Cff_address.Contact.t -> 57 + unit -> 58 + t 53 59 (** Create a person with optional fields. 54 60 55 61 At minimum, provide [family_names] or [given_names]. ··· 62 68 @param affiliation Institution or organization name 63 69 @param address Physical address 64 70 @param contact Contact information (email, ORCID, website, etc.) *) 65 - val make 66 - : ?family_names:string 67 - -> ?given_names:string 68 - -> ?name_particle:string 69 - -> ?name_suffix:string 70 - -> ?alias:string 71 - -> ?affiliation:string 72 - -> ?address:Cff_address.Address.t 73 - -> ?contact:Cff_address.Contact.t 74 - -> unit 75 - -> t 76 71 77 72 (** {2 Name Fields} *) 78 73 79 - (** The person's family name (surname, last name). *) 80 74 val family_names : t -> string option 75 + (** The person's family name (surname, last name). *) 81 76 82 - (** The person's given name(s) (first name, forenames). *) 83 77 val given_names : t -> string option 78 + (** The person's given name(s) (first name, forenames). *) 84 79 85 - (** Name connector appearing before family name. 86 - Examples: ["von"] in "Ludwig von Beethoven". *) 87 80 val name_particle : t -> string option 81 + (** Name connector appearing before family name. Examples: ["von"] in "Ludwig 82 + von Beethoven". *) 88 83 89 - (** Generational or honorary suffix. 90 - Examples: ["Jr."], ["Sr."], ["III"]. *) 91 84 val name_suffix : t -> string option 85 + (** Generational or honorary suffix. Examples: ["Jr."], ["Sr."], ["III"]. *) 92 86 87 + val alias : t -> string option 93 88 (** Nickname, pseudonym, or alternative name. *) 94 - val alias : t -> string option 95 89 96 - (** Format name as "Given Particle Family, Suffix". 97 - Examples: ["Jane Smith"], ["Guido van Rossum"]. *) 98 90 val full_name : t -> string 91 + (** Format name as "Given Particle Family, Suffix". Examples: ["Jane Smith"], 92 + ["Guido van Rossum"]. *) 99 93 100 94 (** {2 Affiliation and Location} *) 101 95 102 - (** The person's institutional affiliation. *) 103 96 val affiliation : t -> string option 97 + (** The person's institutional affiliation. *) 104 98 105 - (** Physical address information. *) 106 99 val address : t -> Cff_address.Address.t 100 + (** Physical address information. *) 107 101 108 102 (** {2 Contact Information} *) 109 103 104 + val contact : t -> Cff_address.Contact.t 110 105 (** Full contact information record. *) 111 - val contact : t -> Cff_address.Contact.t 112 106 107 + val email : t -> string option 113 108 (** The person's email address. *) 114 - val email : t -> string option 115 109 116 - (** The person's ORCID identifier URL. *) 117 110 val orcid : t -> string option 111 + (** The person's ORCID identifier URL. *) 118 112 119 - (** The person's website URL. *) 120 113 val website : t -> string option 114 + (** The person's website URL. *) 121 115 122 116 (** {2 Formatting and Codec} *) 123 117 124 - (** Pretty-print as "Full Name (affiliation)". *) 125 118 val pp : Format.formatter -> t -> unit 119 + (** Pretty-print as "Full Name (affiliation)". *) 126 120 121 + val jsont : t Json.Codec.t 127 122 (** JSON/YAML codec for person records. *) 128 - val jsont : t Jsont.t 129 123 end 130 124 131 125 (** {1 Entity} ··· 138 132 - Open source projects (["The Rust Project"]) 139 133 - Academic conferences (["ICSE 2024"]) with dates 140 134 141 - Entities are distinguished from persons in YAML by the presence 142 - of a required [name] field. *) 135 + Entities are distinguished from persons in YAML by the presence of a 136 + required [name] field. *) 143 137 module Entity : sig 138 + type t 144 139 (** An entity record. *) 145 - type t 146 140 141 + val make : 142 + name:string -> 143 + ?alias:string -> 144 + ?address:Cff_address.Address.t -> 145 + ?contact:Cff_address.Contact.t -> 146 + ?date_start:Cff_date.t -> 147 + ?date_end:Cff_date.t -> 148 + ?location:string -> 149 + unit -> 150 + t 147 151 (** Create an entity. 148 152 149 153 @param name The entity's official name (required) ··· 153 157 @param date_start Event start date (for conferences) 154 158 @param date_end Event end date (for conferences) 155 159 @param location Event location description *) 156 - val make 157 - : name:string 158 - -> ?alias:string 159 - -> ?address:Cff_address.Address.t 160 - -> ?contact:Cff_address.Contact.t 161 - -> ?date_start:Cff_date.t 162 - -> ?date_end:Cff_date.t 163 - -> ?location:string 164 - -> unit 165 - -> t 166 160 167 161 (** {2 Core Fields} *) 168 162 169 - (** The entity's official name. *) 170 163 val name : t -> string 164 + (** The entity's official name. *) 171 165 172 - (** Short name, acronym, or alternative name. *) 173 166 val alias : t -> string option 167 + (** Short name, acronym, or alternative name. *) 174 168 175 169 (** {2 Location} *) 176 170 171 + val address : t -> Cff_address.Address.t 177 172 (** Physical address information. *) 178 - val address : t -> Cff_address.Address.t 179 173 174 + val location : t -> string option 180 175 (** Event location description (for conferences). *) 181 - val location : t -> string option 182 176 183 177 (** {2 Event Dates} *) 184 178 185 - (** The start date of the event (for conferences). *) 186 179 val date_start : t -> Cff_date.t option 180 + (** The start date of the event (for conferences). *) 187 181 188 - (** The end date of the event (for conferences). *) 189 182 val date_end : t -> Cff_date.t option 183 + (** The end date of the event (for conferences). *) 190 184 191 185 (** {2 Contact Information} *) 192 186 193 - (** Full contact information record. *) 194 187 val contact : t -> Cff_address.Contact.t 188 + (** Full contact information record. *) 195 189 190 + val email : t -> string option 196 191 (** The entity's contact email. *) 197 - val email : t -> string option 198 192 193 + val orcid : t -> string option 199 194 (** The entity's ORCID (organizations can have ORCIDs). *) 200 - val orcid : t -> string option 201 195 202 - (** The entity's official website URL. *) 203 196 val website : t -> string option 197 + (** The entity's official website URL. *) 204 198 205 199 (** {2 Formatting and Codec} *) 206 200 207 - (** Pretty-print as "Name (alias)". *) 208 201 val pp : Format.formatter -> t -> unit 202 + (** Pretty-print as "Name (alias)". *) 209 203 204 + val jsont : t Json.Codec.t 210 205 (** JSON/YAML codec for entity records. *) 211 - val jsont : t Jsont.t 212 206 end 213 207 214 208 (** {1 Author Type} 215 209 216 - The main author type is a polymorphic variant that can hold either 217 - a person or an entity. *) 210 + The main author type is a polymorphic variant that can hold either a person 211 + or an entity. *) 218 212 213 + type t = [ `Person of Person.t | `Entity of Entity.t ] 219 214 (** An author: either a person or an entity. *) 220 - type t = 221 - [ `Person of Person.t 222 - | `Entity of Entity.t 223 - ] 224 215 225 216 (** {2 Smart Constructors} *) 226 217 218 + val person : 219 + ?family_names:string -> 220 + ?given_names:string -> 221 + ?name_particle:string -> 222 + ?name_suffix:string -> 223 + ?alias:string -> 224 + ?affiliation:string -> 225 + ?address:Cff_address.Address.t -> 226 + ?contact:Cff_address.Contact.t -> 227 + unit -> 228 + t 227 229 (** Create a person author directly. 228 230 229 231 Equivalent to [`Person (Person.make ...)]. *) 230 - val person 231 - : ?family_names:string 232 - -> ?given_names:string 233 - -> ?name_particle:string 234 - -> ?name_suffix:string 235 - -> ?alias:string 236 - -> ?affiliation:string 237 - -> ?address:Cff_address.Address.t 238 - -> ?contact:Cff_address.Contact.t 239 - -> unit 240 - -> t 241 232 233 + val entity : 234 + name:string -> 235 + ?alias:string -> 236 + ?address:Cff_address.Address.t -> 237 + ?contact:Cff_address.Contact.t -> 238 + ?date_start:Cff_date.t -> 239 + ?date_end:Cff_date.t -> 240 + ?location:string -> 241 + unit -> 242 + t 242 243 (** Create an entity author directly. 243 244 244 245 Equivalent to [`Entity (Entity.make ...)]. *) 245 - val entity 246 - : name:string 247 - -> ?alias:string 248 - -> ?address:Cff_address.Address.t 249 - -> ?contact:Cff_address.Contact.t 250 - -> ?date_start:Cff_date.t 251 - -> ?date_end:Cff_date.t 252 - -> ?location:string 253 - -> unit 254 - -> t 255 246 256 247 (** {2 Common Accessors} *) 257 248 249 + val name : t -> string 258 250 (** Get the display name. 259 251 260 - For persons, returns the full formatted name. 261 - For entities, returns the entity name. *) 262 - val name : t -> string 252 + For persons, returns the full formatted name. For entities, returns the 253 + entity name. *) 263 254 255 + val orcid : t -> string option 264 256 (** Get the ORCID if present. Works for both persons and entities. *) 265 - val orcid : t -> string option 266 257 267 - (** Get the email if present. Works for both persons and entities. *) 268 258 val email : t -> string option 259 + (** Get the email if present. Works for both persons and entities. *) 269 260 270 261 (** {2 Formatting and Codec} *) 271 262 272 - (** Pretty-print the author. *) 273 263 val pp : Format.formatter -> t -> unit 264 + (** Pretty-print the author. *) 274 265 266 + val jsont : t Json.Codec.t 275 267 (** JSON/YAML codec that discriminates based on [name] field presence. 276 268 277 269 When decoding: 278 270 - If the object has a [name] field -> Entity 279 271 - Otherwise -> Person *) 280 - val jsont : t Jsont.t
+8 -11
lib/cff_country.ml
··· 13 13 try 14 14 let _ = ISO3166.alpha2_of_string s in 15 15 Ok s 16 - with 17 - | Invalid_argument _ -> Error (`Invalid_country s) 18 - ;; 16 + with Invalid_argument _ -> Error (`Invalid_country s) 19 17 20 18 let to_string t = t 21 19 22 20 let to_iso3166 t = 23 - try Some (ISO3166.alpha2_to_country (ISO3166.alpha2_of_string t)) with 24 - | Invalid_argument _ -> None 25 - ;; 21 + try Some (ISO3166.alpha2_to_country (ISO3166.alpha2_of_string t)) 22 + with Invalid_argument _ -> None 26 23 27 24 let name t = Option.map ISO3166.Country.name (to_iso3166 t) 28 25 let equal = String.equal 29 26 let compare = String.compare 30 27 let pp ppf t = Format.pp_print_string ppf t 31 28 32 - (* Jsont codec for country codes *) 29 + (* JSON codec for country codes *) 33 30 let jsont = 34 31 let dec s = 35 32 match of_string s with 36 33 | Ok c -> c 37 34 | Error (`Invalid_country s) -> 38 - Jsont.Error.msgf Jsont.Meta.none "Invalid ISO 3166-1 alpha-2 country code: %s" s 35 + Json.Error.failf Json.Error.Meta.none 36 + "Invalid ISO 3166-1 alpha-2 country code: %s" s 39 37 in 40 38 let enc t = to_string t in 41 - Jsont.string |> Jsont.map ~dec ~enc 42 - ;; 39 + Json.Codec.string |> Json.Codec.map ~dec ~enc 43 40 44 41 (* Lenient codec that accepts any string *) 45 - let jsont_lenient = Jsont.string 42 + let jsont_lenient = Json.Codec.string
+19 -19
lib/cff_country.mli
··· 5 5 6 6 (** ISO 3166-1 alpha-2 country codes for CFF. 7 7 8 - CFF uses {{:https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2} 9 - ISO 3166-1 alpha-2} two-letter country codes for the [country] 10 - field on persons and entities. 8 + CFF uses 9 + {{:https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2} ISO 3166-1 alpha-2} 10 + two-letter country codes for the [country] field on persons and entities. 11 11 12 12 {1 Format} 13 13 ··· 26 26 27 27 {1 Validation} 28 28 29 - This module validates country codes against the {!ISO3166} library, 30 - which maintains the official list of assigned codes. 29 + This module validates country codes against the {!ISO3166} library, which 30 + maintains the official list of assigned codes. 31 31 32 32 {1 Example} 33 33 ··· 39 39 country: DE 40 40 ]} *) 41 41 42 - (** An ISO 3166-1 alpha-2 country code (two uppercase letters). *) 43 42 type t = string 43 + (** An ISO 3166-1 alpha-2 country code (two uppercase letters). *) 44 44 45 + val of_string : string -> (t, [> `Invalid_country of string ]) result 45 46 (** Parse and validate a country code. 46 47 47 - Case-insensitive: ["us"], ["US"], and ["Us"] all produce ["US"]. 48 - Returns [Error (`Invalid_country s)] for unknown codes. *) 49 - val of_string : string -> (t, [> `Invalid_country of string ]) result 48 + Case-insensitive: ["us"], ["US"], and ["Us"] all produce ["US"]. Returns 49 + [Error (`Invalid_country s)] for unknown codes. *) 50 50 51 - (** Return the uppercase country code. *) 52 51 val to_string : t -> string 52 + (** Return the uppercase country code. *) 53 53 54 + val to_iso3166 : t -> ISO3166.Country.t option 54 55 (** Look up the full country record from {!ISO3166}. 55 56 56 57 Returns [None] if the code is not in the ISO 3166-1 list. *) 57 - val to_iso3166 : t -> ISO3166.Country.t option 58 58 59 + val name : t -> string option 59 60 (** Get the country name if the code is valid. 60 61 61 62 Examples: 62 63 - [name "US" = Some "United States of America"] 63 64 - [name "GB" = Some "United Kingdom of Great Britain and Northern Ireland"] 64 65 - [name "XX" = None] *) 65 - val name : t -> string option 66 66 67 - (** Country code equality (case-sensitive after normalization). *) 68 67 val equal : t -> t -> bool 68 + (** Country code equality (case-sensitive after normalization). *) 69 69 70 + val compare : t -> t -> int 70 71 (** Alphabetical comparison of country codes. *) 71 - val compare : t -> t -> int 72 72 73 + val pp : Format.formatter -> t -> unit 73 74 (** Pretty-print the country code. *) 74 - val pp : Format.formatter -> t -> unit 75 75 76 + val jsont : t Json.Codec.t 76 77 (** JSON/YAML codec that validates country codes. 77 78 78 79 Returns an error for invalid ISO 3166-1 alpha-2 codes. *) 79 - val jsont : t Jsont.t 80 80 81 + val jsont_lenient : t Json.Codec.t 81 82 (** JSON/YAML codec that accepts any string. 82 83 83 - Use this when parsing CFF files that may contain non-standard 84 - country codes. *) 85 - val jsont_lenient : t Jsont.t 84 + Use this when parsing CFF files that may contain non-standard country codes. 85 + *)
+16 -14
lib/cff_date.ml
··· 10 10 let of_string s = 11 11 (* CFF dates are YYYY-MM-DD format *) 12 12 match String.split_on_char '-' s with 13 - | [ y; m; d ] -> 14 - (match int_of_string_opt y, int_of_string_opt m, int_of_string_opt d with 15 - | Some year, Some month, Some day -> 16 - (* Validate the date components *) 17 - if year >= 0 && year <= 9999 && month >= 1 && month <= 12 && day >= 1 && day <= 31 18 - then Ok (year, month, day) 19 - else Error (`Invalid_date s) 20 - | _ -> Error (`Invalid_date s)) 13 + | [ y; m; d ] -> ( 14 + match (int_of_string_opt y, int_of_string_opt m, int_of_string_opt d) with 15 + | Some year, Some month, Some day -> 16 + (* Validate the date components *) 17 + if 18 + year >= 0 && year <= 9999 && month >= 1 && month <= 12 && day >= 1 19 + && day <= 31 20 + then Ok (year, month, day) 21 + else Error (`Invalid_date s) 22 + | _ -> Error (`Invalid_date s)) 21 23 | _ -> Error (`Invalid_date s) 22 - ;; 23 24 24 - let to_string (year, month, day) = Printf.sprintf "%04d-%02d-%02d" year month day 25 + let to_string (year, month, day) = 26 + Printf.sprintf "%04d-%02d-%02d" year month day 27 + 25 28 let year (y, _, _) = y 26 29 let month (_, m, _) = m 27 30 let day (_, _, d) = d ··· 29 32 let compare = Stdlib.compare 30 33 let pp ppf date = Format.pp_print_string ppf (to_string date) 31 34 32 - (* Jsont codec for dates *) 35 + (* JSON codec for dates *) 33 36 let jsont = 34 37 let dec s = 35 38 match of_string s with 36 39 | Ok d -> d 37 40 | Error (`Invalid_date s) -> 38 - Jsont.Error.msgf Jsont.Meta.none "Invalid date format: %s" s 41 + Json.Error.failf Json.Error.Meta.none "Invalid date format: %s" s 39 42 in 40 43 let enc date = to_string date in 41 - Jsont.string |> Jsont.map ~dec ~enc 42 - ;; 44 + Json.Codec.string |> Json.Codec.map ~dec ~enc
+17 -17
lib/cff_date.mli
··· 5 5 6 6 (** Date handling for CFF. 7 7 8 - CFF uses ISO 8601 date format ([YYYY-MM-DD]) for all date fields. 9 - This module wraps {!Ptime.date} for date representation and provides 10 - parsing and formatting functions. 8 + CFF uses ISO 8601 date format ([YYYY-MM-DD]) for all date fields. This 9 + module wraps {!Ptime.date} for date representation and provides parsing and 10 + formatting functions. 11 11 12 12 {1 Date Fields in CFF} 13 13 ··· 42 42 43 43 {1 Year-Only Dates} 44 44 45 - For historical works or when only the year is known, use the [year] 46 - field (an integer) instead of a full date. *) 45 + For historical works or when only the year is known, use the [year] field 46 + (an integer) instead of a full date. *) 47 47 48 + type t = Ptime.date 48 49 (** A date as [(year, month, day)] tuple. 49 50 50 51 The tuple contains: 51 52 - [year]: Four-digit year (e.g., [2024]) 52 53 - [month]: Month number (1-12) 53 54 - [day]: Day of month (1-31) *) 54 - type t = Ptime.date 55 55 56 + val of_string : string -> (t, [> `Invalid_date of string ]) result 56 57 (** Parse a date from [YYYY-MM-DD] format. 57 58 58 59 Returns [Error (`Invalid_date s)] if the string is not a valid date. 59 60 Validates that the date is a real calendar date (e.g., rejects Feb 30). *) 60 - val of_string : string -> (t, [> `Invalid_date of string ]) result 61 61 62 + val to_string : t -> string 62 63 (** Format a date as [YYYY-MM-DD]. *) 63 - val to_string : t -> string 64 64 65 - (** Extract the year component. *) 66 65 val year : t -> int 66 + (** Extract the year component. *) 67 67 68 + val month : t -> int 68 69 (** Extract the month component (1-12). *) 69 - val month : t -> int 70 70 71 - (** Extract the day component (1-31). *) 72 71 val day : t -> int 72 + (** Extract the day component (1-31). *) 73 73 74 - (** Date equality. *) 75 74 val equal : t -> t -> bool 75 + (** Date equality. *) 76 76 77 - (** Date comparison (chronological order). *) 78 77 val compare : t -> t -> int 78 + (** Date comparison (chronological order). *) 79 79 80 - (** Pretty-print a date in [YYYY-MM-DD] format. *) 81 80 val pp : Format.formatter -> t -> unit 81 + (** Pretty-print a date in [YYYY-MM-DD] format. *) 82 82 83 + val jsont : t Json.Codec.t 83 84 (** JSON/YAML codec for dates. 84 85 85 - Parses strings in [YYYY-MM-DD] format and serializes back to the 86 - same format. *) 87 - val jsont : t Jsont.t 86 + Parses strings in [YYYY-MM-DD] format and serializes back to the same 87 + format. *)
+198 -217
lib/cff_enums.ml
··· 22 22 let pp ppf t = Format.pp_print_string ppf (to_string t) 23 23 24 24 let jsont = 25 - Jsont.string 26 - |> Jsont.map 25 + Json.Codec.string 26 + |> Json.Codec.map 27 27 ~dec:(fun s -> 28 28 match of_string s with 29 29 | Some t -> t 30 - | None -> Jsont.Error.msgf Jsont.Meta.none "Invalid %s: %s" type_name s) 30 + | None -> 31 + Json.Error.failf Json.Error.Meta.none "Invalid %s: %s" type_name 32 + s) 31 33 ~enc:to_string 32 - ;; 33 34 end 34 35 35 36 module Identifier_type = Make_enum (struct 36 - type t = 37 - [ `Doi 38 - | `Url 39 - | `Swh 40 - | `Other 41 - ] 37 + type t = [ `Doi | `Url | `Swh | `Other ] 42 38 43 - let type_name = "identifier type" 39 + let type_name = "identifier type" 44 40 45 - let of_string = function 46 - | "doi" -> Some `Doi 47 - | "url" -> Some `Url 48 - | "swh" -> Some `Swh 49 - | "other" -> Some `Other 50 - | _ -> None 51 - ;; 41 + let of_string = function 42 + | "doi" -> Some `Doi 43 + | "url" -> Some `Url 44 + | "swh" -> Some `Swh 45 + | "other" -> Some `Other 46 + | _ -> None 52 47 53 - let to_string = function 54 - | `Doi -> "doi" 55 - | `Url -> "url" 56 - | `Swh -> "swh" 57 - | `Other -> "other" 58 - ;; 59 - end) 48 + let to_string = function 49 + | `Doi -> "doi" 50 + | `Url -> "url" 51 + | `Swh -> "swh" 52 + | `Other -> "other" 53 + end) 60 54 61 55 module Reference_type = Make_enum (struct 62 - type t = 63 - [ `Art 64 - | `Article 65 - | `Audiovisual 66 - | `Bill 67 - | `Blog 68 - | `Book 69 - | `Catalogue 70 - | `Conference 71 - | `Conference_paper 72 - | `Data 73 - | `Database 74 - | `Dictionary 75 - | `Edited_work 76 - | `Encyclopedia 77 - | `Film_broadcast 78 - | `Generic 79 - | `Government_document 80 - | `Grant 81 - | `Hearing 82 - | `Historical_work 83 - | `Legal_case 84 - | `Legal_rule 85 - | `Magazine_article 86 - | `Manual 87 - | `Map 88 - | `Multimedia 89 - | `Music 90 - | `Newspaper_article 91 - | `Pamphlet 92 - | `Patent 93 - | `Personal_communication 94 - | `Proceedings 95 - | `Report 96 - | `Serial 97 - | `Slides 98 - | `Software 99 - | `Software_code 100 - | `Software_container 101 - | `Software_executable 102 - | `Software_virtual_machine 103 - | `Sound_recording 104 - | `Standard 105 - | `Statute 106 - | `Thesis 107 - | `Unpublished 108 - | `Video 109 - | `Website 110 - ] 56 + type t = 57 + [ `Art 58 + | `Article 59 + | `Audiovisual 60 + | `Bill 61 + | `Blog 62 + | `Book 63 + | `Catalogue 64 + | `Conference 65 + | `Conference_paper 66 + | `Data 67 + | `Database 68 + | `Dictionary 69 + | `Edited_work 70 + | `Encyclopedia 71 + | `Film_broadcast 72 + | `Generic 73 + | `Government_document 74 + | `Grant 75 + | `Hearing 76 + | `Historical_work 77 + | `Legal_case 78 + | `Legal_rule 79 + | `Magazine_article 80 + | `Manual 81 + | `Map 82 + | `Multimedia 83 + | `Music 84 + | `Newspaper_article 85 + | `Pamphlet 86 + | `Patent 87 + | `Personal_communication 88 + | `Proceedings 89 + | `Report 90 + | `Serial 91 + | `Slides 92 + | `Software 93 + | `Software_code 94 + | `Software_container 95 + | `Software_executable 96 + | `Software_virtual_machine 97 + | `Sound_recording 98 + | `Standard 99 + | `Statute 100 + | `Thesis 101 + | `Unpublished 102 + | `Video 103 + | `Website ] 111 104 112 - let type_name = "reference type" 105 + let type_name = "reference type" 113 106 114 - let of_string = function 115 - | "art" -> Some `Art 116 - | "article" -> Some `Article 117 - | "audiovisual" -> Some `Audiovisual 118 - | "bill" -> Some `Bill 119 - | "blog" -> Some `Blog 120 - | "book" -> Some `Book 121 - | "catalogue" -> Some `Catalogue 122 - | "conference" -> Some `Conference 123 - | "conference-paper" -> Some `Conference_paper 124 - | "data" -> Some `Data 125 - | "database" -> Some `Database 126 - | "dictionary" -> Some `Dictionary 127 - | "edited-work" -> Some `Edited_work 128 - | "encyclopedia" -> Some `Encyclopedia 129 - | "film-broadcast" -> Some `Film_broadcast 130 - | "generic" -> Some `Generic 131 - | "government-document" -> Some `Government_document 132 - | "grant" -> Some `Grant 133 - | "hearing" -> Some `Hearing 134 - | "historical-work" -> Some `Historical_work 135 - | "legal-case" -> Some `Legal_case 136 - | "legal-rule" -> Some `Legal_rule 137 - | "magazine-article" -> Some `Magazine_article 138 - | "manual" -> Some `Manual 139 - | "map" -> Some `Map 140 - | "multimedia" -> Some `Multimedia 141 - | "music" -> Some `Music 142 - | "newspaper-article" -> Some `Newspaper_article 143 - | "pamphlet" -> Some `Pamphlet 144 - | "patent" -> Some `Patent 145 - | "personal-communication" -> Some `Personal_communication 146 - | "proceedings" -> Some `Proceedings 147 - | "report" -> Some `Report 148 - | "serial" -> Some `Serial 149 - | "slides" -> Some `Slides 150 - | "software" -> Some `Software 151 - | "software-code" -> Some `Software_code 152 - | "software-container" -> Some `Software_container 153 - | "software-executable" -> Some `Software_executable 154 - | "software-virtual-machine" -> Some `Software_virtual_machine 155 - | "sound-recording" -> Some `Sound_recording 156 - | "standard" -> Some `Standard 157 - | "statute" -> Some `Statute 158 - | "thesis" -> Some `Thesis 159 - | "unpublished" -> Some `Unpublished 160 - | "video" -> Some `Video 161 - | "website" -> Some `Website 162 - | _ -> None 163 - ;; 107 + let of_string = function 108 + | "art" -> Some `Art 109 + | "article" -> Some `Article 110 + | "audiovisual" -> Some `Audiovisual 111 + | "bill" -> Some `Bill 112 + | "blog" -> Some `Blog 113 + | "book" -> Some `Book 114 + | "catalogue" -> Some `Catalogue 115 + | "conference" -> Some `Conference 116 + | "conference-paper" -> Some `Conference_paper 117 + | "data" -> Some `Data 118 + | "database" -> Some `Database 119 + | "dictionary" -> Some `Dictionary 120 + | "edited-work" -> Some `Edited_work 121 + | "encyclopedia" -> Some `Encyclopedia 122 + | "film-broadcast" -> Some `Film_broadcast 123 + | "generic" -> Some `Generic 124 + | "government-document" -> Some `Government_document 125 + | "grant" -> Some `Grant 126 + | "hearing" -> Some `Hearing 127 + | "historical-work" -> Some `Historical_work 128 + | "legal-case" -> Some `Legal_case 129 + | "legal-rule" -> Some `Legal_rule 130 + | "magazine-article" -> Some `Magazine_article 131 + | "manual" -> Some `Manual 132 + | "map" -> Some `Map 133 + | "multimedia" -> Some `Multimedia 134 + | "music" -> Some `Music 135 + | "newspaper-article" -> Some `Newspaper_article 136 + | "pamphlet" -> Some `Pamphlet 137 + | "patent" -> Some `Patent 138 + | "personal-communication" -> Some `Personal_communication 139 + | "proceedings" -> Some `Proceedings 140 + | "report" -> Some `Report 141 + | "serial" -> Some `Serial 142 + | "slides" -> Some `Slides 143 + | "software" -> Some `Software 144 + | "software-code" -> Some `Software_code 145 + | "software-container" -> Some `Software_container 146 + | "software-executable" -> Some `Software_executable 147 + | "software-virtual-machine" -> Some `Software_virtual_machine 148 + | "sound-recording" -> Some `Sound_recording 149 + | "standard" -> Some `Standard 150 + | "statute" -> Some `Statute 151 + | "thesis" -> Some `Thesis 152 + | "unpublished" -> Some `Unpublished 153 + | "video" -> Some `Video 154 + | "website" -> Some `Website 155 + | _ -> None 164 156 165 - let to_string = function 166 - | `Art -> "art" 167 - | `Article -> "article" 168 - | `Audiovisual -> "audiovisual" 169 - | `Bill -> "bill" 170 - | `Blog -> "blog" 171 - | `Book -> "book" 172 - | `Catalogue -> "catalogue" 173 - | `Conference -> "conference" 174 - | `Conference_paper -> "conference-paper" 175 - | `Data -> "data" 176 - | `Database -> "database" 177 - | `Dictionary -> "dictionary" 178 - | `Edited_work -> "edited-work" 179 - | `Encyclopedia -> "encyclopedia" 180 - | `Film_broadcast -> "film-broadcast" 181 - | `Generic -> "generic" 182 - | `Government_document -> "government-document" 183 - | `Grant -> "grant" 184 - | `Hearing -> "hearing" 185 - | `Historical_work -> "historical-work" 186 - | `Legal_case -> "legal-case" 187 - | `Legal_rule -> "legal-rule" 188 - | `Magazine_article -> "magazine-article" 189 - | `Manual -> "manual" 190 - | `Map -> "map" 191 - | `Multimedia -> "multimedia" 192 - | `Music -> "music" 193 - | `Newspaper_article -> "newspaper-article" 194 - | `Pamphlet -> "pamphlet" 195 - | `Patent -> "patent" 196 - | `Personal_communication -> "personal-communication" 197 - | `Proceedings -> "proceedings" 198 - | `Report -> "report" 199 - | `Serial -> "serial" 200 - | `Slides -> "slides" 201 - | `Software -> "software" 202 - | `Software_code -> "software-code" 203 - | `Software_container -> "software-container" 204 - | `Software_executable -> "software-executable" 205 - | `Software_virtual_machine -> "software-virtual-machine" 206 - | `Sound_recording -> "sound-recording" 207 - | `Standard -> "standard" 208 - | `Statute -> "statute" 209 - | `Thesis -> "thesis" 210 - | `Unpublished -> "unpublished" 211 - | `Video -> "video" 212 - | `Website -> "website" 213 - ;; 214 - end) 157 + let to_string = function 158 + | `Art -> "art" 159 + | `Article -> "article" 160 + | `Audiovisual -> "audiovisual" 161 + | `Bill -> "bill" 162 + | `Blog -> "blog" 163 + | `Book -> "book" 164 + | `Catalogue -> "catalogue" 165 + | `Conference -> "conference" 166 + | `Conference_paper -> "conference-paper" 167 + | `Data -> "data" 168 + | `Database -> "database" 169 + | `Dictionary -> "dictionary" 170 + | `Edited_work -> "edited-work" 171 + | `Encyclopedia -> "encyclopedia" 172 + | `Film_broadcast -> "film-broadcast" 173 + | `Generic -> "generic" 174 + | `Government_document -> "government-document" 175 + | `Grant -> "grant" 176 + | `Hearing -> "hearing" 177 + | `Historical_work -> "historical-work" 178 + | `Legal_case -> "legal-case" 179 + | `Legal_rule -> "legal-rule" 180 + | `Magazine_article -> "magazine-article" 181 + | `Manual -> "manual" 182 + | `Map -> "map" 183 + | `Multimedia -> "multimedia" 184 + | `Music -> "music" 185 + | `Newspaper_article -> "newspaper-article" 186 + | `Pamphlet -> "pamphlet" 187 + | `Patent -> "patent" 188 + | `Personal_communication -> "personal-communication" 189 + | `Proceedings -> "proceedings" 190 + | `Report -> "report" 191 + | `Serial -> "serial" 192 + | `Slides -> "slides" 193 + | `Software -> "software" 194 + | `Software_code -> "software-code" 195 + | `Software_container -> "software-container" 196 + | `Software_executable -> "software-executable" 197 + | `Software_virtual_machine -> "software-virtual-machine" 198 + | `Sound_recording -> "sound-recording" 199 + | `Standard -> "standard" 200 + | `Statute -> "statute" 201 + | `Thesis -> "thesis" 202 + | `Unpublished -> "unpublished" 203 + | `Video -> "video" 204 + | `Website -> "website" 205 + end) 215 206 216 207 module Status = Make_enum (struct 217 - type t = 218 - [ `Abstract 219 - | `Advance_online 220 - | `In_preparation 221 - | `In_press 222 - | `Preprint 223 - | `Submitted 224 - ] 208 + type t = 209 + [ `Abstract 210 + | `Advance_online 211 + | `In_preparation 212 + | `In_press 213 + | `Preprint 214 + | `Submitted ] 225 215 226 - let type_name = "status" 216 + let type_name = "status" 227 217 228 - let of_string = function 229 - | "abstract" -> Some `Abstract 230 - | "advance-online" -> Some `Advance_online 231 - | "in-preparation" -> Some `In_preparation 232 - | "in-press" -> Some `In_press 233 - | "preprint" -> Some `Preprint 234 - | "submitted" -> Some `Submitted 235 - | _ -> None 236 - ;; 218 + let of_string = function 219 + | "abstract" -> Some `Abstract 220 + | "advance-online" -> Some `Advance_online 221 + | "in-preparation" -> Some `In_preparation 222 + | "in-press" -> Some `In_press 223 + | "preprint" -> Some `Preprint 224 + | "submitted" -> Some `Submitted 225 + | _ -> None 237 226 238 - let to_string = function 239 - | `Abstract -> "abstract" 240 - | `Advance_online -> "advance-online" 241 - | `In_preparation -> "in-preparation" 242 - | `In_press -> "in-press" 243 - | `Preprint -> "preprint" 244 - | `Submitted -> "submitted" 245 - ;; 246 - end) 227 + let to_string = function 228 + | `Abstract -> "abstract" 229 + | `Advance_online -> "advance-online" 230 + | `In_preparation -> "in-preparation" 231 + | `In_press -> "in-press" 232 + | `Preprint -> "preprint" 233 + | `Submitted -> "submitted" 234 + end) 247 235 248 236 module Cff_type = Make_enum (struct 249 - type t = 250 - [ `Software 251 - | `Dataset 252 - ] 237 + type t = [ `Software | `Dataset ] 253 238 254 - let type_name = "CFF type" 239 + let type_name = "CFF type" 255 240 256 - let of_string = function 257 - | "software" -> Some `Software 258 - | "dataset" -> Some `Dataset 259 - | _ -> None 260 - ;; 241 + let of_string = function 242 + | "software" -> Some `Software 243 + | "dataset" -> Some `Dataset 244 + | _ -> None 261 245 262 - let to_string = function 263 - | `Software -> "software" 264 - | `Dataset -> "dataset" 265 - ;; 266 - end) 246 + let to_string = function `Software -> "software" | `Dataset -> "dataset" 247 + end)
+31 -40
lib/cff_enums.mli
··· 5 5 6 6 (** Enumeration types for CFF. 7 7 8 - CFF defines several enumerated types using fixed string values. 9 - This module represents them as polymorphic variants for type safety 10 - while providing bidirectional conversion to/from strings. 8 + CFF defines several enumerated types using fixed string values. This module 9 + represents them as polymorphic variants for type safety while providing 10 + bidirectional conversion to/from strings. 11 11 12 12 {1 Identifier Types} 13 13 ··· 33 33 34 34 - [`Doi] - Digital Object Identifier ({{:https://doi.org}doi.org}) 35 35 - [`Url] - Web URL 36 - - [`Swh] - Software Heritage identifier ({{:https://www.softwareheritage.org}softwareheritage.org}) 36 + - [`Swh] - Software Heritage identifier 37 + ({{:https://www.softwareheritage.org}softwareheritage.org}) 37 38 - [`Other] - Any other identifier type 38 39 39 40 {2 Examples} ··· 48 49 description: Software Heritage archive 49 50 ]} *) 50 51 module Identifier_type : sig 52 + type t = [ `Doi | `Url | `Swh | `Other ] 51 53 (** Identifier types. *) 52 - type t = 53 - [ `Doi 54 - | `Url 55 - | `Swh 56 - | `Other 57 - ] 58 54 59 - (** Parse from YAML string: ["doi"], ["url"], ["swh"], ["other"]. *) 60 55 val of_string : string -> t option 56 + (** Parse from YAML string: ["doi"], ["url"], ["swh"], ["other"]. *) 61 57 62 - (** Convert to YAML string representation. *) 63 58 val to_string : t -> string 59 + (** Convert to YAML string representation. *) 64 60 65 61 val equal : t -> t -> bool 66 62 val compare : t -> t -> int 67 63 val pp : Format.formatter -> t -> unit 68 64 65 + val jsont : t Json.Codec.t 69 66 (** JSON/YAML codec. *) 70 - val jsont : t Jsont.t 71 67 end 72 68 73 69 (** Reference type for bibliographic entries. 74 70 75 - CFF 1.2.0 supports 40+ reference types covering virtually all forms 76 - of citable content. The type determines which fields are relevant. 71 + CFF 1.2.0 supports 40+ reference types covering virtually all forms of 72 + citable content. The type determines which fields are relevant. 77 73 78 74 {2 Academic/Research} 79 75 ··· 143 139 - [`Standard] - Technical standard 144 140 - [`Unpublished] - Unpublished work *) 145 141 module Reference_type : sig 146 - (** All supported reference types. *) 147 142 type t = 148 143 [ `Art 149 144 | `Article ··· 191 186 | `Thesis 192 187 | `Unpublished 193 188 | `Video 194 - | `Website 195 - ] 189 + | `Website ] 190 + (** All supported reference types. *) 196 191 197 - (** Parse from YAML string. Hyphenated names like ["conference-paper"] 198 - map to underscored variants like [`Conference_paper]. *) 199 192 val of_string : string -> t option 193 + (** Parse from YAML string. Hyphenated names like ["conference-paper"] map to 194 + underscored variants like [`Conference_paper]. *) 200 195 201 - (** Convert to YAML string representation. 202 - Underscored variants like [`Conference_paper] become ["conference-paper"]. *) 203 196 val to_string : t -> string 197 + (** Convert to YAML string representation. Underscored variants like 198 + [`Conference_paper] become ["conference-paper"]. *) 204 199 205 200 val equal : t -> t -> bool 206 201 val compare : t -> t -> int 207 202 val pp : Format.formatter -> t -> unit 208 203 204 + val jsont : t Json.Codec.t 209 205 (** JSON/YAML codec. *) 210 - val jsont : t Jsont.t 211 206 end 212 207 213 208 (** Publication status for works in progress. 214 209 215 - The [status] field indicates the publication stage of a work that 216 - is not yet formally published: 210 + The [status] field indicates the publication stage of a work that is not yet 211 + formally published: 217 212 218 213 - [`Abstract] - Only an abstract is available 219 214 - [`Advance_online] - Published online ahead of print ··· 235 230 status: submitted 236 231 ]} *) 237 232 module Status : sig 238 - (** Publication status values. *) 239 233 type t = 240 234 [ `Abstract 241 235 | `Advance_online 242 236 | `In_preparation 243 237 | `In_press 244 238 | `Preprint 245 - | `Submitted 246 - ] 239 + | `Submitted ] 240 + (** Publication status values. *) 247 241 248 - (** Parse from YAML string: ["abstract"], ["advance-online"], etc. *) 249 242 val of_string : string -> t option 243 + (** Parse from YAML string: ["abstract"], ["advance-online"], etc. *) 250 244 251 - (** Convert to YAML string representation. *) 252 245 val to_string : t -> string 246 + (** Convert to YAML string representation. *) 253 247 254 248 val equal : t -> t -> bool 255 249 val compare : t -> t -> int 256 250 val pp : Format.formatter -> t -> unit 257 251 252 + val jsont : t Json.Codec.t 258 253 (** JSON/YAML codec. *) 259 - val jsont : t Jsont.t 260 254 end 261 255 262 256 (** CFF file type: software or dataset. 263 257 264 - The [type] field at the root level indicates whether the CFF file 265 - describes software or a dataset: 258 + The [type] field at the root level indicates whether the CFF file describes 259 + software or a dataset: 266 260 267 261 - [`Software] - Software project (default if omitted) 268 262 - [`Dataset] - Dataset or data package ··· 276 270 # ... 277 271 ]} *) 278 272 module Cff_type : sig 273 + type t = [ `Software | `Dataset ] 279 274 (** CFF file types. *) 280 - type t = 281 - [ `Software 282 - | `Dataset 283 - ] 284 275 285 - (** Parse from YAML string: ["software"] or ["dataset"]. *) 286 276 val of_string : string -> t option 277 + (** Parse from YAML string: ["software"] or ["dataset"]. *) 287 278 288 - (** Convert to YAML string representation. *) 289 279 val to_string : t -> string 280 + (** Convert to YAML string representation. *) 290 281 291 282 val equal : t -> t -> bool 292 283 val compare : t -> t -> int 293 284 val pp : Format.formatter -> t -> unit 294 285 286 + val jsont : t Json.Codec.t 295 287 (** JSON/YAML codec. *) 296 - val jsont : t Jsont.t 297 288 end
+17 -17
lib/cff_identifier.ml
··· 5 5 6 6 (** Identifier type for CFF. *) 7 7 8 - type t = 9 - { type_ : Cff_enums.Identifier_type.t 10 - ; value : string 11 - ; description : string option 12 - } 8 + type t = { 9 + type_ : Cff_enums.Identifier_type.t; 10 + value : string; 11 + description : string option; 12 + } 13 13 14 14 let make ~type_ ~value ?description () = { type_; value; description } 15 15 let type_ t = t.type_ ··· 17 17 let description t = t.description 18 18 19 19 let equal a b = 20 - Cff_enums.Identifier_type.equal a.type_ b.type_ && String.equal a.value b.value 21 - ;; 20 + Cff_enums.Identifier_type.equal a.type_ b.type_ 21 + && String.equal a.value b.value 22 22 23 23 let compare a b = 24 24 match Cff_enums.Identifier_type.compare a.type_ b.type_ with 25 25 | 0 -> String.compare a.value b.value 26 26 | n -> n 27 - ;; 28 27 29 - let pp ppf t = Format.fprintf ppf "%a: %s" Cff_enums.Identifier_type.pp t.type_ t.value 28 + let pp ppf t = 29 + Format.fprintf ppf "%a: %s" Cff_enums.Identifier_type.pp t.type_ t.value 30 30 31 31 let jsont = 32 - Jsont.Object.map ~kind:"Identifier" (fun type_ value description -> 33 - { type_; value; description }) 34 - |> Jsont.Object.mem "type" Cff_enums.Identifier_type.jsont ~enc:(fun i -> i.type_) 35 - |> Jsont.Object.mem "value" Jsont.string ~enc:(fun i -> i.value) 36 - |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun i -> i.description) 37 - |> Jsont.Object.skip_unknown 38 - |> Jsont.Object.finish 39 - ;; 32 + Json.Codec.Object.map ~kind:"Identifier" (fun type_ value description -> 33 + { type_; value; description }) 34 + |> Json.Codec.Object.member "type" Cff_enums.Identifier_type.jsont 35 + ~enc:(fun i -> i.type_) 36 + |> Json.Codec.Object.member "value" Json.Codec.string ~enc:(fun i -> i.value) 37 + |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun i -> 38 + i.description) 39 + |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal
+21 -23
lib/cff_identifier.mli
··· 5 5 6 6 (** Typed identifiers for CFF. 7 7 8 - The [identifiers] field in CFF allows listing multiple typed 9 - identifiers for a work. Each identifier has a type, value, and 10 - optional description. 8 + The [identifiers] field in CFF allows listing multiple typed identifiers for 9 + a work. Each identifier has a type, value, and optional description. 11 10 12 11 {1 Identifier Types} 13 12 14 13 CFF supports four identifier types: 15 14 16 - - {b DOI}: Digital Object Identifier 17 - ({{:https://doi.org}doi.org}) 15 + - {b DOI}: Digital Object Identifier ({{:https://doi.org}doi.org}) 18 16 - {b URL}: Web URL 19 17 - {b SWH}: Software Heritage identifier 20 18 ({{:https://www.softwareheritage.org}softwareheritage.org}) ··· 54 52 55 53 {1 Software Heritage} 56 54 57 - Software Heritage (SWH) provides persistent identifiers for source 58 - code. SWH identifiers follow the format: 55 + Software Heritage (SWH) provides persistent identifiers for source code. SWH 56 + identifiers follow the format: 59 57 60 58 [swh:1:<object_type>:<hash>] 61 59 ··· 66 64 - [rel]: Release 67 65 - [snp]: Snapshot *) 68 66 69 - (** An identifier with type, value, and optional description. *) 70 67 type t 68 + (** An identifier with type, value, and optional description. *) 71 69 70 + val make : 71 + type_:Cff_enums.Identifier_type.t -> 72 + value:string -> 73 + ?description:string -> 74 + unit -> 75 + t 72 76 (** Create an identifier. 73 77 74 78 @param type_ The identifier type ([`Doi], [`Url], [`Swh], or [`Other]) 75 79 @param value The identifier value (DOI, URL, SWH ID, etc.) 76 80 @param description Optional human-readable description *) 77 - val make 78 - : type_:Cff_enums.Identifier_type.t 79 - -> value:string 80 - -> ?description:string 81 - -> unit 82 - -> t 83 81 82 + val type_ : t -> Cff_enums.Identifier_type.t 84 83 (** The identifier type. *) 85 - val type_ : t -> Cff_enums.Identifier_type.t 86 84 85 + val value : t -> string 87 86 (** The identifier value. 88 87 89 - For DOIs, this is just the DOI (e.g., ["10.5281/zenodo.1234567"]), 90 - not the full URL. *) 91 - val value : t -> string 88 + For DOIs, this is just the DOI (e.g., ["10.5281/zenodo.1234567"]), not the 89 + full URL. *) 92 90 91 + val description : t -> string option 93 92 (** Optional description explaining what this identifier refers to. 94 93 95 94 Examples: 96 95 - ["The concept DOI for all versions"] 97 96 - ["Version 1.0.0 archive"] 98 97 - ["Release on GitHub"] *) 99 - val description : t -> string option 100 98 101 - (** Identifier equality (compares all fields). *) 102 99 val equal : t -> t -> bool 100 + (** Identifier equality (compares all fields). *) 103 101 102 + val compare : t -> t -> int 104 103 (** Identifier comparison. *) 105 - val compare : t -> t -> int 106 104 105 + val pp : Format.formatter -> t -> unit 107 106 (** Pretty-print as "[type]: value (description)". *) 108 - val pp : Format.formatter -> t -> unit 109 107 108 + val jsont : t Json.Codec.t 110 109 (** JSON/YAML codec for identifiers. *) 111 - val jsont : t Jsont.t
+28 -49
lib/cff_license.ml
··· 5 5 6 6 (** SPDX license handling for CFF. *) 7 7 8 - type t = 9 - [ `Spdx of Spdx_licenses.t 10 - | `Other of string list * string option 11 - ] 8 + type t = [ `Spdx of Spdx_licenses.t | `Other of string list * string option ] 12 9 13 10 let of_spdx spdx = `Spdx spdx 14 11 ··· 16 13 match Spdx_licenses.parse s with 17 14 | Ok spdx -> `Spdx spdx 18 15 | Error _ -> `Other ([ s ], None) 19 - ;; 20 16 21 17 let of_strings ss = 22 18 (* Try to parse as OR combination, fall back to Other *) 23 19 let try_parse_all () = 24 20 let rec build = function 25 21 | [] -> None 26 - | [ s ] -> 27 - (match Spdx_licenses.parse s with 28 - | Ok spdx -> Some spdx 29 - | Error _ -> None) 30 - | s :: rest -> 31 - (match Spdx_licenses.parse s, build rest with 32 - | Ok spdx, Some rest_spdx -> Some (Spdx_licenses.OR (spdx, rest_spdx)) 33 - | _ -> None) 22 + | [ s ] -> ( 23 + match Spdx_licenses.parse s with 24 + | Ok spdx -> Some spdx 25 + | Error _ -> None) 26 + | s :: rest -> ( 27 + match (Spdx_licenses.parse s, build rest) with 28 + | Ok spdx, Some rest_spdx -> Some (Spdx_licenses.OR (spdx, rest_spdx)) 29 + | _ -> None) 34 30 in 35 31 build ss 36 32 in 37 33 match try_parse_all () with 38 34 | Some spdx -> `Spdx spdx 39 35 | None -> `Other (ss, None) 40 - ;; 41 36 42 37 let with_url url = function 43 - | `Spdx _ as t -> t (* SPDX licenses have well-known URLs, ignore provided URL *) 38 + | `Spdx _ as t -> 39 + t (* SPDX licenses have well-known URLs, ignore provided URL *) 44 40 | `Other (ids, _) -> `Other (ids, Some url) 45 - ;; 46 41 47 42 let with_url_opt url_opt t = 48 - match url_opt with 49 - | None -> t 50 - | Some url -> with_url url t 51 - ;; 43 + match url_opt with None -> t | Some url -> with_url url t 52 44 53 - let to_spdx = function 54 - | `Spdx spdx -> Some spdx 55 - | `Other _ -> None 56 - ;; 45 + let to_spdx = function `Spdx spdx -> Some spdx | `Other _ -> None 57 46 58 47 let to_strings = function 59 48 | `Spdx spdx -> [ Spdx_licenses.to_string spdx ] 60 49 | `Other (ss, _) -> ss 61 - ;; 62 50 63 - let url = function 64 - | `Spdx _ -> None 65 - | `Other (_, url) -> url 66 - ;; 51 + let url = function `Spdx _ -> None | `Other (_, url) -> url 67 52 68 53 let pp ppf = function 69 54 | `Spdx spdx -> Format.pp_print_string ppf (Spdx_licenses.to_string spdx) 70 55 | `Other (ss, url_opt) -> 71 - (match ss with 72 - | [ s ] -> Format.pp_print_string ppf s 73 - | _ -> 74 - Format.fprintf 75 - ppf 76 - "[%a]" 77 - (Format.pp_print_list 78 - ~pp_sep:(fun ppf () -> Format.fprintf ppf ", ") 79 - Format.pp_print_string) 80 - ss); 81 - Option.iter (Format.fprintf ppf " <%s>") url_opt 82 - ;; 56 + (match ss with 57 + | [ s ] -> Format.pp_print_string ppf s 58 + | _ -> 59 + Format.fprintf ppf "[%a]" 60 + (Format.pp_print_list 61 + ~pp_sep:(fun ppf () -> Format.fprintf ppf ", ") 62 + Format.pp_print_string) 63 + ss); 64 + Option.iter (Format.fprintf ppf " <%s>") url_opt 83 65 84 66 let jsont = 85 67 let string_codec = 86 - Jsont.string 87 - |> Jsont.map ~dec:of_string ~enc:(function 68 + Json.Codec.string 69 + |> Json.Codec.map ~dec:of_string ~enc:(function 88 70 | `Spdx spdx -> Spdx_licenses.to_string spdx 89 71 | `Other ([ s ], _) -> s 90 72 | `Other _ -> assert false) 91 73 in 92 74 let array_codec = 93 - Jsont.(array string) 94 - |> Jsont.map 75 + Json.Codec.(array string) 76 + |> Json.Codec.map 95 77 ~dec:(fun ss -> of_strings (Array.to_list ss)) 96 78 ~enc:(fun t -> Array.of_list (to_strings t)) 97 79 in 98 - Jsont.any 99 - ~dec_string:string_codec 100 - ~dec_array:array_codec 80 + Json.Codec.any ~dec_string:string_codec ~dec_array:array_codec 101 81 ~enc:(function 102 82 | `Spdx (Spdx_licenses.Simple _) -> string_codec 103 83 | `Other ([ _ ], _) -> string_codec 104 84 | _ -> array_codec) 105 85 () 106 - ;;
+28 -30
lib/cff_license.mli
··· 5 5 6 6 (** SPDX license expressions for CFF. 7 7 8 - CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers} 9 - for the [license] field. This module combines license identification 10 - with optional URLs for non-standard licenses. 8 + CFF uses {{:https://spdx.org/licenses/}SPDX license identifiers} for the 9 + [license] field. This module combines license identification with optional 10 + URLs for non-standard licenses. 11 11 12 12 {1 License Representation} 13 13 ··· 20 20 21 21 {1 Why Combined?} 22 22 23 - The CFF spec has separate [license] and [license-url] fields, but they 24 - have a hidden relationship: 25 - - SPDX licenses have well-known URLs (e.g., MIT → https://spdx.org/licenses/MIT.html) 23 + The CFF spec has separate [license] and [license-url] fields, but they have 24 + a hidden relationship: 25 + - SPDX licenses have well-known URLs (e.g., MIT → 26 + https://spdx.org/licenses/MIT.html) 26 27 - [license-url] is only meaningful for non-SPDX licenses 27 28 28 - This type makes that relationship explicit: [`Spdx] licenses don't need 29 - a URL, while [`Other] licenses can optionally include one. 29 + This type makes that relationship explicit: [`Spdx] licenses don't need a 30 + URL, while [`Other] licenses can optionally include one. 30 31 31 32 {1 Examples} 32 33 ··· 47 48 license: ACME-Proprietary-1.0 48 49 license-url: https://acme.com/license 49 50 ]} 50 - Parsed as [`Other (["ACME-Proprietary-1.0"], Some "https://acme.com/license")]. *) 51 + Parsed as 52 + [`Other (["ACME-Proprietary-1.0"], Some "https://acme.com/license")]. *) 51 53 54 + type t = [ `Spdx of Spdx_licenses.t | `Other of string list * string option ] 52 55 (** License type: SPDX expression or unknown ID(s) with optional URL. *) 53 - type t = 54 - [ `Spdx of Spdx_licenses.t 55 - | `Other of string list * string option 56 - ] 57 56 58 57 (** {1 Construction} *) 59 58 60 - (** [of_spdx expr] wraps a valid SPDX expression. *) 61 59 val of_spdx : Spdx_licenses.t -> t 60 + (** [of_spdx expr] wraps a valid SPDX expression. *) 62 61 63 - (** [of_string s] parses [s] as an SPDX expression. 64 - Returns [`Spdx] on success, [`Other ([s], None)] on parse failure. *) 65 62 val of_string : string -> t 63 + (** [of_string s] parses [s] as an SPDX expression. Returns [`Spdx] on success, 64 + [`Other ([s], None)] on parse failure. *) 66 65 67 - (** [of_strings ss] parses a list of license strings. 68 - If all are valid SPDX IDs, returns [`Spdx] with OR combination. 69 - Otherwise returns [`Other (ss, None)]. *) 70 66 val of_strings : string list -> t 67 + (** [of_strings ss] parses a list of license strings. If all are valid SPDX IDs, 68 + returns [`Spdx] with OR combination. Otherwise returns [`Other (ss, None)]. 69 + *) 71 70 71 + val with_url : string -> t -> t 72 72 (** [with_url url t] adds a URL to the license. 73 73 - For [`Spdx], returns unchanged (SPDX URLs are well-known) 74 74 - For [`Other (ids, _)], returns [`Other (ids, Some url)] *) 75 - val with_url : string -> t -> t 76 75 77 - (** [with_url_opt url_opt t] optionally adds a URL. 78 - Convenience for combining during jsont decoding. *) 79 76 val with_url_opt : string option -> t -> t 77 + (** [with_url_opt url_opt t] optionally adds a URL. Convenience for combining 78 + during jsont decoding. *) 80 79 81 80 (** {1 Access} *) 82 81 82 + val to_spdx : t -> Spdx_licenses.t option 83 83 (** [to_spdx t] returns [Some expr] if valid SPDX, [None] otherwise. *) 84 - val to_spdx : t -> Spdx_licenses.t option 85 84 86 - (** [to_strings t] returns the license identifier(s) as strings. *) 87 85 val to_strings : t -> string list 86 + (** [to_strings t] returns the license identifier(s) as strings. *) 88 87 88 + val url : t -> string option 89 89 (** [url t] returns the license URL. 90 90 - For [`Spdx], always [None] (use SPDX's well-known URLs) 91 91 - For [`Other], returns the URL if provided *) 92 - val url : t -> string option 93 92 94 93 (** {1 Formatting} *) 95 94 96 - (** Pretty-print the license. *) 97 95 val pp : Format.formatter -> t -> unit 96 + (** Pretty-print the license. *) 98 97 99 98 (** {1 Codec} *) 100 99 100 + val jsont : t Json.Codec.t 101 101 (** JSON/YAML codec for the license field only. 102 102 103 - This handles just the [license] field. The [license-url] field 104 - is handled separately by the parent codec which combines them 105 - using {!with_url}. 103 + This handles just the [license] field. The [license-url] field is handled 104 + separately by the parent codec which combines them using {!with_url}. 106 105 107 106 Lenient: accepts any string without validation for round-tripping. *) 108 - val jsont : t Jsont.t
+601 -650
lib/cff_reference.ml
··· 7 7 8 8 (** Core identity of a reference. *) 9 9 module Core = struct 10 - type t = 11 - { type_ : Cff_enums.Reference_type.t 12 - ; title : string 13 - ; authors : Cff_author.t list 14 - ; abstract : string option 15 - ; abbreviation : string option 16 - } 10 + type t = { 11 + type_ : Cff_enums.Reference_type.t; 12 + title : string; 13 + authors : Cff_author.t list; 14 + abstract : string option; 15 + abbreviation : string option; 16 + } 17 17 18 18 let make ~type_ ~title ~authors ?abstract ?abbreviation () = 19 19 { type_; title; authors; abstract; abbreviation } 20 - ;; 21 20 22 21 let type_ t = t.type_ 23 22 let title t = t.title 24 23 let authors t = t.authors 25 24 let abstract t = t.abstract 26 25 let abbreviation t = t.abbreviation 27 - let pp ppf t = Format.fprintf ppf "%s (%a)" t.title Cff_enums.Reference_type.pp t.type_ 26 + 27 + let pp ppf t = 28 + Format.fprintf ppf "%s (%a)" t.title Cff_enums.Reference_type.pp t.type_ 28 29 end 29 30 30 31 (** Publication information (journal, volume, pages, etc.). *) 31 32 module Publication = struct 32 - type t = 33 - { journal : string option 34 - ; volume : string option 35 - ; issue : string option 36 - ; pages : string option 37 - ; start : string option 38 - ; end_ : string option 39 - ; edition : string option 40 - ; section : string option 41 - ; status : Cff_enums.Status.t option 42 - } 33 + type t = { 34 + journal : string option; 35 + volume : string option; 36 + issue : string option; 37 + pages : string option; 38 + start : string option; 39 + end_ : string option; 40 + edition : string option; 41 + section : string option; 42 + status : Cff_enums.Status.t option; 43 + } 43 44 44 45 let empty = 45 - { journal = None 46 - ; volume = None 47 - ; issue = None 48 - ; pages = None 49 - ; start = None 50 - ; end_ = None 51 - ; edition = None 52 - ; section = None 53 - ; status = None 46 + { 47 + journal = None; 48 + volume = None; 49 + issue = None; 50 + pages = None; 51 + start = None; 52 + end_ = None; 53 + edition = None; 54 + section = None; 55 + status = None; 54 56 } 55 - ;; 56 57 57 - let make ?journal ?volume ?issue ?pages ?start ?end_ ?edition ?section ?status () = 58 + let make ?journal ?volume ?issue ?pages ?start ?end_ ?edition ?section ?status 59 + () = 58 60 { journal; volume; issue; pages; start; end_; edition; section; status } 59 - ;; 60 61 61 62 let journal t = t.journal 62 63 let volume t = t.volume ··· 69 70 let status t = t.status 70 71 71 72 let is_empty t = 72 - t.journal = None 73 - && t.volume = None 74 - && t.issue = None 75 - && t.pages = None 76 - && t.start = None 77 - && t.end_ = None 78 - && t.edition = None 79 - && t.section = None 73 + t.journal = None && t.volume = None && t.issue = None && t.pages = None 74 + && t.start = None && t.end_ = None && t.edition = None && t.section = None 80 75 && t.status = None 81 - ;; 82 76 end 83 77 84 78 (** Collection information (proceedings, book series, etc.). *) 85 79 module Collection = struct 86 - type t = 87 - { collection_title : string option 88 - ; collection_type : string option 89 - ; collection_doi : string option 90 - ; volume_title : string option 91 - ; number_volumes : string option 92 - } 80 + type t = { 81 + collection_title : string option; 82 + collection_type : string option; 83 + collection_doi : string option; 84 + volume_title : string option; 85 + number_volumes : string option; 86 + } 93 87 94 88 let empty = 95 - { collection_title = None 96 - ; collection_type = None 97 - ; collection_doi = None 98 - ; volume_title = None 99 - ; number_volumes = None 89 + { 90 + collection_title = None; 91 + collection_type = None; 92 + collection_doi = None; 93 + volume_title = None; 94 + number_volumes = None; 100 95 } 101 - ;; 102 96 103 - let make 104 - ?collection_title 105 - ?collection_type 106 - ?collection_doi 107 - ?volume_title 108 - ?number_volumes 109 - () 110 - = 111 - { collection_title; collection_type; collection_doi; volume_title; number_volumes } 112 - ;; 97 + let make ?collection_title ?collection_type ?collection_doi ?volume_title 98 + ?number_volumes () = 99 + { 100 + collection_title; 101 + collection_type; 102 + collection_doi; 103 + volume_title; 104 + number_volumes; 105 + } 113 106 114 107 let collection_title t = t.collection_title 115 108 let collection_type t = t.collection_type ··· 118 111 let number_volumes t = t.number_volumes 119 112 120 113 let is_empty t = 121 - t.collection_title = None 122 - && t.collection_type = None 123 - && t.collection_doi = None 124 - && t.volume_title = None 114 + t.collection_title = None && t.collection_type = None 115 + && t.collection_doi = None && t.volume_title = None 125 116 && t.number_volumes = None 126 - ;; 127 117 end 128 118 129 119 (** Date information. *) 130 120 module Dates = struct 131 - type t = 132 - { date_accessed : Cff_date.t option 133 - ; date_downloaded : Cff_date.t option 134 - ; date_published : Cff_date.t option 135 - ; date_released : Cff_date.t option 136 - ; year : int option 137 - ; year_original : int option 138 - ; month : int option 139 - ; issue_date : string option 140 - } 121 + type t = { 122 + date_accessed : Cff_date.t option; 123 + date_downloaded : Cff_date.t option; 124 + date_published : Cff_date.t option; 125 + date_released : Cff_date.t option; 126 + year : int option; 127 + year_original : int option; 128 + month : int option; 129 + issue_date : string option; 130 + } 141 131 142 132 let empty = 143 - { date_accessed = None 144 - ; date_downloaded = None 145 - ; date_published = None 146 - ; date_released = None 147 - ; year = None 148 - ; year_original = None 149 - ; month = None 150 - ; issue_date = None 133 + { 134 + date_accessed = None; 135 + date_downloaded = None; 136 + date_published = None; 137 + date_released = None; 138 + year = None; 139 + year_original = None; 140 + month = None; 141 + issue_date = None; 151 142 } 152 - ;; 153 143 154 - let make 155 - ?date_accessed 156 - ?date_downloaded 157 - ?date_published 158 - ?date_released 159 - ?year 160 - ?year_original 161 - ?month 162 - ?issue_date 163 - () 164 - = 165 - { date_accessed 166 - ; date_downloaded 167 - ; date_published 168 - ; date_released 169 - ; year 170 - ; year_original 171 - ; month 172 - ; issue_date 144 + let make ?date_accessed ?date_downloaded ?date_published ?date_released ?year 145 + ?year_original ?month ?issue_date () = 146 + { 147 + date_accessed; 148 + date_downloaded; 149 + date_published; 150 + date_released; 151 + year; 152 + year_original; 153 + month; 154 + issue_date; 173 155 } 174 - ;; 175 156 176 157 let date_accessed t = t.date_accessed 177 158 let date_downloaded t = t.date_downloaded ··· 183 164 let issue_date t = t.issue_date 184 165 185 166 let is_empty t = 186 - t.date_accessed = None 187 - && t.date_downloaded = None 188 - && t.date_published = None 189 - && t.date_released = None 190 - && t.year = None 191 - && t.year_original = None 192 - && t.month = None 193 - && t.issue_date = None 194 - ;; 167 + t.date_accessed = None && t.date_downloaded = None 168 + && t.date_published = None && t.date_released = None && t.year = None 169 + && t.year_original = None && t.month = None && t.issue_date = None 195 170 end 196 171 197 172 (** Identifiers and links. *) 198 173 module Identifiers = struct 199 - type t = 200 - { doi : string option 201 - ; url : string option 202 - ; repository : string option 203 - ; repository_code : string option 204 - ; repository_artifact : string option 205 - ; isbn : string option 206 - ; issn : string option 207 - ; pmcid : string option 208 - ; nihmsid : string option 209 - ; identifiers : Cff_identifier.t list option 210 - } 174 + type t = { 175 + doi : string option; 176 + url : string option; 177 + repository : string option; 178 + repository_code : string option; 179 + repository_artifact : string option; 180 + isbn : string option; 181 + issn : string option; 182 + pmcid : string option; 183 + nihmsid : string option; 184 + identifiers : Cff_identifier.t list option; 185 + } 211 186 212 187 let empty = 213 - { doi = None 214 - ; url = None 215 - ; repository = None 216 - ; repository_code = None 217 - ; repository_artifact = None 218 - ; isbn = None 219 - ; issn = None 220 - ; pmcid = None 221 - ; nihmsid = None 222 - ; identifiers = None 188 + { 189 + doi = None; 190 + url = None; 191 + repository = None; 192 + repository_code = None; 193 + repository_artifact = None; 194 + isbn = None; 195 + issn = None; 196 + pmcid = None; 197 + nihmsid = None; 198 + identifiers = None; 223 199 } 224 - ;; 225 200 226 - let make 227 - ?doi 228 - ?url 229 - ?repository 230 - ?repository_code 231 - ?repository_artifact 232 - ?isbn 233 - ?issn 234 - ?pmcid 235 - ?nihmsid 236 - ?identifiers 237 - () 238 - = 239 - { doi 240 - ; url 241 - ; repository 242 - ; repository_code 243 - ; repository_artifact 244 - ; isbn 245 - ; issn 246 - ; pmcid 247 - ; nihmsid 248 - ; identifiers 201 + let make ?doi ?url ?repository ?repository_code ?repository_artifact ?isbn 202 + ?issn ?pmcid ?nihmsid ?identifiers () = 203 + { 204 + doi; 205 + url; 206 + repository; 207 + repository_code; 208 + repository_artifact; 209 + isbn; 210 + issn; 211 + pmcid; 212 + nihmsid; 213 + identifiers; 249 214 } 250 - ;; 251 215 252 216 let doi t = t.doi 253 217 let url t = t.url ··· 261 225 let identifiers t = t.identifiers 262 226 263 227 let is_empty t = 264 - t.doi = None 265 - && t.url = None 266 - && t.repository = None 228 + t.doi = None && t.url = None && t.repository = None 267 229 && t.repository_code = None 268 230 && t.repository_artifact = None 269 - && t.isbn = None 270 - && t.issn = None 271 - && t.pmcid = None 272 - && t.nihmsid = None 231 + && t.isbn = None && t.issn = None && t.pmcid = None && t.nihmsid = None 273 232 && t.identifiers = None 274 - ;; 275 233 end 276 234 277 235 (** Related entities (editors, publisher, etc.). *) 278 236 module Entities = struct 279 - type t = 280 - { editors : Cff_author.t list option 281 - ; editors_series : Cff_author.t list option 282 - ; translators : Cff_author.t list option 283 - ; recipients : Cff_author.t list option 284 - ; senders : Cff_author.t list option 285 - ; contact : Cff_author.t list option 286 - ; publisher : Cff_author.Entity.t option 287 - ; institution : Cff_author.Entity.t option 288 - ; conference : Cff_author.Entity.t option 289 - ; database_provider : Cff_author.Entity.t option 290 - ; location : Cff_author.Entity.t option 291 - } 237 + type t = { 238 + editors : Cff_author.t list option; 239 + editors_series : Cff_author.t list option; 240 + translators : Cff_author.t list option; 241 + recipients : Cff_author.t list option; 242 + senders : Cff_author.t list option; 243 + contact : Cff_author.t list option; 244 + publisher : Cff_author.Entity.t option; 245 + institution : Cff_author.Entity.t option; 246 + conference : Cff_author.Entity.t option; 247 + database_provider : Cff_author.Entity.t option; 248 + location : Cff_author.Entity.t option; 249 + } 292 250 293 251 let empty = 294 - { editors = None 295 - ; editors_series = None 296 - ; translators = None 297 - ; recipients = None 298 - ; senders = None 299 - ; contact = None 300 - ; publisher = None 301 - ; institution = None 302 - ; conference = None 303 - ; database_provider = None 304 - ; location = None 252 + { 253 + editors = None; 254 + editors_series = None; 255 + translators = None; 256 + recipients = None; 257 + senders = None; 258 + contact = None; 259 + publisher = None; 260 + institution = None; 261 + conference = None; 262 + database_provider = None; 263 + location = None; 305 264 } 306 - ;; 307 265 308 - let make 309 - ?editors 310 - ?editors_series 311 - ?translators 312 - ?recipients 313 - ?senders 314 - ?contact 315 - ?publisher 316 - ?institution 317 - ?conference 318 - ?database_provider 319 - ?location 320 - () 321 - = 322 - { editors 323 - ; editors_series 324 - ; translators 325 - ; recipients 326 - ; senders 327 - ; contact 328 - ; publisher 329 - ; institution 330 - ; conference 331 - ; database_provider 332 - ; location 266 + let make ?editors ?editors_series ?translators ?recipients ?senders ?contact 267 + ?publisher ?institution ?conference ?database_provider ?location () = 268 + { 269 + editors; 270 + editors_series; 271 + translators; 272 + recipients; 273 + senders; 274 + contact; 275 + publisher; 276 + institution; 277 + conference; 278 + database_provider; 279 + location; 333 280 } 334 - ;; 335 281 336 282 let editors t = t.editors 337 283 let editors_series t = t.editors_series ··· 346 292 let location t = t.location 347 293 348 294 let is_empty t = 349 - t.editors = None 350 - && t.editors_series = None 351 - && t.translators = None 352 - && t.recipients = None 353 - && t.senders = None 354 - && t.contact = None 355 - && t.publisher = None 356 - && t.institution = None 357 - && t.conference = None 358 - && t.database_provider = None 359 - && t.location = None 360 - ;; 295 + t.editors = None && t.editors_series = None && t.translators = None 296 + && t.recipients = None && t.senders = None && t.contact = None 297 + && t.publisher = None && t.institution = None && t.conference = None 298 + && t.database_provider = None && t.location = None 361 299 end 362 300 363 301 (** Metadata and description. *) 364 302 module Metadata = struct 365 - type t = 366 - { keywords : string list option 367 - ; languages : string list option 368 - ; license : Cff_license.t option 369 - ; copyright : string option 370 - ; scope : string option 371 - ; notes : string option 372 - } 303 + type t = { 304 + keywords : string list option; 305 + languages : string list option; 306 + license : Cff_license.t option; 307 + copyright : string option; 308 + scope : string option; 309 + notes : string option; 310 + } 373 311 374 312 let empty = 375 - { keywords = None 376 - ; languages = None 377 - ; license = None 378 - ; copyright = None 379 - ; scope = None 380 - ; notes = None 313 + { 314 + keywords = None; 315 + languages = None; 316 + license = None; 317 + copyright = None; 318 + scope = None; 319 + notes = None; 381 320 } 382 - ;; 383 321 384 322 let make ?keywords ?languages ?license ?copyright ?scope ?notes () = 385 323 { keywords; languages; license; copyright; scope; notes } 386 - ;; 387 324 388 325 let keywords t = t.keywords 389 326 let languages t = t.languages ··· 393 330 let notes t = t.notes 394 331 395 332 let is_empty t = 396 - t.keywords = None 397 - && t.languages = None 398 - && t.license = None 399 - && t.copyright = None 400 - && t.scope = None 401 - && t.notes = None 402 - ;; 333 + t.keywords = None && t.languages = None && t.license = None 334 + && t.copyright = None && t.scope = None && t.notes = None 403 335 end 404 336 405 337 (** Technical and domain-specific fields. *) 406 338 module Technical = struct 407 - type t = 408 - { commit : string option 409 - ; version : string option 410 - ; filename : string option 411 - ; format : string option 412 - ; medium : string option 413 - ; data_type : string option 414 - ; database : string option 415 - ; number : string option 416 - ; patent_states : string list option 417 - ; thesis_type : string option 418 - ; term : string option 419 - ; entry : string option 420 - ; department : string option 421 - ; loc_start : string option 422 - ; loc_end : string option 423 - } 339 + type t = { 340 + commit : string option; 341 + version : string option; 342 + filename : string option; 343 + format : string option; 344 + medium : string option; 345 + data_type : string option; 346 + database : string option; 347 + number : string option; 348 + patent_states : string list option; 349 + thesis_type : string option; 350 + term : string option; 351 + entry : string option; 352 + department : string option; 353 + loc_start : string option; 354 + loc_end : string option; 355 + } 424 356 425 357 let empty = 426 - { commit = None 427 - ; version = None 428 - ; filename = None 429 - ; format = None 430 - ; medium = None 431 - ; data_type = None 432 - ; database = None 433 - ; number = None 434 - ; patent_states = None 435 - ; thesis_type = None 436 - ; term = None 437 - ; entry = None 438 - ; department = None 439 - ; loc_start = None 440 - ; loc_end = None 358 + { 359 + commit = None; 360 + version = None; 361 + filename = None; 362 + format = None; 363 + medium = None; 364 + data_type = None; 365 + database = None; 366 + number = None; 367 + patent_states = None; 368 + thesis_type = None; 369 + term = None; 370 + entry = None; 371 + department = None; 372 + loc_start = None; 373 + loc_end = None; 441 374 } 442 - ;; 443 375 444 - let make 445 - ?commit 446 - ?version 447 - ?filename 448 - ?format 449 - ?medium 450 - ?data_type 451 - ?database 452 - ?number 453 - ?patent_states 454 - ?thesis_type 455 - ?term 456 - ?entry 457 - ?department 458 - ?loc_start 459 - ?loc_end 460 - () 461 - = 462 - { commit 463 - ; version 464 - ; filename 465 - ; format 466 - ; medium 467 - ; data_type 468 - ; database 469 - ; number 470 - ; patent_states 471 - ; thesis_type 472 - ; term 473 - ; entry 474 - ; department 475 - ; loc_start 476 - ; loc_end 376 + let make ?commit ?version ?filename ?format ?medium ?data_type ?database 377 + ?number ?patent_states ?thesis_type ?term ?entry ?department ?loc_start 378 + ?loc_end () = 379 + { 380 + commit; 381 + version; 382 + filename; 383 + format; 384 + medium; 385 + data_type; 386 + database; 387 + number; 388 + patent_states; 389 + thesis_type; 390 + term; 391 + entry; 392 + department; 393 + loc_start; 394 + loc_end; 477 395 } 478 - ;; 479 396 480 397 let commit t = t.commit 481 398 let version t = t.version ··· 494 411 let loc_end t = t.loc_end 495 412 496 413 let is_empty t = 497 - t.commit = None 498 - && t.version = None 499 - && t.filename = None 500 - && t.format = None 501 - && t.medium = None 502 - && t.data_type = None 503 - && t.database = None 504 - && t.number = None 505 - && t.patent_states = None 506 - && t.thesis_type = None 507 - && t.term = None 508 - && t.entry = None 509 - && t.department = None 510 - && t.loc_start = None 511 - && t.loc_end = None 512 - ;; 414 + t.commit = None && t.version = None && t.filename = None && t.format = None 415 + && t.medium = None && t.data_type = None && t.database = None 416 + && t.number = None && t.patent_states = None && t.thesis_type = None 417 + && t.term = None && t.entry = None && t.department = None 418 + && t.loc_start = None && t.loc_end = None 513 419 end 514 420 421 + type t = { 422 + core : Core.t; 423 + publication : Publication.t; 424 + collection : Collection.t; 425 + dates : Dates.t; 426 + identifiers : Identifiers.t; 427 + entities : Entities.t; 428 + metadata : Metadata.t; 429 + technical : Technical.t; 430 + } 515 431 (** Complete reference type. *) 516 - type t = 517 - { core : Core.t 518 - ; publication : Publication.t 519 - ; collection : Collection.t 520 - ; dates : Dates.t 521 - ; identifiers : Identifiers.t 522 - ; entities : Entities.t 523 - ; metadata : Metadata.t 524 - ; technical : Technical.t 432 + 433 + let make ~core ?(publication = Publication.empty) 434 + ?(collection = Collection.empty) ?(dates = Dates.empty) 435 + ?(identifiers = Identifiers.empty) ?(entities = Entities.empty) 436 + ?(metadata = Metadata.empty) ?(technical = Technical.empty) () = 437 + { 438 + core; 439 + publication; 440 + collection; 441 + dates; 442 + identifiers; 443 + entities; 444 + metadata; 445 + technical; 525 446 } 526 447 527 - let make 528 - ~core 529 - ?(publication = Publication.empty) 530 - ?(collection = Collection.empty) 531 - ?(dates = Dates.empty) 532 - ?(identifiers = Identifiers.empty) 533 - ?(entities = Entities.empty) 534 - ?(metadata = Metadata.empty) 535 - ?(technical = Technical.empty) 536 - () 537 - = 538 - { core; publication; collection; dates; identifiers; entities; metadata; technical } 539 - ;; 540 - 541 448 let make_simple ~type_ ~title ~authors ?doi ?year ?journal () = 542 449 let core = Core.make ~type_ ~title ~authors () in 543 450 let publication = Publication.make ?journal () in 544 451 let dates = Dates.make ?year () in 545 452 let identifiers = Identifiers.make ?doi () in 546 453 make ~core ~publication ~dates ~identifiers () 547 - ;; 548 454 549 455 (* Accessors for sub-records *) 550 456 let core t = t.core ··· 566 472 567 473 (* Helper for string that can also be int (for pages, etc.) *) 568 474 let string_or_int_jsont = 569 - Jsont.any 475 + Json.Codec.any 570 476 ~dec_number: 571 - (Jsont.number 572 - |> Jsont.map ~dec:(fun f -> string_of_int (int_of_float f)) ~enc:float_of_string) 573 - ~dec_string:Jsont.string 477 + (Json.Codec.number 478 + |> Json.Codec.map 479 + ~dec:(fun f -> string_of_int (int_of_float f)) 480 + ~enc:float_of_string) 481 + ~dec_string:Json.Codec.string 574 482 ~enc:(fun s -> 575 483 match float_of_string_opt s with 576 484 | Some _ -> 577 - Jsont.number |> Jsont.map ~dec:(fun _ -> assert false) ~enc:float_of_string 578 - | None -> Jsont.string) 485 + Json.Codec.number 486 + |> Json.Codec.map ~dec:(fun _ -> assert false) ~enc:float_of_string 487 + | None -> Json.Codec.string) 579 488 () 580 - ;; 581 489 582 490 (* Helper to convert array jsont to list jsont *) 583 491 let list_jsont elt = 584 - Jsont.(array elt |> map ~dec:Stdlib.Array.to_list ~enc:Stdlib.Array.of_list) 585 - ;; 492 + Json.Codec.( 493 + array elt |> map ~dec:Stdlib.Array.to_list ~enc:Stdlib.Array.of_list) 586 494 587 - (* Jsont codec for the full reference type *) 495 + (* JSON codec for the full reference type *) 588 496 let jsont = 589 497 let authors_list_jsont = list_jsont Cff_author.jsont in 590 498 let identifiers_list_jsont = list_jsont Cff_identifier.jsont in 591 - let string_list_jsont = list_jsont Jsont.string in 499 + let string_list_jsont = list_jsont Json.Codec.string in 592 500 (* We need to decode all 60+ fields and then group into sub-records *) 593 - Jsont.Object.map 594 - ~kind:"Reference" 501 + Json.Codec.Object.map ~kind:"Reference" 595 502 (fun 596 - type_ 597 - title 598 - authors 599 - abstract 600 - abbreviation 601 - (* Publication *) 602 - journal 603 - volume 604 - issue 605 - pages 606 - start 607 - end_ 608 - edition 609 - section 610 - status 611 - (* Collection *) 612 - collection_title 613 - collection_type 614 - collection_doi 615 - volume_title 616 - number_volumes 617 - (* Dates *) 618 - date_accessed 619 - date_downloaded 620 - date_published 621 - date_released 622 - year 623 - year_original 624 - month 625 - issue_date 626 - (* Identifiers *) 627 - doi 628 - url 629 - repository 630 - repository_code 631 - repository_artifact 632 - isbn 633 - issn 634 - pmcid 635 - nihmsid 636 - identifiers_list 637 - (* Entities *) 638 - editors 639 - editors_series 640 - translators 641 - recipients 642 - senders 643 - contact 644 - publisher 645 - institution 646 - conference 647 - database_provider 648 - location_entity 649 - (* Metadata *) 650 - keywords 651 - languages 652 - license 653 - license_url 654 - copyright 655 - scope 656 - notes 657 - (* Technical *) 658 - commit 659 - version 660 - filename 661 - format 662 - medium 663 - data_type 664 - database 665 - number 666 - patent_states 667 - thesis_type 668 - term 669 - entry 670 - department 671 - loc_start 672 - loc_end 673 - -> 674 - let core = { Core.type_; title; authors; abstract; abbreviation } in 675 - let publication = 676 - { Publication.journal 677 - ; volume 678 - ; issue 679 - ; pages 680 - ; start 681 - ; end_ 682 - ; edition 683 - ; section 684 - ; status 685 - } 686 - in 687 - let collection = 688 - { Collection.collection_title 689 - ; collection_type 690 - ; collection_doi 691 - ; volume_title 692 - ; number_volumes 693 - } 694 - in 695 - let dates = 696 - { Dates.date_accessed 697 - ; date_downloaded 698 - ; date_published 699 - ; date_released 700 - ; year 701 - ; year_original 702 - ; month 703 - ; issue_date 704 - } 705 - in 706 - let identifiers = 707 - { Identifiers.doi 708 - ; url 709 - ; repository 710 - ; repository_code 711 - ; repository_artifact 712 - ; isbn 713 - ; issn 714 - ; pmcid 715 - ; nihmsid 716 - ; identifiers = identifiers_list 717 - } 718 - in 719 - let entities = 720 - { Entities.editors 721 - ; editors_series 722 - ; translators 723 - ; recipients 724 - ; senders 725 - ; contact 726 - ; publisher 727 - ; institution 728 - ; conference 729 - ; database_provider 730 - ; location = location_entity 731 - } 732 - in 733 - let license = Option.map (Cff_license.with_url_opt license_url) license in 734 - let metadata = 735 - { Metadata.keywords; languages; license; copyright; scope; notes } 736 - in 737 - let technical = 738 - { Technical.commit 739 - ; version 740 - ; filename 741 - ; format 742 - ; medium 743 - ; data_type 744 - ; database 745 - ; number 746 - ; patent_states 747 - ; thesis_type 748 - ; term 749 - ; entry 750 - ; department 751 - ; loc_start 752 - ; loc_end 753 - } 754 - in 755 - { core 756 - ; publication 757 - ; collection 758 - ; dates 759 - ; identifiers 760 - ; entities 761 - ; metadata 762 - ; technical 763 - }) 503 + type_ 504 + title 505 + authors 506 + abstract 507 + abbreviation 508 + (* Publication *) 509 + journal 510 + volume 511 + issue 512 + pages 513 + start 514 + end_ 515 + edition 516 + section 517 + status 518 + (* Collection *) 519 + collection_title 520 + collection_type 521 + collection_doi 522 + volume_title 523 + number_volumes 524 + (* Dates *) 525 + date_accessed 526 + date_downloaded 527 + date_published 528 + date_released 529 + year 530 + year_original 531 + month 532 + issue_date 533 + (* Identifiers *) 534 + doi 535 + url 536 + repository 537 + repository_code 538 + repository_artifact 539 + isbn 540 + issn 541 + pmcid 542 + nihmsid 543 + identifiers_list 544 + (* Entities *) 545 + editors 546 + editors_series 547 + translators 548 + recipients 549 + senders 550 + contact 551 + publisher 552 + institution 553 + conference 554 + database_provider 555 + location_entity 556 + (* Metadata *) 557 + keywords 558 + languages 559 + license 560 + license_url 561 + copyright 562 + scope 563 + notes 564 + (* Technical *) 565 + commit 566 + version 567 + filename 568 + format 569 + medium 570 + data_type 571 + database 572 + number 573 + patent_states 574 + thesis_type 575 + term 576 + entry 577 + department 578 + loc_start 579 + loc_end 580 + -> 581 + let core = { Core.type_; title; authors; abstract; abbreviation } in 582 + let publication = 583 + { 584 + Publication.journal; 585 + volume; 586 + issue; 587 + pages; 588 + start; 589 + end_; 590 + edition; 591 + section; 592 + status; 593 + } 594 + in 595 + let collection = 596 + { 597 + Collection.collection_title; 598 + collection_type; 599 + collection_doi; 600 + volume_title; 601 + number_volumes; 602 + } 603 + in 604 + let dates = 605 + { 606 + Dates.date_accessed; 607 + date_downloaded; 608 + date_published; 609 + date_released; 610 + year; 611 + year_original; 612 + month; 613 + issue_date; 614 + } 615 + in 616 + let identifiers = 617 + { 618 + Identifiers.doi; 619 + url; 620 + repository; 621 + repository_code; 622 + repository_artifact; 623 + isbn; 624 + issn; 625 + pmcid; 626 + nihmsid; 627 + identifiers = identifiers_list; 628 + } 629 + in 630 + let entities = 631 + { 632 + Entities.editors; 633 + editors_series; 634 + translators; 635 + recipients; 636 + senders; 637 + contact; 638 + publisher; 639 + institution; 640 + conference; 641 + database_provider; 642 + location = location_entity; 643 + } 644 + in 645 + let license = Option.map (Cff_license.with_url_opt license_url) license in 646 + let metadata = 647 + { Metadata.keywords; languages; license; copyright; scope; notes } 648 + in 649 + let technical = 650 + { 651 + Technical.commit; 652 + version; 653 + filename; 654 + format; 655 + medium; 656 + data_type; 657 + database; 658 + number; 659 + patent_states; 660 + thesis_type; 661 + term; 662 + entry; 663 + department; 664 + loc_start; 665 + loc_end; 666 + } 667 + in 668 + { 669 + core; 670 + publication; 671 + collection; 672 + dates; 673 + identifiers; 674 + entities; 675 + metadata; 676 + technical; 677 + }) 764 678 (* Core fields *) 765 - |> Jsont.Object.mem "type" Cff_enums.Reference_type.jsont ~enc:(fun r -> r.core.type_) 766 - |> Jsont.Object.mem "title" Jsont.string ~enc:(fun r -> r.core.title) 767 - |> Jsont.Object.mem "authors" authors_list_jsont ~enc:(fun r -> r.core.authors) 768 - |> Jsont.Object.opt_mem "abstract" Jsont.string ~enc:(fun r -> r.core.abstract) 769 - |> Jsont.Object.opt_mem "abbreviation" Jsont.string ~enc:(fun r -> r.core.abbreviation) 679 + |> Json.Codec.Object.member "type" Cff_enums.Reference_type.jsont 680 + ~enc:(fun r -> r.core.type_) 681 + |> Json.Codec.Object.member "title" Json.Codec.string ~enc:(fun r -> 682 + r.core.title) 683 + |> Json.Codec.Object.member "authors" authors_list_jsont ~enc:(fun r -> 684 + r.core.authors) 685 + |> Json.Codec.Object.opt_member "abstract" Json.Codec.string ~enc:(fun r -> 686 + r.core.abstract) 687 + |> Json.Codec.Object.opt_member "abbreviation" Json.Codec.string 688 + ~enc:(fun r -> r.core.abbreviation) 770 689 (* Publication fields *) 771 - |> Jsont.Object.opt_mem "journal" Jsont.string ~enc:(fun r -> r.publication.journal) 772 - |> Jsont.Object.opt_mem "volume" string_or_int_jsont ~enc:(fun r -> 773 - r.publication.volume) 774 - |> Jsont.Object.opt_mem "issue" string_or_int_jsont ~enc:(fun r -> r.publication.issue) 775 - |> Jsont.Object.opt_mem "pages" string_or_int_jsont ~enc:(fun r -> r.publication.pages) 776 - |> Jsont.Object.opt_mem "start" string_or_int_jsont ~enc:(fun r -> r.publication.start) 777 - |> Jsont.Object.opt_mem "end" string_or_int_jsont ~enc:(fun r -> r.publication.end_) 778 - |> Jsont.Object.opt_mem "edition" Jsont.string ~enc:(fun r -> r.publication.edition) 779 - |> Jsont.Object.opt_mem "section" string_or_int_jsont ~enc:(fun r -> 780 - r.publication.section) 781 - |> Jsont.Object.opt_mem "status" Cff_enums.Status.jsont ~enc:(fun r -> 782 - r.publication.status) 690 + |> Json.Codec.Object.opt_member "journal" Json.Codec.string ~enc:(fun r -> 691 + r.publication.journal) 692 + |> Json.Codec.Object.opt_member "volume" string_or_int_jsont ~enc:(fun r -> 693 + r.publication.volume) 694 + |> Json.Codec.Object.opt_member "issue" string_or_int_jsont ~enc:(fun r -> 695 + r.publication.issue) 696 + |> Json.Codec.Object.opt_member "pages" string_or_int_jsont ~enc:(fun r -> 697 + r.publication.pages) 698 + |> Json.Codec.Object.opt_member "start" string_or_int_jsont ~enc:(fun r -> 699 + r.publication.start) 700 + |> Json.Codec.Object.opt_member "end" string_or_int_jsont ~enc:(fun r -> 701 + r.publication.end_) 702 + |> Json.Codec.Object.opt_member "edition" Json.Codec.string ~enc:(fun r -> 703 + r.publication.edition) 704 + |> Json.Codec.Object.opt_member "section" string_or_int_jsont ~enc:(fun r -> 705 + r.publication.section) 706 + |> Json.Codec.Object.opt_member "status" Cff_enums.Status.jsont ~enc:(fun r -> 707 + r.publication.status) 783 708 (* Collection fields *) 784 - |> Jsont.Object.opt_mem "collection-title" Jsont.string ~enc:(fun r -> 785 - r.collection.collection_title) 786 - |> Jsont.Object.opt_mem "collection-type" Jsont.string ~enc:(fun r -> 787 - r.collection.collection_type) 788 - |> Jsont.Object.opt_mem "collection-doi" Jsont.string ~enc:(fun r -> 789 - r.collection.collection_doi) 790 - |> Jsont.Object.opt_mem "volume-title" Jsont.string ~enc:(fun r -> 791 - r.collection.volume_title) 792 - |> Jsont.Object.opt_mem "number-volumes" string_or_int_jsont ~enc:(fun r -> 793 - r.collection.number_volumes) 709 + |> Json.Codec.Object.opt_member "collection-title" Json.Codec.string 710 + ~enc:(fun r -> r.collection.collection_title) 711 + |> Json.Codec.Object.opt_member "collection-type" Json.Codec.string 712 + ~enc:(fun r -> r.collection.collection_type) 713 + |> Json.Codec.Object.opt_member "collection-doi" Json.Codec.string 714 + ~enc:(fun r -> r.collection.collection_doi) 715 + |> Json.Codec.Object.opt_member "volume-title" Json.Codec.string 716 + ~enc:(fun r -> r.collection.volume_title) 717 + |> Json.Codec.Object.opt_member "number-volumes" string_or_int_jsont 718 + ~enc:(fun r -> r.collection.number_volumes) 794 719 (* Date fields *) 795 - |> Jsont.Object.opt_mem "date-accessed" Cff_date.jsont ~enc:(fun r -> 796 - r.dates.date_accessed) 797 - |> Jsont.Object.opt_mem "date-downloaded" Cff_date.jsont ~enc:(fun r -> 798 - r.dates.date_downloaded) 799 - |> Jsont.Object.opt_mem "date-published" Cff_date.jsont ~enc:(fun r -> 800 - r.dates.date_published) 801 - |> Jsont.Object.opt_mem "date-released" Cff_date.jsont ~enc:(fun r -> 802 - r.dates.date_released) 803 - |> Jsont.Object.opt_mem "year" Jsont.int ~enc:(fun r -> r.dates.year) 804 - |> Jsont.Object.opt_mem "year-original" Jsont.int ~enc:(fun r -> r.dates.year_original) 805 - |> Jsont.Object.opt_mem "month" Jsont.int ~enc:(fun r -> r.dates.month) 806 - |> Jsont.Object.opt_mem "issue-date" Jsont.string ~enc:(fun r -> r.dates.issue_date) 720 + |> Json.Codec.Object.opt_member "date-accessed" Cff_date.jsont ~enc:(fun r -> 721 + r.dates.date_accessed) 722 + |> Json.Codec.Object.opt_member "date-downloaded" Cff_date.jsont 723 + ~enc:(fun r -> r.dates.date_downloaded) 724 + |> Json.Codec.Object.opt_member "date-published" Cff_date.jsont ~enc:(fun r -> 725 + r.dates.date_published) 726 + |> Json.Codec.Object.opt_member "date-released" Cff_date.jsont ~enc:(fun r -> 727 + r.dates.date_released) 728 + |> Json.Codec.Object.opt_member "year" Json.Codec.int ~enc:(fun r -> 729 + r.dates.year) 730 + |> Json.Codec.Object.opt_member "year-original" Json.Codec.int ~enc:(fun r -> 731 + r.dates.year_original) 732 + |> Json.Codec.Object.opt_member "month" Json.Codec.int ~enc:(fun r -> 733 + r.dates.month) 734 + |> Json.Codec.Object.opt_member "issue-date" Json.Codec.string ~enc:(fun r -> 735 + r.dates.issue_date) 807 736 (* Identifier fields *) 808 - |> Jsont.Object.opt_mem "doi" Jsont.string ~enc:(fun r -> r.identifiers.doi) 809 - |> Jsont.Object.opt_mem "url" Jsont.string ~enc:(fun r -> r.identifiers.url) 810 - |> Jsont.Object.opt_mem "repository" Jsont.string ~enc:(fun r -> 811 - r.identifiers.repository) 812 - |> Jsont.Object.opt_mem "repository-code" Jsont.string ~enc:(fun r -> 813 - r.identifiers.repository_code) 814 - |> Jsont.Object.opt_mem "repository-artifact" Jsont.string ~enc:(fun r -> 815 - r.identifiers.repository_artifact) 816 - |> Jsont.Object.opt_mem "isbn" Jsont.string ~enc:(fun r -> r.identifiers.isbn) 817 - |> Jsont.Object.opt_mem "issn" string_or_int_jsont ~enc:(fun r -> r.identifiers.issn) 818 - |> Jsont.Object.opt_mem "pmcid" Jsont.string ~enc:(fun r -> r.identifiers.pmcid) 819 - |> Jsont.Object.opt_mem "nihmsid" Jsont.string ~enc:(fun r -> r.identifiers.nihmsid) 820 - |> Jsont.Object.opt_mem "identifiers" identifiers_list_jsont ~enc:(fun r -> 821 - r.identifiers.identifiers) 737 + |> Json.Codec.Object.opt_member "doi" Json.Codec.string ~enc:(fun r -> 738 + r.identifiers.doi) 739 + |> Json.Codec.Object.opt_member "url" Json.Codec.string ~enc:(fun r -> 740 + r.identifiers.url) 741 + |> Json.Codec.Object.opt_member "repository" Json.Codec.string ~enc:(fun r -> 742 + r.identifiers.repository) 743 + |> Json.Codec.Object.opt_member "repository-code" Json.Codec.string 744 + ~enc:(fun r -> r.identifiers.repository_code) 745 + |> Json.Codec.Object.opt_member "repository-artifact" Json.Codec.string 746 + ~enc:(fun r -> r.identifiers.repository_artifact) 747 + |> Json.Codec.Object.opt_member "isbn" Json.Codec.string ~enc:(fun r -> 748 + r.identifiers.isbn) 749 + |> Json.Codec.Object.opt_member "issn" string_or_int_jsont ~enc:(fun r -> 750 + r.identifiers.issn) 751 + |> Json.Codec.Object.opt_member "pmcid" Json.Codec.string ~enc:(fun r -> 752 + r.identifiers.pmcid) 753 + |> Json.Codec.Object.opt_member "nihmsid" Json.Codec.string ~enc:(fun r -> 754 + r.identifiers.nihmsid) 755 + |> Json.Codec.Object.opt_member "identifiers" identifiers_list_jsont 756 + ~enc:(fun r -> r.identifiers.identifiers) 822 757 (* Entity fields *) 823 - |> Jsont.Object.opt_mem "editors" authors_list_jsont ~enc:(fun r -> r.entities.editors) 824 - |> Jsont.Object.opt_mem "editors-series" authors_list_jsont ~enc:(fun r -> 825 - r.entities.editors_series) 826 - |> Jsont.Object.opt_mem "translators" authors_list_jsont ~enc:(fun r -> 827 - r.entities.translators) 828 - |> Jsont.Object.opt_mem "recipients" authors_list_jsont ~enc:(fun r -> 829 - r.entities.recipients) 830 - |> Jsont.Object.opt_mem "senders" authors_list_jsont ~enc:(fun r -> r.entities.senders) 831 - |> Jsont.Object.opt_mem "contact" authors_list_jsont ~enc:(fun r -> r.entities.contact) 832 - |> Jsont.Object.opt_mem "publisher" Cff_author.Entity.jsont ~enc:(fun r -> 833 - r.entities.publisher) 834 - |> Jsont.Object.opt_mem "institution" Cff_author.Entity.jsont ~enc:(fun r -> 835 - r.entities.institution) 836 - |> Jsont.Object.opt_mem "conference" Cff_author.Entity.jsont ~enc:(fun r -> 837 - r.entities.conference) 838 - |> Jsont.Object.opt_mem "database-provider" Cff_author.Entity.jsont ~enc:(fun r -> 839 - r.entities.database_provider) 840 - |> Jsont.Object.opt_mem "location" Cff_author.Entity.jsont ~enc:(fun r -> 841 - r.entities.location) 758 + |> Json.Codec.Object.opt_member "editors" authors_list_jsont ~enc:(fun r -> 759 + r.entities.editors) 760 + |> Json.Codec.Object.opt_member "editors-series" authors_list_jsont 761 + ~enc:(fun r -> r.entities.editors_series) 762 + |> Json.Codec.Object.opt_member "translators" authors_list_jsont 763 + ~enc:(fun r -> r.entities.translators) 764 + |> Json.Codec.Object.opt_member "recipients" authors_list_jsont ~enc:(fun r -> 765 + r.entities.recipients) 766 + |> Json.Codec.Object.opt_member "senders" authors_list_jsont ~enc:(fun r -> 767 + r.entities.senders) 768 + |> Json.Codec.Object.opt_member "contact" authors_list_jsont ~enc:(fun r -> 769 + r.entities.contact) 770 + |> Json.Codec.Object.opt_member "publisher" Cff_author.Entity.jsont 771 + ~enc:(fun r -> r.entities.publisher) 772 + |> Json.Codec.Object.opt_member "institution" Cff_author.Entity.jsont 773 + ~enc:(fun r -> r.entities.institution) 774 + |> Json.Codec.Object.opt_member "conference" Cff_author.Entity.jsont 775 + ~enc:(fun r -> r.entities.conference) 776 + |> Json.Codec.Object.opt_member "database-provider" Cff_author.Entity.jsont 777 + ~enc:(fun r -> r.entities.database_provider) 778 + |> Json.Codec.Object.opt_member "location" Cff_author.Entity.jsont 779 + ~enc:(fun r -> r.entities.location) 842 780 (* Metadata fields *) 843 - |> Jsont.Object.opt_mem "keywords" string_list_jsont ~enc:(fun r -> r.metadata.keywords) 844 - |> Jsont.Object.opt_mem "languages" string_list_jsont ~enc:(fun r -> 845 - r.metadata.languages) 846 - |> Jsont.Object.opt_mem "license" Cff_license.jsont ~enc:(fun r -> r.metadata.license) 847 - |> Jsont.Object.opt_mem "license-url" Jsont.string ~enc:(fun r -> 848 - Option.bind r.metadata.license Cff_license.url) 849 - |> Jsont.Object.opt_mem "copyright" Jsont.string ~enc:(fun r -> r.metadata.copyright) 850 - |> Jsont.Object.opt_mem "scope" Jsont.string ~enc:(fun r -> r.metadata.scope) 851 - |> Jsont.Object.opt_mem "notes" Jsont.string ~enc:(fun r -> r.metadata.notes) 781 + |> Json.Codec.Object.opt_member "keywords" string_list_jsont ~enc:(fun r -> 782 + r.metadata.keywords) 783 + |> Json.Codec.Object.opt_member "languages" string_list_jsont ~enc:(fun r -> 784 + r.metadata.languages) 785 + |> Json.Codec.Object.opt_member "license" Cff_license.jsont ~enc:(fun r -> 786 + r.metadata.license) 787 + |> Json.Codec.Object.opt_member "license-url" Json.Codec.string ~enc:(fun r -> 788 + Option.bind r.metadata.license Cff_license.url) 789 + |> Json.Codec.Object.opt_member "copyright" Json.Codec.string ~enc:(fun r -> 790 + r.metadata.copyright) 791 + |> Json.Codec.Object.opt_member "scope" Json.Codec.string ~enc:(fun r -> 792 + r.metadata.scope) 793 + |> Json.Codec.Object.opt_member "notes" Json.Codec.string ~enc:(fun r -> 794 + r.metadata.notes) 852 795 (* Technical fields *) 853 - |> Jsont.Object.opt_mem "commit" Jsont.string ~enc:(fun r -> r.technical.commit) 854 - |> Jsont.Object.opt_mem "version" string_or_int_jsont ~enc:(fun r -> 855 - r.technical.version) 856 - |> Jsont.Object.opt_mem "filename" Jsont.string ~enc:(fun r -> r.technical.filename) 857 - |> Jsont.Object.opt_mem "format" Jsont.string ~enc:(fun r -> r.technical.format) 858 - |> Jsont.Object.opt_mem "medium" Jsont.string ~enc:(fun r -> r.technical.medium) 859 - |> Jsont.Object.opt_mem "data-type" Jsont.string ~enc:(fun r -> r.technical.data_type) 860 - |> Jsont.Object.opt_mem "database" Jsont.string ~enc:(fun r -> r.technical.database) 861 - |> Jsont.Object.opt_mem "number" string_or_int_jsont ~enc:(fun r -> r.technical.number) 862 - |> Jsont.Object.opt_mem "patent-states" string_list_jsont ~enc:(fun r -> 863 - r.technical.patent_states) 864 - |> Jsont.Object.opt_mem "thesis-type" Jsont.string ~enc:(fun r -> 865 - r.technical.thesis_type) 866 - |> Jsont.Object.opt_mem "term" Jsont.string ~enc:(fun r -> r.technical.term) 867 - |> Jsont.Object.opt_mem "entry" Jsont.string ~enc:(fun r -> r.technical.entry) 868 - |> Jsont.Object.opt_mem "department" Jsont.string ~enc:(fun r -> r.technical.department) 869 - |> Jsont.Object.opt_mem "loc-start" string_or_int_jsont ~enc:(fun r -> 870 - r.technical.loc_start) 871 - |> Jsont.Object.opt_mem "loc-end" string_or_int_jsont ~enc:(fun r -> 872 - r.technical.loc_end) 873 - |> Jsont.Object.skip_unknown 874 - |> Jsont.Object.finish 875 - ;; 796 + |> Json.Codec.Object.opt_member "commit" Json.Codec.string ~enc:(fun r -> 797 + r.technical.commit) 798 + |> Json.Codec.Object.opt_member "version" string_or_int_jsont ~enc:(fun r -> 799 + r.technical.version) 800 + |> Json.Codec.Object.opt_member "filename" Json.Codec.string ~enc:(fun r -> 801 + r.technical.filename) 802 + |> Json.Codec.Object.opt_member "format" Json.Codec.string ~enc:(fun r -> 803 + r.technical.format) 804 + |> Json.Codec.Object.opt_member "medium" Json.Codec.string ~enc:(fun r -> 805 + r.technical.medium) 806 + |> Json.Codec.Object.opt_member "data-type" Json.Codec.string ~enc:(fun r -> 807 + r.technical.data_type) 808 + |> Json.Codec.Object.opt_member "database" Json.Codec.string ~enc:(fun r -> 809 + r.technical.database) 810 + |> Json.Codec.Object.opt_member "number" string_or_int_jsont ~enc:(fun r -> 811 + r.technical.number) 812 + |> Json.Codec.Object.opt_member "patent-states" string_list_jsont 813 + ~enc:(fun r -> r.technical.patent_states) 814 + |> Json.Codec.Object.opt_member "thesis-type" Json.Codec.string ~enc:(fun r -> 815 + r.technical.thesis_type) 816 + |> Json.Codec.Object.opt_member "term" Json.Codec.string ~enc:(fun r -> 817 + r.technical.term) 818 + |> Json.Codec.Object.opt_member "entry" Json.Codec.string ~enc:(fun r -> 819 + r.technical.entry) 820 + |> Json.Codec.Object.opt_member "department" Json.Codec.string ~enc:(fun r -> 821 + r.technical.department) 822 + |> Json.Codec.Object.opt_member "loc-start" string_or_int_jsont ~enc:(fun r -> 823 + r.technical.loc_start) 824 + |> Json.Codec.Object.opt_member "loc-end" string_or_int_jsont ~enc:(fun r -> 825 + r.technical.loc_end) 826 + |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal
+218 -218
lib/cff_reference.mli
··· 6 6 (** Bibliographic reference type for CFF. 7 7 8 8 References represent citable works in the [references] and 9 - [preferred-citation] fields of a CFF file. They can describe any 10 - type of scholarly output: journal articles, books, conference papers, 11 - software, datasets, theses, patents, and many more. 9 + [preferred-citation] fields of a CFF file. They can describe any type of 10 + scholarly output: journal articles, books, conference papers, software, 11 + datasets, theses, patents, and many more. 12 12 13 13 {1 Structure} 14 14 15 - CFF references have 60+ possible fields. This module organizes them 16 - into logical sub-records for easier manipulation: 15 + CFF references have 60+ possible fields. This module organizes them into 16 + logical sub-records for easier manipulation: 17 17 18 18 - {!Core} - Required fields: type, title, authors 19 19 - {!Publication} - Journal articles: journal, volume, issue, pages ··· 26 26 27 27 {1 Reference Types} 28 28 29 - The [type] field determines what kind of work is being referenced. 30 - CFF 1.2.0 supports 40+ types including: 29 + The [type] field determines what kind of work is being referenced. CFF 1.2.0 30 + supports 40+ types including: 31 31 32 32 - Academic: [`Article], [`Book], [`Conference_paper], [`Thesis] 33 33 - Software: [`Software], [`Software_code], [`Software_container] ··· 71 71 72 72 (** Core identity fields (required for all references). 73 73 74 - Every reference must have a type, title, and at least one author. 75 - The type determines what additional fields are relevant. *) 74 + Every reference must have a type, title, and at least one author. The type 75 + determines what additional fields are relevant. *) 76 76 module Core : sig 77 77 type t 78 78 79 + val make : 80 + type_:Cff_enums.Reference_type.t -> 81 + title:string -> 82 + authors:Cff_author.t list -> 83 + ?abstract:string -> 84 + ?abbreviation:string -> 85 + unit -> 86 + t 79 87 (** Create a core record. 80 88 81 89 @param type_ The reference type (article, book, software, etc.) 82 90 @param title The title of the work 83 91 @param authors List of persons and/or entities *) 84 - val make 85 - : type_:Cff_enums.Reference_type.t 86 - -> title:string 87 - -> authors:Cff_author.t list 88 - -> ?abstract:string 89 - -> ?abbreviation:string 90 - -> unit 91 - -> t 92 92 93 - (** The reference type. Determines which other fields are applicable. *) 94 93 val type_ : t -> Cff_enums.Reference_type.t 94 + (** The reference type. Determines which other fields are applicable. *) 95 95 96 - (** The title of the referenced work. *) 97 96 val title : t -> string 97 + (** The title of the referenced work. *) 98 98 99 + val authors : t -> Cff_author.t list 99 100 (** The authors/creators of the work. *) 100 - val authors : t -> Cff_author.t list 101 101 102 + val abstract : t -> string option 102 103 (** A description or abstract of the work. *) 103 - val abstract : t -> string option 104 104 105 - (** Abbreviated form of the title (e.g., for journal names). *) 106 105 val abbreviation : t -> string option 106 + (** Abbreviated form of the title (e.g., for journal names). *) 107 107 108 108 val pp : Format.formatter -> t -> unit 109 109 end 110 110 111 111 (** Publication metadata for journal articles and periodicals. 112 112 113 - Fields for works published in journals, magazines, or other 114 - serial publications. Page numbers can be specified as a range 115 - ([pages]) or as separate [start] and [end_] values. *) 113 + Fields for works published in journals, magazines, or other serial 114 + publications. Page numbers can be specified as a range ([pages]) or as 115 + separate [start] and [end_] values. *) 116 116 module Publication : sig 117 117 type t 118 118 119 - (** Empty publication record with all fields as [None]. *) 120 119 val empty : t 120 + (** Empty publication record with all fields as [None]. *) 121 121 122 - val make 123 - : ?journal:string 124 - -> ?volume:string 125 - -> ?issue:string 126 - -> ?pages:string 127 - -> ?start:string 128 - -> ?end_:string 129 - -> ?edition:string 130 - -> ?section:string 131 - -> ?status:Cff_enums.Status.t 132 - -> unit 133 - -> t 122 + val make : 123 + ?journal:string -> 124 + ?volume:string -> 125 + ?issue:string -> 126 + ?pages:string -> 127 + ?start:string -> 128 + ?end_:string -> 129 + ?edition:string -> 130 + ?section:string -> 131 + ?status:Cff_enums.Status.t -> 132 + unit -> 133 + t 134 134 135 - (** The name of the journal or magazine. *) 136 135 val journal : t -> string option 136 + (** The name of the journal or magazine. *) 137 137 138 - (** The volume number of the journal. *) 139 138 val volume : t -> string option 139 + (** The volume number of the journal. *) 140 140 141 + val issue : t -> string option 141 142 (** The issue number within the volume. *) 142 - val issue : t -> string option 143 143 144 - (** Page range (e.g., ["123-145"]). Alternative to [start]/[end_]. *) 145 144 val pages : t -> string option 145 + (** Page range (e.g., ["123-145"]). Alternative to [start]/[end_]. *) 146 146 147 - (** Starting page number. *) 148 147 val start : t -> string option 148 + (** Starting page number. *) 149 149 150 - (** Ending page number. *) 151 150 val end_ : t -> string option 151 + (** Ending page number. *) 152 152 153 - (** The edition of the work (e.g., ["2nd edition"]). *) 154 153 val edition : t -> string option 154 + (** The edition of the work (e.g., ["2nd edition"]). *) 155 155 156 - (** The section of a work (e.g., newspaper section). *) 157 156 val section : t -> string option 157 + (** The section of a work (e.g., newspaper section). *) 158 158 159 + val status : t -> Cff_enums.Status.t option 159 160 (** Publication status: preprint, in-press, submitted, etc. *) 160 - val status : t -> Cff_enums.Status.t option 161 161 162 + val is_empty : t -> bool 162 163 (** [true] if all fields are [None]. *) 163 - val is_empty : t -> bool 164 164 end 165 165 166 166 (** Collection metadata for works in edited volumes. 167 167 168 - Used for book chapters, conference proceedings, and other works 169 - that appear within a larger collection. *) 168 + Used for book chapters, conference proceedings, and other works that appear 169 + within a larger collection. *) 170 170 module Collection : sig 171 171 type t 172 172 173 173 val empty : t 174 174 175 - val make 176 - : ?collection_title:string 177 - -> ?collection_type:string 178 - -> ?collection_doi:string 179 - -> ?volume_title:string 180 - -> ?number_volumes:string 181 - -> unit 182 - -> t 175 + val make : 176 + ?collection_title:string -> 177 + ?collection_type:string -> 178 + ?collection_doi:string -> 179 + ?volume_title:string -> 180 + ?number_volumes:string -> 181 + unit -> 182 + t 183 183 184 - (** Title of the collection (proceedings, book series, etc.). *) 185 184 val collection_title : t -> string option 185 + (** Title of the collection (proceedings, book series, etc.). *) 186 186 187 - (** Type of collection (e.g., ["proceedings"], ["book series"]). *) 188 187 val collection_type : t -> string option 188 + (** Type of collection (e.g., ["proceedings"], ["book series"]). *) 189 189 190 - (** DOI of the collection itself (not the individual work). *) 191 190 val collection_doi : t -> string option 191 + (** DOI of the collection itself (not the individual work). *) 192 192 193 - (** Title of the specific volume within a multi-volume collection. *) 194 193 val volume_title : t -> string option 194 + (** Title of the specific volume within a multi-volume collection. *) 195 195 196 + val number_volumes : t -> string option 196 197 (** Total number of volumes in the collection. *) 197 - val number_volumes : t -> string option 198 198 199 199 val is_empty : t -> bool 200 200 end ··· 207 207 - {b date-accessed}: When an online resource was last accessed 208 208 - {b date-downloaded}: When a resource was downloaded 209 209 210 - For older works or when only the year is known, use [year] instead 211 - of a full date. *) 210 + For older works or when only the year is known, use [year] instead of a full 211 + date. *) 212 212 module Dates : sig 213 213 type t 214 214 215 215 val empty : t 216 216 217 - val make 218 - : ?date_accessed:Cff_date.t 219 - -> ?date_downloaded:Cff_date.t 220 - -> ?date_published:Cff_date.t 221 - -> ?date_released:Cff_date.t 222 - -> ?year:int 223 - -> ?year_original:int 224 - -> ?month:int 225 - -> ?issue_date:string 226 - -> unit 227 - -> t 217 + val make : 218 + ?date_accessed:Cff_date.t -> 219 + ?date_downloaded:Cff_date.t -> 220 + ?date_published:Cff_date.t -> 221 + ?date_released:Cff_date.t -> 222 + ?year:int -> 223 + ?year_original:int -> 224 + ?month:int -> 225 + ?issue_date:string -> 226 + unit -> 227 + t 228 228 229 - (** Date when an online resource was accessed for citation. *) 230 229 val date_accessed : t -> Cff_date.t option 230 + (** Date when an online resource was accessed for citation. *) 231 231 232 + val date_downloaded : t -> Cff_date.t option 232 233 (** Date when a resource was downloaded. *) 233 - val date_downloaded : t -> Cff_date.t option 234 234 235 + val date_published : t -> Cff_date.t option 235 236 (** Formal publication date. *) 236 - val date_published : t -> Cff_date.t option 237 237 238 - (** Release date (typically for software). *) 239 238 val date_released : t -> Cff_date.t option 239 + (** Release date (typically for software). *) 240 240 241 - (** Publication year when full date is unknown. *) 242 241 val year : t -> int option 242 + (** Publication year when full date is unknown. *) 243 243 244 + val year_original : t -> int option 244 245 (** Year of original publication (for reprints, translations). *) 245 - val year_original : t -> int option 246 246 247 - (** Publication month (1-12) when only month/year is known. *) 248 247 val month : t -> int option 248 + (** Publication month (1-12) when only month/year is known. *) 249 249 250 - (** Issue date as a string (for periodicals with specific dates). *) 251 250 val issue_date : t -> string option 251 + (** Issue date as a string (for periodicals with specific dates). *) 252 252 253 253 val is_empty : t -> bool 254 254 end ··· 267 267 268 268 val empty : t 269 269 270 - val make 271 - : ?doi:string 272 - -> ?url:string 273 - -> ?repository:string 274 - -> ?repository_code:string 275 - -> ?repository_artifact:string 276 - -> ?isbn:string 277 - -> ?issn:string 278 - -> ?pmcid:string 279 - -> ?nihmsid:string 280 - -> ?identifiers:Cff_identifier.t list 281 - -> unit 282 - -> t 270 + val make : 271 + ?doi:string -> 272 + ?url:string -> 273 + ?repository:string -> 274 + ?repository_code:string -> 275 + ?repository_artifact:string -> 276 + ?isbn:string -> 277 + ?issn:string -> 278 + ?pmcid:string -> 279 + ?nihmsid:string -> 280 + ?identifiers:Cff_identifier.t list -> 281 + unit -> 282 + t 283 283 284 + val doi : t -> string option 284 285 (** Digital Object Identifier (e.g., ["10.1234/example"]). *) 285 - val doi : t -> string option 286 286 287 + val url : t -> string option 287 288 (** URL where the work can be accessed. *) 288 - val url : t -> string option 289 289 290 - (** General repository URL. *) 291 290 val repository : t -> string option 291 + (** General repository URL. *) 292 292 293 - (** Source code repository (GitHub, GitLab, etc.). *) 294 293 val repository_code : t -> string option 294 + (** Source code repository (GitHub, GitLab, etc.). *) 295 295 296 - (** Built artifact repository (npm, PyPI, Docker Hub, etc.). *) 297 296 val repository_artifact : t -> string option 297 + (** Built artifact repository (npm, PyPI, Docker Hub, etc.). *) 298 298 299 + val isbn : t -> string option 299 300 (** International Standard Book Number. *) 300 - val isbn : t -> string option 301 301 302 - (** International Standard Serial Number (for journals). *) 303 302 val issn : t -> string option 303 + (** International Standard Serial Number (for journals). *) 304 304 305 + val pmcid : t -> string option 305 306 (** PubMed Central identifier. *) 306 - val pmcid : t -> string option 307 307 308 + val nihmsid : t -> string option 308 309 (** NIH Manuscript Submission System identifier. *) 309 - val nihmsid : t -> string option 310 310 311 - (** Additional typed identifiers (DOI, URL, SWH, other). *) 312 311 val identifiers : t -> Cff_identifier.t list option 312 + (** Additional typed identifiers (DOI, URL, SWH, other). *) 313 313 314 314 val is_empty : t -> bool 315 315 end ··· 326 326 327 327 val empty : t 328 328 329 - val make 330 - : ?editors:Cff_author.t list 331 - -> ?editors_series:Cff_author.t list 332 - -> ?translators:Cff_author.t list 333 - -> ?recipients:Cff_author.t list 334 - -> ?senders:Cff_author.t list 335 - -> ?contact:Cff_author.t list 336 - -> ?publisher:Cff_author.Entity.t 337 - -> ?institution:Cff_author.Entity.t 338 - -> ?conference:Cff_author.Entity.t 339 - -> ?database_provider:Cff_author.Entity.t 340 - -> ?location:Cff_author.Entity.t 341 - -> unit 342 - -> t 329 + val make : 330 + ?editors:Cff_author.t list -> 331 + ?editors_series:Cff_author.t list -> 332 + ?translators:Cff_author.t list -> 333 + ?recipients:Cff_author.t list -> 334 + ?senders:Cff_author.t list -> 335 + ?contact:Cff_author.t list -> 336 + ?publisher:Cff_author.Entity.t -> 337 + ?institution:Cff_author.Entity.t -> 338 + ?conference:Cff_author.Entity.t -> 339 + ?database_provider:Cff_author.Entity.t -> 340 + ?location:Cff_author.Entity.t -> 341 + unit -> 342 + t 343 343 344 + val editors : t -> Cff_author.t list option 344 345 (** Editors of the work (for edited volumes). *) 345 - val editors : t -> Cff_author.t list option 346 346 347 + val editors_series : t -> Cff_author.t list option 347 348 (** Series editors (for book series). *) 348 - val editors_series : t -> Cff_author.t list option 349 349 350 - (** Translators of the work. *) 351 350 val translators : t -> Cff_author.t list option 351 + (** Translators of the work. *) 352 352 353 - (** Recipients (for personal communications). *) 354 353 val recipients : t -> Cff_author.t list option 354 + (** Recipients (for personal communications). *) 355 355 356 - (** Senders (for personal communications). *) 357 356 val senders : t -> Cff_author.t list option 357 + (** Senders (for personal communications). *) 358 358 359 - (** Contact persons for the work. *) 360 359 val contact : t -> Cff_author.t list option 360 + (** Contact persons for the work. *) 361 361 362 - (** Publishing organization. *) 363 362 val publisher : t -> Cff_author.Entity.t option 363 + (** Publishing organization. *) 364 364 365 + val institution : t -> Cff_author.Entity.t option 365 366 (** Academic/research institution (for theses, reports). *) 366 - val institution : t -> Cff_author.Entity.t option 367 367 368 + val conference : t -> Cff_author.Entity.t option 368 369 (** Conference where the work was presented. *) 369 - val conference : t -> Cff_author.Entity.t option 370 370 371 - (** Provider of a database (for data references). *) 372 371 val database_provider : t -> Cff_author.Entity.t option 372 + (** Provider of a database (for data references). *) 373 373 374 - (** Location entity (city, venue for conferences). *) 375 374 val location : t -> Cff_author.Entity.t option 375 + (** Location entity (city, venue for conferences). *) 376 376 377 377 val is_empty : t -> bool 378 378 end ··· 385 385 386 386 val empty : t 387 387 388 - val make 389 - : ?keywords:string list 390 - -> ?languages:string list 391 - -> ?license:Cff_license.t 392 - -> ?copyright:string 393 - -> ?scope:string 394 - -> ?notes:string 395 - -> unit 396 - -> t 388 + val make : 389 + ?keywords:string list -> 390 + ?languages:string list -> 391 + ?license:Cff_license.t -> 392 + ?copyright:string -> 393 + ?scope:string -> 394 + ?notes:string -> 395 + unit -> 396 + t 397 397 398 - (** Descriptive keywords for the work. *) 399 398 val keywords : t -> string list option 399 + (** Descriptive keywords for the work. *) 400 400 401 - (** Languages the work is available in (ISO 639 codes). *) 402 401 val languages : t -> string list option 402 + (** Languages the work is available in (ISO 639 codes). *) 403 403 404 + val license : t -> Cff_license.t option 404 405 (** SPDX license identifier(s), or unknown license with optional URL. *) 405 - val license : t -> Cff_license.t option 406 406 407 - (** Copyright statement. *) 408 407 val copyright : t -> string option 408 + (** Copyright statement. *) 409 409 410 + val scope : t -> string option 410 411 (** Scope of the reference (what aspect it covers). *) 411 - val scope : t -> string option 412 412 413 + val notes : t -> string option 413 414 (** Additional notes or comments. *) 414 - val notes : t -> string option 415 415 416 416 val is_empty : t -> bool 417 417 end ··· 429 429 430 430 val empty : t 431 431 432 - val make 433 - : ?commit:string 434 - -> ?version:string 435 - -> ?filename:string 436 - -> ?format:string 437 - -> ?medium:string 438 - -> ?data_type:string 439 - -> ?database:string 440 - -> ?number:string 441 - -> ?patent_states:string list 442 - -> ?thesis_type:string 443 - -> ?term:string 444 - -> ?entry:string 445 - -> ?department:string 446 - -> ?loc_start:string 447 - -> ?loc_end:string 448 - -> unit 449 - -> t 432 + val make : 433 + ?commit:string -> 434 + ?version:string -> 435 + ?filename:string -> 436 + ?format:string -> 437 + ?medium:string -> 438 + ?data_type:string -> 439 + ?database:string -> 440 + ?number:string -> 441 + ?patent_states:string list -> 442 + ?thesis_type:string -> 443 + ?term:string -> 444 + ?entry:string -> 445 + ?department:string -> 446 + ?loc_start:string -> 447 + ?loc_end:string -> 448 + unit -> 449 + t 450 450 451 + val commit : t -> string option 451 452 (** Git commit hash or VCS revision. *) 452 - val commit : t -> string option 453 453 454 + val version : t -> string option 454 455 (** Version string of the software/data. *) 455 - val version : t -> string option 456 456 457 - (** Name of the file being referenced. *) 458 457 val filename : t -> string option 458 + (** Name of the file being referenced. *) 459 459 460 - (** Format of the work (e.g., ["PDF"], ["HTML"]). *) 461 460 val format : t -> string option 461 + (** Format of the work (e.g., ["PDF"], ["HTML"]). *) 462 462 463 - (** Physical medium (e.g., ["CD-ROM"], ["print"]). *) 464 463 val medium : t -> string option 464 + (** Physical medium (e.g., ["CD-ROM"], ["print"]). *) 465 465 466 - (** Type of data (for datasets). *) 467 466 val data_type : t -> string option 467 + (** Type of data (for datasets). *) 468 468 469 - (** Name of the database. *) 470 469 val database : t -> string option 470 + (** Name of the database. *) 471 471 472 + val number : t -> string option 472 473 (** Report/patent/standard number. *) 473 - val number : t -> string option 474 474 475 + val patent_states : t -> string list option 475 476 (** Countries where a patent is held. *) 476 - val patent_states : t -> string list option 477 477 478 - (** Type of thesis (["PhD"], ["Master's"], etc.). *) 479 478 val thesis_type : t -> string option 479 + (** Type of thesis (["PhD"], ["Master's"], etc.). *) 480 480 481 - (** Dictionary/encyclopedia term being referenced. *) 482 481 val term : t -> string option 482 + (** Dictionary/encyclopedia term being referenced. *) 483 483 484 - (** Encyclopedia entry name. *) 485 484 val entry : t -> string option 485 + (** Encyclopedia entry name. *) 486 486 487 + val department : t -> string option 487 488 (** Academic department (for theses). *) 488 - val department : t -> string option 489 489 490 - (** Starting line/location in source code. *) 491 490 val loc_start : t -> string option 491 + (** Starting line/location in source code. *) 492 492 493 + val loc_end : t -> string option 493 494 (** Ending line/location in source code. *) 494 - val loc_end : t -> string option 495 495 496 496 val is_empty : t -> bool 497 497 end 498 498 499 499 (** {1 Reference Type} *) 500 500 501 - (** The complete reference type combining all sub-records. *) 502 501 type t 502 + (** The complete reference type combining all sub-records. *) 503 503 504 + val make : 505 + core:Core.t -> 506 + ?publication:Publication.t -> 507 + ?collection:Collection.t -> 508 + ?dates:Dates.t -> 509 + ?identifiers:Identifiers.t -> 510 + ?entities:Entities.t -> 511 + ?metadata:Metadata.t -> 512 + ?technical:Technical.t -> 513 + unit -> 514 + t 504 515 (** Construct a reference from sub-records. 505 516 506 517 Only [core] is required; other sub-records default to empty. *) 507 - val make 508 - : core:Core.t 509 - -> ?publication:Publication.t 510 - -> ?collection:Collection.t 511 - -> ?dates:Dates.t 512 - -> ?identifiers:Identifiers.t 513 - -> ?entities:Entities.t 514 - -> ?metadata:Metadata.t 515 - -> ?technical:Technical.t 516 - -> unit 517 - -> t 518 518 519 + val make_simple : 520 + type_:Cff_enums.Reference_type.t -> 521 + title:string -> 522 + authors:Cff_author.t list -> 523 + ?doi:string -> 524 + ?year:int -> 525 + ?journal:string -> 526 + unit -> 527 + t 519 528 (** Convenience constructor for simple references. 520 529 521 - Creates a reference with just the most common fields. Suitable 522 - for quick article or software references. *) 523 - val make_simple 524 - : type_:Cff_enums.Reference_type.t 525 - -> title:string 526 - -> authors:Cff_author.t list 527 - -> ?doi:string 528 - -> ?year:int 529 - -> ?journal:string 530 - -> unit 531 - -> t 530 + Creates a reference with just the most common fields. Suitable for quick 531 + article or software references. *) 532 532 533 533 (** {2 Sub-record Accessors} *) 534 534 535 - (** The core identity fields. *) 536 535 val core : t -> Core.t 536 + (** The core identity fields. *) 537 537 538 - (** Publication metadata (journal, volume, pages). *) 539 538 val publication : t -> Publication.t 539 + (** Publication metadata (journal, volume, pages). *) 540 540 541 - (** Collection metadata (proceedings, book series). *) 542 541 val collection : t -> Collection.t 542 + (** Collection metadata (proceedings, book series). *) 543 543 544 + val dates : t -> Dates.t 544 545 (** Date-related fields. *) 545 - val dates : t -> Dates.t 546 546 547 + val identifiers : t -> Identifiers.t 547 548 (** Identifiers and links. *) 548 - val identifiers : t -> Identifiers.t 549 549 550 - (** Related entities (editors, publisher). *) 551 550 val entities : t -> Entities.t 551 + (** Related entities (editors, publisher). *) 552 552 553 - (** Descriptive metadata (keywords, license). *) 554 553 val metadata : t -> Metadata.t 554 + (** Descriptive metadata (keywords, license). *) 555 555 556 - (** Technical fields (commit, version, format). *) 557 556 val technical : t -> Technical.t 557 + (** Technical fields (commit, version, format). *) 558 558 559 559 (** {2 Direct Accessors for Common Fields} 560 560 561 561 Convenience accessors that delegate to sub-records. *) 562 562 563 + val type_ : t -> Cff_enums.Reference_type.t 563 564 (** Shortcut for [Core.type_ (core t)]. *) 564 - val type_ : t -> Cff_enums.Reference_type.t 565 565 566 - (** Shortcut for [Core.title (core t)]. *) 567 566 val title : t -> string 567 + (** Shortcut for [Core.title (core t)]. *) 568 568 569 + val authors : t -> Cff_author.t list 569 570 (** Shortcut for [Core.authors (core t)]. *) 570 - val authors : t -> Cff_author.t list 571 571 572 + val doi : t -> string option 572 573 (** Shortcut for [Identifiers.doi (identifiers t)]. *) 573 - val doi : t -> string option 574 574 575 - (** Shortcut for [Dates.year (dates t)]. *) 576 575 val year : t -> int option 576 + (** Shortcut for [Dates.year (dates t)]. *) 577 577 578 578 (** {1 Formatting and Codec} *) 579 579 580 - (** Pretty-print a reference in a human-readable format. *) 581 580 val pp : Format.formatter -> t -> unit 581 + (** Pretty-print a reference in a human-readable format. *) 582 582 583 + val jsont : t Json.Codec.t 583 584 (** JSON/YAML codec for serialization. *) 584 - val jsont : t Jsont.t
+1 -1
lib/dune
··· 1 1 (library 2 2 (name cff) 3 3 (public_name cff) 4 - (libraries ptime ISO3166 spdx_licenses jsont)) 4 + (libraries ptime ISO3166 spdx_licenses nox-json))
+22 -28
lib_eio/cff_eio.ml
··· 11 11 let () = 12 12 Eio.Exn.register_pp (fun f -> function 13 13 | E msg -> 14 - Format.fprintf f "Cff %s" msg; 15 - true 14 + Format.fprintf f "Cff %s" msg; 15 + true 16 16 | _ -> false) 17 - ;; 18 17 19 18 let err msg = Eio.Exn.create (E msg) 19 + let codec = Yaml_json.of_json Cff.jsont 20 + 21 + let single_doc = function 22 + | [ cff ] -> cff 23 + | [] -> raise (err "expected one YAML document, got empty stream") 24 + | _ -> raise (err "expected one YAML document, got multiple") 20 25 21 26 let of_yaml_string s = 22 - let reader = Bytesrw.Bytes.Reader.of_string s in 23 - match Yamlt.decode ~layout:true Cff.jsont reader with 24 - | Ok cff -> cff 25 - | Error msg -> raise (err msg) 26 - ;; 27 + match Yaml.of_string codec s with 28 + | Ok docs -> single_doc docs 29 + | Error e -> raise (err (Format.asprintf "%a" Yaml.Error.pp e)) 27 30 28 31 let to_yaml_string t = 29 - let buf = Buffer.create 1024 in 30 - let writer = Bytesrw.Bytes.Writer.of_buffer buf in 31 - match Yamlt.encode ~format:Yamlt.Block Cff.jsont t ~eod:true writer with 32 - | Ok () -> Buffer.contents buf 33 - | Error msg -> raise (err msg) 34 - ;; 32 + try Yaml.to_string ~indent:2 codec [ t ] 33 + with Yaml.Error e -> raise (err (Format.asprintf "%a" Yaml.Error.pp e)) 35 34 36 35 let of_yaml_flow flow = 37 - let reader = Bytesrw_eio.bytes_reader_of_flow flow in 38 - match Yamlt.decode ~layout:true Cff.jsont reader with 39 - | Ok cff -> cff 40 - | Error msg -> raise (err msg) 41 - ;; 36 + let r = Bytesrw_eio.bytes_reader_of_flow flow in 37 + match Yaml_bytesrw.of_reader codec r with 38 + | Ok docs -> single_doc docs 39 + | Error e -> raise (err (Format.asprintf "%a" Yaml.Error.pp e)) 42 40 43 41 let to_yaml_flow flow t = 44 - let writer = Bytesrw_eio.bytes_writer_of_flow flow in 45 - match Yamlt.encode ~format:Yamlt.Block Cff.jsont t ~eod:true writer with 46 - | Ok () -> () 47 - | Error msg -> raise (err msg) 48 - ;; 42 + let w = Bytesrw_eio.bytes_writer_of_flow flow in 43 + try Yaml_bytesrw.to_writer ~indent:2 codec [ t ] w 44 + with Yaml.Error e -> raise (err (Format.asprintf "%a" Yaml.Error.pp e)) 49 45 50 46 let of_file ~fs path = 51 47 let data = Eio.Path.load Eio.Path.(fs / path) in 52 - try of_yaml_string data with 53 - | Eio.Exn.Io _ as ex -> 48 + try of_yaml_string data 49 + with Eio.Exn.Io _ as ex -> 54 50 let bt = Printexc.get_raw_backtrace () in 55 51 Eio.Exn.reraise_with_context ex bt "parsing CFF file %S" path 56 - ;; 57 52 58 53 let to_file ~fs path t = 59 54 let data = to_yaml_string t in 60 55 Eio.Path.save ~create:(`Or_truncate 0o644) Eio.Path.(fs / path) data 61 - ;;
+22 -20
lib_eio/cff_eio.mli
··· 8 8 This module provides YAML parsing and serialization for CFF using 9 9 {{:https://github.com/ocaml-multicore/eio}Eio} for effect-based I/O. 10 10 11 - All functions raise {!Eio.Exn.Io} on errors, with context indicating 12 - that CFF parsing/encoding failed. This integrates naturally with Eio's 13 - error handling conventions. 11 + All functions raise {!Eio.Exn.Io} on errors, with context indicating that 12 + CFF parsing/encoding failed. This integrates naturally with Eio's error 13 + handling conventions. 14 14 15 15 {1 Example} 16 16 17 17 {[ 18 - Eio_main.run @@ fun env -> 19 - let fs = Eio.Stdenv.fs env in 20 - let cff = Cff_eio.of_file ~fs "CITATION.cff" in 21 - Eio.traceln "Title: %s" (Cff.title cff); 22 - Eio.traceln "Version: %s" 23 - (Option.value ~default:"unspecified" (Cff.version cff)) 18 + Eio_main.run @@ fun env -> 19 + let fs = Eio.Stdenv.fs env in 20 + let cff = Cff_eio.of_file ~fs "CITATION.cff" in 21 + Eio.traceln "Title: %s" (Cff.title cff); 22 + Eio.traceln "Version: %s" 23 + (Option.value ~default:"unspecified" (Cff.version cff)) 24 24 ]} 25 25 26 26 {1 Errors} 27 27 28 - Parsing and encoding errors are raised as {!Eio.Exn.Io} exceptions 29 - with the error type {!E}. *) 28 + Parsing and encoding errors are raised as {!Eio.Exn.Io} exceptions with the 29 + error type {!E}. *) 30 30 31 31 type Eio.Exn.err += 32 32 | E of string 33 - (** CFF parsing or encoding error. The string contains the error message. *) 33 + (** CFF parsing or encoding error. The string contains the error 34 + message. *) 34 35 35 36 (** {1 String Functions} *) 36 37 38 + val of_yaml_string : string -> Cff.t 37 39 (** [of_yaml_string s] parses a CFF from YAML string [s]. 38 40 39 41 @raise Eio.Exn.Io on parse error. *) 40 - val of_yaml_string : string -> Cff.t 41 42 43 + val to_yaml_string : Cff.t -> string 42 44 (** [to_yaml_string cff] serializes [cff] to a YAML string. 43 45 44 46 The output uses YAML block style for readability. 45 47 46 48 @raise Eio.Exn.Io on encoding error. *) 47 - val to_yaml_string : Cff.t -> string 48 49 49 50 (** {1 Flow Functions} *) 50 51 52 + val of_yaml_flow : _ Eio.Flow.source -> Cff.t 51 53 (** [of_yaml_flow flow] parses a CFF from an Eio source flow. 52 54 53 55 Reads directly from the flow using bytesrw-eio. 54 56 55 57 @raise Eio.Exn.Io on parse error. *) 56 - val of_yaml_flow : _ Eio.Flow.source -> Cff.t 57 58 59 + val to_yaml_flow : _ Eio.Flow.sink -> Cff.t -> unit 58 60 (** [to_yaml_flow flow cff] serializes [cff] to an Eio sink flow. 59 61 60 62 Writes directly to the flow using bytesrw-eio. 61 63 62 64 @raise Eio.Exn.Io on encoding error. *) 63 - val to_yaml_flow : _ Eio.Flow.sink -> Cff.t -> unit 64 65 65 66 (** {1 File Functions} *) 66 67 68 + val of_file : fs:_ Eio.Path.t -> string -> Cff.t 67 69 (** [of_file ~fs path] reads and parses a [CITATION.cff] file. 68 70 69 71 @param fs The Eio filesystem (e.g., [Eio.Stdenv.fs env]) 70 72 @param path Path to the CFF file 71 - @raise Eio.Exn.Io if the file cannot be read or contains invalid CFF data. 72 - The exception context includes the file path. *) 73 - val of_file : fs:_ Eio.Path.t -> string -> Cff.t 73 + @raise Eio.Exn.Io 74 + if the file cannot be read or contains invalid CFF data. The exception 75 + context includes the file path. *) 74 76 77 + val to_file : fs:_ Eio.Path.t -> string -> Cff.t -> unit 75 78 (** [to_file ~fs path cff] writes [cff] to a file at [path]. 76 79 77 80 Creates or overwrites the file. ··· 79 82 @param fs The Eio filesystem (e.g., [Eio.Stdenv.fs env]) 80 83 @param path Path to write the CFF file 81 84 @raise Eio.Exn.Io on I/O or encoding failure. *) 82 - val to_file : fs:_ Eio.Path.t -> string -> Cff.t -> unit
+1 -1
lib_eio/dune
··· 1 1 (library 2 2 (name cff_eio) 3 3 (public_name cff.eio) 4 - (libraries cff yamlt bytesrw bytesrw-eio eio)) 4 + (libraries cff nox-yaml nox-yaml.json nox-yaml.bytesrw bytesrw-eio eio))
+16 -15
lib_unix/cff_unix.ml
··· 5 5 6 6 (** Unix file I/O for CFF. *) 7 7 8 + let codec = Yaml_json.of_json Cff.jsont 9 + 8 10 let of_yaml_string s = 9 - let reader = Bytesrw.Bytes.Reader.of_string s in 10 - Yamlt.decode ~layout:true Cff.jsont reader 11 - ;; 11 + match Yaml.of_string codec s with 12 + | Ok [ cff ] -> Ok cff 13 + | Ok [] -> Error "expected one YAML document, got empty stream" 14 + | Ok _ -> Error "expected one YAML document, got multiple" 15 + | Error e -> Error (Format.asprintf "%a" Yaml.Error.pp e) 12 16 13 17 let to_yaml_string t = 14 - let buf = Buffer.create 1024 in 15 - let writer = Bytesrw.Bytes.Writer.of_buffer buf in 16 - match Yamlt.encode ~format:Yamlt.Block Cff.jsont t ~eod:true writer with 17 - | Ok () -> Ok (Buffer.contents buf) 18 - | Error e -> Error e 19 - ;; 18 + try Ok (Yaml.to_string ~indent:2 codec [ t ]) 19 + with Yaml.Error e -> Error (Format.asprintf "%a" Yaml.Error.pp e) 20 20 21 21 let of_file path = 22 22 match In_channel.with_open_text path In_channel.input_all with 23 23 | s -> of_yaml_string s 24 24 | exception Sys_error e -> Error e 25 - ;; 26 25 27 26 let to_file path t = 28 27 match to_yaml_string t with 29 28 | Error e -> Error e 30 - | Ok s -> 31 - (match Out_channel.with_open_text path (fun oc -> Out_channel.output_string oc s) with 32 - | () -> Ok () 33 - | exception Sys_error e -> Error e) 34 - ;; 29 + | Ok s -> ( 30 + match 31 + Out_channel.with_open_text path (fun oc -> 32 + Out_channel.output_string oc s) 33 + with 34 + | () -> Ok () 35 + | exception Sys_error e -> Error e)
+13 -14
lib_unix/cff_unix.mli
··· 5 5 6 6 (** Unix file I/O for CFF. 7 7 8 - This module provides YAML parsing and serialization for CFF using 9 - standard Unix file operations ({!In_channel}, {!Out_channel}). 8 + This module provides YAML parsing and serialization for CFF using standard 9 + Unix file operations ({!In_channel}, {!Out_channel}). 10 10 11 11 {1 Example} 12 12 13 13 {[ 14 - match Cff_unix.of_file "CITATION.cff" with 15 - | Ok cff -> 14 + match Cff_unix.of_file "CITATION.cff" with 15 + | Ok cff -> 16 16 Printf.printf "Title: %s\n" (Cff.title cff); 17 17 Printf.printf "Version: %s\n" 18 18 (Option.value ~default:"unspecified" (Cff.version cff)) 19 - | Error msg -> 20 - Printf.eprintf "Parse error: %s\n" msg 19 + | Error msg -> Printf.eprintf "Parse error: %s\n" msg 21 20 ]} 22 21 23 22 {1 Functions} *) 24 23 24 + val of_yaml_string : string -> (Cff.t, string) result 25 25 (** [of_yaml_string s] parses a CFF from YAML string [s]. 26 26 27 - Returns [Ok cff] on success or [Error msg] with a descriptive error 28 - message on failure. *) 29 - val of_yaml_string : string -> (Cff.t, string) result 27 + Returns [Ok cff] on success or [Error msg] with a descriptive error message 28 + on failure. *) 30 29 30 + val to_yaml_string : Cff.t -> (string, string) result 31 31 (** [to_yaml_string cff] serializes [cff] to a YAML string. 32 32 33 33 The output uses YAML block style for readability. *) 34 - val to_yaml_string : Cff.t -> (string, string) result 35 34 35 + val of_file : string -> (Cff.t, string) result 36 36 (** [of_file path] reads and parses a [CITATION.cff] file. 37 37 38 - Returns [Ok cff] on success or [Error msg] if the file cannot be 39 - read or contains invalid CFF data. *) 40 - val of_file : string -> (Cff.t, string) result 38 + Returns [Ok cff] on success or [Error msg] if the file cannot be read or 39 + contains invalid CFF data. *) 41 40 41 + val to_file : string -> Cff.t -> (unit, string) result 42 42 (** [to_file path cff] writes [cff] to a file at [path]. 43 43 44 44 Creates or overwrites the file. Returns [Error msg] on I/O failure. *) 45 - val to_file : string -> Cff.t -> (unit, string) result
+1 -1
lib_unix/dune
··· 1 1 (library 2 2 (name cff_unix) 3 3 (public_name cff.unix) 4 - (libraries cff yamlt bytesrw)) 4 + (libraries cff nox-yaml nox-yaml.json))
+2
test/dune
··· 1 1 (test 2 2 (name test_cff) 3 + (modules test_cff) 3 4 (package cff) 4 5 (libraries cff cff.unix alcotest) 5 6 (deps ··· 7 8 8 9 (test 9 10 (name test_cff_eio) 11 + (modules test_cff_eio) 10 12 (package cff) 11 13 (libraries cff cff.eio alcotest eio_main) 12 14 (deps
+214 -212
test/test_cff.ml
··· 14 14 - family-names: Haines 15 15 given-names: Robert 16 16 |} 17 - ;; 18 17 19 18 let simple_cff = 20 19 {| ··· 29 28 doi: 10.5281/zenodo.1234567 30 29 date-released: 2021-08-11 31 30 |} 32 - ;; 33 31 34 32 let test_parse_minimal () = 35 33 match Cff_unix.of_yaml_string minimal_cff with 36 34 | Ok cff -> 37 - Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 38 - Alcotest.(check string) "title" "Ruby CFF Library" (Cff.title cff); 39 - Alcotest.(check int) "authors count" 1 (List.length (Cff.authors cff)) 40 - | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse minimal CFF: %s" e) 41 - ;; 35 + Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 36 + Alcotest.(check string) "title" "Ruby CFF Library" (Cff.title cff); 37 + Alcotest.(check int) "authors count" 1 (List.length (Cff.authors cff)) 38 + | Error e -> 39 + Alcotest.fail (Printf.sprintf "Failed to parse minimal CFF: %s" e) 42 40 43 41 let test_parse_simple () = 44 42 match Cff_unix.of_yaml_string simple_cff with 45 - | Ok cff -> 46 - Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 47 - Alcotest.(check string) "title" "My Research Software" (Cff.title cff); 48 - Alcotest.(check (option string)) "version" (Some "1.0.0") (Cff.version cff); 49 - Alcotest.(check (option string)) "doi" (Some "10.5281/zenodo.1234567") (Cff.doi cff); 50 - (match Cff.date_released cff with 51 - | Some (2021, 8, 11) -> () 52 - | Some d -> Alcotest.fail (Printf.sprintf "Wrong date: %s" (Cff.Date.to_string d)) 53 - | None -> Alcotest.fail "Missing date-released") 43 + | Ok cff -> ( 44 + Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 45 + Alcotest.(check string) "title" "My Research Software" (Cff.title cff); 46 + Alcotest.(check (option string)) 47 + "version" (Some "1.0.0") (Cff.version cff); 48 + Alcotest.(check (option string)) 49 + "doi" (Some "10.5281/zenodo.1234567") (Cff.doi cff); 50 + match Cff.date_released cff with 51 + | Some (2021, 8, 11) -> () 52 + | Some d -> 53 + Alcotest.fail (Printf.sprintf "Wrong date: %s" (Cff.Date.to_string d)) 54 + | None -> Alcotest.fail "Missing date-released") 54 55 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse simple CFF: %s" e) 55 - ;; 56 56 57 57 let test_create_programmatic () = 58 58 let author = Cff.Author.person ~family_names:"Smith" ~given_names:"Jane" () in 59 - let cff = Cff.make ~title:"My Software" ~authors:[ author ] ~version:"1.0.0" () in 59 + let cff = 60 + Cff.make ~title:"My Software" ~authors:[ author ] ~version:"1.0.0" () 61 + in 60 62 Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 61 63 Alcotest.(check string) "title" "My Software" (Cff.title cff); 62 64 Alcotest.(check (option string)) "version" (Some "1.0.0") (Cff.version cff) 63 - ;; 64 65 65 66 let test_roundtrip () = 66 67 match Cff_unix.of_yaml_string simple_cff with 67 68 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse: %s" e) 68 - | Ok cff1 -> 69 - (match Cff_unix.to_yaml_string cff1 with 70 - | Error e -> Alcotest.fail (Printf.sprintf "Failed to encode: %s" e) 71 - | Ok yaml -> 72 - (match Cff_unix.of_yaml_string yaml with 73 - | Error e -> Alcotest.fail (Printf.sprintf "Failed to reparse: %s" e) 74 - | Ok cff2 -> 75 - Alcotest.(check string) "title preserved" (Cff.title cff1) (Cff.title cff2); 76 - Alcotest.(check string) 77 - "cff-version preserved" 78 - (Cff.cff_version cff1) 79 - (Cff.cff_version cff2))) 80 - ;; 69 + | Ok cff1 -> ( 70 + match Cff_unix.to_yaml_string cff1 with 71 + | Error e -> Alcotest.fail (Printf.sprintf "Failed to encode: %s" e) 72 + | Ok yaml -> ( 73 + match Cff_unix.of_yaml_string yaml with 74 + | Error e -> Alcotest.fail (Printf.sprintf "Failed to reparse: %s" e) 75 + | Ok cff2 -> 76 + Alcotest.(check string) 77 + "title preserved" (Cff.title cff1) (Cff.title cff2); 78 + Alcotest.(check string) 79 + "cff-version preserved" (Cff.cff_version cff1) 80 + (Cff.cff_version cff2))) 81 81 82 82 let test_parse_key_complete () = 83 83 let path = 84 84 "../vendor/git/citation-file-format/examples/1.2.0/pass/key-complete/CITATION.cff" 85 85 in 86 86 match Cff_unix.of_file path with 87 - | Ok cff -> 88 - (* Check basic fields *) 89 - Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 90 - Alcotest.(check string) "title" "Citation File Format 1.0.0" (Cff.title cff); 91 - Alcotest.(check (option string)) "version" (Some "1.0.0") (Cff.version cff); 92 - Alcotest.(check (option string)) "doi" (Some "10.5281/zenodo.1003150") (Cff.doi cff); 93 - Alcotest.(check (option string)) 94 - "abstract" 95 - (Some "This is an awesome piece of research software!") 96 - (Cff.abstract cff); 97 - Alcotest.(check (option string)) 98 - "commit" 99 - (Some "156a04c74a8a79d40c5d705cddf9d36735feab4d") 100 - (Cff.commit cff); 101 - (* Check authors - should have 2 (1 person + 1 entity) *) 102 - Alcotest.(check int) "authors count" 2 (List.length (Cff.authors cff)); 103 - (* Check first author is a Person *) 104 - (match List.hd (Cff.authors cff) with 105 - | `Person p -> 106 - Alcotest.(check (option string)) 107 - "person family-names" 108 - (Some "Real Person") 109 - (Cff.Person.family_names p); 110 - Alcotest.(check (option string)) 111 - "person given-names" 112 - (Some "One Truly") 113 - (Cff.Person.given_names p) 114 - | `Entity _ -> Alcotest.fail "Expected Person, got Entity"); 115 - (* Check second author is an Entity *) 116 - (match List.nth (Cff.authors cff) 1 with 117 - | `Entity e -> 118 - Alcotest.(check string) 119 - "entity name" 120 - "Entity Project Team Conference entity" 121 - (Cff.Entity.name e) 122 - | `Person _ -> Alcotest.fail "Expected Entity, got Person"); 123 - (* Check identifiers *) 124 - (match Cff.identifiers cff with 125 - | Some ids -> Alcotest.(check int) "identifiers count" 4 (List.length ids) 126 - | None -> Alcotest.fail "Expected identifiers"); 127 - (* Check keywords *) 128 - (match Cff.keywords cff with 129 - | Some kws -> 130 - Alcotest.(check int) "keywords count" 4 (List.length kws); 131 - Alcotest.(check string) "first keyword" "One" (List.hd kws) 132 - | None -> Alcotest.fail "Expected keywords"); 133 - (* Check preferred-citation *) 134 - (match Cff.preferred_citation cff with 135 - | Some ref -> 136 - Alcotest.(check string) 137 - "preferred-citation title" 138 - "Book Title" 139 - (Cff.Reference.title ref) 140 - | None -> Alcotest.fail "Expected preferred-citation"); 141 - (* Check references *) 142 - (match Cff.references cff with 143 - | Some refs -> Alcotest.(check int) "references count" 1 (List.length refs) 144 - | None -> Alcotest.fail "Expected references") 145 - | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse key-complete CFF: %s" e) 146 - ;; 87 + | Ok cff -> ( 88 + (* Check basic fields *) 89 + Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 90 + Alcotest.(check string) 91 + "title" "Citation File Format 1.0.0" (Cff.title cff); 92 + Alcotest.(check (option string)) 93 + "version" (Some "1.0.0") (Cff.version cff); 94 + Alcotest.(check (option string)) 95 + "doi" (Some "10.5281/zenodo.1003150") (Cff.doi cff); 96 + Alcotest.(check (option string)) 97 + "abstract" (Some "This is an awesome piece of research software!") 98 + (Cff.abstract cff); 99 + Alcotest.(check (option string)) 100 + "commit" (Some "156a04c74a8a79d40c5d705cddf9d36735feab4d") 101 + (Cff.commit cff); 102 + (* Check authors - should have 2 (1 person + 1 entity) *) 103 + Alcotest.(check int) "authors count" 2 (List.length (Cff.authors cff)); 104 + (* Check first author is a Person *) 105 + (match List.hd (Cff.authors cff) with 106 + | `Person p -> 107 + Alcotest.(check (option string)) 108 + "person family-names" (Some "Real Person") 109 + (Cff.Person.family_names p); 110 + Alcotest.(check (option string)) 111 + "person given-names" (Some "One Truly") (Cff.Person.given_names p) 112 + | `Entity _ -> Alcotest.fail "Expected Person, got Entity"); 113 + (* Check second author is an Entity *) 114 + (match List.nth (Cff.authors cff) 1 with 115 + | `Entity e -> 116 + Alcotest.(check string) 117 + "entity name" "Entity Project Team Conference entity" 118 + (Cff.Entity.name e) 119 + | `Person _ -> Alcotest.fail "Expected Entity, got Person"); 120 + (* Check identifiers *) 121 + (match Cff.identifiers cff with 122 + | Some ids -> Alcotest.(check int) "identifiers count" 4 (List.length ids) 123 + | None -> Alcotest.fail "Expected identifiers"); 124 + (* Check keywords *) 125 + (match Cff.keywords cff with 126 + | Some kws -> 127 + Alcotest.(check int) "keywords count" 4 (List.length kws); 128 + Alcotest.(check string) "first keyword" "One" (List.hd kws) 129 + | None -> Alcotest.fail "Expected keywords"); 130 + (* Check preferred-citation *) 131 + (match Cff.preferred_citation cff with 132 + | Some ref -> 133 + Alcotest.(check string) 134 + "preferred-citation title" "Book Title" (Cff.Reference.title ref) 135 + | None -> Alcotest.fail "Expected preferred-citation"); 136 + (* Check references *) 137 + match Cff.references cff with 138 + | Some refs -> 139 + Alcotest.(check int) "references count" 1 (List.length refs) 140 + | None -> Alcotest.fail "Expected references") 141 + | Error e -> 142 + Alcotest.fail (Printf.sprintf "Failed to parse key-complete CFF: %s" e) 147 143 148 144 (* All 1.2.0 pass fixtures *) 149 145 (* Note: reference-article is skipped due to Yamlt parser limitation with 150 146 multi-line quoted strings (see issue with indentation in quoted scalars) *) 151 147 let pass_fixtures_1_2_0 = 152 - [ "bjmorgan/bsym" 153 - ; "esalmela/haplowinder" 154 - ; "key-complete" 155 - ; "ls1mardyn/ls1-mardyn" 156 - ; "minimal" 157 - ; "poc" 158 - ; "reference-art" 159 - ; (* "reference-article"; -- skipped: Yamlt multi-line quoted string issue *) 160 - "reference-blog" 161 - ; "reference-book" 162 - ; "reference-conference-paper" 163 - ; "reference-edited-work" 164 - ; "reference-report" 165 - ; "reference-thesis" 166 - ; "short" 167 - ; "simple" 168 - ; "software-container" 169 - ; "software-executable" 170 - ; "software-with-a-doi" 171 - ; "software-with-a-doi-expanded" 172 - ; "software-without-a-doi" 173 - ; "software-without-a-doi-closed-source" 174 - ; "software-with-reference" 175 - ; "tue-excellent-buildings/bso-toolbox" 176 - ; "xenon-middleware_xenon-adaptors-cloud" 148 + [ 149 + "bjmorgan/bsym"; 150 + "esalmela/haplowinder"; 151 + "key-complete"; 152 + "ls1mardyn/ls1-mardyn"; 153 + "minimal"; 154 + "poc"; 155 + "reference-art"; 156 + (* "reference-article"; -- skipped: Yamlt multi-line quoted string issue *) 157 + "reference-blog"; 158 + "reference-book"; 159 + "reference-conference-paper"; 160 + "reference-edited-work"; 161 + "reference-report"; 162 + "reference-thesis"; 163 + "short"; 164 + "simple"; 165 + "software-container"; 166 + "software-executable"; 167 + "software-with-a-doi"; 168 + "software-with-a-doi-expanded"; 169 + "software-without-a-doi"; 170 + "software-without-a-doi-closed-source"; 171 + "software-with-reference"; 172 + "tue-excellent-buildings/bso-toolbox"; 173 + "xenon-middleware_xenon-adaptors-cloud"; 177 174 ] 178 - ;; 179 175 180 176 let make_fixture_test name = 181 177 let test_name = String.map (fun c -> if c = '/' then '-' else c) name in ··· 187 183 in 188 184 match Cff_unix.of_file path with 189 185 | Ok cff -> 190 - (* Basic sanity checks that apply to all valid CFF files *) 191 - Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 192 - Alcotest.(check bool) "has title" true (String.length (Cff.title cff) > 0); 193 - Alcotest.(check bool) "has authors" true (List.length (Cff.authors cff) > 0) 186 + (* Basic sanity checks that apply to all valid CFF files *) 187 + Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 188 + Alcotest.(check bool) 189 + "has title" true 190 + (String.length (Cff.title cff) > 0); 191 + Alcotest.(check bool) 192 + "has authors" true 193 + (List.length (Cff.authors cff) > 0) 194 194 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse %s: %s" name e) 195 195 in 196 196 Alcotest.test_case test_name `Quick test 197 - ;; 198 197 199 198 (* License parsing tests *) 200 199 ··· 207 206 - family-names: Test 208 207 license: MIT 209 208 |} 210 - ;; 211 209 212 210 let cff_with_license_expression = 213 211 {| ··· 218 216 - family-names: Test 219 217 license: GPL-3.0-or-later WITH Classpath-exception-2.0 220 218 |} 221 - ;; 222 219 223 220 let cff_with_license_array = 224 221 {| ··· 231 228 - Apache-2.0 232 229 - MIT 233 230 |} 234 - ;; 235 231 236 232 let cff_with_unknown_license = 237 233 {| ··· 242 238 - family-names: Test 243 239 license: Some-Unknown-License-v1.0 244 240 |} 245 - ;; 246 241 247 242 let cff_with_unknown_license_array = 248 243 {| ··· 255 250 - MIT 256 251 - Not-A-Real-License 257 252 |} 258 - ;; 259 253 260 254 let test_license_single () = 261 255 match Cff_unix.of_yaml_string cff_with_single_license with 262 - | Ok cff -> 263 - (match Cff.license cff with 264 - | Some (`Spdx (Spdx_licenses.Simple (Spdx_licenses.LicenseID "MIT"))) -> () 265 - | Some (`Spdx _) -> Alcotest.fail "Expected simple MIT license" 266 - | Some (`Other _) -> Alcotest.fail "License should have parsed as valid SPDX" 267 - | None -> Alcotest.fail "Missing license") 256 + | Ok cff -> ( 257 + match Cff.license cff with 258 + | Some (`Spdx (Spdx_licenses.Simple (Spdx_licenses.LicenseID "MIT"))) -> 259 + () 260 + | Some (`Spdx _) -> Alcotest.fail "Expected simple MIT license" 261 + | Some (`Other _) -> 262 + Alcotest.fail "License should have parsed as valid SPDX" 263 + | None -> Alcotest.fail "Missing license") 268 264 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse: %s" e) 269 - ;; 270 265 271 266 let test_license_expression () = 272 267 match Cff_unix.of_yaml_string cff_with_license_expression with 273 - | Ok cff -> 274 - (match Cff.license cff with 275 - | Some (`Spdx (Spdx_licenses.WITH _)) -> () 276 - | Some (`Spdx _) -> Alcotest.fail "Expected WITH expression" 277 - | Some (`Other _) -> Alcotest.fail "License should have parsed as valid SPDX" 278 - | None -> Alcotest.fail "Missing license") 268 + | Ok cff -> ( 269 + match Cff.license cff with 270 + | Some (`Spdx (Spdx_licenses.WITH _)) -> () 271 + | Some (`Spdx _) -> Alcotest.fail "Expected WITH expression" 272 + | Some (`Other _) -> 273 + Alcotest.fail "License should have parsed as valid SPDX" 274 + | None -> Alcotest.fail "Missing license") 279 275 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse: %s" e) 280 - ;; 281 276 282 277 let test_license_array () = 283 278 match Cff_unix.of_yaml_string cff_with_license_array with 284 - | Ok cff -> 285 - (match Cff.license cff with 286 - | Some (`Spdx (Spdx_licenses.OR _)) -> () 287 - | Some (`Spdx _) -> Alcotest.fail "Expected OR expression" 288 - | Some (`Other _) -> Alcotest.fail "License should have parsed as valid SPDX" 289 - | None -> Alcotest.fail "Missing license") 279 + | Ok cff -> ( 280 + match Cff.license cff with 281 + | Some (`Spdx (Spdx_licenses.OR _)) -> () 282 + | Some (`Spdx _) -> Alcotest.fail "Expected OR expression" 283 + | Some (`Other _) -> 284 + Alcotest.fail "License should have parsed as valid SPDX" 285 + | None -> Alcotest.fail "Missing license") 290 286 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse: %s" e) 291 - ;; 292 287 293 288 let test_license_unknown () = 294 289 match Cff_unix.of_yaml_string cff_with_unknown_license with 295 - | Ok cff -> 296 - (match Cff.license cff with 297 - | Some (`Other ([ "Some-Unknown-License-v1.0" ], None)) -> () 298 - | Some (`Other (ss, _)) -> 299 - Alcotest.fail (Printf.sprintf "Wrong value: [%s]" (String.concat "; " ss)) 300 - | Some (`Spdx _) -> Alcotest.fail "Unknown license should be Other, not Spdx" 301 - | None -> Alcotest.fail "Missing license") 290 + | Ok cff -> ( 291 + match Cff.license cff with 292 + | Some (`Other ([ "Some-Unknown-License-v1.0" ], None)) -> () 293 + | Some (`Other (ss, _)) -> 294 + Alcotest.fail 295 + (Printf.sprintf "Wrong value: [%s]" (String.concat "; " ss)) 296 + | Some (`Spdx _) -> 297 + Alcotest.fail "Unknown license should be Other, not Spdx" 298 + | None -> Alcotest.fail "Missing license") 302 299 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse: %s" e) 303 - ;; 304 300 305 301 let test_license_unknown_in_array () = 306 302 match Cff_unix.of_yaml_string cff_with_unknown_license_array with 307 - | Ok cff -> 308 - (match Cff.license cff with 309 - | Some (`Other ([ "MIT"; "Not-A-Real-License" ], None)) -> () 310 - | Some (`Other (ss, _)) -> 311 - Alcotest.fail (Printf.sprintf "Wrong value: [%s]" (String.concat "; " ss)) 312 - | Some (`Spdx _) -> Alcotest.fail "Array with unknown should be Other" 313 - | None -> Alcotest.fail "Missing license") 303 + | Ok cff -> ( 304 + match Cff.license cff with 305 + | Some (`Other ([ "MIT"; "Not-A-Real-License" ], None)) -> () 306 + | Some (`Other (ss, _)) -> 307 + Alcotest.fail 308 + (Printf.sprintf "Wrong value: [%s]" (String.concat "; " ss)) 309 + | Some (`Spdx _) -> Alcotest.fail "Array with unknown should be Other" 310 + | None -> Alcotest.fail "Missing license") 314 311 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse: %s" e) 315 - ;; 316 312 317 313 let test_license_unknown_roundtrip () = 318 314 match Cff_unix.of_yaml_string cff_with_unknown_license with 319 315 | Error e -> Alcotest.fail (Printf.sprintf "Failed to parse: %s" e) 320 - | Ok cff1 -> 321 - (match Cff_unix.to_yaml_string cff1 with 322 - | Error e -> Alcotest.fail (Printf.sprintf "Failed to encode: %s" e) 323 - | Ok yaml -> 324 - (match Cff_unix.of_yaml_string yaml with 325 - | Error e -> Alcotest.fail (Printf.sprintf "Failed to reparse: %s" e) 326 - | Ok cff2 -> 327 - (match Cff.license cff2 with 328 - | Some (`Other ([ "Some-Unknown-License-v1.0" ], None)) -> () 329 - | Some (`Other (ss, _)) -> 330 - Alcotest.fail 331 - (Printf.sprintf "Roundtrip changed value: [%s]" (String.concat "; " ss)) 332 - | Some (`Spdx _) -> Alcotest.fail "Roundtrip changed Other to Spdx" 333 - | None -> Alcotest.fail "Roundtrip lost license"))) 334 - ;; 316 + | Ok cff1 -> ( 317 + match Cff_unix.to_yaml_string cff1 with 318 + | Error e -> Alcotest.fail (Printf.sprintf "Failed to encode: %s" e) 319 + | Ok yaml -> ( 320 + match Cff_unix.of_yaml_string yaml with 321 + | Error e -> Alcotest.fail (Printf.sprintf "Failed to reparse: %s" e) 322 + | Ok cff2 -> ( 323 + match Cff.license cff2 with 324 + | Some (`Other ([ "Some-Unknown-License-v1.0" ], None)) -> () 325 + | Some (`Other (ss, _)) -> 326 + Alcotest.fail 327 + (Printf.sprintf "Roundtrip changed value: [%s]" 328 + (String.concat "; " ss)) 329 + | Some (`Spdx _) -> 330 + Alcotest.fail "Roundtrip changed Other to Spdx" 331 + | None -> Alcotest.fail "Roundtrip lost license"))) 335 332 336 333 (* Test that we correctly reject or handle known-invalid files *) 337 334 let test_fail_invalid_date () = ··· 340 337 in 341 338 match Cff_unix.of_file path with 342 339 | Ok _ -> 343 - (* Our parser might be lenient - that's OK for now *) 344 - () 340 + (* Our parser might be lenient - that's OK for now *) 341 + () 345 342 | Error _ -> 346 - (* Expected to fail due to invalid date "2020-05-xx" *) 347 - () 348 - ;; 343 + (* Expected to fail due to invalid date "2020-05-xx" *) 344 + () 349 345 350 346 (* Test fail fixture with additional key - should parse since we skip unknown *) 351 347 let test_fail_additional_key () = ··· 354 350 in 355 351 match Cff_unix.of_file path with 356 352 | Ok cff -> 357 - (* Our parser is lenient and skips unknown keys *) 358 - Alcotest.(check string) "title" "My Research Tool" (Cff.title cff) 353 + (* Our parser is lenient and skips unknown keys *) 354 + Alcotest.(check string) "title" "My Research Tool" (Cff.title cff) 359 355 | Error e -> 360 - Alcotest.fail (Printf.sprintf "Should parse with unknown keys skipped: %s" e) 361 - ;; 356 + Alcotest.fail 357 + (Printf.sprintf "Should parse with unknown keys skipped: %s" e) 362 358 363 359 let () = 364 - Alcotest.run 365 - "CFF" 366 - [ ( "parsing" 367 - , [ Alcotest.test_case "minimal" `Quick test_parse_minimal 368 - ; Alcotest.test_case "simple" `Quick test_parse_simple 369 - ; Alcotest.test_case "key-complete" `Quick test_parse_key_complete 370 - ] ) 371 - ; "creation", [ Alcotest.test_case "programmatic" `Quick test_create_programmatic ] 372 - ; "roundtrip", [ Alcotest.test_case "simple roundtrip" `Quick test_roundtrip ] 373 - ; ( "license" 374 - , [ Alcotest.test_case "single license" `Quick test_license_single 375 - ; Alcotest.test_case "license expression" `Quick test_license_expression 376 - ; Alcotest.test_case "license array" `Quick test_license_array 377 - ; Alcotest.test_case "unknown license" `Quick test_license_unknown 378 - ; Alcotest.test_case "unknown in array" `Quick test_license_unknown_in_array 379 - ; Alcotest.test_case "unknown roundtrip" `Quick test_license_unknown_roundtrip 380 - ] ) 381 - ; "1.2.0 fixtures", List.map make_fixture_test pass_fixtures_1_2_0 382 - ; ( "fail fixtures" 383 - , [ Alcotest.test_case "invalid-date" `Quick test_fail_invalid_date 384 - ; Alcotest.test_case "additional-key" `Quick test_fail_additional_key 385 - ] ) 360 + Alcotest.run "CFF" 361 + [ 362 + ( "parsing", 363 + [ 364 + Alcotest.test_case "minimal" `Quick test_parse_minimal; 365 + Alcotest.test_case "simple" `Quick test_parse_simple; 366 + Alcotest.test_case "key-complete" `Quick test_parse_key_complete; 367 + ] ); 368 + ( "creation", 369 + [ Alcotest.test_case "programmatic" `Quick test_create_programmatic ] ); 370 + ( "roundtrip", 371 + [ Alcotest.test_case "simple roundtrip" `Quick test_roundtrip ] ); 372 + ( "license", 373 + [ 374 + Alcotest.test_case "single license" `Quick test_license_single; 375 + Alcotest.test_case "license expression" `Quick test_license_expression; 376 + Alcotest.test_case "license array" `Quick test_license_array; 377 + Alcotest.test_case "unknown license" `Quick test_license_unknown; 378 + Alcotest.test_case "unknown in array" `Quick 379 + test_license_unknown_in_array; 380 + Alcotest.test_case "unknown roundtrip" `Quick 381 + test_license_unknown_roundtrip; 382 + ] ); 383 + ("1.2.0 fixtures", List.map make_fixture_test pass_fixtures_1_2_0); 384 + ( "fail fixtures", 385 + [ 386 + Alcotest.test_case "invalid-date" `Quick test_fail_invalid_date; 387 + Alcotest.test_case "additional-key" `Quick test_fail_additional_key; 388 + ] ); 386 389 ] 387 - ;;
+27 -39
test/test_cff_eio.ml
··· 14 14 - family-names: Smith 15 15 given-names: Jane 16 16 |} 17 - ;; 18 17 19 18 let simple_cff = 20 19 {| ··· 29 28 doi: 10.5281/zenodo.1234567 30 29 date-released: 2021-08-11 31 30 |} 32 - ;; 33 31 34 32 let test_parse_string () = 35 33 let cff = Cff_eio.of_yaml_string minimal_cff in 36 34 Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 37 35 Alcotest.(check string) "title" "Test Software" (Cff.title cff); 38 36 Alcotest.(check int) "authors count" 1 (List.length (Cff.authors cff)) 39 - ;; 40 37 41 38 let test_roundtrip_string () = 42 39 let cff1 = Cff_eio.of_yaml_string simple_cff in ··· 44 41 let cff2 = Cff_eio.of_yaml_string yaml in 45 42 Alcotest.(check string) "title preserved" (Cff.title cff1) (Cff.title cff2); 46 43 Alcotest.(check string) 47 - "cff-version preserved" 48 - (Cff.cff_version cff1) 49 - (Cff.cff_version cff2) 50 - ;; 44 + "cff-version preserved" (Cff.cff_version cff1) (Cff.cff_version cff2) 51 45 52 46 let test_parse_error () = 53 - let invalid_yaml = 54 - {| 47 + let invalid_yaml = {| 55 48 cff-version: 1.2.0 56 49 title: Missing authors field 57 - |} 58 - in 50 + |} in 59 51 match Cff_eio.of_yaml_string invalid_yaml with 60 52 | _ -> Alcotest.fail "Expected parse error" 61 53 | exception Eio.Exn.Io (Cff_eio.E _, _) -> () 62 54 | exception ex -> 63 - Alcotest.fail (Printf.sprintf "Wrong exception type: %s" (Printexc.to_string ex)) 64 - ;; 55 + Alcotest.fail 56 + (Printf.sprintf "Wrong exception type: %s" (Printexc.to_string ex)) 65 57 66 58 let test_file_read env = 67 59 let fs = Eio.Stdenv.fs env in 68 60 let cff = 69 - Cff_eio.of_file 70 - ~fs 61 + Cff_eio.of_file ~fs 71 62 "../vendor/git/citation-file-format/examples/1.2.0/pass/minimal/CITATION.cff" 72 63 in 73 64 Alcotest.(check string) "cff-version" "1.2.0" (Cff.cff_version cff); 74 65 Alcotest.(check bool) "has title" true (String.length (Cff.title cff) > 0); 75 66 Alcotest.(check bool) "has authors" true (List.length (Cff.authors cff) > 0) 76 - ;; 77 67 78 68 let test_file_not_found env = 79 69 let fs = Eio.Stdenv.fs env in ··· 81 71 | _ -> Alcotest.fail "Expected file not found error" 82 72 | exception Eio.Exn.Io _ -> () 83 73 | exception ex -> 84 - Alcotest.fail (Printf.sprintf "Wrong exception type: %s" (Printexc.to_string ex)) 85 - ;; 74 + Alcotest.fail 75 + (Printf.sprintf "Wrong exception type: %s" (Printexc.to_string ex)) 86 76 87 77 let test_file_roundtrip env = 88 78 let fs = Eio.Stdenv.fs env in 89 79 let cff1 = 90 - Cff_eio.of_file 91 - ~fs 80 + Cff_eio.of_file ~fs 92 81 "../vendor/git/citation-file-format/examples/1.2.0/pass/simple/CITATION.cff" 93 82 in 94 83 let tmp_path = "_build/test_roundtrip.cff" in ··· 96 85 let cff2 = Cff_eio.of_file ~fs tmp_path in 97 86 Alcotest.(check string) "title preserved" (Cff.title cff1) (Cff.title cff2); 98 87 Alcotest.(check string) 99 - "cff-version preserved" 100 - (Cff.cff_version cff1) 101 - (Cff.cff_version cff2) 102 - ;; 88 + "cff-version preserved" (Cff.cff_version cff1) (Cff.cff_version cff2) 103 89 104 90 let () = 105 - Eio_main.run 106 - @@ fun env -> 107 - Alcotest.run 108 - "CFF Eio" 109 - [ ( "string parsing" 110 - , [ Alcotest.test_case "parse string" `Quick test_parse_string 111 - ; Alcotest.test_case "roundtrip string" `Quick test_roundtrip_string 112 - ; Alcotest.test_case "parse error" `Quick test_parse_error 113 - ] ) 114 - ; ( "file operations" 115 - , [ Alcotest.test_case "read file" `Quick (fun () -> test_file_read env) 116 - ; Alcotest.test_case "file not found" `Quick (fun () -> test_file_not_found env) 117 - ; Alcotest.test_case "file roundtrip" `Quick (fun () -> test_file_roundtrip env) 118 - ] ) 91 + Eio_main.run @@ fun env -> 92 + Alcotest.run "CFF Eio" 93 + [ 94 + ( "string parsing", 95 + [ 96 + Alcotest.test_case "parse string" `Quick test_parse_string; 97 + Alcotest.test_case "roundtrip string" `Quick test_roundtrip_string; 98 + Alcotest.test_case "parse error" `Quick test_parse_error; 99 + ] ); 100 + ( "file operations", 101 + [ 102 + Alcotest.test_case "read file" `Quick (fun () -> test_file_read env); 103 + Alcotest.test_case "file not found" `Quick (fun () -> 104 + test_file_not_found env); 105 + Alcotest.test_case "file roundtrip" `Quick (fun () -> 106 + test_file_roundtrip env); 107 + ] ); 119 108 ] 120 - ;;