algosul 0.0.5

Some user-friendly yet personalized tools
Documentation
use std::{
  env,
  fmt::{Display, Formatter},
  fs::Permissions,
  path::{Path, PathBuf},
  sync::Arc,
  time::SystemTime,
};

use log::debug;

use crate::app::{AppGetter, AppLicense, AppPath};

pub fn get_home_dir() -> super::Result<PathBuf>
{
  env::home_dir().ok_or(super::Error::FailedToGetHomeDir)
}
pub(super) fn rust_license() -> AppLicense
{
  AppLicense::Or(
    Box::new(AppLicense::Text("Apache".to_string())),
    Box::new(AppLicense::Text("MIT".to_string())),
  )
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct RustVersion<'a>
{
  pub tool_name: &'a str,
  pub version:   &'a str,
  pub hash:      Option<&'a str>,
  pub date:      &'a str,
}
pub mod regexs
{
  use std::sync::LazyLock;

  use regex::Regex;
  pub const TOOL_NAME: &str = r"(.+)";
  pub const VERSION: &str = r"([\w.\-]+)";
  pub const HASH: &str = r"(?P<hash>[a-f0-9]+)";
  pub const DATE: &str = r"(?P<date>\d{4}-\d{2}-\d{2})";
  pub static RUST_VERSION: LazyLock<Result<Regex, regex::Error>> =
    LazyLock::new(|| {
      Regex::new(&format!(
        r"{TOOL_NAME}\s+{VERSION}\s+\((?:{HASH}\s+)?{DATE}\)"
      ))
    });
}
pub trait ToRustVersion
{
  fn to_rust_version(&'_ self) -> super::Result<RustVersion<'_>>;
}
pub trait RustAppExt: Sized + AppPath<Error = super::Error>
{
  fn new(home_path: Arc<PathBuf>) -> super::Result<Self>;
}
impl<T: RustAppExt> AppGetter for T
{
  async fn get_by_current_user() -> super::Result<Self>
  {
    Self::new(Arc::new(get_home_dir()?.join(".cargo/")))
  }

  #[cfg(unix)]
  async fn get_by_all_user() -> super::Result<Self>
  {
    Self::new(Arc::new(PathBuf::from("/usr/local/bin")))
  }

  #[cfg(windows)]
  async fn get_by_all_user() -> Result<Self, Self::Error>
  {
    Self::new(Arc::new(PathBuf::from(r"C:\Program Files\Rust stable GNU\")))
  }
}
impl ToRustVersion for String
{
  fn to_rust_version(&'_ self) -> super::Result<RustVersion<'_>>
  {
    let regex = regexs::RUST_VERSION.clone()?;
    debug!("Parsing Rust version from string [{regex}]: {self}");
    let captures =
      regex.captures(self).ok_or(super::Error::VersionStringNoMatch)?;
    Ok(RustVersion {
      tool_name: captures.get(1).unwrap().as_str(),
      version:   captures.get(2).unwrap().as_str(),
      hash:      captures.name("hash").map(|m| m.as_str()),
      date:      captures.name("date").unwrap().as_str(),
    })
  }
}
impl Display for RustVersion<'_>
{
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result
  {
    let RustVersion { tool_name, version, hash, date } = self;
    if let Some(hash) = hash
    {
      f.write_fmt(format_args!("{tool_name} {version} ({hash} {date})"))
    }
    else
    {
      f.write_fmt(format_args!("{tool_name} {version} ({date})"))
    }
  }
}
pub async fn dwld_rsinit_sh() -> super::Result<String>
{
  let url = "https://sh.rustup.rs";
  debug!("Downloading rsinit.sh from {url}");
  let content = reqwest::get(url).await?.text().await?;
  debug!("Downloaded rsinit.sh content: {}", &content[..100]);
  if !content.starts_with("#!")
  {
    return Err(super::Error::InvalidRsinitShContent);
  }
  Ok(content)
}
pub async fn dwld_rsinit_sh_and_save_plus_x(path: &Path) -> super::Result<()>
{
  if !path.exists()
    || !path.is_file()
    || (SystemTime::now()
      .duration_since(path.metadata()?.modified()?)?
      .as_secs()
      >= 60 * 24)
  {
    std::fs::write(path, dwld_rsinit_sh().await?)?;
  }
  #[cfg(unix)]
  {
    use std::os::unix::fs::PermissionsExt;
    std::fs::set_permissions(path, Permissions::from_mode(0o755))?;
  }
  Ok(())
}
#[cfg(test)]
mod tests
{
  use super::*;
  #[test]
  fn test_to_rust_version()
  {
    let version = "rustup 1.28.2 (e4f3ad6f8 2025-04-28)".to_string();
    let version = version.to_rust_version().unwrap();
    assert_eq!(version, RustVersion {
      tool_name: "rustup",
      version:   "1.28.2",
      hash:      Some("e4f3ad6f8"),
      date:      "2025-04-28",
    });

    let version = "rustup 1.28.2 (2025-04-28)".to_string();
    let version = version.to_rust_version().unwrap();
    assert_eq!(version, RustVersion {
      tool_name: "rustup",
      version:   "1.28.2",
      hash:      None,
      date:      "2025-04-28",
    });
  }
}