tloop-arduino 0.0.1

Bundled arduino-cli wrapper for tloop — extracts and runs the embedded arduino-cli binary
// crates/loop-arduino/src/lib.rs
use std::{
    path::{Path, PathBuf},
    process::{Command, Output},
};

static ARDUINO_CLI_BYTES: &[u8] = include_bytes!(env!("ARDUINO_CLI_PATH"));

pub fn extract_arduino_cli(cache_dir: PathBuf) -> Result<PathBuf, ArduinoError> {
    std::fs::create_dir_all(&cache_dir)?;
    let name = if cfg!(windows) { "arduino-cli.exe" } else { "arduino-cli" };
    let dest = cache_dir.join(name);
    if !dest.exists() {
        std::fs::write(&dest, ARDUINO_CLI_BYTES)?;
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            std::fs::set_permissions(&dest, std::fs::Permissions::from_mode(0o755))?;
        }
    }
    Ok(dest)
}

/// Installs Arduino libraries via `arduino-cli lib install`.
///
/// Each entry can be:
///   - Registry name:           `"Servo"`
///   - Versioned registry name: `"FastLED@3.5.0"`
///   - Git URL:                 `"https://github.com/example/lib.git"`
///   - Local zip path:          `"/path/to/lib.zip"`
///
/// Already-installed libraries are silently skipped by arduino-cli.
pub fn install_libraries(cli: &Path, libraries: &[String]) -> Result<(), ArduinoError> {
    if libraries.is_empty() {
        return Ok(());
    }
    let args: Vec<&str> = std::iter::once("lib")
        .chain(std::iter::once("install"))
        .chain(libraries.iter().map(String::as_str))
        .collect();
    run(cli, &args)?;
    Ok(())
}

pub fn run(cli: &Path, args: &[&str]) -> Result<Output, ArduinoError> {
    let output = Command::new(cli)
        .args(args)
        .output()
        .map_err(ArduinoError::Spawn)?;
    if !output.status.success() {
        return Err(ArduinoError::CliError(
            String::from_utf8_lossy(&output.stderr).to_string(),
        ));
    }
    Ok(output)
}

#[derive(Debug, thiserror::Error)]
pub enum ArduinoError {
    #[error("failed to spawn arduino-cli: {0}")]
    Spawn(std::io::Error),
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),
    #[error("arduino-cli error: {0}")]
    CliError(String),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn arduino_cli_version_succeeds() {
        let tmp = std::env::temp_dir().join("loop_arduino_shared_test");
        let path = extract_arduino_cli(tmp).unwrap();
        let output = Command::new(&path).arg("version").output().unwrap();
        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains("arduino-cli"));
    }
}