use std::net::SocketAddr;
use clap::{CommandFactory, Parser, Subcommand};
use dfir_rs::util::{bind_udp_bytes, ipv4_resolve};
use dfir_rs::{dfir_syntax, tokio, DemuxEnum};
use gossip_kv::{ClientRequest, ClientResponse, Key};
use tracing::error;
#[derive(Debug, Parser)]
struct Opts {
#[clap(short, long, help = "Server address to connect to.")]
server_address: Option<String>,
}
#[derive(Debug, Parser)]
#[command(multicall = true)]
struct InteractiveApp {
#[clap(subcommand)]
commands: InteractiveCommands,
}
#[derive(Debug, Subcommand, DemuxEnum)]
enum InteractiveCommands {
Get {
#[arg(value_parser = parse_key, required = true, help = "Key to get")]
key: Key,
},
Set {
#[arg(value_parser = parse_key, required = true, help = "Key to set")]
key: Key,
value: String,
},
Delete {
#[arg(value_parser = parse_key, required = true, help = "Key to delete")]
key: Key,
},
Exit,
}
fn parse_key(s: &str) -> Result<Key, String> {
s.parse::<Key>().map_err(|e| e.to_string())
}
fn parse_command(line: String) -> Option<InteractiveCommands> {
if line.trim() == "help" {
InteractiveApp::command()
.help_template("\nAvailable Commands: \n{subcommands}")
.print_help()
.unwrap();
return None;
}
let line_parts = shlex::split(&line);
if line_parts.is_none() {
error!("\nUnable to parse command.");
return None;
}
let maybe_parsed = InteractiveApp::try_parse_from(line_parts.unwrap());
match maybe_parsed {
Err(e) => {
error!("\n{}", e);
None
}
Ok(cli) => Some(cli.commands),
}
}
#[dfir_rs::main]
async fn main() {
tracing_subscriber::fmt::init();
let opts = Opts::parse();
let address = ipv4_resolve("0.0.0.0:0").unwrap();
let server_address = opts.server_address.map_or_else(
|| ipv4_resolve("localhost:3001").unwrap(),
|s| ipv4_resolve(&s).unwrap(),
);
let (outbound, inbound, _) = bind_udp_bytes(address).await;
let mut cli = dfir_syntax! {
inbound_messages = source_stream_serde(inbound) -> map(Result::unwrap) -> for_each(|(response, _addr): (ClientResponse, SocketAddr)| println!("{:?}", response));
outbound_messages = union() -> dest_sink_serde(outbound);
commands = source_stdin()
-> filter_map(|line| parse_command(line.unwrap()))
-> demux_enum::<InteractiveCommands>();
commands[Get] -> map(|(key,)| (ClientRequest::Get {key}, server_address)) -> outbound_messages;
commands[Set] -> map(|(key, value)| (ClientRequest::Set {key, value}, server_address)) -> outbound_messages;
commands[Delete] -> map(|(key,)| (ClientRequest::Delete {key}, server_address)) -> outbound_messages;
commands[Exit] -> for_each(|()| std::process::exit(0)); };
cli.run_async().await;
}