#![allow(dead_code)]
pub(crate) mod cmd;
pub(crate) mod fs;
pub(crate) mod model;
pub(crate) mod store;
#[doc(hidden)]
pub use cmd::Ocd;
#[cfg(test)]
mod tests;
use tracing::{instrument, warn};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Cannot determine path to home directory")]
NoWayHome,
#[error(transparent)]
Toml(#[from] toml_edit::TomlError),
#[error("Dependency {name:?} not found in cluster definition")]
DependencyNotFound { name: String },
#[error("Cluster contains cycle(s): {cycle:?}")]
CircularDependencies { cycle: Vec<String> },
#[error("Expect {name:?} to be defined as a table")]
EntryNotTable { name: String },
#[error("Could not find {name:?} in cluster definition")]
EntryNotFound { name: String },
#[error(transparent)]
Shellexpand(#[from] shellexpand::LookupError<std::env::VarError>),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Program {program:?} called non-interactively, but failed to execute\n{message}")]
SyscallNonInteractive { program: String, message: String },
#[error("Program {program:?} called interactively, but failed to execute")]
SyscallInteractive { program: String },
#[error(transparent)]
Git2(#[from] git2::Error),
#[error("Cannot find \"cluster.toml\" file in root repository")]
NoClusterFile,
#[error("Repository {name:?} defined as bare-alias, but actual repository is normal")]
BareAliasMixup { name: String },
#[error("Repository {name:?} defined as normal, but actual repository is bare-alias")]
NormalMixup { name: String },
#[error("Cannot determine current branch of {repo:?}")]
Git2UnknownBranch { repo: String },
#[error(transparent)]
ProgressStyle(#[from] indicatif::style::TemplateError),
#[error(transparent)]
Inquire(#[from] inquire::InquireError),
#[error("Node not given a name")]
NoNodeName,
#[error(transparent)]
RunScript(#[from] run_script::types::ScriptError),
}
impl From<Error> for i32 {
fn from(error: Error) -> Self {
match error {
Error::NoWayHome => exitcode::IOERR,
Error::Toml(..) => exitcode::CONFIG,
Error::DependencyNotFound { .. } => exitcode::CONFIG,
Error::CircularDependencies { .. } => exitcode::CONFIG,
Error::EntryNotTable { .. } => exitcode::CONFIG,
Error::EntryNotFound { .. } => exitcode::CONFIG,
Error::Shellexpand(..) => exitcode::IOERR,
Error::Io(..) => exitcode::IOERR,
Error::SyscallNonInteractive { .. } => exitcode::OSERR,
Error::SyscallInteractive { .. } => exitcode::OSERR,
Error::Git2(..) => exitcode::SOFTWARE,
Error::NoClusterFile => exitcode::CONFIG,
Error::BareAliasMixup { .. } => exitcode::CONFIG,
Error::NormalMixup { .. } => exitcode::CONFIG,
Error::Git2UnknownBranch { .. } => exitcode::SOFTWARE,
Error::ProgressStyle(..) => exitcode::SOFTWARE,
Error::Inquire(..) => exitcode::SOFTWARE,
Error::NoNodeName => exitcode::USAGE,
Error::RunScript(..) => exitcode::OSERR,
}
}
}
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
pub fn exit_status_from_error(error: anyhow::Error) -> i32 {
match error.downcast::<Error>() {
Ok(error) => error.into(),
Err(_) => exitcode::SOFTWARE,
}
}
#[instrument(skip(patterns, entries), level = "debug")]
pub(crate) fn glob_match(
patterns: impl IntoIterator<Item = impl Into<String>> + std::fmt::Debug,
entries: impl IntoIterator<Item = impl Into<String>> + std::fmt::Debug,
) -> Vec<String> {
let patterns = patterns.into_iter().map(Into::into).collect::<Vec<String>>();
let entries = entries.into_iter().map(Into::into).collect::<Vec<String>>();
let mut matched = Vec::new();
for pattern in &patterns {
let pattern = match glob::Pattern::new(pattern) {
Ok(pattern) => pattern,
Err(error) => {
warn!("Invalid pattern {pattern}: {error}");
continue;
}
};
let mut found = false;
for entry in &entries {
if pattern.matches(entry) {
found = true;
matched.push(entry.to_string());
}
}
if !found {
warn!("Pattern {} does not match any entries", pattern.as_str());
}
}
matched
}