From 3b71a199ec955b394c69098b8d6f48684f717ad5 Mon Sep 17 00:00:00 2001 From: Diretnan Domnan Date: Fri, 4 Oct 2024 01:20:27 +0200 Subject: [PATCH] Implemented ctrl-C exit, clearsreen (ctrl-l) and fix spaces not printing --- ahnlich/cli/Cargo.toml | 2 +- ahnlich/cli/src/connect.rs | 8 ++ ahnlich/cli/src/term.rs | 149 +++++++++++++++++++---------- ahnlich/dsl/src/ai.rs | 34 +++---- ahnlich/dsl/src/db.rs | 34 +++---- ahnlich/dsl/src/syntax/syntax.pest | 12 +-- 6 files changed, 148 insertions(+), 91 deletions(-) diff --git a/ahnlich/cli/Cargo.toml b/ahnlich/cli/Cargo.toml index 9bed2c8c..4690c2c2 100644 --- a/ahnlich/cli/Cargo.toml +++ b/ahnlich/cli/Cargo.toml @@ -13,7 +13,7 @@ path = "src/lib.rs" [dependencies] -crossterm = "0.28.1" +crossterm = { version = "0.28.1", feature = ["bracketed-paste"]} clap.workspace = true dsl = { path = "../dsl", version = "*" } thiserror.workspace = true diff --git a/ahnlich/cli/src/connect.rs b/ahnlich/cli/src/connect.rs index 5dd2e78d..82d1679a 100644 --- a/ahnlich/cli/src/connect.rs +++ b/ahnlich/cli/src/connect.rs @@ -36,6 +36,14 @@ impl AgentPool { } } + /// Returns the commands for the agent pool in question + pub fn commands(&self) -> &[&str] { + match self { + AgentPool::AI(_) => dsl::ai::COMMANDS, + AgentPool::DB(_) => dsl::db::COMMANDS, + } + } + /// Checks if the connection to to a host and post is alive, also checks the cli is connected /// to the right server( ahnlich ai or db) pub async fn is_valid_connection(&self) -> Result { diff --git a/ahnlich/cli/src/term.rs b/ahnlich/cli/src/term.rs index 3cdfc62f..68ae19b6 100644 --- a/ahnlich/cli/src/term.rs +++ b/ahnlich/cli/src/term.rs @@ -3,15 +3,13 @@ use crossterm::{ event::{self, Event, KeyCode, KeyEvent}, queue, style::{Color, Print, SetForegroundColor, Stylize}, - terminal::{disable_raw_mode, enable_raw_mode}, + terminal::{self, disable_raw_mode, enable_raw_mode}, ExecutableCommand, }; use std::io::{self, stdout, Stdout, Write}; use crate::connect::AgentPool; -const RESERVED_WORDS: [&str; 3] = ["ping", "infoserver", "createpredindex"]; - #[derive(Debug)] enum SpecialEntry { Enter, @@ -20,6 +18,8 @@ enum SpecialEntry { Left, Right, Del, + Exit, + ClrScr, } #[derive(Debug)] @@ -29,6 +29,12 @@ enum Entry { None, } +#[derive(Debug)] +enum LineResult { + Command(String), + Exit, +} + pub struct Term { client_pool: AgentPool, } @@ -39,29 +45,40 @@ impl Term { } fn read_char(&self) -> io::Result { - if let Event::Key(KeyEvent { code, .. }) = event::read()? { - Ok(match code { - KeyCode::Enter => Entry::Special(SpecialEntry::Enter), - KeyCode::Char(c) => Entry::Char(c), - KeyCode::Left => Entry::Special(SpecialEntry::Left), - KeyCode::Up => Entry::Special(SpecialEntry::Up), - KeyCode::Down => Entry::Special(SpecialEntry::Down), - KeyCode::Right => Entry::Special(SpecialEntry::Right), - KeyCode::Backspace => Entry::Special(SpecialEntry::Del), - _ => Entry::None, - }) - } else { - Ok(Entry::None) + match event::read()? { + Event::Key(KeyEvent { + code, modifiers, .. + }) => { + if code == KeyCode::Char('c') && modifiers == event::KeyModifiers::CONTROL { + return Ok(Entry::Special(SpecialEntry::Exit)); + } + if code == KeyCode::Char('l') && modifiers == event::KeyModifiers::CONTROL { + return Ok(Entry::Special(SpecialEntry::ClrScr)); + } + Ok(match code { + KeyCode::Enter => Entry::Special(SpecialEntry::Enter), + KeyCode::Char(c) => Entry::Char(c), + KeyCode::Left => Entry::Special(SpecialEntry::Left), + KeyCode::Up => Entry::Special(SpecialEntry::Up), + KeyCode::Down => Entry::Special(SpecialEntry::Down), + KeyCode::Right => Entry::Special(SpecialEntry::Right), + KeyCode::Backspace => Entry::Special(SpecialEntry::Del), + _ => Entry::None, + }) + } + _ => Ok(Entry::None), } } pub fn welcome_message(&self) -> io::Result<()> { let mut stdout = stdout(); - stdout.execute(SetForegroundColor(Color::White))?; - stdout.execute(Print(format!( - "Welcome To Ahnlich {}\n\n", - self.client_pool - )))?; - stdout.execute(SetForegroundColor(Color::White))?; + queue!( + stdout, + terminal::Clear(terminal::ClearType::All), + cursor::MoveTo(0, 0), + SetForegroundColor(Color::White), + Print(format!("Welcome To Ahnlich {}\n\n", self.client_pool)), + SetForegroundColor(Color::White), + )?; stdout.flush()?; Ok(()) } @@ -76,13 +93,26 @@ impl Term { } pub(crate) fn format_output(&self, query: &str) -> String { - let output = String::from_iter(query.split(' ').map(|ex| { - if RESERVED_WORDS.contains(&(ex.to_lowercase().as_str())) { - format!("{}", ex.magenta()) - } else { - format!("{}", ex.white()) - } - })); + let matches = |c| c == ';' || c == ' '; + let output = query + .split_inclusive(matches) + .map(|ex| { + // Trim the trailing space or semicolon from the command part + let trimmed_ex = ex.trim_end_matches(matches); + + if self + .client_pool + .commands() + .contains(&(trimmed_ex.to_lowercase().as_str())) + { + // Add back the space or semicolon at the end (if present) + format!("{}{}", trimmed_ex.magenta(), &ex[trimmed_ex.len()..]) + } else { + format!("{}{}", trimmed_ex.white(), &ex[trimmed_ex.len()..]) + } + }) + .collect::(); + output } @@ -131,7 +161,7 @@ impl Term { Ok(()) } - fn read_line(&self, stdout: &mut Stdout) -> io::Result { + fn read_line(&self, stdout: &mut Stdout) -> io::Result { let (start_pos_col, _) = cursor::position()?; let mut output = String::new(); @@ -174,13 +204,23 @@ impl Term { stdout.execute(cursor::MoveToColumn(current_pos_col - 1))?; } } + SpecialEntry::ClrScr => { + queue!( + stdout, + cursor::MoveTo(0, 0), + terminal::Clear(terminal::ClearType::All), + )?; + self.ahnlich_prompt(stdout)?; + self.move_to_pos_and_print(stdout, &output, start_pos_col)?; + } + SpecialEntry::Exit => return Ok(LineResult::Exit), }, Entry::None => { continue; } } } - Ok(output) + Ok(LineResult::Command(output)) } pub async fn run(&self) -> io::Result<()> { @@ -192,34 +232,39 @@ impl Term { loop { self.ahnlich_prompt(&mut stdout)?; let input = self.read_line(&mut stdout)?; - match input.as_str() { - "quit" | "exit" | "exit()" => break, - command => { - let response = self.client_pool.parse_queries(command).await; - - match response { - Ok(success) => { - disable_raw_mode()?; - for msg in success { + match input { + LineResult::Exit => { + break; + } + LineResult::Command(input) => match input.as_str() { + "quit" | "exit" | "exit()" => break, + command => { + let response = self.client_pool.parse_queries(command).await; + + match response { + Ok(success) => { + disable_raw_mode()?; + for msg in success { + queue!( + stdout, + Print(format!("{}\n", msg)), + cursor::MoveToColumn(0) + )?; + } + stdout.flush()?; + enable_raw_mode()? + } + Err(err) => { queue!( stdout, - Print(format!("{}\n", msg)), + Print(format!("{}\n", err.red())), cursor::MoveToColumn(0) )?; + stdout.flush()?; } - stdout.flush()?; - enable_raw_mode()? - } - Err(err) => { - queue!( - stdout, - Print(format!("{}\n", err.red())), - cursor::MoveToColumn(0) - )?; - stdout.flush()?; } } - } + }, }; } disable_raw_mode()?; diff --git a/ahnlich/dsl/src/ai.rs b/ahnlich/dsl/src/ai.rs index 52416675..7bc21ce2 100644 --- a/ahnlich/dsl/src/ai.rs +++ b/ahnlich/dsl/src/ai.rs @@ -42,22 +42,24 @@ fn parse_to_ai_model(input: &str) -> Result { // Parse raw strings separated by ; into a Vec. Examples include but are not restricted // to -// -// PING -// LISTCLIENTS -// LISTSTORES -// INFOSERVER -// PURGESTORES -// DROPSTORE store_name IF EXISTS -// CREATEPREDINDEX (key_1, key_2) in store_name -// DROPPREDINDEX IF EXISTS (key1, key2) in store_name -// CREATENONLINEARALGORITHMINDEX (kdtree) in store_name -// DROPNONLINEARALGORITHMINDEX IF EXISTS (kdtree) in store_name -// DELKEY ([input 1 text], [input 2 text]) IN my_store -// GETPRED ((author = dickens) OR (country != Nigeria)) IN my_store -// GETSIMN 4 WITH [random text inserted here] USING cosinesimilarity IN my_store WHERE (author = dickens) -// CREATESTORE IF NOT EXISTS my_store QUERYMODEL dalle3 INDEXMODEL dalle3 PREDICATES (author, country) NONLINEARALGORITHMINDEX (kdtree) -// SET (([This is the life of Haks paragraphed], {name: Haks, category: dev}), ([This is the life of Deven paragraphed], {name: Deven, category: dev})) in store +pub const COMMANDS: &[&str] = &[ + "ping", + "listclients", + "liststores", + "infoserver", + "purgestores", + "dropstore", // store_name if exists can be handled dynamically + "createpredindex", // (key_1, key_2) in store_name + "droppredindex", // if exists (key1, key2) in store_name + "createnonlinearalgorithmindex", // (kdtree) in store_name + "dropnonlinearalgorithmindex", // if exists (kdtree) in store_name + "delkey", // ([input 1 text], [input 2 text]) in my_store + "getpred", // ((author = dickens) or (country != Nigeria)) in my_store + "getsimn", // 4 with [random text inserted here] using cosinesimilarity in my_store where (author = dickens) + "createstore", // if not exists my_store querymodel dalle3 indexmodel dalle3 predicates (author, country) nonlinearalgorithmindex (kdtree) + "set", // (([This is the life of Haks paragraphed], {name: Haks, category: dev}), ([This is the life of Deven paragraphed], {name: Deven, category: dev})) in store +]; + pub fn parse_ai_query(input: &str) -> Result, DslError> { let pairs = QueryParser::parse(Rule::ai_query, input).map_err(Box::new)?; let statements = pairs.into_iter().collect::>(); diff --git a/ahnlich/dsl/src/db.rs b/ahnlich/dsl/src/db.rs index 9b14f41b..b30a9988 100644 --- a/ahnlich/dsl/src/db.rs +++ b/ahnlich/dsl/src/db.rs @@ -17,22 +17,24 @@ use crate::{error::DslError, predicate::parse_predicate_expression}; // Parse raw strings separated by ; into a Vec. Examples include but are not restricted // to -// -// PING -// LISTCLIENTS -// LISTSTORES -// INFOSERVER -// DROPSTORE store_name IF EXISTS -// CREATEPREDINDEX (key_1, key_2) in store_name -// DROPPREDINDEX IF EXISTS (key1, key2) in store_name -// CREATENONLINEARALGORITHMINDEX (kdtree) in store_name -// DROPNONLINEARALGORITHMINDEX IF EXISTS (kdtree) in store_name -// GETKEY ([1.0, 2.0], [3.0, 4.0]) IN my_store -// DELKEY ([1.2, 3.0], [5.6, 7.8]) IN my_store -// GETPRED ((author = dickens) OR (country != Nigeria)) IN my_store -// GETSIMN 4 WITH [0.65, 2.78] USING cosinesimilarity IN my_store WHERE (author = dickens) -// CREATESTORE IF NOT EXISTS my_store DIMENSION 21 PREDICATES (author, country) NONLINEARALGORITHMINDEX (kdtree) -// SET (([1.0, 2.1, 3.2], {name: Haks, category: dev}), ([3.1, 4.8, 5.0], {name: Deven, category: dev})) in store +pub const COMMANDS: &[&str] = &[ + "ping", + "listclients", + "liststores", + "infoserver", + "dropstore", // store_name if exists can be handled dynamically + "createpredindex", // (key_1, key_2) in store_name + "droppredindex", // if exists (key1, key2) in store_name + "createnonlinearalgorithmindex", // (kdtree) in store_name + "dropnonlinearalgorithmindex", // if exists (kdtree) in store_name + "getkey", // ([1.0, 2.0], [3.0, 4.0]) in my_store + "delkey", // ([1.2, 3.0], [5.6, 7.8]) in my_store + "getpred", // ((author = dickens) or (country != Nigeria)) in my_store + "getsimn", // 4 with [0.65, 2.78] using cosinesimilarity in my_store where (author = dickens) + "createstore", // if not exists my_store dimension 21 predicates (author, country) nonlinearalgorithmindex (kdtree) + "set", // (([1.0, 2.1, 3.2], {name: Haks, category: dev}), ([3.1, 4.8, 5.0], {name: Deven, category: dev})) in store +]; + pub fn parse_db_query(input: &str) -> Result, DslError> { let pairs = QueryParser::parse(Rule::db_query, input).map_err(Box::new)?; let statements = pairs.into_iter().collect::>(); diff --git a/ahnlich/dsl/src/syntax/syntax.pest b/ahnlich/dsl/src/syntax/syntax.pest index 6b8c3e96..e2c598fc 100644 --- a/ahnlich/dsl/src/syntax/syntax.pest +++ b/ahnlich/dsl/src/syntax/syntax.pest @@ -40,12 +40,12 @@ db_statement = _{ invalid_statement } -ping = { whitespace* ~ ^"ping" ~ whitespace* } -info_server = { whitespace* ~ ^"infoserver" ~ whitespace* } -list_stores = { whitespace* ~ ^"liststores" ~ whitespace* } -list_clients = { whitespace* ~ ^"listclients" ~ whitespace* } -purge_stores = { whitespace* ~ ^"purgestores" ~ whitespace* } -drop_store = { whitespace* ~ ^"dropstore" ~ whitespace* ~ store_name ~ (if_exists | invalid_statement)? } +ping = { whitespace* ~ ^"ping" ~ whitespace* ~ !(ASCII_ALPHANUMERIC) } +info_server = { whitespace* ~ ^"infoserver" ~ whitespace* ~ !(ASCII_ALPHANUMERIC)} +list_stores = { whitespace* ~ ^"liststores" ~ whitespace* ~ !(ASCII_ALPHANUMERIC)} +list_clients = { whitespace* ~ ^"listclients" ~ whitespace* ~ !(ASCII_ALPHANUMERIC)} +purge_stores = { whitespace* ~ ^"purgestores" ~ whitespace* ~ !(ASCII_ALPHANUMERIC)} +drop_store = { whitespace* ~ ^"dropstore" ~ whitespace* ~ store_name ~ (if_exists)? ~ !(ASCII_ALPHANUMERIC)} create_pred_index = { whitespace* ~ ^"createpredindex" ~ whitespace* ~ "(" ~ index_names ~ ")" ~ in_ignored ~ store_name } create_non_linear_algorithm_index = { whitespace* ~ ^"createnonlinearalgorithmindex" ~ whitespace* ~ "(" ~ non_linear_algorithms ~ ")" ~ in_ignored ~ store_name} drop_pred_index = { whitespace* ~ ^"droppredindex" ~ whitespace* ~ (if_exists)? ~ "(" ~ index_names ~ ")" ~ in_ignored ~ store_name }