Skip to content

Commit

Permalink
chore: add a function for running commands as standalone
Browse files Browse the repository at this point in the history
  • Loading branch information
raklaptudirm committed Aug 15, 2024
1 parent fbb9633 commit 33e0acd
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 69 deletions.
108 changes: 45 additions & 63 deletions uxi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ use std::default::Default;
use std::io::{self, BufRead};

use crate::context::Context;
use crate::context::GuardedBundledCtx;
use crate::inbuilt::new_guarded_ctx;
use crate::{error, flag, inbuilt, Command, GuardedBundledCtx, Parameter, RunError};
use crate::{error, flag, inbuilt, Command, Parameter, RunError};

/// Client represents an UXI engine client. It can accept and parse commands
/// from the GUI and send commands to the GUI though its input and output.
Expand Down Expand Up @@ -47,28 +48,8 @@ impl<T: Send + 'static> Client<T> {
// Iterate over the lines in the input, since Commands for the GUI are
// separated by newlines and we want to parse each Command separately.
'reading: for line in stdin.lock().lines() {
// Split the Command into parts by whitespace.
let line = line.unwrap();
let parts = line.split_whitespace().collect::<Vec<&str>>();

if parts.is_empty() {
continue 'reading;
}

let (cmd_name, args) = (parts[0], &parts[1..]);

// Try to find a Command with the given name.
let cmd = match self.commands.get(cmd_name) {
Some(c) => c,
None => {
// Command not found, return error and continue.
println!("info error command {} not found", cmd_name);
continue 'reading;
}
};

// Parsing complete, run the Command and handle any errors.
if let Err(err) = self.run(cmd, &context, args) {
// Run the Command and handle any errors.
if let Err(err) = self.run_from_string::<false>(line.unwrap(), &context) {
println!("{}", err);
if err.should_quit() {
break 'reading;
Expand All @@ -77,57 +58,58 @@ impl<T: Send + 'static> Client<T> {
}
}

/// run runs the given Command with the given [context](GuardedBundledCtx) and
/// the given arguments. This function is used internally when a Client is
/// started. Only use this function if you know what you are doing.
fn run(
/// run_cmd_strings allows running a Command independently from the main uxi
/// loop. Since the commands are run in a standalone way, everything is run
/// synchronously.
pub fn run_cmd_string(&self, str: String, context: T) -> Result<(), RunError> {
let context = new_guarded_ctx(context, self.initial_context.clone());
self.run_from_string::<false>(str, &context)
}

/// run_from_string parses the Command and its flag values from the given
/// String and then runs that Command with the flag values and the context.
fn run_from_string<const PARALLEL: bool>(
&self,
cmd: &Command<T>,
str: String,
context: &GuardedBundledCtx<T>,
args: &[&str],
) -> Result<(), RunError> {
// Initialize an empty list of the Command's Flags' values.
let mut flags: flag::Values = Default::default();
let parts = str.split_whitespace().collect::<Vec<&str>>();

let mut args = args;
if parts.is_empty() {
return Ok(());
}

// 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.
let (cmd_name, args) = (parts[0], &parts[1..]);

// 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);
// Try to find a Command with the given name.
let cmd = match self.commands.get(cmd_name) {
Some(c) => c,
None => {
// Command not found, return error and continue.
return error!("info error command {} not found", cmd_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(),
);
}
self.run::<PARALLEL>(cmd, context, args)
}

// Collect that number of arguments from the remaining args.
let collected = &args[..yank];
flags.insert(flag_name, *flag, collected);
args = &args[yank..];
}
/// run runs the given Command with the given [context](GuardedBundledCtx) and
/// the given arguments. This function is used internally when a Client is
/// started. Only use this function if you know what you are doing.
fn run<const PARALLEL: bool>(
&self,
cmd: &Command<T>,
context: &GuardedBundledCtx<T>,
args: &[&str],
) -> Result<(), RunError> {
// Initialize an empty list of the Command's Flags' values.
let flags = match flag::Values::parse(args, &cmd.flags) {
Ok(values) => values,
Err(err) => return Err(RunError::Error(err)),
};

// Parsing complete, run the Command and handle any errors.
cmd.run(context, flags)
cmd.run::<PARALLEL>(context, flags)
}
}

Expand Down
12 changes: 8 additions & 4 deletions uxi/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use std::error::Error;
use std::fmt;
use std::thread;

use crate::context::new_bundle;
use crate::{flag, Bundle, Flag, GuardedBundledCtx};
use crate::context::{new_bundle, GuardedBundledCtx};
use crate::{flag, Bundle, Flag};

/// Command represents a runnable UAI command. It contains all the metadata
/// needed to parse and verify a Command request from the GUI for a Command, and
Expand All @@ -42,12 +42,16 @@ impl<T: Send + 'static> Command<T> {
/// run runs the current Command with the given context and flag values.
/// A new thread is spawned and detached to run parallel Commands. It returns
/// the error returned by the Command's execution, or [`Ok`] for parallel.
pub fn run(&self, context: &GuardedBundledCtx<T>, flags: flag::Values) -> CmdResult {
pub fn run<const PARALLEL: bool>(
&self,
context: &GuardedBundledCtx<T>,
flags: flag::Values,
) -> CmdResult {
// Clone values which might be moved by spawning a new thread.
let context = new_bundle(context, flags);
let func = self.run_fn;

if self.parallel {
if PARALLEL && self.parallel {
// If the Command is supposed to be run in parallel, spawn a new
// thread and detach it for its execution. Syncing with the thread
// should be handled by the user using the context.
Expand Down
46 changes: 46 additions & 0 deletions uxi/src/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,49 @@ impl Values {
};
}
}

impl Values {
/// parses converts the given arguments flags into a Flag [Values] value.
pub fn parse(value: &[&str], flag_set: &HashMap<String, Flag>) -> Result<Self, String> {
let mut flags: Self = Default::default();

let mut args = value;

// 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 = flag_set.get(flag_name);
if flag.is_none() {
// Flag not found, return error and continue.
return Err(format!("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 Err(format!(
"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..];
}

Ok(flags)
}
}
2 changes: 1 addition & 1 deletion uxi/src/inbuilt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex};

use crate::{context::Context, GuardedBundledCtx};
use crate::context::{Context, GuardedBundledCtx};

/// A BundledCtx bundles the user-provided context `C` and the inbuilt context
/// into a single type of ease of mutex guarding for concurrency. It provides
Expand Down
2 changes: 1 addition & 1 deletion uxi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// without their parent namespace.
pub use self::client::*;
pub use self::cmd::*;
pub use self::context::{Bundle, GuardedBundledCtx};
pub use self::context::Bundle;
pub use self::flag::Flag;
pub use self::inbuilt::BundledCtx;
pub use self::parameter::Parameter;
Expand Down

0 comments on commit 33e0acd

Please sign in to comment.