Something went wrong. Try again.
Nix-based distributed command runner now.dev.br
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120// now: Nix-based distributed command runner// Copyright (C) 2026 Eric Rodrigues Pires//// This program is free software: you can redistribute it and/or modify it under// the terms of the GNU Affero General Public License as published by the Free// Software Foundation, either version 3 of the License, or (at your option)// any later version.//// This program is distributed in the hope that it will be useful, but WITHOUT// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for// more details.//// You should have received a copy of the GNU Affero General Public License along// with this program. If not, see <https://www.gnu.org/licenses/>.
use std::{io::Write, process::Output};
use rand::distr::SampleString;use smol::{ channel::{Receiver, TryRecvError}, future::zip, io::AsyncReadExt, process::Child,};
use crate::job::JobError;
/// Waits for `child` to exit while draining its pipe buffers concurrently.pub(crate) async fn wait_for_output( child: &mut Child, cancellation: Option<&Receiver<()>>,) -> Result<Output, JobError> { let mut stdout = child.stdout.take(); let mut stderr = child.stderr.take(); let mut stdout_buf = Vec::new(); let mut stderr_buf = Vec::new();
let run = async { let (status, (out, err)) = zip( child.status(), zip( async { if let Some(pipe) = stdout.as_mut() { pipe.read_to_end(&mut stdout_buf).await?; } Ok::<(), color_eyre::Report>(()) }, async { if let Some(pipe) = stderr.as_mut() { pipe.read_to_end(&mut stderr_buf).await?; } Ok::<(), color_eyre::Report>(()) }, ), ) .await; out?; err?; let status = status.map_err(|error| JobError::Other(error.into()))?; Ok(Output { status, stdout: stdout_buf, stderr: stderr_buf, }) };
let result = if let Some(cancellation) = cancellation { smol::future::or(run, async { let _ = cancellation.recv().await; Err(JobError::RunnerAborted) }) .await } else { run.await }; let _ = child.kill(); result}
pub(crate) fn write_output_to_stderr(output: &Output) -> color_eyre::Result<()> { let mut stderr = std::io::stderr(); stderr.write_all(&output.stderr)?; stderr.flush()?; Ok(())}
pub(crate) fn get_random_string(len: usize) -> String { rand::distr::Alphanumeric.sample_string(&mut rand::rng(), len)}
pub(crate) fn trim_string(original: &str, max_chars: usize) -> String { debug_assert!(max_chars > 0); let mut output = String::with_capacity(max_chars); let mut iter = original.chars(); for _ in 0..max_chars - 1 { if let Some(char) = iter.next() { output.push(char); } else { return output; } } if iter.next().is_some() { output.push('…'); } output}
pub(crate) fn parse_bool_from_bytes<T: AsRef<[u8]>>(input: T) -> bool { let input = input.as_ref().trim_ascii(); [&b"1"[..], &b"true"[..], &b"yes"[..], &b"y"[..]] .iter() .any(|truthy_slice| input.eq_ignore_ascii_case(truthy_slice))}
pub(crate) fn job_is_aborted(receiver: &Receiver<()>, abort: Option<&Receiver<()>>) -> bool { matches!(receiver.try_recv(), Ok(()) | Err(TryRecvError::Closed)) || abort.is_some_and(|a| matches!(a.try_recv(), Ok(()) | Err(TryRecvError::Closed)))}