use std::path::{Path, PathBuf};
use crate::error::Result;
use crate::infra::env::Environment;
use crate::model::{
ActivationPolicy, InstallReport, InstallRequest, MigrateManagedBlocksReport,
MigrateManagedBlocksRequest, OperationEvent, RemoveReport, Shell, UninstallRequest,
};
use crate::service::{
detect, install, migrate, resolve_default_target_path, uninstall, with_operation_event_hook,
with_operation_trace,
};
pub fn default_install_path(shell: Shell, program_name: &str) -> Result<PathBuf> {
resolve_default_target_path(&Environment::system(), &shell, program_name)
}
pub fn with_operation_events<R>(
hook: Option<impl Fn(&OperationEvent) + Send + Sync + 'static>,
f: impl FnOnce() -> R,
) -> R {
let hook = hook.map(|hook| {
let hook: std::sync::Arc<dyn Fn(&OperationEvent) + Send + Sync> = std::sync::Arc::new(hook);
hook
});
with_operation_event_hook(hook, f)
}
pub fn install(request: InstallRequest<'_>) -> Result<InstallReport> {
with_operation_trace(|_| install::execute(&Environment::system(), request))
}
pub fn install_with_policy(
request: InstallRequest<'_>,
activation_policy: ActivationPolicy,
) -> Result<InstallReport> {
with_operation_trace(|_| {
install::execute_with_policy(&Environment::system(), request, activation_policy)
})
}
pub fn uninstall(request: UninstallRequest<'_>) -> Result<RemoveReport> {
with_operation_trace(|_| uninstall::execute(&Environment::system(), request))
}
pub fn uninstall_with_policy(
request: UninstallRequest<'_>,
activation_policy: ActivationPolicy,
) -> Result<RemoveReport> {
with_operation_trace(|_| {
uninstall::execute_with_policy(&Environment::system(), request, activation_policy)
})
}
pub fn detect_activation(shell: Shell, program_name: &str) -> Result<crate::ActivationReport> {
with_operation_trace(|_| detect::execute(&Environment::system(), shell, program_name))
}
pub fn detect_activation_at_path(
shell: Shell,
program_name: &str,
target_path: &Path,
) -> Result<crate::ActivationReport> {
with_operation_trace(|_| {
detect::execute_at_path(&Environment::system(), shell, program_name, target_path)
})
}
pub fn migrate_managed_blocks(
request: MigrateManagedBlocksRequest<'_>,
) -> Result<MigrateManagedBlocksReport> {
with_operation_trace(|_| migrate::execute(&Environment::system(), request))
}
#[cfg(feature = "clap")]
#[cfg_attr(docsrs, doc(cfg(feature = "clap")))]
fn render_clap_completion_bytes(
shell: impl Into<Shell>,
bin_name: &str,
command: &mut clap::Command,
) -> Result<Vec<u8>> {
use clap_complete::generate;
let generator = <clap_complete::Shell as TryFrom<Shell>>::try_from(shell.into())?;
let mut output = Vec::new();
generate(generator, command, bin_name, &mut output);
Ok(output)
}
#[cfg(feature = "clap")]
#[cfg_attr(docsrs, doc(cfg(feature = "clap")))]
pub fn render_clap_completion<T: clap::CommandFactory>(
shell: impl Into<Shell>,
bin_name: &str,
) -> Result<Vec<u8>> {
let mut command = T::command();
render_clap_completion_bytes(shell, bin_name, &mut command)
}
#[cfg(feature = "clap")]
#[cfg_attr(docsrs, doc(cfg(feature = "clap")))]
pub fn render_clap_completion_from_command(
shell: impl Into<Shell>,
bin_name: &str,
mut command: clap::Command,
) -> Result<Vec<u8>> {
render_clap_completion_bytes(shell, bin_name, &mut command)
}
#[cfg(all(test, feature = "clap"))]
mod clap_tests {
use clap::{Arg, CommandFactory, Parser};
use super::{render_clap_completion, render_clap_completion_from_command};
use crate::Shell;
#[derive(Parser)]
struct TestCli {
#[arg(long)]
verbose: bool,
}
#[test]
fn renders_clap_completion() {
let script = render_clap_completion::<TestCli>(Shell::Bash, "test-cli")
.expect("bash completion should render");
let rendered = String::from_utf8(script).expect("completion output should be utf-8");
assert!(rendered.contains("test-cli"));
assert!(rendered.contains("_test-cli"));
}
#[test]
fn renders_clap_completion_from_clap_complete_shell() {
let script =
render_clap_completion::<TestCli>(crate::clap_complete::Shell::Fish, "test-cli")
.expect("fish completion should render");
let rendered = String::from_utf8(script).expect("completion output should be utf-8");
assert!(rendered.contains("test-cli"));
}
#[test]
fn renders_clap_completion_from_adjusted_command() {
let command = TestCli::command().arg(Arg::new("profile").long("profile"));
let script = render_clap_completion_from_command(Shell::Bash, "test-cli", command)
.expect("bash completion should render from an adjusted command");
let rendered = String::from_utf8(script).expect("completion output should be utf-8");
assert!(rendered.contains("test-cli"));
assert!(rendered.contains("--profile"));
}
#[test]
fn rejects_other_shell_for_clap_generation() {
let error = render_clap_completion::<TestCli>(Shell::Other("xonsh".to_owned()), "test-cli")
.expect_err("unsupported shell should fail");
assert!(matches!(
error,
crate::Error::UnsupportedShell(Shell::Other(value)) if value == "xonsh"
));
}
#[test]
fn rejects_other_shell_for_prebuilt_clap_command() {
let command = TestCli::command();
let error = render_clap_completion_from_command(
Shell::Other("xonsh".to_owned()),
"test-cli",
command,
)
.expect_err("unsupported shell should fail");
assert!(matches!(
error,
crate::Error::UnsupportedShell(Shell::Other(value)) if value == "xonsh"
));
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{InstallRequest, install};
#[test]
fn api_surface_attaches_trace_id_to_structural_failure() {
let error = install(InstallRequest {
shell: crate::Shell::Bash,
program_name: "tool",
script: b"complete -F _tool tool\n",
path_override: Some(PathBuf::from("tool.bash")),
})
.expect_err("install should fail with validation error");
let report = crate::tests::assert_structural_failure(error, "api-install");
assert_ne!(report.trace_id, 0);
}
}