vyder_std 0.3.4

Standard library for vyder.
Documentation
//! Various functions to interact with the environment.
//!
//! Available under the name `std-env`.

use std::process::ExitCode;

use vyder::{values, Error, ExpectArity, Module, Span, Value, ValueResult};

/// Execute a shell command and waits for it to finish.
///
/// The first argument is the command the execute, the following arguments are given as arguments to the command.
/// Returns a map containing `stdout`, `stderr` and `exit_code`.
pub fn exec(
    arguments: &[ValueResult],
    span: Span,
) -> Result<(ValueResult, Option<ExitCode>), Error> {
    let (process, string_arguments) = get_command_arguments(arguments, span.clone())?;

    let output = match std::process::Command::new(process)
        .args(string_arguments)
        .output()
    {
        Ok(child) => child,
        Err(_) => {
            return Ok((
                Value::new_err_value(
                    values::Str {
                        value: "failed to spawn child process".to_string(),
                    }
                    .into(),
                    span.clone(),
                ),
                None,
            ));
        }
    };

    let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
    let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
    let exit_code = output.status.code().unwrap_or(0) as f64;

    Ok((
        Value::new_ok_value(
            values::Map {
                values: vec![
                    (
                        values::Str {
                            value: "stdout".to_string(),
                        }
                        .into(),
                        values::Str { value: stdout }.into(),
                    ),
                    (
                        values::Str {
                            value: "stderr".to_string(),
                        }
                        .into(),
                        values::Str { value: stderr }.into(),
                    ),
                    (
                        values::Str {
                            value: "exit_code".to_string(),
                        }
                        .into(),
                        values::Number { value: exit_code }.into(),
                    ),
                ],
            }
            .into(),
            span,
        ),
        None,
    ))
}

/// Execute a shell command and doesn't wait for it to finish.
///
/// The first argument is the command the execute, the following arguments are given as arguments to the command.
pub fn spawn(
    arguments: &[ValueResult],
    span: Span,
) -> Result<(ValueResult, Option<ExitCode>), Error> {
    let (process, string_arguments) = get_command_arguments(arguments, span.clone())?;

    let _child = match std::process::Command::new(process)
        .args(string_arguments)
        .spawn()
    {
        Ok(child) => child,
        Err(_) => {
            return Ok((
                Value::new_err_value(
                    values::Str {
                        value: "failed to spawn child process".to_string(),
                    }
                    .into(),
                    span.clone(),
                ),
                None,
            ));
        }
    };

    Ok((Value::new_ok_value(values::Nil.into(), span), None))
}

fn get_command_arguments(
    arguments: &[ValueResult],
    span: Span,
) -> Result<(String, Vec<String>), Error> {
    let (process, arguments) = arguments.to_vec().expect_min::<1>(&span)?;
    let process = process[0]
        .clone()
        .expect_non_error()?
        .expect::<values::Str>()?
        .value;

    let mut string_arguments = vec![];
    for argument in arguments {
        string_arguments.push(argument.expect_non_error()?.expect::<values::Str>()?.value);
    }

    Ok((process, string_arguments))
}

pub fn get_module() -> Module {
    Module::builder()
        .function("exec", exec, true)
        .function("spawn", spawn, true)
        .build()
}