use std::env;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct PromptFormatter {
r_version: String,
shell_name: String,
}
impl PromptFormatter {
pub fn new() -> Self {
let r_version = get_r_version();
let shell_name = get_shell_name();
Self {
r_version,
shell_name,
}
}
pub fn format(&self, template: &str) -> String {
let mut result = template.to_string();
result = result.replace("{version}", &self.r_version);
result = result.replace("{shell}", &self.shell_name);
if result.contains("{cwd}") || result.contains("{cwd_short}") {
let cwd = get_cwd();
let cwd_short = get_cwd_short(&cwd);
result = result.replace("{cwd}", &cwd);
result = result.replace("{cwd_short}", &cwd_short);
}
result
}
}
impl Default for PromptFormatter {
fn default() -> Self {
Self::new()
}
}
pub fn get_r_version() -> String {
if arf_libr::r_library().is_err() {
return String::new();
}
match arf_harp::eval_string(r#"invisible(paste0(R.version$major, ".", R.version$minor))"#) {
Ok(result) => extract_string(result.sexp()).unwrap_or_default(),
Err(_) => String::new(),
}
}
fn extract_string(sexp: arf_libr::SEXP) -> Option<String> {
let lib = arf_libr::r_library().ok()?;
unsafe {
if (lib.rf_isstring)(sexp) == 0 || (lib.rf_length)(sexp) == 0 {
return None;
}
let elt = (lib.string_elt)(sexp, 0);
let cstr = (lib.r_charsxp)(elt);
if cstr.is_null() {
return None;
}
std::ffi::CStr::from_ptr(cstr)
.to_str()
.ok()
.map(|s| s.to_string())
}
}
fn get_cwd() -> String {
env::current_dir()
.map(|p| p.display().to_string())
.unwrap_or_else(|_| "?".to_string())
}
fn get_cwd_short(cwd: &str) -> String {
Path::new(cwd)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(cwd)
.to_string()
}
fn get_shell_name() -> String {
#[cfg(windows)]
{
"cmd".to_string()
}
#[cfg(not(windows))]
{
env::var("SHELL")
.ok()
.and_then(|shell_path| {
Path::new(&shell_path)
.file_name()
.and_then(|n| n.to_str())
.map(|s| s.to_string())
})
.unwrap_or_else(|| "sh".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_placeholders() {
let formatter = PromptFormatter {
r_version: "4.4.0".to_string(),
shell_name: "bash".to_string(),
};
assert_eq!(formatter.format("r> "), "r> ");
assert_eq!(formatter.format("+ "), "+ ");
}
#[test]
fn test_version_placeholder() {
let formatter = PromptFormatter {
r_version: "4.4.0".to_string(),
shell_name: "bash".to_string(),
};
assert_eq!(formatter.format("R {version}> "), "R 4.4.0> ");
assert_eq!(formatter.format("[{version}] r> "), "[4.4.0] r> ");
}
#[test]
fn test_cwd_placeholders() {
let formatter = PromptFormatter {
r_version: "4.4.0".to_string(),
shell_name: "bash".to_string(),
};
let result = formatter.format("{cwd}> ");
assert!(result.ends_with("> "));
let result = formatter.format("{cwd_short}> ");
assert!(result.ends_with("> "));
}
#[test]
fn test_shell_placeholder() {
let formatter = PromptFormatter {
r_version: "4.4.0".to_string(),
shell_name: "zsh".to_string(),
};
assert_eq!(formatter.format("[{shell}] $ "), "[zsh] $ ");
assert_eq!(formatter.format("{shell}> "), "zsh> ");
}
}