use super::*;
#[derive(Debug, PartialEq)]
pub struct Builder<'a, R> {
parents: Vec<SubClass<'a, R>>,
current: SubClass<'a, R>,
}
pub trait BuilderChain<'a, R> {
fn begin_class(self, name: &str, help_msg: &'a str) -> BuilderResult<'a, R>;
fn end_class(self) -> BuilderResult<'a, R>;
fn add_action<F: FnMut(&mut dyn Write, &[&str]) -> R + Send + 'a>(
self,
name: &str,
help_msg: &'a str,
closure: F,
) -> BuilderResult<'a, R>;
fn root(self) -> BuilderResult<'a, R>;
fn into_commander<'c>(self) -> Result<Commander<'a, R>, BuildError>;
}
pub type BuilderResult<'a, R> = Result<Builder<'a, R>, BuildError>;
impl<'a> Builder<'a, ()> {
pub fn default_config(root_name: &str) -> Self {
Builder::<()>::new(root_name)
}
}
impl<'a, R> Builder<'a, R> {
pub fn new(root_name: &str) -> Self {
Builder {
parents: Vec::new(),
current: SubClass::with_name(root_name, "base class of commander tree"),
}
}
}
impl<'a, R> BuilderChain<'a, R> for Builder<'a, R> {
fn begin_class(mut self, name: &str, help_msg: &'a str) -> BuilderResult<'a, R> {
check_names(name, &self.current).map(|_| {
self.parents.push(self.current);
self.current = SubClass::with_name(name, help_msg);
self
})
}
fn end_class(mut self) -> BuilderResult<'a, R> {
let mut parent = self.parents.pop().ok_or(BuildError::NoParent)?;
parent.classes.push(Arc::new(self.current));
self.current = parent;
Ok(self)
}
fn root(self) -> BuilderResult<'a, R> {
let mut root = self;
while root.parents.len() > 0 {
root = root.end_class().expect("shouldn't dip below zero parents");
}
Ok(root)
}
fn add_action<F>(mut self, name: &str, help_msg: &'a str, closure: F) -> BuilderResult<'a, R>
where
F: FnMut(&mut dyn Write, &[&str]) -> R + Send + 'a,
{
check_names(name, &self.current).map(|_| {
self.current.actions.push(Action {
name: name.to_lowercase(),
help: help_msg,
closure: Mutex::new(Box::new(closure)),
});
self
})
}
fn into_commander<'c>(self) -> Result<Commander<'a, R>, BuildError> {
let root = self.root()?;
let rc = Arc::new(root.current);
Ok(Commander {
root: Arc::clone(&rc),
current: Arc::clone(&rc),
path: rc.name.to_string(),
})
}
}
impl<'a, R> BuilderChain<'a, R> for BuilderResult<'a, R> {
fn begin_class(self, name: &str, help_msg: &'a str) -> BuilderResult<'a, R> {
self?.begin_class(name, help_msg)
}
fn end_class(self) -> BuilderResult<'a, R> {
self?.end_class()
}
fn root(self) -> BuilderResult<'a, R> {
self?.root()
}
fn add_action<F: FnMut(&mut dyn Write, &[&str]) -> R + Send + 'a>(
self,
name: &str,
help_msg: &'a str,
closure: F,
) -> BuilderResult<'a, R> {
self?.add_action(name, help_msg, closure)
}
fn into_commander<'c>(self) -> Result<Commander<'a, R>, BuildError> {
self?.into_commander()
}
}
fn check_names<R>(name: &str, subclass: &SubClass<'_, R>) -> Result<(), BuildError> {
let lwr = name.to_lowercase();
if lwr == "help" || lwr == "cancel" || lwr == "c" || lwr == "exit" {
Err(BuildError::NameExistsAsAction)
} else if subclass.actions.iter().any(|x| x.name == lwr) {
Err(BuildError::NameExistsAsAction)
} else if subclass.classes.iter().any(|x| x.name == lwr) {
Err(BuildError::NameExistsAsClass)
} else {
Ok(())
}
}
#[derive(Debug, PartialEq)]
pub enum BuildError {
NameExistsAsClass,
NameExistsAsAction,
NoParent,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_names_test() {
let mut sc = SubClass::with_name("name", "adsf");
assert_eq!(check_names("name1", &sc), Ok(()));
sc.classes
.push(Arc::new(SubClass::with_name("sub-name", "asdf")));
assert_eq!(check_names("name1", &sc), Ok(()));
assert_eq!(
check_names("sub-name", &sc),
Err(BuildError::NameExistsAsClass)
);
sc.actions.push(Action {
name: "name1".to_string(),
help: "adf",
closure: Mutex::new(Box::new(|_, _| ())),
});
assert_eq!(
check_names("name1", &sc),
Err(BuildError::NameExistsAsAction)
);
}
#[test]
fn no_help_cancel_or_exit_classes() {
let cmdr = Builder::default_config("adf").begin_class("help", "shouldn't work");
assert_eq!(cmdr, Err(BuildError::NameExistsAsAction));
}
#[test]
fn builder_root_test() {
let cmdr = Builder::default_config("root")
.begin_class("adsf", "adf")
.begin_class("adsf", "adsf")
.begin_class("asdf", "adsf")
.root()
.unwrap();
assert_eq!(cmdr.parents.len(), 0);
assert_eq!(cmdr.current.name, "root");
}
}