···11+# swhid_core
22+33+[swhid_core] is an [OCaml] library to work with [persistent identifiers] used by [Software Heritage], also known as swhid. This is the core library, for most use cases you should use the [swhid library] instead.
44+55+## Installation
66+77+`swhid_core` can be installed with [opam]:
88+99+```sh
1010+opam install swhid_core
1111+```
1212+1313+If you don't have `opam`, you can install it following the [how to install opam] guide.
1414+1515+If you can't or don't want to use `opam`, consult the [opam file] for build instructions.
1616+1717+## Quickstart
1818+1919+Have a look at the [example] folder or at the [documentation].
2020+2121+## About
2222+2323+- [LICENSE]
2424+- [CHANGELOG]
2525+2626+[CHANGELOG]: ./CHANGES.md
2727+[example]: ./example
2828+[LICENSE]: ./LICENSE.md
2929+[opam file]: ./swhid_core.opam
3030+[test suite]: ./test
3131+3232+[documentation]: https://ocamlpro.github.io/swhid_core/api/swhid_core/
3333+[how to install opam]: https://opam.ocaml.org/doc/Install.html
3434+[OCaml]: https://ocaml.org
3535+[opam]: https://opam.ocaml.org/
3636+[persistent identifiers]: https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html
3737+[Software Heritage]: https://www.softwareheritage.org
3838+[swhid library]: https://github.com/OCamlPro/swhid
3939+[swhid_core]: https://github.com/OCamlPro/swhid_core
···11+<!DOCTYPE html>
22+<html lang="en">
33+<head>
44+ <meta charset="utf-8">
55+ <title>swhid_core</title>
66+ <link rel="stylesheet" href="style.css">
77+ <link rel="icon" type="image/png" sizes="804x804" href="favicon.png">
88+</head>
99+<body>
1010+ <div class="content">
1111+ <div class="header">
1212+ <h1>swhid_core</h1>
1313+1414+ <p>swhid_core is an <a href="https://ocaml.org">OCaml</a> library to work with <a href="https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html">persistent identifiers</a> found in <a href="https://www.softwareheritage.org">Software Heritage identifiers</a>, also known as swhid.</p>
1515+ </div>
1616+1717+ <div class="section">
1818+ <ul>
1919+ <li>
2020+ <a href="https://github.com/OCamlPro/swhid_core">Project on Github</a>
2121+ </li>
2222+2323+ <li>
2424+ <a href="api/swhid_core/">API Documentation</a>
2525+ </li>
2626+2727+ <li>
2828+ <a href="coverage">Code coverage report</a>
2929+ </li>
3030+3131+ <li>
3232+ <a href="https://github.com/OCamlPro/swhid_core/issues">Bug reports</a>
3333+ </li>
3434+ </ul>
3535+ </div>
3636+ </div>
3737+</body>
3838+</html>
+9
vendor/opam/swhid_core/doc/index.mld
···11+{0 swhid_core}
22+33+{{:https://github.com/OCamlPro/swhid_core} swhid_core} is an {{:https://ocaml.org} OCaml} library to work with Software Heritage persistent identifiers (swhids).
44+55+{1:api API}
66+77+{!modules:
88+Swhid_core
99+}
···11+open Swhid_core
22+33+let swhid =
44+ match
55+ Object.of_string "swh:1:cnt:bac494ecb6840e6b66f21aae7feb847b23f0745a"
66+ with
77+ | Error e ->
88+ Format.eprintf "error: %s@." e;
99+ exit 1
1010+ | Ok id -> Format.printf "%a is a valid swhid !@." Object.pp id
+295
vendor/opam/swhid_core/src/compute.ml
···11+(* TODO: remove once we get >= 4.08 *)
22+let result_is_error = function Ok _v -> false | Error _v -> true
33+let result_get_ok = function Ok v -> v | Error _v -> invalid_arg "Result.get_ok"
44+55+type directory_entry_kind =
66+ | File
77+ | Dir
88+99+module Make (SHA1 : sig
1010+ val digest_string_to_hex : string -> string
1111+end) (OS : sig
1212+ val contents : string -> string list option
1313+1414+ val typ : string -> directory_entry_kind option
1515+1616+ val read_file : string -> string option
1717+1818+ val permissions : string -> int option
1919+2020+ val base : string -> string
2121+end) =
2222+struct
2323+ module Git = struct
2424+ let target_kind_to_git = function
2525+ | Object.Kind.Content _hash_type -> "blob"
2626+ | Directory -> "tree"
2727+ | Release -> "tag"
2828+ | Revision -> "commit"
2929+ | Snapshot -> "refs"
3030+3131+ let id_to_bytes id =
3232+ String.init
3333+ (String.length id / 2)
3434+ (fun i ->
3535+ let s = String.sub id (2 * i) 2 in
3636+ Char.chr @@ int_of_string @@ "0x" ^ s )
3737+3838+ let object_to_swhid (obj : string) object_type =
3939+ let scheme = Object.Scheme_version.default in
4040+ let hash = SHA1.digest_string_to_hex obj in
4141+ match Object.Hash.of_string hash with
4242+ | Error _msg as e -> e
4343+ | Ok hash ->
4444+ let core_identifier =
4545+ Object.Core_identifier.mk scheme object_type hash
4646+ in
4747+ Ok (Object.mk core_identifier [])
4848+4949+ let object_header fmt (git_type, len) =
5050+ match git_type with
5151+ | "blob" | "commit" | "extid" | "raw_extrinsic_metadata" | "snapshot"
5252+ | "tag" | "tree" ->
5353+ Format.fprintf fmt "%s %d\x00" git_type len
5454+ | git_type ->
5555+ invalid_arg
5656+ (Format.sprintf "invalid git object type `%s` (Git.object_header)"
5757+ git_type )
5858+5959+ let object_from_contents_strtarget target_kind contents =
6060+ let len = String.length contents in
6161+ Format.asprintf "%a%s" object_header (target_kind, len) contents
6262+6363+ let object_from_contents target_kind contents =
6464+ object_from_contents_strtarget (target_kind_to_git target_kind) contents
6565+6666+ (* TODO: remove once we have > 4.03 *)
6767+ let string_split_on_char sep s =
6868+ let r = ref [] in
6969+ let j = ref (String.length s) in
7070+ for i = String.length s - 1 downto 0 do
7171+ if String.unsafe_get s i = sep then begin
7272+ r := String.sub s (i + 1) (!j - i - 1) :: !r;
7373+ j := i
7474+ end
7575+ done;
7676+ String.sub s 0 !j :: !r
7777+7878+ let escape_newlines snippet =
7979+ String.concat "\n " (string_split_on_char '\n' snippet)
8080+8181+ (* TODO: replace with Int.abs when we have >= 4.08 *)
8282+ let abs x = if x >= 0 then x else -x
8383+8484+ let format_offset fmt (offset, negative_utc) =
8585+ let sign =
8686+ if offset < 0 || (offset = 0 && negative_utc) then "-" else "+"
8787+ in
8888+ let offset = abs offset in
8989+ let hours = offset / 60 in
9090+ let minutes = offset mod 60 in
9191+ Format.fprintf fmt "%s%02d%02d" sign hours minutes
9292+9393+ let format_author_data fmt (author, date) =
9494+ Format.fprintf fmt "%s" author;
9595+ match date with
9696+ | None -> ()
9797+ | Some (timestamp, tz_offset, negative_utc) ->
9898+ Format.fprintf fmt " %Ld %a" timestamp format_offset
9999+ (tz_offset, negative_utc)
100100+ end
101101+102102+ type directory_entry =
103103+ { typ : directory_entry_kind
104104+ ; permissions : int
105105+ ; name : string
106106+ ; target : Object.Core_identifier.t
107107+ }
108108+109109+ type date =
110110+ { timestamp : Int64.t
111111+ ; tz_offset : int
112112+ ; negative_utc : bool
113113+ }
114114+115115+ let content_identifier content =
116116+ let typ = Object.Kind.Content "sha1_git" in
117117+ let git_object = Git.object_from_contents typ content in
118118+ Git.object_to_swhid git_object typ
119119+120120+ let directory_identifier entries =
121121+ let entries =
122122+ List.sort
123123+ (fun entry1 entry2 ->
124124+ String.compare
125125+ (if entry1.typ = Dir then entry1.name ^ "/" else entry1.name)
126126+ (if entry2.typ = Dir then entry2.name ^ "/" else entry2.name) )
127127+ entries
128128+ in
129129+ let content =
130130+ Format.asprintf "%a"
131131+ (Format.pp_print_list
132132+ ~pp_sep:(fun _fmt () -> ())
133133+ (fun fmt entry ->
134134+ Format.fprintf fmt "%o %s%c%s" entry.permissions entry.name '\x00'
135135+ (Git.id_to_bytes
136136+ ( Object.Hash.to_string
137137+ @@ Object.Core_identifier.get_hash entry.target ) ) ) )
138138+ entries
139139+ in
140140+ let typ = Object.Kind.Directory in
141141+ let git_object = Git.object_from_contents typ content in
142142+ Git.object_to_swhid git_object typ
143143+144144+ (* TODO: remove once we have >= 4.05 *)
145145+ let rec list_find_opt p = function
146146+ | [] -> None
147147+ | x :: l -> if p x then Some x else list_find_opt p l
148148+149149+ let rec directory_identifier_deep name =
150150+ match OS.contents name with
151151+ | None -> Error (Format.sprintf "can't get contents of `%s`" name)
152152+ | Some contents -> (
153153+ let entries =
154154+ List.map
155155+ (fun name ->
156156+ let typ = OS.typ name in
157157+ let target =
158158+ match typ with
159159+ | Some File -> begin
160160+ match OS.read_file name with
161161+ | None -> Error (Format.sprintf "can't read file `%s`" name)
162162+ | Some content -> content_identifier content
163163+ end
164164+ | Some Dir -> directory_identifier_deep name
165165+ | None ->
166166+ Error (Format.sprintf "can't get type of file `%s`" name)
167167+ in
168168+ let permissions = OS.permissions name in
169169+ match (typ, permissions, target) with
170170+ | Some typ, Some permissions, Ok target ->
171171+ let name = OS.base name in
172172+ let target = Object.get_core target in
173173+ Ok { typ; permissions; target; name }
174174+ | _ -> Error "can't compute directory deep identifier" )
175175+ contents
176176+ in
177177+ match list_find_opt result_is_error entries with
178178+ | Some (Error _ as e) -> e
179179+ | Some _ -> assert false
180180+ | None -> directory_identifier (List.map result_get_ok entries) )
181181+182182+ (* TODO: remove once we have >= 4.08 *)
183183+ let option_map f = function None -> None | Some v -> Some (f v)
184184+185185+ let release_identifier target target_kind ~name ~author date ~message =
186186+ let buff = Buffer.create 512 in
187187+ let fmt = Format.formatter_of_buffer buff in
188188+189189+ Format.fprintf fmt "object %a%ctype %s%ctag %s%c" Object.Hash.pp target '\n'
190190+ (Git.target_kind_to_git target_kind)
191191+ '\n' (Git.escape_newlines name) '\n';
192192+193193+ begin
194194+ match author with
195195+ | None -> ()
196196+ | Some author ->
197197+ Format.fprintf fmt "tagger %a%c" Git.format_author_data
198198+ ( Git.escape_newlines author
199199+ , option_map
200200+ (fun o -> (o.timestamp, o.tz_offset, o.negative_utc))
201201+ date )
202202+ '\n'
203203+ end;
204204+205205+ begin
206206+ match message with
207207+ | None -> ()
208208+ | Some message -> Format.fprintf fmt "%c%s" '\n' message
209209+ end;
210210+211211+ Format.pp_print_flush fmt ();
212212+213213+ let content = Buffer.contents buff in
214214+215215+ let typ = Object.Kind.Release in
216216+ let git_object = Git.object_from_contents typ content in
217217+ Git.object_to_swhid git_object typ
218218+219219+ let revision_identifier directory parents ~author ~author_date ~committer
220220+ ~committer_date extra_headers ~message =
221221+ let buff = Buffer.create 512 in
222222+ let fmt = Format.formatter_of_buffer buff in
223223+224224+ Format.fprintf fmt "tree %a%c" Object.Hash.pp directory '\n';
225225+226226+ List.iter
227227+ (fun parent -> Format.fprintf fmt "parent %a%c" Object.Hash.pp parent '\n')
228228+ parents;
229229+230230+ Format.fprintf fmt "author %a%c" Git.format_author_data
231231+ ( Git.escape_newlines author
232232+ , option_map
233233+ (fun o -> (o.timestamp, o.tz_offset, o.negative_utc))
234234+ author_date )
235235+ '\n';
236236+237237+ Format.fprintf fmt "committer %a%c" Git.format_author_data
238238+ ( Git.escape_newlines committer
239239+ , option_map
240240+ (fun o -> (o.timestamp, o.tz_offset, o.negative_utc))
241241+ committer_date )
242242+ '\n';
243243+244244+ Array.iter
245245+ (fun (k, v) -> Format.fprintf fmt "%s %s%c" k (Git.escape_newlines v) '\n')
246246+ extra_headers;
247247+248248+ begin
249249+ match message with
250250+ | None -> ()
251251+ | Some message -> Format.fprintf fmt "%c%s" '\n' message
252252+ end;
253253+254254+ Format.pp_print_flush fmt ();
255255+256256+ let content = Buffer.contents buff in
257257+258258+ let typ = Object.Kind.Revision in
259259+ let git_object = Git.object_from_contents typ content in
260260+ Git.object_to_swhid git_object typ
261261+262262+ let snapshot_identifier (branches : (string * (string * string) option) list)
263263+ =
264264+ let branches =
265265+ List.sort
266266+ (fun (name1, _target) (name2, _target) -> String.compare name1 name2)
267267+ branches
268268+ in
269269+ let buff = Buffer.create 512 in
270270+ let fmt = Format.formatter_of_buffer buff in
271271+ List.iter
272272+ (fun (branch_name, target) ->
273273+ let target, target_kind, target_id_len =
274274+ match target with
275275+ | None -> ("", "dangling", 0)
276276+ | Some (target, target_kind) -> (
277277+ match target_kind with
278278+ | "content" | "directory" | "revision" | "release" | "snapshot" ->
279279+ (Git.id_to_bytes target, target_kind, 20)
280280+ | "alias" -> (target, "alias", String.length target)
281281+ | target_kind ->
282282+ invalid_arg
283283+ (Format.sprintf
284284+ "invalid target type: `%s` (Compute.snapshot_identifier)"
285285+ target_kind ) )
286286+ in
287287+ Format.fprintf fmt "%s %s%c%d:%s" target_kind branch_name '\x00'
288288+ target_id_len target )
289289+ branches;
290290+ Format.pp_print_flush fmt ();
291291+ let content = Buffer.contents buff in
292292+293293+ let git_object = Git.object_from_contents_strtarget "snapshot" content in
294294+ Git.object_to_swhid git_object Object.Kind.Snapshot
295295+end
+123
vendor/opam/swhid_core/src/compute.mli
···11+(** Module to compute a swhid for all kinds of objects. *)
22+33+(** The type of directory entries. *)
44+type directory_entry_kind =
55+ | File
66+ | Dir
77+88+module Make (SHA1 : sig
99+ (** A module containing the needed hash functions. *)
1010+1111+ (** [digest_string_to_hex s] computes the SHA1 hash of [s] and returns its
1212+ hexadecimal representation. *)
1313+ val digest_string_to_hex : string -> string
1414+end) (OS : sig
1515+ (** A module containing the needed OS-related functions. *)
1616+1717+ (** [contents dir] returns the list of files in the directory [dir]. *)
1818+ val contents : string -> string list option
1919+2020+ (** [type file] returns [Dir] if [file] is a directory and [File] otherwise. *)
2121+ val typ : string -> directory_entry_kind option
2222+2323+ (** [read_file f] returns the content of the file [f]. *)
2424+ val read_file : string -> string option
2525+2626+ (** [permissions f] returns the 16-bit file mode (as stored by Git) of the
2727+ file [f]. That is:
2828+2929+ - [0o120000] if [f] is a symlink
3030+ - [0o040000] if [f] is a directory
3131+ - [0o100755] if [f] is an executable file
3232+ - [0o100644] if [f] is a regular file *)
3333+ val permissions : string -> int option
3434+3535+ (** [base f] is the basename of file [f]. *)
3636+ val base : string -> string
3737+end) : sig
3838+ (** A functor that, given a SHA1 module and a OS module, provides various
3939+ functions to compute the swhid of a given object. Supported objects are
4040+ [content], [directory], [release], [revision] and [snapshot]. The origins
4141+ and visits objects are not supported. *)
4242+4343+ (** The type for directory entries list, needed to compute directories
4444+ identifiers. *)
4545+ type directory_entry =
4646+ { typ : directory_entry_kind
4747+ ; permissions : int
4848+ ; name : string
4949+ ; target : Object.Core_identifier.t
5050+ }
5151+5252+ (** The type for dates, needed to compute releases and revisions identifiers. *)
5353+ type date =
5454+ { timestamp : Int64.t
5555+ ; tz_offset : int
5656+ ; negative_utc : bool
5757+ }
5858+5959+ (** [content_identifier s] computes the swhid for the [s] content. [s] is the
6060+ raw content of a file as a [string].
6161+6262+ E.g. [content_identifier "_build\n"] is the swhid of this library's
6363+ [.gitignore] file. *)
6464+ val content_identifier : string -> (Object.t, string) result
6565+6666+ (** [directory_identifier entries] compute the swhid for the [entries]
6767+ directory. [entries] is a list of [Kinds.directory_entry] where each
6868+ element points to another object (usually a file content or a
6969+ sub-directory).
7070+7171+ E.g.
7272+ [directory_identifier \[ { typ = "file"
7373+ ; permissions = 33188
7474+ ; name = "README"
7575+ ; target = "37ec8ea2110c0b7a32fbb0e872f6e7debbf95e21"
7676+ }\]]
7777+ is the swhid of a directory which has a single file [README] with
7878+ permissions 33188 and whose core identifier from [content_identifier] is
7979+ [37ec8ea2110c0b7a32fbb0e872f6e7debbf95e21]. *)
8080+ val directory_identifier : directory_entry list -> (Object.t, string) result
8181+8282+ (** [directory_identifier_deep] compute the swhid for a given directory name,
8383+ it uses the various functions provided in the [OS] module parameter to
8484+ list directory contents, get file permissions and read file contents.*)
8585+ val directory_identifier_deep : string -> (Object.t, string) result
8686+8787+ (** [release_identifier target target_kind name ~author date ~message]
8888+ computes the swhid for a release object pointing to an object of type
8989+ [target_kind] whose identifier is [target], the release having [~name],
9090+ [~author] and has been published on [date] with the release [~message]. *)
9191+ val release_identifier :
9292+ Object.Hash.t
9393+ -> Object.Kind.t
9494+ -> name:string
9595+ -> author:string option
9696+ -> date option
9797+ -> message:string option
9898+ -> (Object.t, string) result
9999+100100+ (** [revision dir parents ~author ~author_date ~committer ~committer_date extra_headers message]
101101+ computes the swhid for a revision object whose directory has id [dir] and
102102+ whose parents has ids [parents] which was authored by [~author] on
103103+ [~author_date] and committed by [~committer] on [~committer_date] with
104104+ extra headers [extra_headers] and message [message]. *)
105105+ val revision_identifier :
106106+ Object.Hash.t
107107+ -> Object.Hash.t list
108108+ -> author:string
109109+ -> author_date:date option
110110+ -> committer:string
111111+ -> committer_date:date option
112112+ -> (string * string) array
113113+ -> message:string option
114114+ -> (Object.t, string) result
115115+116116+ (** [snapshot_identifier branches] computes the swhid of the snapshot made of
117117+ branches [branches] where [branches] is a list of branch elements. Each
118118+ branch is of the form [name, target] where [name] is the name of the
119119+ branch and where [target] is a pair made of the identifier of the branch
120120+ and its type. *)
121121+ val snapshot_identifier :
122122+ (string * (string * string) option) list -> (Object.t, string) result
123123+end
···11+(* TODO: remove once we have >= 4.08 *)
22+let result_is_error = function Ok _v -> false | Error _v -> true
33+let result_get_ok = function Ok v -> v | Error _v -> invalid_arg "Result.get_ok"
44+55+66+module Scheme_version = struct
77+ type t = int
88+99+ let of_string = function
1010+ | "1" -> Ok 1
1111+ | invalid -> Error (Format.sprintf "invalid scheme version `%s`" invalid)
1212+1313+ let of_int = function
1414+ | 1 -> Ok 1
1515+ | invalid -> Error (Format.sprintf "invalid scheme version `%d`" invalid)
1616+1717+ let to_int x = x
1818+1919+ let pp fmt v = Format.fprintf fmt "%d" v
2020+2121+ let default = 1
2222+end
2323+2424+module Kind = struct
2525+ type t =
2626+ | Content of string
2727+ | Directory
2828+ | Revision
2929+ | Release
3030+ | Snapshot
3131+3232+ let compare t t' =
3333+ match (t, t') with
3434+ | Directory, Directory
3535+ | Release, Release
3636+ | Revision, Revision
3737+ | Snapshot, Snapshot ->
3838+ 0
3939+ | Content c, Content c' -> String.compare c c'
4040+ | Content _, _ -> 1
4141+ | _, Content _ -> -1
4242+ | Directory, _ -> 1
4343+ | _, Directory -> -1
4444+ | Revision, _ -> 1
4545+ | _, Revision -> -1
4646+ | Release, _ -> 1
4747+ | _, Release -> -1
4848+4949+ let equal t t' = compare t t' = 0
5050+5151+ let of_string = function
5252+ | "cnt" -> Ok (Content "sha1_git")
5353+ | "dir" -> Ok Directory
5454+ | "rel" -> Ok Release
5555+ | "rev" -> Ok Revision
5656+ | "snp" -> Ok Snapshot
5757+ | invalid -> Error (Format.sprintf "invalid object kind `%s`" invalid)
5858+5959+ let to_string = function
6060+ | Content _f -> "cnt"
6161+ | Directory -> "dir"
6262+ | Release -> "rel"
6363+ | Revision -> "rev"
6464+ | Snapshot -> "snp"
6565+6666+ let pp fmt v = Format.fprintf fmt "%s" (to_string v)
6767+end
6868+6969+module Hash = struct
7070+ type t = string
7171+7272+ let compare = String.compare
7373+7474+ let equal = String.equal
7575+7676+ let of_string s =
7777+ let len = ref 0 in
7878+ try
7979+ String.iter
8080+ (function
8181+ | 'a' .. 'f' | '0' .. '9' -> incr len | _invalid_char -> raise Exit )
8282+ s;
8383+ if !len = 40 then Ok s else raise Exit
8484+ with Exit -> Error (Format.sprintf "invalid object hash `%s`" s)
8585+8686+ let to_string v = v
8787+8888+ let pp fmt v = Format.fprintf fmt "%s" v
8989+end
9090+9191+(* TODO: remove once we have > 4.03 *)
9292+let string_split_on_char sep s =
9393+ let r = ref [] in
9494+ let j = ref (String.length s) in
9595+ for i = String.length s - 1 downto 0 do
9696+ if String.unsafe_get s i = sep then begin
9797+ r := String.sub s (i + 1) (!j - i - 1) :: !r;
9898+ j := i
9999+ end
100100+ done;
101101+ String.sub s 0 !j :: !r
102102+103103+module Core_identifier = struct
104104+ type t = Scheme_version.t * Kind.t * Hash.t
105105+106106+ let compare (sch_version, object_type, hash)
107107+ (sch_version', object_type', hash') =
108108+ let scheme_version = sch_version - sch_version' in
109109+ if scheme_version <> 0 then scheme_version
110110+ else
111111+ let object_type = Kind.compare object_type object_type' in
112112+ if object_type <> 0 then object_type else Hash.compare hash hash'
113113+114114+ let equal t t' = compare t t' = 0
115115+116116+ let of_string s =
117117+ match string_split_on_char ':' s with
118118+ | [ "swh"; "1"; t; hash ] -> begin
119119+ match Kind.of_string t with
120120+ | Error _msg as e -> e
121121+ | Ok t -> begin
122122+ match Hash.of_string hash with
123123+ | Error _msg as e -> e
124124+ | Ok hash ->
125125+ let scheme = Scheme_version.default in
126126+ Ok (scheme, t, hash)
127127+ end
128128+ end
129129+ | _whatever -> Error "invalid core identifier"
130130+131131+ let mk scheme typ hash = (scheme, typ, hash)
132132+133133+ let pp fmt (scheme, typ, hash) =
134134+ Format.fprintf fmt "swh:%a:%a:%a" Scheme_version.pp scheme Kind.pp typ
135135+ Hash.pp hash
136136+137137+ let to_string v = Format.asprintf "%a" pp v
138138+139139+ let get_scheme (scheme, _kind, _hash) = scheme
140140+141141+ let get_kind (_scheme, kind, _hash) = kind
142142+143143+ let get_hash (_scheme, _kind, hash) = hash
144144+end
145145+146146+module Qualifier = struct
147147+ type t =
148148+ | Anchor of Core_identifier.t
149149+ | Origin of string
150150+ | Path of string
151151+ | Visit of Core_identifier.t
152152+ | Fragment of (int * int option)
153153+154154+ let int_of_string_opt s = try Some (int_of_string s) with Failure _ -> None
155155+156156+ let of_string s =
157157+ match string_split_on_char '=' s with
158158+ | "lines" :: lines -> begin
159159+ match string_split_on_char '-' (String.concat "" lines) with
160160+ | [ l1 ] -> begin
161161+ match int_of_string_opt l1 with
162162+ | None -> Error "invalid qualifier"
163163+ | Some i -> Ok (Fragment (i, None))
164164+ end
165165+ | [ l1; l2 ] -> begin
166166+ match (int_of_string_opt l1, int_of_string_opt l2) with
167167+ | Some i1, Some i2 -> Ok (Fragment (i1, Some i2))
168168+ | _, _ -> Error "invalid qualifier"
169169+ end
170170+ | _whatever -> Error "invalid qualifier"
171171+ end
172172+ | "path" :: path ->
173173+ (* TODO: check RFC 3987 IRI compliance *)
174174+ let path = String.concat "" path in
175175+ Ok (Path path)
176176+ | "origin" :: url ->
177177+ (* TODO: check RFC 3987 absolute path compliance *)
178178+ let url = String.concat "" url in
179179+ Ok (Origin url)
180180+ | "visit" :: id -> (
181181+ let id = String.concat "" id in
182182+ match Core_identifier.of_string id with
183183+ | Error _msg as e -> e
184184+ | Ok id -> Ok (Visit id) )
185185+ | "anchor" :: id -> (
186186+ let id = String.concat "" id in
187187+ match Core_identifier.of_string id with
188188+ | Error _msg as e -> e
189189+ | Ok id -> Ok (Anchor id) )
190190+ | _whatever -> Error "invalid qualifier"
191191+192192+ let pp fmt = function
193193+ | Anchor id -> Format.fprintf fmt "anchor=%a" Core_identifier.pp id
194194+ | Origin uri -> Format.fprintf fmt "origin=%s" uri
195195+ | Path path -> Format.fprintf fmt "path=%s" path
196196+ | Visit id -> Format.fprintf fmt "visit=%a" Core_identifier.pp id
197197+ | Fragment (l1, l2) -> (
198198+ Format.fprintf fmt "lines=%d" l1;
199199+ match l2 with None -> () | Some l2 -> Format.fprintf fmt "-%d" l2 )
200200+201201+ let to_string q = Format.asprintf "%a" pp q
202202+end
203203+204204+type t = Core_identifier.t * Qualifier.t list
205205+206206+(* TODO: remove once we have >= 4.05 *)
207207+let rec list_find_opt p = function
208208+ | [] -> None
209209+ | x :: l -> if p x then Some x else list_find_opt p l
210210+211211+let of_string s =
212212+ match string_split_on_char ';' s with
213213+ | id :: qualifiers -> begin
214214+ match Core_identifier.of_string id with
215215+ | Error _msg as e -> e
216216+ | Ok object_core_identifier -> begin
217217+ let qualifiers = List.map Qualifier.of_string qualifiers in
218218+ match list_find_opt result_is_error qualifiers with
219219+ | Some (Error _msg as e) -> e
220220+ | Some _ -> assert false
221221+ | None ->
222222+ let qualifiers = List.map result_get_ok qualifiers in
223223+ Ok (object_core_identifier, qualifiers)
224224+ end
225225+ end
226226+ | _whatever -> Error "invalid swhid"
227227+228228+let mk object_core_identifier qualifiers = (object_core_identifier, qualifiers)
229229+230230+let get_core (core, _qualifiers) = core
231231+232232+let get_scheme (core, _qualifiers) = Core_identifier.get_scheme core
233233+234234+let get_kind (core, _qualifiers) = Core_identifier.get_kind core
235235+236236+let get_hash (core, _qualifiers) = Core_identifier.get_hash core
237237+238238+let get_qualifiers (_core, qualifiers) = qualifiers
239239+240240+let pp_qualifiers fmt q = List.iter (Format.fprintf fmt ";%a" Qualifier.pp) q
241241+242242+let pp fmt id =
243243+ let i = get_core id in
244244+ let q = get_qualifiers id in
245245+ Format.fprintf fmt "%a%a" Core_identifier.pp i pp_qualifiers q
246246+247247+let to_string id = Format.asprintf "%a" pp id
+187
vendor/opam/swhid_core/src/object.mli
···11+(** This module contains the various types used to represent a Software Heritage
22+ persistent identifier (swhid). These identifiers are documented on
33+ {{:https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html}
44+ Software Heritage}.
55+66+ In short, a swhid is made of a mandatory core identifier (a scheme version,
77+ a kind and a hash) and an optional list of qualifiers. *)
88+99+module Scheme_version : sig
1010+ (** Module to work with the scheme version of a swhid.*)
1111+1212+ (** The type of scheme versions. *)
1313+ type t
1414+1515+ (** [of_string s] is [Ok v] if [s] is a valid scheme version, otherwise it is
1616+ [Error e]. *)
1717+ val of_string : string -> (t, string) result
1818+1919+ (** [of_int n] is [Ok v] if [n] is a valid scheme version, otherwise it is
2020+ [Error e]. *)
2121+ val of_int : int -> (t, string) result
2222+2323+ (** [to_int v] is the representation of [v] as an integer. *)
2424+ val to_int : t -> int
2525+2626+ (** [pp fmt v] prints [v] on formatter [fmt]. *)
2727+ val pp : Format.formatter -> t -> unit
2828+2929+ (** [default] is the default scheme version. *)
3030+ val default : t
3131+end
3232+3333+module Kind : sig
3434+ (** Module to work with the different kinds of software artifacts a swhid can
3535+ points to. They're documented
3636+ {{:https://docs.softwareheritage.org/devel/swh-model/data-model.html#software-artifacts}
3737+ here}. *)
3838+3939+ (** The type of the different kinds of software artifacts. *)
4040+ type t =
4141+ | Content of string
4242+ (** Contents (AKA "blobs"). the string parameter is the name of the hash
4343+ function used for the computation, defaults to ["sha1_git"] and in
4444+ most use cases you don't care about it. *)
4545+ | Directory (** Directories. *)
4646+ | Revision (** Revisions. *)
4747+ | Release (** Releases. *)
4848+ | Snapshot (** Snapshots. *)
4949+5050+ (** [compare x y] returns [0] if [x] is equal to [y], a negative integer if
5151+ [x] is less than [y], and a positive integer if [x] is greater than [y]. *)
5252+ val compare : t -> t -> int
5353+5454+ (** [equal x y] returns [true] iff [x] is equal to [y]. *)
5555+ val equal : t -> t -> bool
5656+5757+ (** [of_string s] is [Ok v] if [s] is a valid kind of object, otherwise it is
5858+ [Error e]. The valid kinds are ["cnt"], ["dir"], ["rel"], ["rev"] and
5959+ ["snp"]. *)
6060+ val of_string : string -> (t, string) result
6161+6262+ (** [pp fmt v] prints [v] on formatter [fmt]. *)
6363+ val pp : Format.formatter -> t -> unit
6464+6565+ (** [to_string v] is the representation of [v] as a string. *)
6666+ val to_string : t -> string
6767+end
6868+6969+module Hash : sig
7070+ (** Module to work with the hash component of a swhid. *)
7171+7272+ (** The type of hashes. *)
7373+ type t
7474+7575+ (** [compare x y] returns [0] if [x] is equal to [y], a negative integer if
7676+ [x] is less than [y], and a positive integer if [x] is greater than [y]. *)
7777+ val compare : t -> t -> int
7878+7979+ (** [equal x y] returns [true] iff [x] is equal to [y]. *)
8080+ val equal : t -> t -> bool
8181+8282+ (** [of_string s] is [Ok v] if [s] is a valid hash, otherwise it is [Error e].
8383+ A hash is valid if it's made of 40 hexadecimal characters. *)
8484+ val of_string : string -> (t, string) result
8585+8686+ (** [pp fmt v] prints [v] on formatter [fmt]. *)
8787+ val pp : Format.formatter -> t -> unit
8888+8989+ (** [to_string v] is the representation of [v] as a string. *)
9090+ val to_string : t -> string
9191+end
9292+9393+module Core_identifier : sig
9494+ (** Module to work with the core identifier of a swhid. The core identifier of
9595+ a swhid is made of its scheme version, its kind and its hash. They're
9696+ documented
9797+ {{:https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html#core-identifiers}
9898+ here}. *)
9999+100100+ (** The type of core identifiers. *)
101101+ type t
102102+103103+ (** [compare x y] returns [0] if [x] is equal to [y], a negative integer if
104104+ [x] is less than [y], and a positive integer if [x] is greater than [y]. *)
105105+ val compare : t -> t -> int
106106+107107+ (** [equal x y] returns [true] iff [x] is equal to [y]. *)
108108+ val equal : t -> t -> bool
109109+110110+ (** [of_string s] is [Ok v] if [s] is a valid core identifier, otherwise it is
111111+ [Error e]. *)
112112+ val of_string : string -> (t, string) result
113113+114114+ (** [mk scheme kind hash] is the core identifier made of the scheme version
115115+ [scheme], the kind [kind] and the hash [hash]. *)
116116+ val mk : Scheme_version.t -> Kind.t -> Hash.t -> t
117117+118118+ (** [pp fmt v] prints [v] on formatter [fmt]. *)
119119+ val pp : Format.formatter -> t -> unit
120120+121121+ (** [to_string v] is the representation of [v] as a string. *)
122122+ val to_string : t -> string
123123+124124+ (** [get_scheme v] is the scheme version of [v]. *)
125125+ val get_scheme : t -> Scheme_version.t
126126+127127+ (** [get_kind v] is the kind of [v] *)
128128+ val get_kind : t -> Kind.t
129129+130130+ (** [get_hash v] is the hash of [v]. *)
131131+ val get_hash : t -> Hash.t
132132+end
133133+134134+module Qualifier : sig
135135+ (** Module to work with qualifiers. They are documented
136136+ {{:https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html#qualifiers}
137137+ here}. *)
138138+139139+ (** The type of qualifiers. *)
140140+ type t =
141141+ | Anchor of Core_identifier.t
142142+ | Origin of string
143143+ | Path of string
144144+ | Visit of Core_identifier.t
145145+ | Fragment of (int * int option)
146146+147147+ (** [of_string s] is [Ok v] if [s] is a valid qualifier, otherwise it is
148148+ [Error e]. *)
149149+ val of_string : string -> (t, string) result
150150+151151+ (** [pp fmt v] prints [v] on formatter [fmt]. *)
152152+ val pp : Format.formatter -> t -> unit
153153+154154+ (** [to_string v] is the representation of [v] as a string. *)
155155+ val to_string : t -> string
156156+end
157157+158158+(** The type of swhids. *)
159159+type t
160160+161161+(** [of_string s] is [Ok v] if [s] is a valid swhid, otherwise it is [Error e]. *)
162162+val of_string : string -> (t, string) result
163163+164164+(** [mk c q] is the swhid made of the core identifier [c] and the list of
165165+ qualifiers [q]. *)
166166+val mk : Core_identifier.t -> Qualifier.t list -> t
167167+168168+(** [get_core v] is the core identifier of [v]. *)
169169+val get_core : t -> Core_identifier.t
170170+171171+(** [get_scheme v] is the scheme of [v]. *)
172172+val get_scheme : t -> Scheme_version.t
173173+174174+(** [get_kind v] is the kind of [v]. *)
175175+val get_kind : t -> Kind.t
176176+177177+(** [get_hash v] is the hash of [v]. *)
178178+val get_hash : t -> Hash.t
179179+180180+(** [get_qualifiers v] is the list of qualifiers of [v]*)
181181+val get_qualifiers : t -> Qualifier.t list
182182+183183+(** [pp fmt v] prints [v] on formatter [fmt]. *)
184184+val pp : Format.formatter -> t -> unit
185185+186186+(** [to_string v] is the representation of [v] as a string. *)
187187+val to_string : t -> string
+32
vendor/opam/swhid_core/swhid_core.opam
···11+# This file is generated by dune, edit dune-project instead
22+opam-version: "2.0"
33+synopsis: "OCaml library to work with swhids"
44+description:
55+ "swhid_core is an OCaml library to with with Software Heritage persistent identifiers (swhids). This is the core library, for most use cases you should use the swhid library instead."
66+maintainer: ["Léo Andrès <contact@ndrs.fr>"]
77+authors: [
88+ "Léo Andrès <contact@ndrs.fr>" "Dario Pinto <dario.pinto@ocamlpro.com>"
99+]
1010+license: "ISC"
1111+homepage: "https://github.com/ocamlpro/swhid_core"
1212+bug-reports: "https://github.com/ocamlpro/swhid_core/issues"
1313+depends: [
1414+ "ocaml" {>= "4.03"}
1515+ "dune" {>= "1.11"}
1616+ "odoc" {with-doc}
1717+]
1818+build: [
1919+ ["dune" "subst"] {pinned}
2020+ [
2121+ "dune"
2222+ "build"
2323+ "-p"
2424+ name
2525+ "-j"
2626+ jobs
2727+ "@install"
2828+ "@runtest" {with-test}
2929+ "@doc" {with-doc}
3030+ ]
3131+]
3232+dev-repo: "git+https://github.com/ocamlpro/swhid_core.git"
···11+open Swhid_core
22+33+let () =
44+ match
55+ Object.of_string "swh:1:cnt:bac494ecb6840e6b66f21aae7feb847b23f0745a"
66+ with
77+ | Ok v ->
88+ let s = Object.to_string v in
99+ assert (String.equal "swh:1:cnt:bac494ecb6840e6b66f21aae7feb847b23f0745a" s)
1010+ | Error _e -> assert false