algosul 0.0.5

Some user-friendly yet personalized tools
Documentation
use std::{
  borrow::Cow,
  collections::BTreeSet,
  ffi::OsStr,
  fmt::{Display, Formatter},
  hash::{DefaultHasher, Hash, Hasher},
  path::{Path, PathBuf},
  process::Command,
  sync::Arc,
};

use algosul_core::{args, cows, utils::std::MapToArgs};
use log::info;

use crate::app::{
  apps::{rust, rust::utils},
  AppInfo,
  AppLicense,
  AppPath,
};

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Rustc
{
  home_path: Arc<PathBuf>,
}
impl Rustc
{
  pub fn as_home_path(&self) -> Arc<PathBuf>
  {
    self.home_path.clone()
  }
}

#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct RustcCommandBuilder
{
  pub compile_flag: RustcCompileFlagForHash,
  pub output:       Option<Cow<'static, OsStr>>,
  pub output_dir:   Option<Cow<'static, OsStr>>,
}
#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
pub struct RustcCompileFlagForHash
{
  pub source_path: Cow<'static, OsStr>,
  pub rustc_path:  Cow<'static, OsStr>,
  pub crate_type:  CrateType,
  pub crate_name:  Option<Cow<'static, str>>,
  pub edition:     Option<Cow<'static, str>>,
  pub target:      rust::HostTriple,
  pub features:    BTreeSet<Cow<'static, str>>,
  pub check_cfg:   Option<Cow<'static, str>>,
  pub codegen:     Vec<Cow<'static, str>>,
  pub link:        Vec<Cow<'static, str>>,
}
#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
pub enum CrateType
{
  #[default]
  Binary,
  Library,
  RustLibrary,
  DynamicLibrary,
  CDynamicLibrary,
  StaticLibrary,
  ProcMacroLibrary,
}

pub const EDITION_2015: &str = "2015";
pub const EDITION_2018: &str = "2018";
pub const EDITION_2021: &str = "2021";
pub const EDITION_2024: &str = "2024";

impl AppInfo for Rustc
{
  type Error = super::Error;

  async fn name(&self) -> Cow<'_, str>
  {
    Cow::Borrowed("rustc")
  }

  async fn license(&self) -> Result<Cow<'_, AppLicense>, Self::Error>
  {
    Ok(Cow::Owned(utils::rust_license()))
  }

  async fn readme(&self) -> Result<String, Self::Error>
  {
    todo!()
  }

  async fn readme_md(&self) -> Result<String, Self::Error>
  {
    todo!()
  }

  async fn documentation(&self) -> Result<Cow<'_, str>, Self::Error>
  {
    Ok(Cow::Borrowed("https://doc.rust-lang.org/rustc"))
  }

  async fn homepage(&self) -> Result<Cow<'_, str>, Self::Error>
  {
    self.repository().await
  }

  async fn repository(&self) -> Result<Cow<'_, str>, Self::Error>
  {
    Ok(Cow::Borrowed("https://github.com/rust-lang/rustc"))
  }

  async fn version(&self) -> Result<Cow<'_, str>, Self::Error>
  {
    todo!()
  }
}
impl AppPath for Rustc
{
  async fn home_path(&self) -> Result<Cow<'_, Path>, Self::Error>
  {
    Ok(Cow::Borrowed(self.home_path.as_ref()))
  }

  async fn bin_path(&self) -> Result<Cow<'_, Path>, Self::Error>
  {
    Ok(Cow::Owned(self.home_path.join("rustc")))
  }
}
impl utils::RustAppExt for Rustc
{
  fn new(home_path: Arc<PathBuf>) -> rust::Result<Self>
  {
    Ok(Self { home_path })
  }
}
impl RustcCompileFlagForHash
{
  pub fn rustc_path(&self) -> &OsStr
  {
    self.rustc_path.as_ref()
  }

