use serde_json::{json, Map, Value};
pub fn environment(environ: &Map<String, Value>, variable: &str) -> Option<String> {
environ
.get(variable)
.and_then(|v| v.as_str().map(String::from))
}
pub fn virtualenv(
environ: &Map<String, Value>,
ignore_venv: bool,
ignore_conda: bool,
ignored_names: &[&str],
) -> Option<String> {
if !ignore_venv {
let raw = environ
.get("VIRTUAL_ENV")
.and_then(|v| v.as_str())
.unwrap_or("");
for candidate in raw.split('/').rev() {
if !candidate.is_empty() && !ignored_names.contains(&candidate) {
return Some(candidate.to_string());
}
}
}
if !ignore_conda {
let raw = environ
.get("CONDA_DEFAULT_ENV")
.and_then(|v| v.as_str())
.unwrap_or("");
for candidate in raw.split('/').rev() {
if !candidate.is_empty() && !ignored_names.contains(&candidate) {
return Some(candidate.to_string());
}
}
}
None
}
pub fn get_shortened_path(
cwd_result: std::io::Result<String>,
home: Option<&str>,
shorten_home: bool,
) -> Result<String, std::io::Error> {
let path = match cwd_result {
Ok(p) => p,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok("[not found]".to_string());
}
Err(e) => return Err(e),
};
if shorten_home {
if let Some(h) = home {
if !h.is_empty() && path.starts_with(h) {
return Ok(format!("~{}", &path[h.len()..]));
}
}
}
Ok(path)
}
pub fn cwd_segments(
cwd: &str,
dir_shorten_len: Option<usize>,
dir_limit_depth: Option<usize>,
use_path_separator: bool,
ellipsis: Option<&str>,
) -> Vec<Value> {
let sep = std::path::MAIN_SEPARATOR;
let cwd_split: Vec<&str> = cwd.split(sep).collect();
let cwd_split_len = cwd_split.len();
let mut parts: Vec<String> = if cwd_split.is_empty() {
Vec::new()
} else {
let last_idx = cwd_split_len - 1;
let mut v: Vec<String> = cwd_split[..last_idx]
.iter()
.map(|i| {
if let Some(n) = dir_shorten_len {
if !i.is_empty() && n > 0 {
i.chars().take(n).collect()
} else {
(*i).to_string()
}
} else {
(*i).to_string()
}
})
.collect();
v.push(cwd_split[last_idx].to_string());
v
};
if let Some(depth) = dir_limit_depth {
if cwd_split_len > depth + 1 {
let drop_count = parts.len() - depth;
parts.drain(..drop_count);
if let Some(e) = ellipsis {
parts.insert(0, e.to_string());
}
}
}
if let Some(first) = parts.first_mut() {
if first.is_empty() {
*first = sep.to_string();
}
}
let draw_inner_divider = !use_path_separator;
let mut ret: Vec<Value> = Vec::new();
for part in &parts {
if part.is_empty() {
continue;
}
let contents = if use_path_separator {
format!("{}{}", part, sep)
} else {
part.clone()
};
ret.push(json!({
"contents": contents,
"divider_highlight_group": "cwd:divider",
"draw_inner_divider": draw_inner_divider,
}));
}
if let Some(last) = ret.last_mut() {
last["highlight_groups"] = json!(["cwd:current_folder", "cwd"]);
}
if use_path_separator {
if let Some(last) = ret.last_mut() {
let s = last["contents"].as_str().unwrap_or("").to_string();
let trimmed: String = s.chars().take(s.chars().count() - 1).collect();
last["contents"] = json!(trimmed);
}
if ret.len() > 1 {
let first = ret[0]["contents"].as_str().unwrap_or("").to_string();
if let Some(first_char) = first.chars().next() {
if first_char == sep {
let stripped: String = first.chars().skip(1).collect();
ret[0]["contents"] = json!(stripped);
}
}
}
}
ret
}
pub fn _get_user(environ: &Map<String, Value>) -> Option<String> {
environ
.get("USER")
.and_then(|v| v.as_str().map(String::from))
.or_else(|| std::env::var("USER").ok())
}
pub const POWERLINE_TEST_USER_UUID: &str = "ee5bcdc6-b749-11e7-9456-50465d597777";
#[derive(Debug, Clone, Copy, Default)]
pub struct CwdSegment;
impl CwdSegment {
pub fn argspecobjs(&self) -> Vec<(&'static str, &'static str)> {
vec![("get_shortened_path", "get_shortened_path")]
}
pub fn omitted_args(&self, method: &str) -> Vec<&'static str> {
if method == "get_shortened_path" {
Vec::new()
} else {
Vec::new()
}
}
}
pub fn _geteuid() -> u32 {
#[cfg(unix)]
{
unsafe { libc::geteuid() as u32 }
}
#[cfg(not(unix))]
{
1
}
}
pub fn user(
environ: &Map<String, Value>,
hide_user: Option<&str>,
hide_domain: bool,
euid: u32,
) -> Option<Vec<Value>> {
if let Some(test_uuid) = environ
.get("_POWERLINE_RUNNING_SHELL_TESTS")
.and_then(|v| v.as_str())
{
if test_uuid == POWERLINE_TEST_USER_UUID {
return Some(vec![json!({
"contents": "user",
})]);
}
}
let mut username = _get_user(environ)?;
if let Some(hu) = hide_user {
if username == hu {
return None;
}
}
if hide_domain {
if let Some(idx) = username.find('@') {
username.truncate(idx);
}
}
let groups: Vec<Value> = if euid == 0 {
vec![
Value::String("superuser".into()),
Value::String("user".into()),
]
} else {
vec![Value::String("user".into())]
};
Some(vec![json!({
"contents": username,
"highlight_groups": groups,
})])
}
#[cfg(test)]
mod tests {
use super::*;
fn env_with(pairs: &[(&str, &str)]) -> Map<String, Value> {
let mut m = Map::new();
for (k, v) in pairs {
m.insert(k.to_string(), Value::String((*v).into()));
}
m
}
#[test]
fn environment_returns_value_when_set() {
let env = env_with(&[("FOO", "bar")]);
assert_eq!(environment(&env, "FOO"), Some("bar".to_string()));
}
#[test]
fn environment_returns_none_when_unset() {
let env = Map::new();
assert!(environment(&env, "FOO").is_none());
}
#[test]
fn virtualenv_returns_last_path_component_from_virtual_env() {
let env = env_with(&[("VIRTUAL_ENV", "/path/to/myenv")]);
assert_eq!(
virtualenv(&env, false, false, &["venv", ".venv"]),
Some("myenv".to_string())
);
}
#[test]
fn virtualenv_skips_ignored_names_and_walks_to_parent() {
let env = env_with(&[("VIRTUAL_ENV", "/path/to/myproj/venv")]);
assert_eq!(
virtualenv(&env, false, false, &["venv", ".venv"]),
Some("myproj".to_string())
);
}
#[test]
fn virtualenv_falls_back_to_conda_when_no_virtualenv() {
let env = env_with(&[("CONDA_DEFAULT_ENV", "/conda/envs/datasci")]);
assert_eq!(
virtualenv(&env, false, false, &["venv", ".venv"]),
Some("datasci".to_string())
);
}
#[test]
fn virtualenv_ignore_venv_skips_virtual_env() {
let env = env_with(&[
("VIRTUAL_ENV", "/path/myenv"),
("CONDA_DEFAULT_ENV", "/conda/datasci"),
]);
assert_eq!(
virtualenv(&env, true, false, &[]),
Some("datasci".to_string())
);
}
#[test]
fn virtualenv_no_env_returns_none() {
let env = Map::new();
assert!(virtualenv(&env, false, false, &[]).is_none());
}
#[test]
fn get_shortened_path_not_found_returns_sentinel() {
let r = get_shortened_path(
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "x")),
Some("/home/user"),
true,
);
assert_eq!(r.unwrap(), "[not found]");
}
#[test]
fn get_shortened_path_shortens_home_prefix() {
let r = get_shortened_path(
Ok("/home/user/projects".to_string()),
Some("/home/user"),
true,
);
assert_eq!(r.unwrap(), "~/projects");
}
#[test]
fn get_shortened_path_no_home_returns_full_path() {
let r = get_shortened_path(Ok("/home/user/projects".to_string()), None, true);
assert_eq!(r.unwrap(), "/home/user/projects");
}
#[test]
fn get_shortened_path_shorten_home_false_passes_through() {
let r = get_shortened_path(
Ok("/home/user/projects".to_string()),
Some("/home/user"),
false,
);
assert_eq!(r.unwrap(), "/home/user/projects");
}
#[test]
fn cwd_segments_root_path() {
let r = cwd_segments("/", None, None, false, Some("..."));
assert_eq!(r.len(), 1);
assert_eq!(r[0]["contents"], "/");
assert_eq!(
r[0]["highlight_groups"],
json!(["cwd:current_folder", "cwd"])
);
}
#[test]
fn cwd_segments_multi_component() {
let r = cwd_segments("/a/b/c", None, None, false, Some("..."));
assert_eq!(r.len(), 4);
assert_eq!(r[0]["contents"], "/");
assert_eq!(r[3]["contents"], "c");
assert!(r[0].get("highlight_groups").is_none());
assert_eq!(
r[3]["highlight_groups"],
json!(["cwd:current_folder", "cwd"])
);
}
#[test]
fn cwd_segments_dir_shorten_len_truncates_non_leaf() {
let r = cwd_segments("/very/long/path/to/proj", Some(2), None, false, Some("..."));
let texts: Vec<String> = r
.iter()
.map(|s| s["contents"].as_str().unwrap().to_string())
.collect();
assert_eq!(texts, vec!["/", "ve", "lo", "pa", "to", "proj"]);
}
#[test]
fn cwd_segments_dir_limit_depth_adds_ellipsis() {
let r = cwd_segments("/very/long/path/to/proj", None, Some(2), false, Some("..."));
let texts: Vec<String> = r
.iter()
.map(|s| s["contents"].as_str().unwrap().to_string())
.collect();
assert_eq!(texts, vec!["...", "to", "proj"]);
}
#[test]
fn cwd_segments_use_path_separator_appends_to_non_last_and_strips_last() {
let r = cwd_segments("/a/b", None, None, true, Some("..."));
let last_text = r.last().unwrap()["contents"].as_str().unwrap();
assert_eq!(last_text, "b");
}
#[test]
fn cwd_segments_divider_group_set_on_all() {
let r = cwd_segments("/a/b", None, None, false, Some("..."));
for s in &r {
assert_eq!(s["divider_highlight_group"], "cwd:divider");
}
}
#[test]
fn cwd_segments_draw_inner_divider_inverse_of_use_path_separator() {
let r = cwd_segments("/a", None, None, false, Some("..."));
assert_eq!(r[0]["draw_inner_divider"], true);
let r2 = cwd_segments("/a", None, None, true, Some("..."));
assert_eq!(r2[0]["draw_inner_divider"], false);
}
#[test]
fn cwd_segments_empty_string_yields_root_segment() {
let r = cwd_segments("", None, None, false, Some("..."));
assert_eq!(r.len(), 1);
assert_eq!(r[0]["contents"], "/");
}
#[test]
fn user_test_uuid_returns_user_verbatim() {
let env = env_with(&[("_POWERLINE_RUNNING_SHELL_TESTS", POWERLINE_TEST_USER_UUID)]);
let r = user(&env, None, false, 1000).unwrap();
assert_eq!(r[0]["contents"], "user");
}
#[test]
fn user_returns_username_with_user_highlight_for_non_root() {
let env = env_with(&[("USER", "alice")]);
let r = user(&env, None, false, 1000).unwrap();
assert_eq!(r[0]["contents"], "alice");
assert_eq!(r[0]["highlight_groups"], json!(["user"]));
}
#[test]
fn user_root_gets_superuser_highlight() {
let env = env_with(&[("USER", "root")]);
let r = user(&env, None, false, 0).unwrap();
assert_eq!(r[0]["highlight_groups"], json!(["superuser", "user"]));
}
#[test]
fn user_hidden_returns_none() {
let env = env_with(&[("USER", "alice")]);
assert!(user(&env, Some("alice"), false, 1000).is_none());
}
#[test]
fn user_hide_domain_strips_at_suffix() {
let env = env_with(&[("USER", "alice@example.com")]);
let r = user(&env, None, true, 1000).unwrap();
assert_eq!(r[0]["contents"], "alice");
}
#[test]
fn user_hide_domain_no_at_passes_through() {
let env = env_with(&[("USER", "alice")]);
let r = user(&env, None, true, 1000).unwrap();
assert_eq!(r[0]["contents"], "alice");
}
#[test]
fn get_user_reads_environ_user_variable() {
let env = env_with(&[("USER", "bob")]);
assert_eq!(_get_user(&env), Some("bob".to_string()));
}
#[test]
fn powerline_test_user_uuid_matches_upstream() {
assert_eq!(
POWERLINE_TEST_USER_UUID,
"ee5bcdc6-b749-11e7-9456-50465d597777"
);
}
#[test]
fn cwd_segment_argspecobjs_yields_get_shortened_path() {
let s = CwdSegment;
let entries = s.argspecobjs();
assert!(entries
.iter()
.any(|(name, _)| *name == "get_shortened_path"));
}
#[test]
fn cwd_segment_omitted_args_for_get_shortened_path_is_empty() {
let s = CwdSegment;
assert!(s.omitted_args("get_shortened_path").is_empty());
}
#[test]
fn cwd_segment_omitted_args_for_unknown_method_is_empty() {
let s = CwdSegment;
assert!(s.omitted_args("anything_else").is_empty());
}
#[test]
fn geteuid_returns_non_negative() {
let uid = _geteuid();
let _ = uid;
}
}