From 69d29007ef922d337393cbc22aa600e43aaf1008 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm <rak@laptudirm.com> Date: Fri, 26 Apr 2024 20:56:50 +0530 Subject: [PATCH] chore: start with working options --- uai/src/client.rs | 20 +++++--- uai/src/cmd.rs | 11 ++-- uai/src/context.rs | 23 +++++++-- uai/src/inbuilt.rs | 74 +++++++++++++++++++++++++-- uai/src/parameter.rs | 119 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 228 insertions(+), 19 deletions(-) diff --git a/uai/src/client.rs b/uai/src/client.rs index 21958ce..89d71b6 100644 --- a/uai/src/client.rs +++ b/uai/src/client.rs @@ -26,7 +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> { - context: inbuilt::Context, + initial_context: inbuilt::Context, commands: HashMap<String, Command<T, E>>, } @@ -41,7 +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(self.context.clone())); + let our_ctx = Arc::new(Mutex::new(self.initial_context.clone())); // Iterate over the lines in the input, since Commands for the GUI are // separated by newlines and we want to parse each Command separately. @@ -139,7 +139,8 @@ impl<T: Send + 'static, E: RunError + 'static> Client<T, E> { } // Parsing complete, run the Command and handle any errors. - cmd.run(context, flags).map_err(|e| e.into()) + cmd.run(context, flags, self.initial_context.option_values.clone()) + .map_err(|e| e.into()) } } @@ -155,7 +156,7 @@ impl<T: Send, E: RunError> Client<T, E> { #[rustfmt::skip] pub fn new() -> Self { Client::<T, E> { - context: Default::default(), + initial_context: Default::default(), commands: HashMap::new(), } } @@ -178,17 +179,22 @@ impl<T: Send, E: RunError> Client<T, E> { } pub fn option(mut self, name: &str, option: Parameter) -> Self { - self.context.options.insert(name.to_string(), option); + self.initial_context + .options + .insert(name.to_string(), option.clone()); + self.initial_context + .option_values + .insert_default(name.to_string(), &option); self } pub fn engine(mut self, name: &str) -> Self { - self.context.engine = name.to_owned(); + self.initial_context.engine = name.to_owned(); self } pub fn author(mut self, name: &str) -> Self { - self.context.author = name.to_owned(); + self.initial_context.author = name.to_owned(); self } } diff --git a/uai/src/cmd.rs b/uai/src/cmd.rs index 564ea9a..3375354 100644 --- a/uai/src/cmd.rs +++ b/uai/src/cmd.rs @@ -16,7 +16,7 @@ use std::fmt; use std::sync::{Arc, Mutex}; use std::thread; -use crate::Context; +use crate::{Context, ParameterValues}; use super::{Flag, FlagValues}; @@ -38,9 +38,14 @@ impl<T: Send + 'static, E: RunError + 'static> Command<T, E> { /// 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: &Arc<Mutex<T>>, flags: FlagValues) -> Result<(), E> { + pub fn run( + &self, + context: &Arc<Mutex<T>>, + flags: FlagValues, + options: ParameterValues, + ) -> Result<(), E> { // Clone values which might be moved by spawning a new thread. - let context = Context::new(context, flags); + let context = Context::new(context, flags, options); let func = self.run_fn; if self.parallel { diff --git a/uai/src/context.rs b/uai/src/context.rs index 49b1c4d..1649658 100644 --- a/uai/src/context.rs +++ b/uai/src/context.rs @@ -1,16 +1,21 @@ use std::sync::{Arc, Mutex, MutexGuard}; -use crate::FlagValues; +use crate::{FlagValues, Number, ParameterValues}; pub struct Context<T: Send> { context: Arc<Mutex<T>>, flags: FlagValues, + options: ParameterValues, } impl<T: Send> Context<T> { - pub fn new(context: &Arc<Mutex<T>>, flags: FlagValues) -> Context<T> { + pub fn new(context: &Arc<Mutex<T>>, flags: FlagValues, options: ParameterValues) -> Context<T> { let context = Arc::clone(context); - Context { context, flags } + Context { + context, + flags, + options, + } } pub fn lock(&self) -> MutexGuard<'_, T> { @@ -28,4 +33,16 @@ impl<T: Send> Context<T> { pub fn get_array_flag(&self, name: &str) -> Option<Vec<String>> { self.flags.get_array(name) } + + pub fn get_check_option(&self, name: &str) -> Option<bool> { + self.options.get_check(name) + } + + pub fn get_string_option(&self, name: &str) -> Option<String> { + self.options.get_string(name) + } + + pub fn get_spin_option(&self, name: &str) -> Option<Number> { + self.options.get_spin(name) + } } diff --git a/uai/src/inbuilt.rs b/uai/src/inbuilt.rs index 2328bb1..5fb3526 100644 --- a/uai/src/inbuilt.rs +++ b/uai/src/inbuilt.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; -use crate::{quit, Parameter, RunErrorType}; +use crate::{error, quit, Flag, Parameter, ParameterValues, RunErrorType}; use lazy_static::lazy_static; pub type Command = crate::Command<Context, RunErrorType>; @@ -21,24 +21,74 @@ pub type Command = crate::Command<Context, RunErrorType>; lazy_static! { pub static ref COMMANDS: HashMap<String, Command> = HashMap::from( [ - ("quit", Command::new(|_ctx, _flags| quit!())), + ("quit", Command::new(|_ctx| quit!())), ( "isready", - Command::new(|_ctx, _flags| { + Command::new(|_ctx| { println!("readyok"); Ok(()) }) ), ( "uai", - Command::new(|ctx, _flags| { - let ctx = ctx.lock().unwrap(); + Command::new(|ctx| { + let ctx = ctx.lock(); println!("id name {}", ctx.engine); println!("id author {}", ctx.author); println!(); + if !ctx.options.is_empty() { + for (name, option) in ctx.options.clone() { + println!("option name {} type {}", name, option); + } + + println!(); + } println!("uaiok"); + Ok(()) + }) + ), + ( + "setoption", + Command::new(|ctx| { + let name = ctx.get_single_flag("name"); + let value = ctx.get_array_flag("value"); + + if name.is_none() || value.is_none() { + return error!("expected \"name\" and \"value\" flags"); + } + + let name = name.unwrap(); + let value = value.unwrap().join(" "); + + let mut ctx = ctx.lock(); + + ctx.setoption(&name, &value).map_err(RunErrorType::Error) + }) + .flag("name", Flag::Single) + .flag("value", Flag::Variadic) + ), + ( + "options", + Command::new(|ctx| { + let ctx = ctx.lock(); + + for (name, option) in ctx.options.clone() { + print!("option name {} value ", name); + match option { + Parameter::Check(_) => { + println!("{}", ctx.option_values.get_check(&name).unwrap()) + } + Parameter::String(_) | Parameter::Combo(_, _) => { + println!("{}", ctx.option_values.get_string(&name).unwrap()) + } + Parameter::Spin(_, _, _) => { + println!("{}", ctx.option_values.get_spin(&name).unwrap()) + } + } + } + Ok(()) }) ) @@ -56,6 +106,19 @@ pub struct Context { pub author: String, pub options: HashMap<String, Parameter>, + pub option_values: ParameterValues, +} + +impl Context { + fn setoption(&mut self, name: &str, value: &str) -> Result<(), String> { + let option = self.options.get(name); + if option.is_none() { + return Err(format!("unknown option \"{}\"", name)); + } + + self.option_values + .insert(name.to_owned(), option.unwrap(), value) + } } impl Default for Context { @@ -64,6 +127,7 @@ impl Default for Context { engine: "Nameless v0.0.0".to_string(), author: "Anonymous".to_string(), options: HashMap::new(), + option_values: Default::default(), } } } diff --git a/uai/src/parameter.rs b/uai/src/parameter.rs index ba1d3d4..a6399a8 100644 --- a/uai/src/parameter.rs +++ b/uai/src/parameter.rs @@ -1,3 +1,8 @@ +use std::collections::HashMap; +use std::fmt; + +pub type Number = i32; + #[derive(Clone)] pub enum Parameter { /// Check represents a checkbox parameter which can be true or false. @@ -16,7 +21,7 @@ pub enum Parameter { /// value, and the third argument is the maximum value of this parameter. The /// minimum and maximum bounds are inclusive and and the default must be in /// the range defined by them. - Spin(i32, i32, i32), + Spin(Number, Number, Number), /// Combo represents a combo box which can have the value of one of the /// predefined strings. @@ -25,3 +30,115 @@ pub enum Parameter { /// list of predefined strings. The default value must be included in the list. Combo(String, Vec<String>), } + +#[derive(Clone, Default)] +pub struct ParameterValues { + checks: HashMap<String, bool>, + strings: HashMap<String, String>, + numbers: HashMap<String, Number>, +} + +impl ParameterValues { + pub fn get_check(&self, name: &str) -> Option<bool> { + self.checks.get(name).copied() + } + + pub fn get_string(&self, name: &str) -> Option<String> { + self.strings.get(name).cloned() + } + + pub fn get_spin(&self, name: &str) -> Option<Number> { + self.numbers.get(name).copied() + } +} + +impl ParameterValues { + pub fn insert( + &mut self, + name: String, + option: &Parameter, + value_str: &str, + ) -> Result<(), String> { + match option { + Parameter::Check(_) => { + let value = value_str.parse(); + if value.is_err() { + return Err(format!( + "option {}: expected boolean, received {}", + name, value_str + )); + } + self.checks.insert(name, value.unwrap()); + } + Parameter::String(_) => { + self.strings.insert(name, value_str.to_owned()); + } + Parameter::Spin(_, min, max) => { + let value = value_str.parse(); + if value.is_err() { + return Err(format!( + "option {}: expected a number, received {}", + name, value_str + )); + } + + let value = value.unwrap(); + if value < *min || value > *max { + return Err(format!( + "option {}: expected a number between {} and {} (inclusive), received {}", + name, min, max, value_str + )); + } + self.numbers.insert(name, value); + } + Parameter::Combo(_, strings) => { + let value = value_str.to_owned(); + if strings.contains(&value) { + return Err(format!( + "option {}: {} is not one of the combo strings", + name, value + )); + } + self.strings.insert(name, value); + } + }; + + Ok(()) + } + + pub fn insert_default(&mut self, name: String, option: &Parameter) { + match option { + Parameter::Check(default) => { + self.checks.insert(name, *default); + } + Parameter::String(default) => { + self.strings.insert(name, default.clone()); + } + Parameter::Spin(default, _, _) => { + self.numbers.insert(name, *default); + } + Parameter::Combo(default, _) => { + self.strings.insert(name, default.clone()); + } + }; + } +} + +impl fmt::Display for Parameter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Parameter::Check(default) => write!(f, "check default {}", default), + Parameter::String(default) => write!(f, "string default {}", default), + Parameter::Spin(default, min, max) => { + write!(f, "spin default {} min {} max {}", default, min, max) + } + Parameter::Combo(default, combos) => { + write!(f, "combo default {}", default)?; + for combo in combos { + write!(f, " var {}", combo)? + } + Ok(()) + } + } + } +}