pub mod apt;
pub mod cargo;
pub mod custom;
pub mod cwd;
pub mod dnf;
pub mod flatpak;
pub mod pacman;
pub mod path;
use crate::error::prelude::*;
use enum_dispatch::enum_dispatch;
use futures::TryFutureExt;
use prelude::*;
use serde_derive::Deserialize;
use std::process::ExitStatus;
use apt::Apt;
use cargo::Cargo;
use custom::Custom;
use cwd::Cwd;
use dnf::Dnf;
use flatpak::Flatpak;
use pacman::Pacman;
use path::Path;
#[allow(unused_imports)]
pub(crate) mod prelude {
pub(crate) use crate::{
environment::{Environment, ExecutionError, IsEnvironment},
provider::{Actions, Candidate, Error as ProviderError, IsProvider, Provider, Query},
util::{CommandLine, OutputMatcher, cmd},
};
pub(crate) use anyhow::{Context, anyhow};
pub(crate) use async_trait::async_trait;
pub(crate) use displaydoc::Display;
pub(crate) use logerr::LoggableError;
pub(crate) use thiserror::Error as ThisError;
pub(crate) use tracing::{Instrument, debug, error, info, trace, warn};
pub(crate) use std::{fmt, sync::Arc};
pub(crate) type ProviderResult<T> = std::result::Result<T, ProviderError>;
}
#[async_trait]
#[enum_dispatch]
pub trait IsProvider: std::fmt::Debug + std::fmt::Display {
async fn search_internal(
&self,
command: &str,
target_env: Arc<crate::environment::Environment>,
) -> ProviderResult<Vec<Candidate>>;
}
#[tracing::instrument(level = "debug", skip(provider, target_env), fields(%target_env, %provider))]
pub async fn search_in(
provider: Arc<Provider>,
command: &str,
target_env: Arc<Environment>,
) -> Query {
let results =
match IsProvider::search_internal(provider.as_ref(), command, target_env.clone()).await {
Ok(results) if !results.is_empty() => Ok(results),
Ok(_) => Err(ProviderError::NotFound(command.to_string())),
Err(error) => Err(error),
};
Query {
env: target_env,
provider,
term: command.to_string(),
results,
}
}
#[enum_dispatch(IsProvider)]
#[derive(Debug, PartialEq)]
pub enum Provider {
Apt,
Cargo,
Custom,
Cwd,
Dnf,
Flatpak,
Pacman,
Path,
}
impl fmt::Display for Provider {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Apt(val) => write!(f, "{}", val),
Self::Cargo(val) => write!(f, "{}", val),
Self::Custom(val) => write!(f, "{}", val),
Self::Cwd(val) => write!(f, "{}", val),
Self::Dnf(val) => write!(f, "{}", val),
Self::Flatpak(val) => write!(f, "{}", val),
Self::Pacman(val) => write!(f, "{}", val),
Self::Path(val) => write!(f, "{}", val),
}
}
}
#[derive(Debug)]
pub struct Query {
pub env: Arc<crate::environment::Environment>,
pub provider: Arc<Provider>,
pub term: String,
pub results: ProviderResult<Vec<Candidate>>,
}
impl Query {
pub async fn install(&self, index: usize) -> Result<()> {
let err_context = || {
format!(
"failed to install package '{}' from {} inside {}",
self.term, self.provider, self.env,
)
};
let results = self
.results
.as_ref()
.or_else(|error| {
Err(anyhow::anyhow!("{}", error)).with_context(|| {
format!("provider '{}' doesn't offer results to run", self.provider)
})
})
.with_context(err_context)?;
let candidate = results
.get(index)
.with_context(|| format!("requested candidate {} doesn't exist", index))
.with_context(err_context)?;
if let Some(ref command) = candidate.actions.install {
let mut command = command.clone();
command.is_interactive(true);
self.env
.execute(command)
.map_err(anyhow::Error::new)
.and_then(|mut cmd| cmd.status().map_err(anyhow::Error::new))
.await
.with_context(err_context)
.map_err(CnfError::ApplicationError)?;
}
Ok(())
}
pub async fn run(&self, index: usize, args: &[&str]) -> Result<ExitStatus> {
let err_context = || {
format!(
"failed to run package '{}' from {} inside {}",
self.term, self.provider, self.env,
)
};
let results = self
.results
.as_ref()
.or_else(|error| {
Err(anyhow::anyhow!("{}", error)).with_context(|| {
format!("provider '{}' doesn't offer results to run", self.provider)
})
})
.with_context(err_context)?;
let candidate = results
.get(index)
.with_context(|| format!("requested candidate {} doesn't exist", index))
.with_context(err_context)?;
let mut command = candidate.actions.execute.clone();
command.is_interactive(true);
command.append(args);
self.env
.execute(command)
.map_err(anyhow::Error::new)
.and_then(|mut cmd| cmd.status().map_err(anyhow::Error::new))
.await
.with_context(err_context)
.map_err(CnfError::ApplicationError)
}
}
#[derive(Debug, Default, Deserialize)]
pub struct Candidate {
pub package: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub version: String,
#[serde(default)]
pub origin: String,
pub actions: Actions,
}
impl fmt::Display for Candidate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
macro_rules! write_format {
($string:expr, $var:expr) => {
writeln!(
f,
"{:>14}: {}",
$string,
if $var.is_empty() { "n/a" } else { $var }
)
};
}
write_format!("Package", &self.package)?;
write_format!("Description", &self.description)?;
write_format!("Version", &self.version)?;
write_format!("Origin", &self.origin)?;
write_format!(
"Needs install",
if self.actions.install.is_some() {
"yes"
} else {
"no"
}
)
}
}
#[derive(Debug, Default, Deserialize)]
pub struct Actions {
#[serde(default)]
pub install: Option<CommandLine>,
pub execute: CommandLine,
}
#[derive(ThisError, Debug)]
pub enum Error {
#[error("command not found: '{0}'")]
NotFound(String),
#[error("requirement not fulfilled: '{0}'")]
Requirements(String),
#[error(transparent)]
ApplicationError(#[from] anyhow::Error),
#[error("please implement '{0}' first!")]
NotImplemented(String),
#[error(transparent)]
Execution(ExecutionError),
}
impl From<CnfError> for ProviderError {
fn from(value: CnfError) -> Self {
match value {
CnfError::ApplicationError(val) => Self::ApplicationError(val),
_ => Self::ApplicationError(anyhow::Error::new(value)),
}
}
}
impl From<ExecutionError> for ProviderError {
fn from(value: ExecutionError) -> Self {
match value {
ExecutionError::NotFound(val) => ProviderError::Requirements(val),
_ => ProviderError::Execution(value),
}
}
}