git-kv/app/mgit.ml

176 lines
5.8 KiB
OCaml

let () = Printexc.record_backtrace true
module Store = Git_kv.Make (Pclock)
let reporter ppf =
let report src level ~over k msgf =
let k _ =
over () ;
k () in
let with_metadata header _tags k ppf fmt =
Format.kfprintf k ppf
("[%a]%a[%a]: " ^^ fmt ^^ "\n%!")
Fmt.(styled `Blue int)
(Unix.getpid ()) Logs_fmt.pp_header (level, header)
Fmt.(styled `Magenta string)
(Logs.Src.name src) in
msgf @@ fun ?header ?tags fmt -> with_metadata header tags k ppf fmt in
{ Logs.report }
(*
let () = Fmt_tty.setup_std_outputs ~style_renderer:`Ansi_tty ~utf_8:true ()
let () = Logs.set_reporter (reporter Fmt.stderr)
let () = Logs.set_level ~all:true (Some Logs.Debug)
*)
open Lwt.Infix
let get ~quiet store key =
Store.get store key >>= function
| Ok contents when not quiet ->
Fmt.pr "@[<hov>%a@]\n%!" (Hxd_string.pp Hxd.default) contents ;
Lwt.return (Ok 0)
| Ok _ -> Lwt.return (Ok 0)
| Error err ->
if not quiet then Fmt.epr "%a.\n%!" Store.pp_error err ;
Lwt.return (Ok 1)
let exists ~quiet store key =
Store.exists store key >>= function
| Ok k when not quiet ->
( match k with
| None -> Fmt.pr "%a does not exists\n%!" Mirage_kv.Key.pp key
| Some `Dictionary -> Fmt.pr "%a exists as a dictionary\n%!" Mirage_kv.Key.pp key
| Some `Value -> Fmt.pr "%a exists as a value\n%!" Mirage_kv.Key.pp key ) ;
Lwt.return (Ok 0)
| Ok _ -> Lwt.return (Ok 0)
| Error err ->
if not quiet then Fmt.epr "%a.\n%!" Store.pp_error err ;
Lwt.return (Ok 1)
let value_of_string str =
let v = ref None in
match Scanf.sscanf str "%S" (fun str -> v := Some str) with
| () -> Option.get !v
| exception _ ->
Scanf.sscanf str "%s" (fun str -> v := Some str) ;
Option.get !v
let set ~quiet store key str =
let value = value_of_string str in
Store.set store key value >>= function
| Ok () -> Lwt.return (Ok 0)
| Error err ->
if not quiet then Fmt.epr "%a.\n%!" Store.pp_write_error err ;
Lwt.return (Ok 1)
let remove ~quiet store key =
Store.remove store key >>= function
| Ok () -> Lwt.return (Ok 0)
| Error err ->
if not quiet then Fmt.epr "%a.\n%!" Store.pp_write_error err ;
Lwt.return (Ok 1)
let list ~quiet store key =
Store.list store key >>= function
| Ok lst when not quiet ->
List.iter (fun (name, k) -> match k with
| `Dictionary -> Fmt.pr "d %a\n%!" Mirage_kv.Key.pp name
| `Value -> Fmt.pr "- %a\n%!" Mirage_kv.Key.pp name) lst ;
Lwt.return (Ok 0)
| Ok _ -> Lwt.return (Ok 0)
| Error err ->
if not quiet then Fmt.epr "%a.\n%!" Store.pp_error err ;
Lwt.return (Ok 1)
let pull ~quiet store =
Git_kv.pull store >>= function
| Error (`Msg err) -> if not quiet then Fmt.epr "%s.\n%!" err ; Lwt.return (Ok 1)
| Ok diff when not quiet ->
List.iter (function
| `Add key -> Fmt.pr "+ %a\n%!" Mirage_kv.Key.pp key
| `Remove key -> Fmt.pr "- %a\n%!" Mirage_kv.Key.pp key
| `Change key -> Fmt.pr "* %a\n%!" Mirage_kv.Key.pp key) diff ;
Lwt.return (Ok 0)
| Ok _ -> Lwt.return (Ok 0)
let save store filename =
let oc = open_out filename in
let stream = Git_kv.to_octets store in
Lwt_stream.iter_p (fun str -> output_string oc str; Lwt.return_unit) stream >>= fun () ->
close_out oc ;
Lwt.return (Ok 0)
let trim lst =
List.fold_left (fun acc -> function
| "" -> acc
| str -> str :: acc) [] lst |> List.rev
let with_key ~f key =
match Mirage_kv.Key.v key with
| key -> f key
| exception _ ->
Fmt.epr "Invalid key: %S.\n%!" key ;
Lwt.return (Ok 1)
let repl store fd_in =
let is_a_tty = Unix.isatty fd_in in
let ic = Unix.in_channel_of_descr fd_in in
let rec go store0 =
if is_a_tty then Fmt.pr "# %!" ;
match String.split_on_char ' ' (input_line ic) |> trim with
| [ "get"; key; ] ->
with_key ~f:(get ~quiet:false store0) key
>|= ignore >>= fun () -> go store0
| [ "exists"; key; ] ->
with_key ~f:(exists ~quiet:false store0) key
>|= ignore >>= fun () -> go store0
| "set" :: key :: data ->
let data = String.concat " " data in
with_key ~f:(fun key -> set ~quiet:false store0 key data) key
>|= ignore >>= fun () -> go store0
| [ "remove"; key; ] ->
with_key ~f:(remove ~quiet:false store0) key
>|= ignore >>= fun () -> go store0
| [ "list"; key; ] ->
with_key ~f:(list ~quiet:false store0) key
>|= ignore >>= fun () -> go store0
| [ "pull"; ] ->
if is_a_tty then Fmt.pr "\n%!" ; pull ~quiet:false store0
>|= ignore >>= fun () -> go store0
| [ "quit"; ] -> Lwt.return ()
| [ "fold"; ] ->
Store.change_and_push store0 (fun store1 -> go store1)
>|= Result.fold ~ok:Fun.id ~error:(function `Msg msg -> Fmt.epr "%s.\n%!" msg)
>>= fun () -> go store0
| [ "save"; filename ] ->
save store0 filename >|= ignore
>>= fun _ -> if is_a_tty then Fmt.pr "\n%!" ; go store0
| _ -> Fmt.epr "Invalid command.\n%!" ; go store0
| exception End_of_file -> Lwt.return () in
go store
let run remote = function
| None ->
Lwt_main.run @@
(Git_unix.ctx (Happy_eyeballs_lwt.create ()) >>= fun ctx ->
Git_kv.connect ctx remote >>= fun t ->
repl t Unix.stdin)
| Some filename ->
let contents =
let ic = open_in filename in
let ln = in_channel_length ic in
let bs = Bytes.create ln in
really_input ic bs 0 ln ;
Bytes.unsafe_to_string bs in
Lwt_main.run
( Git_unix.ctx (Happy_eyeballs_lwt.create ()) >>= fun ctx ->
let stream = Lwt_stream.of_list [ contents ] in
Git_kv.of_octets ctx ~remote stream >>= function
| Ok t -> repl t Unix.stdin
| Error (`Msg err) -> Fmt.failwith "%s." err )
let () = match Sys.argv with
| [| _; remote; |] -> run remote None
| [| _; remote; filename; |] when Sys.file_exists filename ->
run remote (Some filename)
| _ -> Fmt.epr "%s <remote> [<filename>]\n%!" Sys.argv.(0)