use std::fs;
use std::path::{Path, PathBuf};
use rho_core::{
RhoResult, Tool, ToolManifest, ensure_parent, normalize_actor_id, require_arg, to_yaml,
validate_actor_id,
};
fn usage() -> ! {
eprintln!(
"usage: rho tools <install-minimal|list|show> ...\n\
install-minimal --shared-root <path> --owner <actor>\n\
list --shared-root <path>\n\
show --shared-root <path> <tool-id>"
);
std::process::exit(2);
}
fn tool_dir(shared_root: &Path) -> PathBuf {
shared_root.join(".rho/tools")
}
fn tool_path(shared_root: &Path, id: &str) -> PathBuf {
tool_dir(shared_root).join(format!("{id}.yaml"))
}
fn write_tool(
shared_root: &Path,
id: &str,
action_type: &str,
owner: &str,
approval_required: bool,
) -> RhoResult<()> {
let path = tool_path(shared_root, id);
ensure_parent(&path)?;
fs::write(
&path,
to_yaml(&ToolManifest {
version: 1,
tool: Tool {
id: id.to_string(),
action_type: action_type.to_string(),
owner: owner.to_string(),
approval_required,
command_template: default_command_template(action_type),
},
})?,
)?;
println!("{}", path.display());
Ok(())
}
fn default_command_template(action_type: &str) -> Vec<String> {
match action_type {
"run_mock_data" | "run_real_data" => vec![
"python3".to_string(),
"CODE_PATH".to_string(),
"DATASET_CSV".to_string(),
],
_ => Vec::new(),
}
}
pub fn run(args: &[String]) -> RhoResult<()> {
let Some(command) = args.first().map(String::as_str) else {
usage();
};
let rest = &args[1..];
match command {
"install-minimal" => {
let shared_root =
PathBuf::from(require_arg(rest, "--shared-root").unwrap_or_else(|_| usage()));
let owner =
normalize_actor_id(&require_arg(rest, "--owner").unwrap_or_else(|_| usage()))?;
validate_actor_id(&owner)?;
write_tool(&shared_root, "run_mock", "run_mock_data", &owner, true)?;
write_tool(&shared_root, "run_real", "run_real_data", &owner, true)?;
write_tool(
&shared_root,
"publish_results",
"release_results",
&owner,
true,
)?;
}
"list" => {
let shared_root =
PathBuf::from(require_arg(rest, "--shared-root").unwrap_or_else(|_| usage()));
let dir = tool_dir(&shared_root);
if !dir.is_dir() {
return Ok(());
}
let mut entries = fs::read_dir(dir)?
.filter_map(Result::ok)
.filter_map(|entry| entry.path().file_stem()?.to_str().map(ToOwned::to_owned))
.collect::<Vec<_>>();
entries.sort();
for entry in entries {
println!("{entry}");
}
}
"show" => {
let shared_root =
PathBuf::from(require_arg(rest, "--shared-root").unwrap_or_else(|_| usage()));
let tool_id = rest.first().cloned().unwrap_or_else(|| usage());
print!("{}", fs::read_to_string(tool_path(&shared_root, &tool_id))?);
}
_ => usage(),
}
Ok(())
}