#![warn(missing_docs)]
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::BTreeSet;
use std::fmt;
use std::io::Write;
use std::ops::Deref;
use std::sync::{Arc, Mutex};
pub mod builder;
pub mod completion;
mod parse;
pub use self::parse::LineResult;
pub use builder::{BuildError, Builder, BuilderChain};
pub struct Commander<R> {
root: Arc<SubClass<R>>,
current: Arc<SubClass<R>>,
path: String,
}
impl<R> Commander<R> {
pub fn root_name(&self) -> &str {
&self.root.name
}
pub fn path(&self) -> &str {
&self.path
}
pub fn at_root(&self) -> bool {
self.current == self.root
}
#[cfg(feature = "runnable")]
pub fn run(self) {
self.run_with_completion(|_| linefeed::complete::DummyCompleter)
}
pub fn structure(&self, from_root: bool) -> BTreeSet<StructureInfo> {
let mut set = BTreeSet::new();
let mut stack: Vec<(String, _)> = {
let r = if from_root { &self.root } else { &self.current };
for action in r.actions.iter() {
set.insert(StructureInfo {
path: format!("..{}", action.name),
itemtype: ItemType::Action,
help_msg: action.help.clone(),
});
}
r.classes.iter().map(|x| (x.name.clone(), x)).collect()
};
while let Some(item) = stack.pop() {
let (parent_path, parent) = item;
for action in parent.actions.iter() {
set.insert(StructureInfo {
path: format!("{}..{}", parent_path, action.name),
itemtype: ItemType::Action,
help_msg: action.help.clone(),
});
}
for class in parent.classes.iter() {
stack.push((format!("{}.{}", parent_path, class.name), class));
}
set.insert(StructureInfo {
path: parent_path,
itemtype: ItemType::Class,
help_msg: parent.help.clone(),
});
}
set
}
}
#[derive(Debug, Eq)]
struct SubClass<R> {
name: String,
help: CmdStr,
classes: Vec<Arc<SubClass<R>>>,
actions: Vec<Action<R>>,
}
impl<R> SubClass<R> {
fn with_name<H: Into<CmdStr>>(name: &str, help_msg: H) -> Self {
SubClass {
name: name.to_lowercase(),
help: help_msg.into(),
classes: Vec::new(),
actions: Vec::new(),
}
}
}
impl<R> PartialEq for SubClass<R> {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.help == other.help
&& self.classes == other.classes
&& self.actions == other.actions
}
}
struct Action<R> {
name: String,
help: CmdStr,
closure: Mutex<Box<dyn FnMut(&mut dyn Write, &[&str]) -> R + Send>>,
}
impl<R> Action<R> {
fn call<W: Write>(&self, wtr: &mut W, arguments: &[&str]) -> R {
let c = &mut *self.closure.lock().expect("locking command action failed");
c(wtr, arguments)
}
}
impl Action<()> {
#[cfg(test)]
fn blank_fn<H: Into<CmdStr>>(name: &str, help_msg: H) -> Self {
Action {
name: name.to_lowercase(),
help: help_msg.into(),
closure: Mutex::new(Box::new(|_, _| ())),
}
}
}
impl<R> PartialEq for Action<R> {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.help == other.help
}
}
impl<R> Eq for Action<R> {}
impl<R> fmt::Debug for Action<R> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Action {{ name: {}, help: {} }}", self.name, self.help)
}
}
pub struct StructureInfo {
pub path: String,
pub itemtype: ItemType,
pub help_msg: CmdStr,
}
impl PartialEq for StructureInfo {
fn eq(&self, other: &StructureInfo) -> bool {
self.path == other.path
}
}
impl Eq for StructureInfo {}
impl PartialOrd for StructureInfo {
fn partial_cmp(&self, other: &StructureInfo) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StructureInfo {
fn cmp(&self, other: &StructureInfo) -> Ordering {
self.path.cmp(&other.path)
}
}
#[derive(Debug, PartialEq)]
pub enum ItemType {
Class,
Action,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CmdStr {
pub inner_cow: Cow<'static, str>,
}
impl CmdStr {
pub fn as_str(&self) -> &str {
&self.inner_cow
}
}
impl Deref for CmdStr {
type Target = str;
fn deref(&self) -> &str {
self.inner_cow.deref()
}
}
impl fmt::Display for CmdStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<&'static str> for CmdStr {
fn from(s: &'static str) -> Self {
Self {
inner_cow: Cow::Borrowed(s),
}
}
}
impl From<String> for CmdStr {
fn from(s: String) -> Self {
Self {
inner_cow: Cow::Owned(s),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subclass_with_name_test() {
let sc = SubClass::<()>::with_name("NAME", "Help Message");
assert_eq!(&sc.name, "name");
assert_eq!(sc.help.as_str(), "Help Message");
}
#[test]
fn action_debug_test() {
let a = Action::blank_fn("action-name", "help me!");
assert_eq!(
&format!("{:?}", a),
"Action { name: action-name, help: help me! }"
);
}
#[test]
fn current_path_test() {
let mut cmder = Builder::default_config("base")
.begin_class("one", "")
.begin_class("two", "")
.into_commander()
.unwrap();
let w = &mut std::io::sink();
assert_eq!(cmder.path(), "base");
cmder.parse_line("one two", true, w);
assert_eq!(cmder.path(), "base.one.two");
cmder.parse_line("c", true, w);
assert_eq!(cmder.path(), "base");
cmder.parse_line("one", true, w);
assert_eq!(cmder.path(), "base.one");
}
#[test]
fn root_test() {
let mut cmder = Builder::default_config("base")
.begin_class("one", "")
.begin_class("two", "")
.into_commander()
.unwrap();
let w = &mut std::io::sink();
assert_eq!(cmder.at_root(), true);
cmder.parse_line("one two", true, w);
assert_eq!(cmder.at_root(), false);
cmder.parse_line("c", true, w);
assert_eq!(cmder.at_root(), true);
}
#[test]
fn structure_test() {
let mut cmder = Builder::default_config("base")
.begin_class("one", "")
.begin_class("two", "")
.end_class()
.add_action("action", "", |_, _| ())
.end_class()
.add_action("action", "", |_, _| ())
.into_commander()
.unwrap();
cmder.parse_line("one", false, &mut std::io::sink());
let structure = cmder.structure(true);
assert_eq!(
structure
.iter()
.map(|x| x.path.as_str())
.collect::<Vec<_>>(),
vec!["..action", "one", "one..action", "one.two",]
);
let structure = cmder.structure(false);
assert_eq!(
structure
.iter()
.map(|x| x.path.as_str())
.collect::<Vec<_>>(),
vec!["..action", "two",]
);
}
}