SM5TELBWYEM67PHIK2RSGJ3E4TCUT2XLVEZIJGM2GEI45RHSBH6AC
ITQF7AQWZNN733JQHK3PHYDWDGM4JJZVFBLB7F7XPS56PMHCKTMQC
FEHWT3MOW3STJXNBQWXSY6ZDSJIJWVU6EWCXA6KARH7DCYFNM5NQC
MGOB3WXKVDFPE5PPKWC4EEST2CR4NQ7B2FASVYHO5JNNGXC5NNPAC
P35HCXDOT4NKKPTM6OMKEVKBLB3VLEODS7LNKRG7777RLOFDGU2AC
B22T6OTOYMTCUQJGC4DVNB2FNKIYQHLU3I7H2OBWTNLX5WT2Y5RQC
WWOXTYSKCYOBP6H7OQNLZJ22O6WVWCHQNJTHBRQ3ZEJH2KSQF3UAC
HMJFS6WWD2JJZB57HBXRHYS2A2N25YDDAJ443KRMT6K3ALHIBRVQC
LILEG6KRUBQN7AIFHDK5N3X4JPBOLK2ATIO5JGRU3AEG5CCIWBNAC
LB4HIYWKTVYQPH77VAQCFOXNO7JD72SMXTK2U6G7UC2MDQM6VHPAC
.discostore
| D.Events.Message_create { content; channel_id; guild_id; _ } -> (
L.warn (fun m -> m "MESSAGE: %s" content);
| Disco_core.Events.Message_create { content; channel_id; guild_id; _ } -> (
let* () = D.Client.send_message channel_id msg client in
D.Client.disconnect client
let* () = Client.send_message channel_id msg client in
Client.disconnect client
| Some ("status", st) ->
let st =
match st with
| "offline" -> `offline
| "invisible" -> `invisible
| "idle" -> `idle
| "dnd" -> `dnd
| _ -> `online
in
let gw = Client.gateway client in
Disco_core.Gateway.send_presence_update gw ~afk:false st
let v_channel_id = M.Snowflake.of_string v_channel_id in
D.Client.join_voice ~guild_id ~channel_id:v_channel_id client
| Some ("play", "kiff") -> (
let vchan = M.Snowflake.of_string vchan in
let voice = Client.voice client in
let call = Voice.Manager.get ~guild_id voice in
Voice.Manager.Call.join call ~channel_id:vchan >>= function
| Error e ->
let msg =
Msg.fmt "⚠️ Couldn't join voice channel: %a" Voice.Error.pp
e
in
Client.send_message channel_id msg client
| Ok () -> Lwt.return_unit)
| Some ("leave", _) ->
let* s = D.Audio_stream.Ffmpeg.create "./kiff.mp3" in
match s with
| Ok audio_stream ->
D.Client.play_audio_stream ~guild_id audio_stream client
| _ -> Lwt.return_unit)
| Some ("play", "soundbite") -> (
let guild_id = Option.get_exn guild_id in
let* s = D.Audio_stream.Ffmpeg.create "./bite2.mp3" in
match s with
| Ok audio_stream ->
D.Client.play_audio_stream ~guild_id audio_stream client
| _ -> Lwt.return_unit)
| Some ("play", "allstar") -> (
let guild_id = Option.get_exn guild_id in
let* s = D.Audio_stream.Ffmpeg.create "./allstar.mp3" in
match s with
| Ok audio_stream ->
D.Client.play_audio_stream ~guild_id audio_stream client
| _ -> Lwt.return_unit)
| Some ("play", query) -> (
let guild_id = Option.get_exn guild_id in
let* tracks = Ytdl.query query in
match tracks with
| Ok [] | Error _ ->
let msg = Msg.fmt "⚠️ No track found for query: '%s'" query in
D.Client.send_message channel_id msg client
| Ok (track :: _) -> (
let* s = D.Audio_stream.Ffmpeg.create track.url in
match s with
| Ok audio_stream ->
let* () =
D.Client.play_audio_stream ~guild_id audio_stream client
in
let msg =
Msg.fmt "🎵 @{<b>Now playing:@} '%s'\n%s" track.title
track.thumbnail
in
D.Client.send_message channel_id msg client
| Error e ->
client
|> D.Client.send_message channel_id
(Msg.fmt "⚠️ Couldn't play track: '%s'\nReason: %a"
track.title D.Error.pp e)))
| Some ("pause", _) ->
let _guild_id = Option.get_exn guild_id in
Lwt.return_unit
let voice = Client.voice client in
let call = Voice.Manager.get ~guild_id voice in
Voice.Manager.Call.leave call
(* | Some ("join", v_channel_id) ->
let guild_id = Option.get_exn guild_id in
let channel_id = M.Snowflake.of_string v_channel_id in
let voice = Client.voice client in
Voice.Manager.join ~guild_id ~channel_id voice
| Some ("play", "soundbite") -> (
let guild_id = Option.get_exn guild_id in
let* s = Voice.Audio_stream.Ffmpeg.create @@ `File "./bite2.mp3" in
match s with
| Ok audio_stream ->
Client.play_audio_stream ~guild_id audio_stream client
| _ -> Lwt.return_unit)
| Some ("play", "kiff") -> (
let guild_id = Option.get_exn guild_id in
let* s = Voice.Audio_stream.Ffmpeg.create @@ `File "./kiff.mp3" in
match s with
| Ok audio_stream ->
Client.play_audio_stream ~guild_id audio_stream client
| _ -> Lwt.return_unit)
| Some ("play", query) -> (
let guild_id = Option.get_exn guild_id in
let* tracks = Ytdl.get ytdl query in
match tracks with
| Ok [] | Error _ ->
let msg = Msg.fmt "⚠️ No track found for query: '%s'" query in
Client.send_message channel_id msg client
| Ok ((track, i) :: _) -> (
let* s = Voice.Audio_stream.Ffmpeg.create i in
match s with
| Ok audio_stream ->
let* () =
Client.play_audio_stream ~guild_id audio_stream client
in
let thumb fmt = function
| Some t -> Format.fprintf fmt "\n%s" t
| None -> ()
in
let msg =
Msg.fmt "🎵 @{<b>Now playing:@} '%s'%a" track.title thumb
track.thumbnail
in
Client.send_message channel_id msg client
| Error e ->
client
|> Client.send_message channel_id
(Msg.fmt "⚠️ Couldn't play track: '%s'\nReason: %a"
track.title Error.pp e))) *)
include Disco_core.Error
module L = (val Relog.logger ~namespace:"Ytdl" ())
module File_stream = struct
module L = (val Relog.clone (module L) ~namespace:"File_stream")
type op = Live of int | Draining of int | Scheduled of unit Lwt.u
type reader = { ic : Lwt_io.input_channel; mutable op : op }
type t = {
path : string;
oc : Lwt_io.output_channel;
mutable readers : reader Ke.Fke.t;
mutable written : int;
}
let create path =
let oflags = Unix.[ O_WRONLY; O_CREAT ] in
let readers = Ke.Fke.empty in
Lwt_io.open_file ~mode:Lwt_io.Output ~flags:oflags path >|= fun oc ->
{ path; oc; readers; written = 0 }
let notify_readers t written =
let rec notify ?(acc = Ke.Fke.empty) k =
match Ke.Fke.pop k with
| Some ({ ic; _ }, xs) when Lwt_io.is_closed ic -> notify ~acc xs
| Some (({ op = Live n; _ } as r), xs) ->
r.op <- (if written = 0 then Draining n else Live (n + written));
notify ~acc:(Ke.Fke.push acc r) xs
| Some (({ op = Scheduled u; _ } as r), xs) ->
r.op <- (if written = 0 then Draining 0 else Live written);
Lwt.wakeup_later u ();
notify ~acc:(Ke.Fke.push acc r) xs
| Some (({ op = Draining _; _ } as r), xs) ->
if written <> 0 then failwith "write after close?";
notify ~acc:(Ke.Fke.push acc r) xs
| None -> acc
in
t.readers <- notify t.readers
module L = (val Relog.logger ~namespace:__MODULE__ ())
let write t ?(off = 0) ?len b =
if Lwt_io.is_closed t.oc then
failwith "File_stream: cannot write to closed file";
let len = Option.get_lazy (fun () -> Bigstringaf.length b - off) len in
Lwt_io.write_from_exactly_bigstring t.oc b off len >|= fun () ->
t.written <- t.written + len;
notify_readers t len
let reader t =
let iflags = Unix.[ O_RDONLY ] in
Lwt_io.open_file ~mode:Lwt_io.Input ~flags:iflags t.path >|= fun ic ->
let op =
if Lwt_io.is_closed t.oc then Draining t.written else Live t.written
in
let r = { ic; op } in
t.readers <- Ke.Fke.push t.readers r;
r
let close t = Lwt_io.close t.oc >|= fun () -> notify_readers t 0
module Reader = struct
let rec read t ?(off = 0) ?len b =
let len = Option.get_lazy (fun () -> Bigstringaf.length b - off) len in
match t.op with
| _ when Lwt_io.is_closed t.ic -> Lwt.return None
| Scheduled _ -> failwith "File_stream: read already scheduled"
| Draining 0 ->
(if not @@ Lwt_io.is_closed t.ic then Lwt_io.close t.ic
else Lwt.return_unit)
>|= fun () -> None
| Live 0 ->
let p, u = Lwt.wait () in
t.op <- Scheduled u;
p >>= fun () -> read t ~off ~len b
| (Live avail | Draining avail) as op ->
Lwt_io.read_into_bigstring t.ic b off (min len avail) >>= fun n ->
t.op <-
(match op with
| Live _ -> Live (avail - n)
| Draining _ -> Draining (avail - n)
| _ -> failwith "unreachable");
Lwt.return @@ Some n
let close t = Lwt_io.close t.ic
let to_pipe t =
let buf = Bigstringaf.create 0x1000 in
let p = Lwt_pipe.create () in
let rec fwd () =
read t buf >>= function
| None -> Lwt.return_unit
| Some n ->
Lwt_pipe.write p (Bigstringaf.copy buf ~off:0 ~len:n) >>= fun ok ->
if ok then fwd () else close t
in
let f = Lwt.finalize fwd (fun () -> close t) in
Lwt.on_termination f (fun () -> Lwt_pipe.close_nonblock p);
Lwt_pipe.keep p f;
p
end
end
type track = {
id : string;
title : string;
url : string;
thumbnail : string option; [@yojson.option]
}
[@@deriving show, yojson] [@@yojson.allow_extra_fields]
let base_args =
[ "--default-search"; "ytsearch1:"; "-f"; "webm[abr>0]/bestaudio/best" ]
module Store = struct
type t = { dir : string; max_size : int }
let kb = 1024
let mb = 1024 * kb
let create ?(max_size = 500 * mb) ?(dir = "./.discostore") () =
let open Result.Infix in
let+ () =
match Sys.is_directory dir with
| false -> Error.msgf "'%s' already exists and is not a directory" dir
| true -> Ok ()
| exception Sys_error _ ->
Sys.mkdir dir 0o751;
Ok ()
in
{ dir; max_size }
let path t filename = Filename.concat t.dir filename [@@inline]
let info = Yojson.Safe.from_string track in
let title = Yojson.Safe.Util.(member "title" info |> to_string) in
let url = Yojson.Safe.Util.(member "url" info |> to_string) in
let thumbnail =
Yojson.Safe.Util.(member "thumbnail" info |> to_string)
in
Ok [ { title; url; thumbnail } ])
let track_json = Yojson.Safe.from_string track in
let track = track_of_yojson track_json in
Ok [ track ])
let download t track =
let open Lwt.Syntax in
let filename = Store.path t.store track.id in
let p, u = Lwt.wait () in
let w () =
Lwt_process.with_process_full
("", [| t.bin; "-a"; "-"; "-o"; "-" |])
(fun proc ->
let* () = Lwt_io.write proc#stdin track.url in
let* () = Lwt_io.close proc#stdin in
let* sf = File_stream.create filename in
let buf = Bigstringaf.create 0x1000 in
let rec poll () =
Lwt_io.read_into_bigstring proc#stdout buf 0 0x1000 >>= function
| 0 -> File_stream.close sf
| n ->
let* () =
if Lwt.is_sleeping p then
File_stream.reader sf >|= File_stream.Reader.to_pipe
>|= fun s -> Lwt.wakeup_later u (Ok s)
else Lwt.return_unit
in
File_stream.write sf ~off:0 ~len:n buf >>= poll
in
poll () >>= fun () ->
proc#close >>= function
| Unix.WEXITED 0 ->
if Lwt.is_sleeping p then Lwt.return (Error.msg "0 byte stream?")
else Lwt_result.return ()
| WEXITED n | WSTOPPED n | WSIGNALED n ->
Lwt.return (Error.msgf "youtube-dl returned non 0 code: %d" n))
in
Lwt.async (fun () ->
w () >|= fun r ->
match (Lwt.is_sleeping p, r) with
| true, (Error _ as e) -> Lwt.wakeup_later u e
| true, Ok () -> ()
| false, _ -> ());
p
let stream path =
let open Lwt_result.Syntax in
let+ ic = Lwt_io.open_file ~mode:Lwt_io.input path |> Error.catch_lwt in
let p = Lwt_pipe.create () in
let b = Bigstringaf.create 0x1000 in
let blen = Bigstringaf.length b in
let rec fwd () =
Lwt_io.read_into_bigstring ic b 0 blen >>= function
| 0 -> Lwt.return_unit
| len ->
Lwt_pipe.write p (Bigstringaf.copy b ~off:0 ~len) >>= fun ok ->
if ok then fwd () else Lwt.return_unit
in
let f = Lwt.finalize fwd (fun () -> Lwt_io.close ic) in
Lwt.on_termination f (fun () -> Lwt_pipe.close_nonblock p);
Lwt_pipe.keep p f;
p
let get t q =
query t q >>= function
| (Ok [] | Error _) as o -> Lwt.return o
| Ok (track :: _) -> (
let f =
Store.get t.store track.id
|> Option.map (fun path ->
L.debug (fun m -> m "track file '%s' cached in store" path);
stream path |> Lwt_result.map (fun s -> `Stream s))
|> Option.get_lazy (fun () ->
L.debug (fun m ->
m "track '%s' not cached, downloading..." track.id);
download t track |> Lwt_result.map (fun s -> `Stream s))
in
f >|= function Ok i -> Ok [ (track, i) ] | Error _ as e -> e)
let create ?(bin = "youtube-dl") ?store () =
let open Result.Infix in
let* () =
match Sys.command (bin ^ " --version") with
| 0 -> Ok ()
| _ -> Error.msgf "'%s' is not a valid youtube-dl binary" bin
in
let+ store = Option.map Result.return store |> Option.get_lazy Store.create in
{ bin; store }
open Lwt.Infix
module Mpmc = Disco_utils.Mpmc
let rec iter_sink n s =
Mpmc.Sink.pull s >>= function
| Some v ->
Printf.printf "(sink %d) %d\n%!" n v;
iter_sink n s
| None ->
Printf.printf "(sink %d) dropped\n%!" n;
Lwt.return_unit
let rec write_all src l =
match l with
| [] -> Lwt.return_unit
| x :: xs -> Mpmc.Source.write src x >>= fun () -> write_all src xs
let work () =
let sine_stream = Discord.Audio_stream.Gen.sine ~freq:1000 3. in
let buf = Bigstringaf.create (960 * 2 * 4) in
let fill_buff frame =
let f = Faraday.create (1024 * 4) in
Seq.(
0 --^ Bigarray.Array1.dim frame
|> iter (fun i -> Faraday.LE.write_float f frame.{i}));
Faraday.serialize_to_bigstring f
let base () =
let open Lwt.Syntax in
let source, sink = Mpmc.make () in
let* sink2 =
Mpmc.Sink.clone sink >>= fun s ->
Mpmc.Sink.clone s >|= fun s2 -> s2
(* let () = Lwt_main.run (work ())*)
let reads = Lwt.join [ iter_sink 2 sink2 ] in
let writes =
Lwt.join [ write_all source List.(1 -- 10); write_all source List.(1 -- 5) ]
>>= fun () ->
Mpmc.Source.close source >>= fun () -> iter_sink 1 sink
in
Lwt.join [ reads; writes ]
module Mpmc = Dp_utils.Mpmc
let gc () =
let open Lwt.Syntax in
let src, snk = Mpmc.make () in
let clone_drop snk = Mpmc.Sink.clone snk >|= ignore in
let* () = clone_drop snk in
let* () = clone_drop snk in
Gc.full_major ();
Lwt.join
[
(write_all src List.(1 -- 5) >>= fun () -> Mpmc.Source.close src);
iter_sink 1 snk;
]
let source, sink = Mpmc.create () in
let sink2 = Mpmc.Sink.(clone sink |> map ~f:(fun i -> i * 2)) in
let _cancel =
Mpmc.Sink.iter sink2 ~f:(function
| Some v -> Printf.printf "(sink 2) %d\n%!" v
| None -> Printf.printf "(sink 2) dropped\n%!")
let runs = [ ("base", base); ("gc", gc) ] in
let rec run = function
| [] -> Lwt.return_unit
| (name, fn) :: xs ->
Printf.printf "\nrunning %s\n----------\n\n%!" name;
fn () >>= fun () -> run xs
let _cancel =
Mpmc.Sink.iter sink ~f:(function
| Some v -> Printf.printf "(sink 1) %d\n%!" v
| None -> Printf.printf "(sink 1) dropped\n%!")
in
Seq.(1 -- 10 |> iter (fun v -> Mpmc.Source.write source v |> ignore));
Seq.(1 -- 5 |> iter (fun v -> Mpmc.Source.write source v |> ignore));
Mpmc.Source.close source
Lwt_main.run @@ run runs
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/reason@opam:3.7.0@191be014",
"@opam/ptime@opam:0.8.5@0051d642", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/containers@opam:3.2@eb2e6c56"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/reason@opam:3.7.0@494dd52d",
"@opam/ptime@opam:0.8.5@0051d642", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/containers@opam:3.3@dbb7483a"
"@opam/uri@opam:4.1.0@aebd13aa", "@opam/toml@opam:6.0.0@494345d6",
"@opam/stdint@opam:0.7.0@f8c664d1",
"@opam/uri@opam:4.1.0@4aeee2d3", "@opam/toml@opam:6.0.0@5769bd7b",
"@opam/stdint@opam:0.7.0@b96a8a2c",
"@opam/lwt-pipe@opam:0.1@d2ebc796", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/ke@opam:0.4@db86f566", "@opam/hxd@opam:0.3.1@a1c09d49",
"@opam/lwt-pipe@opam:0.1@d2ebc796", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/ke@opam:0.4@10bac7d7", "@opam/hxd@opam:0.3.1@a1c09d49",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/containers-data@opam:3.2@bd14fdbd",
"@opam/containers@opam:3.2@eb2e6c56",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/containers-data@opam:3.3@f1de1b21",
"@opam/containers@opam:3.3@dbb7483a",
"@opam/odoc@opam:1.5.2@94f47c8b",
"@opam/ocamlformat@opam:0.18.0@be695bbf",
"@opam/ocaml-lsp-server@opam:1.5.0@a7ce3ad8",
"@opam/merlin@opam:4.1-412@a9da5f4b",
"@opam/earlybird@opam:1.1.0@4bff968a",
"@opam/alcotest-lwt@opam:1.3.0@b6a6a7a5",
"@opam/alcotest@opam:1.3.0@41168bef"
]
},
"@opam/zed@opam:3.1.0@86c55416": {
"id": "@opam/zed@opam:3.1.0@86c55416",
"name": "@opam/zed",
"version": "opam:3.1.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/51/51e8676ba972e5ad727633c161e404b1#md5:51e8676ba972e5ad727633c161e404b1",
"archive:https://github.com/ocaml-community/zed/archive/3.1.0.tar.gz#md5:51e8676ba972e5ad727633c161e404b1"
],
"opam": {
"name": "zed",
"version": "3.1.0",
"path": "esy.lock/opam/zed.3.1.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/charInfo_width@opam:1.1.0@4296bdfe",
"@opam/camomile@opam:1.0.2@40411a6b",
"@opam/base-bytes@opam:base@19d0c2ff",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/charInfo_width@opam:1.1.0@4296bdfe",
"@opam/camomile@opam:1.0.2@40411a6b",
"@opam/base-bytes@opam:base@19d0c2ff"
"@opam/odoc@opam:1.5.2@3d78163d",
"@opam/ocamlformat@opam:0.18.0@4d3e0b0e",
"@opam/ocaml-lsp-server@opam:1.7.0@2cdbe0ed",
"@opam/merlin@opam:4.2-412@845ccff0",
"@opam/alcotest-lwt@opam:1.4.0@8227b9f9",
"@opam/alcotest@opam:1.4.0@827862b4"
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@0484b3c4",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/cppo@opam:1.6.7@c28ac3ae",
"@opam/biniou@opam:1.2.1@d7570399",
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@1ea9f987",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/cppo@opam:1.6.7@57a6d52c",
"@opam/biniou@opam:1.2.1@420bda02",
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@0484b3c4",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/biniou@opam:1.2.1@d7570399"
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@1ea9f987",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/biniou@opam:1.2.1@420bda02"
"@opam/faraday-lwt-unix@opam:0.7.2@03e4a439",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/faraday-lwt-unix@opam:0.7.3@e0ae04f9",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"@opam/utop@opam:2.7.0@4ba5133d": {
"id": "@opam/utop@opam:2.7.0@4ba5133d",
"name": "@opam/utop",
"version": "opam:2.7.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/sha256/e0/e068ac53df267c3cc0f2f69bbc204404f0362cc4e6472a1fc547e326a63c3fdd#sha256:e068ac53df267c3cc0f2f69bbc204404f0362cc4e6472a1fc547e326a63c3fdd",
"archive:https://github.com/ocaml-community/utop/releases/download/2.7.0/utop-2.7.0.tbz#sha256:e068ac53df267c3cc0f2f69bbc204404f0362cc4e6472a1fc547e326a63c3fdd"
],
"opam": {
"name": "utop",
"version": "2.7.0",
"path": "esy.lock/opam/utop.2.7.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/ocamlfind@opam:1.8.1@b7dc3072",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/lambda-term@opam:3.1.0@8adc2660",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/cppo@opam:1.6.7@c28ac3ae",
"@opam/camomile@opam:1.0.2@40411a6b",
"@opam/base-unix@opam:base@87d0b2eb",
"@opam/base-threads@opam:base@36803084",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/ocamlfind@opam:1.8.1@b7dc3072",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/lambda-term@opam:3.1.0@8adc2660",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/camomile@opam:1.0.2@40411a6b",
"@opam/base-unix@opam:base@87d0b2eb",
"@opam/base-threads@opam:base@36803084"
]
},
"@opam/uri@opam:4.1.0@aebd13aa": {
"id": "@opam/uri@opam:4.1.0@aebd13aa",
"@opam/uri@opam:4.1.0@4aeee2d3": {
"id": "@opam/uri@opam:4.1.0@4aeee2d3",
"ocaml@4.12.0@d41d8cd9", "@opam/stringext@opam:1.6.0@104bc94b",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/angstrom@opam:0.15.0@48ede9cb",
"ocaml@4.12.0@d41d8cd9", "@opam/stringext@opam:1.6.0@d9079793",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/angstrom@opam:0.15.0@105656d9",
"ocaml@4.12.0@d41d8cd9", "@opam/stringext@opam:1.6.0@104bc94b",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/angstrom@opam:0.15.0@48ede9cb"
"ocaml@4.12.0@d41d8cd9", "@opam/stringext@opam:1.6.0@d9079793",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/angstrom@opam:0.15.0@105656d9"
"archive:https://opam.ocaml.org/cache/sha256/51/516394dd4a5c31726997c51d66aa31cacb91e3c46d4e16c7699130e204042530#sha256:516394dd4a5c31726997c51d66aa31cacb91e3c46d4e16c7699130e204042530",
"archive:https://github.com/ocsigen/tyxml/releases/download/4.4.0/tyxml-4.4.0.tbz#sha256:516394dd4a5c31726997c51d66aa31cacb91e3c46d4e16c7699130e204042530"
"archive:https://opam.ocaml.org/cache/sha256/c6/c69accef5df4dd89d38f6aa0baad01e8fda4e9e98bb7dad61bec1452c5716068#sha256:c69accef5df4dd89d38f6aa0baad01e8fda4e9e98bb7dad61bec1452c5716068",
"archive:https://github.com/ocsigen/tyxml/releases/download/4.5.0/tyxml-4.5.0.tbz#sha256:c69accef5df4dd89d38f6aa0baad01e8fda4e9e98bb7dad61bec1452c5716068"
"@opam/seq@opam:base@d8d7de1d", "@opam/re@opam:1.9.0@d4d5e13d",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/seq@opam:base@d8d7de1d", "@opam/re@opam:1.9.0@9373f267",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/seq@opam:base@d8d7de1d", "@opam/re@opam:1.9.0@d4d5e13d",
"@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/trie@opam:1.0.0@d2efc587": {
"id": "@opam/trie@opam:1.0.0@d2efc587",
"name": "@opam/trie",
"version": "opam:1.0.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/84/84519b5f8bd92490bfc68a52f706ba14#md5:84519b5f8bd92490bfc68a52f706ba14",
"archive:https://github.com/kandu/trie/archive/1.0.0.tar.gz#md5:84519b5f8bd92490bfc68a52f706ba14"
],
"opam": {
"name": "trie",
"version": "1.0.0",
"path": "esy.lock/opam/trie.1.0.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1"
"@opam/seq@opam:base@d8d7de1d", "@opam/re@opam:1.9.0@9373f267",
"@opam/dune@opam:2.8.4@4ac03159"
"@opam/time_now@opam:v0.14.0@5e4046b3": {
"id": "@opam/time_now@opam:v0.14.0@5e4046b3",
"name": "@opam/time_now",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/a9/a93116938783587f8b9f5152dd543037#md5:a93116938783587f8b9f5152dd543037",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/time_now-v0.14.0.tar.gz#md5:a93116938783587f8b9f5152dd543037"
],
"opam": {
"name": "time_now",
"version": "v0.14.0",
"path": "esy.lock/opam/time_now.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppx_optcomp@opam:v0.14.1@181fd1a0",
"@opam/ppx_base@opam:v0.14.0@b4702ed9",
"@opam/jst-config@opam:v0.14.0@d0d7469e",
"@opam/jane-street-headers@opam:v0.14.0@59432b6a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppx_optcomp@opam:v0.14.1@181fd1a0",
"@opam/ppx_base@opam:v0.14.0@b4702ed9",
"@opam/jst-config@opam:v0.14.0@d0d7469e",
"@opam/jane-street-headers@opam:v0.14.0@59432b6a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/stringext@opam:1.6.0@104bc94b": {
"id": "@opam/stringext@opam:1.6.0@104bc94b",
"@opam/stringext@opam:1.6.0@d9079793": {
"id": "@opam/stringext@opam:1.6.0@d9079793",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/base@opam:v0.14.1@d14008e2",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/base@opam:v0.14.1@9b424fee",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/base@opam:v0.14.1@d14008e2"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/base@opam:v0.14.1@9b424fee"
"@opam/sexplib@opam:v0.14.0@f67f18de": {
"id": "@opam/sexplib@opam:v0.14.0@f67f18de",
"name": "@opam/sexplib",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/6e/6e230eae22face46cb8645e53e351067#md5:6e230eae22face46cb8645e53e351067",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/sexplib-v0.14.0.tar.gz#md5:6e230eae22face46cb8645e53e351067"
],
"opam": {
"name": "sexplib",
"version": "v0.14.0",
"path": "esy.lock/opam/sexplib.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/parsexp@opam:v0.14.0@c5bad87a", "@opam/num@opam:1.4@a5195c8d",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/parsexp@opam:v0.14.0@c5bad87a", "@opam/num@opam:1.4@a5195c8d",
"@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/merlin-extend@opam:0.6@404f814c",
"@opam/menhir@opam:20210310@50de9216",
"@opam/fix@opam:20201120@5c318621", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/merlin-extend@opam:0.6@88755c91",
"@opam/menhir@opam:20210419@11c42419",
"@opam/fix@opam:20201120@0b212fb9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/merlin-extend@opam:0.6@404f814c",
"@opam/menhir@opam:20210310@50de9216",
"@opam/fix@opam:20201120@5c318621", "@opam/dune@opam:2.8.4@1490e2a1"
"@opam/merlin-extend@opam:0.6@88755c91",
"@opam/menhir@opam:20210419@11c42419",
"@opam/fix@opam:20201120@0b212fb9", "@opam/dune@opam:2.8.4@4ac03159"
},
"@opam/react@opam:1.2.1@0e11855f": {
"id": "@opam/react@opam:1.2.1@0e11855f",
"name": "@opam/react",
"version": "opam:1.2.1",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/ce/ce1454438ce4e9d2931248d3abba1fcc#md5:ce1454438ce4e9d2931248d3abba1fcc",
"archive:http://erratique.ch/software/react/releases/react-1.2.1.tbz#md5:ce1454438ce4e9d2931248d3abba1fcc"
],
"opam": {
"name": "react",
"version": "1.2.1",
"path": "esy.lock/opam/react.1.2.1"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/topkg@opam:1.0.3@e4e10f1c",
"@opam/ocamlfind@opam:1.8.1@b7dc3072",
"@opam/ocamlbuild@opam:0.14.0@6ac75d03",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [ "ocaml@4.12.0@d41d8cd9" ]
"archive:https://opam.ocaml.org/cache/sha256/3e/3eeb91e03966662284a3222e612dee7f4fa2b7637c53d9572d2a74134bb96d7a#sha256:3eeb91e03966662284a3222e612dee7f4fa2b7637c53d9572d2a74134bb96d7a",
"archive:https://github.com/ocaml-ppx/ppxlib/releases/download/0.22.0/ppxlib-0.22.0.tbz#sha256:3eeb91e03966662284a3222e612dee7f4fa2b7637c53d9572d2a74134bb96d7a"
"archive:https://opam.ocaml.org/cache/sha256/d0/d0e8a1ebdc6220b1574d7a926f008460c5118ccef79bf9a0ce0242f34cff225a#sha256:d0e8a1ebdc6220b1574d7a926f008460c5118ccef79bf9a0ce0242f34cff225a",
"archive:https://github.com/ocaml-ppx/ppxlib/releases/download/0.22.2/ppxlib-0.22.2.tbz#sha256:d0e8a1ebdc6220b1574d7a926f008460c5118ccef79bf9a0ce0242f34cff225a"
"@opam/ocaml-migrate-parsetree@opam:2.1.0@a3b6747d",
"@opam/ocaml-compiler-libs@opam:v0.12.3@f0f069bd",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/ocaml-migrate-parsetree@opam:2.2.0@d1434b92",
"@opam/ocaml-compiler-libs@opam:v0.12.3@316a19dc",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/ocaml-migrate-parsetree@opam:2.1.0@a3b6747d",
"@opam/ocaml-compiler-libs@opam:v0.12.3@f0f069bd",
"@opam/dune@opam:2.8.4@1490e2a1"
"@opam/ocaml-migrate-parsetree@opam:2.2.0@d1434b92",
"@opam/ocaml-compiler-libs@opam:v0.12.3@316a19dc",
"@opam/dune@opam:2.8.4@4ac03159"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@116b53d6",
"@opam/ppx_js_style@opam:v0.14.0@10b020a8",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@116b53d6",
"@opam/ppx_js_style@opam:v0.14.0@10b020a8",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4": {
"id": "@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4",
"name": "@opam/ppx_sexp_conv",
"version": "opam:v0.14.3",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/25/25caf01245e0113e035ccefe275f85d9#md5:25caf01245e0113e035ccefe275f85d9",
"archive:https://github.com/janestreet/ppx_sexp_conv/archive/v0.14.3.tar.gz#md5:25caf01245e0113e035ccefe275f85d9"
],
"opam": {
"name": "ppx_sexp_conv",
"version": "v0.14.3",
"path": "esy.lock/opam/ppx_sexp_conv.v0.14.3"
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.2@61009929",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@605a6997",
"@opam/ppx_js_style@opam:v0.14.1@03f2d62f",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/base@opam:v0.14.1@9b424fee",
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.2@61009929",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@605a6997",
"@opam/ppx_js_style@opam:v0.14.1@03f2d62f",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/base@opam:v0.14.1@9b424fee"
"archive:https://opam.ocaml.org/cache/md5/4b/4ba24037b097bfedbbeb5a5c577694e1#md5:4ba24037b097bfedbbeb5a5c577694e1",
"archive:https://github.com/janestreet/ppx_optcomp/archive/v0.14.1.tar.gz#md5:4ba24037b097bfedbbeb5a5c577694e1"
"archive:https://opam.ocaml.org/cache/md5/2d/2d79afa4f954aeafb81b64ecfc11c3fb#md5:2d79afa4f954aeafb81b64ecfc11c3fb",
"archive:https://github.com/janestreet/ppx_js_style/archive/refs/tags/v0.14.1.tar.gz#md5:2d79afa4f954aeafb81b64ecfc11c3fb"
"name": "ppx_optcomp",
"version": "v0.14.1",
"path": "esy.lock/opam/ppx_optcomp.v0.14.1"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/stdio@opam:v0.14.0@a624e254",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/stdio@opam:v0.14.0@a624e254",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_js_style@opam:v0.14.0@10b020a8": {
"id": "@opam/ppx_js_style@opam:v0.14.0@10b020a8",
"name": "@opam/ppx_js_style",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/ea/eab9c17616a2ba4cbd69a88db76070fd#md5:eab9c17616a2ba4cbd69a88db76070fd",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_js_style-v0.14.0.tar.gz#md5:eab9c17616a2ba4cbd69a88db76070fd"
],
"opam": {
"version": "v0.14.0",
"path": "esy.lock/opam/ppx_js_style.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/octavius@opam:1.2.2@2c6624f5",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/octavius@opam:1.2.2@2c6624f5",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_inline_test@opam:v0.14.1@2e4fdd8d": {
"id": "@opam/ppx_inline_test@opam:v0.14.1@2e4fdd8d",
"name": "@opam/ppx_inline_test",
"version": "opam:v0.14.1",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/13/132754f0757188c3b700a2c5b6a2fb3f#md5:132754f0757188c3b700a2c5b6a2fb3f",
"archive:https://github.com/janestreet/ppx_inline_test/archive/v0.14.1.tar.gz#md5:132754f0757188c3b700a2c5b6a2fb3f"
],
"opam": {
"name": "ppx_inline_test",
"path": "esy.lock/opam/ppx_inline_test.v0.14.1"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/time_now@opam:v0.14.0@5e4046b3",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/time_now@opam:v0.14.0@5e4046b3",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_here@opam:v0.14.0@5ccc1c01": {
"id": "@opam/ppx_here@opam:v0.14.0@5ccc1c01",
"name": "@opam/ppx_here",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/bb/bb3bbde0964a1f866de09f3df44def4d#md5:bb3bbde0964a1f866de09f3df44def4d",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_here-v0.14.0.tar.gz#md5:bb3bbde0964a1f866de09f3df44def4d"
],
"opam": {
"name": "ppx_here",
"version": "v0.14.0",
"path": "esy.lock/opam/ppx_here.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_hash@opam:v0.14.0@8e9618e4": {
"id": "@opam/ppx_hash@opam:v0.14.0@8e9618e4",
"name": "@opam/ppx_hash",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/b7/b78aee19bb4469731f9626b04fe7f341#md5:b78aee19bb4469731f9626b04fe7f341",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_hash-v0.14.0.tar.gz#md5:b78aee19bb4469731f9626b04fe7f341"
],
"opam": {
"name": "ppx_hash",
"version": "v0.14.0",
"path": "esy.lock/opam/ppx_hash.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4",
"@opam/ppx_compare@opam:v0.14.0@615de7a6",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4",
"@opam/ppx_compare@opam:v0.14.0@615de7a6",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_expect@opam:v0.14.1@cee36131": {
"id": "@opam/ppx_expect@opam:v0.14.1@cee36131",
"name": "@opam/ppx_expect",
"version": "opam:v0.14.1",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/9c/9cc03dcabb00c72e17f7f5b0e4d28603#md5:9cc03dcabb00c72e17f7f5b0e4d28603",
"archive:https://github.com/janestreet/ppx_expect/archive/v0.14.1.tar.gz#md5:9cc03dcabb00c72e17f7f5b0e4d28603"
],
"opam": {
"name": "ppx_expect",
"version": "v0.14.1",
"path": "esy.lock/opam/ppx_expect.v0.14.1"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/stdio@opam:v0.14.0@a624e254",
"@opam/re@opam:1.9.0@d4d5e13d", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_inline_test@opam:v0.14.1@2e4fdd8d",
"@opam/ppx_here@opam:v0.14.0@5ccc1c01",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/stdio@opam:v0.14.0@a624e254",
"@opam/re@opam:1.9.0@d4d5e13d", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_inline_test@opam:v0.14.1@2e4fdd8d",
"@opam/ppx_here@opam:v0.14.0@5ccc1c01",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_enumerate@opam:v0.14.0@63db8245": {
"id": "@opam/ppx_enumerate@opam:v0.14.0@63db8245",
"name": "@opam/ppx_enumerate",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/18/188421af960759f6e45dd748f4f08e8d#md5:188421af960759f6e45dd748f4f08e8d",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_enumerate-v0.14.0.tar.gz#md5:188421af960759f6e45dd748f4f08e8d"
],
"opam": {
"name": "ppx_enumerate",
"version": "v0.14.0",
"path": "esy.lock/opam/ppx_enumerate.v0.14.0"
"path": "esy.lock/opam/ppx_js_style.v0.14.1"
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.2@61009929",
"@opam/octavius@opam:1.2.2@2205cc65",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/base@opam:v0.14.1@9b424fee",
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.2@61009929",
"@opam/octavius@opam:1.2.2@2205cc65",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/base@opam:v0.14.1@9b424fee"
"@opam/ppx_deriving_yojson@opam:3.6.1@faf11a7c": {
"id": "@opam/ppx_deriving_yojson@opam:3.6.1@faf11a7c",
"name": "@opam/ppx_deriving_yojson",
"version": "opam:3.6.1",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/sha256/71/712ee9207c70dd144e72cd689bee2d2beb120b804e77c74ec6f7b843a88944e6#sha256:712ee9207c70dd144e72cd689bee2d2beb120b804e77c74ec6f7b843a88944e6",
"archive:https://github.com/ocaml-ppx/ppx_deriving_yojson/releases/download/v3.6.1/ppx_deriving_yojson-v3.6.1.tbz#sha256:712ee9207c70dd144e72cd689bee2d2beb120b804e77c74ec6f7b843a88944e6"
],
"opam": {
"name": "ppx_deriving_yojson",
"version": "3.6.1",
"path": "esy.lock/opam/ppx_deriving_yojson.3.6.1"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/result@opam:1.5@6b753c82",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_deriving@opam:5.2.1@479736f0",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/result@opam:1.5@6b753c82",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_deriving@opam:5.2.1@479736f0",
"@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/ppx_deriving@opam:5.2.1@479736f0": {
"id": "@opam/ppx_deriving@opam:5.2.1@479736f0",
"@opam/ppx_deriving@opam:5.2.1@089e5dd3": {
"id": "@opam/ppx_deriving@opam:5.2.1@089e5dd3",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/ppxlib@opam:0.22.2@61009929",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/ppxlib@opam:0.22.2@61009929",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/re@opam:1.9.0@d4d5e13d", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/re@opam:1.9.0@9373f267", "@opam/ppxlib@opam:0.22.2@61009929",
"@opam/cppo@opam:1.6.7@c28ac3ae",
"@opam/containers@opam:3.2@eb2e6c56",
"@opam/bigarray-compat@opam:1.0.0@3a87ad65",
"@opam/cppo@opam:1.6.7@57a6d52c",
"@opam/containers@opam:3.3@dbb7483a",
"@opam/bigarray-compat@opam:1.0.0@951830c6",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/re@opam:1.9.0@d4d5e13d", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/re@opam:1.9.0@9373f267", "@opam/ppxlib@opam:0.22.2@61009929",
"@opam/containers@opam:3.2@eb2e6c56",
"@opam/bigarray-compat@opam:1.0.0@3a87ad65"
]
},
"@opam/ppx_compare@opam:v0.14.0@615de7a6": {
"id": "@opam/ppx_compare@opam:v0.14.0@615de7a6",
"name": "@opam/ppx_compare",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/91/9149b3a0c954fe2cef2b0705d254b9e3#md5:9149b3a0c954fe2cef2b0705d254b9e3",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_compare-v0.14.0.tar.gz#md5:9149b3a0c954fe2cef2b0705d254b9e3"
],
"opam": {
"name": "ppx_compare",
"version": "v0.14.0",
"path": "esy.lock/opam/ppx_compare.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
]
},
"@opam/ppx_cold@opam:v0.14.0@345dec7c": {
"id": "@opam/ppx_cold@opam:v0.14.0@345dec7c",
"name": "@opam/ppx_cold",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/6a/6a61807cd3b105b8c885bd2076986339#md5:6a61807cd3b105b8c885bd2076986339",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_cold-v0.14.0.tar.gz#md5:6a61807cd3b105b8c885bd2076986339"
],
"opam": {
"name": "ppx_cold",
"version": "v0.14.0",
"path": "esy.lock/opam/ppx_cold.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
"@opam/containers@opam:3.3@dbb7483a",
"@opam/bigarray-compat@opam:1.0.0@951830c6"
"@opam/ppx_base@opam:v0.14.0@b4702ed9": {
"id": "@opam/ppx_base@opam:v0.14.0@b4702ed9",
"name": "@opam/ppx_base",
"version": "opam:v0.14.0",
"@opam/pp@opam:1.1.2@89ad03b5": {
"id": "@opam/pp@opam:1.1.2@89ad03b5",
"name": "@opam/pp",
"version": "opam:1.1.2",
"archive:https://opam.ocaml.org/cache/md5/b2/b29a24907e60f42e050ad90e5209bb92#md5:b29a24907e60f42e050ad90e5209bb92",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_base-v0.14.0.tar.gz#md5:b29a24907e60f42e050ad90e5209bb92"
"archive:https://opam.ocaml.org/cache/sha256/e4/e4a4e98d96b1bb76950fcd6da4e938c86d989df4d7e48f02f7a44595f5af1d56#sha256:e4a4e98d96b1bb76950fcd6da4e938c86d989df4d7e48f02f7a44595f5af1d56",
"archive:https://github.com/ocaml-dune/pp/releases/download/1.1.2/pp-1.1.2.tbz#sha256:e4a4e98d96b1bb76950fcd6da4e938c86d989df4d7e48f02f7a44595f5af1d56"
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4",
"@opam/ppx_js_style@opam:v0.14.0@10b020a8",
"@opam/ppx_hash@opam:v0.14.0@8e9618e4",
"@opam/ppx_enumerate@opam:v0.14.0@63db8245",
"@opam/ppx_compare@opam:v0.14.0@615de7a6",
"@opam/ppx_cold@opam:v0.14.0@345dec7c",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4",
"@opam/ppx_js_style@opam:v0.14.0@10b020a8",
"@opam/ppx_hash@opam:v0.14.0@8e9618e4",
"@opam/ppx_enumerate@opam:v0.14.0@63db8245",
"@opam/ppx_compare@opam:v0.14.0@615de7a6",
"@opam/ppx_cold@opam:v0.14.0@345dec7c",
"@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/ppx_assert@opam:v0.14.0@877b5900": {
"id": "@opam/ppx_assert@opam:v0.14.0@877b5900",
"name": "@opam/ppx_assert",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/53/535b5f241eb7f10da8c044c26afbc186#md5:535b5f241eb7f10da8c044c26afbc186",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_assert-v0.14.0.tar.gz#md5:535b5f241eb7f10da8c044c26afbc186"
],
"opam": {
"name": "ppx_assert",
"version": "v0.14.0",
"path": "esy.lock/opam/ppx_assert.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4",
"@opam/ppx_here@opam:v0.14.0@5ccc1c01",
"@opam/ppx_compare@opam:v0.14.0@615de7a6",
"@opam/ppx_cold@opam:v0.14.0@345dec7c",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/ppx_sexp_conv@opam:v0.14.3@1ee195f4",
"@opam/ppx_here@opam:v0.14.0@5ccc1c01",
"@opam/ppx_compare@opam:v0.14.0@615de7a6",
"@opam/ppx_cold@opam:v0.14.0@345dec7c",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159"
"ocaml@4.12.0@d41d8cd9", "@opam/uri@opam:4.1.0@aebd13aa",
"@opam/ssl@opam:0.5.10@ae7a31df",
"@opam/ocaml-syntax-shims@opam:1.0.0@a9aa3bfa",
"@opam/magic-mime@opam:1.1.3@8faa00ed",
"@opam/lwt_ssl@opam:1.1.3@653af026", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"ocaml@4.12.0@d41d8cd9", "@opam/uri@opam:4.1.0@4aeee2d3",
"@opam/ssl@opam:0.5.10@b044030d",
"@opam/ocaml-syntax-shims@opam:1.0.0@9f361fbb",
"@opam/magic-mime@opam:1.1.3@b3f714ac",
"@opam/lwt_ssl@opam:1.1.3@653af026", "@opam/lwt@opam:5.4.1@37ffbe37",
"ocaml@4.12.0@d41d8cd9", "@opam/uri@opam:4.1.0@aebd13aa",
"@opam/ssl@opam:0.5.10@ae7a31df",
"@opam/ocaml-syntax-shims@opam:1.0.0@a9aa3bfa",
"@opam/magic-mime@opam:1.1.3@8faa00ed",
"@opam/lwt_ssl@opam:1.1.3@653af026", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"ocaml@4.12.0@d41d8cd9", "@opam/uri@opam:4.1.0@4aeee2d3",
"@opam/ssl@opam:0.5.10@b044030d",
"@opam/ocaml-syntax-shims@opam:1.0.0@9f361fbb",
"@opam/magic-mime@opam:1.1.3@b3f714ac",
"@opam/lwt_ssl@opam:1.1.3@653af026", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/bigstringaf@opam:0.7.0@4784da9b"
]
},
"@opam/path_glob@opam:0.2@e298ab39": {
"id": "@opam/path_glob@opam:0.2@e298ab39",
"name": "@opam/path_glob",
"version": "opam:0.2",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/sha256/5e/5e09a2148876b68ac8fb315679ba69b1e207ced55d91a3ea5b3046f917102a07#sha256:5e09a2148876b68ac8fb315679ba69b1e207ced55d91a3ea5b3046f917102a07",
"archive:https://gasche.gitlab.io/path_glob/releases/path_glob-0.2.tbz#sha256:5e09a2148876b68ac8fb315679ba69b1e207ced55d91a3ea5b3046f917102a07"
],
"opam": {
"name": "path_glob",
"version": "0.2",
"path": "esy.lock/opam/path_glob.0.2"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/parsexp@opam:v0.14.0@c5bad87a": {
"id": "@opam/parsexp@opam:v0.14.0@c5bad87a",
"name": "@opam/parsexp",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/c2/c2c5fa3f9b082e4fe729e2cf95cacd3b#md5:c2c5fa3f9b082e4fe729e2cf95cacd3b",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/parsexp-v0.14.0.tar.gz#md5:c2c5fa3f9b082e4fe729e2cf95cacd3b"
],
"opam": {
"name": "parsexp",
"version": "v0.14.0",
"path": "esy.lock/opam/parsexp.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
"@opam/bigstringaf@opam:0.7.0@152be977"
"ocaml@4.12.0@d41d8cd9", "@opam/tyxml@opam:4.4.0@1dca5713",
"@opam/result@opam:1.5@6b753c82", "@opam/fpath@opam:0.7.3@674d8125",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/cppo@opam:1.6.7@c28ac3ae",
"ocaml@4.12.0@d41d8cd9", "@opam/tyxml@opam:4.5.0@4676c9e9",
"@opam/result@opam:1.5@1c6a6533", "@opam/fpath@opam:0.7.3@674d8125",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/cppo@opam:1.6.7@57a6d52c",
"ocaml@4.12.0@d41d8cd9", "@opam/tyxml@opam:4.4.0@1dca5713",
"@opam/result@opam:1.5@6b753c82", "@opam/fpath@opam:0.7.3@674d8125",
"@opam/dune@opam:2.8.4@1490e2a1",
"ocaml@4.12.0@d41d8cd9", "@opam/tyxml@opam:4.5.0@4676c9e9",
"@opam/result@opam:1.5@1c6a6533", "@opam/fpath@opam:0.7.3@674d8125",
"@opam/dune@opam:2.8.4@4ac03159",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/cppo@opam:1.6.7@c28ac3ae",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/cppo@opam:1.6.7@57a6d52c",
"@opam/stdio@opam:v0.14.0@a624e254", "@opam/re@opam:1.9.0@d4d5e13d",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/odoc@opam:1.5.2@94f47c8b",
"@opam/ocaml-version@opam:3.1.0@a34970ed",
"@opam/menhirSdk@opam:20210310@5abaafca",
"@opam/menhirLib@opam:20210310@f9315713",
"@opam/menhir@opam:20210310@50de9216",
"@opam/stdio@opam:v0.14.0@a5affb43", "@opam/re@opam:1.9.0@9373f267",
"@opam/ppxlib@opam:0.22.2@61009929",
"@opam/odoc@opam:1.5.2@3d78163d",
"@opam/ocaml-version@opam:3.1.0@731fe067",
"@opam/menhirSdk@opam:20210419@9c7661a4",
"@opam/menhirLib@opam:20210419@0b3db8d0",
"@opam/menhir@opam:20210419@11c42419",
"@opam/fix@opam:20201120@5c318621",
"@opam/dune-build-info@opam:2.8.5@1dae72be",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/fix@opam:20201120@0b212fb9",
"@opam/dune-build-info@opam:2.8.5@e0f9f654",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/stdio@opam:v0.14.0@a624e254", "@opam/re@opam:1.9.0@d4d5e13d",
"@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/odoc@opam:1.5.2@94f47c8b",
"@opam/ocaml-version@opam:3.1.0@a34970ed",
"@opam/menhirSdk@opam:20210310@5abaafca",
"@opam/menhirLib@opam:20210310@f9315713",
"@opam/menhir@opam:20210310@50de9216",
"@opam/stdio@opam:v0.14.0@a5affb43", "@opam/re@opam:1.9.0@9373f267",
"@opam/ppxlib@opam:0.22.2@61009929",
"@opam/odoc@opam:1.5.2@3d78163d",
"@opam/ocaml-version@opam:3.1.0@731fe067",
"@opam/menhirSdk@opam:20210419@9c7661a4",
"@opam/menhirLib@opam:20210419@0b3db8d0",
"@opam/menhir@opam:20210419@11c42419",
"@opam/fix@opam:20201120@5c318621",
"@opam/dune-build-info@opam:2.8.5@1dae72be",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/fix@opam:20201120@0b212fb9",
"@opam/dune-build-info@opam:2.8.5@e0f9f654",
"@opam/dune@opam:2.8.4@4ac03159",
"archive:https://opam.ocaml.org/cache/sha256/38/387b788ee4c0537f1fe02c25e05f0335af424828fc6fe940acc0db5948a5a71f#sha256:387b788ee4c0537f1fe02c25e05f0335af424828fc6fe940acc0db5948a5a71f",
"archive:https://github.com/ocaml-ppx/ocaml-migrate-parsetree/releases/download/v2.1.0/ocaml-migrate-parsetree-v2.1.0.tbz#sha256:387b788ee4c0537f1fe02c25e05f0335af424828fc6fe940acc0db5948a5a71f"
"archive:https://opam.ocaml.org/cache/sha256/b2/b2a68f3d3899cec3a50a99b05738295cc8a18672680406d0f68fbc95c01f1ba1#sha256:b2a68f3d3899cec3a50a99b05738295cc8a18672680406d0f68fbc95c01f1ba1",
"archive:https://github.com/ocaml-ppx/ocaml-migrate-parsetree/releases/download/v2.2.0/ocaml-migrate-parsetree-v2.2.0.tbz#sha256:b2a68f3d3899cec3a50a99b05738295cc8a18672680406d0f68fbc95c01f1ba1"
"archive:https://opam.ocaml.org/cache/md5/2c/2c19731536a4f62923554c1947c39211#md5:2c19731536a4f62923554c1947c39211",
"archive:https://github.com/ocaml/ocaml-lsp/releases/download/1.5.0/jsonrpc-1.5.0.tbz#md5:2c19731536a4f62923554c1947c39211"
"archive:https://opam.ocaml.org/cache/sha256/ca/ca43b6608366ddf891d7c1e1cc38de2c7f93a6da0511de164959db1f88fc42ed#sha256:ca43b6608366ddf891d7c1e1cc38de2c7f93a6da0511de164959db1f88fc42ed",
"archive:https://github.com/ocaml/ocaml-lsp/releases/download/1.7.0/jsonrpc-1.7.0.tbz#sha256:ca43b6608366ddf891d7c1e1cc38de2c7f93a6da0511de164959db1f88fc42ed"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/stdlib-shims@opam:0.3.0@0d088929",
"@opam/result@opam:1.5@6b753c82",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@116b53d6",
"@opam/ocamlfind@opam:1.8.1@b7dc3072",
"@opam/dune-build-info@opam:2.8.5@1dae72be",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/dot-merlin-reader@opam:4.1@120afa42",
"@opam/csexp@opam:1.5.1@f2f16ef6", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/result@opam:1.5@1c6a6533", "@opam/re@opam:1.9.0@9373f267",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@605a6997",
"@opam/pp@opam:1.1.2@89ad03b5",
"@opam/dune-build-info@opam:2.8.5@e0f9f654",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/dot-merlin-reader@opam:4.1@84436e1c",
"@opam/csexp@opam:1.5.1@8a8fb3a7", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/stdlib-shims@opam:0.3.0@0d088929",
"@opam/result@opam:1.5@6b753c82",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@116b53d6",
"@opam/ocamlfind@opam:1.8.1@b7dc3072",
"@opam/dune-build-info@opam:2.8.5@1dae72be",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/dot-merlin-reader@opam:4.1@120afa42",
"@opam/csexp@opam:1.5.1@f2f16ef6"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/result@opam:1.5@1c6a6533", "@opam/re@opam:1.9.0@9373f267",
"@opam/ppx_yojson_conv_lib@opam:v0.14.0@605a6997",
"@opam/pp@opam:1.1.2@89ad03b5",
"@opam/dune-build-info@opam:2.8.5@e0f9f654",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/dot-merlin-reader@opam:4.1@84436e1c",
"@opam/csexp@opam:1.5.1@8a8fb3a7"
"@opam/mew_vi@opam:0.5.0@cf66c299": {
"id": "@opam/mew_vi@opam:0.5.0@cf66c299",
"name": "@opam/mew_vi",
"version": "opam:0.5.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/34/341e9a9a20383641015bf503952906bc#md5:341e9a9a20383641015bf503952906bc",
"archive:https://github.com/kandu/mew_vi/archive/0.5.0.tar.gz#md5:341e9a9a20383641015bf503952906bc"
],
"opam": {
"name": "mew_vi",
"version": "0.5.0",
"path": "esy.lock/opam/mew_vi.0.5.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/mew@opam:0.1.0@a74f69d6", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/mew@opam:0.1.0@a74f69d6", "@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/mew@opam:0.1.0@a74f69d6": {
"id": "@opam/mew@opam:0.1.0@a74f69d6",
"name": "@opam/mew",
"version": "opam:0.1.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/22/2298149d1415cd804ab4e01f01ea10a0#md5:2298149d1415cd804ab4e01f01ea10a0",
"archive:https://github.com/kandu/mew/archive/0.1.0.tar.gz#md5:2298149d1415cd804ab4e01f01ea10a0"
],
"opam": {
"name": "mew",
"version": "0.1.0",
"path": "esy.lock/opam/mew.0.1.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/trie@opam:1.0.0@d2efc587",
"@opam/result@opam:1.5@6b753c82", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/trie@opam:1.0.0@d2efc587",
"@opam/result@opam:1.5@6b753c82", "@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/merlin-extend@opam:0.6@404f814c": {
"id": "@opam/merlin-extend@opam:0.6@404f814c",
"@opam/merlin-extend@opam:0.6@88755c91": {
"id": "@opam/merlin-extend@opam:0.6@88755c91",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/cppo@opam:1.6.7@c28ac3ae", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/cppo@opam:1.6.7@57a6d52c", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"archive:https://opam.ocaml.org/cache/sha256/fb/fb4caede73bdb8393bd60e31792af74b901ae2d319ac2f2a2252c694d2069d8d#sha256:fb4caede73bdb8393bd60e31792af74b901ae2d319ac2f2a2252c694d2069d8d",
"archive:https://github.com/ocaml/merlin/releases/download/v4.1-412/merlin-v4.1-412.tbz#sha256:fb4caede73bdb8393bd60e31792af74b901ae2d319ac2f2a2252c694d2069d8d"
"archive:https://opam.ocaml.org/cache/sha256/86/86c30769277d3e2c09a8be6c68a98cd342bc0bdbde07c7225bfe2e6d0da2d394#sha256:86c30769277d3e2c09a8be6c68a98cd342bc0bdbde07c7225bfe2e6d0da2d394",
"archive:https://github.com/ocaml/merlin/releases/download/v4.2-412/merlin-v4.2-412.tbz#sha256:86c30769277d3e2c09a8be6c68a98cd342bc0bdbde07c7225bfe2e6d0da2d394"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/result@opam:1.5@6b753c82", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/dot-merlin-reader@opam:4.1@120afa42",
"@opam/csexp@opam:1.5.1@f2f16ef6", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/result@opam:1.5@1c6a6533", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/dot-merlin-reader@opam:4.1@84436e1c",
"@opam/csexp@opam:1.5.1@8a8fb3a7", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/result@opam:1.5@6b753c82", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/dot-merlin-reader@opam:4.1@120afa42",
"@opam/csexp@opam:1.5.1@f2f16ef6"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/result@opam:1.5@1c6a6533", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/dot-merlin-reader@opam:4.1@84436e1c",
"@opam/csexp@opam:1.5.1@8a8fb3a7"
"archive:https://opam.ocaml.org/cache/md5/1c/1cbc71c0bc1f3ddc3e71d5c1f919fd1a#md5:1cbc71c0bc1f3ddc3e71d5c1f919fd1a",
"archive:https://gitlab.inria.fr/fpottier/menhir/repository/20210310/archive.tar.gz#md5:1cbc71c0bc1f3ddc3e71d5c1f919fd1a"
"archive:https://opam.ocaml.org/cache/md5/1a/1af2d137eb20811c74ca516500164fd4#md5:1af2d137eb20811c74ca516500164fd4",
"archive:https://gitlab.inria.fr/fpottier/menhir/-/archive/20210419/archive.tar.gz#md5:1af2d137eb20811c74ca516500164fd4"
"archive:https://opam.ocaml.org/cache/md5/1c/1cbc71c0bc1f3ddc3e71d5c1f919fd1a#md5:1cbc71c0bc1f3ddc3e71d5c1f919fd1a",
"archive:https://gitlab.inria.fr/fpottier/menhir/repository/20210310/archive.tar.gz#md5:1cbc71c0bc1f3ddc3e71d5c1f919fd1a"
"archive:https://opam.ocaml.org/cache/md5/1a/1af2d137eb20811c74ca516500164fd4#md5:1af2d137eb20811c74ca516500164fd4",
"archive:https://gitlab.inria.fr/fpottier/menhir/-/archive/20210419/archive.tar.gz#md5:1af2d137eb20811c74ca516500164fd4"
"archive:https://opam.ocaml.org/cache/md5/1c/1cbc71c0bc1f3ddc3e71d5c1f919fd1a#md5:1cbc71c0bc1f3ddc3e71d5c1f919fd1a",
"archive:https://gitlab.inria.fr/fpottier/menhir/repository/20210310/archive.tar.gz#md5:1cbc71c0bc1f3ddc3e71d5c1f919fd1a"
"archive:https://opam.ocaml.org/cache/md5/1a/1af2d137eb20811c74ca516500164fd4#md5:1af2d137eb20811c74ca516500164fd4",
"archive:https://gitlab.inria.fr/fpottier/menhir/-/archive/20210419/archive.tar.gz#md5:1af2d137eb20811c74ca516500164fd4"
"ocaml@4.12.0@d41d8cd9", "@opam/menhirSdk@opam:20210310@5abaafca",
"@opam/menhirLib@opam:20210310@f9315713",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/menhirSdk@opam:20210419@9c7661a4",
"@opam/menhirLib@opam:20210419@0b3db8d0",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/menhirSdk@opam:20210310@5abaafca",
"@opam/menhirLib@opam:20210310@f9315713",
"@opam/dune@opam:2.8.4@1490e2a1"
"ocaml@4.12.0@d41d8cd9", "@opam/menhirSdk@opam:20210419@9c7661a4",
"@opam/menhirLib@opam:20210419@0b3db8d0",
"@opam/dune@opam:2.8.4@4ac03159"
"ocaml@4.12.0@d41d8cd9", "@opam/ssl@opam:0.5.10@ae7a31df",
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1",
"ocaml@4.12.0@d41d8cd9", "@opam/ssl@opam:0.5.10@b044030d",
"@opam/lwt@opam:5.4.1@37ffbe37", "@opam/dune@opam:2.8.4@4ac03159",
"ocaml@4.12.0@d41d8cd9", "@opam/ssl@opam:0.5.10@ae7a31df",
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1",
"ocaml@4.12.0@d41d8cd9", "@opam/ssl@opam:0.5.10@b044030d",
"@opam/lwt@opam:5.4.1@37ffbe37", "@opam/dune@opam:2.8.4@4ac03159",
]
},
"@opam/lwt_react@opam:1.1.4@7d2054d1": {
"id": "@opam/lwt_react@opam:1.1.4@7d2054d1",
"name": "@opam/lwt_react",
"version": "opam:1.1.4",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/fc/fc4721bdb1a01225b96e3a2debde95fa#md5:fc4721bdb1a01225b96e3a2debde95fa",
"archive:https://github.com/ocsigen/lwt/archive/5.4.0.zip#md5:fc4721bdb1a01225b96e3a2debde95fa"
],
"opam": {
"name": "lwt_react",
"version": "1.1.4",
"path": "esy.lock/opam/lwt_react.1.1.4"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/react@opam:1.2.1@0e11855f",
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/lwt_ppx@opam:2.0.2@d18729de": {
"id": "@opam/lwt_ppx@opam:2.0.2@d18729de",
"name": "@opam/lwt_ppx",
"version": "opam:2.0.2",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/fc/fc4721bdb1a01225b96e3a2debde95fa#md5:fc4721bdb1a01225b96e3a2debde95fa",
"archive:https://github.com/ocsigen/lwt/archive/5.4.0.zip#md5:fc4721bdb1a01225b96e3a2debde95fa"
],
"opam": {
"name": "lwt_ppx",
"version": "2.0.2",
"path": "esy.lock/opam/lwt_ppx.2.0.2"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/ppxlib@opam:0.22.0@d2d2223a",
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/lwt_log@opam:1.1.1@fc97477f": {
"id": "@opam/lwt_log@opam:1.1.1@fc97477f",
"name": "@opam/lwt_log",
"version": "opam:1.1.1",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/02/02e93be62288037870ae5b1ce099fe59#md5:02e93be62288037870ae5b1ce099fe59",
"archive:https://github.com/aantron/lwt_log/archive/1.1.1.tar.gz#md5:02e93be62288037870ae5b1ce099fe59"
],
"opam": {
"name": "lwt_log",
"version": "1.1.1",
"path": "esy.lock/opam/lwt_log.1.1.1"
}
},
"overrides": [],
"dependencies": [
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"@opam/lwt@opam:5.4.0@1ec6dbfd", "@opam/dune@opam:2.8.4@1490e2a1"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"archive:https://opam.ocaml.org/cache/md5/fc/fc4721bdb1a01225b96e3a2debde95fa#md5:fc4721bdb1a01225b96e3a2debde95fa",
"archive:https://github.com/ocsigen/lwt/archive/5.4.0.zip#md5:fc4721bdb1a01225b96e3a2debde95fa"
"archive:https://opam.ocaml.org/cache/md5/5a/5a8d2a83ee9314781f137d147a4c62ae#md5:5a8d2a83ee9314781f137d147a4c62ae",
"archive:https://github.com/ocsigen/lwt/archive/refs/tags/5.4.1.tar.gz#md5:5a8d2a83ee9314781f137d147a4c62ae"
"@opam/lru@opam:0.3.0@5f38669d": {
"id": "@opam/lru@opam:0.3.0@5f38669d",
"name": "@opam/lru",
"version": "opam:0.3.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/ec/ecaa8c9f5f708879140961ce35bcdba4#md5:ecaa8c9f5f708879140961ce35bcdba4",
"archive:https://github.com/pqwy/lru/releases/download/v0.3.0/lru-v0.3.0.tbz#md5:ecaa8c9f5f708879140961ce35bcdba4"
],
"opam": {
"name": "lru",
"version": "0.3.0",
"path": "esy.lock/opam/lru.0.3.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/psq@opam:0.2.0@247756d4",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/psq@opam:0.2.0@247756d4",
"@opam/dune@opam:2.8.4@1490e2a1"
]
},
},
"@opam/lambda-term@opam:3.1.0@8adc2660": {
"id": "@opam/lambda-term@opam:3.1.0@8adc2660",
"name": "@opam/lambda-term",
"version": "opam:3.1.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/78/78180c04ecfc8060b23d7d0014f24196#md5:78180c04ecfc8060b23d7d0014f24196",
"archive:https://github.com/ocaml-community/lambda-term/archive/3.1.0.tar.gz#md5:78180c04ecfc8060b23d7d0014f24196"
],
"opam": {
"name": "lambda-term",
"version": "3.1.0",
"path": "esy.lock/opam/lambda-term.3.1.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/zed@opam:3.1.0@86c55416",
"@opam/react@opam:1.2.1@0e11855f",
"@opam/mew_vi@opam:0.5.0@cf66c299",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt_log@opam:1.1.1@fc97477f", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/camomile@opam:1.0.2@40411a6b",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/zed@opam:3.1.0@86c55416",
"@opam/react@opam:1.2.1@0e11855f",
"@opam/mew_vi@opam:0.5.0@cf66c299",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt_log@opam:1.1.1@fc97477f", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/camomile@opam:1.0.2@40411a6b"
]
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigarray-compat@opam:1.0.0@3a87ad65"
]
},
"@opam/jst-config@opam:v0.14.0@d0d7469e": {
"id": "@opam/jst-config@opam:v0.14.0@d0d7469e",
"name": "@opam/jst-config",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/ee/eefbac104a59bf90c82992cc9fb487d5#md5:eefbac104a59bf90c82992cc9fb487d5",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/jst-config-v0.14.0.tar.gz#md5:eefbac104a59bf90c82992cc9fb487d5"
],
"opam": {
"name": "jst-config",
"version": "v0.14.0",
"path": "esy.lock/opam/jst-config.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/stdio@opam:v0.14.0@a624e254",
"@opam/ppx_assert@opam:v0.14.0@877b5900",
"@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/stdio@opam:v0.14.0@a624e254",
"@opam/ppx_assert@opam:v0.14.0@877b5900",
"@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/base@opam:v0.14.1@d14008e2"
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigarray-compat@opam:1.0.0@951830c6"
"@opam/jane-street-headers@opam:v0.14.0@59432b6a": {
"id": "@opam/jane-street-headers@opam:v0.14.0@59432b6a",
"name": "@opam/jane-street-headers",
"version": "opam:v0.14.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/e8/e8d253ac44d25c8c66367153a0c77495#md5:e8d253ac44d25c8c66367153a0c77495",
"archive:https://ocaml.janestreet.com/ocaml-core/v0.14/files/jane-street-headers-v0.14.0.tar.gz#md5:e8d253ac44d25c8c66367153a0c77495"
],
"opam": {
"name": "jane-street-headers",
"version": "v0.14.0",
"path": "esy.lock/opam/jane-street-headers.v0.14.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1"
]
},
"@opam/iter@opam:1.2.1@cf59a8b0": {
"id": "@opam/iter@opam:1.2.1@cf59a8b0",
"name": "@opam/iter",
"version": "opam:1.2.1",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/76/76f805c96d10f2649dd5c65b28052a82#md5:76f805c96d10f2649dd5c65b28052a82",
"archive:https://github.com/c-cube/iter/archive/1.2.1.tar.gz#md5:76f805c96d10f2649dd5c65b28052a82"
],
"opam": {
"name": "iter",
"version": "1.2.1",
"path": "esy.lock/opam/iter.1.2.1"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/base-bytes@opam:base@19d0c2ff",
"@opam/base-bigarray@opam:base@b03491b0",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/base-bytes@opam:base@19d0c2ff"
]
},
"@opam/integers@opam:0.4.0@f7acfaeb": {
"id": "@opam/integers@opam:0.4.0@f7acfaeb",
"@opam/integers@opam:0.4.0@76f68c9d": {
"id": "@opam/integers@opam:0.4.0@76f68c9d",
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune@opam:2.8.4@1490e2a1",
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/dune-configurator@opam:2.8.5@428293ca",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/faraday-lwt-unix@opam:0.7.2@03e4a439",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/faraday-lwt-unix@opam:0.7.3@e0ae04f9",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"@opam/angstrom@opam:0.15.0@48ede9cb",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"@opam/angstrom@opam:0.15.0@105656d9",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"@opam/angstrom@opam:0.15.0@48ede9cb"
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"@opam/angstrom@opam:0.15.0@105656d9"
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/angstrom@opam:0.15.0@48ede9cb",
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/angstrom@opam:0.15.0@105656d9",
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/angstrom@opam:0.15.0@48ede9cb"
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/angstrom@opam:0.15.0@105656d9"
"@opam/faraday-lwt-unix@opam:0.7.2@03e4a439",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/faraday-lwt-unix@opam:0.7.3@e0ae04f9",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/hpack@opam:0.2.0@9f3eae78",
"@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"@opam/hpack@opam:0.2.0@acd9f903",
"@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"@opam/hpack@opam:0.2.0@9f3eae78",
"@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"@opam/hpack@opam:0.2.0@acd9f903",
"@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"@opam/faraday-lwt-unix@opam:0.7.2@03e4a439",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"@opam/faraday-lwt-unix@opam:0.7.3@e0ae04f9",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/gluten@opam:0.2.1@fca26440", "@opam/dune@opam:2.8.4@1490e2a1",
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/gluten@opam:0.2.1@fca26440", "@opam/dune@opam:2.8.4@4ac03159",
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/gluten@opam:0.2.1@fca26440", "@opam/dune@opam:2.8.4@1490e2a1"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/gluten@opam:0.2.1@fca26440", "@opam/dune@opam:2.8.4@4ac03159"
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b"
"ocaml@4.12.0@d41d8cd9", "@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977"
"archive:https://opam.ocaml.org/cache/md5/61/61bb83e1a4bed100eb0bd1365878e3a1#md5:61bb83e1a4bed100eb0bd1365878e3a1",
"archive:https://github.com/inhabitedtype/faraday/archive/0.7.2.tar.gz#md5:61bb83e1a4bed100eb0bd1365878e3a1"
"archive:https://opam.ocaml.org/cache/md5/1e/1e6d8f5950d099c6ad9ae0e960fe17a9#md5:1e6d8f5950d099c6ad9ae0e960fe17a9",
"archive:https://github.com/inhabitedtype/faraday/archive/0.7.3.tar.gz#md5:1e6d8f5950d099c6ad9ae0e960fe17a9"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/faraday-lwt@opam:0.7.2@391bc143",
"@opam/dune@opam:2.8.4@1490e2a1",
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/faraday-lwt@opam:0.7.3@612dbe66",
"@opam/dune@opam:2.8.4@4ac03159",
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/faraday-lwt@opam:0.7.2@391bc143",
"@opam/dune@opam:2.8.4@1490e2a1",
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/faraday-lwt@opam:0.7.3@612dbe66",
"@opam/dune@opam:2.8.4@4ac03159",
"archive:https://opam.ocaml.org/cache/md5/61/61bb83e1a4bed100eb0bd1365878e3a1#md5:61bb83e1a4bed100eb0bd1365878e3a1",
"archive:https://github.com/inhabitedtype/faraday/archive/0.7.2.tar.gz#md5:61bb83e1a4bed100eb0bd1365878e3a1"
"archive:https://opam.ocaml.org/cache/md5/1e/1e6d8f5950d099c6ad9ae0e960fe17a9#md5:1e6d8f5950d099c6ad9ae0e960fe17a9",
"archive:https://github.com/inhabitedtype/faraday/archive/0.7.3.tar.gz#md5:1e6d8f5950d099c6ad9ae0e960fe17a9"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/faraday@opam:0.7.2@5dfdf1f9",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/faraday@opam:0.7.3@18c4d732",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/faraday@opam:0.7.2@5dfdf1f9", "@opam/dune@opam:2.8.4@1490e2a1"
"ocaml@4.12.0@d41d8cd9", "@opam/lwt@opam:5.4.1@37ffbe37",
"@opam/faraday@opam:0.7.3@18c4d732", "@opam/dune@opam:2.8.4@4ac03159"
"archive:https://opam.ocaml.org/cache/md5/61/61bb83e1a4bed100eb0bd1365878e3a1#md5:61bb83e1a4bed100eb0bd1365878e3a1",
"archive:https://github.com/inhabitedtype/faraday/archive/0.7.2.tar.gz#md5:61bb83e1a4bed100eb0bd1365878e3a1"
"archive:https://opam.ocaml.org/cache/md5/1e/1e6d8f5950d099c6ad9ae0e960fe17a9#md5:1e6d8f5950d099c6ad9ae0e960fe17a9",
"archive:https://github.com/inhabitedtype/faraday/archive/0.7.3.tar.gz#md5:1e6d8f5950d099c6ad9ae0e960fe17a9"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigarray-compat@opam:1.0.0@3a87ad65",
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigarray-compat@opam:1.0.0@951830c6",
"@opam/earlybird@opam:1.1.0@4bff968a": {
"id": "@opam/earlybird@opam:1.1.0@4bff968a",
"name": "@opam/earlybird",
"version": "opam:1.1.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/sha256/aa/aae7257fa73a502ea808eb5c3f3bf9fa0218cedf43342289ca44f03b32839fdf#sha256:aae7257fa73a502ea808eb5c3f3bf9fa0218cedf43342289ca44f03b32839fdf",
"archive:https://github.com/hackwaly/ocamlearlybird/releases/download/1.1.0/earlybird-1.1.0.tbz#sha256:aae7257fa73a502ea808eb5c3f3bf9fa0218cedf43342289ca44f03b32839fdf"
],
"opam": {
"name": "earlybird",
"version": "1.1.0",
"path": "esy.lock/opam/earlybird.1.1.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib@opam:v0.14.0@f67f18de",
"@opam/ppx_deriving_yojson@opam:3.6.1@faf11a7c",
"@opam/ppx_deriving@opam:5.2.1@479736f0",
"@opam/path_glob@opam:0.2@e298ab39",
"@opam/ocaml-compiler-libs@opam:v0.12.3@f0f069bd",
"@opam/menhirLib@opam:20210310@f9315713",
"@opam/menhir@opam:20210310@50de9216",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt_ppx@opam:2.0.2@d18729de", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/lru@opam:0.3.0@5f38669d", "@opam/logs@opam:0.7.0@1d03143e",
"@opam/iter@opam:1.2.1@cf59a8b0", "@opam/fmt@opam:0.8.9@e0843a5b",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/dap@opam:1.0.6@d7969661",
"@opam/csexp@opam:1.5.1@f2f16ef6",
"@opam/cmdliner@opam:1.0.4@93208aac",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib@opam:v0.14.0@f67f18de",
"@opam/ppx_deriving_yojson@opam:3.6.1@faf11a7c",
"@opam/ppx_deriving@opam:5.2.1@479736f0",
"@opam/path_glob@opam:0.2@e298ab39",
"@opam/ocaml-compiler-libs@opam:v0.12.3@f0f069bd",
"@opam/menhirLib@opam:20210310@f9315713",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt_ppx@opam:2.0.2@d18729de", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/lru@opam:0.3.0@5f38669d", "@opam/logs@opam:0.7.0@1d03143e",
"@opam/iter@opam:1.2.1@cf59a8b0", "@opam/fmt@opam:0.8.9@e0843a5b",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/dap@opam:1.0.6@d7969661",
"@opam/csexp@opam:1.5.1@f2f16ef6",
"@opam/cmdliner@opam:1.0.4@93208aac"
]
},
"@opam/dune-configurator@opam:2.8.5@eae07e15": {
"id": "@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune-configurator@opam:2.8.5@428293ca": {
"id": "@opam/dune-configurator@opam:2.8.5@428293ca",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/csexp@opam:1.5.1@f2f16ef6",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/csexp@opam:1.5.1@8a8fb3a7",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/dune@opam:2.8.4@1490e2a1", "@opam/csexp@opam:1.5.1@f2f16ef6"
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/dune@opam:2.8.4@4ac03159", "@opam/csexp@opam:1.5.1@8a8fb3a7"
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/result@opam:1.5@6b753c82",
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/result@opam:1.5@1c6a6533",
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/result@opam:1.5@6b753c82",
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@69d87312",
"@opam/result@opam:1.5@1c6a6533",
"@opam/eqaf@opam:0.7@d693cfe3", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigarray-compat@opam:1.0.0@3a87ad65",
"@opam/eqaf@opam:0.7@5e2a7277", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigarray-compat@opam:1.0.0@951830c6",
]
},
"@opam/dap@opam:1.0.6@d7969661": {
"id": "@opam/dap@opam:1.0.6@d7969661",
"name": "@opam/dap",
"version": "opam:1.0.6",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/sha256/e0/e0b249a3e7382125402ad15d71f4924eef60cfcec326383a5168d424087200ff#sha256:e0b249a3e7382125402ad15d71f4924eef60cfcec326383a5168d424087200ff",
"archive:https://github.com/hackwaly/ocaml-dap/releases/download/1.0.6/dap-1.0.6.tbz#sha256:e0b249a3e7382125402ad15d71f4924eef60cfcec326383a5168d424087200ff"
],
"opam": {
"name": "dap",
"version": "1.0.6",
"path": "esy.lock/opam/dap.1.0.6"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/react@opam:1.2.1@0e11855f",
"@opam/ppx_here@opam:v0.14.0@5ccc1c01",
"@opam/ppx_expect@opam:v0.14.1@cee36131",
"@opam/ppx_deriving_yojson@opam:3.6.1@faf11a7c",
"@opam/ppx_deriving@opam:5.2.1@479736f0",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt_ppx@opam:2.0.2@d18729de", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/logs@opam:0.7.0@1d03143e", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/angstrom-lwt-unix@opam:0.15.0@2ffc8053",
"@opam/angstrom@opam:0.15.0@48ede9cb",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/yojson@opam:1.7.0@7056d985",
"@opam/react@opam:1.2.1@0e11855f",
"@opam/ppx_here@opam:v0.14.0@5ccc1c01",
"@opam/ppx_expect@opam:v0.14.1@cee36131",
"@opam/ppx_deriving_yojson@opam:3.6.1@faf11a7c",
"@opam/ppx_deriving@opam:5.2.1@479736f0",
"@opam/lwt_react@opam:1.1.4@7d2054d1",
"@opam/lwt_ppx@opam:2.0.2@d18729de", "@opam/lwt@opam:5.4.0@1ec6dbfd",
"@opam/logs@opam:0.7.0@1d03143e", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/angstrom-lwt-unix@opam:0.15.0@2ffc8053",
"@opam/angstrom@opam:0.15.0@48ede9cb"
"ocaml@4.12.0@d41d8cd9", "@opam/integers@opam:0.4.0@f7acfaeb",
"@opam/bigarray-compat@opam:1.0.0@3a87ad65"
"ocaml@4.12.0@d41d8cd9", "@opam/integers@opam:0.4.0@76f68c9d",
"@opam/bigarray-compat@opam:1.0.0@951830c6"
"archive:https://opam.ocaml.org/cache/md5/14/14787fb6878a94dd728a0ef7e368ab89#md5:14787fb6878a94dd728a0ef7e368ab89",
"archive:https://github.com/c-cube/ocaml-containers/archive/v3.2.tar.gz#md5:14787fb6878a94dd728a0ef7e368ab89"
"archive:https://opam.ocaml.org/cache/md5/aa/aa946f452a156b7cd0b932b5a849b44e#md5:aa946f452a156b7cd0b932b5a849b44e",
"archive:https://github.com/c-cube/ocaml-containers/archive/v3.3.tar.gz#md5:aa946f452a156b7cd0b932b5a849b44e"
"archive:https://opam.ocaml.org/cache/md5/14/14787fb6878a94dd728a0ef7e368ab89#md5:14787fb6878a94dd728a0ef7e368ab89",
"archive:https://github.com/c-cube/ocaml-containers/archive/v3.2.tar.gz#md5:14787fb6878a94dd728a0ef7e368ab89"
"archive:https://opam.ocaml.org/cache/md5/aa/aa946f452a156b7cd0b932b5a849b44e#md5:aa946f452a156b7cd0b932b5a849b44e",
"archive:https://github.com/c-cube/ocaml-containers/archive/v3.3.tar.gz#md5:aa946f452a156b7cd0b932b5a849b44e"
},
"@opam/charInfo_width@opam:1.1.0@4296bdfe": {
"id": "@opam/charInfo_width@opam:1.1.0@4296bdfe",
"name": "@opam/charInfo_width",
"version": "opam:1.1.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/a5/a539436d1da4aeb93711303f107bec7e#md5:a539436d1da4aeb93711303f107bec7e",
"archive:https://github.com/kandu/charInfo_width/archive/1.1.0.tar.gz#md5:a539436d1da4aeb93711303f107bec7e"
],
"opam": {
"name": "charInfo_width",
"version": "1.1.0",
"path": "esy.lock/opam/charInfo_width.1.1.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/camomile@opam:1.0.2@40411a6b",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/camomile@opam:1.0.2@40411a6b"
]
},
"@opam/camomile@opam:1.0.2@40411a6b": {
"id": "@opam/camomile@opam:1.0.2@40411a6b",
"name": "@opam/camomile",
"version": "opam:1.0.2",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/sha256/f0/f0a419b0affc36500f83b086ffaa36c545560cee5d57e84b729e8f851b3d1632#sha256:f0a419b0affc36500f83b086ffaa36c545560cee5d57e84b729e8f851b3d1632",
"archive:https://github.com/yoriyuki/Camomile/releases/download/1.0.2/camomile-1.0.2.tbz#sha256:f0a419b0affc36500f83b086ffaa36c545560cee5d57e84b729e8f851b3d1632"
],
"opam": {
"name": "camomile",
"version": "1.0.2",
"path": "esy.lock/opam/camomile.1.0.2"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1"
]
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@0484b3c4",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@1ea9f987",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@0484b3c4",
"@opam/dune@opam:2.8.4@1490e2a1"
"ocaml@4.12.0@d41d8cd9", "@opam/easy-format@opam:1.3.2@1ea9f987",
"@opam/dune@opam:2.8.4@4ac03159"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigarray-compat@opam:1.0.0@3a87ad65"
"ocaml@4.12.0@d41d8cd9", "@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigarray-compat@opam:1.0.0@951830c6"
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune@opam:2.8.4@1490e2a1", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@155c136c",
"@opam/dune-configurator@opam:2.8.5@428293ca",
"@opam/dune@opam:2.8.4@4ac03159", "@esy-ocaml/substs@0.0.1@d41d8cd9"
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@ddeb6438",
"@opam/dune-configurator@opam:2.8.5@eae07e15",
"@opam/dune@opam:2.8.4@1490e2a1"
"ocaml@4.12.0@d41d8cd9", "@opam/sexplib0@opam:v0.14.0@155c136c",
"@opam/dune-configurator@opam:2.8.5@428293ca",
"@opam/dune@opam:2.8.4@4ac03159"
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/ocaml-syntax-shims@opam:1.0.0@a9aa3bfa",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/ocaml-syntax-shims@opam:1.0.0@9f361fbb",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977",
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@6b753c82",
"@opam/dune@opam:2.8.4@1490e2a1",
"@opam/bigstringaf@opam:0.7.0@4784da9b"
"ocaml@4.12.0@d41d8cd9", "@opam/result@opam:1.5@1c6a6533",
"@opam/dune@opam:2.8.4@4ac03159",
"@opam/bigstringaf@opam:0.7.0@152be977"
"archive:https://opam.ocaml.org/cache/sha256/79/79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac#sha256:79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac",
"archive:https://github.com/mirage/alcotest/releases/download/1.3.0/alcotest-mirage-1.3.0.tbz#sha256:79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac"
"archive:https://opam.ocaml.org/cache/sha256/b1/b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1#sha256:b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1",
"archive:https://github.com/mirage/alcotest/releases/download/1.4.0/alcotest-mirage-1.4.0.tbz#sha256:b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1"
"archive:https://opam.ocaml.org/cache/sha256/79/79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac#sha256:79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac",
"archive:https://github.com/mirage/alcotest/releases/download/1.3.0/alcotest-mirage-1.3.0.tbz#sha256:79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac"
"archive:https://opam.ocaml.org/cache/sha256/b1/b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1#sha256:b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1",
"archive:https://github.com/mirage/alcotest/releases/download/1.4.0/alcotest-mirage-1.4.0.tbz#sha256:b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1"
opam-version: "2.0"
synopsis: "Debug adapter for OCaml 4.11"
maintainer: ["hackwaly@qq.com"]
authors: ["hackwaly@qq.com"]
homepage: "https://github.com/hackwaly/ocamlearlybird"
bug-reports: "https://github.com/hackwaly/ocamlearlybird/issues"
depends: [
"dune" {>= "2.8"}
"ppx_deriving" {>= "5.1"}
"ppx_deriving_yojson" {>= "3.6.1"}
"menhir" {>= "20201216" & build}
"menhirLib" {>= "20201216"}
"iter" {>= "1.2.1"}
"lwt" {>= "5.4.0"}
"lwt_ppx" {>= "2.0.1"}
"lwt_react" {>= "1.1.3"}
"cmdliner" {>= "1.0.4"}
"logs" {>= "0.7.0"}
"fmt" {>= "0.8.9"}
"path_glob" {>= "0.2"}
"sexplib" {>= "0.14.0"}
"csexp" {>= "1.3.2"}
"lru" {>= "0.3.0"}
"dap" {>= "1.0.6"}
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/hackwaly/ocamlearlybird.git"
url {
src:
checksum: [
]
}
"sha256=aae7257fa73a502ea808eb5c3f3bf9fa0218cedf43342289ca44f03b32839fdf"
"sha512=c1b3e24a52c8c6a4e757b9aae6b2d39131d65df401d35f7a35e175fc12d3ea964e92b7a28d24df6e5d026fd194460f994ba8cecf81fd41e8134f09a5e31973b1"
"https://github.com/hackwaly/ocamlearlybird/releases/download/1.1.0/earlybird-1.1.0.tbz"
x-commit-hash: "5f39f4d2f96aafb896ccd0ce494150f38f929b38"
"ocaml-compiler-libs" {>= "0.12.3"}
"ocaml" {>= "4.11.0" & < "4.13"}
opam-version: "2.0"
synopsis: "Debug adapter protocol"
description: """
The Debug Adapter Protocol defines the protocol used between an editor or IDE and a debugger or runtime.
"""
maintainer: "文宇祥 <hackwaly@qq.com>"
authors: "文宇祥 <hackwaly@qq.com>"
license: "MIT"
homepage: "https://github.com/hackwaly/ocaml-dap"
bug-reports: "https://github.com/hackwaly/ocaml-dap/issues"
dev-repo: "git://git@github.com:hackwaly/ocaml-dap.git"
doc: "https://hackwaly.github.io/ocaml-dap/"
depends: [
"ocaml" {>= "4.08"}
"dune" {>= "2.7"}
"yojson"
"ppx_here"
"ppx_deriving"
"ppx_deriving_yojson"
"ppx_expect"
"lwt"
"lwt_ppx"
"lwt_react"
"react"
"angstrom"
"angstrom-lwt-unix"
"logs"
]
build: [
["dune" "subst"] {pinned}
["dune" "build" "-p" name "-j" jobs]
]
x-commit-hash: "34cd793c049c7fb7bd7f78f80e1f36291aa60e70"
url {
src:
"https://github.com/hackwaly/ocaml-dap/releases/download/1.0.6/dap-1.0.6.tbz"
checksum: [
"sha256=e0b249a3e7382125402ad15d71f4924eef60cfcec326383a5168d424087200ff"
"sha512=97805a383ad03ba24f1dabe20798c139678f958dfa2aed1664098f444aaeefd56d6dd7ff3650800eaefe5b17e09098427b78500316699e7267eb1fef233d6a99"
]
}
opam-version: "2.0"
synopsis: ""
description:
"Simple abstraction over `iter` functions, intended to iterate efficiently on collections while performing some transformations"
maintainer: "simon.cruanes.2007@m4x.org"
authors: ["Simon Cruanes" "Gabriel Radanne"]
license: "BSD-2-Clause"
tags: ["iter" "iterator" "iter" "fold"]
homepage: "https://github.com/c-cube/iter/"
doc: "https://c-cube.github.io/iter/doc/1.2"
bug-reports: "https://github.com/c-cube/iter/issues"
depends: [
"ocaml"
"base-bytes"
"result"
"dune"
"dune-configurator"
"qcheck" {with-test}
"qtest" {with-test}
"mdx" {with-test}
"odoc" {with-doc}
]
depopts: ["base-bigarray"]
build: [
["dune" "build" "@install" "-p" name "-j" jobs]
["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc}
["dune" "runtest" "-p" name "-j" jobs] {with-test & ocaml:version >= "4.03.0"}
]
dev-repo: "git+https://github.com/c-cube/iter.git"
url {
src: "https://github.com/c-cube/iter/archive/1.2.1.tar.gz"
checksum: [
"md5=76f805c96d10f2649dd5c65b28052a82"
"sha512=f4c71a62fb2350e2cac78acf07abeb67c206f487101aa189b545e91a154d936cbe59092b6557ea516108b2faeabd034640d61450e99a2e930bd7559a6eee2675"
]
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/jane-street-headers"
bug-reports: "https://github.com/janestreet/jane-street-headers/issues"
dev-repo: "git+https://github.com/janestreet/jane-street-headers.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/jane-street-headers/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"dune" {>= "2.0.0"}
]
synopsis: "Jane Street C header files"
description: "
C header files shared between the various Jane Street packages
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/jane-street-headers-v0.14.0.tar.gz"
checksum: "md5=e8d253ac44d25c8c66367153a0c77495"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/jst-config"
bug-reports: "https://github.com/janestreet/jst-config/issues"
dev-repo: "git+https://github.com/janestreet/jst-config.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/jst-config/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"ppx_assert" {>= "v0.14" & < "v0.15"}
"stdio" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"dune-configurator"
]
synopsis: "Compile-time configuration for Jane Street libraries"
description: "
Defines compile-time constants used in Jane Street libraries such as Base, Core, and
Async.
This package has an unstable interface; it is intended only to share configuration between
different packages from Jane Street. Future updates may not be backward-compatible, and we
do not recommend using this package directly.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/jst-config-v0.14.0.tar.gz"
checksum: "md5=eefbac104a59bf90c82992cc9fb487d5"
}
opam-version: "2.0"
maintainer: "David Kaloper Meršinjak <dk505@cam.ac.uk>"
authors: ["David Kaloper Meršinjak <dk505@cam.ac.uk>"]
homepage: "https://github.com/pqwy/lru"
doc: "https://pqwy.github.io/lru/doc"
license: "ISC"
dev-repo: "git+https://github.com/pqwy/lru.git"
bug-reports: "https://github.com/pqwy/lru/issues"
synopsis: "Scalable LRU caches"
build: [ [ "dune" "subst" ] {pinned}
[ "dune" "build" "-p" name "-j" jobs ]
[ "dune" "runtest" "-p" name ] {with-test} ]
depends: [
"ocaml" {>="4.03.0"}
"dune" {>= "1.7"}
"psq" {>="0.2.0"}
"qcheck-core" {with-test}
"qcheck-alcotest" {with-test}
"alcotest" {with-test}
]
description: """
Lru provides weight-bounded finite maps that can remove the least-recently-used
(LRU) bindings in order to maintain a weight constraint.
"""
url {
src: "https://github.com/pqwy/lru/releases/download/v0.3.0/lru-v0.3.0.tbz"
checksum: "md5=ecaa8c9f5f708879140961ce35bcdba4"
}
opam-version: "2.0"
synopsis:
"PPX syntax for Lwt, providing something similar to async/await from JavaScript"
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
authors: "Gabriel Radanne"
license: "MIT"
homepage: "https://github.com/ocsigen/lwt"
doc: "https://ocsigen.org/lwt/dev/api/Ppx_lwt"
bug-reports: "https://github.com/ocsigen/lwt/issues"
depends: [
"dune" {>= "1.8.0"}
"lwt"
"ocaml" {>= "4.02.0"}
"ppxlib" {>= "0.16.0"}
]
build: ["dune" "build" "-p" name "-j" jobs]
dev-repo: "git+https://github.com/ocsigen/lwt.git"
url {
src: "https://github.com/ocsigen/lwt/archive/5.4.0.zip"
checksum: [
"md5=fc4721bdb1a01225b96e3a2debde95fa"
"sha512=e427f08223b77f9af696c9e6f90ff68e27e02e446910ef90d3da542e7b00bf23dd191ac77c1871288faa2289f8d28fc2f44efc3d3fe9165fe1c7a6be88ee49ff"
]
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/parsexp"
bug-reports: "https://github.com/janestreet/parsexp/issues"
dev-repo: "git+https://github.com/janestreet/parsexp.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/parsexp/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"sexplib0" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
]
synopsis: "S-expression parsing library"
description: "
This library provides generic parsers for parsing S-expressions from
strings or other medium.
The library is focused on performances but still provide full generic
parsers that can be used with strings, bigstrings, lexing buffers,
character streams or any other sources effortlessly.
It provides three different class of parsers:
- the normal parsers, producing [Sexp.t] or [Sexp.t list] values
- the parsers with positions, building compact position sequences so
that one can recover original positions in order to report properly
located errors at little cost
- the Concrete Syntax Tree parsers, produce values of type
[Parsexp.Cst.t] which record the concrete layout of the s-expression
syntax, including comments
This library is portable and doesn't provide IO functions. To read
s-expressions from files or other external sources, you should use
parsexp_io.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/parsexp-v0.14.0.tar.gz"
checksum: "md5=c2c5fa3f9b082e4fe729e2cf95cacd3b"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_assert"
bug-reports: "https://github.com/janestreet/ppx_assert/issues"
dev-repo: "git+https://github.com/janestreet/ppx_assert.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_assert/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"ppx_cold" {>= "v0.14" & < "v0.15"}
"ppx_compare" {>= "v0.14" & < "v0.15"}
"ppx_here" {>= "v0.14" & < "v0.15"}
"ppx_sexp_conv" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.11.0"}
]
synopsis: "Assert-like extension nodes that raise useful errors on failure"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_assert-v0.14.0.tar.gz"
checksum: "md5=535b5f241eb7f10da8c044c26afbc186"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_base"
bug-reports: "https://github.com/janestreet/ppx_base/issues"
dev-repo: "git+https://github.com/janestreet/ppx_base.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_base/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"ppx_cold" {>= "v0.14" & < "v0.15"}
"ppx_compare" {>= "v0.14" & < "v0.15"}
"ppx_enumerate" {>= "v0.14" & < "v0.15"}
"ppx_hash" {>= "v0.14" & < "v0.15"}
"ppx_js_style" {>= "v0.14" & < "v0.15"}
"ppx_sexp_conv" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.11.0"}
]
synopsis: "Base set of ppx rewriters"
description: "
ppx_base is the set of ppx rewriters used for Base.
Note that Base doesn't need ppx to build, it is only used as a
verification tool.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_base-v0.14.0.tar.gz"
checksum: "md5=b29a24907e60f42e050ad90e5209bb92"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_cold"
bug-reports: "https://github.com/janestreet/ppx_cold/issues"
dev-repo: "git+https://github.com/janestreet/ppx_cold.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_cold/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.11.0"}
]
synopsis: "Expands [@cold] into [@inline never][@specialise never][@local never]"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_cold-v0.14.0.tar.gz"
checksum: "md5=6a61807cd3b105b8c885bd2076986339"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_compare"
bug-reports: "https://github.com/janestreet/ppx_compare/issues"
dev-repo: "git+https://github.com/janestreet/ppx_compare.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_compare/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.11.0"}
]
synopsis: "Generation of comparison functions from types"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_compare-v0.14.0.tar.gz"
checksum: "md5=9149b3a0c954fe2cef2b0705d254b9e3"
}
opam-version: "2.0"
maintainer: "whitequark <whitequark@whitequark.org>"
authors: [ "whitequark <whitequark@whitequark.org>" ]
license: "MIT"
homepage: "https://github.com/ocaml-ppx/ppx_deriving_yojson"
bug-reports: "https://github.com/ocaml-ppx/ppx_deriving_yojson/issues"
dev-repo: "git+https://github.com/ocaml-ppx/ppx_deriving_yojson.git"
tags: [ "syntax" "json" ]
build: [
["dune" "subst"] {dev}
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]
depends: [
"ocaml" {>= "4.05.0"}
"dune" {>= "1.0"}
"yojson" {>= "1.6.0" & < "2.0.0"}
"result"
"ppx_deriving" {>= "5.1"}
"ppxlib" {>= "0.14.0"}
"ounit" {with-test & >= "2.0.0"}
]
synopsis:
"JSON codec generator for OCaml"
description: """
ppx_deriving_yojson is a ppx_deriving plugin that provides
a JSON codec generator.
"""
url {
src:
"https://github.com/ocaml-ppx/ppx_deriving_yojson/releases/download/v3.6.1/ppx_deriving_yojson-v3.6.1.tbz"
checksum: [
"sha256=712ee9207c70dd144e72cd689bee2d2beb120b804e77c74ec6f7b843a88944e6"
"sha512=d8c828902b8441f73e08fc03e2173ce81a09cccfe091471fbcffe098b2272739b98a05e8308016da3efeb3d4d1abd7d941bfaac42c85961ea40915ddce526577"
]
}
x-commit-hash: "d0abe462de8bab52d763eeafd751e8ea1ba211ac"
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_enumerate"
bug-reports: "https://github.com/janestreet/ppx_enumerate/issues"
dev-repo: "git+https://github.com/janestreet/ppx_enumerate.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_enumerate/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.11.0"}
]
synopsis: "Generate a list containing all values of a finite type"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_enumerate-v0.14.0.tar.gz"
checksum: "md5=188421af960759f6e45dd748f4f08e8d"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_expect"
bug-reports: "https://github.com/janestreet/ppx_expect/issues"
dev-repo: "git+https://github.com/janestreet/ppx_expect.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_expect/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"ppx_here" {>= "v0.14" & < "v0.15"}
"ppx_inline_test" {>= "v0.14" & < "v0.15"}
"stdio" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.18.0"}
"re" {>= "1.8.0"}
]
synopsis: "Cram like framework for OCaml"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://github.com/janestreet/ppx_expect/archive/v0.14.1.tar.gz"
checksum: "md5=9cc03dcabb00c72e17f7f5b0e4d28603"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_hash"
bug-reports: "https://github.com/janestreet/ppx_hash/issues"
dev-repo: "git+https://github.com/janestreet/ppx_hash.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_hash/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"ppx_compare" {>= "v0.14" & < "v0.15"}
"ppx_sexp_conv" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.11.0"}
]
synopsis: "A ppx rewriter that generates hash functions from type expressions and definitions"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_hash-v0.14.0.tar.gz"
checksum: "md5=b78aee19bb4469731f9626b04fe7f341"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_here"
bug-reports: "https://github.com/janestreet/ppx_here/issues"
dev-repo: "git+https://github.com/janestreet/ppx_here.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_here/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.11.0"}
]
synopsis: "Expands [%here] into its location"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_here-v0.14.0.tar.gz"
checksum: "md5=bb3bbde0964a1f866de09f3df44def4d"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_inline_test"
bug-reports: "https://github.com/janestreet/ppx_inline_test/issues"
dev-repo: "git+https://github.com/janestreet/ppx_inline_test.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_inline_test/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"time_now" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.14.0"}
]
synopsis: "Syntax extension for writing in-line tests in ocaml code"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://github.com/janestreet/ppx_inline_test/archive/v0.14.1.tar.gz"
checksum: "md5=132754f0757188c3b700a2c5b6a2fb3f"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_optcomp"
bug-reports: "https://github.com/janestreet/ppx_optcomp/issues"
dev-repo: "git+https://github.com/janestreet/ppx_optcomp.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_optcomp/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"stdio" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.18.0"}
]
synopsis: "Optional compilation for OCaml"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://github.com/janestreet/ppx_optcomp/archive/v0.14.1.tar.gz"
checksum: "md5=4ba24037b097bfedbbeb5a5c577694e1"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/ppx_sexp_conv"
bug-reports: "https://github.com/janestreet/ppx_sexp_conv/issues"
dev-repo: "git+https://github.com/janestreet/ppx_sexp_conv.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_sexp_conv/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"sexplib0" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"ppxlib" {>= "0.22.0"}
]
synopsis: "[@@deriving] plugin to generate S-expression conversion functions"
description: "
Part of the Jane Street's PPX rewriters collection.
"
url {
src: "https://github.com/janestreet/ppx_sexp_conv/archive/v0.14.3.tar.gz"
checksum: "md5=25caf01245e0113e035ccefe275f85d9"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/sexplib"
bug-reports: "https://github.com/janestreet/sexplib/issues"
dev-repo: "git+https://github.com/janestreet/sexplib.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/sexplib/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"parsexp" {>= "v0.14" & < "v0.15"}
"sexplib0" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
"num"
]
synopsis: "Library for serializing OCaml values to and from S-expressions"
description: "
Part of Jane Street's Core library
The Core suite of libraries is an industrial strength alternative to
OCaml's standard library that was developed by Jane Street, the
largest industrial user of OCaml.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/sexplib-v0.14.0.tar.gz"
checksum: "md5=6e230eae22face46cb8645e53e351067"
}
opam-version: "2.0"
maintainer: "opensource@janestreet.com"
authors: ["Jane Street Group, LLC <opensource@janestreet.com>"]
homepage: "https://github.com/janestreet/time_now"
bug-reports: "https://github.com/janestreet/time_now/issues"
dev-repo: "git+https://github.com/janestreet/time_now.git"
doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/time_now/index.html"
license: "MIT"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.04.2"}
"base" {>= "v0.14" & < "v0.15"}
"jane-street-headers" {>= "v0.14" & < "v0.15"}
"jst-config" {>= "v0.14" & < "v0.15"}
"ppx_base" {>= "v0.14" & < "v0.15"}
"ppx_optcomp" {>= "v0.14" & < "v0.15"}
"dune" {>= "2.0.0"}
]
synopsis: "Reports the current time"
description: "
Provides a single function to report the current time in nanoseconds
since the start of the Unix epoch.
"
url {
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/time_now-v0.14.0.tar.gz"
checksum: "md5=a93116938783587f8b9f5152dd543037"
}
opam-version: "2.0"
synopsis: "A Unicode library"
description: """
Camomile is a Unicode library for OCaml. Camomile provides Unicode character
type, UTF-8, UTF-16, UTF-32 strings, conversion to/from about 200 encodings,
collation and locale-sensitive case mappings, and more. The library is currently
designed for Unicode Standard 3.2."""
maintainer: ["yoriyuki.y@gmail.com"]
authors: ["Yoriyuki Yamagata"]
license: "LGPL-2.1-or-later with OCaml-LGPL-linking-exception"
homepage: "https://github.com/yoriyuki/Camomile"
doc: "https://yoriyuki.github.io/Camomile/"
bug-reports: "https://github.com/yoriyuki/Camomile/issues"
depends: [
"dune" {>= "1.11"}
"ocaml" {>= "4.02.3"}
]
dev-repo: "git+https://github.com/yoriyuki/Camomile.git"
build: [
["ocaml" "configure.ml" "--share" "%{share}%/camomile"]
["dune" "subst"] {pinned}
["dune" "build" "-p" name "-j" jobs
"@install"
"@doc" {with-doc}
]
]
url {
src:
"https://github.com/yoriyuki/Camomile/releases/download/1.0.2/camomile-1.0.2.tbz"
checksum: [
"sha256=f0a419b0affc36500f83b086ffaa36c545560cee5d57e84b729e8f851b3d1632"
"sha512=7586422e68779476206027c6ebbe19b677fbe459153221f7c952c7fae374c5c8232249cb76fdb1f482069707aa1580be827cd39693906142988268b7f0e7f6d0"
]
}
available: arch != "ppc64"
opam-version: "2.0"
maintainer: "zandoye@gmail.com"
authors: [ "ZAN DoYe" ]
homepage: "https://github.com/kandu/charinfo_width/"
bug-reports: "https://github.com/kandu/charinfo_width/issues"
license: "MIT"
dev-repo: "git://github.com/kandu/charinfo_width.git"
build: [
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test & (ocaml:version >= "4.04.0")}
]
depends: [
"ocaml" {>= "4.02.3"}
"result"
"camomile" {>= "1.0.0" & < "2.0~"}
"dune"
"ppx_expect" {with-test & < "v0.15"}
]
synopsis: "Determine column width for a character"
description: """
This module is implemented purely in OCaml and the width function follows the prototype of POSIX's wcwidth."""
url {
src:"https://github.com/kandu/charInfo_width/archive/1.1.0.tar.gz"
checksum: "md5=a539436d1da4aeb93711303f107bec7e"
}
opam-version: "2.0"
maintainer: "jeremie@dimino.org"
authors: ["Jérémie Dimino"]
homepage: "https://github.com/ocaml-community/lambda-term"
bug-reports: "https://github.com/ocaml-community/lambda-term/issues"
dev-repo: "git://github.com/ocaml-community/lambda-term.git"
license: "BSD-3-Clause"
build: [
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]
depends: [
"ocaml" {>= "4.02.3"}
"lwt" {>= "4.0.0"}
"lwt_log"
"react"
"zed" {>= "3.1.0" & < "4.0"}
"camomile" {>= "1.0.1"}
"lwt_react"
"mew_vi" {>= "0.5.0" & < "0.6.0"}
"dune" {>= "1.1.0"}
]
synopsis: "Terminal manipulation library for OCaml"
description: """
Lambda-term is a cross-platform library for manipulating the terminal. It
provides an abstraction for keys, mouse events, colors, as well as a set of
widgets to write curses-like applications. The main objective of lambda-term is
to provide a higher level functional interface to terminal manipulation than,
for example, ncurses, by providing a native OCaml interface instead of bindings
to a C library. Lambda-term integrates with zed to provide text edition
facilities in console applications."""
url {
src: "https://github.com/ocaml-community/lambda-term/archive/3.1.0.tar.gz"
checksum: "md5=78180c04ecfc8060b23d7d0014f24196"
}
opam-version: "2.0"
synopsis: "Lwt logging library (deprecated)"
license: "LGPL-2.0-or-later"
homepage: "https://github.com/ocsigen/lwt_log"
doc: "https://github.com/ocsigen/lwt_log/blob/master/src/core/lwt_log_core.mli"
bug-reports: "https://github.com/ocsigen/lwt_log/issues"
authors: [
"Shawn Wagner"
"Jérémie Dimino"
]
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
dev-repo: "git+https://github.com/ocsigen/lwt_log.git"
depends: [
"dune" {>= "1.0"}
"lwt" {>= "4.0.0"}
]
build: [
["dune" "build" "-p" name "-j" jobs]
]
url {
src: "https://github.com/aantron/lwt_log/archive/1.1.1.tar.gz"
checksum: "md5=02e93be62288037870ae5b1ce099fe59"
}
opam-version: "2.0"
synopsis: "Helpers for using React with Lwt"
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
authors: "Jérémie Dimino"
license: "MIT"
homepage: "https://github.com/ocsigen/lwt"
doc: "https://ocsigen.org/lwt/dev/api/Lwt_react"
bug-reports: "https://github.com/ocsigen/lwt/issues"
depends: [
"dune" {>= "1.8.0"}
"lwt" {>= "3.0.0"}
"ocaml"
"react" {>= "1.0.0"}
]
build: ["dune" "build" "-p" name "-j" jobs]
dev-repo: "git+https://github.com/ocsigen/lwt.git"
url {
src: "https://github.com/ocsigen/lwt/archive/5.4.0.zip"
checksum: [
"md5=fc4721bdb1a01225b96e3a2debde95fa"
"sha512=e427f08223b77f9af696c9e6f90ff68e27e02e446910ef90d3da542e7b00bf23dd191ac77c1871288faa2289f8d28fc2f44efc3d3fe9165fe1c7a6be88ee49ff"
]
}
opam-version: "2.0"
maintainer: "zandoye@gmail.com"
authors: [ "ZAN DoYe" ]
homepage: "https://github.com/kandu/mew"
bug-reports: "https://github.com/kandu/mew/issues"
license: "MIT"
dev-repo: "git+https://github.com/kandu/mew.git"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.02.3"}
"result"
"trie"
"dune" {>= "1.1.0"}
]
synopsis: "Modal editing witch"
description: """
This is the core module of mew, a general modal editing engine generator."""
url {
src: "https://github.com/kandu/mew/archive/0.1.0.tar.gz"
checksum: "md5=2298149d1415cd804ab4e01f01ea10a0"
}
opam-version: "2.0"
maintainer: "zandoye@gmail.com"
authors: [ "ZAN DoYe" ]
homepage: "https://github.com/kandu/mew_vi"
bug-reports: "https://github.com/kandu/mew_vi/issues"
license: "MIT"
dev-repo: "git+https://github.com/kandu/mew_vi.git"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.02.3"}
"mew" {>= "0.1.0" & < "0.2"}
"react"
"dune" {>= "1.1.0"}
]
synopsis: "Modal editing witch, VI interpreter"
description: """
A vi-like modal editing engine generator."""
url {
src: "https://github.com/kandu/mew_vi/archive/0.5.0.tar.gz"
checksum: "md5=341e9a9a20383641015bf503952906bc"
}
opam-version: "2.0"
maintainer: "Daniel Bünzli <daniel.buenzl i@erratique.ch>"
homepage: "http://erratique.ch/software/react"
authors: ["Daniel Bünzli <daniel.buenzl i@erratique.ch>"]
doc: "http://erratique.ch/software/react/doc/React"
dev-repo: "git+http://erratique.ch/repos/react.git"
bug-reports: "https://github.com/dbuenzli/react/issues"
tags: [ "reactive" "declarative" "signal" "event" "frp" "org:erratique" ]
license: "ISC"
depends: [
"ocaml" {>= "4.01.0"}
"ocamlfind" {build}
"ocamlbuild" {build}
"topkg" {build & >= "0.9.0"}
]
build:
[[ "ocaml" "pkg/pkg.ml" "build"
"--dev-pkg" "%{pinned}%" ]]
synopsis: "Declarative events and signals for OCaml"
description: """
Release %%VERSION%%
React is an OCaml module for functional reactive programming (FRP). It
provides support to program with time varying values : declarative
events and signals. React doesn't define any primitive event or
signal, it lets the client chooses the concrete timeline.
React is made of a single, independent, module and distributed under
the ISC license."""
url {
src: "http://erratique.ch/software/react/releases/react-1.2.1.tbz"
checksum: "md5=ce1454438ce4e9d2931248d3abba1fcc"
}
opam-version: "2.0"
maintainer: "zandoye@gmail.com"
authors: [ "ZAN DoYe" ]
homepage: "https://github.com/kandu/trie/"
bug-reports: "https://github.com/kandu/trie/issues"
license: "MIT"
dev-repo: "git://github.com/kandu/trie.git"
build: [
["dune" "build" "-p" name "-j" jobs]
]
depends: [
"ocaml" {>= "4.02"}
"dune" {>= "1.0"}
]
synopsis: "Strict impure trie tree"
url {
src: "https://github.com/kandu/trie/archive/1.0.0.tar.gz"
checksum: "md5=84519b5f8bd92490bfc68a52f706ba14"
}
opam-version: "2.0"
maintainer: "jeremie@dimino.org"
authors: "Jérémie Dimino"
license: "BSD3"
homepage: "https://github.com/ocaml-community/utop"
bug-reports: "https://github.com/ocaml-community/utop/issues"
doc: "https://ocaml-community.github.io/utop/"
depends: [
"ocaml" {>= "4.03.0" & < "4.13"}
"base-unix"
"base-threads"
"ocamlfind" {>= "1.7.2"}
"lambda-term" {>= "3.1.0" & < "4.0"}
"lwt"
"lwt_react"
"camomile"
"react" {>= "1.0.0"}
"cppo" {build & >= "1.1.2"}
"dune" {>= "1.0"}
]
build: [
["dune" "subst"] {pinned}
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]
dev-repo: "git+https://github.com/ocaml-community/utop.git"
synopsis: "Universal toplevel for OCaml"
description: """
utop is an improved toplevel (i.e., Read-Eval-Print Loop or REPL) for
OCaml. It can run in a terminal or in Emacs. It supports line
edition, history, real-time and context sensitive completion, colors,
and more. It integrates with the Tuareg mode in Emacs.
"""
x-commit-hash: "a5ff52bbf608e1112b5c0d41a36e3267f39f4084"
url {
src:
"https://github.com/ocaml-community/utop/releases/download/2.7.0/utop-2.7.0.tbz"
checksum: [
"sha256=e068ac53df267c3cc0f2f69bbc204404f0362cc4e6472a1fc547e326a63c3fdd"
"sha512=fc6237ff3e80c509a698872e5571b58e914d24c308a634e45972b7f104d960f17eba507535f56fcec972ea8c71143a8036cd122618e63cdf77fb6034297924df"
]
}
opam-version: "2.0"
maintainer: "opam-devel@lists.ocaml.org"
authors: ["Jérémie Dimino"]
homepage: "https://github.com/ocaml-community/zed"
bug-reports: "https://github.com/ocaml-community/zed/issues"
dev-repo: "git://github.com/ocaml-community/zed.git"
license: "BSD-3-Clause"
depends: [
"ocaml" {>= "4.02.3"}
"dune" {>= "1.1.0"}
"base-bytes"
"camomile" {>= "1.0.1"}
"react"
"charInfo_width" {>= "1.1.0" & < "2.0~"}
]
build: [
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]
synopsis: "Abstract engine for text edition in OCaml"
description: """
Zed is an abstract engine for text edition. It can be used to write text
editors, edition widgets, readlines, ... Zed uses Camomile to fully support the
Unicode specification, and implements an UTF-8 encoded string type with
validation, and a rope datastructure to achieve efficient operations on large
Unicode buffers. Zed also features a regular expression search on ropes. To
support efficient text edition capabilities, Zed provides macro recording and
cursor management facilities."""
url {
src: "https://github.com/ocaml-community/zed/archive/3.1.0.tar.gz"
checksum: "md5=51e8676ba972e5ad727633c161e404b1"
}
opam-version: "2.0"
maintainer: "dev@ocsigen.org"
homepage: "https://github.com/ocsigen/tyxml/"
bug-reports: "https://github.com/ocsigen/tyxml/issues"
doc: "https://ocsigen.org/tyxml/manual/"
dev-repo: "git+https://github.com/ocsigen/tyxml.git"
license: "LGPL-2.1 with OCaml linking exception"
build: [
["dune" "subst"] {pinned}
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]
depends: [
"ocaml" {>= "4.02"}
"dune"
"alcotest" {with-test}
"seq"
"uutf" {>= "1.0.0"}
"re" {>= "1.5.0"}
]
synopsis:"TyXML is a library for building correct HTML and SVG documents"
description:"""
TyXML provides a set of convenient combinators that uses the OCaml
type system to ensure the validity of the generated documents. TyXML
can be used with any representation of HTML and SVG: the textual one,
provided directly by this package, or DOM trees (`js_of_ocaml-tyxml`)
virtual DOM (`virtual-dom`) and reactive or replicated trees
(`eliom`). You can also create your own representation and use it to
instantiate a new set of combinators.
```ocaml
open Tyxml
let to_ocaml = Html.(a ~a:[a_href "ocaml.org"] [txt "OCaml!"])
```
"""
authors: "The ocsigen team"
url {
src:
"https://github.com/ocsigen/tyxml/releases/download/4.4.0/tyxml-4.4.0.tbz"
checksum: [
"sha256=516394dd4a5c31726997c51d66aa31cacb91e3c46d4e16c7699130e204042530"
"sha512=d5f2187f8410524cec7a14b28e8950837070eb0b6571b015dd06076c2841eb7ccaffa86d5d2307eaf1950ee62f9fb926477dac01c870d9c1a2f525853cb44d0c"
]
}
"sha256=79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac"
"sha512=c41b17354d391d72f5f7bbbf520d7d227ec3df1bb25183e4a6761bb6d76e787ab89302bf58695cfe5a05b7d00cd77fe9d18d1eee396ecc724dfe942ecd1144aa"
"sha256=b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1"
"sha512=8a13d5d4c8c77f115903e6b8e58160c6e6ec27870440bd38a674e9406f57f1eff299e65f006fd77728015d1a8f0ae30a714fe47e035824950a71ebfdff2cf3c9"
"sha256=79f9debdbca895374d6fdd73af8a470dcbe068b410483d35c04bb6ccc33e89ac"
"sha512=c41b17354d391d72f5f7bbbf520d7d227ec3df1bb25183e4a6761bb6d76e787ab89302bf58695cfe5a05b7d00cd77fe9d18d1eee396ecc724dfe942ecd1144aa"
"sha256=b1aaccfb2d651c902592c04953e2619169c91f797cf4f04a7dda2cab09b93ec1"
"sha512=8a13d5d4c8c77f115903e6b8e58160c6e6ec27870440bd38a674e9406f57f1eff299e65f006fd77728015d1a8f0ae30a714fe47e035824950a71ebfdff2cf3c9"
["dune" "build" "@doc" "-p" name ] {with-doc}
["dune" "runtest" "-p" name "-j" jobs] {with-test}
["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc}
["dune" "runtest" "-p" name "-j" jobs] {with-test & arch != "x86_32"}
"md5=14787fb6878a94dd728a0ef7e368ab89"
"sha512=9debbd79542fbe24e6b0ec5e0fb74077566663fa53b868aa381962653d65543a86606ed6703a75cf3e14962b66068747b237a88bb1eea15b6062665e294795ac"
"md5=aa946f452a156b7cd0b932b5a849b44e"
"sha512=fbb6e519ea918afd3895de4cb74bb93a1d7d8899aa1d9def0ee0576a4f648413e3a7d9639040a1117516efb74c66c3432e6da79e6284d2315327175e22766717"
"md5=14787fb6878a94dd728a0ef7e368ab89"
"sha512=9debbd79542fbe24e6b0ec5e0fb74077566663fa53b868aa381962653d65543a86606ed6703a75cf3e14962b66068747b237a88bb1eea15b6062665e294795ac"
"md5=aa946f452a156b7cd0b932b5a849b44e"
"sha512=fbb6e519ea918afd3895de4cb74bb93a1d7d8899aa1d9def0ee0576a4f648413e3a7d9639040a1117516efb74c66c3432e6da79e6284d2315327175e22766717"
Meanwhile, OCaml code, including code creating and waiting on promises, runs in
a single thread by default. This reduces the need for locks or other
synchronization primitives. Code can be run in parallel on an opt-in basis."""
maintainer: [
"Raphaël Proust <code@bnwr.net>" "Anton Bachin <antonbachin@yahoo.com>"
]
authors: ["Jérôme Vouillon" "Jérémie Dimino"]
dev-repo: "git+https://github.com/ocsigen/lwt.git"
description: "A promise is a value that may become determined in the future.
Lwt provides typed, composable promises. Promises that are resolved by I/O are
resolved by Lwt in parallel.
Meanwhile, OCaml code, including code creating and waiting on promises, runs in
a single thread by default. This reduces the need for locks or other
synchronization primitives. Code can be run in parallel on an opt-in basis."
"md5=fc4721bdb1a01225b96e3a2debde95fa"
"sha512=e427f08223b77f9af696c9e6f90ff68e27e02e446910ef90d3da542e7b00bf23dd191ac77c1871288faa2289f8d28fc2f44efc3d3fe9165fe1c7a6be88ee49ff"
"md5=5a8d2a83ee9314781f137d147a4c62ae"
"sha512=b872b7abe546c431ba62fe466423d7ace8e487ebd85ea5e859f462eb4c0a6884b242d9efd4a557b6da3ae699b0b695e0a783f89a1d1147cba7d99c4ae9d2db17"
"md5=1cbc71c0bc1f3ddc3e71d5c1f919fd1a"
"sha512=3c309fa2cc4ad7c6fba85107bd946a542894882fa39741496b150307e93455b717418f19e94b5dad06ab269f5c55e8dc25705c96c0a5092e623fa38f1ce43c7f"
"md5=1af2d137eb20811c74ca516500164fd4"
"sha512=37a88b3ea0bde6089e5fbf0c1f10c1867c4edcd033ed3d5b75e7ed93e14ddd4f4c4db96baf638a054f65e294b83411497615c7fc14c6ff3a2a007e70f9d12c98"
"md5=1cbc71c0bc1f3ddc3e71d5c1f919fd1a"
"sha512=3c309fa2cc4ad7c6fba85107bd946a542894882fa39741496b150307e93455b717418f19e94b5dad06ab269f5c55e8dc25705c96c0a5092e623fa38f1ce43c7f"
"md5=1af2d137eb20811c74ca516500164fd4"
"sha512=37a88b3ea0bde6089e5fbf0c1f10c1867c4edcd033ed3d5b75e7ed93e14ddd4f4c4db96baf638a054f65e294b83411497615c7fc14c6ff3a2a007e70f9d12c98"
"md5=1cbc71c0bc1f3ddc3e71d5c1f919fd1a"
"sha512=3c309fa2cc4ad7c6fba85107bd946a542894882fa39741496b150307e93455b717418f19e94b5dad06ab269f5c55e8dc25705c96c0a5092e623fa38f1ce43c7f"
"md5=1af2d137eb20811c74ca516500164fd4"
"sha512=37a88b3ea0bde6089e5fbf0c1f10c1867c4edcd033ed3d5b75e7ed93e14ddd4f4c4db96baf638a054f65e294b83411497615c7fc14c6ff3a2a007e70f9d12c98"
"Since version 4.2, Merlin integration with completion packages company
and auto-complete has moved to separate modules. Make sure to add
`(require 'merlin-company)` or `(require 'merlin-ac)` to your emacs
configuration if you were using one of these."
{success}
"sha256=fb4caede73bdb8393bd60e31792af74b901ae2d319ac2f2a2252c694d2069d8d"
"sha512=ec301e0f97e11c1c331478030372d373d381a0ddbb7f72c83f7baa4c2c6d4f26094c3398f56bcf3d40c1242044391369fd06e8cd2ccfe1f5d78467eb3e9d33be"
"sha256=86c30769277d3e2c09a8be6c68a98cd342bc0bdbde07c7225bfe2e6d0da2d394"
"sha512=27fbfb2ac50d7cd86807bd8cb02ff1e4661ee46abf071b9bec505e1df6d41f366c9c287988e66096fb7a14b67fd806df39c56b9f70c53ec40612192ba2a0e530"
"md5=2c19731536a4f62923554c1947c39211"
"sha512=9bc252e2564fe6c9017b5ee1b2c4ddebf73c4be4b2a3d48f1d61b6ec1910a2cb9f4fa4952a7a6d89482c28ddbad8e0d9c34c206a1b2fe42bb2c3a7156aa953e9"
"sha256=ca43b6608366ddf891d7c1e1cc38de2c7f93a6da0511de164959db1f88fc42ed"
"sha512=43a00604f25bd1d3e93bfd43f1ef9c4cad9aa392c15a5db0c5ba0264f396e7ca6f60a0293467609402e87aeec441a05e7ee2990b37c98dc27b92a22afbebfd02"
"sha256=387b788ee4c0537f1fe02c25e05f0335af424828fc6fe940acc0db5948a5a71f"
"sha512=6ac80face6b77531c8d89a77d7a246bd5d43da435c355f62c03c8b8e360e1d7e339c904709fd3dbc9aa340c86ada9a69d5ebcf97cbdb7bd51bec97f831741b99"
"sha256=b2a68f3d3899cec3a50a99b05738295cc8a18672680406d0f68fbc95c01f1ba1"
"sha512=d1a6e2a639f77d297690f9ed79318b7a403444585b062d2add9f370320f735ba54bca191a34401f15c576c7ee55b5ed232f20d9599aa67821c747d7e684fc5a7"
x-commit-hash: "aeeb9317936937d360aa6cdb0cab953d11ff2c5d"
An implementation of 'glob' patterns for file paths,
extracted from ocamlbuild.
"""
This library provides a lean alternative to the Format [1] module of
the OCaml standard library. It aims to make it easy for users to do
the right thing. If you have tried Format before but find its API
complicated and difficult to use, then Pp might be a good choice for
you.
homepage: "https://gitlab.com/gasche/path_glob"
bug-reports: "https://gitlab.com/gasche/path_glob/-/issues"
doc: "https://gasche.gitlab.io/path_glob/doc/path_glob"
dev-repo: "git+https://gitlab.com/gasche/path_glob.git"
Pp uses the same concepts of boxes and break hints, and the final
rendering is done to formatter from the Format module. However it
defines its own algebra which some might find easier to work with and
reason about. No previous knowledge is required to start using this
library, however the various guides for the Format module such as this
one [2] should be applicable to Pp as well.
"sha256=5e09a2148876b68ac8fb315679ba69b1e207ced55d91a3ea5b3046f917102a07"
"sha512=f55775c694e4b66acdfc9210cccc4af505ecbce3101b638495623d7f18a169e4c904e1b86c1c13ec3af9ae765acd6eedfa6cb7059a0c8a4a1aff375b7e9114ab"
"sha256=e4a4e98d96b1bb76950fcd6da4e938c86d989df4d7e48f02f7a44595f5af1d56"
"sha512=58f78b083483006b40814be9aac33c895349eb1c6427d2762b4d760192613401262478bd5deff909763517560b06af7bf013c6a6f87d549aafa77b26345303f2"
src: "https://ocaml.janestreet.com/ocaml-core/v0.14/files/ppx_js_style-v0.14.0.tar.gz"
checksum: "md5=eab9c17616a2ba4cbd69a88db76070fd"
src: "https://github.com/janestreet/ppx_js_style/archive/refs/tags/v0.14.1.tar.gz"
checksum: "md5=2d79afa4f954aeafb81b64ecfc11c3fb"
"sha256=3eeb91e03966662284a3222e612dee7f4fa2b7637c53d9572d2a74134bb96d7a"
"sha512=425051dff9df53579a6edd17369d66c10f87a78daeddf1691e50997990ed643e874fcc6a30112a4dacbfd2d0097a19445354e04cd920d9522f76c51cdbc7f1db"
"sha256=d0e8a1ebdc6220b1574d7a926f008460c5118ccef79bf9a0ce0242f34cff225a"
"sha512=6010a59be6af873eaf193670f9cc8c9a7f091cfd89ec6c5b68d1f0c72d7c6015eec6371c009fc473cf2cb37d24f0934d04d0eacefa567a4945234197c3b31741"
opam-version: "2.0"
synopsis: "A library for building correct HTML and SVG documents"
description:
"TyXML provides a set of convenient combinators that uses the OCaml type system to ensure the validity of the generated documents. TyXML can be used with any representation of HTML and SVG: the textual one, provided directly by this package, or DOM trees (`js_of_ocaml-tyxml`) virtual DOM (`virtual-dom`) and reactive or replicated trees (`eliom`). You can also create your own representation and use it to instantiate a new set of combinators."
maintainer: ["dev@ocsigen.org"]
authors: ["The ocsigen team"]
license: "LGPL-2.1-only with OCaml-LGPL-linking-exception"
homepage: "https://github.com/ocsigen/tyxml"
doc: "https://ocsigen.org/tyxml/latest/manual/intro"
bug-reports: "https://github.com/ocsigen/tyxml/issues"
depends: [
"dune" {>= "2.0"}
"ocaml" {>= "4.02"}
"alcotest" {with-test}
"re" {>= "1.5.0"}
"seq"
"uutf" {>= "1.0.0"}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/ocsigen/tyxml.git"
x-commit-hash: "ef431a4bceaefb2d9248e79092e6c1a1a9420095"
url {
src:
"https://github.com/ocsigen/tyxml/releases/download/4.5.0/tyxml-4.5.0.tbz"
checksum: [
"sha256=c69accef5df4dd89d38f6aa0baad01e8fda4e9e98bb7dad61bec1452c5716068"
"sha512=772535441b09c393d53c27152e65f404a0a541aa0cea1bda899a8d751ab64d1729237e583618c3ff33d75e3865d53503d1ea413c6bbc8c68c413347efd1709b3"
]
}
open Globals
module L = (val Relog.logger ~namespace:__MODULE__ ())
open Lwt.Infix
let silence_frame =
[ '\xf8'; '\xff'; '\xfe' ] |> String.of_list
|> Bigstringaf.of_string ~off:0 ~len:3
let silence_stream = Lwt_stream.from_direct (fun () -> Some silence_frame)
let n_silence_pipe ?(n = 10) () =
Lwt_pipe.of_list (List.init n (fun _ -> silence_frame))
let of_pipe p =
let out = Lwt_pipe.create () in
let rec poll' () =
Lwt_pipe.read p >>= function
| None -> Lwt.return_unit
| Some data -> (
Lwt_pipe.write out data >>= function
| true -> poll' ()
| false -> Lwt.return_unit)
in
let fwd = poll' () in
Lwt_pipe.keep out fwd;
Lwt.on_termination fwd (fun () -> Lwt_pipe.close_nonblock out);
out
module Gen = struct
(** Fractional part of a float. *)
let fracf x = if x <. 1. then x else if x <. 2. then x -. 1. else fst (modf x)
let s16 =
let r = Int16.(max_int |> to_float) in
fun f -> Int.of_float (Float.(min 1. (max (-1.) f)) *. r)
let sine ?(freq = 440) ?(phase = 0.) ?(samplerate = 48000) ?(framelen = 20)
?(channels = 2) duration =
let phase = ref phase in
let framesize = samplerate / 1000 * framelen in
let volume = 0.5 in
let omega = float freq /. float samplerate in
let gen =
let buf =
Bigarray.(Array1.create float32 c_layout (framesize * channels))
in
fun () ->
for i = 0 to framesize - 1 do
let sample = volume *. sin ((float i *. omega) +. !phase) in
(* let sample = s16 sample in *)
for c = 0 to channels - 1 do
buf.{i + c} <- sample
done
done;
phase :=
mod_float (!phase +. (float framesize *. omega)) (2. *. Float.pi);
buf
in
let p = Lwt_pipe.create () in
let rec write = function
| 0 -> Lwt.return_unit
| n ->
Lwt_pipe.write p (gen ()) >>= fun ok ->
if ok then write (n - 1) else Lwt.return_unit
in
let nframes = Int.of_float (duration *. 1e3) / framelen in
let fut = write nframes in
Lwt_pipe.keep p fut;
Lwt.on_termination fut (fun () -> Lwt_pipe.close_nonblock p);
p
end
module Ffmpeg = struct
module L = (val Relog.clone (module L) ~namespace:"Ffmpeg")
module Parser = struct
let frame ~sample_p ~channels ~size ~kind =
let buf =
Bigarray.Array1.create kind Bigarray.c_layout (channels * size)
in
let p =
let open Angstrom in
let sample = count channels sample_p in
let rec samples ?(i = 0) () =
end_of_input >>| (fun () -> `eof) <|> (sample >>| fun s -> `sample s)
>>= function
| `eof -> return ()
| `sample chans ->
List.iteri (fun ci c -> buf.{i + ci} <- c) chans;
samples ~i:(i + channels) ()
in
samples () >>| fun () -> buf
in
fun buf ->
Angstrom.parse_bigstring ~consume:Angstrom.Consume.All p buf |> function
| Ok frame -> frame
| Error str -> failwith @@ "error parsing raw audio frame " ^ str
let s16le ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.LE.any_int16 ~channels ~size:frame_size
~kind:Bigarray.int16_signed
let s16be ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.BE.any_int16 ~channels ~size:frame_size
~kind:Bigarray.int16_signed
let f32le ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.LE.any_float ~channels ~size:frame_size
~kind:Bigarray.float32
let f32be ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.BE.any_float ~channels ~size:frame_size
~kind:Bigarray.float32
let f64le ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.LE.any_double ~channels ~size:frame_size
~kind:Bigarray.float64
let f64be ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.BE.any_double ~channels ~size:frame_size
~kind:Bigarray.float64
end
let pcm_args =
[ "-analyzeduration"; "0"; "-f"; "s16le"; "-ar"; "48000"; "-ac"; "2" ]
(* let ogg_args =
[ "-analyzeduration"; "0"; "-f"; "s16le"; "-ar"; "48000"; "-ac"; "2" ] *)
let stdout_args = [ "-" ]
let cmd = "ffmpeg" in
let p = Lwt_pipe.create () in
let p_p, u_p = Lwt.wait () in
let spawn () =
let cmd_str = cmd :: args |> List.to_string ~sep:" " Fun.id in
L.info (fun m -> m "running cmd: %s" cmd_str);
Lwt_process.with_process_full
("", Array.of_list @@ cmd :: args)
(fun proc ->
let raw = proc#stdout in
let logs () = Lwt_io.read proc#stderr in
L.info (fun m -> m "ffmpeg running with pid=%d" proc#pid);
let buf_len = Bigstringaf.length buf in
let parse_frame =
let parser =
in
fun () -> parser buf
in
let rec poll' () =
L.trace (fun m -> m "polling");
Lwt_io.read_into_exactly_bigstring raw buf 0 buf_len
|> Lwt_result.catch
>>= function
| Ok () ->
L.trace (fun m -> m "parsing frame");
let frame = parse_frame () in
if Lwt.is_sleeping p_p then Lwt.wakeup_later u_p (Ok p);
Lwt_pipe.write p frame >>= fun ok ->
if ok then poll' () else proc#close >|= ignore
| Error End_of_file -> Lwt.return_unit
| Error exn -> Lwt.fail exn
in
let open Lwt.Syntax in
let* status = poll' () >>= fun () -> proc#status in
let+ res =
match status with
| Unix.WEXITED 0 ->
if Lwt.is_sleeping p_p then
else Lwt_result.return ()
| WEXITED n ->
L.error (fun m ->
m "got non 0 status code for ffmpeg (status=%d)" n);
let+ logs = logs () in
L.error (fun m -> m "logs:@.%s" logs);
| WSIGNALED n | WSTOPPED n ->
L.warn (fun m -> m "ffmpeg process was closed with code=%d" n);
Lwt_result.return ()
in
match (Lwt.is_sleeping p_p, res) with
| true, (Error _ as e) -> Lwt.wakeup_later u_p e
| true, Ok () -> ()
| false, _ -> ())
in
let k = spawn () in
Lwt_pipe.keep p k;
Lwt.on_termination k (fun () -> Lwt_pipe.close_nonblock p);
p_p |> Lwt_result.map of_pipe
end
Error.msgf "got non 0 status code for ffmpeg (status=%d)\n%s" n
logs
Lwt.return (Error.msg "0 byte stream?")
Parser.s16le ~channels:Rtp._CHANNELS ~frame_size:Rtp._FRAME_SIZE
let buf = Bigstringaf.create (Rtp._FRAME_SIZE * Rtp._CHANNELS * 2) in
let args = List.concat [ [ "-i"; input ]; pcm_args; stdout_args ] in
let create input =
module L = (val Relog.logger ~namespace:__MODULE__ ())
module Payload = Gateway_payload
let get_gateway_url http =
let api () =
let open Lwt_result.Syntax in
let* res = Http.get "/gateway/bot" http in
let* body = Piaf.Body.to_string res.body in
Result.guard_str (fun () ->
let raw = Yojson.Safe.from_string body in
Yojson.Safe.Util.(raw |> member "url" |> to_string))
|> Result.map_err (fun e -> `Msg e)
|> Lwt.return
in
api ()
|> Lwt.map (function
| Ok url -> url
| Error e ->
L.err (fun m ->
m "Couldn't get gateway url from API, using fallback: %s@."
(Piaf.Error.to_string e));
"wss://gateway.discord.gg")
|> Lwt_result.ok
let connect ?http token =
let open Lwt_result.Syntax in
let* http =
match http with None -> Http.create token | Some d -> Lwt_result.return d
in
let* url = get_gateway_url http in
let+ session = Session.create token (Uri.of_string url) in
let get_voice ~guild_id { voice_sessions; _ } =
SfMap.get guild_id voice_sessions
let wait_for_updates =
let rec f' ?st ?srv () =
let* evt = Lwt_pipe.read evs in
match ((evt : Events.t option), st, srv) with
L.debug (fun m -> m "got voice state");
f' ~st ?srv ()
L.debug (fun m -> m "got voice server");
f' ?st ~srv ()
Lwt.return (st, srv)
| _ -> assert false
and evs =
_fork_events t
|> Lwt_pipe.Reader.filter ~f:(function
| _ -> false)
in
let p = f' () in
Lwt.on_termination p (fun () ->
L.info (fun m -> m "closing events fork");
Lwt_pipe.close_nonblock evs);
p
in
let open Lwt_result.Syntax in
let* st, srv =
Lwt.pick
[
Lwt_unix.sleep 5. |> Lwt.map (fun () -> `Timeout);
wait_for_updates |> Lwt.map (fun o -> `Ok o);
]
|> Lwt.map (function
| `Ok o -> Ok o
| `Timeout -> Error (`Discord "timed out waiting for update events"))
in
let cleanup _ =
L.warn (fun m -> m "cleanup");
match SfMap.get guild_id t.voice_sessions with
| Some { mixer; _ } ->
Mixer.destroy mixer;
t.voice_sessions <- SfMap.remove guild_id t.voice_sessions
| None -> ()
in
Voice.create ~on_destroy:cleanup ~server_id:guild_id ~user_id:user.id
~session_id:st.session_id ~token:srv.token srv.endpoint
in
let open Lwt_result.Syntax in
match SfMap.get guild_id t.voice_sessions with
| Some vs when Models.Snowflake.(vs.channel_id = channel_id) ->
L.info (fun m ->
m "there an active voice connection for channel '%Ld' already"
vs.channel_id);
Lwt_result.return vs
| Some _vs ->
L.info (fun m ->
m "already active voice session for guild %Ld, switching channel..."
guild_id);
let* conn = join () in
let+ mixer = Mixer.create conn |> Lwt_result.lift in
let vs = { conn; channel_id; mixer } in
t.voice_sessions <- SfMap.add guild_id vs t.voice_sessions;
vs
| None ->
let* conn = join () in
let+ mixer = Mixer.create conn |> Lwt_result.lift in
let vs = { conn; channel_id; mixer } in
t.voice_sessions <- SfMap.add guild_id vs t.voice_sessions;
vs
| Events.Voice_state_update st -> is_own_vs st
| Events.Voice_server_update srv -> is_own_srv srv
| Some (Voice_state_update st), _, Some srv
| Some (Voice_server_update srv), Some st, _ ->
| Some (Voice_server_update srv), None, _ ->
| Some (Voice_state_update st), _, None ->
let rec t = { session; events; voice_sessions }
and events = Session.events session |> Lwt_pipe.to_stream
and voice_sessions = SfMap.empty in
t
let user { session; _ } = Session.user session
let events { events; _ } = events |> Lwt_pipe.of_stream
let _fork_events t = Lwt_stream.clone t.events |> Lwt_pipe.of_stream
let leave_voice ~guild_id ({ session; voice_sessions; _ } as t) =
let open Lwt.Infix in
match SfMap.get guild_id voice_sessions with
| Some vs ->
Voice.disconnect vs.conn >>= fun () ->
t.voice_sessions <- SfMap.remove guild_id voice_sessions;
Session.send_voice_state_update session ~self_mute:true ~self_deaf:true
guild_id
| None -> Lwt.return_unit
let join_voice ~guild_id ~channel_id ({ session; _ } as t) =
let open Lwt.Syntax in
let user = Session.user session in
let join () =
let* () =
Session.send_voice_state_update session ~channel_id ~self_deaf:false
~self_mute:false guild_id
in
let is_own_vs
match (guild, chan) with
| Some g, Some c ->
Models.Snowflake.(guild_id = g && channel_id = c && user_id = user.id)
| _ -> false
in
Models.Snowflake.(guild_id = guild)
in
let is_own_srv { Events.Voice_server_update.guild_id = guild; _ } =
{ Events.Voice_state.guild_id = guild; channel_id = chan; user_id; _ } =
let disconnect t =
SfMap.to_seq t.voice_sessions
|> Seq.map (fun (_, { conn; _ }) -> Voice.disconnect conn)
|> Seq.to_list |> Lwt.join
>>= fun () ->
t.voice_sessions <- SfMap.empty;
Session.disconnect t.session
type t = {
session : Session.t;
mutable events : Events.t Lwt_stream.t;
mutable voice_sessions : voice_session SfMap.t;
}
and voice_session = { channel_id : snowflake; conn : Voice.t; mixer : Mixer.t }
module SfMap = Map.Make (Models.Snowflake)
open Globals
open Lwt.Infix
include Containers
module Models = Discord_models
type snowflake = Models.Snowflake.t
type bigstring = Bigstringaf.t
module Lwt_pipe = struct
include Lwt_pipe
(* TODO this hasn't actually been tested *)
let multicast ~n p =
assert (n > 1);
let main = create () in
let subs = Seq.(1 --^ n >|= (fun _ -> create ()) |> to_list) in
let running_pipes () = List.filter (fun p -> not @@ is_closed p) subs in
let rec fwd_rec () =
let open Lwt.Infix in
read p >>= function
| Some d ->
main :: running_pipes () |> Lwt_list.exists_p (fun p -> write p d)
>>= fun ok -> if ok then fwd_rec () else Lwt.return_unit
| None -> Lwt.return_unit
in
let fwd = fwd_rec () in
keep main fwd;
List.iter (fun p -> link_close p ~after:main) subs;
Lwt.on_termination fwd (fun () -> close_nonblock main);
(main, subs)
let fork p =
let p, sibs = multicast ~n:2 p in
(p, List.hd sibs)
end
include Dp_utils
include Stdint
open Globals
open Lwt.Infix
module L = (val Relog.logger ~namespace:__MODULE__ ())
module F = Relog.Field
module Pl = Voice_payload
module Ws = struct
type t = t' ref
let create conn = ref (Open (Token_bucket.make ~capacity:2 1., conn))
let send t pl =
match !t with
| Open (tb, conn) ->
Lwt.(
Token_bucket.take tb >|= fun () ->
Ok ())
let send_exn t pl =
Lwt.(
send t pl >|= function
| Ok () -> ()
| Error e -> failwith (Error.to_string e))
let close ~code t =
match !t with
| Open (tb, conn) ->
t := Closed;
Token_bucket.cancel_waiting tb;
| Closed -> ()
end
module Close_code = struct
type discord =
[ `Unknown_op
| `Invalid_payload
| `Not_authenticated
| `Authentication_failed
| `Already_authenticated
| `Invalid_session
| `Session_timeout
| `Server_not_found
| `Unknown_protocol
| `Disconnected
| `Voice_server_crashed
| `Unknown_encryption_mode ]
type t = [ Websocket.Close_code.standard | discord ]
let is_discord = function #discord -> true | _ -> false
let is_std = function #Websocket.Close_code.standard -> true | _ -> false
let to_int = function
| #Websocket.Close_code.standard as c -> Websocket.Close_code.to_int c
| `Unknown_op -> 4001
| `Invalid_payload -> 4002
| `Not_authenticated -> 4003
| `Authentication_failed -> 4004
| `Already_authenticated -> 4005
| `Invalid_session -> 4006
| `Session_timeout -> 4009
| `Server_not_found -> 4011
| `Unknown_protocol -> 4012
| `Disconnected -> 4014
| `Voice_server_crashed -> 4015
| `Unknown_encryption_mode -> 4016
let of_close_code_exn = function
| #Websocket.Close_code.standard as c -> c
| `Other 4001 -> `Unknown_op
| `Other 4002 -> `Invalid_payload
| `Other 4003 -> `Not_authenticated
| `Other 4004 -> `Authentication_failed
| `Other 4005 -> `Already_authenticated
| `Other 4006 -> `Invalid_session
| `Other 4009 -> `Session_timeout
| `Other 4011 -> `Server_not_found
| `Other 4012 -> `Unknown_protocol
| `Other 4014 -> `Disconnected
| `Other 4015 -> `Voice_server_crashed
| `Other 4016 -> `Unknown_encryption_mode
| `Other c -> failwith (Printf.sprintf "unknown voice close code: %d" c)
let pp =
let pp' fmt = function
| `Unknown_op -> Format.fprintf fmt "invalid opcode"
| `Invalid_payload ->
Format.fprintf fmt "invalid payload while identifying"
| `Not_authenticated -> Format.fprintf fmt "not authenticated"
| `Authentication_failed -> Format.fprintf fmt "authentication failed"
| `Already_authenticated -> Format.fprintf fmt "already authenticated"
| `Invalid_session -> Format.fprintf fmt "session is no longer valid"
| `Session_timeout -> Format.fprintf fmt "session timed out"
| `Server_not_found -> Format.fprintf fmt "voice server not found"
| `Unknown_protocol -> Format.fprintf fmt "unknown protocol"
| `Disconnected -> Format.fprintf fmt "disconnected"
| `Voice_server_crashed -> Format.fprintf fmt "voice server crashed"
| `Unknown_encryption_mode -> Format.fprintf fmt "unknown encryption mode"
in
fun fmt t ->
match t with
| #Websocket.Close_code.standard as c -> Websocket.Close_code.pp fmt c
| #discord as t -> Format.fprintf fmt "(%d %a)" (to_int t) pp' t
let is_recoverable = function
| `Unknown_op | `Invalid_payload | `Not_authenticated
| `Authentication_failed | `Already_authenticated | `Unknown_protocol
| `Disconnected | `Unknown_encryption_mode
| #Websocket.Close_code.standard ->
false
| _ -> true
end
type t = {
disconnect : unit -> unit Lwt.t;
ws_writer : Pl.send Lwt_pipe.Writer.t;
speak : bool Lwt_pipe.Writer.t;
rtp_writer : bigstring Lwt_pipe.Writer.t;
}
type hb = {
mutable interval : float;
mutable preempt : ?interval:float -> unit -> unit;
mutable ack : int -> unit;
mutable cancel : unit -> unit;
}
(* TODO Check if resuming also implies a fresh UDP handshake *)
type handshake_state =
| Greet of conn
| Id of hb
| Resuming of hb * session
| Establish_udp of hb * Udp_connection.t
and conn = Fresh | Reconnection of session
and session = { rtp : Rtp.t; mutable speaking : bool }
let make_heartbeat ?err fn interval =
let err =
Option.get_or
~default:(fun () -> failwith "no ACK of last heartbeat received")
err
in
let out =
{
interval;
preempt = (fun ?interval:_ -> ignore);
ack = ignore;
cancel = ignore;
}
in
let gen_nonce =
let st = Random.State.make_self_init () in
fun () -> Random.State.bits st
in
let rec loop () =
let open Lwt.Syntax in
let acked = ref false in
let nonce = gen_nonce () in
let () = fn nonce in
let p_preempt, u_preempt = Lwt.wait () in
let p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) in
out.ack <- (fun n -> if not !acked then acked := nonce = n);
out.cancel <-
(fun () ->
if Lwt.is_sleeping p_preempt then Lwt.wakeup_later u_preempt `Cancel);
out.preempt <-
(fun ?(interval = interval) () ->
if Lwt.is_sleeping p_preempt then
Lwt.wakeup_later u_preempt (`Preempt interval));
let* r = Lwt.pick [ p_sleep; p_preempt ] in
match (r, !acked) with
| `Sleep, true -> loop ()
| `Preempt interval, _ ->
out.interval <- interval;
loop ()
| `Sleep, false ->
err ();
Lwt.return ()
| `Cancel, _ -> Lwt.return ()
in
Lwt.async loop;
out
let secret_of_int_list l =
Seq.of_list l |> Seq.map Char.of_int_exn |> Bytes.of_seq
let with_ws_params ~version uri =
let uri = Uri.with_path uri "/" in
Uri.with_query uri [ ("v", [ Versions.Voice.to_string version ]) ]
let do_handshake ~server_id ~user_id ~session_id ~token ?session ws pl_pipe =
let open Lwt_result.Syntax in
let make_hb interval =
make_heartbeat
(fun nonce -> Lwt.async (fun () -> Ws.send_exn ws (Pl.heartbeat nonce)))
interval
in
let rec poll' st =
let* pl =
Lwt_pipe.read pl_pipe
|> Lwt.map (function
| Some (`Pl pl) -> Ok pl
| Some (`Closed code) -> Error (`Closed code)
| None -> assert false)
in
match (st, pl) with
| Greet Fresh, Pl.Hello hb ->
L.info (fun m -> m "got greeting, identifying");
let* () =
Ws.send ws (Pl.make_identify ~server_id ~user_id ~session_id ~token)
in
let hb_secs = Float.of_int hb /. 1e3 in
let hb = make_hb hb_secs in
poll' (Id hb)
| Greet (Reconnection session), Hello hb ->
L.info (fun m -> m "got greeting, resuming session '%s'" session_id);
let* () = Ws.send ws (Pl.make_resume ~server_id ~session_id ~token) in
let hb_secs = Float.of_int hb /. 1e3 in
let hb = make_hb hb_secs in
poll' (Resuming (hb, session))
| ( ((Id hb | Resuming (hb, _) | Establish_udp (hb, _)) as st),
Hello new_interval ) ->
let hb_secs = Float.of_int new_interval /. 1e3 in
L.warn (fun m -> m "got new greeting, updating heartbeat");
hb.preempt ~interval:hb_secs ();
poll' st
| ( ((Id hb | Resuming (hb, _) | Establish_udp (hb, _)) as st),
L.debug (fun m -> m "got hearbeat ack for nonce '%d'" nonce);
hb.ack nonce;
poll' st
| Resuming (hb, session), Resumed -> Lwt_result.return (hb, session)
| Id hb, Ready ({ ip; port; ssrc; _ } as info) ->
L.info (fun m ->
m
"voice ws session is ready, connecting to UDP voice server with:\n\
%a"
Pl.Ready.pp info);
let* udp = Udp_connection.create ~ssrc (ip, port) |> Error.catch_lwt in
let ip, port = Udp_connection.local_addr udp in
let address = Unix.string_of_inet_addr ip in
let* () =
Ws.send ws
(Pl.make_select_protocol ~address ~port ~mode:"xsalsa20_poly1305")
in
poll' (Establish_udp (hb, udp))
L.info (fun m -> m "successfuly handshaked voice connection");
let secret = secret_of_int_list desc.secret_key in
let mode = Udp_connection.encryption_mode_of_string desc.mode in
let crypt = Udp_connection.{ secret; mode } in
Lwt_result.return (hb, { rtp = Rtp.make ~udp ~crypt; speaking = false })
| st, _pl ->
L.warn (fun m -> m "ignoring non-control payload during handshake");
poll' st
in
let c = match session with Some s -> Reconnection s | None -> Fresh in
poll' (Greet c)
let create_conn uri =
let open Lwt_result.Syntax in
let p =
|> Lwt_pipe.Reader.map ~f:(function
| Close code ->
let code = Close_code.of_close_code_exn code in
L.warn (fun m ->
m "voice ws session was closed: %a" Close_code.pp code);
`Closed code)
in
( Ws.create conn,
(p : [ `Pl of Pl.recv | `Closed of Close_code.t ] Lwt_pipe.Reader.t) )
let create ?(on_destroy = fun _ -> ()) ?(version = Versions.Voice.V4) ~server_id
~user_id ~session_id ~token endpoint =
let open Lwt_result.Syntax in
let uri = Uri.of_string ("wss://" ^ endpoint) |> with_ws_params ~version in
let p_init, u_init = Lwt.wait () in
let ws_writer = Lwt_pipe.create () in
let rtp_writer = Lwt_pipe.create () in
let speak_pipe = Lwt_pipe.create () in
let read_exn p = Lwt_pipe.read p >|= Option.get_exn in
let dc = ref (fun () -> Lwt.return ()) in
let rec manage' ?session () =
let rec connect () =
let* ws, pipe = create_conn uri in
let res =
do_handshake ~server_id ~user_id ~session_id ~token ?session ws pipe
in
Lwt.bind res (function
| Ok (hb, session) -> Lwt_result.return (ws, pipe, hb, session)
| Error (`Closed code) when Close_code.is_recoverable code ->
L.warn (fun m ->
m "recoverable close code during handshake, retrying...");
connect ()
| Error (`Closed `Disconnected) ->
L.warn (fun m ->
m "disconnected during handshake, invalid permissions?");
Lwt.return
(Error (`Discord "voice session disconnected during handshake"))
| Error (`Closed _) ->
L.error (fun m -> m "unrecoverable close code during handshake");
assert false
| Error #Error.t as e -> Lwt.return e)
in
let* ws, pipe, hb, session = connect () in
let p_dc, u_dc = Lwt.wait () in
let p_dc'ed, u_dc'ed = Lwt.wait () in
(dc :=
fun () ->
Lwt.wakeup_later u_dc `Dc;
p_dc'ed);
if Lwt.is_sleeping p_init then
Lwt.wakeup_later u_init
(Ok
{
disconnect = (fun () -> !dc ());
ws_writer;
rtp_writer;
speak = speak_pipe;
});
let bus =
Lwt_pipe.Reader.merge_all
[
pipe |> Lwt_pipe.Reader.map ~f:(fun pl -> `Ws pl);
ws_writer |> Lwt_pipe.Reader.map ~f:(fun pl -> `Fwd pl);
speak_pipe |> Lwt_pipe.Reader.map ~f:(fun sp -> `Speak sp);
rtp_writer |> Lwt_pipe.Reader.map ~f:(fun pl -> `Fwd_rtp pl);
]
in
let rec poll' () =
let open Lwt.Syntax in
let pl = read_exn bus in
let* res = Lwt.pick [ pl; p_dc ] in
match res with
| `Fwd pl -> Ws.send_exn ws pl >>= poll'
| `Speak sp when Stdlib.(sp != session.speaking) ->
session.speaking <- sp;
Ws.send_exn ws
(Pl.make_speaking ~ssrc:(Rtp.ssrc session.rtp) ~delay:0
(if sp then 1 else 0))
>>= poll'
| `Speak _ -> poll' ()
| `Fwd_rtp audio -> Rtp.send_packet session.rtp audio >>= poll'
| `Ws (`Pl pl) -> handle_payload pl
| `Ws (`Closed code) when Close_code.is_recoverable code ->
L.warn (fun m ->
m "session closed with recoverable close code, retrying...");
session.speaking <- false;
hb.cancel ();
manage' ~session ()
| `Ws (`Closed code) ->
L.error (fun m ->
m "session closed with unrecoverable close code: %a" Close_code.pp
code);
Lwt.return
(Error (`Discord (Format.asprintf "%a" Close_code.pp code)))
| `Dc ->
Lwt_pipe.close_nonblock rtp_writer;
Rtp.destroy session.rtp;
Ws.close ~code:`Normal_closure ws;
Lwt_pipe.close_nonblock pipe;
Lwt_pipe.close_nonblock ws_writer;
Lwt_pipe.close_nonblock bus;
Lwt.wakeup_later u_dc'ed ();
Lwt.return (Ok ())
and handle_payload = function
| Hello new_hb ->
L.warn (fun m -> m "got new greeting, updating heartbeat");
hb.preempt ~interval:(Float.of_int new_hb /. 1e3) ();
poll' ()
| Ready info ->
L.error (fun m ->
m "got ready on established session, new voice server??@.%a"
Pl.Ready.pp info);
assert false
L.warn (fun m ->
m "got session description, updating...@.%a"
let secret = secret_of_int_list desc.secret_key in
let mode = Udp_connection.encryption_mode_of_string desc.mode in
Rtp.set_crypt session.rtp Udp_connection.{ secret; mode };
poll' ()
hb.ack nonce;
poll' ()
L.debug (fun m -> m "ignoring speaking/resumed/clientdisconnect");
poll' ()
in
poll' () >|= fun out ->
hb.cancel ();
Lwt_pipe.close_nonblock rtp_writer;
Rtp.destroy session.rtp;
Ws.close ~code:`Going_away ws;
Lwt_pipe.close_nonblock pipe;
Lwt_pipe.close_nonblock ws_writer;
Lwt_pipe.close_nonblock bus;
out
in
Lwt.async (fun () ->
manage' ()
|> Lwt.map (function
| Ok () -> ()
| Error e when Lwt.is_sleeping p_init ->
Lwt.wakeup_later u_init (Error e)
| Error e -> on_destroy e));
p_init
let _speak s { speak; _ } = Lwt_pipe.write_exn speak s
let start_speaking = _speak true
let stop_speaking = _speak false
let send_rtp { rtp_writer; _ } audio = Lwt_pipe.write_exn rtp_writer audio
let disconnect { disconnect; _ } = disconnect ()
| Speaking _ | Resumed | Client_disconnect ->
| Heartbeat_ack nonce ->
Pl.Session_description.pp desc);
| Session_description desc ->
| Ws_conn.Payload pl -> `Pl pl
Lwt_pipe.of_stream (Ws_conn.stream conn)
let+ conn = Ws_conn.create ~zlib:false uri in
| Establish_udp (hb, udp), Session_description desc ->
Heartbeat_ack nonce ) ->
Ws_conn.close ~code conn
| Closed -> Lwt.return (Error.msg "cannot send payload to closed ws")
Ws_conn.send conn pl;
and t' = Open of Token_bucket.t * Ws_conn.t | Closed
module Ws_conn = Websocket.Make (Voice_payload)
open Lwt.Infix
type t = Cache
let create () = Lwt.return Cache
let send_message channel_id content { http; _ } =
let msg = { content; nonce = Websocket.gen_nonce 20; tts = false } in
let uri =
Format.sprintf "/channels/%s/messages"
(Models.Snowflake.to_string channel_id)
in
let ser = yojson_of_create_msg msg in
Http.post ~body:ser uri http
|> Lwt.map (function
| Ok _ -> ()
| Error _ -> L.error (fun m -> m "error sending message"))
val destroy : t -> unit Lwt.t
end
let join_voice ~guild_id ~channel_id { gw; _ } =
Gateway.join_voice ~guild_id ~channel_id gw
|> Lwt.map (function
| Ok _ -> ()
| Error e ->
L.error (fun m ->
m "couldn't join voice channel '%Ld' on guild '%Ld: %s"
channel_id guild_id (Error.to_string e)))
module Make (Voice : Voice_manager_intf) = struct
type t = { http : Http.t; gw : Gateway.t; cache : Cache.t; voice : Voice.t }
let play_audio_stream ~guild_id stream { gw; _ } =
match Gateway.get_voice ~guild_id gw with
| Some vs -> Mixer.play vs.mixer stream >|= ignore
| None -> Lwt.return_unit
type create_msg = { content : string; nonce : string; tts : bool }
[@@deriving yojson]
let disconnect { gw; _ } = Gateway.disconnect gw
let send_message channel_id content { http; _ } =
let msg = { content; nonce = Websocket.gen_nonce 20; tts = false } in
let uri =
Format.sprintf "/channels/%s/messages"
(Models.Snowflake.to_string channel_id)
in
let ser = yojson_of_create_msg msg in
Http.post ~body:ser uri http
|> Lwt.map (function
| Ok _ -> ()
| Error _ -> L.error (fun m -> m "error sending message"))
let create ~handler token =
let open Lwt_result.Syntax in
L.info (fun m -> m "creating HTTP client");
let* http = Http.create token in
L.info (fun m -> m "connecting to gateway");
let* gw = Gateway.connect ~http token in
let t = { http; gw } in
Gateway.events gw
|> Lwt_pipe.Reader.iter_s ~f:(fun ev -> handler t ev)
|> Lwt_result.ok
let gateway { gw; _ } = gw
let disconnect { gw; voice; _ } =
Voice.destroy voice >>= fun () -> Gateway.disconnect gw
let voice { voice; _ } = voice
let run ~handler token =
let open Lwt_result.Syntax in
L.info (fun m -> m "creating HTTP client");
let* http = Http.create token in
L.debug (fun m -> m "getting gateway url");
let* gw_info = Http.get_gateway_info http |> Lwt_result.ok in
let url, max_concurrency =
match gw_info with
| Fallback { url; max_concurrency } -> (url, max_concurrency)
| Real { url; session_start_limit; _ } ->
(url, session_start_limit.max_concurrency)
in
L.info (fun m -> m "connecting to gateway");
let* gw = Gateway.connect ~max_concurrency ~token url in
L.info (fun m -> m "initialising cache");
let* cache = Cache.create () |> Lwt_result.ok in
L.info (fun m -> m "initialising voice manager");
let voice = Voice.make gw in
let t = { http; gw; cache; voice } in
L.info (fun m -> m "starting main event loop");
let ev_handler = handler t in
let evs = Gateway.events gw in
let rec fwd () =
Mpmc.Sink.pull evs >>= function
| Some ev -> ev_handler ev >>= fwd
| None -> Lwt.return_ok ()
in
fwd ()
end
module Nop_voice = struct
type t = Nop
let make _ = Nop
let destroy _ = Lwt.return_unit
end
include Make (Nop_voice)
(library
(name disco_core)
(public_name disco.core)
(preprocess
(pps ppx_deriving.show ppx_deriving.ord ppx_yojson_conv))
(libraries
disco.models
disco.utils
containers
containers-data
stdint
relog.lib
uri
lwt
lwt.unix
lwt-pipe
angstrom
angstrom-lwt-unix
yojson
mtime
mtime.clock.os
hxd.core
hxd.string
piaf
sodium
opus
websocketaf
websocketaf-lwt-unix
httpaf-lwt-unix))
open! Globals
open Lwt.Infix
module L = (val Relog.logger ~namespace:__MODULE__ ())
module Payload = Gateway_payload
type t = {
mutable user : Models.User.t;
shards : shard list;
id_bucket : Token_bucket.t;
ev_rx : Events.t Lwt_pipe.Reader.t;
ev_snk : Events.t Mpmc.Sink.t;
}
and shard = Lwt_mutex.t * Session.t
let disconnect t =
List.map
(fun (mtx, sess) ->
Lwt_mutex.with_lock mtx (fun () -> Session.disconnect sess))
t.shards
|> Lwt.join
>>= fun () -> Lwt_pipe.close t.ev_rx
let connect ?(max_concurrency = 1) ~token url =
let open Lwt_result.Syntax in
(* docs: identify requests allowed per 5 seconds *)
let rate = 1. /. (float max_concurrency /. 5.) in
let id_bucket = Token_bucket.make ~capacity:max_concurrency rate in
let+ main = Session.create ~id_bucket token (Uri.of_string url) in
let user = Session.user main in
let shards = [ main ] in
let ev_rx = List.map Session.events shards |> Lwt_pipe.Reader.merge_all in
let src, snk = Mpmc.make () in
let rec fwd () =
Lwt_pipe.read ev_rx >>= function
| Some v -> Mpmc.Source.write src v >>= fwd
| None -> Mpmc.Source.close src
in
Lwt.async fwd;
{
user;
shards = List.map (fun s -> (Lwt_mutex.create (), s)) shards;
id_bucket;
ev_rx;
ev_snk = snk;
}
let user { user; _ } = user
let shards { shards; _ } = shards
let shard ~guild_id ~f { shards; _ } =
let len = List.length shards in
let id = Int64.(guild_id lsr 22 |> to_int) mod len in
let mtx, sess = List.get_at_idx_exn id shards in
Lwt_mutex.with_lock mtx (fun () -> f sess)
let main_shard { shards; _ } = List.hd shards
let events { ev_snk; _ } = ev_snk
let send_presence_update t ?since ~afk status =
let mtx, shard = main_shard t in
Lwt_mutex.with_lock mtx (fun () ->
Session.send_presence_update shard ?since ~afk status)
let send_voice_state_update t ?channel_id ?self_mute ?self_deaf guild_id =
let f shard =
Session.send_voice_state_update shard ?channel_id ?self_mute ?self_deaf
guild_id
in
shard ~guild_id ~f t
let send_guild_request_members t ?presences ?nonce ~q guild_id =
let f shard =
Session.send_guild_request_members shard ?presences ?nonce ~q guild_id
in
shard ~guild_id ~f t
module Intents = struct
type t = int [@@deriving yojson]
let make = List.fold_left (fun out intent -> out lor intent) 0
let add i t = t lor i
let rm i t = lnot i land t
let guilds = 1 lsl 0
let guild_members = 1 lsl 1
let guild_bans = 1 lsl 2
let guild_emojis = 1 lsl 3
let guild_integrations = 1 lsl 4
let guild_webhooks = 1 lsl 5
let guild_invites = 1 lsl 6
let guild_voice_states = 1 lsl 7
let guild_messages = 1 lsl 9
let guild_message_reactions = 1 lsl 10
let guild_message_typing = 1 lsl 11
let direct_messages = 1 lsl 12
let direct_message_reactions = 1 lsl 13
let direct_message_typing = 1 lsl 14
let all = (1 lsl 15) - 1
let all_unprivileged =
List.fold_left (fun is i -> rm i is) all [ guild_presences; guild_members ]
let pp fmt t =
let out =
let rec aux acc d miss =
match (d, miss) with
| 0, 0 -> String.concat "" acc
| 0, n when n > 0 -> String.make n '0' ^ String.concat "" acc
| _, n -> aux (string_of_int (d land 1) :: acc) (d lsr 1) (n - 1)
in
aux [] t 15
in
Format.fprintf fmt "%s" out
let show = Format.asprintf "%a" pp
end
include Stdint
include Containers
include Disco_utils
module Models = Disco_models
type snowflake = Models.Snowflake.t
type sf = Models.Snowflake.t
type bigstring = Bigstringaf.t
type bs = Bigstringaf.t
open! Globals
type t = {
mutable interval : float;
mutable preempt : ?interval:float -> unit -> unit;
mutable ack : unit -> unit;
mutable cancel : unit -> unit;
}
let interval { interval; _ } = interval
let preempt ?interval { preempt; _ } = preempt ?interval ()
let ack { ack; _ } = ack ()
let cancel { cancel; _ } = cancel ()
let make ?err fn interval =
let err =
Option.get_or
~default:(fun () -> failwith "no ACK of last heartbeat received")
err
in
let stub = Fun.const () in
let out =
{ interval; preempt = (fun ?interval:_ -> stub); ack = stub; cancel = stub }
in
let rec loop () =
let open Lwt.Syntax in
let acked = ref false in
let () = fn () in
let p_preempt, u_preempt = Lwt.wait () in
let p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) in
out.ack <- (fun () -> acked := true);
out.cancel <-
(fun () ->
if Lwt.is_sleeping p_preempt then Lwt.wakeup_later u_preempt `Cancel);
out.preempt <-
(fun ?(interval = interval) () ->
if Lwt.is_sleeping p_preempt then
Lwt.wakeup_later u_preempt (`Preempt interval));
let* r = Lwt.pick [ p_sleep; p_preempt ] in
match (r, !acked) with
| `Sleep, true -> loop ()
| `Preempt interval, _ ->
out.interval <- interval;
loop ()
| `Sleep, false ->
err ();
Lwt.return ()
| `Cancel, _ -> Lwt.return ()
in
Lwt.async loop;
out
module R = struct
type gateway_info = {
url: string;
shards: int;
session_start_limit: session_start_limit;
}
and session_start_limit = {
total: int;
remaining: int;
reset_after: int;
max_concurrency: int;
}
[@@deriving yojson, show] [@@yojson.allow_extra_fields]
end
type gateway_info =
| Fallback of { url: string; max_concurrency: int }
| Real of R.gateway_info
let fallback_gw_url = "wss://gateway.discord.gg"
let gateway_info = Fallback { url = fallback_gw_url; max_concurrency = 1 }
let get_gateway_info t =
let api () =
let open Lwt_result.Syntax in
let* res = get "/gateway/bot" t in
let* body = Piaf.Body.to_string res.body in
Result.guard (fun () ->
let raw = Yojson.Safe.from_string body in
R.gateway_info_of_yojson raw)
|> Result.map_err Error.of_exn
|> Lwt.return
in
api () >|= function
| Ok real -> Real real
| Error e ->
L.warn (fun m ->
m "Couldn't get gateway url from API, using fallback (%s):@.%a"
fallback_gw_url Piaf.Error.pp_hum e);
gateway_info
let create conn = ref (Open (Token_bucket.make ~capacity:2 1., conn))
(* docs: clients are allowed to send 120 gateway commands every 60 seconds *)
let create ~id_bucket conn =
let capacity = 120 in
let rate = 1. /. (float capacity /. 60.) in
let bucket = Token_bucket.make ~capacity rate in
ref (Open { id_bucket; bucket; conn })
| Open (tb, conn) ->
Lwt.(
Token_bucket.take tb >|= fun () ->
Ws_conn.send conn pl;
Ok ())
| Open t ->
let bucket =
match pl with Pl.Identify _ -> t.id_bucket | _ -> t.bucket
in
Token_bucket.take bucket >|= fun () ->
Ws_conn.send t.conn pl;
Ok ()
let make_heartbeat ?err fn interval =
let err =
Option.get_or
~default:(fun () -> failwith "no ACK of last heartbeat received")
err
in
let stub = Fun.const () in
let out =
{ interval; preempt = (fun ?interval:_ -> stub); ack = stub; cancel = stub }
in
let rec loop () =
let open Lwt.Syntax in
let acked = ref false in
let () = fn () in
let p_preempt, u_preempt = Lwt.wait () in
let p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) in
out.ack <- (fun () -> acked := true);
out.cancel <-
(fun () ->
if Lwt.is_sleeping p_preempt then Lwt.wakeup_later u_preempt `Cancel);
out.preempt <-
(fun ?(interval = interval) () ->
if Lwt.is_sleeping p_preempt then
Lwt.wakeup_later u_preempt (`Preempt interval));
let* r = Lwt.pick [ p_sleep; p_preempt ] in
match (r, !acked) with
| `Sleep, true -> loop ()
| `Preempt interval, _ ->
out.interval <- interval;
loop ()
| `Sleep, false ->
err ();
Lwt.return ()
| `Cancel, _ -> Lwt.return ()
in
Lwt.async loop;
out
let _send_exn { pl_tx; _ } pl = Lwt_pipe.write_exn pl_tx pl
let is_dead { op_tx; _ } = Lwt_pipe.is_closed op_tx
let send_exn { op_tx; _ } op =
Lwt_pipe.write op_tx op >>= fun ok ->
if ok then Lwt.return_unit
else (
L.err (fun m -> m "trying to interact with dead session");
Lwt.fail (Failure "trying to interact with dead session"))
allow : string;
deny : string;
guild_id : Snowflake.t option; [@yojson.option]
position : int option; [@yojson.option]
permission_overwrites : overwrite list option; [@yojson.option]
name : string option; [@yojson.option]
topic : string option option; [@yojson.option]
nsfw : bool option; [@yojson.option]
last_message_id : Snowflake.t option option; [@yojson.option]
bitrate : int option; [@yojson.option]
user_limit : int option; [@yojson.option]
rate_limit_per_user : int option; [@yojson.option]
recipients : User.t option; [@yojson.option]
icon : string option option; [@yojson.option]
owner_id : Snowflake.t option; [@yojson.option]
application_id : Snowflake.t option; [@yojson.option]
parent_id : Snowflake.t option; [@yojson.option]
last_pin_timestamp : string option option; [@yojson.option]
}
[@@yojson.allow_extra_fields]
and overwrite = Overwrite.t [@@deriving yojson, show]
end
(* module S = struct
type voice = { user_limit : int; bitrate : int; rtc_region : string option }
[@@deriving yojson, show] [@@yojson.allow_extra_fields]
type guild = {
guild_id : Snowflake.t option; [@yojson.option]
position : int;
permission_overwrites : R.overwrite list;
name : string;
topic : string option;
nsfw : bool;
last_message_id : Snowflake.t option;
last_pin_timestamp : string option;
rate_limit_per_user : int option; [@yojson.option]
parent_id : Snowflake.t option;
type t = {
id : Snowflake.t;
typ : int; [@key "type"]
guild_id : Snowflake.t option; [@yojson.option]
position : int option; [@yojson.option]
permission_overwrites : overwrite list option; [@yojson.option]
name : string option; [@yojson.option]
topic : string option option; [@yojson.option]
nsfw : bool option; [@yojson.option]
last_message_id : Snowflake.t option option; [@yojson.option]
bitrate : int option; [@yojson.option]
user_limit : int option; [@yojson.option]
rate_limit_per_user : int option; [@yojson.option]
recipients : User.t option; [@yojson.option]
icon : string option option; [@yojson.option]
owner_id : Snowflake.t option; [@yojson.option]
application_id : Snowflake.t option; [@yojson.option]
parent_id : Snowflake.t option; [@yojson.option]
last_pin_timestamp : string option option; [@yojson.option]
}
[@@yojson.allow_extra_fields]
module V = struct
let user_limit t = t.user_limit
let bitrate t = t.bitrate
let rtc_region t = t.rtc_region
end
module G = struct end
type t = Snowflake.t * channel
and channel =
| Guild_text
| DM
| Guild_voice
| Group_DM
| Guild_category
| Guild_news
| Guild_store
| Guild_stage_voice
let t_of_yojson j =
let id = Yojson.Safe.Util.(member "id" j |> Snowflake.t_of_yojson) in
let chan =
match Yojson.Safe.Util.(member "type" j |> to_int) with
| 0 -> Guild_text
| 1 -> DM
| 2 -> Guild_voice
| 3 -> Group_DM
| 4 -> Guild_category
| 5 -> Guild_news
| 6 -> Guild_store
| 13 -> Guild_stage_voice
| _n -> failwith "unsupported channel type"
in
(id, chan) *)
type t = int [@@deriving yojson]
let make = List.fold_left (fun out intent -> out lor intent) 0
let add i t = t lor i
let rm i t = lnot i land t
let guilds = 1 lsl 0
let guild_members = 1 lsl 1
let guild_bans = 1 lsl 2
let guild_emojis = 1 lsl 3
let guild_integrations = 1 lsl 4
let guild_webhooks = 1 lsl 5
let guild_invites = 1 lsl 6
let guild_voice_states = 1 lsl 7
let guild_presences = 1 lsl 8
let guild_messages = 1 lsl 9
let guild_message_reactions = 1 lsl 10
let guild_message_typing = 1 lsl 11
let direct_messages = 1 lsl 12
let direct_message_reactions = 1 lsl 13
let direct_message_typing = 1 lsl 14
let all = (1 lsl 15) - 1
let all_unprivileged =
List.fold_left (fun is i -> rm i is) all [ guild_presences; guild_members ]
let pp fmt t =
let out =
let rec aux acc d miss =
match (d, miss) with
| 0, 0 -> String.concat "" acc
| 0, n when n > 0 -> String.make n '0' ^ String.concat "" acc
| _, n -> aux (string_of_int (d land 1) :: acc) (d lsr 1) (n - 1)
in
aux [] t 15
in
Format.fprintf fmt "%s" out
let show = Format.asprintf "%a" pp
let ( <> ) a b = not (a = b)
let schedule_read t ~f =
match read_opt t with
| Some a ->
f (Some a);
stub
| None when t.dropped ->
f None;
stub
| None ->
let r = { canceled = false; dispatch = f } in
t.readers <- Ke.Fke.push t.readers r;
fun () -> if not r.canceled then r.canceled <- true
let mem t ~key = access t @@ fun table -> Table.mem table key
let push t v =
if t.dropped then false
else
let rec f q =
match Ke.Fke.pop q with
| Some ({ canceled = true; _ }, tl) ->
print_endline "skipping cancelled reader";
f tl
| Some ({ dispatch; _ }, tl) ->
print_endline "waking up reader";
t.readers <- tl;
dispatch (Some v)
| None ->
print_endline "pushed to buffer";
t.readers <- Ke.Fke.empty;
t.buf <- Ke.Fke.push t.buf v
in
f t.readers;
true
let remove t ~key = access t @@ fun table -> Table.remove table key
let filter ~f t =
let o = make () in
let rec fwd = function
| Some v when f v ->
if push o v then schedule_read t ~f:fwd |> ignore else drop o
| Some _ -> schedule_read t ~f:fwd |> ignore
| None -> drop o
in
schedule_read t ~f:fwd |> ignore;
o
let of_seq seq =
let tbl, mtx = make () in
Seq.iter (fun (k, v) -> Table.add tbl k v) seq;
(tbl, mtx)
end
let rec merge sinks =
let ks = ref @@ ((1 lsl List.length sinks) - 1) in
let o = make ~clone:(fun () -> List.map clone sinks |> merge) () in
let c = Array.make (List.length sinks) stub in
let cancel () = Array.iter (fun c -> c ()) c in
let rec fwd i k = function
| Some v ->
if push o v then c.(i) <- schedule_read k ~f:(fwd i k)
else (
cancel ();
List.iter drop sinks)
| None ->
ks := !ks land lnot (1 lsl i);
if !ks = 0 then drop o
in
List.iteri (fun i k -> c.(i) <- schedule_read k ~f:(fwd i k)) sinks;
o
module Cache = MakeCache (Ephemeron.K1.MakeSeeded (struct
let equal (K l) (K r) = l = r
let iter_p ~f t =
let open Lwt.Infix in
let rec cleanup ?(acc = []) = function
| [] -> Ok acc
| p :: ps -> (
match Lwt.state p with
| Lwt.Fail e -> Error e
| Lwt.Sleep -> cleanup ~acc:(p :: acc) ps
| Lwt.Return _ -> cleanup ~acc ps)
in
let rec join ?(acc = []) () =
read t >>= function
| None -> Lwt.join acc
| Some v -> (
match cleanup acc with
| Ok acc -> join ~acc:(f v :: acc) ()
| Error exn -> Lwt.fail exn)
in
join ()
end
type 'a source = { sinks : 'a sink Cache.t; mutable closed : bool }
and 'a sink = {
mutable readers : 'a reader Ke.Fke.t;
mutable buf : 'a Ke.Fke.t;
mutable dropped : bool;
}
and 'a reader = 'a option Lwt.t * 'a option Lwt.u
type 'a handle = { key : Cache.key; source : 'a source }
let gen_key () =
Random.self_init ();
K (Random.bits ())
let make_sink () =
{ readers = Ke.Fke.empty; buf = Ke.Fke.empty; dropped = false }
let connect source sink =
let sink = { sink with dropped = source.closed } in
let key = gen_key () in
Cache.add source.sinks ~key sink >|= fun () -> { key; source }
let drop_sink t =
if not t.dropped then (
t.dropped <- true;
Ke.Fke.iter
(fun (p, u) -> if Lwt.is_sleeping p then Lwt.wakeup_later u None)
t.readers;
t.readers <- Ke.Fke.empty)
module Sink = struct
type 'a t = 'a handle
let get { key; source } = Cache.find source.sinks ~key
let peek t = get t >|= fun t -> Ke.Fke.peek t.buf
let pull t =
get t >>= fun t ->
match Ke.Fke.pop t.buf with
| Some (v, tl) ->
t.buf <- tl;
Lwt.return @@ Some v
| None when t.dropped -> Lwt.return None
| None ->
let p, u = Lwt.task () in
t.readers <- Ke.Fke.push t.readers (p, u);
p
let drop t = get t >|= drop_sink
let clone t = get t >>= fun sink -> connect t.source sink
let write t x =
if t.closed then false
else
sinks t
|> Seq.filter (fun s -> not @@ s.dropped)
|> Seq.fold (fun flag s -> flag || Sink.push s x) false
let subscribe source = make_sink () |> connect source
let write source v =
let f _ sink =
(* Printf.printf "handle %d: " k; *)
if sink.dropped then ()
else
let rec f q =
match Ke.Fke.pop q with
| Some ((p, u), tl) when Lwt.is_sleeping p ->
(* print_endline "waking up reader"; *)
sink.readers <- tl;
Lwt.wakeup_later u @@ Some v
| Some (_, tl) ->
(* print_endline "skipping cancelled reader"; *)
f tl
| None ->
(* print_endline "pushed to buffer"; *)
sink.readers <- Ke.Fke.empty;
sink.buf <- Ke.Fke.push sink.buf v
in
f sink.readers
in
Cache.iter source.sinks ~f
let grow t =
let len = Weak.length t.sinks in
if Obj.Ephemeron.max_ephe_length - len < len then
failwith "reached maximum number of sinks";
let w = Weak.create (len * 2) in
Weak.blit t.sinks 0 w 0 len;
t.sinks <- w
let get_free_sink_ref t =
let len = Weak.length t.sinks in
let rec f' ?(i = 0) w =
if i >= len then None
else
match Weak.get w i with
| None -> Some i
| Some c when c.dropped -> Some i
| Some _ -> f' ~i:(i + 1) w
in
match f' t.sinks with
| Some i -> i
| None ->
grow t;
len
let create () =
let sinks = Weak.create 4 in
let source = { sinks; closed = false } in
let rec make_sink () =
let r = get_free_sink_ref source in
let sink =
{
readers = Ke.Fke.empty;
buf = Ke.Fke.empty;
dropped = false;
clone = make_sink;
}
in
Gc.finalise
(fun k ->
Printf.printf "gc'ing sink with %d buffered elements and %d readers\n%!"
(Ke.Fke.length k.buf) (Ke.Fke.length k.readers);
Sink.drop k)
sink;
Weak.set source.sinks r (Some sink);
sink
in
(source, make_sink ())
let make () =
let sink = make_sink () in
let key = gen_key () in
let sinks = Seq.pure (key, sink) |> Cache.of_seq in
let source = Source.make ~sinks () in
(source, { key; source })
open! Disco_core.Globals
module L = (val Relog.logger ~namespace:__MODULE__ ())
open Lwt.Infix
let silence_frame =
[ '\xf8'; '\xff'; '\xfe' ] |> String.of_list
|> Bigstringaf.of_string ~off:0 ~len:3
let silence_stream = Lwt_stream.from_direct (fun () -> Some silence_frame)
(** Conservative default of 10 already taking into
account the occasional packet loss that might occur *)
let n_silence_pipe ?(n = 10) () =
Lwt_pipe.of_list (List.init n (fun _ -> silence_frame))
let of_pipe p = p
module Ffmpeg = struct
module L = (val Relog.clone (module L) ~namespace:"Ffmpeg")
module Parser = struct
let frame ~sample_p ~channels ~size ~kind =
let buf =
Bigarray.Array1.create kind Bigarray.c_layout (channels * size)
in
let p =
let open Angstrom in
let sample = count channels sample_p in
let rec samples ?(i = 0) () =
end_of_input >>| (fun () -> `eof) <|> (sample >>| fun s -> `sample s)
>>= function
| `eof -> return ()
| `sample chans ->
List.iteri (fun ci c -> buf.{i + ci} <- c) chans;
samples ~i:(i + channels) ()
in
samples () >>| fun () -> buf
in
fun buf ->
Angstrom.parse_bigstring ~consume:Angstrom.Consume.All p buf |> function
| Ok frame -> frame
| Error str -> failwith @@ "error parsing raw audio frame " ^ str
let s16le ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.LE.any_int16 ~channels ~size:frame_size
~kind:Bigarray.int16_signed
let s16be ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.BE.any_int16 ~channels ~size:frame_size
~kind:Bigarray.int16_signed
let f32le ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.LE.any_float ~channels ~size:frame_size
~kind:Bigarray.float32
let f32be ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.BE.any_float ~channels ~size:frame_size
~kind:Bigarray.float32
let f64le ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.LE.any_double ~channels ~size:frame_size
~kind:Bigarray.float64
let f64be ?(channels = 2) ?(frame_size = 48000) =
frame ~sample_p:Angstrom.BE.any_double ~channels ~size:frame_size
~kind:Bigarray.float64
end
let pcm_args =
[
"-nostdin";
"-analyzeduration";
"0";
"-f";
"s16le";
"-ar";
"48000";
"-ac";
"2";
"-flush_packets";
"1";
]
let stdout_args = [ "-" ]
let frame_reader () =
let buf = Bigstringaf.create (Rtp._FRAME_SIZE * Rtp._CHANNELS * 2) in
let blen = Bigstringaf.length buf in
let parse_frame =
let parser =
Parser.s16le ~channels:Rtp._CHANNELS ~frame_size:Rtp._FRAME_SIZE
in
fun () -> parser buf
in
fun ic ->
Lwt_io.read_into_exactly_bigstring ic buf 0 blen |> Lwt_result.catch
>|= function
| Ok () -> `Frame (parse_frame ())
| Error End_of_file -> `Eof
| Error exn -> `Exn exn
let writer = function
| `Stream s ->
let f oc =
Lwt_pipe.read s >>= function
| Some d -> (
Lwt_io.write_from_exactly_bigstring oc d 0 (Bigstringaf.length d)
|> Lwt_result.catch
>|= function
| Ok () -> true
| _ -> false)
| None -> Lwt_io.close oc >|= Fun.const false
in
f
| _ -> fun oc -> Lwt_io.close oc >|= Fun.const false
let close_input = function `Stream s -> Lwt_pipe.close_nonblock s | _ -> ()
let create input =
let open Lwt_result.Syntax in
let cmd = "ffmpeg" in
let* i =
match input with
| `Stream _ -> Lwt_result.return [ "-i"; "-" ]
| `File path -> (
Lwt_unix.file_exists path >|= function
| true -> Ok [ "-i"; path ]
| false -> Error.msgf "input file '%s' does not exist" path)
| `Url url -> Lwt_result.return [ "-i"; url ]
in
let args = List.concat [ i; pcm_args; stdout_args ] in
let p = Lwt_pipe.create () in
let p_p, u_p = Lwt.wait () in
let spawn () =
let cmd_str = cmd :: args |> List.to_string ~sep:" " Fun.id in
L.debug (fun m -> m "running cmd: %s" cmd_str);
Lwt_process.with_process_full
("", Array.of_list @@ cmd :: args)
(fun proc ->
L.debug (fun m -> m "ffmpeg running with pid=%d" proc#pid);
let read_logs () =
Lwt_io.read ~count:0x100 proc#stderr >|= fun s ->
`Logs (String.length s = 0)
in
let read_frame () = frame_reader () proc#stdout in
let write () = writer input proc#stdin >|= fun r -> `Write r in
let rec poll' q =
Lwt.nchoose_split q >>= function
| [], [] -> Lwt_result.return ()
| rs, ps -> (
match handle ~out:ps rs with
| Ok [] -> Lwt_result.return ()
| Ok q -> poll' q
| Error _ as e -> Lwt.return e)
and handle ?(out = []) = function
| [] -> Ok out
| (`Write false | `Eof | `Logs true) :: xs -> handle ~out xs
| `Logs false :: xs -> handle ~out:(read_logs () :: out) xs
| `Frame frame :: xs ->
if Lwt.is_sleeping p_p then Lwt.wakeup_later u_p (Ok p);
let fwd = Lwt_pipe.write p frame >|= fun r -> `Fwd r in
handle ~out:(fwd :: out) xs
| `Fwd true :: xs ->
let r = read_frame () in
handle ~out:(r :: out) xs
| `Fwd false :: _ ->
L.warn (fun m ->
m "receiving end of pipe closed, cleaning up...");
List.iter Lwt.cancel out;
let clean =
Lwt_io.abort proc#stdin >>= fun () ->
Lwt_io.abort proc#stdout >>= fun () ->
Lwt_io.abort proc#stderr >|= fun () ->
proc#terminate;
`Eof
in
Ok [ clean ]
| `Write true :: xs ->
let w = write () in
handle ~out:(w :: out) xs
| (`Exn _ as e) :: _ ->
List.iter Lwt.cancel out;
L.err (fun m -> m "boop %a" Error.pp e);
Error e
in
let res =
let open Lwt_result.Syntax in
let* () =
poll' [ read_frame (); write () ] >>= function
| Error _ as e ->
Lwt_pipe.close_nonblock p;
close_input input;
proc#close >|= Fun.const e
| Ok () as o -> Lwt.return o
in
L.info (fun m -> m "closing the process");
proc#close >|= function
| Unix.WEXITED 0 when Lwt.is_sleeping p_p ->
Error.msg "0 byte stream?"
| Unix.WEXITED 0 -> Ok ()
| Unix.WEXITED n -> Error.msgf "non 0 status code: %d" n
| Unix.WSIGNALED n | Unix.WSTOPPED n ->
L.warn (fun m -> m "stopped with code: %d" n);
Ok ()
in
res >|= fun r ->
L.err (fun m -> m "oyy m8");
match (Lwt.is_sleeping p_p, r) with
| true, (Error _ as e) -> Lwt.wakeup_later u_p e
| true, Ok () -> failwith "unreachable"
| false, Error e -> L.err (fun m -> m "%s" (Error.to_string e))
| false, Ok () -> ())
in
let k =
Lwt.catch spawn (fun e ->
L.err (fun m -> m "spawn error: %a" Error.pp (`Exn e));
Lwt.return_unit)
in
Lwt_pipe.keep p k;
Lwt.on_termination k (fun () ->
L.err (fun m -> m "HALP");
Lwt_pipe.close_nonblock p);
p_p |> Lwt_result.map of_pipe
end
module Websocket = Disco_core.Websocket
type discord =
[ `Unknown_op
| `Invalid_payload
| `Not_authenticated
| `Authentication_failed
| `Already_authenticated
| `Invalid_session
| `Session_timeout
| `Server_not_found
| `Unknown_protocol
| `Disconnected
| `Voice_server_crashed
| `Unknown_encryption_mode ]
type t = [ Websocket.Close_code.standard | discord ]
let is_discord = function #discord -> true | _ -> false
let is_std = function #Websocket.Close_code.standard -> true | _ -> false
let to_int = function
| #Websocket.Close_code.standard as c -> Websocket.Close_code.to_int c
| `Unknown_op -> 4001
| `Invalid_payload -> 4002
| `Not_authenticated -> 4003
| `Authentication_failed -> 4004
| `Already_authenticated -> 4005
| `Invalid_session -> 4006
| `Session_timeout -> 4009
| `Server_not_found -> 4011
| `Unknown_protocol -> 4012
| `Disconnected -> 4014
| `Voice_server_crashed -> 4015
| `Unknown_encryption_mode -> 4016
let of_close_code_exn = function
| #Websocket.Close_code.standard as c -> c
| `Other 4001 -> `Unknown_op
| `Other 4002 -> `Invalid_payload
| `Other 4003 -> `Not_authenticated
| `Other 4004 -> `Authentication_failed
| `Other 4005 -> `Already_authenticated
| `Other 4006 -> `Invalid_session
| `Other 4009 -> `Session_timeout
| `Other 4011 -> `Server_not_found
| `Other 4012 -> `Unknown_protocol
| `Other 4014 -> `Disconnected
| `Other 4015 -> `Voice_server_crashed
| `Other 4016 -> `Unknown_encryption_mode
| `Other c -> failwith (Printf.sprintf "unknown voice close code: %d" c)
let pp =
let pp' fmt = function
| `Unknown_op -> Format.fprintf fmt "invalid opcode"
| `Invalid_payload -> Format.fprintf fmt "invalid payload while identifying"
| `Not_authenticated -> Format.fprintf fmt "not authenticated"
| `Authentication_failed -> Format.fprintf fmt "authentication failed"
| `Already_authenticated -> Format.fprintf fmt "already authenticated"
| `Invalid_session -> Format.fprintf fmt "session is no longer valid"
| `Session_timeout -> Format.fprintf fmt "session timed out"
| `Server_not_found -> Format.fprintf fmt "voice server not found"
| `Unknown_protocol -> Format.fprintf fmt "unknown protocol"
| `Disconnected -> Format.fprintf fmt "disconnected"
| `Voice_server_crashed -> Format.fprintf fmt "voice server crashed"
| `Unknown_encryption_mode -> Format.fprintf fmt "unknown encryption mode"
in
fun fmt t ->
match t with
| #Websocket.Close_code.standard as c -> Websocket.Close_code.pp fmt c
| #discord as t -> Format.fprintf fmt "(%d %a)" (to_int t) pp' t
let is_recoverable = function
| `Unknown_op | `Invalid_payload | `Not_authenticated | `Authentication_failed
| `Already_authenticated | `Unknown_protocol | `Disconnected
| `Unknown_encryption_mode
| #Websocket.Close_code.standard ->
false
| _ -> true
(library
(name disco_voice)
(public_name disco.voice)
(optional)
(preprocess
(pps ppx_deriving.show ppx_deriving.ord ppx_yojson_conv))
(libraries
disco.core
disco.models
disco.utils
containers
containers-data
stdint
relog.lib
uri
lwt
lwt.unix
lwt-pipe
angstrom
angstrom-lwt-unix
yojson
mtime
mtime.clock.os
hxd.core
hxd.string
piaf
sodium
opus
websocketaf
websocketaf-lwt-unix
httpaf-lwt-unix))
include Disco_core.Error
open! Disco_core.Globals
type t = {
mutable interval : float;
mutable preempt : ?interval:float -> unit -> unit;
mutable ack : int -> unit;
mutable cancel : unit -> unit;
}
let interval { interval; _ } = interval
let preempt ?interval { preempt; _ } = preempt ?interval ()
let ack nonce { ack; _ } = ack nonce
let cancel { cancel; _ } = cancel ()
let make ?err fn interval =
let err =
Option.get_or
~default:(fun () -> failwith "no ACK of last heartbeat received")
err
in
let out =
{
interval;
preempt = (fun ?interval:_ -> ignore);
ack = ignore;
cancel = ignore;
}
in
let gen_nonce =
let st = Random.State.make_self_init () in
fun () -> Random.State.bits st
in
let rec loop () =
let open Lwt.Syntax in
let acked = ref false in
let nonce = gen_nonce () in
let () = fn nonce in
let p_preempt, u_preempt = Lwt.wait () in
let p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) in
out.ack <- (fun n -> if not !acked then acked := nonce = n);
out.cancel <-
(fun () ->
if Lwt.is_sleeping p_preempt then Lwt.wakeup_later u_preempt `Cancel);
out.preempt <-
(fun ?(interval = interval) () ->
if Lwt.is_sleeping p_preempt then
Lwt.wakeup_later u_preempt (`Preempt interval));
let* r = Lwt.pick [ p_sleep; p_preempt ] in
match (r, !acked) with
| `Sleep, true -> loop ()
| `Preempt interval, _ ->
out.interval <- interval;
loop ()
| `Sleep, false ->
err ();
Lwt.return ()
| `Cancel, _ -> Lwt.return ()
in
Lwt.async loop;
out
open! Disco_core.Globals
open Lwt.Infix
module Snowflake = Disco_models.Snowflake
module E = Disco_core.Events
module Sf_map = Map.Make (Snowflake)
module Gateway = Disco_core.Gateway
module L = (val Relog.logger ~namespace:__MODULE__ ())
module F = Relog.Field
module Call = struct
(* TODO this should really be /yet another/ event loop *)
module L = (val Relog.logger ~namespace:"Call" ())
type t = {
gw : Gateway.t;
guild_id : snowflake;
mutable muted : bool;
mutable deafened : bool;
mutable conn : conn;
mutable req : (unit, Error.t) result Lwt.u option;
}
and conn =
| Init of snowflake
| Server of { channel_id : snowflake; token : string; endpoint : string }
| Session of { channel_id : snowflake; session_id : string }
| Live of { mixer : Mixer.t; session : Session.t }
| Detached
let make ?(muted = false) ?(deafened = false) ~gw guild_id =
{ gw; guild_id; muted; deafened; conn = Detached; req = None }
let guild_id { guild_id; _ } = guild_id
let muted { muted; _ } = muted
let deafened { deafened; _ } = deafened
let is_live { conn; _ } = match conn with Live _ -> true | _ -> false
let session { conn; _ } =
match conn with
| Live { mixer; session } -> Some (mixer, session)
| _ -> None
let session_exn t = session t |> Option.get_exn
let notify_req t res =
match (res, t.req) with
| Ok _, Some req ->
t.req <- None;
Lwt.wakeup_later req (Ok ())
| (Error _ as err), Some req ->
t.conn <- Detached;
t.req <- None;
Lwt.wakeup_later req err
| _ -> ()
let connect t ~channel_id ~session_id ~token ~endpoint =
let inner () =
let open Lwt_result.Syntax in
let user_id = (Gateway.user t.gw).id in
let guild_id = t.guild_id in
let* session =
Session.create ~guild_id ~user_id ~channel_id ~session_id ~token
endpoint
in
let+ mixer = Mixer.create session |> Lwt_result.lift in
t.conn <- Live { mixer; session };
(mixer, session)
in
inner () >|= notify_req t
let should_reconnect s = function
| `Server (token, endpoint) ->
String.(Session.token s <> token || Session.endpoint s <> endpoint)
| `Session (session_id, channel_id) ->
String.(Session.session_id s <> session_id)
|| Snowflake.(Session.channel_id s <> channel_id)
let update t upd =
match (t.conn, upd) with
| Init channel_id, `Server (token, endpoint) ->
t.conn <- Server { channel_id; token; endpoint };
Lwt.return_unit
| (Init _ | Session _ | Server _), `Session (_, None) ->
L.err (fun m ->
m "got voice state update with no channel while joining?!?!");
failwith "unimplemented"
| Init cid, `Session (session_id, Some channel_id) ->
if Snowflake.(cid <> channel_id) then
L.warn (fun m ->
m "got unexpected channel_id while joining"
~fields:
F.
[
str "got" (Snowflake.to_string channel_id);
str "expected" (Snowflake.to_string cid);
]);
t.conn <- Session { channel_id; session_id };
Lwt.return_unit
| Server s, `Server (token, endpoint) ->
t.conn <- Server { s with token; endpoint };
Lwt.return_unit
| Session _, `Session (session_id, Some channel_id) ->
t.conn <- Session { session_id; channel_id };
Lwt.return_unit
| ( Server { channel_id = cid; token; endpoint },
`Session (session_id, Some channel_id) ) ->
if Snowflake.(cid <> channel_id) then
L.warn (fun m ->
m "got unexpected channel_id while joining"
~fields:
F.
[
str "got" (Snowflake.to_string channel_id);
str "expected" (Snowflake.to_string cid);
]);
connect t ~channel_id ~session_id ~token ~endpoint >|= ignore
| Session { session_id; channel_id }, `Server (token, endpoint) ->
connect t ~channel_id ~session_id ~token ~endpoint >|= ignore
| Live { session; mixer }, (`Server (token, endpoint) as upd)
when should_reconnect session upd ->
Mixer.destroy mixer;
Session.disconnect session >>= fun () ->
let session_id = Session.session_id session in
let channel_id = Session.channel_id session in
connect t ~channel_id ~session_id ~token ~endpoint >|= ignore
| Live { session; mixer }, `Session (session_id, Some channel_id)
when should_reconnect session (`Session (session_id, channel_id)) ->
Mixer.destroy mixer;
Session.disconnect session >>= fun () ->
let token = Session.token session in
let endpoint = Session.endpoint session in
connect t ~channel_id ~session_id ~token ~endpoint >|= ignore
| Live { session; mixer }, `Session (_, None) ->
L.dbg (fun m -> m "got session leave on live call, disconneting...");
t.conn <- Detached;
Mixer.destroy mixer;
Session.disconnect session
| Live _, _ ->
notify_req t (Ok ());
Lwt.return_unit
| Detached, `Session (_, None) ->
L.dbg (fun m -> m "got session leave on detached session, ignoring...");
Lwt.return_unit
| Detached, _ ->
L.err (fun m -> m "getting voice events on detached call?????");
failwith "unimplemented"
let join t ~channel_id =
let do_req () =
L.dbg (fun m -> m "connecting to requested channel");
t.conn <- Init channel_id;
let p, u = Lwt.wait () in
t.req <- Some u;
Gateway.send_voice_state_update t.gw ~channel_id ~self_mute:t.muted
~self_deaf:t.deafened t.guild_id
>>= fun () -> p
in
match session t with
| Some (_, s) when Snowflake.(Session.channel_id s = channel_id) ->
L.dbg (fun m -> m "already in channel, ignoring...");
Lwt_result.return ()
| Some (mix, sess) ->
L.dbg (fun m -> m "call live in different channel, disconnecting...");
Mixer.destroy mix;
Session.disconnect sess >>= do_req
| None -> do_req ()
let leave t =
match session t with
| Some _ ->
L.dbg (fun m -> m "call is live, disconnecting...");
Gateway.send_voice_state_update t.gw ~self_mute:t.muted
~self_deaf:t.deafened t.guild_id
| None ->
L.dbg (fun m -> m "call detached, ignoring...");
Lwt.return ()
end
type t = { mutable calls : Call.t Sf_map.t; gw : Gateway.t }
let user_id { gw; _ } =
let user = Gateway.user gw in
user.id
let server_update t srv =
L.info (fun m -> m "srv update");
Sf_map.get srv.E.Voice_server_update.guild_id t.calls
|> Option.map (fun call ->
Call.update call (`Server (srv.token, srv.endpoint)))
|> Option.get_or ~default:Lwt.return_unit
let state_update t vst =
L.info (fun m -> m "state update");
match vst.E.Voice_state.guild_id with
| Some guild_id when Snowflake.(user_id t = vst.user_id) ->
Sf_map.get guild_id t.calls
|> Option.map (fun call ->
Call.update call (`Session (vst.session_id, vst.channel_id)))
|> Option.get_or ~default:Lwt.return_unit
| _ -> Lwt.return_unit
let make gw =
let t = { calls = Sf_map.empty; gw } in
let evloop () =
let open Lwt.Syntax in
let* evs = Gateway.events gw |> Mpmc.Sink.clone in
let rec fwd () =
Mpmc.Sink.pull evs >>= function
| Some (E.Voice_server_update v) -> server_update t v >>= fwd
| Some (Voice_state_update v) -> state_update t v >>= fwd
| Some _ -> fwd ()
| None -> Lwt.return_unit
in
fwd ()
in
Lwt.async evloop;
t
let destroy t =
Sf_map.to_seq t.calls
|> Seq.map (fun (_, call) -> Call.leave call)
|> Seq.to_list |> Lwt.join
>|= fun () -> t.calls <- Sf_map.empty
let get t ~guild_id =
match Sf_map.get guild_id t.calls with
| Some call -> call
| None ->
let call = Call.make ~gw:t.gw guild_id in
t.calls <- Sf_map.add guild_id call t.calls;
call
open! Disco_core.Globals
open Lwt.Infix
module L = (val Relog.logger ~namespace:__MODULE__ ())
module F = Relog.Field
module Ws_conn = Disco_core.Websocket.Make (Payload)
module Pl = Payload
module Versions = struct
type t = V4
let to_string = function V4 -> "4"
let to_query t = ("v", [ to_string t ])
end
module Wsd = struct
type t = {
tb : Token_bucket.t;
ws : Ws_conn.t;
rx : [ `Pl of Pl.recv | `Closed of Close_code.t ] Lwt_pipe.Reader.t;
}
(* TODO i'm assuming the same rate limiting of the
main gateway applies here as well *)
let create uri =
let open Lwt_result.Syntax in
let capacity = 120 in
let rate = 1. /. (float capacity /. 60.) in
let tb = Token_bucket.make ~capacity rate in
let+ ws = Ws_conn.create ~zlib:false uri in
let rx =
Lwt_pipe.of_stream (Ws_conn.stream ws)
|> Lwt_pipe.Reader.map ~f:(function
| Ws_conn.Payload pl -> `Pl pl
| Close code ->
let code = Close_code.of_close_code_exn code in
L.warn (fun m ->
m "voice ws session was closed: %a" Close_code.pp code);
`Closed code)
in
{ tb; ws; rx }
let rx { rx; _ } = rx
let is_closed { ws; _ } = Ws_conn.is_closed ws
let send t pl =
if is_closed t then
Lwt.return (Error.msg "cannot send payload to closed ws")
else
Token_bucket.take t.tb >|= fun () ->
Ws_conn.send t.ws pl;
Ok ()
let send_exn t pl =
send t pl >|= function
| Ok () -> ()
| Error e -> failwith (Error.to_string e)
let close ~code t =
Lwt_pipe.close_nonblock t.rx;
if is_closed t then ()
else (
Token_bucket.cancel_waiting t.tb;
Ws_conn.close ~code t.ws)
end
type handshake_state =
| Greet of conn
| Id of Heartbeat.t
| Resuming of Heartbeat.t * rtp
| Establish_udp of Heartbeat.t * Udp_connection.t
and conn = Fresh | Reconnection of rtp
and rtp = { rtp : Rtp.t; mutable speaking : bool }
let secret_of_int_list l =
Seq.of_list l |> Seq.map Char.of_int_exn |> Bytes.of_seq
let with_ws_params ~version uri =
let uri = Uri.with_path uri "/" in
Uri.with_query uri [ ("v", [ Versions.to_string version ]) ]
let do_handshake ~server_id ~user_id ~session_id ~token ?rtp wsd =
let open Lwt_result.Syntax in
let ws_rx = Wsd.rx wsd in
let make_hb interval =
Heartbeat.make
(fun nonce -> Lwt.async (fun () -> Wsd.send_exn wsd (Pl.heartbeat nonce)))
interval
in
let rec poll' st =
let* pl =
Lwt_pipe.read ws_rx
|> Lwt.map (function
| Some (`Pl pl) -> Ok pl
| Some (`Closed code) -> Error (`Closed code)
| None -> assert false)
in
match (st, pl) with
| Greet Fresh, Pl.Hello hb ->
L.info (fun m -> m "got greeting, identifying");
let* () =
Wsd.send wsd (Pl.make_identify ~server_id ~user_id ~session_id ~token)
in
let hb_secs = Float.of_int hb /. 1e3 in
let hb = make_hb hb_secs in
poll' (Id hb)
| Greet (Reconnection rtp), Hello hb ->
L.info (fun m -> m "got greeting, resuming session '%s'" session_id);
let* () = Wsd.send wsd (Pl.make_resume ~server_id ~session_id ~token) in
let hb_secs = Float.of_int hb /. 1e3 in
let hb = make_hb hb_secs in
poll' (Resuming (hb, rtp))
| ( ((Id hb | Resuming (hb, _) | Establish_udp (hb, _)) as st),
Hello new_interval ) ->
let hb_secs = Float.of_int new_interval /. 1e3 in
L.warn (fun m -> m "got new greeting, updating heartbeat");
Heartbeat.preempt ~interval:hb_secs hb;
poll' st
| ( ((Id hb | Resuming (hb, _) | Establish_udp (hb, _)) as st),
Heartbeat_ack nonce ) ->
L.debug (fun m -> m "got hearbeat ack for nonce '%d'" nonce);
Heartbeat.ack nonce hb;
poll' st
| Resuming (hb, rtp), Resumed -> Lwt_result.return (hb, rtp)
| Id hb, Ready ({ ip; port; ssrc; _ } as info) ->
L.info (fun m ->
m
"voice ws session is ready, connecting to UDP voice server with:\n\
%a"
Pl.Ready.pp info);
let* udp = Udp_connection.create ~ssrc (ip, port) |> Error.catch_lwt in
let ip, port = Udp_connection.local_addr udp in
let address = Unix.string_of_inet_addr ip in
let* () =
Wsd.send wsd
(Pl.make_select_protocol ~address ~port ~mode:"xsalsa20_poly1305")
in
poll' (Establish_udp (hb, udp))
| Establish_udp (hb, udp), Session_description desc ->
L.info (fun m -> m "successfuly handshaked voice connection");
let secret = secret_of_int_list desc.secret_key in
let mode = Udp_connection.encryption_mode_of_string desc.mode in
let crypt = Udp_connection.{ secret; mode } in
Lwt_result.return (hb, { rtp = Rtp.make ~udp ~crypt; speaking = false })
| st, _pl ->
L.warn (fun m -> m "ignoring non-control payload during handshake");
poll' st
in
let c = match rtp with Some s -> Reconnection s | None -> Fresh in
poll' (Greet c)
type t = {
version : Versions.t;
guild_id : snowflake;
user_id : snowflake;
channel_id : snowflake;
token : string;
session_id : string;
endpoint : string;
op_tx : op Lwt_pipe.Writer.t;
on_destroy : Error.t Lwt.t;
}
and op =
| Ws of Pl.send
| Speak of bool
| Rtp of bigstring
| Dc of (unit -> unit)
let create ?(version = Versions.V4) ~guild_id ~user_id ~channel_id ~session_id
~token endpoint =
let open Lwt_result.Syntax in
let uri = Uri.of_string ("wss://" ^ endpoint) |> with_ws_params ~version in
let p_init, u_init = Lwt.wait () in
let chan = Lwt_pipe.create () in
let on_destroy, destroyed_u = Lwt.wait () in
let read_exn p = Lwt_pipe.read p >|= Option.get_exn in
let rec manage' ?rtp () =
let rec connect () =
let* wsd = Wsd.create uri in
let res =
do_handshake ~server_id:guild_id ~user_id ~session_id ~token ?rtp wsd
in
Lwt.bind res (function
| Ok (hb, rtp) -> Lwt_result.return (wsd, hb, rtp)
| Error (`Closed code) when Close_code.is_recoverable code ->
L.warn (fun m ->
m "recoverable close code during handshake, retrying...");
connect ()
| Error (`Closed `Disconnected) ->
L.warn (fun m ->
m "disconnected during handshake, invalid permissions?");
Lwt.return
(Error (`Discord "voice session disconnected during handshake"))
| Error (`Closed _) ->
L.error (fun m -> m "unrecoverable close code during handshake");
assert false
| Error #Error.t as e -> Lwt.return e)
in
let* wsd, hb, rtp = connect () in
if Lwt.is_sleeping p_init then
Lwt.wakeup_later u_init
(Ok
{
version;
guild_id;
user_id;
channel_id;
token;
session_id;
endpoint;
op_tx = chan;
on_destroy;
});
let bus =
Lwt_pipe.Reader.merge_all
[
Wsd.rx wsd |> Lwt_pipe.Reader.map ~f:(fun pl -> `Ws pl);
chan |> Lwt_pipe.Reader.map ~f:(fun op -> `Op op);
]
in
let rec poll' () =
let open Lwt.Syntax in
let* task = read_exn bus in
match task with
| `Ws (`Pl pl) -> handle_payload pl
| `Ws (`Closed code) when Close_code.is_recoverable code ->
L.warn (fun m ->
m "session closed with recoverable close code, retrying...");
rtp.speaking <- false;
Lwt_pipe.close_nonblock bus;
Heartbeat.cancel hb;
manage' ~rtp ()
| `Ws (`Closed code) ->
L.error (fun m ->
m "session closed with unrecoverable close code: %a" Close_code.pp
code);
Lwt.return
(Error (`Discord (Format.asprintf "%a" Close_code.pp code)))
| `Op (Ws pl) -> Wsd.send_exn wsd pl >>= poll'
| `Op (Speak sp) when Stdlib.(sp != rtp.speaking) ->
rtp.speaking <- sp;
Wsd.send_exn wsd
(Pl.make_speaking ~ssrc:(Rtp.ssrc rtp.rtp) ~delay:0
(if sp then 1 else 0))
>>= poll'
| `Op (Speak _) -> poll' ()
| `Op (Rtp audio) -> Rtp.send_packet rtp.rtp audio >>= poll'
| `Op (Dc k) ->
Lwt_pipe.close_nonblock bus;
Lwt_pipe.close_nonblock chan;
Rtp.destroy rtp.rtp;
Wsd.close ~code:`Normal_closure wsd;
k ();
Lwt.return (Ok ())
and handle_payload = function
| Hello new_hb ->
L.warn (fun m -> m "got new greeting, updating heartbeat");
Heartbeat.preempt ~interval:(Float.of_int new_hb /. 1e3) hb;
poll' ()
| Ready info ->
L.error (fun m ->
m "got ready on established session, new voice server??@.%a"
Pl.Ready.pp info);
assert false
| Session_description desc ->
L.warn (fun m ->
m "got session description, updating...@.%a"
Pl.Session_description.pp desc);
let secret = secret_of_int_list desc.secret_key in
let mode = Udp_connection.encryption_mode_of_string desc.mode in
Rtp.set_crypt rtp.rtp Udp_connection.{ secret; mode };
poll' ()
| Heartbeat_ack nonce ->
Heartbeat.ack nonce hb;
poll' ()
| Speaking _ | Resumed | Client_disconnect ->
L.debug (fun m -> m "ignoring speaking/resumed/clientdisconnect");
poll' ()
in
poll' () >|= fun out ->
Heartbeat.cancel hb;
Rtp.destroy rtp.rtp;
Wsd.close ~code:`Going_away wsd;
Lwt_pipe.close_nonblock bus;
Lwt_pipe.close_nonblock chan;
out
in
Lwt.async (fun () ->
manage' ()
|> Lwt.map (function
| Ok () -> ()
| Error e when Lwt.is_sleeping p_init ->
Lwt.wakeup_later u_init (Error e)
| Error e -> Lwt.wakeup_later destroyed_u e));
p_init
let is_dead { op_tx; _ } = Lwt_pipe.is_closed op_tx
let version { version; _ } = version
let guild_id { guild_id; _ } = guild_id
let user_id { user_id; _ } = user_id
let channel_id { channel_id; _ } = channel_id
let token { token; _ } = token
let session_id { session_id; _ } = session_id
let endpoint { endpoint; _ } = endpoint
let send_exn { op_tx; _ } op =
Lwt_pipe.write op_tx op >>= fun ok ->
if ok then Lwt.return_unit
else (
L.err (fun m -> m "trying to interact with dead session");
Lwt.fail (Failure "trying to interact with dead session"))
let speak s t = send_exn t (Speak s)
let start_speaking = speak true
let stop_speaking = speak false
let send_rtp t audio = send_exn t (Rtp audio)
let disconnect t =
let p, u = Lwt.wait () in
send_exn t (Dc (Lwt.wakeup_later u)) >>= fun () -> p
"build": "dune build -p #{self.name} --profile=release",
"buildDev": "dune build --promote-install-files --root . --only-package #{self.name}"
"build": "dune build --profile=release",
"buildDev": "dune build --promote-install-files --root ."
"@opam/hxd": "0.3.1",
"@opam/ke": "0.4",
"@opam/lwt": "5.4.0",
"@opam/lwt-pipe": "0.1",
"@opam/mtime": "1.2.0",
"@opam/hxd": "~0.3.1",
"@opam/ke": ">=0.4",
"@opam/lwt": "~5.4.0",
"@opam/lwt-pipe": ">=0.1",
"@opam/mtime": "~1.2.0",