Skip to content

Commit

Permalink
feat: add separate support for inbuilt commands
Browse files Browse the repository at this point in the history
  • Loading branch information
raklaptudirm committed Apr 19, 2024
1 parent 799ff67 commit 433c7b1
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 70 deletions.
155 changes: 88 additions & 67 deletions uai/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use std::collections::HashMap;
use std::io::{self, BufRead};
use std::sync::{Arc, Mutex};

use crate::inbuilt;
use std::default::Default;

use crate::{error, inbuilt};

use super::{Command, FlagValues, RunError, RunErrorType};

Expand All @@ -24,6 +26,7 @@ use super::{Command, FlagValues, RunError, RunErrorType};
/// Commands sent from the GUI are automatically parsed and executed according
/// to the Command schema provided by the user to the Client.
pub struct Client<T: Send, E: RunError> {
inbuilt: HashMap<String, inbuilt::Command>,
commands: HashMap<String, Command<T, E>>,
}

Expand All @@ -38,6 +41,7 @@ impl<T: Send + 'static, E: RunError + 'static> Client<T, E> {

// Make the context thread safe to allow commands to run in parallel.
let context = Arc::new(Mutex::new(context));
let our_ctx = Arc::new(Mutex::new(Default::default()));

// Iterate over the lines in the input, since Commands for the GUI are
// separated by newlines and we want to parse each Command separately.
Expand All @@ -49,80 +53,96 @@ impl<T: Send + 'static, E: RunError + 'static> Client<T, E> {
let parts = line.split_whitespace().collect::<Vec<&str>>();

let cmd_name = parts[0]; // The first part is the Command name.
let mut args = &parts[1..]; // The others are flags and their args.
let args = &parts[1..]; // The others are flags and their args.

// Try to find a Command with the given name.
let cmd = self.commands.get(cmd_name);
if cmd.is_none() {
// Command not found, return error and continue.
println!("info error command {} not found", cmd_name);
if cmd.is_some() {
// Parsing complete, run the Command and handle any errors.
match self.run(cmd.unwrap(), &context, args) {
Ok(_) => (),
Err(err) => {
println!("{}", err);
if err.should_quit() {
break 'reading;
}
}
};

continue 'reading;
}

// The Option<Command> is not None, so it can be safely unwrapped.
let cmd = cmd.unwrap();

// Initialize an empty list of the Command's Flags' values.
let mut flags: FlagValues = Default::default();

// The arguments have the following format:
// { flag_name { flag_arg... } ... }
while !args.is_empty() {
let flag_name = args[0]; // The first arg has to be a flag name.
args = &args[1..]; // Remove the flag name from the rest of the args.

// Try to find a flag with the given name.
let flag = cmd.flags.get(flag_name);
if flag.is_none() {
// Flag not found, return error and continue.
println!("info error flag {} not found", flag_name);
continue 'reading;
}

// The Option<Flag> in not None, so it can be safely unwrapped.
let flag = flag.unwrap();

// Find the number of arguments the Flag expects.
let yank = flag.collect(args);

// Check if args has the required number of arguments.
if args.len() < yank {
println!(
"info error flag {} expects {} arguments, found {}",
flag_name,
yank,
args.len(),
);
continue 'reading;
}

// Collect that number of arguments from the remaining args.
let collected = &args[..yank];
flags.insert(flag_name, *flag, collected);
args = &args[yank..];
// Try to find a Command with the given name.
let cmd = self.inbuilt.get(cmd_name);
if cmd.is_some() {
// Parsing complete, run the Command and handle any errors.
match self.run(cmd.unwrap(), &our_ctx, args) {
Ok(_) => (),
Err(err) => {
println!("{}", err);
if err.should_quit() {
break 'reading;
}
}
};

continue 'reading;
}

// Parsing complete, run the Command and handle any errors.
match cmd.run(&context, flags) {
Ok(_) => (),
Err(err) => match err.into() {
// Quit is a directive to quit the Client, so break
// out of the main Command loop reading from stdin.
RunErrorType::Quit => break,

// Command encountered some simple error, report it
// to the GUI and continue parsing Commands.
RunErrorType::Error(o_o) => println!("info error {}", o_o),

// Fatal error encountered, report it to the GUI and quit
// the Client, since this error can't be recovered from.
RunErrorType::Fatal(o_o) => {
println!("info error {}", o_o);
break;
}
},
};
// Command not found, return error and continue.
println!("info error command {} not found", cmd_name);
continue 'reading;
}
}

pub fn run<CT: Send + 'static, CE: RunError + 'static>(
&self,
cmd: &Command<CT, CE>,
context: &Arc<Mutex<CT>>,
args: &[&str],
) -> Result<(), RunErrorType> {
// Initialize an empty list of the Command's Flags' values.
let mut flags: FlagValues = Default::default();

let mut args = args;

// The arguments have the following format:
// { flag_name { flag_arg... } ... }
while !args.is_empty() {
let flag_name = args[0]; // The first arg has to be a flag name.
args = &args[1..]; // Remove the flag name from the rest of the args.

// Try to find a flag with the given name.
let flag = cmd.flags.get(flag_name);
if flag.is_none() {
// Flag not found, return error and continue.
return error!("info error flag {} not found", flag_name);
}

// The Option<Flag> in not None, so it can be safely unwrapped.
let flag = flag.unwrap();

// Find the number of arguments the Flag expects.
let yank = flag.collect(args);

// Check if args has the required number of arguments.
if args.len() < yank {
return error!(
"info error flag {} expects {} arguments, found {}",
flag_name,
yank,
args.len(),
);
}

// Collect that number of arguments from the remaining args.
let collected = &args[..yank];
flags.insert(flag_name, *flag, collected);
args = &args[yank..];
}

// Parsing complete, run the Command and handle any errors.
cmd.run(context, flags).map_err(|e| e.into())
}
}

Expand All @@ -138,10 +158,11 @@ impl<T: Send, E: RunError> Client<T, E> {
#[rustfmt::skip]
pub fn new() -> Self {
Client::<T, E> {
commands: HashMap::from([
inbuilt: HashMap::from([
("quit".into(), inbuilt::quit()),
("isready".into(), inbuilt::isready()),
]),
commands: HashMap::new(),
}
}

Expand Down
21 changes: 21 additions & 0 deletions uai/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// limitations under the License.

use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Mutex};
use std::thread;

Expand Down Expand Up @@ -181,3 +182,23 @@ pub enum RunErrorType {
/// Fatal represents an unrecoverable error, report and quit the Client.
Fatal(String),
}

impl RunErrorType {
/// should_quit checks if the current error requires the Client to quit.
pub fn should_quit(&self) -> bool {
// Except Error, all other variants cause the Client to quit.
!matches!(self, Self::Error(_err))
}
}

impl fmt::Display for RunErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RunErrorType::Quit => Ok(()),
RunErrorType::Error(o_o) => write!(f, "info error {}", o_o),
RunErrorType::Fatal(o_o) => write!(f, "info error {}", o_o),
}
}
}

impl RunError for RunErrorType {}
24 changes: 21 additions & 3 deletions uai/src/inbuilt.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
use crate::{quit, Command, RunError, RunErrorType};
// Copyright © 2024 Rak Laptudirm <rak@laptudirm.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pub fn quit<T: Send, E: RunError>() -> Command<T, E> {
use crate::{quit, RunErrorType};

pub type Command = crate::Command<Context, RunErrorType>;

pub fn quit() -> Command {
Command::new(|_ctx, _flag| quit!())
}

pub fn isready<T: Send, E: RunError>() -> Command<T, E> {
pub fn isready() -> Command {
Command::new(|_ctx, _flag| {
println!("readyok");
Ok(())
})
}

#[derive(Default)]
pub struct Context {}

0 comments on commit 433c7b1

Please sign in to comment.