// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// A temporary pipe used to read output from a spawned process
struct ReadFromProcess {
io : @event_loop.IoHandle
read_buf : @io.ReaderBuffer
}
///|
/// A temporary pipe used to write data to a spawned process
struct WriteToProcess(@event_loop.IoHandle)
///|
/// Create a temporary pipe for reading from stdout/stderr of a process.
/// The return value is a pair `(r, w)`,
/// where `r` is a temporary pipe that can be used to read process output,
/// and `w` should be passed to `@process.run`.
///
/// `w` is temporary: it can only be passed to one `@process.run` call.
/// However, it is safe to pass `w` to both `stdout` and `stderr` of the same process.
pub fn read_from_process() -> (ReadFromProcess, &ProcessOutput) raise {
let context = "@process.read_from_process()"
let (r, w) = @fd_util.pipe(
read_end_is_async=true,
write_end_is_async=false,
context~,
)
let r = @event_loop.IoHandle::from_fd(r, kind=Pipe)
let w = @event_loop.IoHandle::from_fd(w, kind=Pipe, is_async=false)
(
{ io: r, read_buf: @io.ReaderBuffer::new() },
TempPipeWrite::{ pipe: w, closed: false },
)
}
///|
/// Create a temporary pipe for writing to stdin of a process.
/// The return value is a pair `(r, w)`,
/// where `w` is a temporary pipe that can be used to write to process output,
/// and `r` should be passed to `@process.run`.
pub fn write_to_process() -> (&ProcessInput, WriteToProcess) raise {
let context = "@process.write_to_process()"
let (r, w) = @fd_util.pipe(
read_end_is_async=false,
write_end_is_async=true,
context~,
)
let r = @event_loop.IoHandle::from_fd(r, kind=Pipe, is_async=false)
let w = @event_loop.IoHandle::from_fd(w, kind=Pipe)
(TempPipeRead::{ pipe: r, closed: false }, w)
}
///|
pub fn ReadFromProcess::close(self : ReadFromProcess) -> Unit {
self.io.close()
}
///|
pub impl @io.Reader for ReadFromProcess with fn _get_internal_buffer(self) {
self.read_buf
}
///|
pub impl @io.Reader for ReadFromProcess with fn _direct_read(
self,
buf,
offset~,
max_len~,
) {
self.io.read(
buf,
offset~,
len=max_len,
context="@process.ReadFromProcess::read()",
)
}
///|
pub fn WriteToProcess::close(self : WriteToProcess) -> Unit {
let WriteToProcess(io) = self
io.close()
}
///|
pub impl @io.Writer for WriteToProcess with fn write_once(
self,
buf,
offset~,
len~,
) {
let WriteToProcess(io) = self
io.write(buf, offset~, len~, context="@process.WriteToProcess::write()")
}
///|
fn @fs.CreateMode::to_int(self : @fs.CreateMode) -> Int = "%identity"
///|
/// Redirect the output of a process to the file at `path`.
/// The meaning of `append`, `create_mode` and `permission` is the same as `@fs.open`,
/// see the document of `@fs.open` for more details.
#label_migration(create, fill=false, msg="the option `create` is deprecated, use `create_mode` and `permission` instead")
#label_migration(truncate, fill=false, msg="the option `truncate` is deprecated, use `create_mode` instead")
pub async fn redirect_to_file(
path : String,
append? : Bool = false,
create_mode? : @fs.CreateMode,
permission? : Int,
create? : Int,
truncate? : Bool = false,
) -> &ProcessOutput {
let create_mode = match create_mode {
Some(mode) => mode
None =>
match (create is Some(_), truncate) {
(true, true) => CreateOrTruncate
(true, false) => OpenOrCreate
(false, true) => TruncateExisting
(false, false) => OpenExisting
}
}
let permission = match permission {
Some(perm) => perm
None if create is Some(perm) => perm
None => 0o644
}
let (file, _) = @event_loop.open(
path,
1, // write only
create=create_mode.to_int(),
append~,
sync=0,
mode=permission,
context="@process.redirect_to_file()",
)
RedirectToFile(file)
}
///|
/// Redirect the content of a file at `path` to the stdin of a process.
pub async fn redirect_from_file(path : String) -> &ProcessInput {
let (file, _) = @event_loop.open(
path,
0, // read only
create=0, // `OpenExisting`
append=false,
sync=0,
mode=0,
context="@process.redirect_from_file()",
)
RedirectToFile(file)
}
///|
/// An entity that can be used to redirect stdin of a process
trait ProcessInput {
fn fd(Self) -> @fd_util.Fd raise
fn after_spawn(Self) -> Unit = _
}
///|
impl ProcessInput with fn after_spawn(_) {
()
}
///|
pub impl ProcessInput for @pipe.PipeRead with fn fd(self) {
self.fd()
}
///|
pub impl ProcessInput for @stdio.Input with fn fd(self) {
self.fd()
}
///|
/// An entity that can be used to redirect stdout/stderr of a process
trait ProcessOutput {
fn fd(Self) -> @fd_util.Fd raise
fn after_spawn(Self) -> Unit = _
}
///|
impl ProcessOutput with fn after_spawn(_) {
()
}
///|
pub impl ProcessOutput for @pipe.PipeWrite with fn fd(self) {
self.fd()
}
///|
pub impl ProcessOutput for @stdio.Output with fn fd(self) {
self.fd()
}
///|
priv struct TempPipeRead {
pipe : @event_loop.IoHandle
mut closed : Bool
}
///|
priv struct TempPipeWrite {
pipe : @event_loop.IoHandle
mut closed : Bool
}
///|
impl ProcessOutput for TempPipeWrite with fn fd(self) {
self.pipe.fd()
}
///|
impl ProcessOutput for TempPipeWrite with fn after_spawn(self) {
if !self.closed {
self.closed = true
self.pipe.close()
}
}
///|
impl ProcessInput for TempPipeRead with fn fd(self) {
self.pipe.fd()
}
///|
impl ProcessInput for TempPipeRead with fn after_spawn(self) {
if !self.closed {
self.closed = true
self.pipe.close()
}
}
///|
priv struct RedirectToFile(@event_loop.IoHandle)
///|
impl ProcessOutput for RedirectToFile with fn fd(self) {
self.0.fd()
}
///|
impl ProcessOutput for RedirectToFile with fn after_spawn(self) {
self.0.close()
}
///|
impl ProcessInput for RedirectToFile with fn fd(self) {
self.0.fd()
}
///|
impl ProcessInput for RedirectToFile with fn after_spawn(self) {
self.0.close()
}