Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

uxi: add a function for running commands as standalone #12

Merged
merged 1 commit into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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