From 8debb50ceeecdf5414370174dae75cca377ed57d Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Wed, 29 Jul 2026 16:16:44 +0100 Subject: [PATCH] Add stdin, stdout and stderr customisation --- example/main.ml | 9 ++---- src/eio_mem.ml | 4 +-- src/eio_mem.mli | 8 ++++- src/low_level.ml | 81 ++++++++++++++++++++++++++++++++++++++++-------- test/dune | 11 ++++++- test/fs.md | 11 +++++++ 6 files changed, 101 insertions(+), 23 deletions(-) diff --git a/example/main.ml b/example/main.ml index 674c3f4..98cc9a6 100644 --- a/example/main.ml +++ b/example/main.ml @@ -6,9 +6,6 @@ module L = Eio_mem.Low_level let () = Eio_main.run @@ fun e -> - Eio_mem.run @@ fun env -> - Eio.Switch.run @@ fun sw -> - let r, w = L.pipe sw in - let _ : int = L.writev w [ Cstruct.of_string "Hello, world!" ] in - L.close w; - Eio.Flow.copy (Eio_mem.Flow.of_fd r) e#stdout + Eio_mem.run ~stdout:e#stdout @@ fun env -> + let _ : int = L.writev L.Fd.stdout [ Cstruct.of_string "Write to stdout!" ] in + () diff --git a/src/eio_mem.ml b/src/eio_mem.ml index c7243a3..44dfa12 100644 --- a/src/eio_mem.ml +++ b/src/eio_mem.ml @@ -196,8 +196,8 @@ end type stdenv = < fs : Eio.Fs.dir_ty Eio.Path.t ; cwd : Eio.Fs.dir_ty Eio.Path.t > -let run ?(cwd = "/") fn = - Low_level.reset (); +let run ?(cwd = "/") ?stdin ?stdout ?stderr fn = + Low_level.reset ?stdin ?stdout ?stderr (); let o = object method fs = ((Fs.fs, "") :> Eio.Fs.dir_ty Eio.Path.t) diff --git a/src/eio_mem.mli b/src/eio_mem.mli index 10d9d68..7d55852 100644 --- a/src/eio_mem.mli +++ b/src/eio_mem.mli @@ -53,7 +53,13 @@ end type stdenv = < fs : Eio.Fs.dir_ty Eio.Path.t ; cwd : Eio.Fs.dir_ty Eio.Path.t > -val run : ?cwd:string -> (stdenv -> 'a) -> 'a +val run : + ?cwd:string -> + ?stdin:_ Eio.Flow.source -> + ?stdout:_ Eio.Flow.sink -> + ?stderr:_ Eio.Flow.sink -> + (stdenv -> 'a) -> + 'a (** [run fn] runs [fn] which provides in-memory resources to [fn]. Note that this does not install an effect handler and you should use something like [Eio_mock.Backend.run] around this call. diff --git a/src/low_level.ml b/src/low_level.ml index 362be8a..d7ab695 100644 --- a/src/low_level.ml +++ b/src/low_level.ml @@ -116,18 +116,18 @@ end module Resource = struct type t = - | Stdin - | Stdout - | Stderr + | Stdin of Eio.Flow.source_ty Eio.Flow.source + | Stdout of Eio.Flow.sink_ty Eio.Flow.sink + | Stderr of Eio.Flow.sink_ty Eio.Flow.sink | File of File.t | Dir of (Inode.t * string) Dir.t | Symlink of Symlink.t | Pipe of Pipe.t let pp ppf = function - | Stdin -> Fmt.string ppf "stdin" - | Stdout -> Fmt.string ppf "stdout" - | Stderr -> Fmt.string ppf "stderr" + | Stdin _ -> Fmt.string ppf "stdin" + | Stdout _ -> Fmt.string ppf "stdout" + | Stderr _ -> Fmt.string ppf "stderr" | Pipe _ -> Fmt.string ppf "pipe" | File f -> Fmt.pf ppf "file:%s:[%a]" f.name @@ -163,13 +163,53 @@ end type resource = Resource.t -let std_table = +module Null = struct + type t = unit + + let read_methods = [] + let single_read () _dst = raise End_of_file + let single_write () bufs = Cstruct.lenv bufs + let copy t ~src = Eio.Flow.Pi.simple_copy ~single_write t ~src +end + +let null_handler = + Eio.Resource.( + handler + [ + H (Eio.Flow.Pi.Source, (module Null)); + H (Eio.Flow.Pi.Sink, (module Null)); + ]) + +let null = + let x = Eio.Resource.T ((), null_handler) in + (x + : Eio.Flow.([ sink_ty | source_ty ]) Eio.Std.r + :> Eio.Flow.([< sink_ty | source_ty ]) Eio.Std.r) + +let null_stdin = (null :> Eio.Flow.source_ty Eio.Flow.source) + +let std_table ?stdin ?stdout ?stderr () = let open Resource in - Fd_table.add 0 (ref Stdin) Fd_table.empty - |> Fd_table.add 1 (ref Stdout) - |> Fd_table.add 2 (ref Stderr) + let stdin = + match stdin with + | Some f -> (f :> Eio.Flow.source_ty Eio.Flow.source) + | None -> null_stdin + in + let stdout = + match stdout with + | Some f -> (f :> Eio.Flow.sink_ty Eio.Flow.sink) + | None -> null + in + let stderr = + match stderr with + | Some f -> (f :> Eio.Flow.sink_ty Eio.Flow.sink) + | None -> null + in + Fd_table.add 0 (ref (Stdin stdin)) Fd_table.empty + |> Fd_table.add 1 (ref (Stdout stdout)) + |> Fd_table.add 2 (ref (Stderr stderr)) -let fd_table : resource ref Fd_table.t ref = ref std_table +let fd_table : resource ref Fd_table.t ref = ref Fd_table.empty let update_fd_table_with_result fn = let v, new_fd_table = fn !fd_table in @@ -189,6 +229,9 @@ module Fd : sig val of_int : sw:Eio.Switch.t -> int -> t val incr : sw:Eio.Switch.t -> t -> t val close : t -> unit + val stdin : t + val stdout : t + val stderr : t end = struct type t = { fd : int; mutable hook : Eio.Switch.hook } @@ -215,6 +258,9 @@ end = struct v let incr ~sw t = of_int ~sw (t.fd + 1) + let stdin = { fd = 0; hook = Eio.Switch.null_hook } + let stdout = { fd = 1; hook = Eio.Switch.null_hook } + let stderr = { fd = 2; hook = Eio.Switch.null_hook } end module Fd_tbl = struct @@ -712,6 +758,12 @@ let writev fd bufs = let () = update_entry fd fds @@ fun r -> match !r with + | Stdout f -> + let _ : int = Eio.Flow.single_write f bufs in + () + | Stderr f -> + let _ : int = Eio.Flow.single_write f bufs in + () | Pipe p -> List.iter (Pipe.write p) bufs | File e -> let size = e.stat.size in @@ -762,6 +814,9 @@ let readv fd bufs = let () = update_entry fd fds @@ fun r -> match !r with + | Stdin f -> + let i = Eio.Flow.single_read f (List.hd bufs) in + read := i | Pipe p -> ( match Pipe.read p with | Some buf -> read := blit_into_bufs buf bufs @@ -788,6 +843,6 @@ let readv fd bufs = in (!read, fds) -let reset () = +let reset ?stdin ?stdout ?stderr () = Filesystem.root := Filesystem.empty; - fd_table := std_table + fd_table := std_table ?stdin ?stdout ?stderr () diff --git a/test/dune b/test/dune index aa4c223..97c20cc 100644 --- a/test/dune +++ b/test/dune @@ -1,3 +1,12 @@ (mdx (files fs.md) - (libraries eio eio.core cstruct eio.mock optint eio_mem fmt)) + (libraries + eio + eio.core + cstruct + eio.mock + optint + eio_mem + fmt + eio_main + eio.unix)) diff --git a/test/fs.md b/test/fs.md index 452fc50..178ad05 100644 --- a/test/fs.md +++ b/test/fs.md @@ -413,3 +413,14 @@ Using `pipe` to create a read/write pipe. - : unit = () ``` +Using the actual stdout. + +```ocaml +# Eio_main.run @@ fun e -> + Eio_mem.run ~stdout:e#stdout @@ fun env -> + let _ : int = L.writev L.Fd.stdout [ Cstruct.of_string "Write to stdout!" ] in + () +Write to stdout! +- : unit = () +``` + -- 2.51.2