luars 0.26.2

A library for lua 5.5 runtime implementation in Rust
Documentation
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};

#[cfg(not(target_arch = "wasm32"))]
use std::process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio};

pub(crate) struct ProcessExit {
    pub(crate) success: bool,
    pub(crate) kind: &'static str,
    pub(crate) code: i32,
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) struct PopenReader {
    child: Child,
    reader: BufReader<ChildStdout>,
    pushback: Option<u8>,
}

#[cfg(not(target_arch = "wasm32"))]
impl PopenReader {
    fn new(child: Child, stdout: ChildStdout) -> Self {
        Self {
            child,
            reader: BufReader::new(stdout),
            pushback: None,
        }
    }

    pub(crate) fn read_line(&mut self, keep_newline: bool) -> io::Result<Option<String>> {
        let mut bytes = Vec::new();
        if let Some(byte) = self.pushback.take() {
            bytes.push(byte);
        }
        let count = self.reader.read_until(b'\n', &mut bytes)?;
        if bytes.is_empty() && count == 0 {
            return Ok(None);
        }
        if !keep_newline && bytes.last() == Some(&b'\n') {
            bytes.pop();
            if bytes.last() == Some(&b'\r') {
                bytes.pop();
            }
        }
        String::from_utf8(bytes)
            .map(Some)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "stream is not valid UTF-8"))
    }

    pub(crate) fn read_all(&mut self) -> io::Result<Vec<u8>> {
        let mut bytes = Vec::new();
        if let Some(byte) = self.pushback.take() {
            bytes.push(byte);
        }
        self.reader.read_to_end(&mut bytes)?;
        Ok(bytes)
    }

    pub(crate) fn read_bytes(&mut self, n: usize) -> io::Result<Vec<u8>> {
        let mut bytes = Vec::with_capacity(n);
        if let Some(byte) = self.pushback.take() {
            bytes.push(byte);
        }
        while bytes.len() < n {
            let mut buffer = vec![0u8; n - bytes.len()];
            let read = self.reader.read(&mut buffer)?;
            if read == 0 {
                break;
            }
            bytes.extend_from_slice(&buffer[..read]);
        }
        Ok(bytes)
    }

    pub(crate) fn read_byte(&mut self) -> io::Result<Option<u8>> {
        if let Some(byte) = self.pushback.take() {
            return Ok(Some(byte));
        }

        let buffer = self.reader.fill_buf()?;
        if buffer.is_empty() {
            Ok(None)
        } else {
            let byte = buffer[0];
            self.reader.consume(1);
            Ok(Some(byte))
        }
    }

    pub(crate) fn unread_byte(&mut self, byte: u8) {
        self.pushback = Some(byte);
    }

    pub(crate) fn is_eof(&mut self) -> io::Result<bool> {
        if self.pushback.is_some() {
            return Ok(false);
        }
        Ok(self.reader.fill_buf()?.is_empty())
    }

    pub(crate) fn close(mut self) -> io::Result<ProcessExit> {
        self.pushback = None;
        map_exit_status(self.child.wait()?)
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) struct PopenWriter {
    child: Child,
    writer: BufWriter<ChildStdin>,
}

#[cfg(not(target_arch = "wasm32"))]
impl PopenWriter {
    fn new(child: Child, stdin: ChildStdin) -> Self {
        Self {
            child,
            writer: BufWriter::new(stdin),
        }
    }

    pub(crate) fn write_bytes(&mut self, bytes: &[u8]) -> io::Result<()> {
        self.writer.write_all(bytes)
    }

    pub(crate) fn flush(&mut self) -> io::Result<()> {
        self.writer.flush()
    }

    pub(crate) fn close(mut self) -> io::Result<ProcessExit> {
        self.writer.flush()?;
        drop(self.writer);
        map_exit_status(self.child.wait()?)
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) enum PopenFile {
    Read(PopenReader),
    Write(PopenWriter),
}

#[cfg(not(target_arch = "wasm32"))]
fn map_exit_status(status: ExitStatus) -> io::Result<ProcessExit> {
    #[cfg(unix)]
    {
        use std::os::unix::process::ExitStatusExt;

        if let Some(code) = status.code() {
            return Ok(ProcessExit {
                success: status.success(),
                kind: "exit",
                code,
            });
        }

        if let Some(signal) = status.signal() {
            return Ok(ProcessExit {
                success: false,
                kind: "signal",
                code: signal,
            });
        }
    }

    Ok(ProcessExit {
        success: status.success(),
        kind: "exit",
        code: status.code().unwrap_or(-1),
    })
}

pub(crate) fn validate_popen_mode(mode: &str) -> bool {
    #[cfg(target_arch = "wasm32")]
    {
        let _ = mode;
        false
    }

    #[cfg(all(not(target_arch = "wasm32"), target_os = "windows"))]
    {
        matches!(mode, "r" | "w" | "rb" | "wb" | "rt" | "wt")
    }

    #[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))]
    {
        matches!(mode, "r" | "w")
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn spawn_popen(command: &str, mode: &str) -> io::Result<PopenFile> {
    let mode_char = mode
        .chars()
        .next()
        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid mode"))?;

    let mut cmd = shell_command(command);
    match mode_char {
        'r' => {
            let mut child = cmd
                .stdin(Stdio::inherit())
                .stdout(Stdio::piped())
                .stderr(Stdio::inherit())
                .spawn()?;
            let stdout = child
                .stdout
                .take()
                .ok_or_else(|| io::Error::other("failed to capture process stdout"))?;
            Ok(PopenFile::Read(PopenReader::new(child, stdout)))
        }
        'w' => {
            let mut child = cmd
                .stdin(Stdio::piped())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .spawn()?;
            let stdin = child
                .stdin
                .take()
                .ok_or_else(|| io::Error::other("failed to capture process stdin"))?;
            Ok(PopenFile::Write(PopenWriter::new(child, stdin)))
        }
        _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid mode")),
    }
}

#[cfg(not(target_arch = "wasm32"))]
fn shell_command(command: &str) -> Command {
    #[cfg(target_os = "windows")]
    {
        let mut cmd = Command::new("cmd");
        cmd.args(["/C", command]);
        cmd
    }

    #[cfg(not(target_os = "windows"))]
    {
        let mut cmd = Command::new("sh");
        cmd.arg("-c").arg(command);
        cmd
    }
}

#[cfg(target_arch = "wasm32")]
pub(crate) fn spawn_popen(_command: &str, _mode: &str) -> io::Result<()> {
    Err(io::Error::other("popen is not supported on wasm"))
}