use clap::error::ErrorKind;
use std::path::Path;
const TRACING_LOG_DIR_ENV: &str = "ATHENA_TRACING_LOG_DIR";
pub fn cli_parse_hint(args: &[String], error_kind: ErrorKind) -> Option<String> {
if !matches!(
error_kind,
ErrorKind::InvalidSubcommand | ErrorKind::UnknownArgument
) {
return None;
}
if !looks_like_cargo_build_invocation(args) {
return None;
}
Some(
"This is the Athena runtime/CLI binary (`athena`), not cargo.\n\
Preferred: athena --help\n\
Server: athena server (or just `athena` when you have a config.yaml)\n\
Build: cargo build --bin athena --bin athena-cli\n\
Dev: cargo run --bin athena -- server"
.to_string(),
)
}
pub fn tracing_log_permission_hint(
permission_denied_path: &Path,
selected_log_dir: Option<&Path>,
) -> Option<String> {
#[cfg(unix)]
{
let target: String = sh_single_quote(&permission_denied_path.display().to_string());
let export_target: String = sh_single_quote(
&selected_log_dir
.unwrap_or(permission_denied_path)
.display()
.to_string(),
);
return Some(format!(
"To keep Athena logging there, run `sudo mkdir -p '{target}' && sudo chown \"$USER\":\"$USER\" '{target}' && sudo chmod 755 '{target}'`. For systemd, replace `$USER` with the service user/group (for example `athena`). Or pin a writable directory with `export {TRACING_LOG_DIR_ENV}='{export_target}'`."
));
}
#[cfg(windows)]
{
let target: String = ps_single_quote(&permission_denied_path.display().to_string());
let export_target: String = ps_single_quote(
&selected_log_dir
.unwrap_or(permission_denied_path)
.display()
.to_string(),
);
return Some(format!(
"PowerShell fix: `New-Item -ItemType Directory -Force -Path '{target}' | Out-Null; icacls '{target}' /grant \"$env:USERNAME:(OI)(CI)M\"`. Or pin a writable directory with `$env:{TRACING_LOG_DIR_ENV} = '{export_target}'`."
));
}
#[cfg(not(any(unix, windows)))]
{
let _ = permission_denied_path;
let _ = selected_log_dir;
None
}
}
fn looks_like_cargo_build_invocation(args: &[String]) -> bool {
args.iter()
.skip(1)
.any(|arg| matches!(arg.as_str(), "build" | "--release" | "--bin" | "--features"))
}
#[cfg(unix)]
fn sh_single_quote(value: &str) -> String {
value.replace('\'', "'\"'\"'")
}
#[cfg(windows)]
fn ps_single_quote(value: &str) -> String {
value.replace('\'', "''")
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn cargo_build_invocation_receives_runtime_cli_hint() {
let args: Vec<String> = vec![
"athena_rs".to_string(),
"build".to_string(),
"--release".to_string(),
];
let hint: String = cli_parse_hint(&args, ErrorKind::InvalidSubcommand)
.expect("expected Athena-specific cargo build hint");
assert!(hint.contains("cargo build --bin athena"));
assert!(hint.contains("athena --help"));
}
#[test]
fn normal_cli_args_do_not_receive_build_hint() {
let args = vec!["athena_rs".to_string(), "server".to_string()];
assert!(cli_parse_hint(&args, ErrorKind::InvalidSubcommand).is_none());
}
#[test]
fn tracing_permission_hint_mentions_env_override() {
let hint = tracing_log_permission_hint(
Path::new("/var/log/athena"),
Some(Path::new("./var/log/athena")),
)
.expect("expected permission hint");
assert!(hint.contains(TRACING_LOG_DIR_ENV));
#[cfg(unix)]
assert!(hint.contains("sudo mkdir -p"));
#[cfg(windows)]
assert!(hint.contains("icacls"));
}
}