  pub fn rustc_version(&self) -> String
  {
    let buffer = String::from_utf8(
      Command::new(self.rustc_path())
        .args(["--version", "--verbose"])
        .output()
        .unwrap()
        .stdout,
    )
    .unwrap();
    info!("{buffer}");
    buffer
  }

  pub fn metadata_hash(&self, hasher: &mut impl Hasher)
  {
    self.source_path.hash(hasher);
    self.crate_type.hash(hasher);
    self.crate_name.hash(hasher);
    self.edition.hash(hasher);
    self.target.hash(hasher);
    self.features.hash(hasher);
    self.codegen.hash(hasher);
    self.link.hash(hasher);
    self.rustc_version().hash(hasher);
  }

  pub fn default_metadata_hash(&self) -> u64
  {
    let mut default_hasher = DefaultHasher::new();
    self.metadata_hash(&mut default_hasher);
    default_hasher.finish()
  }

  pub fn into_args(self) -> (Vec<Cow<'static, OsStr>>, Cow<'static, OsStr>)
  {
    let mut args = Vec::<Cow<'static, OsStr>>::new();
    args!(args:
      if let Some(name) = self.crate_name => cows!["--crate-name", name];
      if let Some(edition) = self.edition => cows!["--edition", edition];
      cows!["--crate-type", self.crate_type.to_string()];
      if let Some(target) = self.target.to_string() =>
        cows!["--target", target];
      if let Some(check_cfg) = self.check_cfg =>
        cows!["--check-cfg", check_cfg];
      self.features.into_iter().flat_map(|x| {
        cows!["--cfg", format!("feature=\"{x}\"")]
      });
      self.codegen.into_iter().flat_map(|x| cows!["-C", x]);
      self.link.into_iter().flat_map(|x| cows!["-L", x]);
    );
    (args, self.source_path)
  }

  pub fn to_cargo_toml(&self) -> toml::Table
  {
    let crate_name = self.crate_name.clone();
    let edition = self.edition.clone();
    let mut package = toml::Table::new();
    if let Some(name) = crate_name
    {
      package.insert("name".to_string(), name.into_owned().into());
    }
    if let Some(edition) = edition
    {
      package.insert("edition".to_string(), edition.into_owned().into());
    }
    package.insert("version".to_string(), "0.1.0".into());
    package.insert("build".to_string(), "build.rs".into());
    let package = package.into();
    let mut table = toml::Table::new();
    table.insert("package".to_string(), package);
    table
  }
}

impl RustcCommandBuilder
{
  pub fn into_args(self) -> Vec<Cow<'static, OsStr>>
  {
    let mut args = Vec::<Cow<'static, OsStr>>::new();
    let metadata_hash =
      format!("{:016x}", self.compile_flag.default_metadata_hash());
    let (flags, source_path) = self.compile_flag.into_args();
    args!(args:
      flags;
      cows![
        "-C", format!("metadata={metadata_hash}"),
        "-C", format!("extra-filename={metadata_hash}")
      ];
      if let Some(output) = self.output =>
        cows![OsStr::new("-o"), output].map_to_args();
      if let Some(output_dir) = self.output_dir =>
        cows![OsStr::new("--out-dir"), output_dir].map_to_args();
      @push source_path;
    );
    args
  }

  pub fn into_command(self) -> Command
  {
    let mut command = Command::new(self.compile_flag.rustc_path());
    command.args(self.into_args());
    command
  }
}
impl Display for CrateType
{
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result
  {
    match self
    {
      CrateType::Binary => f.write_str("bin"),
      CrateType::Library => f.write_str("lib"),
      CrateType::RustLibrary => f.write_str("rlib"),
      CrateType::DynamicLibrary => f.write_str("dylib"),
      CrateType::CDynamicLibrary => f.write_str("cdylib"),
      CrateType::StaticLibrary => f.write_str("staticlib"),
      CrateType::ProcMacroLibrary => f.write_str("proc-macro"),
    }
  }
}