use async_trait::async_trait;
use crate::ast::Value;
use crate::interpreter::{ExecResult, OutputData};
use crate::tools::{ExecContext, ParamSchema, Tool, ToolArgs, ToolSchema};
pub struct Uname;
struct UnameInfo {
sysname: String,
nodename: String,
release: String,
version: String,
machine: String,
os: String,
}
impl UnameInfo {
fn kaish() -> Self {
let version = env!("CARGO_PKG_VERSION");
let git_hash = option_env!("KAISH_GIT_HASH").unwrap_or("unknown");
let build_date = option_env!("KAISH_BUILD_DATE").unwrap_or("unknown");
Self {
sysname: "kaish".to_string(),
nodename: read_hostname(),
release: version.to_string(),
version: format!("kaish {version} ({git_hash} {build_date})"),
machine: std::env::consts::ARCH.to_string(),
os: "Kaijutsu".to_string(),
}
}
fn host() -> Self {
let read = |name: &str| {
std::fs::read_to_string(format!("/proc/sys/kernel/{name}"))
.map(|s| s.trim().to_string())
.unwrap_or_else(|_| "unknown".to_string())
};
Self {
sysname: read("ostype"),
nodename: read_hostname(),
release: read("osrelease"),
version: read("version"),
machine: std::env::consts::ARCH.to_string(),
os: "GNU/Linux".to_string(),
}
}
}
pub(super) fn read_hostname() -> String {
std::fs::read_to_string("/proc/sys/kernel/hostname")
.map(|s| s.trim().to_string())
.unwrap_or_else(|_| "unknown".to_string())
}
#[async_trait]
impl Tool for Uname {
fn name(&self) -> &str {
"uname"
}
fn schema(&self) -> ToolSchema {
ToolSchema::new("uname", "Print system identification")
.param(ParamSchema::optional(
"s",
"bool",
Value::Bool(false),
"Print the system name (default)",
))
.param(ParamSchema::optional(
"n",
"bool",
Value::Bool(false),
"Print the network node hostname",
))
.param(ParamSchema::optional(
"r",
"bool",
Value::Bool(false),
"Print the kernel release",
))
.param(ParamSchema::optional(
"v",
"bool",
Value::Bool(false),
"Print the kernel version string",
))
.param(ParamSchema::optional(
"m",
"bool",
Value::Bool(false),
"Print the machine hardware name",
))
.param(ParamSchema::optional(
"o",
"bool",
Value::Bool(false),
"Print the operating system",
))
.param(ParamSchema::optional(
"p",
"bool",
Value::Bool(false),
"Print the processor type (alias for -m)",
))
.param(ParamSchema::optional(
"i",
"bool",
Value::Bool(false),
"Print the hardware platform (alias for -m)",
))
.param(ParamSchema::optional(
"a",
"bool",
Value::Bool(false),
"Print all information",
))
.param(ParamSchema::optional(
"host",
"bool",
Value::Bool(false),
"Report host OS identity instead of kaish",
))
.example("System name", "uname")
.example("All info", "uname -a")
.example("Host OS identity", "uname --host")
}
async fn execute(&self, args: ToolArgs, _ctx: &mut ExecContext) -> ExecResult {
let host_mode = args.has_flag("host");
let info = if host_mode {
UnameInfo::host()
} else {
UnameInfo::kaish()
};
let all = args.has_flag("a");
let flag_s = all || args.has_flag("s");
let flag_n = all || args.has_flag("n");
let flag_r = all || args.has_flag("r");
let flag_v = all || args.has_flag("v");
let flag_m = all || args.has_flag("m") || args.has_flag("p") || args.has_flag("i");
let flag_o = all || args.has_flag("o");
let any_explicit = flag_s || flag_n || flag_r || flag_v || flag_m || flag_o;
let mut parts: Vec<&str> = Vec::new();
if flag_s || !any_explicit {
parts.push(&info.sysname);
}
if flag_n {
parts.push(&info.nodename);
}
if flag_r {
parts.push(&info.release);
}
if flag_v {
parts.push(&info.version);
}
if flag_m {
parts.push(&info.machine);
}
if flag_o {
parts.push(&info.os);
}
let output = parts.join(" ");
ExecResult::with_output(OutputData::text(output))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vfs::{MemoryFs, VfsRouter};
use std::sync::Arc;
fn make_ctx() -> ExecContext {
let mut vfs = VfsRouter::new();
vfs.mount("/", MemoryFs::new());
ExecContext::new(Arc::new(vfs))
}
#[tokio::test]
async fn test_default_is_sysname() {
let mut ctx = make_ctx();
let result = Uname.execute(ToolArgs::new(), &mut ctx).await;
assert!(result.ok());
assert_eq!(&*result.text_out(), "kaish");
}
#[tokio::test]
async fn test_flag_s() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("s".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
assert_eq!(&*result.text_out(), "kaish");
}
#[tokio::test]
async fn test_flag_n() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("n".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
assert!(!result.text_out().is_empty());
}
#[tokio::test]
async fn test_flag_r() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("r".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
assert_eq!(&*result.text_out(), env!("CARGO_PKG_VERSION"));
}
#[tokio::test]
async fn test_flag_v() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("v".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
assert!(result.text_out().starts_with("kaish "));
assert!(result.text_out().contains(env!("CARGO_PKG_VERSION")));
}
#[tokio::test]
async fn test_flag_m() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("m".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
assert_eq!(&*result.text_out(), std::env::consts::ARCH);
}
#[tokio::test]
async fn test_flag_o() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("o".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
assert_eq!(&*result.text_out(), "Kaijutsu");
}
#[tokio::test]
async fn test_processor_aliases_machine() {
let mut ctx = make_ctx();
let mut args_p = ToolArgs::new();
args_p.flags.insert("p".to_string());
let result_p = Uname.execute(args_p, &mut ctx).await;
let mut args_m = ToolArgs::new();
args_m.flags.insert("m".to_string());
let result_m = Uname.execute(args_m, &mut ctx).await;
assert_eq!(&*result_p.text_out(), &*result_m.text_out());
let mut args_i = ToolArgs::new();
args_i.flags.insert("i".to_string());
let result_i = Uname.execute(args_i, &mut ctx).await;
assert_eq!(&*result_i.text_out(), &*result_m.text_out());
}
#[tokio::test]
async fn test_all_flag() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("a".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
let text = result.text_out();
let fields: Vec<&str> = text.split_whitespace().collect();
assert!(fields.len() >= 6, "expected ≥6 fields, got: {:?}", fields);
assert_eq!(fields[0], "kaish");
assert_eq!(*fields.last().unwrap(), "Kaijutsu");
}
#[tokio::test]
async fn test_multiple_flags() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("s".to_string());
args.flags.insert("n".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
let text = result.text_out();
let parts: Vec<&str> = text.split(' ').collect();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0], "kaish");
}
#[tokio::test]
async fn test_host_mode() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("host".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
assert_ne!(&*result.text_out(), "kaish", "host mode should return real OS");
assert_eq!(&*result.text_out(), "Linux");
}
#[tokio::test]
async fn test_host_all() {
let mut ctx = make_ctx();
let mut args = ToolArgs::new();
args.flags.insert("host".to_string());
args.flags.insert("a".to_string());
let result = Uname.execute(args, &mut ctx).await;
assert!(result.ok());
let text = result.text_out();
let fields: Vec<&str> = text.split_whitespace().collect();
assert_eq!(fields[0], "Linux");
assert_eq!(*fields.last().unwrap(), "GNU/Linux");
}
}