openrunner-rs 1.0.0

A Rust library for running OpenScript
Documentation
//! Type definitions for OpenRunner script execution.

use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
use tempfile::NamedTempFile;
use tokio::process::Child;

/// Configuration options for script execution.
///
/// This struct provides fine-grained control over how OpenScript code is executed,
/// including environment variables, I/O redirection, timeouts, and more.
///
/// # Examples
///
/// ```rust
/// use openrunner_rs::ScriptOptions;
/// use std::time::Duration;
///
/// let options = ScriptOptions::new()
///     .timeout(Duration::from_secs(30))
///     .env("DEBUG", "1")
///     .args(vec!["arg1".to_string(), "arg2".to_string()]);
/// ```
#[derive(Debug, Clone)]
pub struct ScriptOptions {
    pub(crate) openscript_path: Option<PathBuf>,
    pub(crate) working_directory: Option<PathBuf>,
    pub(crate) env_vars: HashMap<String, String>,
    pub(crate) args: Vec<String>,
    pub(crate) stdin: IoOptions,
    pub(crate) stdout: IoOptions,
    pub(crate) stderr: IoOptions,
    pub(crate) timeout: Option<Duration>,
    pub(crate) exit_on_error: bool,
    pub(crate) print_commands: bool,
    pub(crate) ai_enabled: bool,
    pub(crate) clear_env: bool,
}

impl ScriptOptions {
    /// Create a new `ScriptOptions` with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the path to the OpenScript interpreter.
    ///
    /// If not set, the system will look for `openscript` in the PATH.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use openrunner_rs::ScriptOptions;
    ///
    /// let options = ScriptOptions::new()
    ///     .openscript_path("/usr/local/bin/openscript");
    /// ```
    pub fn openscript_path(mut self, path: impl Into<PathBuf>) -> Self {
        self.openscript_path = Some(path.into());
        self
    }

    /// Set the working directory for script execution.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use openrunner_rs::ScriptOptions;
    ///
    /// let options = ScriptOptions::new()
    ///     .working_directory("/tmp");
    /// ```
    pub fn working_directory(mut self, path: impl Into<PathBuf>) -> Self {
        self.working_directory = Some(path.into());
        self
    }

    /// Add an environment variable for script execution.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use openrunner_rs::ScriptOptions;
    ///
    /// let options = ScriptOptions::new()
    ///     .env("DEBUG", "1")
    ///     .env("LOG_LEVEL", "info");
    /// ```
    pub fn env(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
        self.env_vars.insert(key.into(), val.into());
        self
    }

    /// Set command-line arguments for the script.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use openrunner_rs::ScriptOptions;
    ///
    /// let options = ScriptOptions::new()
    ///     .args(vec!["--verbose".to_string(), "input.txt".to_string()]);
    /// ```
    pub fn args(mut self, args: Vec<String>) -> Self {
        self.args = args;
        self
    }

    /// Configure stdin handling.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use openrunner_rs::{types::IoOptions, ScriptOptions};
    ///
    /// let options = ScriptOptions::new()
    ///     .stdin(IoOptions::Null);
    /// ```
    pub fn stdin(mut self, opt: IoOptions) -> Self {
        self.stdin = opt;
        self
    }

    /// Configure stdout handling.
    pub fn stdout(mut self, opt: IoOptions) -> Self {
        self.stdout = opt;
        self
    }

    /// Configure stderr handling.
    pub fn stderr(mut self, opt: IoOptions) -> Self {
        self.stderr = opt;
        self
    }

    /// Set a timeout for script execution.
    ///
    /// If the script runs longer than the specified duration, it will be terminated.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use openrunner_rs::ScriptOptions;
    /// use std::time::Duration;
    ///
    /// let options = ScriptOptions::new()
    ///     .timeout(Duration::from_secs(60));
    /// ```
    pub fn timeout(mut self, duration: Duration) -> Self {
        self.timeout = Some(duration);
        self
    }

    /// Configure whether to exit on script errors.
    pub fn exit_on_error(mut self, exit: bool) -> Self {
        self.exit_on_error = exit;
        self
    }

    /// Configure whether to print commands before execution.
    pub fn print_commands(mut self, print: bool) -> Self {
        self.print_commands = print;
        self
    }

    /// Configure whether AI features are enabled.
    pub fn ai_enabled(mut self, enabled: bool) -> Self {
        self.ai_enabled = enabled;
        self
    }

    /// Configure whether to clear the environment before execution.
    ///
    /// If true, only explicitly set environment variables will be available.
    pub fn clear_env(mut self, clear: bool) -> Self {
        self.clear_env = clear;
        self
    }
}

impl Default for ScriptOptions {
    fn default() -> Self {
        Self {
            openscript_path: None,
            working_directory: None,
            env_vars: HashMap::new(),
            args: Vec::new(),
            stdin: IoOptions::Inherit,
            stdout: IoOptions::Pipe,
            stderr: IoOptions::Pipe,
            timeout: None,
            exit_on_error: true,
            print_commands: false,
            ai_enabled: false,
            clear_env: false,
        }
    }
}

/// Result of script execution.
///
/// Contains all information about the completed script execution, including
/// exit code, captured output, execution duration, and timeout status.
///
/// # Examples
///
/// ```rust
/// use openrunner_rs::{run, ScriptOptions};
///
/// # #[tokio::main]
/// # async fn main() -> openrunner_rs::Result<()> {
/// let options = ScriptOptions::new().openscript_path("/bin/sh");
/// let result = run("echo 'test'", options).await?;
///
/// println!("Exit code: {}", result.exit_code);
/// println!("Output: {}", result.stdout);
/// println!("Duration: {:?}", result.duration);
///
/// if result.timed_out {
///     println!("Script was terminated due to timeout");
/// }
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, Default)]
pub struct ExecResult {
    /// The exit code of the script process.
    pub exit_code: i32,
    /// Captured stdout from the script execution.
    pub stdout: String,
    /// Captured stderr from the script execution.
    pub stderr: String,
    /// Total duration of script execution.
    pub duration: Duration,
    /// Whether the script was terminated due to timeout.
    pub timed_out: bool,
}

/// I/O redirection options for script execution.
///
/// Controls how stdin, stdout, and stderr are handled during script execution.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IoOptions {
    /// Inherit from the parent process.
    ///
    /// The script will use the same stdin/stdout/stderr as the calling process.
    Inherit,
    /// Pipe to the parent process.
    ///
    /// The script's I/O will be captured and made available in the `ExecResult`.
    #[default]
    Pipe,
    /// Discard the output.
    ///
    /// The script's I/O will be redirected to /dev/null (or equivalent).
    Null,
}

/// Result of a spawn operation.
///
/// This struct holds the `Child` process handle and ensures that any temporary
/// files created for the script live as long as the handle.
#[derive(Debug)]
pub struct SpawnResult {
    pub child: Child,
    pub(crate) _temp_file: Option<NamedTempFile>,
}