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 inD.Client.disconnect client
let* () = Client.send_message channel_id msg client inClient.disconnect client| Some ("status", st) ->let st =match st with| "offline" -> `offline| "invisible" -> `invisible| "idle" -> `idle| "dnd" -> `dnd| _ -> `onlineinlet gw = Client.gateway client inDisco_core.Gateway.send_presence_update gw ~afk:false st
let v_channel_id = M.Snowflake.of_string v_channel_id inD.Client.join_voice ~guild_id ~channel_id:v_channel_id client| Some ("play", "kiff") -> (
let vchan = M.Snowflake.of_string vchan inlet voice = Client.voice client inlet call = Voice.Manager.get ~guild_id voice inVoice.Manager.Call.join call ~channel_id:vchan >>= function| Error e ->let msg =Msg.fmt "⚠️ Couldn't join voice channel: %a" Voice.Error.ppeinClient.send_message channel_id msg client| Ok () -> Lwt.return_unit)| Some ("leave", _) ->
let* s = D.Audio_stream.Ffmpeg.create "./kiff.mp3" inmatch 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 inlet* s = D.Audio_stream.Ffmpeg.create "./bite2.mp3" inmatch 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 inlet* s = D.Audio_stream.Ffmpeg.create "./allstar.mp3" inmatch 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 inlet* tracks = Ytdl.query query inmatch tracks with| Ok [] | Error _ ->let msg = Msg.fmt "⚠️ No track found for query: '%s'" query inD.Client.send_message channel_id msg client| Ok (track :: _) -> (let* s = D.Audio_stream.Ffmpeg.create track.url inmatch s with| Ok audio_stream ->let* () =D.Client.play_audio_stream ~guild_id audio_stream clientinlet msg =Msg.fmt "🎵 @{<b>Now playing:@} '%s'\n%s" track.titletrack.thumbnailinD.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 inLwt.return_unit
let voice = Client.voice client inlet call = Voice.Manager.get ~guild_id voice inVoice.Manager.Call.leave call(* | Some ("join", v_channel_id) ->let guild_id = Option.get_exn guild_id inlet channel_id = M.Snowflake.of_string v_channel_id inlet voice = Client.voice client inVoice.Manager.join ~guild_id ~channel_id voice| Some ("play", "soundbite") -> (let guild_id = Option.get_exn guild_id inlet* s = Voice.Audio_stream.Ffmpeg.create @@ `File "./bite2.mp3" inmatch 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 inlet* s = Voice.Audio_stream.Ffmpeg.create @@ `File "./kiff.mp3" inmatch 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 inlet* tracks = Ytdl.get ytdl query inmatch tracks with| Ok [] | Error _ ->let msg = Msg.fmt "⚠️ No track found for query: '%s'" query inClient.send_message channel_id msg client| Ok ((track, i) :: _) -> (let* s = Voice.Audio_stream.Ffmpeg.create i inmatch s with| Ok audio_stream ->let* () =Client.play_audio_stream ~guild_id audio_stream clientinlet thumb fmt = function| Some t -> Format.fprintf fmt "\n%s" t| None -> ()inlet msg =Msg.fmt "🎵 @{<b>Now playing:@} '%s'%a" track.title thumbtrack.thumbnailinClient.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 = structmodule L = (val Relog.clone (module L) ~namespace:"File_stream")type op = Live of int | Draining of int | Scheduled of unit Lwt.utype 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 ] inlet readers = Ke.Fke.empty inLwt_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 -> accint.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 thenfailwith "File_stream: cannot write to closed file";let len = Option.get_lazy (fun () -> Bigstringaf.length b - off) len inLwt_io.write_from_exactly_bigstring t.oc b off len >|= fun () ->t.written <- t.written + len;notify_readers t lenlet reader t =let iflags = Unix.[ O_RDONLY ] inLwt_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.writteninlet r = { ic; op } int.readers <- Ke.Fke.push t.readers r;rlet close t = Lwt_io.close t.oc >|= fun () -> notify_readers t 0module Reader = structlet rec read t ?(off = 0) ?len b =let len = Option.get_lazy (fun () -> Bigstringaf.length b - off) len inmatch 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.icelse Lwt.return_unit)>|= fun () -> None| Live 0 ->let p, u = Lwt.wait () int.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 nlet close t = Lwt_io.close t.iclet to_pipe t =let buf = Bigstringaf.create 0x1000 inlet p = Lwt_pipe.create () inlet 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 tinlet f = Lwt.finalize fwd (fun () -> close t) inLwt.on_termination f (fun () -> Lwt_pipe.close_nonblock p);Lwt_pipe.keep p f;pendendtype 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 = structtype t = { dir : string; max_size : int }let kb = 1024let mb = 1024 * kblet create ?(max_size = 500 * mb) ?(dir = "./.discostore") () =let open Result.Infix inlet+ () =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 inlet title = Yojson.Safe.Util.(member "title" info |> to_string) inlet url = Yojson.Safe.Util.(member "url" info |> to_string) inlet thumbnail =Yojson.Safe.Util.(member "thumbnail" info |> to_string)inOk [ { title; url; thumbnail } ])
let track_json = Yojson.Safe.from_string track inlet track = track_of_yojson track_json inOk [ track ])
let download t track =let open Lwt.Syntax inlet filename = Store.path t.store track.id inlet p, u = Lwt.wait () inlet w () =Lwt_process.with_process_full("", [| t.bin; "-a"; "-"; "-o"; "-" |])(fun proc ->let* () = Lwt_io.write proc#stdin track.url inlet* () = Lwt_io.close proc#stdin inlet* sf = File_stream.create filename inlet buf = Bigstringaf.create 0x1000 inlet 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 thenFile_stream.reader sf >|= File_stream.Reader.to_pipe>|= fun s -> Lwt.wakeup_later u (Ok s)else Lwt.return_unitinFile_stream.write sf ~off:0 ~len:n buf >>= pollinpoll () >>= 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))inLwt.async (fun () ->w () >|= fun r ->match (Lwt.is_sleeping p, r) with| true, (Error _ as e) -> Lwt.wakeup_later u e| true, Ok () -> ()| false, _ -> ());plet stream path =let open Lwt_result.Syntax inlet+ ic = Lwt_io.open_file ~mode:Lwt_io.input path |> Error.catch_lwt inlet p = Lwt_pipe.create () inlet b = Bigstringaf.create 0x1000 inlet blen = Bigstringaf.length b inlet 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_unitinlet f = Lwt.finalize fwd (fun () -> Lwt_io.close ic) inLwt.on_termination f (fun () -> Lwt_pipe.close_nonblock p);Lwt_pipe.keep p f;plet 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))inf >|= function Ok i -> Ok [ (track, i) ] | Error _ as e -> e)let create ?(bin = "youtube-dl") ?store () =let open Result.Infix inlet* () =match Sys.command (bin ^ " --version") with| 0 -> Ok ()| _ -> Error.msgf "'%s' is not a valid youtube-dl binary" bininlet+ store = Option.map Result.return store |> Option.get_lazy Store.create in{ bin; store }
open Lwt.Infixmodule Mpmc = Disco_utils.Mpmclet 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_unitlet 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. inlet buf = Bigstringaf.create (960 * 2 * 4) inlet fill_buff frame =let f = Faraday.create (1024 * 4) inSeq.(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 inlet source, sink = Mpmc.make () inlet* 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 ] inlet writes =Lwt.join [ write_all source List.(1 -- 10); write_all source List.(1 -- 5) ]>>= fun () ->Mpmc.Source.close source >>= fun () -> iter_sink 1 sinkinLwt.join [ reads; writes ]
module Mpmc = Dp_utils.Mpmc
let gc () =let open Lwt.Syntax inlet src, snk = Mpmc.make () inlet clone_drop snk = Mpmc.Sink.clone snk >|= ignore inlet* () = clone_drop snk inlet* () = clone_drop snk inGc.full_major ();Lwt.join[(write_all src List.(1 -- 5) >>= fun () -> Mpmc.Source.close src);iter_sink 1 snk;]
let source, sink = Mpmc.create () inlet sink2 = Mpmc.Sink.(clone sink |> map ~f:(fun i -> i * 2)) inlet _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) ] inlet 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%!")inSeq.(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, andAsync.This package has an unstable interface; it is intended only to share configuration betweendifferent packages from Jane Street. Future updates may not be backward-compatible, and wedo 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 fromstrings or other medium.The library is focused on performances but still provide full genericparsers 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 sothat one can recover original positions in order to report properlylocated errors at little cost- the Concrete Syntax Tree parsers, produce values of type[Parsexp.Cst.t] which record the concrete layout of the s-expressionsyntax, including commentsThis library is portable and doesn't provide IO functions. To reads-expressions from files or other external sources, you should useparsexp_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 averification 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 providesa 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 libraryThe Core suite of libraries is an industrial strength alternative toOCaml's standard library that was developed by Jane Street, thelargest 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 nanosecondssince 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 charactertype, UTF-8, UTF-16, UTF-32 strings, conversion to/from about 200 encodings,collation and locale-sensitive case mappings, and more. The library is currentlydesigned 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. Itprovides an abstraction for keys, mouse events, colors, as well as a set ofwidgets to write curses-like applications. The main objective of lambda-term isto provide a higher level functional interface to terminal manipulation than,for example, ncurses, by providing a native OCaml interface instead of bindingsto a C library. Lambda-term integrates with zed to provide text editionfacilities 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). Itprovides support to program with time varying values : declarativeevents and signals. React doesn't define any primitive event orsignal, it lets the client chooses the concrete timeline.React is made of a single, independent, module and distributed underthe 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) forOCaml. It can run in a terminal or in Emacs. It supports lineedition, 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 texteditors, edition widgets, readlines, ... Zed uses Camomile to fully support theUnicode specification, and implements an UTF-8 encoded string type withvalidation, and a rope datastructure to achieve efficient operations on largeUnicode buffers. Zed also features a regular expression search on ropes. Tosupport efficient text edition capabilities, Zed provides macro recording andcursor 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 OCamltype system to ensure the validity of the generated documents. TyXMLcan 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 toinstantiate a new set of combinators.```ocamlopen Tyxmllet 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 ina single thread by default. This reduces the need for locks or othersynchronization 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 areresolved by Lwt in parallel.Meanwhile, OCaml code, including code creating and waiting on promises, runs ina single thread by default. This reduces the need for locks or othersynchronization 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 companyand auto-complete has moved to separate modules. Make sure to add`(require 'merlin-company)` or `(require 'merlin-ac)` to your emacsconfiguration 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 ofthe OCaml standard library. It aims to make it easy for users to dothe right thing. If you have tried Format before but find its APIcomplicated and difficult to use, then Pp might be a good choice foryou.
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 finalrendering is done to formatter from the Format module. However itdefines its own algebra which some might find easier to work with andreason about. No previous knowledge is required to start using thislibrary, however the various guides for the Format module such as thisone [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 Globalsmodule L = (val Relog.logger ~namespace:__MODULE__ ())open Lwt.Infixlet silence_frame =[ '\xf8'; '\xff'; '\xfe' ] |> String.of_list|> Bigstringaf.of_string ~off:0 ~len:3let 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 () inlet 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)inlet fwd = poll' () inLwt_pipe.keep out fwd;Lwt.on_termination fwd (fun () -> Lwt_pipe.close_nonblock out);outmodule 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) infun 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 inlet framesize = samplerate / 1000 * framelen inlet volume = 0.5 inlet omega = float freq /. float samplerate inlet gen =let buf =Bigarray.(Array1.create float32 c_layout (framesize * channels))infun () ->for i = 0 to framesize - 1 dolet sample = volume *. sin ((float i *. omega) +. !phase) in(* let sample = s16 sample in *)for c = 0 to channels - 1 dobuf.{i + c} <- sampledonedone;phase :=mod_float (!phase +. (float framesize *. omega)) (2. *. Float.pi);bufinlet p = Lwt_pipe.create () inlet rec write = function| 0 -> Lwt.return_unit| n ->Lwt_pipe.write p (gen ()) >>= fun ok ->if ok then write (n - 1) else Lwt.return_unitinlet nframes = Int.of_float (duration *. 1e3) / framelen inlet fut = write nframes inLwt_pipe.keep p fut;Lwt.on_termination fut (fun () -> Lwt_pipe.close_nonblock p);pendmodule Ffmpeg = structmodule L = (val Relog.clone (module L) ~namespace:"Ffmpeg")module Parser = structlet frame ~sample_p ~channels ~size ~kind =let buf =Bigarray.Array1.create kind Bigarray.c_layout (channels * size)inlet p =let open Angstrom inlet sample = count channels sample_p inlet 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) ()insamples () >>| fun () -> bufinfun buf ->Angstrom.parse_bigstring ~consume:Angstrom.Consume.All p buf |> function| Ok frame -> frame| Error str -> failwith @@ "error parsing raw audio frame " ^ strlet s16le ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.LE.any_int16 ~channels ~size:frame_size~kind:Bigarray.int16_signedlet s16be ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.BE.any_int16 ~channels ~size:frame_size~kind:Bigarray.int16_signedlet f32le ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.LE.any_float ~channels ~size:frame_size~kind:Bigarray.float32let f32be ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.BE.any_float ~channels ~size:frame_size~kind:Bigarray.float32let f64le ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.LE.any_double ~channels ~size:frame_size~kind:Bigarray.float64let f64be ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.BE.any_double ~channels ~size:frame_size~kind:Bigarray.float64endlet 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" inlet p = Lwt_pipe.create () inlet p_p, u_p = Lwt.wait () inlet spawn () =let cmd_str = cmd :: args |> List.to_string ~sep:" " Fun.id inL.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 inlet logs () = Lwt_io.read proc#stderr inL.info (fun m -> m "ffmpeg running with pid=%d" proc#pid);let buf_len = Bigstringaf.length buf inlet parse_frame =let parser =infun () -> parser bufinlet 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 () inif 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 exninlet open Lwt.Syntax inlet* status = poll' () >>= fun () -> proc#status inlet+ res =match status with| Unix.WEXITED 0 ->if Lwt.is_sleeping p_p thenelse Lwt_result.return ()| WEXITED n ->L.error (fun m ->m "got non 0 status code for ffmpeg (status=%d)" n);let+ logs = logs () inL.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 ()inmatch (Lwt.is_sleeping p_p, res) with| true, (Error _ as e) -> Lwt.wakeup_later u_p e| true, Ok () -> ()| false, _ -> ())inlet k = spawn () inLwt_pipe.keep p k;Lwt.on_termination k (fun () -> Lwt_pipe.close_nonblock p);p_p |> Lwt_result.map of_pipeendError.msgf "got non 0 status code for ffmpeg (status=%d)\n%s" nlogsLwt.return (Error.msg "0 byte stream?")Parser.s16le ~channels:Rtp._CHANNELS ~frame_size:Rtp._FRAME_SIZElet buf = Bigstringaf.create (Rtp._FRAME_SIZE * Rtp._CHANNELS * 2) inlet args = List.concat [ [ "-i"; input ]; pcm_args; stdout_args ] inlet create input =
module L = (val Relog.logger ~namespace:__MODULE__ ())module Payload = Gateway_payloadlet get_gateway_url http =let api () =let open Lwt_result.Syntax inlet* res = Http.get "/gateway/bot" http inlet* body = Piaf.Body.to_string res.body inResult.guard_str (fun () ->let raw = Yojson.Safe.from_string body inYojson.Safe.Util.(raw |> member "url" |> to_string))|> Result.map_err (fun e -> `Msg e)|> Lwt.returninapi ()|> 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.oklet connect ?http token =let open Lwt_result.Syntax inlet* http =match http with None -> Http.create token | Some d -> Lwt_result.return dinlet* url = get_gateway_url http inlet+ session = Session.create token (Uri.of_string url) inlet get_voice ~guild_id { voice_sessions; _ } =SfMap.get guild_id voice_sessionslet wait_for_updates =let rec f' ?st ?srv () =let* evt = Lwt_pipe.read evs inmatch ((evt : Events.t option), st, srv) withL.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 falseand evs =_fork_events t|> Lwt_pipe.Reader.filter ~f:(function| _ -> false)inlet p = f' () inLwt.on_termination p (fun () ->L.info (fun m -> m "closing events fork");Lwt_pipe.close_nonblock evs);pinlet open Lwt_result.Syntax inlet* 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"))inlet 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 -> ()inVoice.create ~on_destroy:cleanup ~server_id:guild_id ~user_id:user.id~session_id:st.session_id ~token:srv.token srv.endpointinlet open Lwt_result.Syntax inmatch 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 () inlet+ mixer = Mixer.create conn |> Lwt_result.lift inlet vs = { conn; channel_id; mixer } int.voice_sessions <- SfMap.add guild_id vs t.voice_sessions;vs| None ->let* conn = join () inlet+ mixer = Mixer.create conn |> Lwt_result.lift inlet vs = { conn; channel_id; mixer } int.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_streamand voice_sessions = SfMap.empty intlet user { session; _ } = Session.user sessionlet events { events; _ } = events |> Lwt_pipe.of_streamlet _fork_events t = Lwt_stream.clone t.events |> Lwt_pipe.of_streamlet leave_voice ~guild_id ({ session; voice_sessions; _ } as t) =let open Lwt.Infix inmatch 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:trueguild_id| None -> Lwt.return_unitlet join_voice ~guild_id ~channel_id ({ session; _ } as t) =let open Lwt.Syntax inlet user = Session.user session inlet join () =let* () =Session.send_voice_state_update session ~channel_id ~self_deaf:false~self_mute:false guild_idinlet is_own_vsmatch (guild, chan) with| Some g, Some c ->Models.Snowflake.(guild_id = g && channel_id = c && user_id = user.id)| _ -> falseinModels.Snowflake.(guild_id = guild)inlet 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.sessiontype 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 Globalsopen Lwt.Infix
include Containersmodule Models = Discord_modelstype snowflake = Models.Snowflake.ttype bigstring = Bigstringaf.tmodule Lwt_pipe = structinclude Lwt_pipe(* TODO this hasn't actually been tested *)let multicast ~n p =assert (n > 1);let main = create () inlet subs = Seq.(1 --^ n >|= (fun _ -> create ()) |> to_list) inlet running_pipes () = List.filter (fun p -> not @@ is_closed p) subs inlet rec fwd_rec () =let open Lwt.Infix inread 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_unitinlet fwd = fwd_rec () inkeep 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)endinclude Dp_utilsinclude Stdint
open Globalsopen Lwt.Infixmodule L = (val Relog.logger ~namespace:__MODULE__ ())module F = Relog.Fieldmodule Pl = Voice_payloadmodule Ws = structtype t = t' reflet 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 -> ()endmodule Close_code = structtype 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 | _ -> falselet is_std = function #Websocket.Close_code.standard -> true | _ -> falselet 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 -> 4016let 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"infun 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' tlet is_recoverable = function| `Unknown_op | `Invalid_payload | `Not_authenticated| `Authentication_failed | `Already_authenticated | `Unknown_protocol| `Disconnected | `Unknown_encryption_mode| #Websocket.Close_code.standard ->false| _ -> trueendtype 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.tand conn = Fresh | Reconnection of sessionand 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")errinlet out ={interval;preempt = (fun ?interval:_ -> ignore);ack = ignore;cancel = ignore;}inlet gen_nonce =let st = Random.State.make_self_init () infun () -> Random.State.bits stinlet rec loop () =let open Lwt.Syntax inlet acked = ref false inlet nonce = gen_nonce () inlet () = fn nonce inlet p_preempt, u_preempt = Lwt.wait () inlet p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) inout.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 thenLwt.wakeup_later u_preempt (`Preempt interval));let* r = Lwt.pick [ p_sleep; p_preempt ] inmatch (r, !acked) with| `Sleep, true -> loop ()| `Preempt interval, _ ->out.interval <- interval;loop ()| `Sleep, false ->err ();Lwt.return ()| `Cancel, _ -> Lwt.return ()inLwt.async loop;outlet secret_of_int_list l =Seq.of_list l |> Seq.map Char.of_int_exn |> Bytes.of_seqlet with_ws_params ~version uri =let uri = Uri.with_path uri "/" inUri.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 inlet make_hb interval =make_heartbeat(fun nonce -> Lwt.async (fun () -> Ws.send_exn ws (Pl.heartbeat nonce)))intervalinlet 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)inmatch (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)inlet hb_secs = Float.of_int hb /. 1e3 inlet hb = make_hb hb_secs inpoll' (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) inlet hb_secs = Float.of_int hb /. 1e3 inlet hb = make_hb hb_secs inpoll' (Resuming (hb, session))| ( ((Id hb | Resuming (hb, _) | Establish_udp (hb, _)) as st),Hello new_interval ) ->let hb_secs = Float.of_int new_interval /. 1e3 inL.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 inlet ip, port = Udp_connection.local_addr udp inlet address = Unix.string_of_inet_addr ip inlet* () =Ws.send ws(Pl.make_select_protocol ~address ~port ~mode:"xsalsa20_poly1305")inpoll' (Establish_udp (hb, udp))L.info (fun m -> m "successfuly handshaked voice connection");let secret = secret_of_int_list desc.secret_key inlet mode = Udp_connection.encryption_mode_of_string desc.mode inlet crypt = Udp_connection.{ secret; mode } inLwt_result.return (hb, { rtp = Rtp.make ~udp ~crypt; speaking = false })| st, _pl ->L.warn (fun m -> m "ignoring non-control payload during handshake");poll' stinlet c = match session with Some s -> Reconnection s | None -> Fresh inpoll' (Greet c)let create_conn uri =let open Lwt_result.Syntax inlet p =|> Lwt_pipe.Reader.map ~f:(function| Close code ->let code = Close_code.of_close_code_exn code inL.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 inlet uri = Uri.of_string ("wss://" ^ endpoint) |> with_ws_params ~version inlet p_init, u_init = Lwt.wait () inlet ws_writer = Lwt_pipe.create () inlet rtp_writer = Lwt_pipe.create () inlet speak_pipe = Lwt_pipe.create () inlet read_exn p = Lwt_pipe.read p >|= Option.get_exn inlet dc = ref (fun () -> Lwt.return ()) inlet rec manage' ?session () =let rec connect () =let* ws, pipe = create_conn uri inlet res =do_handshake ~server_id ~user_id ~session_id ~token ?session ws pipeinLwt.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)inlet* ws, pipe, hb, session = connect () inlet p_dc, u_dc = Lwt.wait () inlet 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 thenLwt.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);]inlet rec poll' () =let open Lwt.Syntax inlet pl = read_exn bus inlet* res = Lwt.pick [ pl; p_dc ] inmatch 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.ppcode);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 falseL.warn (fun m ->m "got session description, updating...@.%a"let secret = secret_of_int_list desc.secret_key inlet mode = Udp_connection.encryption_mode_of_string desc.mode inRtp.set_crypt session.rtp Udp_connection.{ secret; mode };poll' ()hb.ack nonce;poll' ()L.debug (fun m -> m "ignoring speaking/resumed/clientdisconnect");poll' ()inpoll' () >|= 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;outinLwt.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_initlet _speak s { speak; _ } = Lwt_pipe.write_exn speak slet start_speaking = _speak truelet stop_speaking = _speak falselet send_rtp { rtp_writer; _ } audio = Lwt_pipe.write_exn rtp_writer audiolet disconnect { disconnect; _ } = disconnect ()| Speaking _ | Resumed | Client_disconnect ->| Heartbeat_ack nonce ->Pl.Session_description.pp desc);| Session_description desc ->| Ws_conn.Payload pl -> `Pl plLwt_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 | Closedmodule Ws_conn = Websocket.Make (Voice_payload)
open Lwt.Infixtype t = Cachelet create () = Lwt.return Cache
let send_message channel_id content { http; _ } =let msg = { content; nonce = Websocket.gen_nonce 20; tts = false } inlet uri =Format.sprintf "/channels/%s/messages"(Models.Snowflake.to_string channel_id)inlet ser = yojson_of_create_msg msg inHttp.post ~body:ser uri http|> Lwt.map (function| Ok _ -> ()| Error _ -> L.error (fun m -> m "error sending message"))
val destroy : t -> unit Lwt.tend
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) = structtype 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 } inlet uri =Format.sprintf "/channels/%s/messages"(Models.Snowflake.to_string channel_id)inlet ser = yojson_of_create_msg msg inHttp.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 inL.info (fun m -> m "creating HTTP client");let* http = Http.create token inL.info (fun m -> m "connecting to gateway");let* gw = Gateway.connect ~http token inlet t = { http; gw } inGateway.events gw|> Lwt_pipe.Reader.iter_s ~f:(fun ev -> handler t ev)|> Lwt_result.ok
let gateway { gw; _ } = gwlet disconnect { gw; voice; _ } =Voice.destroy voice >>= fun () -> Gateway.disconnect gwlet voice { voice; _ } = voicelet run ~handler token =let open Lwt_result.Syntax inL.info (fun m -> m "creating HTTP client");let* http = Http.create token inL.debug (fun m -> m "getting gateway url");let* gw_info = Http.get_gateway_info http |> Lwt_result.ok inlet 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)inL.info (fun m -> m "connecting to gateway");let* gw = Gateway.connect ~max_concurrency ~token url inL.info (fun m -> m "initialising cache");let* cache = Cache.create () |> Lwt_result.ok inL.info (fun m -> m "initialising voice manager");let voice = Voice.make gw inlet t = { http; gw; cache; voice } inL.info (fun m -> m "starting main event loop");let ev_handler = handler t inlet evs = Gateway.events gw inlet rec fwd () =Mpmc.Sink.pull evs >>= function| Some ev -> ev_handler ev >>= fwd| None -> Lwt.return_ok ()infwd ()endmodule Nop_voice = structtype t = Noplet make _ = Noplet destroy _ = Lwt.return_unitendinclude Make (Nop_voice)
(library(name disco_core)(public_name disco.core)(preprocess(pps ppx_deriving.show ppx_deriving.ord ppx_yojson_conv))(librariesdisco.modelsdisco.utilscontainerscontainers-datastdintrelog.liburilwtlwt.unixlwt-pipeangstromangstrom-lwt-unixyojsonmtimemtime.clock.oshxd.corehxd.stringpiafsodiumopuswebsocketafwebsocketaf-lwt-unixhttpaf-lwt-unix))
open! Globalsopen Lwt.Infixmodule L = (val Relog.logger ~namespace:__MODULE__ ())module Payload = Gateway_payloadtype 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.tlet 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_rxlet 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.) inlet id_bucket = Token_bucket.make ~capacity:max_concurrency rate inlet+ main = Session.create ~id_bucket token (Uri.of_string url) inlet user = Session.user main inlet shards = [ main ] inlet ev_rx = List.map Session.events shards |> Lwt_pipe.Reader.merge_all inlet src, snk = Mpmc.make () inlet rec fwd () =Lwt_pipe.read ev_rx >>= function| Some v -> Mpmc.Source.write src v >>= fwd| None -> Mpmc.Source.close srcinLwt.async fwd;{user;shards = List.map (fun s -> (Lwt_mutex.create (), s)) shards;id_bucket;ev_rx;ev_snk = snk;}let user { user; _ } = userlet shards { shards; _ } = shardslet shard ~guild_id ~f { shards; _ } =let len = List.length shards inlet id = Int64.(guild_id lsr 22 |> to_int) mod len inlet mtx, sess = List.get_at_idx_exn id shards inLwt_mutex.with_lock mtx (fun () -> f sess)let main_shard { shards; _ } = List.hd shardslet events { ev_snk; _ } = ev_snklet send_presence_update t ?since ~afk status =let mtx, shard = main_shard t inLwt_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_deafguild_idinshard ~guild_id ~f tlet send_guild_request_members t ?presences ?nonce ~q guild_id =let f shard =Session.send_guild_request_members shard ?presences ?nonce ~q guild_idinshard ~guild_id ~f t
module Intents = structtype t = int [@@deriving yojson]let make = List.fold_left (fun out intent -> out lor intent) 0let add i t = t lor ilet rm i t = lnot i land tlet guilds = 1 lsl 0let guild_members = 1 lsl 1let guild_bans = 1 lsl 2let guild_emojis = 1 lsl 3let guild_integrations = 1 lsl 4let guild_webhooks = 1 lsl 5let guild_invites = 1 lsl 6let guild_voice_states = 1 lsl 7
let guild_messages = 1 lsl 9let guild_message_reactions = 1 lsl 10let guild_message_typing = 1 lsl 11let direct_messages = 1 lsl 12let direct_message_reactions = 1 lsl 13let direct_message_typing = 1 lsl 14let all = (1 lsl 15) - 1let 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)inaux [] t 15inFormat.fprintf fmt "%s" outlet show = Format.asprintf "%a" ppend
include Stdintinclude Containersinclude Disco_utilsmodule Models = Disco_modelstype snowflake = Models.Snowflake.ttype sf = Models.Snowflake.ttype bigstring = Bigstringaf.ttype bs = Bigstringaf.t
open! Globalstype t = {mutable interval : float;mutable preempt : ?interval:float -> unit -> unit;mutable ack : unit -> unit;mutable cancel : unit -> unit;}let interval { interval; _ } = intervallet 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")errinlet stub = Fun.const () inlet out ={ interval; preempt = (fun ?interval:_ -> stub); ack = stub; cancel = stub }inlet rec loop () =let open Lwt.Syntax inlet acked = ref false inlet () = fn () inlet p_preempt, u_preempt = Lwt.wait () inlet p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) inout.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 thenLwt.wakeup_later u_preempt (`Preempt interval));let* r = Lwt.pick [ p_sleep; p_preempt ] inmatch (r, !acked) with| `Sleep, true -> loop ()| `Preempt interval, _ ->out.interval <- interval;loop ()| `Sleep, false ->err ();Lwt.return ()| `Cancel, _ -> Lwt.return ()inLwt.async loop;out
module R = structtype 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]endtype gateway_info =| Fallback of { url: string; max_concurrency: int }| Real of R.gateway_infolet 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 inlet* res = get "/gateway/bot" t inlet* body = Piaf.Body.to_string res.body inResult.guard (fun () ->let raw = Yojson.Safe.from_string body inR.gateway_info_of_yojson raw)|> Result.map_err Error.of_exn|> Lwt.returninapi () >|= 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 inlet rate = 1. /. (float capacity /. 60.) inlet bucket = Token_bucket.make ~capacity rate inref (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.bucketinToken_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")errinlet stub = Fun.const () inlet out ={ interval; preempt = (fun ?interval:_ -> stub); ack = stub; cancel = stub }inlet rec loop () =let open Lwt.Syntax inlet acked = ref false inlet () = fn () inlet p_preempt, u_preempt = Lwt.wait () inlet p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) inout.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 thenLwt.wakeup_later u_preempt (`Preempt interval));let* r = Lwt.pick [ p_sleep; p_preempt ] inmatch (r, !acked) with| `Sleep, true -> loop ()| `Preempt interval, _ ->out.interval <- interval;loop ()| `Sleep, false ->err ();Lwt.return ()| `Cancel, _ -> Lwt.return ()inLwt.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_txlet send_exn { op_tx; _ } op =Lwt_pipe.write op_tx op >>= fun ok ->if ok then Lwt.return_unitelse (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 = structtype 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 = structlet user_limit t = t.user_limitlet bitrate t = t.bitratelet rtc_region t = t.rtc_regionendmodule G = struct endtype t = Snowflake.t * channeland channel =| Guild_text| DM| Guild_voice| Group_DM| Guild_category| Guild_news| Guild_store| Guild_stage_voicelet t_of_yojson j =let id = Yojson.Safe.Util.(member "id" j |> Snowflake.t_of_yojson) inlet 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) 0let add i t = t lor ilet rm i t = lnot i land tlet guilds = 1 lsl 0let guild_members = 1 lsl 1let guild_bans = 1 lsl 2let guild_emojis = 1 lsl 3let guild_integrations = 1 lsl 4let guild_webhooks = 1 lsl 5let guild_invites = 1 lsl 6let guild_voice_states = 1 lsl 7let guild_presences = 1 lsl 8let guild_messages = 1 lsl 9let guild_message_reactions = 1 lsl 10let guild_message_typing = 1 lsl 11let direct_messages = 1 lsl 12let direct_message_reactions = 1 lsl 13let direct_message_typing = 1 lsl 14let all = (1 lsl 15) - 1let 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)inaux [] t 15inFormat.fprintf fmt "%s" outlet 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 } int.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 falseelselet 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 vinf t.readers;true
let remove t ~key = access t @@ fun table -> Table.remove table key
let filter ~f t =let o = make () inlet 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 oinschedule_read t ~f:fwd |> ignore;o
let of_seq seq =let tbl, mtx = make () inSeq.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) inlet o = make ~clone:(fun () -> List.map clone sinks |> merge) () inlet c = Array.make (List.length sinks) stub inlet cancel () = Array.iter (fun c -> c ()) c inlet 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 oinList.iteri (fun i k -> c.(i) <- schedule_read k ~f:(fwd i k)) sinks;o
module Cache = MakeCache (Ephemeron.K1.MakeSeeded (structlet equal (K l) (K r) = l = r
let iter_p ~f t =let open Lwt.Infix inlet 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)inlet 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)injoin ()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.utype '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 } inlet key = gen_key () inCache.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 = structtype 'a t = 'a handlelet get { key; source } = Cache.find source.sinks ~keylet peek t = get t >|= fun t -> Ke.Fke.peek t.buflet 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 () int.readers <- Ke.Fke.push t.readers (p, u);plet drop t = get t >|= drop_sinklet clone t = get t >>= fun sink -> connect t.source sink
let write t x =if t.closed then falseelsesinks 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 sourcelet write source v =let f _ sink =(* Printf.printf "handle %d: " k; *)if sink.dropped then ()elselet 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 vinf sink.readersinCache.iter source.sinks ~f
let grow t =let len = Weak.length t.sinks inif Obj.Ephemeron.max_ephe_length - len < len thenfailwith "reached maximum number of sinks";let w = Weak.create (len * 2) inWeak.blit t.sinks 0 w 0 len;t.sinks <- wlet get_free_sink_ref t =let len = Weak.length t.sinks inlet rec f' ?(i = 0) w =if i >= len then Noneelsematch Weak.get w i with| None -> Some i| Some c when c.dropped -> Some i| Some _ -> f' ~i:(i + 1) winmatch f' t.sinks with| Some i -> i| None ->grow t;len
let create () =let sinks = Weak.create 4 inlet source = { sinks; closed = false } inlet rec make_sink () =let r = get_free_sink_ref source inlet sink ={readers = Ke.Fke.empty;buf = Ke.Fke.empty;dropped = false;clone = make_sink;}inGc.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);sinkin(source, make_sink ())
let make () =let sink = make_sink () inlet key = gen_key () inlet sinks = Seq.pure (key, sink) |> Cache.of_seq inlet source = Source.make ~sinks () in(source, { key; source })
open! Disco_core.Globalsmodule L = (val Relog.logger ~namespace:__MODULE__ ())open Lwt.Infixlet silence_frame =[ '\xf8'; '\xff'; '\xfe' ] |> String.of_list|> Bigstringaf.of_string ~off:0 ~len:3let silence_stream = Lwt_stream.from_direct (fun () -> Some silence_frame)(** Conservative default of 10 already taking intoaccount 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 = pmodule Ffmpeg = structmodule L = (val Relog.clone (module L) ~namespace:"Ffmpeg")module Parser = structlet frame ~sample_p ~channels ~size ~kind =let buf =Bigarray.Array1.create kind Bigarray.c_layout (channels * size)inlet p =let open Angstrom inlet sample = count channels sample_p inlet 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) ()insamples () >>| fun () -> bufinfun buf ->Angstrom.parse_bigstring ~consume:Angstrom.Consume.All p buf |> function| Ok frame -> frame| Error str -> failwith @@ "error parsing raw audio frame " ^ strlet s16le ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.LE.any_int16 ~channels ~size:frame_size~kind:Bigarray.int16_signedlet s16be ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.BE.any_int16 ~channels ~size:frame_size~kind:Bigarray.int16_signedlet f32le ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.LE.any_float ~channels ~size:frame_size~kind:Bigarray.float32let f32be ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.BE.any_float ~channels ~size:frame_size~kind:Bigarray.float32let f64le ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.LE.any_double ~channels ~size:frame_size~kind:Bigarray.float64let f64be ?(channels = 2) ?(frame_size = 48000) =frame ~sample_p:Angstrom.BE.any_double ~channels ~size:frame_size~kind:Bigarray.float64endlet 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) inlet blen = Bigstringaf.length buf inlet parse_frame =let parser =Parser.s16le ~channels:Rtp._CHANNELS ~frame_size:Rtp._FRAME_SIZEinfun () -> parser bufinfun 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 exnlet 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 falseinf| _ -> fun oc -> Lwt_io.close oc >|= Fun.const falselet close_input = function `Stream s -> Lwt_pipe.close_nonblock s | _ -> ()let create input =let open Lwt_result.Syntax inlet cmd = "ffmpeg" inlet* 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 ]inlet args = List.concat [ i; pcm_args; stdout_args ] inlet p = Lwt_pipe.create () inlet p_p, u_p = Lwt.wait () inlet spawn () =let cmd_str = cmd :: args |> List.to_string ~sep:" " Fun.id inL.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)inlet read_frame () = frame_reader () proc#stdout inlet write () = writer input proc#stdin >|= fun r -> `Write r inlet 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 inhandle ~out:(fwd :: out) xs| `Fwd true :: xs ->let r = read_frame () inhandle ~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;`EofinOk [ clean ]| `Write true :: xs ->let w = write () inhandle ~out:(w :: out) xs| (`Exn _ as e) :: _ ->List.iter Lwt.cancel out;L.err (fun m -> m "boop %a" Error.pp e);Error einlet res =let open Lwt_result.Syntax inlet* () =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 oinL.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 ()inres >|= 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 () -> ())inlet k =Lwt.catch spawn (fun e ->L.err (fun m -> m "spawn error: %a" Error.pp (`Exn e));Lwt.return_unit)inLwt_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_pipeend
module Websocket = Disco_core.Websockettype 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 | _ -> falselet is_std = function #Websocket.Close_code.standard -> true | _ -> falselet 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 -> 4016let 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"infun 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' tlet 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))(librariesdisco.coredisco.modelsdisco.utilscontainerscontainers-datastdintrelog.liburilwtlwt.unixlwt-pipeangstromangstrom-lwt-unixyojsonmtimemtime.clock.oshxd.corehxd.stringpiafsodiumopuswebsocketafwebsocketaf-lwt-unixhttpaf-lwt-unix))
include Disco_core.Error
open! Disco_core.Globalstype t = {mutable interval : float;mutable preempt : ?interval:float -> unit -> unit;mutable ack : int -> unit;mutable cancel : unit -> unit;}let interval { interval; _ } = intervallet preempt ?interval { preempt; _ } = preempt ?interval ()let ack nonce { ack; _ } = ack noncelet cancel { cancel; _ } = cancel ()let make ?err fn interval =let err =Option.get_or~default:(fun () -> failwith "no ACK of last heartbeat received")errinlet out ={interval;preempt = (fun ?interval:_ -> ignore);ack = ignore;cancel = ignore;}inlet gen_nonce =let st = Random.State.make_self_init () infun () -> Random.State.bits stinlet rec loop () =let open Lwt.Syntax inlet acked = ref false inlet nonce = gen_nonce () inlet () = fn nonce inlet p_preempt, u_preempt = Lwt.wait () inlet p_sleep = Lwt_unix.sleep out.interval |> Lwt.map (Fun.const `Sleep) inout.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 thenLwt.wakeup_later u_preempt (`Preempt interval));let* r = Lwt.pick [ p_sleep; p_preempt ] inmatch (r, !acked) with| `Sleep, true -> loop ()| `Preempt interval, _ ->out.interval <- interval;loop ()| `Sleep, false ->err ();Lwt.return ()| `Cancel, _ -> Lwt.return ()inLwt.async loop;out
open! Disco_core.Globalsopen Lwt.Infixmodule Snowflake = Disco_models.Snowflakemodule E = Disco_core.Eventsmodule Sf_map = Map.Make (Snowflake)module Gateway = Disco_core.Gatewaymodule L = (val Relog.logger ~namespace:__MODULE__ ())module F = Relog.Fieldmodule 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 }| Detachedlet make ?(muted = false) ?(deafened = false) ~gw guild_id ={ gw; guild_id; muted; deafened; conn = Detached; req = None }let guild_id { guild_id; _ } = guild_idlet muted { muted; _ } = mutedlet deafened { deafened; _ } = deafenedlet is_live { conn; _ } = match conn with Live _ -> true | _ -> falselet session { conn; _ } =match conn with| Live { mixer; session } -> Some (mixer, session)| _ -> Nonelet session_exn t = session t |> Option.get_exnlet 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 inlet user_id = (Gateway.user t.gw).id inlet guild_id = t.guild_id inlet* session =Session.create ~guild_id ~user_id ~channel_id ~session_id ~tokenendpointinlet+ mixer = Mixer.create session |> Lwt_result.lift int.conn <- Live { mixer; session };(mixer, session)ininner () >|= notify_req tlet 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) thenL.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) thenL.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 inlet channel_id = Session.channel_id session inconnect 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 inlet endpoint = Session.endpoint session inconnect 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 () int.req <- Some u;Gateway.send_voice_state_update t.gw ~channel_id ~self_mute:t.muted~self_deaf:t.deafened t.guild_id>>= fun () -> pinmatch 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 ()endtype t = { mutable calls : Call.t Sf_map.t; gw : Gateway.t }let user_id { gw; _ } =let user = Gateway.user gw inuser.idlet 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_unitlet 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_unitlet make gw =let t = { calls = Sf_map.empty; gw } inlet evloop () =let open Lwt.Syntax inlet* evs = Gateway.events gw |> Mpmc.Sink.clone inlet 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_unitinfwd ()inLwt.async evloop;tlet 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.emptylet 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 int.calls <- Sf_map.add guild_id call t.calls;call
open! Disco_core.Globalsopen Lwt.Infixmodule L = (val Relog.logger ~namespace:__MODULE__ ())module F = Relog.Fieldmodule Ws_conn = Disco_core.Websocket.Make (Payload)module Pl = Payloadmodule Versions = structtype t = V4let to_string = function V4 -> "4"let to_query t = ("v", [ to_string t ])endmodule Wsd = structtype 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 themain gateway applies here as well *)let create uri =let open Lwt_result.Syntax inlet capacity = 120 inlet rate = 1. /. (float capacity /. 60.) inlet tb = Token_bucket.make ~capacity rate inlet+ ws = Ws_conn.create ~zlib:false uri inlet 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 inL.warn (fun m ->m "voice ws session was closed: %a" Close_code.pp code);`Closed code)in{ tb; ws; rx }let rx { rx; _ } = rxlet is_closed { ws; _ } = Ws_conn.is_closed wslet send t pl =if is_closed t thenLwt.return (Error.msg "cannot send payload to closed ws")elseToken_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)endtype handshake_state =| Greet of conn| Id of Heartbeat.t| Resuming of Heartbeat.t * rtp| Establish_udp of Heartbeat.t * Udp_connection.tand conn = Fresh | Reconnection of rtpand 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_seqlet with_ws_params ~version uri =let uri = Uri.with_path uri "/" inUri.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 inlet ws_rx = Wsd.rx wsd inlet make_hb interval =Heartbeat.make(fun nonce -> Lwt.async (fun () -> Wsd.send_exn wsd (Pl.heartbeat nonce)))intervalinlet 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)inmatch (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)inlet hb_secs = Float.of_int hb /. 1e3 inlet hb = make_hb hb_secs inpoll' (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) inlet hb_secs = Float.of_int hb /. 1e3 inlet hb = make_hb hb_secs inpoll' (Resuming (hb, rtp))| ( ((Id hb | Resuming (hb, _) | Establish_udp (hb, _)) as st),Hello new_interval ) ->let hb_secs = Float.of_int new_interval /. 1e3 inL.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 inlet ip, port = Udp_connection.local_addr udp inlet address = Unix.string_of_inet_addr ip inlet* () =Wsd.send wsd(Pl.make_select_protocol ~address ~port ~mode:"xsalsa20_poly1305")inpoll' (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 inlet mode = Udp_connection.encryption_mode_of_string desc.mode inlet crypt = Udp_connection.{ secret; mode } inLwt_result.return (hb, { rtp = Rtp.make ~udp ~crypt; speaking = false })| st, _pl ->L.warn (fun m -> m "ignoring non-control payload during handshake");poll' stinlet c = match rtp with Some s -> Reconnection s | None -> Fresh inpoll' (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 inlet uri = Uri.of_string ("wss://" ^ endpoint) |> with_ws_params ~version inlet p_init, u_init = Lwt.wait () inlet chan = Lwt_pipe.create () inlet on_destroy, destroyed_u = Lwt.wait () inlet read_exn p = Lwt_pipe.read p >|= Option.get_exn inlet rec manage' ?rtp () =let rec connect () =let* wsd = Wsd.create uri inlet res =do_handshake ~server_id:guild_id ~user_id ~session_id ~token ?rtp wsdinLwt.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)inlet* wsd, hb, rtp = connect () inif Lwt.is_sleeping p_init thenLwt.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);]inlet rec poll' () =let open Lwt.Syntax inlet* task = read_exn bus inmatch 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.ppcode);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 inlet mode = Udp_connection.encryption_mode_of_string desc.mode inRtp.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' ()inpoll' () >|= 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;outinLwt.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_initlet is_dead { op_tx; _ } = Lwt_pipe.is_closed op_txlet version { version; _ } = versionlet guild_id { guild_id; _ } = guild_idlet user_id { user_id; _ } = user_idlet channel_id { channel_id; _ } = channel_idlet token { token; _ } = tokenlet session_id { session_id; _ } = session_idlet endpoint { endpoint; _ } = endpointlet send_exn { op_tx; _ } op =Lwt_pipe.write op_tx op >>= fun ok ->if ok then Lwt.return_unitelse (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 truelet stop_speaking = speak falselet send_rtp t audio = send_exn t (Rtp audio)let disconnect t =let p, u = Lwt.wait () insend_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",