use crate::extensions::p10k::config::{p9k_global, p9k_global_arr, p9k_param};
use crate::extensions::p10k::icons;
use crate::extensions::p10k::render::Segment;
use crate::ported::params::getsparam;
use crate::ported::utils::getkeystring;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant, UNIX_EPOCH};
pub fn build_segment(name: &str) -> Option<Vec<Segment>> {
match name {
"user" => Some(user_segments()), "host" => Some(host_segments()), "date" => Some(date_segments()), "public_ip" => Some(public_ip_segments()), "vpn_ip" => Some(vpn_ip_segments()), "detect_virt" => Some(detect_virt_segments()), "toolbox" => Some(toolbox_segments()), "xplr" => Some(xplr_segments()), "docker_machine" => Some(docker_machine_segments()), "dropbox" => Some(dropbox_segments()), "openfoam" => Some(openfoam_segments()), "chruby" => Some(chruby_segments()), "dotnet_version" => Some(dotnet_version_segments()), "php_version" => Some(php_version_segments()), "swift_version" => Some(swift_version_segments()), "laravel_version" => Some(laravel_version_segments()), "terraform_version" => Some(terraform_version_segments()), "symfony2_version" => Some(symfony2_version_segments()), "rspec_stats" => Some(rspec_stats_segments()), "symfony2_tests" => Some(symfony2_tests_segments()), _ => None,
}
}
fn color1() -> &'static str {
if p9k_global("COLOR_SCHEME", "dark") == "light" {
"7"
} else {
"0"
}
}
fn color2() -> &'static str {
if p9k_global("COLOR_SCHEME", "dark") == "light" {
"0"
} else {
"7"
}
}
fn env_or_param(name: &str) -> String {
if let Some(v) = getsparam(name) {
return v;
}
std::env::var(name).unwrap_or_default()
}
fn global_bool(name: &str, default: bool) -> bool {
match getsparam(&format!("POWERLEVEL9K_{name}")) {
Some(v) => v == "true",
None => default,
}
}
fn global_float(name: &str, default: f64) -> f64 {
getsparam(&format!("POWERLEVEL9K_{name}"))
.and_then(|v| v.trim().parse::<f64>().ok())
.unwrap_or(default)
}
fn esc_pct(s: &str) -> String {
s.replace('%', "%%")
}
fn decode_g(s: &str) -> String {
getkeystring(s).0
}
fn seg_icon(segment: &str, state: Option<&str>, key: &str) -> String {
let probed = p9k_param(segment, state, key, "\u{1}");
if probed == "\u{1}" {
return icons::icon(key).to_string();
}
let decoded = decode_g(&probed);
let mut ch = decoded.chars();
if ch.next() == Some('\u{8}') && ch.next().is_some() && ch.next().is_none() {
return format!("%{{{decoded}%}}");
}
decoded
}
fn apply_visual_identifier(segment: &str, state: Option<&str>, icon: String) -> Option<String> {
let exp = p9k_param(
segment,
state,
"VISUAL_IDENTIFIER_EXPANSION",
"${P9K_VISUAL_IDENTIFIER}",
);
let resolved = if exp == "${P9K_VISUAL_IDENTIFIER}" {
icon
} else if !exp.contains('$') {
exp
} else {
tracing::debug!(
target: "p10k",
segment,
?state,
%exp,
"dynamic VISUAL_IDENTIFIER_EXPANSION unported — using resolved icon"
);
icon
};
if resolved.is_empty() {
None
} else {
Some(resolved)
}
}
fn apply_content_expansion(segment: &str, state: Option<&str>, content: String) -> String {
let exp = p9k_param(segment, state, "CONTENT_EXPANSION", "${P9K_CONTENT}");
if exp == "${P9K_CONTENT}" {
return content;
}
if !exp.contains('$') {
return exp;
}
if exp.contains("${P9K_CONTENT}") {
let out = exp.replace("${P9K_CONTENT}", &content);
if out.contains('$') {
tracing::debug!(
target: "p10k",
segment, ?state, %exp,
"CONTENT_EXPANSION has unevaluated expansions beyond ${{P9K_CONTENT}}"
);
}
return out;
}
tracing::debug!(
target: "p10k",
segment, ?state, %exp,
"dynamic CONTENT_EXPANSION unported — using segment content"
);
content
}
fn make_segment(
name: &str,
state: Option<&str>,
default_bg: &str,
default_fg: &str,
icon_key: &str,
content: String,
) -> Segment {
let bg = p9k_param(name, state, "BACKGROUND", default_bg);
let fg = p9k_param(name, state, "FOREGROUND", default_fg);
let icon_glyph = if icon_key.is_empty() {
String::new()
} else {
seg_icon(name, state, icon_key)
};
let icon = apply_visual_identifier(name, state, icon_glyph);
let content = apply_content_expansion(name, state, content);
Segment {
name: name.to_string(),
state: state.map(|s| s.to_string()),
content,
icon,
fg,
bg,
}
}
fn cmd_on_path(name: &str) -> Option<PathBuf> {
let path_var = env_or_param("PATH");
for dir in path_var.split(':').filter(|d| !d.is_empty()) {
let cand = Path::new(dir).join(name);
let is_exec = std::fs::metadata(&cand)
.map(|md| {
use std::os::unix::fs::PermissionsExt;
md.is_file() && md.permissions().mode() & 0o111 != 0
})
.unwrap_or(false);
if is_exec {
return Some(cand);
}
}
None
}
fn cwd() -> PathBuf {
let pwd = env_or_param("PWD");
if !pwd.is_empty() {
let p = PathBuf::from(&pwd);
if p.is_absolute() {
return p;
}
}
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"))
}
fn is_ssh() -> bool {
!env_or_param("SSH_CLIENT").is_empty()
|| !env_or_param("SSH_TTY").is_empty()
|| !env_or_param("SSH_CONNECTION").is_empty()
}
fn is_root() -> bool {
unsafe { libc::geteuid() == 0 }
}
fn path_readable(path: &Path) -> bool {
use std::os::unix::ffi::OsStrExt;
match std::ffi::CString::new(path.as_os_str().as_bytes()) {
Ok(c) => unsafe { libc::access(c.as_ptr(), libc::R_OK) == 0 },
Err(_) => false,
}
}
fn hostname_short() -> String {
let host = env_or_param("HOST");
if !host.is_empty() {
return host.split('.').next().unwrap_or("").to_string();
}
let mut buf = [0u8; 256];
if unsafe { libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len()) } != 0 {
return String::new();
}
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
let full = String::from_utf8_lossy(&buf[..end]).into_owned();
full.split('.').next().unwrap_or("").to_string()
}
fn up_ipv4_interfaces() -> Vec<(String, String)> {
let mut out: Vec<(String, String)> = Vec::new();
let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
if unsafe { libc::getifaddrs(&mut ifap) } != 0 {
tracing::warn!(target: "p10k", "getifaddrs failed for vpn_ip/public_ip scan");
return out;
}
let mut cur = ifap;
while !cur.is_null() {
let ifa = unsafe { &*cur };
cur = ifa.ifa_next;
if ifa.ifa_flags & (libc::IFF_UP as u32) == 0 {
continue;
}
if ifa.ifa_addr.is_null() {
continue;
}
let sa = unsafe { &*ifa.ifa_addr };
if i32::from(sa.sa_family) != libc::AF_INET {
continue;
}
let sin = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_in) };
let ip = std::net::Ipv4Addr::from(u32::from_be(sin.sin_addr.s_addr)).to_string();
let name = unsafe { std::ffi::CStr::from_ptr(ifa.ifa_name) }
.to_string_lossy()
.into_owned();
if !out.iter().any(|(n, _)| *n == name) {
out.push((name, ip));
}
}
unsafe { libc::freeifaddrs(ifap) };
out
}
fn match_interfaces(pattern: &str) -> Vec<(String, String)> {
if pattern.is_empty() {
return Vec::new();
}
let re = match regex::Regex::new(&format!("^({pattern})$")) {
Ok(r) => r,
Err(e) => {
tracing::warn!(target: "p10k", %pattern, %e, "bad interface regex");
return Vec::new();
}
};
up_ipv4_interfaces()
.into_iter()
.filter(|(name, _)| re.is_match(name))
.collect()
}
static TTL_CACHE: OnceLock<Mutex<HashMap<String, (Instant, Option<String>)>>> = OnceLock::new();
fn cached_ttl(key: &str, run: impl FnOnce() -> (Duration, Option<String>)) -> Option<String> {
let m = TTL_CACHE.get_or_init(Default::default);
if let Ok(guard) = m.lock() {
if let Some((expiry, val)) = guard.get(key) {
if Instant::now() < *expiry {
return val.clone();
}
}
}
let (ttl, val) = run();
let expiry = Instant::now()
.checked_add(ttl)
.unwrap_or_else(|| Instant::now() + Duration::from_secs(3600));
if let Ok(mut guard) = m.lock() {
guard.insert(key.to_string(), (expiry, val.clone()));
}
val
}
fn run_tool_budget(
bin: &Path,
args: &[&str],
dir: Option<&Path>,
budget: Duration,
require_success: bool,
) -> Option<String> {
let mut cmd = std::process::Command::new(bin);
cmd.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null());
if let Some(d) = dir {
cmd.current_dir(d);
}
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => {
tracing::debug!(target: "p10k", bin = %bin.display(), %e, "tool spawn failed");
return None;
}
};
let mut stdout = child.stdout.take()?;
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let mut out = Vec::new();
use std::io::Read;
let _ = stdout.read_to_end(&mut out);
let _ = tx.send(out);
});
match rx.recv_timeout(budget) {
Ok(out) => {
let ok = child.wait().map(|s| s.success()).unwrap_or(false);
if require_success && !ok {
return None;
}
Some(String::from_utf8_lossy(&out).into_owned())
}
Err(_) => {
tracing::debug!(
target: "p10k",
bin = %bin.display(), ?budget,
"tool exceeded budget — killed"
);
let _ = child.kill();
let _ = child.wait();
None
}
}
}
fn mtime(path: &Path) -> i64 {
std::fs::metadata(path)
.and_then(|m| m.modified())
.ok()
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|d| d.as_secs() as i64)
.unwrap_or(-1)
}
type StatSig = Vec<(PathBuf, i64)>;
static STAT_CACHE: OnceLock<Mutex<HashMap<String, (StatSig, Option<String>)>>> = OnceLock::new();
fn stat_cached(
key: &str,
files: &[PathBuf],
run: impl FnOnce() -> Option<String>,
) -> Option<String> {
let sig: StatSig = files.iter().map(|f| (f.clone(), mtime(f))).collect();
let m = STAT_CACHE.get_or_init(Default::default);
if let Ok(guard) = m.lock() {
if let Some((cached_sig, val)) = guard.get(key) {
if *cached_sig == sig {
return val.clone();
}
}
}
let val = run();
if let Ok(mut guard) = m.lock() {
guard.insert(key.to_string(), (sig, val.clone()));
}
val
}
fn cached_cmd(cmd: &str, args: &[&str]) -> Option<String> {
let bin = cmd_on_path(cmd)?;
let key = format!("cached_cmd {cmd} {}", args.join(" "));
let out = stat_cached(&key, &[bin.clone()], || {
run_tool_budget(&bin, args, None, Duration::from_secs(5), true)
.map(|s| s.trim_end_matches('\n').to_string())
})?;
if out.is_empty() { None } else { Some(out) }
}
fn upfind(name: &str) -> Option<PathBuf> {
upfind_pred(&|dir: &Path| dir.join(name).exists())
}
fn upfind_pred(pred: &dyn Fn(&Path) -> bool) -> Option<PathBuf> {
let start = cwd();
let mut dir: &Path = &start;
loop {
if pred(dir) {
return Some(dir.to_path_buf());
}
match dir.parent() {
Some(p) => dir = p,
None => return None,
}
}
}
fn dir_has_ext(dir: &Path, exts: &[&str]) -> bool {
let Ok(rd) = std::fs::read_dir(dir) else { return false };
for entry in rd.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if name.starts_with('.') {
continue;
}
if let Some(ext) = name.rsplit('.').next() {
if name.contains('.') && exts.contains(&ext) {
return true;
}
}
}
false
}
fn glob_recursive(base: &Path, dir: &Path, suffix: &str, out: &mut Vec<PathBuf>) {
let Ok(rd) = std::fs::read_dir(dir) else { return };
for entry in rd.flatten() {
let name = entry.file_name();
let name_s = name.to_string_lossy().into_owned();
if name_s.starts_with('.') {
continue;
}
let path = entry.path();
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
glob_recursive(base, &path, suffix, out);
}
if name_s.ends_with(suffix) {
out.push(path.strip_prefix(base).unwrap_or(&path).to_path_buf());
}
}
}
fn php_version_extract(out: &str) -> Option<String> {
for line in out.lines() {
if let Some(rest) = line.strip_prefix("PHP ") {
let run: String = rest
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.')
.collect();
if !run.is_empty() {
return Some(run);
}
}
}
None
}
fn swift_version_extract(out: &str) -> Option<String> {
let start = out.find(|c: char| c.is_ascii_digit())?;
Some(
out[start..]
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.')
.collect(),
)
}
fn terraform_version_extract(out: &str) -> Option<String> {
let v = out.strip_prefix("Terraform v")?; let v = v.split('\n').next().unwrap_or(v); if v.is_empty() { None } else { Some(v.to_string()) } }
fn laravel_version_extract(v: &str) -> Option<String> {
v.strip_prefix("Laravel Framework ").map(str::to_string)
}
fn symfony2_version_extract(content: &str) -> String {
content
.lines()
.filter(|l| l.contains(" VERSION "))
.collect::<Vec<_>>()
.join("\n")
.chars()
.filter(|c| c.is_ascii_digit() || *c == '.')
.collect()
}
fn test_stats(
code: usize,
tests: usize,
headline: &str,
) -> Option<(&'static str, &'static str, String)> {
if code == 0 {
return None; }
let ratio = 100.0 * tests as f64 / code as f64;
let (state, bg) = if ratio >= 75.0 {
("GOOD", "cyan") } else if ratio >= 50.0 {
("AVG", "yellow") } else {
("BAD", "red") };
Some((state, bg, format!("{headline}: {ratio:.2}%%")))
}
fn valid_public_ip(ip: &str) -> bool {
!ip.is_empty()
&& ip
.bytes()
.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f' | b'.' | b':'))
}
fn dropbox_cut_status(out: &str) -> String {
let first = out.lines().next().unwrap_or("");
match first.split_once(' ') {
Some((_, rest)) => rest.to_string(),
None => first.to_string(),
}
}
fn containerenv_toolbox_name(content: &str) -> Option<String> {
let mut names = content.lines().filter_map(|l| l.strip_prefix("name="));
let name = names.next()?;
if names.next().is_some() {
return None; }
let unq = if name.len() >= 2
&& ((name.starts_with('"') && name.ends_with('"'))
|| (name.starts_with('\'') && name.ends_with('\'')))
{
&name[1..name.len() - 1]
} else {
name
};
if unq.is_empty() { None } else { Some(unq.to_string()) }
}
fn user_segments() -> Vec<Segment> {
let user = getsparam("USERNAME").unwrap_or_default();
if !global_bool("ALWAYS_SHOW_USER", false) && user == env_or_param("DEFAULT_USER") {
return vec![];
}
let template = decode_g(&p9k_global("USER_TEMPLATE", "%n"));
if is_root() {
vec![make_segment(
"user",
Some("ROOT"),
color1(),
"yellow",
"ROOT_ICON",
template,
)]
} else if !env_or_param("SUDO_COMMAND").is_empty() {
vec![make_segment(
"user",
Some("SUDO"),
color1(),
"yellow",
"SUDO_ICON",
template,
)]
} else {
vec![make_segment(
"user",
Some("DEFAULT"),
color1(),
"yellow",
"USER_ICON",
"%n".to_string(),
)]
}
}
fn host_segments() -> Vec<Segment> {
let template = decode_g(&p9k_global("HOST_TEMPLATE", "%m"));
if is_ssh() {
vec![make_segment(
"host",
Some("REMOTE"),
color1(),
"yellow",
"SSH_ICON",
template,
)]
} else {
vec![make_segment(
"host",
Some("LOCAL"),
color1(),
"yellow",
"HOST_ICON",
template,
)]
}
}
fn date_segments() -> Vec<Segment> {
let fmt = decode_g(&p9k_global("DATE_FORMAT", "%D{%d.%m.%y}"));
vec![make_segment("date", None, color2(), color1(), "DATE_ICON", fmt)]
}
fn fetch_public_ip(method: &str, host: &str) -> Option<String> {
let fetched = match method {
"dig" => {
let bin = cmd_on_path("dig")?;
let dig = |family: &str, rr: &str| {
run_tool_budget(
&bin,
&[
"+tries=1",
"+short",
family,
rr,
"myip.opendns.com",
"@resolver1.opendns.com",
],
None,
Duration::from_secs(6),
true,
)
.map(|s| s.trim_end_matches('\n').to_string())
.filter(|s| !s.is_empty() && !s.starts_with(';')) };
dig("-4", "A").or_else(|| dig("-6", "AAAA"))
}
"curl" => run_tool_budget(
&cmd_on_path("curl")?,
&["--max-time", "5", "-w", "\n", host],
None,
Duration::from_secs(6),
true,
)
.map(|s| s.trim_end_matches('\n').to_string()),
"wget" => run_tool_budget(
&cmd_on_path("wget")?,
&["-T", "5", "-qO-", host],
None,
Duration::from_secs(6),
true,
)
.map(|s| s.trim_end_matches('\n').to_string()),
_ => None,
}?;
if valid_public_ip(&fetched) { Some(fetched) } else { None }
}
fn public_ip_segments() -> Vec<Segment> {
let timeout = match global_float("PUBLIC_IP_TIMEOUT", 300.0) {
t if t.is_finite() => t.clamp(5.0, 31_536_000.0),
_ => 300.0,
};
let ip = cached_ttl("public_ip", || {
let mut methods = p9k_global_arr("PUBLIC_IP_METHODS");
if methods.is_empty() {
methods = vec!["dig".into(), "curl".into(), "wget".into()];
}
let host = p9k_global("PUBLIC_IP_HOST", "https://v4.ident.me/");
for method in &methods {
if let Some(ip) = fetch_public_ip(method, &host) {
return (Duration::from_secs_f64(timeout), Some(ip)); }
}
(Duration::from_secs(5), None) })
.unwrap_or_default();
let text = if ip.is_empty() {
decode_g(&p9k_global("PUBLIC_IP_NONE", ""))
} else {
ip
};
if text.is_empty() {
return vec![]; }
let vpn_pat = p9k_global("PUBLIC_IP_VPN_INTERFACE", ""); let icon_key = if !vpn_pat.is_empty() && !match_interfaces(&vpn_pat).is_empty() {
"VPN_ICON" } else {
"PUBLIC_IP_ICON" };
vec![make_segment(
"public_ip",
None,
color1(),
color2(),
icon_key,
esc_pct(&text),
)]
}
fn vpn_ip_segments() -> Vec<Segment> {
let pattern = p9k_global("VPN_IP_INTERFACE", "(gpd|wg|(.*tun)|tailscale)[0-9]*");
if pattern.is_empty() {
return vec![];
}
let matched = match_interfaces(&pattern);
let ips: Vec<String> = if global_bool("VPN_IP_SHOW_ALL", false) {
matched.into_iter().map(|(_, ip)| ip).collect()
} else {
matched.into_iter().take(1).map(|(_, ip)| ip).collect()
};
ips.into_iter()
.map(|ip| make_segment("vpn_ip", None, "cyan", color1(), "VPN_ICON", ip))
.collect()
}
fn detect_virt_segments() -> Vec<Segment> {
let Some(bin) = cmd_on_path("systemd-detect-virt") else {
return vec![];
};
let mut virt = cached_ttl("detect_virt", || {
(
Duration::from_secs(30),
run_tool_budget(&bin, &[], None, Duration::from_secs(2), false)
.map(|s| s.trim_end_matches('\n').to_string()),
)
})
.unwrap_or_default();
if virt == "none" {
use std::os::unix::fs::MetadataExt;
let inode = std::fs::metadata("/").map(|m| m.ino()).unwrap_or(2);
if inode != 2 {
virt = "chroot".to_string();
}
}
if virt.is_empty() {
return vec![];
}
vec![make_segment(
"detect_virt",
None,
color1(),
"yellow",
"",
esc_pct(&virt),
)]
}
fn toolbox_name() -> &'static str {
static NAME: OnceLock<String> = OnceLock::new();
NAME.get_or_init(|| {
let preset = env_or_param("P9K_TOOLBOX_NAME");
if !preset.is_empty() {
return preset;
}
if let Ok(content) = std::fs::read_to_string("/run/.containerenv") {
if let Some(name) = containerenv_toolbox_name(&content) {
return name;
}
}
if !env_or_param("DISTROBOX_ENTER_PATH").is_empty() {
let nm = env_or_param("NAME");
if !nm.is_empty() {
let host = hostname_short(); if host.starts_with(&nm) {
return host;
}
}
}
String::new()
})
}
fn toolbox_segments() -> Vec<Segment> {
let name = toolbox_name();
if name.is_empty() {
return vec![];
}
vec![make_segment(
"toolbox",
None,
color1(),
"yellow",
"TOOLBOX_ICON",
name.to_string(),
)]
}
fn xplr_segments() -> Vec<Segment> {
if env_or_param("XPLR_PID").is_empty() {
return vec![];
}
vec![make_segment(
"xplr",
None,
"6",
color1(),
"XPLR_ICON",
String::new(),
)]
}
fn docker_machine_segments() -> Vec<Segment> {
let name = env_or_param("DOCKER_MACHINE_NAME");
if name.is_empty() {
return vec![];
}
vec![make_segment(
"docker_machine",
None,
"magenta",
color1(),
"SERVER_ICON",
esc_pct(&name),
)]
}
fn dropbox_segments() -> Vec<Segment> {
let Some(bin) = cmd_on_path("dropbox-cli") else {
return vec![];
};
let dir = cwd();
let key = format!("dropbox {}", dir.display());
let Some(status) = cached_ttl(&key, || {
(
Duration::from_secs(10),
run_tool_budget(
&bin,
&["filestatus", "."],
Some(&dir),
Duration::from_secs(2),
false,
)
.map(|out| dropbox_cut_status(&out)),
)
}) else {
return vec![];
};
if status == "unwatched" || status == "isn't running!" {
return vec![];
}
let content = if status.contains("up to date") {
String::new()
} else {
esc_pct(&status)
};
vec![make_segment(
"dropbox",
None,
"white",
"blue",
"DROPBOX_ICON",
content,
)]
}
fn openfoam_segments() -> Vec<Segment> {
let version = env_or_param("WM_PROJECT_VERSION");
if version.is_empty() {
return vec![];
}
let tail = version.rsplit('/').next().unwrap_or(&version);
let content = if env_or_param("WM_FORK").is_empty() {
format!("OF: {}", esc_pct(tail)) } else {
format!("F-X: {}", esc_pct(tail)) };
vec![make_segment("openfoam", None, "yellow", color1(), "", content)]
}
fn chruby_segments() -> Vec<Segment> {
let engine = env_or_param("RUBY_ENGINE");
if engine.is_empty() {
return vec![];
}
let mut v = if global_bool("CHRUBY_SHOW_ENGINE", true) {
engine
} else {
String::new()
};
let ruby_version = env_or_param("RUBY_VERSION");
if global_bool("CHRUBY_SHOW_VERSION", true) && !ruby_version.is_empty() {
if !v.is_empty() {
v.push(' ');
}
v.push_str(&ruby_version);
}
vec![make_segment(
"chruby",
None,
"red",
color1(),
"RUBY_ICON",
esc_pct(&v),
)]
}
fn dotnet_version_segments() -> Vec<Segment> {
if global_bool("DOTNET_VERSION_PROJECT_ONLY", true) {
let found = upfind_pred(&|dir| {
["project.json", "global.json", "packet.dependencies"]
.iter()
.any(|f| dir.join(f).exists())
|| dir_has_ext(dir, &["csproj", "fsproj", "xproj", "sln"])
});
if found.is_none() {
return vec![];
}
}
let Some(v) = cached_cmd("dotnet", &["--version"]) else {
return vec![];
};
vec![make_segment(
"dotnet_version",
None,
"magenta",
"white",
"DOTNET_ICON",
v,
)]
}
fn php_version_segments() -> Vec<Segment> {
if global_bool("PHP_VERSION_PROJECT_ONLY", false) {
let found =
upfind_pred(&|dir| dir.join("composer.json").exists() || dir_has_ext(dir, &["php"]));
if found.is_none() {
return vec![];
}
}
let Some(out) = cached_cmd("php", &["--version"]) else {
return vec![];
};
let Some(v) = php_version_extract(&out) else {
return vec![];
};
vec![make_segment(
"php_version",
None,
"fuchsia",
"grey93",
"PHP_ICON",
esc_pct(&v),
)]
}
fn swift_version_segments() -> Vec<Segment> {
let Some(out) = cached_cmd("swift", &["--version"]) else {
return vec![];
};
let Some(v) = swift_version_extract(&out) else {
return vec![];
};
vec![make_segment(
"swift_version",
None,
"magenta",
"white",
"SWIFT_ICON",
esc_pct(&v),
)]
}
fn laravel_version_segments() -> Vec<Segment> {
let Some(php) = cmd_on_path("php") else {
return vec![];
};
let Some(dir) = upfind("artisan") else {
return vec![];
};
let artisan = dir.join("artisan");
let app = dir.join("vendor/laravel/framework/src/Illuminate/Foundation/Application.php");
if !path_readable(&app) {
return vec![];
}
let key = format!("laravel_version {}", dir.display());
let artisan_arg = artisan.display().to_string();
let ver = stat_cached(&key, &[artisan.clone(), app.clone()], || {
let out = run_tool_budget(
&php,
&[&artisan_arg, "--version"],
None,
Duration::from_secs(5),
false, )?;
laravel_version_extract(out.trim_end_matches('\n'))
});
let Some(v) = ver.filter(|v| !v.is_empty()) else {
return vec![];
};
vec![make_segment(
"laravel_version",
None,
"maroon",
"white",
"LARAVEL_ICON",
esc_pct(&v),
)]
}
fn terraform_version_segments() -> Vec<Segment> {
let Some(out) = cached_cmd("terraform", &["--version"]) else {
return vec![];
};
let Some(v) = terraform_version_extract(&out) else {
return vec![];
};
vec![make_segment(
"terraform_version",
None,
color1(),
"blue",
"TERRAFORM_ICON",
v,
)]
}
fn symfony2_version_segments() -> Vec<Segment> {
let file = cwd().join("app/bootstrap.php.cache");
if !path_readable(&file) {
return vec![];
}
let key = format!("symfony2_version {}", file.display());
let v = stat_cached(&key, &[file.clone()], || {
std::fs::read_to_string(&file)
.ok()
.map(|c| symfony2_version_extract(&c))
})
.unwrap_or_default();
vec![make_segment(
"symfony2_version",
None,
"grey35",
color1(),
"SYMFONY_ICON",
esc_pct(&v),
)]
}
fn build_test_stats_segments(
name: &str,
code: usize,
tests: usize,
headline: &str,
) -> Vec<Segment> {
let Some((state, bg, content)) = test_stats(code, tests, headline) else {
return vec![];
};
vec![make_segment(name, Some(state), bg, color1(), "TEST_ICON", content)]
}
fn rspec_stats_segments() -> Vec<Segment> {
let base = cwd();
if !base.join("app").is_dir() || !base.join("spec").is_dir() {
return vec![];
}
let mut code: Vec<PathBuf> = Vec::new();
glob_recursive(&base, &base.join("app"), ".rb", &mut code);
if code.is_empty() {
return vec![]; }
let mut tests: Vec<PathBuf> = Vec::new();
glob_recursive(&base, &base.join("spec"), ".rb", &mut tests);
build_test_stats_segments("rspec_stats", code.len(), tests.len(), "RSpec")
}
fn symfony2_tests_segments() -> Vec<Segment> {
let base = cwd();
if !base.join("src").is_dir()
|| !base.join("app").is_dir()
|| !base.join("app/AppKernel.php").is_file()
{
return vec![];
}
let mut all: Vec<PathBuf> = Vec::new();
glob_recursive(&base, &base.join("src"), ".php", &mut all);
let code = all
.iter()
.filter(|p| !p.to_string_lossy().contains("Tests"))
.count();
if code == 0 {
return vec![]; }
build_test_stats_segments("symfony2_tests", code, all.len() - code, "SF2")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn php_version_extract_forms() {
assert_eq!(
php_version_extract("PHP 8.3.7 (cli) (built: May 8 2024)").as_deref(),
Some("8.3.7")
);
assert_eq!(
php_version_extract("Deprecation warning\nPHP 7.4.33 (cli)").as_deref(),
Some("7.4.33")
);
assert_eq!(
php_version_extract("PHP warning: x\nPHP 8.0.1").as_deref(),
Some("8.0.1")
);
assert_eq!(php_version_extract("no version here"), None);
}
#[test]
fn swift_version_extract_forms() {
assert_eq!(
swift_version_extract("swift-driver version: 1.90.11.1 Apple Swift version 5.10")
.as_deref(),
Some("1.90.11.1")
);
assert_eq!(
swift_version_extract("Apple Swift version 5.9.2 (swiftlang…)").as_deref(),
Some("5.9.2")
);
assert_eq!(swift_version_extract("no digits"), None);
}
#[test]
fn terraform_version_extract_forms() {
assert_eq!(
terraform_version_extract("Terraform v1.7.5\non darwin_arm64").as_deref(),
Some("1.7.5")
);
assert_eq!(terraform_version_extract("OpenTofu v1.6.0"), None);
assert_eq!(terraform_version_extract("Terraform v"), None);
}
#[test]
fn laravel_version_extract_forms() {
assert_eq!(
laravel_version_extract("Laravel Framework 11.9.2").as_deref(),
Some("11.9.2")
);
assert_eq!(laravel_version_extract("Lumen (11.0.0)"), None);
}
#[test]
fn symfony2_version_extract_forms() {
assert_eq!(
symfony2_version_extract(" const VERSION = '2.8.52';\nother line"),
"2.8.52"
);
assert_eq!(symfony2_version_extract("no version marker"), "");
}
#[test]
fn test_stats_states_and_format() {
assert_eq!(test_stats(0, 10, "RSpec"), None); assert_eq!(
test_stats(4, 3, "RSpec"),
Some(("GOOD", "cyan", "RSpec: 75.00%%".to_string()))
);
assert_eq!(
test_stats(2, 1, "SF2"),
Some(("AVG", "yellow", "SF2: 50.00%%".to_string()))
);
assert_eq!(
test_stats(3, 1, "RSpec"),
Some(("BAD", "red", "RSpec: 33.33%%".to_string()))
);
}
#[test]
fn valid_public_ip_forms() {
assert!(valid_public_ip("93.184.216.34"));
assert!(valid_public_ip("2606:2800:220:1:248:1893:25c8:1946"));
assert!(!valid_public_ip(""));
assert!(!valid_public_ip(";; connection timed out"));
assert!(!valid_public_ip("93.184.216.34\nextra"));
assert!(!valid_public_ip("host.example.com")); }
#[test]
fn dropbox_cut_status_forms() {
assert_eq!(
dropbox_cut_status("/home/u/Dropbox: up to date"),
"up to date"
);
assert_eq!(
dropbox_cut_status("Dropbox isn't running!"),
"isn't running!"
);
assert_eq!(dropbox_cut_status("unwatched"), "unwatched");
assert_eq!(dropbox_cut_status(""), "");
}
#[test]
fn containerenv_toolbox_name_forms() {
assert_eq!(
containerenv_toolbox_name(
"engine=\"podman\"\nname=\"fedora-toolbox-39\"\nimage=\"x\""
)
.as_deref(),
Some("fedora-toolbox-39")
);
assert_eq!(
containerenv_toolbox_name("name=plain").as_deref(),
Some("plain")
);
assert_eq!(containerenv_toolbox_name("name=\"a\"\nname=\"b\""), None);
assert_eq!(containerenv_toolbox_name("name=\"\""), None);
assert_eq!(containerenv_toolbox_name("engine=\"podman\""), None);
}
}