use ipc_broker::client::IPCClient;
use serde::{Deserialize, Serialize};
use serde_json::Value;
fn print_value(value: &serde_json::Value, indent: usize, key_opt: Option<&str>) {
let padding = " ".repeat(indent);
match value {
serde_json::Value::Null => {
if let Some(key) = key_opt {
println!("{padding}{key} : : Null");
} else {
println!("{padding}Null");
}
}
serde_json::Value::Bool(b) => {
if let Some(key) = key_opt {
println!("{padding}{key} : : {b}");
} else {
println!("{padding}{b}");
}
}
serde_json::Value::Number(n) => {
if let Some(key) = key_opt {
println!("{padding}{key} : : {n}");
} else {
println!("{padding}{n}");
}
}
serde_json::Value::String(s) => {
if let Some(key) = key_opt {
println!("{padding}{key} : : \"{s}\"");
} else {
println!("{padding}\"{s}\"");
}
}
serde_json::Value::Array(arr) => {
if let Some(key) = key_opt {
println!("{padding}{key} : : List:");
} else {
println!("{padding}List:");
}
for (i, v) in arr.iter().enumerate() {
print_value(v, indent + 3, Some(&i.to_string()));
}
}
serde_json::Value::Object(obj) => {
if let Some(key) = key_opt {
println!("{padding}{key} : : Map:");
} else {
println!("{padding}Map:");
}
for (k, v) in obj {
print_value(v, indent + 3, Some(k));
}
}
}
}
fn parse_signature(sig: &str, args: &mut std::slice::Iter<String>) -> Result<Value, String> {
let mut chars = sig.chars().peekable();
fn parse(
chars: &mut std::iter::Peekable<std::str::Chars>,
args: &mut std::slice::Iter<String>,
) -> Result<Value, String> {
match chars.next() {
Some('s') => {
let val = args.next().ok_or("expected string argument")?;
Ok(Value::String(val.clone()))
}
Some('i') => {
let val = args.next().ok_or("expected integer argument")?;
let num = val
.parse::<i64>()
.map_err(|_| format!("invalid integer: {val}"))?;
Ok(Value::Number(num.into()))
}
Some('{') => {
let mut map = serde_json::Map::new();
while let Some(&c) = chars.peek() {
if c == '}' {
chars.next(); break;
}
let key = args.next().ok_or("expected map key")?.clone();
let val = parse(chars, args)?;
map.insert(key, val);
}
Ok(Value::Object(map))
}
Some('(') => {
let mut arr = vec![];
while let Some(&c) = chars.peek() {
if c == ')' {
chars.next(); break;
}
arr.push(parse(chars, args)?);
}
Ok(Value::Array(arr))
}
Some(c) => {
if c == 'n' {
return Ok(Value::Null);
}
Err(format!("unexpected char in signature: {c}"))
}
None => Err("unexpected end of signature".into()),
}
}
parse(&mut chars, args)
}
const APP_NAME: &str = "rob";
const APP_VERSION: &str = env!("ROB_VERSION");
#[derive(Debug, Serialize, Deserialize)]
pub struct IoErrorSerde {
pub code: i32,
pub kind: String,
pub message: String,
}
impl From<std::io::Error> for IoErrorSerde {
fn from(err: std::io::Error) -> Self {
IoErrorSerde {
code: err.kind() as i32,
kind: err.kind().to_string(),
message: err.to_string(),
}
}
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let args: Vec<String> = std::env::args().skip(1).collect();
if args.iter().any(|a| a == "--version" || a == "-v") {
println!("{APP_NAME} version {APP_VERSION}");
return Ok(());
}
if args.iter().any(|a| a == "--help" || a == "-h") {
println!("Usage: rob call <object> <method> [signature] [args...]");
return Ok(());
}
if args.len() < 3 {
log::error!("Usage: rob call <object> <method> [signature] [args...]");
return Ok(());
}
let command = args
.first()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, "missing command"))?;
let object = args.get(1).ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "missing object name")
})?;
let method = args.get(2).ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "missing method name")
})?;
let signature = args.get(3).cloned().unwrap_or_default();
let mut iter = if args.len() > 4 {
args[4..].iter()
} else {
[].iter() };
let parsed_args = if signature.is_empty() {
serde_json::Value::Null
} else {
parse_signature(&signature, &mut iter)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?
};
if command == "call" {
let response = match IPCClient::connect().await {
Ok(proxy) => proxy
.remote_call::<Value, Value>(object, method, parsed_args)
.await
.unwrap_or_else(|err| serde_json::json!(IoErrorSerde::from(err))),
Err(err) => {
serde_json::json!(IoErrorSerde::from(err))
}
};
println!("\nResult:");
print_value(&response, 0, None);
} else if command == "listen" {
println!("Listening for: object={object} method={method}. Press ctrl+c to exit.\n\n");
let proxy = IPCClient::connect().await?;
proxy
.subscribe_async(object, method, |param| {
println!("\nResult:");
print_value(¶m, 0, None);
})
.await;
tokio::signal::ctrl_c().await?;
} else if command == "send" {
let response = match IPCClient::connect().await {
Ok(proxy) => proxy
.publish(object, method, &parsed_args)
.await
.map(|_| serde_json::Value::Null)
.unwrap_or_else(|err| serde_json::json!(IoErrorSerde::from(err))),
Err(err) => {
serde_json::json!(IoErrorSerde::from(err))
}
};
println!("\nResult:");
print_value(&response, 0, None);
} else {
eprintln!("Unknown command: {command}");
}
Ok(())
}