mod glyph;
mod traits;
pub use glyph::{Color, Glyph, Presentation};
pub use traits::{Category, Scope, Tag};
#[cfg(feature = "render")]
pub mod render;
use std::collections::HashMap;
use std::io::{self, BufRead, Write};
#[derive(Clone, Copy, Debug)]
pub struct Example {
pub command: &'static str,
pub description: &'static str,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Confirmation {
TypeToken {
message: &'static str,
token: &'static str,
},
YesNo { message: &'static str },
}
impl Confirmation {
pub fn prompt<R: BufRead, W: Write>(&self, reader: &mut R, writer: &mut W) -> io::Result<bool> {
match self {
Confirmation::TypeToken { message, token } => {
writeln!(writer, "{message}")?;
write!(writer, "Type {token} to continue: ")?;
writer.flush()?;
let mut line = String::new();
reader.read_line(&mut line)?;
Ok(line.trim() == *token)
}
Confirmation::YesNo { message } => {
write!(writer, "{message} [y/N] ")?;
writer.flush()?;
let mut line = String::new();
reader.read_line(&mut line)?;
let answer = line.trim().to_ascii_lowercase();
Ok(answer == "y" || answer == "yes")
}
}
}
pub fn prompt_stdio(&self) -> io::Result<bool> {
let mut stdin = io::stdin().lock();
let mut stderr = io::stderr();
self.prompt(&mut stdin, &mut stderr)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ApiEndpoint {
pub method: &'static str,
pub path: &'static str,
}
#[derive(Clone, Copy, Debug)]
pub struct CommandDef<C: Category, T: Tag, S: Scope> {
pub name: &'static str,
pub summary: &'static str,
pub category: C,
pub tags: &'static [T],
pub scope: S,
pub examples: &'static [Example],
pub see_also: &'static [&'static str],
pub long_description: &'static str,
pub api: &'static [ApiEndpoint],
pub confirmation: Option<Confirmation>,
}
impl<C: Category, T: Tag, S: Scope> CommandDef<C, T, S> {
pub fn requires_confirmation(&self) -> bool {
self.confirmation.is_some()
}
pub fn gate(&self) -> io::Result<bool> {
match &self.confirmation {
None => Ok(true),
Some(c) => c.prompt_stdio(),
}
}
}
#[derive(Default)]
pub struct CommandManifest<C: Category, T: Tag, S: Scope> {
commands: HashMap<&'static str, CommandDef<C, T, S>>,
}
impl<C: Category, T: Tag, S: Scope> CommandManifest<C, T, S> {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
}
}
pub fn add(&mut self, def: CommandDef<C, T, S>) -> &mut Self {
let previous = self.commands.insert(def.name, def);
debug_assert!(previous.is_none(), "duplicate command name: {}", def.name);
self
}
pub fn get(&self, name: &str) -> Option<&CommandDef<C, T, S>> {
self.commands.get(name)
}
pub fn all_sorted(&self) -> Vec<&CommandDef<C, T, S>> {
let mut items: Vec<_> = self.commands.values().collect();
items.sort_by_key(|def| (def.category.order(), def.name));
items
}
pub fn by_category(&self, cat: C) -> Vec<&CommandDef<C, T, S>> {
let mut items: Vec<_> = self
.commands
.values()
.filter(|def| def.category == cat)
.collect();
items.sort_by_key(|def| def.name);
items
}
pub fn by_tag(&self, tag: T) -> Vec<&CommandDef<C, T, S>> {
let mut items: Vec<_> = self
.commands
.values()
.filter(|def| def.tags.contains(&tag))
.collect();
items.sort_by_key(|def| def.name);
items
}
pub fn by_scope(&self, scope: S) -> Vec<&CommandDef<C, T, S>> {
let mut items: Vec<_> = self
.commands
.values()
.filter(|def| def.scope == scope)
.collect();
items.sort_by_key(|def| def.name);
items
}
pub fn categories_in_order(&self) -> Vec<C> {
let mut categories = Vec::new();
for def in self.commands.values() {
if !categories.contains(&def.category) {
categories.push(def.category);
}
}
categories.sort_by_key(|cat| cat.order());
categories
}
}