use serde_json::{json, Value};
use std::process::Command;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct KubeContext {
pub context: String,
pub namespace: String,
}
pub fn read_kube_context(cli: &str) -> Option<KubeContext> {
let ctx_out = Command::new(cli)
.args(["config", "current-context"])
.output()
.ok()?;
if !ctx_out.status.success() {
return None;
}
let context = std::str::from_utf8(&ctx_out.stdout)
.ok()?
.trim()
.to_string();
if context.is_empty() {
return None;
}
let ns_out = Command::new(cli)
.args(["config", "view", "--minify", "-o", "jsonpath={..namespace}"])
.output()
.ok()?;
let namespace = if ns_out.status.success() {
std::str::from_utf8(&ns_out.stdout)
.unwrap_or_default()
.trim()
.to_string()
} else {
String::new()
};
Some(KubeContext { context, namespace })
}
pub fn kubecontext(
cli: &str,
format: &str,
default_namespace: &str,
hide_default: bool,
) -> Option<Vec<Value>> {
let state = read_kube_context(cli)?;
let ns = if state.namespace.is_empty() {
default_namespace.to_string()
} else {
state.namespace.clone()
};
let contents = if hide_default && ns == default_namespace {
render_format_drop_ns(format, &state.context)
} else {
render_format(format, &state.context, &ns)
};
Some(vec![json!({
"contents": contents,
"highlight_groups": [
"kubecontext",
"k8s",
],
"divider_highlight_group": "background:divider",
})])
}
fn render_format(fmt: &str, context: &str, namespace: &str) -> String {
fmt.replace("{context}", context)
.replace("{namespace}", namespace)
}
fn render_format_drop_ns(fmt: &str, context: &str) -> String {
let mut s = fmt.replace("{context}", context);
for sep in [":{namespace}", "/{namespace}", "@{namespace}"] {
if s.contains(sep) {
s = s.replace(sep, "");
return s;
}
let trailing = format!("{}{}", "{namespace}", sep.chars().next().unwrap_or(':'));
if s.contains(&trailing) {
s = s.replace(&trailing, "");
return s;
}
}
s.replace("{namespace}", "")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_kube_context_missing_kubectl_returns_none() {
assert!(read_kube_context("/nonexistent/kubectl-xyz").is_none());
}
#[test]
fn kubecontext_missing_kubectl_returns_none() {
let r = kubecontext(
"/nonexistent/kubectl-xyz",
"{context}:{namespace}",
"default",
true,
);
assert!(r.is_none());
}
#[test]
fn render_format_substitutes_context_and_namespace() {
let s = render_format("{context}:{namespace}", "prod-cluster", "kube-system");
assert_eq!(s, "prod-cluster:kube-system");
}
#[test]
fn render_format_leaves_unknown_tokens_intact() {
let s = render_format("{context}/{namespace} v{version}", "c", "ns");
assert_eq!(s, "c/ns v{version}");
}
#[test]
fn render_format_drop_ns_strips_colon_separator() {
let s = render_format_drop_ns("{context}:{namespace}", "prod");
assert_eq!(s, "prod");
}
#[test]
fn render_format_drop_ns_strips_slash_separator() {
let s = render_format_drop_ns("{context}/{namespace}", "prod");
assert_eq!(s, "prod");
}
#[test]
fn render_format_drop_ns_strips_at_separator() {
let s = render_format_drop_ns("{namespace}@{context}", "prod");
assert_eq!(s, "prod");
}
#[test]
fn render_format_drop_ns_handles_namespace_only() {
let s = render_format_drop_ns("{namespace}", "prod");
assert_eq!(s, "");
}
#[test]
fn kubecontext_state_default_is_empty() {
let k = KubeContext::default();
assert!(k.context.is_empty());
assert!(k.namespace.is_empty());
}
}