extern crate lazycell;
use lazycell::AtomicLazyCell;
extern crate os_pipe;
extern crate shared_child;
use shared_child::SharedChild;
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Output, Stdio};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
#[cfg(not(windows))]
use std::os::unix::prelude::*;
#[cfg(windows)]
use std::os::windows::prelude::*;
#[cfg(unix)]
pub mod unix;
use ExpressionInner::*;
use IoExpressionInner::*;
pub fn cmd<T, U, V>(program: T, args: U) -> Expression
where
T: ToExecutable,
U: IntoIterator<Item = V>,
V: Into<OsString>,
{
let mut argv_vec = Vec::new();
argv_vec.push(program.to_executable());
argv_vec.extend(args.into_iter().map(Into::<OsString>::into));
Expression::new(Cmd(argv_vec))
}
#[macro_export]
macro_rules! cmd {
( $program:expr ) => {
{
use std::ffi::OsString;
use std::iter::empty;
$crate::cmd($program, empty::<OsString>())
}
};
( $program:expr $(, $arg:expr )* ) => {
{
use std::ffi::OsString;
let mut args: Vec<OsString> = Vec::new();
$(
args.push(Into::<OsString>::into($arg));
)*
$crate::cmd($program, args)
}
};
}
#[derive(Clone, Debug)]
#[must_use]
pub struct Expression(Arc<ExpressionInner>);
impl Expression {
pub fn run(&self) -> io::Result<Output> {
self.start()?.output()
}
pub fn read(&self) -> io::Result<String> {
let output = self.stdout_capture().run()?;
if let Ok(output_str) = std::str::from_utf8(&output.stdout) {
Ok(trim_right_newlines(output_str).to_owned())
} else {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"stdout is not valid UTF-8",
))
}
}
pub fn start(&self) -> io::Result<Handle> {
let (context, stdout_reader, stderr_reader) = IoContext::new()?;
Ok(Handle {
inner: self.0.start(context)?,
result: AtomicLazyCell::new(),
readers: Mutex::new(Some((stdout_reader, stderr_reader))),
})
}
pub fn pipe<T: Into<Expression>>(&self, right: T) -> Expression {
Self::new(Pipe(self.clone(), right.into()))
}
pub fn then<T: Into<Expression>>(&self, right: T) -> Expression {
Self::new(Then(self.clone(), right.into()))
}
pub fn input<T: Into<Vec<u8>>>(&self, input: T) -> Expression {
Self::new(Io(Input(Arc::new(input.into())), self.clone()))
}
pub fn stdin<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(Stdin(path.into()), self.clone()))
}
#[cfg(not(windows))]
pub fn stdin_handle<T: IntoRawFd>(&self, handle: T) -> Expression {
Self::new(Io(StdinHandle(into_file(handle)), self.clone()))
}
#[cfg(windows)]
pub fn stdin_handle<T: IntoRawHandle>(&self, handle: T) -> Expression {
Self::new(Io(StdinHandle(into_file(handle)), self.clone()))
}
pub fn stdin_null(&self) -> Expression {
Self::new(Io(StdinNull, self.clone()))
}
pub fn stdout<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(Stdout(path.into()), self.clone()))
}
#[cfg(not(windows))]
pub fn stdout_handle<T: IntoRawFd>(&self, handle: T) -> Expression {
Self::new(Io(StdoutHandle(into_file(handle)), self.clone()))
}
#[cfg(windows)]
pub fn stdout_handle<T: IntoRawHandle>(&self, handle: T) -> Expression {
Self::new(Io(StdoutHandle(into_file(handle)), self.clone()))
}
pub fn stdout_null(&self) -> Expression {
Self::new(Io(StdoutNull, self.clone()))
}
pub fn stdout_capture(&self) -> Expression {
Self::new(Io(StdoutCapture, self.clone()))
}
pub fn stdout_to_stderr(&self) -> Expression {
Self::new(Io(StdoutToStderr, self.clone()))
}
pub fn stderr<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(Stderr(path.into()), self.clone()))
}
#[cfg(not(windows))]
pub fn stderr_handle<T: IntoRawFd>(&self, handle: T) -> Expression {
Self::new(Io(StderrHandle(into_file(handle)), self.clone()))
}
#[cfg(windows)]
pub fn stderr_handle<T: IntoRawHandle>(&self, handle: T) -> Expression {
Self::new(Io(StderrHandle(into_file(handle)), self.clone()))
}
pub fn stderr_null(&self) -> Expression {
Self::new(Io(StderrNull, self.clone()))
}
pub fn stderr_capture(&self) -> Expression {
Self::new(Io(StderrCapture, self.clone()))
}
pub fn stderr_to_stdout(&self) -> Expression {
Self::new(Io(StderrToStdout, self.clone()))
}
pub fn dir<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(Dir(path.into()), self.clone()))
}
pub fn env<T, U>(&self, name: T, val: U) -> Expression
where
T: Into<OsString>,
U: Into<OsString>,
{
Self::new(Io(
Env(canonicalize_env_var_name(name.into()), val.into()),
self.clone(),
))
}
pub fn env_remove<T>(&self, name: T) -> Expression
where
T: Into<OsString>,
{
Self::new(Io(
EnvRemove(canonicalize_env_var_name(name.into())),
self.clone(),
))
}
pub fn full_env<T, U, V>(&self, name_vals: T) -> Expression
where
T: IntoIterator<Item = (U, V)>,
U: Into<OsString>,
V: Into<OsString>,
{
let env_map = name_vals
.into_iter()
.map(|(k, v)| (canonicalize_env_var_name(k.into()), v.into()))
.collect();
Self::new(Io(FullEnv(env_map), self.clone()))
}
pub fn unchecked(&self) -> Expression {
Self::new(Io(Unchecked, self.clone()))
}
pub fn before_spawn<F>(&self, hook: F) -> Expression
where
F: Fn(&mut Command) -> io::Result<()> + Send + Sync + 'static,
{
Self::new(Io(BeforeSpawn(BeforeSpawnHook::new(hook)), self.clone()))
}
fn new(inner: ExpressionInner) -> Expression {
Expression(Arc::new(inner))
}
}
impl<'a> From<&'a Expression> for Expression {
fn from(expr: &Expression) -> Expression {
expr.clone()
}
}
pub struct Handle {
inner: HandleInner,
result: AtomicLazyCell<io::Result<Output>>,
readers: Mutex<Option<(ReaderThread, ReaderThread)>>,
}
impl Handle {
pub fn wait(&self) -> io::Result<&Output> {
let status = self.inner
.wait(WaitMode::Blocking)?
.expect("blocking wait can't return None");
let mut readers_lock = self.readers.lock().expect("readers lock poisoned");
if !self.result.filled() {
let (stdout_reader, stderr_reader) = readers_lock
.take()
.expect("readers taken without filling result");
let stdout_result = stdout_reader.join().expect("stdout reader panic");
let stderr_result = stderr_reader.join().expect("stderr reader panic");
let final_result = match (stdout_result, stderr_result) {
(Err(err), _) | (_, Err(err)) => Err(err),
_ if status.is_checked_error() => {
Err(io::Error::new(io::ErrorKind::Other, status.message()))
}
(Ok(stdout), Ok(stderr)) => Ok(Output {
status: status.status,
stdout: stdout,
stderr: stderr,
}),
};
self.result
.fill(final_result)
.expect("result already filled outside the readers lock");
}
match *self.result.borrow().expect("result not filled") {
Ok(ref output) => Ok(output),
Err(ref err) => Err(clone_io_error(err)),
}
}
pub fn try_wait(&self) -> io::Result<Option<&Output>> {
if self.inner.wait(WaitMode::Nonblocking)?.is_none() {
Ok(None)
} else {
self.wait().map(Some)
}
}
pub fn output(self) -> io::Result<Output> {
self.wait()?;
self.result
.into_inner()
.expect("wait didn't set the result")
}
pub fn kill(&self) -> io::Result<()> {
self.inner.kill()
}
}
#[derive(Debug)]
enum ExpressionInner {
Cmd(Vec<OsString>),
Pipe(Expression, Expression),
Then(Expression, Expression),
Io(IoExpressionInner, Expression),
}
impl ExpressionInner {
fn start(&self, context: IoContext) -> io::Result<HandleInner> {
Ok(match *self {
Cmd(ref argv) => HandleInner::Child(start_argv(argv, context)?),
Pipe(ref left, ref right) => {
HandleInner::Pipe(Box::new(PipeHandle::start(left, right, context)?))
}
Then(ref left, ref right) => {
HandleInner::Then(Box::new(ThenHandle::start(left, right.clone(), context)?))
}
Io(ref io_inner, ref expr) => start_io(io_inner, expr, context)?,
})
}
}
enum HandleInner {
Child(ChildHandle),
Pipe(Box<PipeHandle>),
Then(Box<ThenHandle>),
Input(Box<InputHandle>),
Unchecked(Box<HandleInner>),
}
impl HandleInner {
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
match *self {
HandleInner::Child(ref child_handle) => child_handle.wait(mode),
HandleInner::Pipe(ref pipe_handle) => pipe_handle.wait(mode),
HandleInner::Then(ref then_handle) => then_handle.wait(mode),
HandleInner::Input(ref input_handle) => input_handle.wait(mode),
HandleInner::Unchecked(ref inner_handle) => {
Ok(inner_handle.wait(mode)?.map(|mut status| {
status.checked = false;
status
}))
}
}
}
fn kill(&self) -> io::Result<()> {
match *self {
HandleInner::Child(ref child_handle) => child_handle.kill(),
HandleInner::Pipe(ref pipe_handle) => pipe_handle.kill(),
HandleInner::Then(ref then_handle) => then_handle.kill(),
HandleInner::Input(ref input_handle) => input_handle.kill(),
HandleInner::Unchecked(ref inner_handle) => inner_handle.kill(),
}
}
}
fn start_argv(argv: &[OsString], context: IoContext) -> io::Result<ChildHandle> {
let exe = canonicalize_exe_path_for_dir(&argv[0], &context)?;
let mut command = Command::new(exe);
command.args(&argv[1..]);
command.stdin(context.stdin.into_stdio()?);
command.stdout(context.stdout.into_stdio()?);
command.stderr(context.stderr.into_stdio()?);
if let Some(dir) = context.dir {
command.current_dir(dir);
}
command.env_clear();
for (name, val) in context.env {
command.env(name, val);
}
for hook in context.before_spawn_hooks.iter() {
hook.call(&mut command)?;
}
let shared_child = SharedChild::spawn(&mut command)?;
let command_string = format!("{:?}", argv);
Ok(ChildHandle {
child: shared_child,
command_string: command_string,
})
}
struct ChildHandle {
child: shared_child::SharedChild,
command_string: String,
}
impl ChildHandle {
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let maybe_status = match mode {
WaitMode::Blocking => Some(self.child.wait()?),
WaitMode::Nonblocking => self.child.try_wait()?,
};
if let Some(status) = maybe_status {
Ok(Some(ExpressionStatus {
status: status,
checked: true,
command: self.command_string.clone(),
}))
} else {
Ok(None)
}
}
fn kill(&self) -> io::Result<()> {
self.child.kill()
}
}
struct PipeHandle {
left_handle: HandleInner,
right_start_result: io::Result<HandleInner>,
}
impl PipeHandle {
fn start(left: &Expression, right: &Expression, context: IoContext) -> io::Result<PipeHandle> {
let (reader, writer) = os_pipe::pipe()?;
let mut left_context = context.try_clone()?;
left_context.stdout = IoValue::Handle(into_file(writer));
let mut right_context = context;
right_context.stdin = IoValue::Handle(into_file(reader));
let left_handle = left.0.start(left_context)?;
let right_result = right.0.start(right_context);
Ok(PipeHandle {
left_handle: left_handle,
right_start_result: right_result,
})
}
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let left_wait_result = self.left_handle.wait(mode);
let right_handle = match self.right_start_result {
Ok(ref handle) => handle,
Err(ref err) => return Err(clone_io_error(err)),
};
let right_wait_result = right_handle.wait(mode);
let left_status = left_wait_result?;
let right_status = right_wait_result?;
Ok(pipe_status_precedence(left_status, right_status))
}
fn kill(&self) -> io::Result<()> {
let left_kill_result = self.left_handle.kill();
if let Ok(ref right_handle) = self.right_start_result {
let right_kill_result = right_handle.kill();
left_kill_result.and(right_kill_result)
} else {
left_kill_result
}
}
}
fn pipe_status_precedence(
left_maybe_status: Option<ExpressionStatus>,
right_maybe_status: Option<ExpressionStatus>,
) -> Option<ExpressionStatus> {
let (left_status, right_status) = match (left_maybe_status, right_maybe_status) {
(Some(left), Some(right)) => (left, right),
_ => return None,
};
Some(if right_status.is_checked_error() {
right_status
} else if left_status.is_checked_error() {
left_status
} else if !right_status.status.success() {
right_status
} else {
left_status
})
}
struct ThenHandle {
shared_state: Arc<ThenHandleInner>,
background_waiter: SharedThread<io::Result<ExpressionStatus>>,
}
impl ThenHandle {
fn start(left: &Expression, right: Expression, context: IoContext) -> io::Result<ThenHandle> {
let left_context = context.try_clone()?;
let left_handle = left.0.start(left_context)?;
let shared = Arc::new(ThenHandleInner {
left_handle: left_handle,
right_lock: Mutex::new(Some((right, context))),
right_cell: AtomicLazyCell::new(),
});
let clone = Arc::clone(&shared);
let background_waiter = std::thread::spawn(move || {
Ok(clone
.wait(WaitMode::Blocking)?
.expect("blocking wait can't return None"))
});
Ok(ThenHandle {
shared_state: shared,
background_waiter: SharedThread::new(background_waiter),
})
}
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let wait_res = self.shared_state.wait(mode);
if mode.should_join_background_thread(&wait_res) {
self.background_waiter
.join()
.as_ref()
.map_err(clone_io_error)?;
}
wait_res
}
fn kill(&self) -> io::Result<()> {
self.shared_state.kill()
}
}
struct ThenHandleInner {
left_handle: HandleInner,
right_lock: Mutex<Option<(Expression, IoContext)>>,
right_cell: AtomicLazyCell<io::Result<HandleInner>>,
}
impl ThenHandleInner {
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let left_status = match self.left_handle.wait(mode)? {
Some(status) => status,
None => return Ok(None),
};
let mut right_lock_guard = self.right_lock.lock().unwrap();
let maybe_expression_context = right_lock_guard.take();
if left_status.is_checked_error() {
return Ok(Some(left_status));
}
if let Some((expression, context)) = maybe_expression_context {
let right_start_result = expression.0.start(context);
self.right_cell
.fill(right_start_result)
.map_err(|_| "right_cell unexpectedly filled")
.unwrap();
}
drop(right_lock_guard);
match self.right_cell.borrow() {
Some(&Ok(ref handle)) => handle.wait(mode),
Some(&Err(ref err)) => Err(clone_io_error(err)),
None => Ok(Some(left_status)),
}
}
fn kill(&self) -> io::Result<()> {
let mut right_lock_guard = self.right_lock.lock().unwrap();
*right_lock_guard = None;
let left_result = self.left_handle.kill();
if let Some(&Ok(ref handle)) = self.right_cell.borrow() {
let right_result = handle.kill();
left_result.and(right_result)
} else {
left_result
}
}
}
fn start_io(
io_inner: &IoExpressionInner,
expr_inner: &Expression,
mut context: IoContext,
) -> io::Result<HandleInner> {
match *io_inner {
Input(ref v) => {
return Ok(HandleInner::Input(Box::new(InputHandle::start(
expr_inner,
context,
Arc::clone(v),
)?)))
}
Stdin(ref p) => {
context.stdin = IoValue::Handle(File::open(p)?);
}
StdinHandle(ref f) => {
context.stdin = IoValue::Handle(f.try_clone()?);
}
StdinNull => {
context.stdin = IoValue::Null;
}
Stdout(ref p) => {
context.stdout = IoValue::Handle(File::create(p)?);
}
StdoutHandle(ref f) => {
context.stdout = IoValue::Handle(f.try_clone()?);
}
StdoutNull => {
context.stdout = IoValue::Null;
}
StdoutCapture => {
context.stdout = IoValue::Handle(into_file(context.stdout_capture_pipe.try_clone()?));
}
StdoutToStderr => {
context.stdout = context.stderr.try_clone()?;
}
Stderr(ref p) => {
context.stderr = IoValue::Handle(File::create(p)?);
}
StderrHandle(ref f) => {
context.stderr = IoValue::Handle(f.try_clone()?);
}
StderrNull => {
context.stderr = IoValue::Null;
}
StderrCapture => {
context.stderr = IoValue::Handle(into_file(context.stderr_capture_pipe.try_clone()?));
}
StderrToStdout => {
context.stderr = context.stdout.try_clone()?;
}
Dir(ref p) => {
context.dir = Some(p.clone());
}
Env(ref name, ref val) => {
context.env.insert(name.clone(), val.clone());
}
EnvRemove(ref name) => {
context.env.remove(name);
}
FullEnv(ref map) => {
context.env = map.clone();
}
Unchecked => {
let inner_handle = expr_inner.0.start(context)?;
return Ok(HandleInner::Unchecked(Box::new(inner_handle)));
}
BeforeSpawn(ref hook) => {
context.before_spawn_hooks.push(hook.clone());
}
}
expr_inner.0.start(context)
}
struct InputHandle {
inner_handle: HandleInner,
writer_thread: WriterThread,
}
impl InputHandle {
fn start(
expression: &Expression,
mut context: IoContext,
input: Arc<Vec<u8>>,
) -> io::Result<InputHandle> {
let (reader, mut writer) = os_pipe::pipe()?;
context.stdin = IoValue::Handle(into_file(reader));
let inner = expression.0.start(context)?;
let thread = std::thread::spawn(move || writer.write_all(&input));
Ok(InputHandle {
inner_handle: inner,
writer_thread: SharedThread::new(thread),
})
}
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let wait_res = self.inner_handle.wait(mode);
if mode.should_join_background_thread(&wait_res) {
match *self.writer_thread.join() {
Err(ref err) if err.kind() != io::ErrorKind::BrokenPipe => {
return Err(clone_io_error(err));
}
_ => {}
}
}
wait_res
}
fn kill(&self) -> io::Result<()> {
self.inner_handle.kill()
}
}
#[derive(Debug)]
enum IoExpressionInner {
Input(Arc<Vec<u8>>),
Stdin(PathBuf),
StdinHandle(File),
StdinNull,
Stdout(PathBuf),
StdoutHandle(File),
StdoutNull,
StdoutCapture,
StdoutToStderr,
Stderr(PathBuf),
StderrHandle(File),
StderrNull,
StderrCapture,
StderrToStdout,
Dir(PathBuf),
Env(OsString, OsString),
EnvRemove(OsString),
FullEnv(HashMap<OsString, OsString>),
Unchecked,
BeforeSpawn(BeforeSpawnHook),
}
#[derive(Clone)]
struct BeforeSpawnHook {
inner: Arc<Fn(&mut Command) -> io::Result<()> + Send + Sync>,
}
impl BeforeSpawnHook {
fn new<F>(hook: F) -> Self
where
F: Fn(&mut Command) -> io::Result<()> + Send + Sync + 'static,
{
Self {
inner: Arc::new(hook),
}
}
fn call(&self, command: &mut Command) -> io::Result<()> {
(self.inner)(command)
}
}
impl fmt::Debug for BeforeSpawnHook {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<closure>")
}
}
#[derive(Debug)]
struct IoContext {
stdin: IoValue,
stdout: IoValue,
stderr: IoValue,
stdout_capture_pipe: os_pipe::PipeWriter,
stderr_capture_pipe: os_pipe::PipeWriter,
dir: Option<PathBuf>,
env: HashMap<OsString, OsString>,
before_spawn_hooks: Vec<BeforeSpawnHook>,
}
impl IoContext {
fn new() -> io::Result<(IoContext, ReaderThread, ReaderThread)> {
let (stdout_capture_pipe, stdout_reader) = pipe_with_reader_thread()?;
let (stderr_capture_pipe, stderr_reader) = pipe_with_reader_thread()?;
let env: HashMap<_, _> = std::env::vars_os().collect();
let context = IoContext {
stdin: IoValue::ParentStdin,
stdout: IoValue::ParentStdout,
stderr: IoValue::ParentStderr,
stdout_capture_pipe: stdout_capture_pipe,
stderr_capture_pipe: stderr_capture_pipe,
dir: None,
env: env,
before_spawn_hooks: Vec::new(),
};
Ok((context, stdout_reader, stderr_reader))
}
fn try_clone(&self) -> io::Result<IoContext> {
Ok(IoContext {
stdin: self.stdin.try_clone()?,
stdout: self.stdout.try_clone()?,
stderr: self.stderr.try_clone()?,
stdout_capture_pipe: self.stdout_capture_pipe.try_clone()?,
stderr_capture_pipe: self.stderr_capture_pipe.try_clone()?,
dir: self.dir.clone(),
env: self.env.clone(),
before_spawn_hooks: self.before_spawn_hooks.clone(),
})
}
}
#[derive(Debug)]
enum IoValue {
ParentStdin,
ParentStdout,
ParentStderr,
Null,
Handle(File),
}
impl IoValue {
fn try_clone(&self) -> io::Result<IoValue> {
Ok(match *self {
IoValue::ParentStdin => IoValue::ParentStdin,
IoValue::ParentStdout => IoValue::ParentStdout,
IoValue::ParentStderr => IoValue::ParentStderr,
IoValue::Null => IoValue::Null,
IoValue::Handle(ref f) => IoValue::Handle(f.try_clone()?),
})
}
fn into_stdio(self) -> io::Result<Stdio> {
Ok(match self {
IoValue::ParentStdin => os_pipe::dup_stdin()?.into(),
IoValue::ParentStdout => os_pipe::dup_stdout()?.into(),
IoValue::ParentStderr => os_pipe::dup_stderr()?.into(),
IoValue::Null => Stdio::null(),
IoValue::Handle(f) => f.into(),
})
}
}
#[cfg(windows)]
fn into_file<T: IntoRawHandle>(handle: T) -> File {
unsafe { File::from_raw_handle(handle.into_raw_handle()) }
}
#[cfg(not(windows))]
fn into_file<T: IntoRawFd>(handle: T) -> File {
unsafe { File::from_raw_fd(handle.into_raw_fd()) }
}
#[derive(Clone, Debug)]
struct ExpressionStatus {
status: ExitStatus,
checked: bool,
command: String,
}
impl ExpressionStatus {
fn is_checked_error(&self) -> bool {
self.checked && !self.status.success()
}
fn message(&self) -> String {
format!(
"command {} exited with code {}",
self.command,
self.exit_code_string()
)
}
#[cfg(not(windows))]
fn exit_code_string(&self) -> String {
use std::os::unix::process::ExitStatusExt;
if self.status.code().is_none() {
return format!("<signal {}>", self.status.signal().unwrap());
}
self.status.code().unwrap().to_string()
}
#[cfg(windows)]
fn exit_code_string(&self) -> String {
self.status.code().unwrap().to_string()
}
}
fn canonicalize_exe_path_for_dir(exe_name: &OsStr, context: &IoContext) -> io::Result<OsString> {
let has_separator = exe_name
.to_string_lossy()
.chars()
.any(std::path::is_separator);
let is_relative = Path::new(exe_name).is_relative();
if context.dir.is_some() && has_separator && is_relative {
Path::new(exe_name).canonicalize().map(Into::into)
} else {
Ok(exe_name.to_owned())
}
}
fn dotify_relative_exe_path(path: &Path) -> PathBuf {
Path::new(".").join(path)
}
pub trait ToExecutable {
fn to_executable(self) -> OsString;
}
impl<'a> ToExecutable for &'a Path {
fn to_executable(self) -> OsString {
dotify_relative_exe_path(self).into()
}
}
impl ToExecutable for PathBuf {
fn to_executable(self) -> OsString {
dotify_relative_exe_path(&self).into()
}
}
impl<'a> ToExecutable for &'a PathBuf {
fn to_executable(self) -> OsString {
dotify_relative_exe_path(self).into()
}
}
impl<'a> ToExecutable for &'a str {
fn to_executable(self) -> OsString {
self.into()
}
}
impl ToExecutable for String {
fn to_executable(self) -> OsString {
self.into()
}
}
impl<'a> ToExecutable for &'a String {
fn to_executable(self) -> OsString {
self.into()
}
}
impl<'a> ToExecutable for &'a OsStr {
fn to_executable(self) -> OsString {
self.into()
}
}
impl ToExecutable for OsString {
fn to_executable(self) -> OsString {
self
}
}
impl<'a> ToExecutable for &'a OsString {
fn to_executable(self) -> OsString {
self.into()
}
}
type ReaderThread = JoinHandle<io::Result<Vec<u8>>>;
fn pipe_with_reader_thread() -> io::Result<(os_pipe::PipeWriter, ReaderThread)> {
let (mut reader, writer) = os_pipe::pipe()?;
let thread = std::thread::spawn(move || {
let mut output = Vec::new();
reader.read_to_end(&mut output)?;
Ok(output)
});
Ok((writer, thread))
}
type WriterThread = SharedThread<io::Result<()>>;
fn trim_right_newlines(s: &str) -> &str {
s.trim_right_matches(|c| c == '\n' || c == '\r')
}
fn clone_io_error(error: &io::Error) -> io::Error {
if let Some(code) = error.raw_os_error() {
io::Error::from_raw_os_error(code)
} else {
io::Error::new(error.kind(), error.to_string())
}
}
struct SharedThread<T> {
result: AtomicLazyCell<T>,
handle: Mutex<Option<JoinHandle<T>>>,
}
impl<T> SharedThread<T> {
fn new(handle: JoinHandle<T>) -> Self {
SharedThread {
result: AtomicLazyCell::new(),
handle: Mutex::new(Some(handle)),
}
}
fn join(&self) -> &T {
let mut handle_lock = self.handle.lock().expect("shared thread handle poisoned");
if let Some(handle) = handle_lock.take() {
let ret = handle.join().expect("panic on shared thread");
self.result
.fill(ret)
.map_err(|_| "result lazycell unexpectedly full")
.unwrap();
}
self.result
.borrow()
.expect("result lazycell unexpectedly empty")
}
}
#[derive(Clone, Copy, Debug)]
enum WaitMode {
Blocking,
Nonblocking,
}
impl WaitMode {
fn should_join_background_thread(
&self,
expression_result: &io::Result<Option<ExpressionStatus>>,
) -> bool {
match (self, expression_result) {
(&WaitMode::Blocking, _) | (_, &Ok(Some(_))) => true,
_ => false,
}
}
}
#[cfg(windows)]
fn canonicalize_env_var_name(name: OsString) -> OsString {
match name.into_string() {
Ok(name) => name.to_uppercase().into(),
Err(name) => name,
}
}
#[cfg(not(windows))]
fn canonicalize_env_var_name(name: OsString) -> OsString {
name
}
#[cfg(test)]
mod test;