soroban-cli 26.0.0

Soroban CLI
Documentation
use itertools::Itertools;
use std::{path::PathBuf, process::Command};
use which::which;

use crate::utils;

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error(transparent)]
    IO(#[from] std::io::Error),

    #[error(transparent)]
    Which(#[from] which::Error),

    #[error(transparent)]
    Regex(#[from] regex::Error),
}

pub fn run() -> Result<(), Error> {
    if let Some((plugin_bin, args)) = find_plugin() {
        std::process::exit(
            Command::new(plugin_bin)
                .args(args)
                .spawn()?
                .wait()?
                .code()
                .unwrap(),
        );
    }

    Ok(())
}

const MAX_HEX_LENGTH: usize = 10;

fn find_bin(name: &str) -> Result<PathBuf, which::Error> {
    if let Ok(path) = which(format!("stellar-{name}")) {
        Ok(path)
    } else {
        which(format!("soroban-{name}"))
    }
}

pub fn list() -> Result<Vec<String>, Error> {
    let re_str = if cfg!(target_os = "windows") {
        r"^(soroban|stellar)-.*.exe$"
    } else {
        r"^(soroban|stellar)-.*"
    };

    let re = regex::Regex::new(re_str)?;

    Ok(which::which_re(re)?
        .filter_map(|b| {
            let s = b.file_name()?.to_str()?;
            Some(s.strip_suffix(".exe").unwrap_or(s).to_string())
        })
        .filter(|s| !(utils::is_hex_string(s) && s.len() > MAX_HEX_LENGTH))
        .map(|s| s.replace("soroban-", "").replace("stellar-", ""))
        .unique()
        .collect())
}

fn find_plugin() -> Option<(PathBuf, Vec<String>)> {
    let args_vec: Vec<String> = std::env::args().skip(1).collect();
    let mut chain: Vec<String> = args_vec
        .iter()
        .take_while(|arg| !arg.starts_with("--"))
        .map(ToString::to_string)
        .collect();

    while !chain.is_empty() {
        let name = chain.join("-");
        let bin = find_bin(&name).ok();

        if let Some(bin) = &bin {
            let index = chain.len();
            let args = args_vec[index..]
                .iter()
                .map(ToString::to_string)
                .collect::<Vec<String>>();

            return Some((bin.into(), args));
        }

        chain.pop();
    }

    None
}