From ebd04a557eb7c2d24a854eea4829922a3f970aeb Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Sat, 24 Feb 2024 23:23:22 +0530 Subject: [PATCH] skysh: Add `--eval` for running queries directly --- cli/help_text/help | 1 + cli/src/args.rs | 10 +++++-- cli/src/main.rs | 10 +++++++ cli/src/query.rs | 38 ++++++++++++++++++++++-- cli/src/repl.rs | 28 +++--------------- cli/src/resp.rs | 73 +++++++++++++++++++++++++++++----------------- 6 files changed, 104 insertions(+), 56 deletions(-) diff --git a/cli/help_text/help b/cli/help_text/help index 4c00b0b1..b21ab79e 100644 --- a/cli/help_text/help +++ b/cli/help_text/help @@ -14,6 +14,7 @@ OPTIONS: --user Set the user for this client session --password Set the password for this client session --tls-cert Set the TLS certificate to use (for TLS endpoints) + --eval Execute and print the query (password must be set) NOTES: - skysh will also look for the `{password_env_var}` environment variable diff --git a/cli/src/args.rs b/cli/src/args.rs index 1e799866..dbd6eedb 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -68,6 +68,7 @@ pub enum ClientConfigKind { pub enum Task { HelpMessage(String), OpenShell(ClientConfig), + ExecOnce(ClientConfig, String), } enum TaskInner { @@ -156,10 +157,13 @@ pub fn parse() -> CliResult { } } }; + let eval = args.remove("--eval"); if args.is_empty() { - Ok(Task::OpenShell(ClientConfig::new( - endpoint, username, password, - ))) + let client = ClientConfig::new(endpoint, username, password); + match eval { + Some(query) => Ok(Task::ExecOnce(client, query)), + None => Ok(Task::OpenShell(client)), + } } else { Err(CliError::ArgsErr(format!("found unknown arguments"))) } diff --git a/cli/src/main.rs b/cli/src/main.rs index c23a09b6..8cec175c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -50,6 +50,16 @@ fn run() -> error::CliResult<()> { match args::parse()? { Task::HelpMessage(msg) => println!("{msg}"), Task::OpenShell(cfg) => repl::start(cfg)?, + Task::ExecOnce(cfg, query) => { + let query = skytable::query!(query); + let resp = query::connect( + cfg, + false, + |mut c| Ok(c.query(&query)), + |mut c| Ok(c.query(&query)), + )??; + resp::format_response(resp, false, false); + } } Ok(()) } diff --git a/cli/src/query.rs b/cli/src/query.rs index 71732070..bb2c9773 100644 --- a/cli/src/query.rs +++ b/cli/src/query.rs @@ -25,12 +25,46 @@ */ use { - crate::error::{CliError, CliResult}, + crate::{ + args::{ClientConfig, ClientConfigKind}, + error::{CliError, CliResult}, + }, skytable::{ - error::ClientResult, query::SQParam, response::Response, Connection, ConnectionTls, Query, + error::ClientResult, query::SQParam, response::Response, Config, Connection, ConnectionTls, + Query, }, }; +pub fn connect( + cfg: ClientConfig, + print_con_info: bool, + tcp_f: impl Fn(Connection) -> CliResult, + tls_f: impl Fn(ConnectionTls) -> CliResult, +) -> CliResult { + match cfg.kind { + ClientConfigKind::Tcp(host, port) => { + let c = Config::new(&host, port, &cfg.username, &cfg.password).connect()?; + if print_con_info { + println!( + "Authenticated as '{}' on {}:{} over Skyhash/TCP\n---", + &cfg.username, &host, &port + ); + } + tcp_f(c) + } + ClientConfigKind::Tls(host, port, cert) => { + let c = Config::new(&host, port, &cfg.username, &cfg.password).connect_tls(&cert)?; + if print_con_info { + println!( + "Authenticated as '{}' on {}:{} over Skyhash/TLS\n---", + &cfg.username, &host, &port + ); + } + tls_f(c) + } + } +} + pub trait IsConnection { fn execute_query(&mut self, q: Query) -> ClientResult; } diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 80d31500..47e31b49 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -24,18 +24,15 @@ * */ -use crate::query::ExecKind; - use { crate::{ - args::{ClientConfig, ClientConfigKind}, + args::ClientConfig, error::{CliError, CliResult}, - query::{self, IsConnection}, + query::{self, ExecKind, IsConnection}, resp, }, crossterm::{cursor, execute, terminal}, rustyline::{config::Configurer, error::ReadlineError, DefaultEditor}, - skytable::Config, std::io::{stdout, ErrorKind}, }; @@ -43,24 +40,7 @@ const SKYSH_HISTORY_FILE: &str = ".sky_history"; const TXT_WELCOME: &str = include_str!("../help_text/welcome"); pub fn start(cfg: ClientConfig) -> CliResult<()> { - match cfg.kind { - ClientConfigKind::Tcp(host, port) => { - let c = Config::new(&host, port, &cfg.username, &cfg.password).connect()?; - println!( - "Authenticated as '{}' on {}:{} over Skyhash/TCP\n---", - &cfg.username, &host, &port - ); - repl(c) - } - ClientConfigKind::Tls(host, port, cert) => { - let c = Config::new(&host, port, &cfg.username, &cfg.password).connect_tls(&cert)?; - println!( - "Authenticated as '{}' on {}:{} over Skyhash/TLS\n---", - &cfg.username, &host, &port - ); - repl(c) - } - } + query::connect(cfg, true, repl, repl) } fn repl(mut con: C) -> CliResult<()> { @@ -123,7 +103,7 @@ fn repl(mut con: C) -> CliResult<()> { q } }; - if resp::format_response(con.execute_query(q)?, special) { + if resp::format_response(con.execute_query(q)?, special, true) { if let Some(pr) = new_prompt { prompt = pr; } diff --git a/cli/src/resp.rs b/cli/src/resp.rs index 276a9fd0..c7317edc 100644 --- a/cli/src/resp.rs +++ b/cli/src/resp.rs @@ -29,28 +29,43 @@ use { skytable::response::{Response, Row, Value}, }; -pub fn format_response(resp: Response, print_special: bool) -> bool { +macro_rules! pprint { + ($pretty:expr, $base:literal$(.$f:ident())*) => { + if $pretty { + let pretty = $base$(.$f())*; + println!("{}", pretty); + } else { + println!("{}", $base); + } + } +} + +pub fn format_response(resp: Response, print_special: bool, pretty_format: bool) -> bool { match resp { - Response::Empty => println!("{}", "(Okay)".cyan()), + Response::Empty => pprint!(pretty_format, "(Okay)".cyan()), Response::Error(e) => { println!("{}", format!("(server error code: {e})").red()); return false; } Response::Value(v) => { - print_value(v, print_special); + print_value(v, print_special, pretty_format); println!(); } Response::Row(r) => { - print_row(r); + print_row(r, pretty_format); println!(); } Response::Rows(rows) => { if rows.is_empty() { - println!("{}", "[0 rows returned]".grey().italic()); + pprint!(pretty_format, "[0 rows returned]".grey().italic()); } else { for (i, row) in rows.into_iter().enumerate().map(|(i, r)| (i + 1, r)) { - print!("{} ", format!("({i})").grey().bold()); - print_row(row); + if pretty_format { + println!("{}", "({i})".grey().bold()) + } else { + println!("({i})") + } + print_row(row, pretty_format); println!(); } } @@ -59,11 +74,11 @@ pub fn format_response(resp: Response, print_special: bool) -> bool { true } -fn print_row(r: Row) { +fn print_row(r: Row, pretty_format: bool) { print!("("); let mut columns = r.into_values().into_iter().peekable(); while let Some(cell) = columns.next() { - print_value(cell, false); + print_value(cell, false, pretty_format); if columns.peek().is_some() { print!(", "); } @@ -71,10 +86,10 @@ fn print_row(r: Row) { print!(")"); } -fn print_value(v: Value, print_special: bool) { +fn print_value(v: Value, print_special: bool, pretty_format: bool) { match v { - Value::Null => print!("{}", "null".grey().italic()), - Value::String(s) => print_string(&s, print_special), + Value::Null => pprint!(pretty_format, "null".grey().italic()), + Value::String(s) => print_string(&s, print_special, pretty_format), Value::Binary(b) => print_binary(&b), Value::Bool(b) => print!("{b}"), Value::UInt8(i) => print!("{i}"), @@ -91,7 +106,7 @@ fn print_value(v: Value, print_special: bool) { print!("["); let mut items = items.into_iter().peekable(); while let Some(item) = items.next() { - print_value(item, print_special); + print_value(item, print_special, pretty_format); if items.peek().is_some() { print!(", "); } @@ -113,22 +128,26 @@ fn print_binary(b: &[u8]) { print!("]"); } -fn print_string(s: &str, print_special: bool) { - if print_special { - print!("{}", s.italic().grey()); +fn print_string(s: &str, print_special: bool, pretty_format: bool) { + if !pretty_format { + print!("{s}"); } else { - print!("\""); - for ch in s.chars() { - if ch == '"' { - print!("\\{ch}"); - } else if ch == '\t' { - print!("\\t"); - } else if ch == '\n' { - print!("\\n"); - } else { - print!("{ch}"); + if print_special { + print!("{}", s.italic().grey()); + } else { + print!("\""); + for ch in s.chars() { + if ch == '"' { + print!("\\{ch}"); + } else if ch == '\t' { + print!("\\t"); + } else if ch == '\n' { + print!("\\n"); + } else { + print!("{ch}"); + } } + print!("\""); } - print!("\""); } }