use super::registry::*;
pub(crate) fn register(registry: &mut CommandRegistry) {
registry.register(cmd_comment());
registry.register(cmd_echo());
registry.register(cmd_date());
registry.register(cmd_chdir());
registry.register(cmd_cd());
registry.register(cmd_pwd());
registry.register(cmd_epics_env_unset());
registry.register(cmd_epics_env_show());
registry.register(cmd_epics_prt_env_params());
registry.register(cmd_epics_param_show());
registry.register(cmd_epics_thread_sleep());
}
fn cmd_comment() -> CommandDef {
CommandDef::new(
"#",
vec![ArgDesc {
name: "text",
arg_type: ArgType::String,
optional: true,
}],
"# [text] — Comment; ignores its arguments.",
|_args: &[ArgValue], _ctx: &CommandContext| Ok(CommandOutcome::Continue),
)
}
fn cmd_echo() -> CommandDef {
CommandDef::new(
"echo",
vec![ArgDesc {
name: "text",
arg_type: ArgType::String,
optional: true,
}],
"echo [text] — Print text to the console.",
|args: &[ArgValue], ctx: &CommandContext| {
match &args[0] {
ArgValue::String(s) => ctx.println(s),
_ => ctx.println(""),
}
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_date() -> CommandDef {
CommandDef::new(
"date",
vec![],
"date — Print the current date and time.",
|_args: &[ArgValue], ctx: &CommandContext| {
let now = chrono::Local::now();
ctx.println(&now.format("%Y-%m-%d %H:%M:%S%.6f %z").to_string());
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_chdir() -> CommandDef {
CommandDef::new(
"chdir",
vec![ArgDesc {
name: "dir",
arg_type: ArgType::String,
optional: false,
}],
"chdir <dir> — Change the current working directory.",
chdir_handler,
)
}
fn cmd_cd() -> CommandDef {
CommandDef::new(
"cd",
vec![ArgDesc {
name: "dir",
arg_type: ArgType::String,
optional: false,
}],
"cd <dir> — Change the current working directory.",
chdir_handler,
)
}
fn chdir_handler(args: &[ArgValue], ctx: &CommandContext) -> CommandResult {
let dir = match &args[0] {
ArgValue::String(s) => s,
_ => return Err("chdir: missing directory".into()),
};
std::env::set_current_dir(dir).map_err(|e| format!("chdir: {dir}: {e}"))?;
if let Ok(cwd) = std::env::current_dir() {
ctx.println(&cwd.display().to_string());
}
Ok(CommandOutcome::Continue)
}
fn cmd_pwd() -> CommandDef {
CommandDef::new(
"pwd",
vec![],
"pwd — Print the current working directory.",
|_args: &[ArgValue], ctx: &CommandContext| {
match std::env::current_dir() {
Ok(cwd) => ctx.println(&cwd.display().to_string()),
Err(e) => return Err(format!("pwd: {e}")),
}
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_epics_env_unset() -> CommandDef {
CommandDef::new(
"epicsEnvUnset",
vec![ArgDesc {
name: "name",
arg_type: ArgType::String,
optional: false,
}],
"epicsEnvUnset <name> — Remove an environment variable.",
|args: &[ArgValue], _ctx: &CommandContext| {
let name = match &args[0] {
ArgValue::String(s) => s,
_ => return Err("epicsEnvUnset: missing name".into()),
};
unsafe { std::env::remove_var(name) };
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_epics_env_show() -> CommandDef {
CommandDef::new(
"epicsEnvShow",
vec![ArgDesc {
name: "name",
arg_type: ArgType::String,
optional: true,
}],
"epicsEnvShow [name] — Show one or all environment variables.",
|args: &[ArgValue], ctx: &CommandContext| {
match &args[0] {
ArgValue::String(name) => match std::env::var(name) {
Ok(v) => ctx.println(&format!("{name}={v}")),
Err(_) => ctx.println(&format!("{name} is not set")),
},
_ => {
let mut vars: Vec<(String, String)> = std::env::vars().collect();
vars.sort();
for (k, v) in vars {
ctx.println(&format!("{k}={v}"));
}
}
}
Ok(CommandOutcome::Continue)
},
)
}
fn print_epics_params(ctx: &CommandContext) {
let mut vars: Vec<(String, String)> = std::env::vars()
.filter(|(k, _)| k.starts_with("EPICS_"))
.collect();
vars.sort();
for (k, v) in vars {
ctx.println(&format!("{k}={v}"));
}
}
fn cmd_epics_prt_env_params() -> CommandDef {
CommandDef::new(
"epicsPrtEnvParams",
vec![],
"epicsPrtEnvParams — Print the EPICS environment parameters.",
|_args: &[ArgValue], ctx: &CommandContext| {
print_epics_params(ctx);
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_epics_param_show() -> CommandDef {
CommandDef::new(
"epicsParamShow",
vec![],
"epicsParamShow — Print the EPICS environment parameters.",
|_args: &[ArgValue], ctx: &CommandContext| {
print_epics_params(ctx);
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_epics_thread_sleep() -> CommandDef {
CommandDef::new(
"epicsThreadSleep",
vec![ArgDesc {
name: "seconds",
arg_type: ArgType::Double,
optional: false,
}],
"epicsThreadSleep <seconds> — Sleep for the given number of seconds.",
|args: &[ArgValue], _ctx: &CommandContext| {
let secs = match &args[0] {
ArgValue::Double(d) => *d,
ArgValue::Int(n) => *n as f64,
_ => return Err("epicsThreadSleep: missing seconds".into()),
};
if secs > 0.0 {
std::thread::sleep(std::time::Duration::from_secs_f64(secs));
}
Ok(CommandOutcome::Continue)
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::database::PvDatabase;
use std::sync::Arc;
fn make_ctx() -> CommandContext {
let rt = tokio::runtime::Runtime::new().unwrap();
let db = Arc::new(PvDatabase::new());
let handle = rt.handle().clone();
let ctx = CommandContext::new(db, handle);
std::mem::forget(rt);
ctx
}
#[test]
fn echo_and_pwd_run() {
let ctx = make_ctx();
let mut reg = CommandRegistry::new();
register(&mut reg);
for name in ["echo", "pwd", "date", "#"] {
let cmd = reg.get(name).unwrap();
let args = parse_args(&[], &cmd.args).unwrap();
assert!(
cmd.handler.call(&args, &ctx).is_ok(),
"{name} must run cleanly"
);
}
}
#[test]
fn epics_env_unset_removes_var() {
let ctx = make_ctx();
let mut reg = CommandRegistry::new();
register(&mut reg);
unsafe { std::env::set_var("_CORECMD_TEST", "x") };
let cmd = reg.get("epicsEnvUnset").unwrap();
let args = parse_args(&["_CORECMD_TEST".to_string()], &cmd.args).unwrap();
cmd.handler.call(&args, &ctx).unwrap();
assert!(std::env::var("_CORECMD_TEST").is_err());
}
}