use bytes::Bytes;
use clap::{Parser, Subcommand};
use colored::Colorize;
use redis_asyncx::{Client, Result};
use shlex::split;
use std::io::{self, Write};
use std::str;
#[derive(Parser, Debug)]
#[command(name = "redis-async-cli")]
#[command(version = "0.1.0")]
#[command(about = "redis-cli 0.1.0", long_about = None)]
struct Cli {
#[arg(long, default_value = "127.0.0.1", help = "Redis server hostname.")]
host: String,
#[arg(short, long, default_value = "6379", help = "Redis server port.")]
port: u16,
#[command(flatten)]
verbose: clap_verbosity_flag::Verbosity,
#[command(subcommand)]
command: Option<RedisCommand>,
}
#[derive(Parser, Debug)]
struct CliInteractive {
#[command(subcommand)]
command: Option<RedisCommand>,
}
#[derive(Subcommand, Debug, Clone)]
enum RedisCommand {
Hello {
proto: Option<u8>,
},
Ping {
message: Option<Bytes>,
},
Get {
key: String,
},
Set {
key: String,
value: Bytes,
},
Del {
keys: Vec<String>,
},
Exists {
keys: Vec<String>,
},
Expire {
key: String,
seconds: i64,
},
Ttl {
key: String,
},
Incr {
key: String,
},
Decr {
key: String,
},
Lpush {
key: String,
values: Vec<String>,
},
Rpush {
key: String,
values: Vec<String>,
},
Lpop {
key: String,
count: Option<u64>,
},
Rpop {
key: String,
count: Option<u64>,
},
Lrange {
key: String,
start: i64,
end: i64,
},
Clear,
}
impl RedisCommand {
async fn execute(&self, client: &mut Client) -> Result<()> {
match self {
RedisCommand::Hello { proto } => {
let response = client.hello(*proto).await?;
for (key, value) in response {
if let Ok(string) = str::from_utf8(&value) {
println!("\"{}\" => \"{}\"", key, string);
} else {
println!("\"{}\" => {:?}", key, value);
}
}
}
RedisCommand::Ping { message } => {
let message = message.as_deref();
let response = client.ping(message).await?;
if let Ok(string) = str::from_utf8(&response) {
if message.is_some() {
println!("\"{}\"", string);
} else {
println!("PONG");
}
} else {
println!("{response:?}");
}
}
RedisCommand::Get { key } => {
let response = client.get(key).await?;
if let Some(value) = response {
if let Ok(string) = str::from_utf8(&value) {
println!("\"{}\"", string);
} else {
println!("{:?}", value);
}
} else {
println!("(nil)");
}
}
RedisCommand::Set { key, value } => {
let response = client.set(key, value).await?;
if let Some(value) = response {
if let Ok(string) = str::from_utf8(&value) {
println!("{}", string);
} else {
println!("{:?}", value);
}
} else {
println!("(nil)");
}
}
RedisCommand::Del { keys } => {
let response = client
.del(keys.iter().map(String::as_str).collect::<Vec<&str>>())
.await?;
println!("{response:?}");
}
RedisCommand::Exists { keys } => {
let response = client
.exists(keys.iter().map(String::as_str).collect::<Vec<&str>>())
.await?;
println!("(integer) {response}");
}
RedisCommand::Expire { key, seconds } => {
let response = client.expire(key, *seconds).await?;
println!("(integer) {response}");
}
RedisCommand::Ttl { key } => {
let response = client.ttl(key).await?;
println!("(integer) {response}");
}
RedisCommand::Incr { key } => {
let response = client.incr(key).await?;
println!("(integer) {response}");
}
RedisCommand::Decr { key } => {
let response = client.decr(key).await?;
println!("(integer) {response}");
}
RedisCommand::Lpush { key, values } => {
let response = client
.lpush(key, values.iter().map(|s| s.as_bytes()).collect())
.await?;
println!("(integer) {response}");
}
RedisCommand::Rpush { key, values } => {
let response = client
.rpush(key, values.iter().map(|s| s.as_bytes()).collect())
.await?;
println!("(integer) {response}");
}
RedisCommand::Lpop { key, count } => {
match count {
Some(count) => {
if let Some(response) = client.lpop_n(key, *count).await? {
for line in response {
if let Ok(string) = str::from_utf8(&line) {
println!("\"{}\"", string);
} else {
println!("{line:?}");
}
}
} else {
println!("(nil)");
}
}
None => {
if let Some(response) = client.lpop(key).await? {
if let Ok(string) = str::from_utf8(&response) {
println!("\"{}\"", string);
} else {
println!("{response:?}");
}
} else {
println!("(nil)");
}
}
}
}
RedisCommand::Rpop { key, count } => {
match count {
Some(count) => {
if let Some(response) = client.rpop_n(key, *count).await? {
for line in response {
if let Ok(string) = str::from_utf8(&line) {
println!("\"{}\"", string);
} else {
println!("{line:?}");
}
}
} else {
println!("(nil)");
}
}
None => {
if let Some(response) = client.rpop(key).await? {
if let Ok(string) = str::from_utf8(&response) {
println!("\"{}\"", string);
} else {
println!("{response:?}");
}
} else {
println!("(nil)");
}
}
}
}
RedisCommand::Lrange { key, start, end } => {
let response = client.lrange(key, *start, *end).await?;
for line in response {
if let Ok(string) = str::from_utf8(&line) {
println!("\"{}\"", string);
} else {
println!("{line:?}");
}
}
}
RedisCommand::Clear => {
clear_screen();
}
}
Ok(())
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let mut args: Vec<String> = std::env::args().collect();
if args.len() > 1 {
args[1] = args[1].to_lowercase(); }
let cli = Cli::parse_from(&args);
let mut addr = String::with_capacity(cli.host.len() + 1 + cli.port.to_string().len());
addr.push_str(&cli.host);
addr.push(':');
addr.push_str(&cli.port.to_string());
let mut client = Client::connect(&addr).await?;
if let Some(command) = cli.command {
command.execute(&mut client).await?;
} else {
println!("{}", "Interactive mode. Type 'exit' to quit.".green());
loop {
print!("{addr}> "); io::stdout().flush().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let input = input.trim();
if input == "exit" {
break;
}
let args = split(input).unwrap();
if args.is_empty() {
continue;
}
let mut args = args.to_vec();
let lowercased = args[0].to_lowercase();
args[0] = lowercased;
args.insert(0, "".into());
match CliInteractive::try_parse_from(args) {
Ok(cli) => {
if let Some(command) = cli.command {
match command.execute(&mut client).await {
Ok(_) => {}
Err(e) => {
eprintln!("Error executing command: {e}");
continue;
}
}
} else {
println!("Unknown command: {input}");
}
}
Err(e) => {
eprintln!("Error parsing command: {e}");
continue;
}
};
}
}
Ok(())
}
fn clear_screen() {
print!("\x1B[2J\x1B[1;1H"); std::io::stdout().flush().unwrap();
}