diff --git a/Cargo.lock b/Cargo.lock index e1d1ff4a..8402bcf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,9 +394,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.106" +version = "0.2.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" [[package]] name = "libsky" @@ -810,9 +810,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" dependencies = [ "itoa", "ryu", @@ -859,7 +859,7 @@ dependencies = [ "rand", "serde", "serde_json", - "skytable 0.6.1 (git+https://github.com/skytable/client-rust?branch=next)", + "skytable 0.6.2-alpha.2 (git+https://github.com/skytable/client-rust?branch=next)", ] [[package]] @@ -870,7 +870,7 @@ dependencies = [ "clap", "env_logger", "log", - "skytable 0.6.1 (git+https://github.com/skytable/client-rust.git)", + "skytable 0.6.2-alpha.2 (git+https://github.com/skytable/client-rust.git)", ] [[package]] @@ -907,7 +907,7 @@ dependencies = [ "regex", "serde", "sky_macros", - "skytable 0.6.1 (git+https://github.com/skytable/client-rust?branch=next)", + "skytable 0.6.2-alpha.2 (git+https://github.com/skytable/client-rust?branch=next)", "tokio", "tokio-openssl", "toml", @@ -922,14 +922,14 @@ dependencies = [ "crossterm", "libsky", "rustyline", - "skytable 0.6.1 (git+https://github.com/skytable/client-rust?branch=next)", + "skytable 0.6.2-alpha.2 (git+https://github.com/skytable/client-rust?branch=next)", "tokio", ] [[package]] name = "skytable" -version = "0.6.1" -source = "git+https://github.com/skytable/client-rust?branch=next#9a34fd825a22d172e61786783c7c4606b6e38e8a" +version = "0.6.2-alpha.2" +source = "git+https://github.com/skytable/client-rust?branch=next#43ab1e47903a3772657351bc5dea64cc4258f491" dependencies = [ "bytes", "openssl", @@ -939,8 +939,8 @@ dependencies = [ [[package]] name = "skytable" -version = "0.6.1" -source = "git+https://github.com/skytable/client-rust.git#9a34fd825a22d172e61786783c7c4606b6e38e8a" +version = "0.6.2-alpha.2" +source = "git+https://github.com/skytable/client-rust.git#43ab1e47903a3772657351bc5dea64cc4258f491" [[package]] name = "smallvec" @@ -965,7 +965,7 @@ dependencies = [ "log", "num_cpus", "rand", - "skytable 0.6.1 (git+https://github.com/skytable/client-rust?branch=next)", + "skytable 0.6.2-alpha.2 (git+https://github.com/skytable/client-rust?branch=next)", "sysinfo", ] diff --git a/cli/src/argparse.rs b/cli/src/argparse.rs index 89a73455..e4534d9e 100644 --- a/cli/src/argparse.rs +++ b/cli/src/argparse.rs @@ -25,6 +25,7 @@ */ use crate::runner::Runner; +use crate::tokenizer; use clap::load_yaml; use clap::App; use crossterm::terminal::{Clear, ClearType}; @@ -34,6 +35,8 @@ use libsky::VERSION; use readline::config::Configurer; use readline::{error::ReadlineError, Editor}; use rustyline as readline; +use skytable::Pipeline; +use skytable::Query; use std::io::stdout; use std::process; const ADDR: &str = "127.0.0.1"; @@ -69,6 +72,7 @@ the server. These enable you to do convenient things like: - "clear": clears the terminal screen Apart from these, you can use the following shell commands: +- "!pipe": Lets you create a pipeline. Terminate with a semicolon (`;`) - "!help": Brings up this help menu - "?": Describes what the built-in shell command is for @@ -132,57 +136,84 @@ pub async fn start_repl() { } loop { match editor.readline(SKYSH_PROMPT) { - Ok(mut line) => match line.to_lowercase().as_str() { - "exit" => break, - "clear" => { - let mut stdout = stdout(); - execute!(stdout, Clear(ClearType::All)).expect("Failed to clear screen"); - execute!(stdout, cursor::MoveTo(0, 0)) - .expect("Failed to move cursor to origin"); - drop(stdout); // aggressively drop stdout - continue; - } - "help" => { - println!("To get help, run `!help`"); - continue; + Ok(mut line) => { + macro_rules! tokenize { + ($inp:expr) => { + match tokenizer::get_query($inp) { + Ok(q) => q, + Err(e) => { + eskysh!(e); + continue; + } + } + }; + () => { + tokenize!(line.as_bytes()) + }; } - _ => { - if line.is_empty() { + match line.to_lowercase().as_str() { + "exit" => break, + "clear" => { + clear_screen(); continue; } - match line.as_bytes()[0] { - b'#' => continue, - b'!' => { - // handle a shell command - match &line.as_bytes()[1..] { - b"" => eskysh!("Bad shell command"), - b"help" => println!("{}", HELP_TEXT), - _ => eskysh!("Unknown shell command"), - } + "help" => { + println!("To get help, run `!help`"); + continue; + } + _ => { + if line.is_empty() { continue; } - b'?' => { - // handle explanation for a shell command - match &line.as_bytes()[1..] { - b"" => eskysh!("Bad shell command"), - b"help" => println!("`!help` shows the help menu"), - b"exit" => println!("`exit` ends the shell session"), - b"clear" => println!("`clear` clears the terminal screen"), - _ => eskysh!("Unknown shell command"), + match line.as_bytes()[0] { + b'#' => continue, + b'!' => { + match &line.as_bytes()[1..] { + b"" => eskysh!("Bad shell command"), + b"help" => println!("{}", HELP_TEXT), + b"pipe" => { + // so we need to handle a pipeline + let mut pipeline = Pipeline::new(); + line = readln!(editor); + loop { + if !line.is_empty() { + if *(line.as_bytes().last().unwrap()) == b';' { + break; + } else { + let q: Query = tokenize!(); + pipeline.push(q); + } + } + line = readln!(editor); + } + if line.len() > 1 { + line.drain(line.len() - 1..); + let q: Query = tokenize!(); + pipeline.push(q); + } + runner.run_pipeline(pipeline).await; + } + _ => eskysh!("Unknown shell command"), + } + continue; } - continue; + b'?' => { + // handle explanation for a shell command + print_help(&line); + continue; + } + _ => {} } - _ => {} - } - while line.len() >= 2 && line[line.len() - 2..].as_bytes().eq(br#" \"#) { - // continuation on next line - let cl = readln!(editor); - line.drain(line.len() - 2..); - line.extend(cl.chars()); + while line.len() >= 2 && line[line.len() - 2..].as_bytes().eq(br#" \"#) { + // continuation on next line + let cl = readln!(editor); + line.drain(line.len() - 2..); + line.extend(cl.chars()); + } + runner.run_query(&line).await } - runner.run_query(&line).await } - }, + } Err(ReadlineError::Interrupted) => break, Err(err) => fatal!("ERROR: Failed to read line with error: {}", err), } @@ -194,3 +225,21 @@ pub async fn start_repl() { }) .unwrap(); } + +fn print_help(line: &str) { + match &line.as_bytes()[1..] { + b"" => eskysh!("Bad shell command"), + b"help" => println!("`!help` shows the help menu"), + b"exit" => println!("`exit` ends the shell session"), + b"clear" => println!("`clear` clears the terminal screen"), + b"pipe" | b"!pipe" => println!("`!pipe` lets you run pipelines using the shell"), + _ => eskysh!("Unknown shell command"), + } +} + +fn clear_screen() { + let mut stdout = stdout(); + execute!(stdout, Clear(ClearType::All)).expect("Failed to clear screen"); + execute!(stdout, cursor::MoveTo(0, 0)).expect("Failed to move cursor to origin"); + drop(stdout); // aggressively drop stdout +} diff --git a/cli/src/runner.rs b/cli/src/runner.rs index 4f32d660..d858305e 100644 --- a/cli/src/runner.rs +++ b/cli/src/runner.rs @@ -29,6 +29,7 @@ use crossterm::style::{Color, Print, ResetColor, SetForegroundColor}; use skytable::error::Error; use skytable::types::Array; use skytable::types::FlatElement; +use skytable::Pipeline; use skytable::Query; use skytable::{aio, Element, RespCode}; @@ -48,6 +49,24 @@ impl Runner { let con = aio::TlsConnection::new(host, port, cert).await?; Ok(Self::Secure(con)) } + pub async fn run_pipeline(&mut self, pipeline: Pipeline) { + let ret = match self { + Self::Insecure(con) => con.run_pipeline(pipeline).await, + Self::Secure(con) => con.run_pipeline(pipeline).await, + }; + let retok = match ret { + Ok(r) => r, + Err(e) => fatal!("An I/O error occurred while querying: {}", e), + }; + for (idx, resp) in retok + .into_iter() + .enumerate() + .map(|(idx, resp)| (idx + 1, resp)) + { + println!("[Response {}]", idx); + print_element(resp); + } + } pub async fn run_query(&mut self, unescaped: &str) { let query: Query = match tokenizer::get_query(unescaped.as_bytes()) { Ok(q) => q, @@ -61,22 +80,26 @@ impl Runner { Self::Secure(con) => con.run_simple_query(&query).await, }; match ret { - Ok(resp) => match resp { - Element::String(st) => write_str!(st), - Element::Binstr(st) => write_binstr!(st), - Element::Array(Array::Bin(brr)) => print_bin_array(brr), - Element::Array(Array::Str(srr)) => print_str_array(srr), - Element::RespCode(r) => print_rcode(r, None), - Element::UnsignedInt(int) => write_int!(int), - Element::Array(Array::Flat(frr)) => write_flat_array(frr), - Element::Array(Array::Recursive(a)) => print_array(a), - _ => eskysh!("The server possibly sent a newer data type that we can't parse"), - }, + Ok(resp) => print_element(resp), Err(e) => fatal!("An I/O error occurred while querying: {}", e), } } } +fn print_element(el: Element) { + match el { + Element::String(st) => write_str!(st), + Element::Binstr(st) => write_binstr!(st), + Element::Array(Array::Bin(brr)) => print_bin_array(brr), + Element::Array(Array::Str(srr)) => print_str_array(srr), + Element::RespCode(r) => print_rcode(r, None), + Element::UnsignedInt(int) => write_int!(int), + Element::Array(Array::Flat(frr)) => write_flat_array(frr), + Element::Array(Array::Recursive(a)) => print_array(a), + _ => eskysh!("The server possibly sent a newer data type that we can't parse"), + } +} + fn print_rcode(rcode: RespCode, idx: Option) { match rcode { RespCode::Okay => write_okay!(),