use crate::error::{ConfigError, ParseResult};
use std::collections::HashMap;
use std::rc::Rc;
type HandlerFn = Rc<dyn Fn(&HandlerContext) -> ParseResult<()>>;
pub struct HandlerContext {
pub category: Vec<String>,
pub keyword: String,
pub value: String,
pub flags: Option<String>,
}
impl HandlerContext {
pub fn new(keyword: String, value: String) -> Self {
Self {
category: Vec::new(),
keyword,
value,
flags: None,
}
}
pub fn with_category(mut self, category: Vec<String>) -> Self {
self.category = category;
self
}
pub fn with_flags(mut self, flags: String) -> Self {
self.flags = Some(flags);
self
}
pub fn category_path(&self) -> String {
self.category.join(":")
}
}
pub trait Handler: std::fmt::Debug {
fn handle(&self, context: &HandlerContext) -> ParseResult<()>;
fn name(&self) -> &str;
fn accepts_flags(&self) -> bool {
false
}
}
#[derive(Clone)]
pub struct FunctionHandler {
name: String,
accepts_flags: bool,
handler: HandlerFn,
}
impl FunctionHandler {
pub fn new<F>(name: impl Into<String>, handler: F) -> Self
where
F: Fn(&HandlerContext) -> ParseResult<()> + 'static,
{
Self {
name: name.into(),
accepts_flags: false,
handler: Rc::new(handler),
}
}
pub fn with_flags<F>(name: impl Into<String>, handler: F) -> Self
where
F: Fn(&HandlerContext) -> ParseResult<()> + 'static,
{
Self {
name: name.into(),
accepts_flags: true,
handler: Rc::new(handler),
}
}
}
impl Handler for FunctionHandler {
fn handle(&self, context: &HandlerContext) -> ParseResult<()> {
(self.handler)(context)
}
fn name(&self) -> &str {
&self.name
}
fn accepts_flags(&self) -> bool {
self.accepts_flags
}
}
impl std::fmt::Debug for FunctionHandler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FunctionHandler")
.field("name", &self.name)
.field("accepts_flags", &self.accepts_flags)
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HandlerScope {
Global,
Category,
}
pub struct ResolvedHandlerCall<'a> {
pub handler: &'a dyn Handler,
pub handler_keyword: String,
pub actual_keyword: String,
pub flags: Option<String>,
}
pub struct HandlerManager {
global_handlers: HashMap<String, Box<dyn Handler>>,
category_handlers: HashMap<String, HashMap<String, Box<dyn Handler>>>,
}
impl HandlerManager {
pub fn new() -> Self {
Self {
global_handlers: HashMap::new(),
category_handlers: HashMap::new(),
}
}
pub fn register_global<H>(&mut self, keyword: impl Into<String>, handler: H)
where
H: Handler + 'static,
{
self.global_handlers
.insert(keyword.into(), Box::new(handler));
}
pub fn register_category<H>(
&mut self,
category: impl Into<String>,
keyword: impl Into<String>,
handler: H,
) where
H: Handler + 'static,
{
self.category_handlers
.entry(category.into())
.or_default()
.insert(keyword.into(), Box::new(handler));
}
pub fn find_handler(&self, category_path: &[String], keyword: &str) -> Option<&dyn Handler> {
for i in (0..=category_path.len()).rev() {
let path = category_path[..i].join(":");
if let Some(handlers) = self.category_handlers.get(&path)
&& let Some(handler) = handlers.get(keyword)
{
return Some(handler.as_ref());
}
}
self.global_handlers.get(keyword).map(|h| h.as_ref())
}
fn resolve_in_map<'a>(
handlers: &'a HashMap<String, Box<dyn Handler>>,
keyword: &str,
) -> Option<ResolvedHandlerCall<'a>> {
if let Some(handler) = handlers.get(keyword) {
return Some(ResolvedHandlerCall {
handler: handler.as_ref(),
handler_keyword: keyword.to_string(),
actual_keyword: keyword.to_string(),
flags: None,
});
}
if keyword.contains(':') {
return None;
}
let mut best_match: Option<(&str, &dyn Handler)> = None;
for (name, handler) in handlers {
if !handler.accepts_flags() || !keyword.starts_with(name) {
continue;
}
match best_match {
Some((best_name, _)) if best_name.len() >= name.len() => {}
_ => best_match = Some((name.as_str(), handler.as_ref())),
}
}
best_match.map(|(name, handler)| ResolvedHandlerCall {
handler,
handler_keyword: name.to_string(),
actual_keyword: keyword.to_string(),
flags: Some(keyword[name.len()..].to_string()),
})
}
pub fn resolve_invocation<'a>(
&'a self,
category_path: &[String],
keyword: &str,
) -> Option<ResolvedHandlerCall<'a>> {
for i in (0..=category_path.len()).rev() {
let path = category_path[..i].join(":");
if let Some(handlers) = self.category_handlers.get(&path)
&& let Some(resolved) = Self::resolve_in_map(handlers, keyword)
{
return Some(resolved);
}
}
Self::resolve_in_map(&self.global_handlers, keyword)
}
pub fn has_handler(&self, category_path: &[String], keyword: &str) -> bool {
self.resolve_invocation(category_path, keyword).is_some()
}
pub fn execute(
&self,
category_path: &[String],
keyword: &str,
value: &str,
flags: Option<String>,
) -> ParseResult<()> {
let handler = self
.find_handler(category_path, keyword)
.ok_or_else(|| ConfigError::handler(keyword, "handler not found"))?;
if flags.is_some() && !handler.accepts_flags() {
return Err(ConfigError::handler(
keyword,
"handler does not accept flags",
));
}
let context = HandlerContext::new(keyword.to_string(), value.to_string())
.with_category(category_path.to_vec());
let context = if let Some(flags) = flags {
context.with_flags(flags)
} else {
context
};
handler.handle(&context)
}
pub fn execute_resolved(
&self,
category_path: &[String],
resolved: &ResolvedHandlerCall<'_>,
value: &str,
) -> ParseResult<()> {
if resolved.flags.is_some() && !resolved.handler.accepts_flags() {
return Err(ConfigError::handler(
&resolved.actual_keyword,
"handler does not accept flags",
));
}
let context = HandlerContext::new(resolved.actual_keyword.clone(), value.to_string())
.with_category(category_path.to_vec());
let context = if let Some(flags) = resolved.flags.clone() {
context.with_flags(flags)
} else {
context
};
resolved.handler.handle(&context)
}
pub fn unregister_global(&mut self, keyword: &str) -> bool {
self.global_handlers.remove(keyword).is_some()
}
pub fn unregister_category(&mut self, category: &str, keyword: &str) -> bool {
if let Some(handlers) = self.category_handlers.get_mut(category) {
handlers.remove(keyword).is_some()
} else {
false
}
}
pub fn clear(&mut self) {
self.global_handlers.clear();
self.category_handlers.clear();
}
pub fn global_keywords(&self) -> Vec<&str> {
self.global_handlers.keys().map(|s| s.as_str()).collect()
}
pub fn category_keywords(&self, category: &str) -> Vec<&str> {
self.category_handlers
.get(category)
.map(|handlers| handlers.keys().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
}
impl Default for HandlerManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_function_handler() {
let mut manager = HandlerManager::new();
let handler = FunctionHandler::new("test", |ctx| {
assert_eq!(ctx.keyword, "test");
assert_eq!(ctx.value, "value");
Ok(())
});
manager.register_global("test", handler);
assert!(manager.has_handler(&[], "test"));
manager.execute(&[], "test", "value", None).unwrap();
}
#[test]
fn test_handler_with_flags() {
let mut manager = HandlerManager::new();
let handler = FunctionHandler::with_flags("flagged", |ctx| {
assert_eq!(ctx.flags, Some("abc".to_string()));
Ok(())
});
manager.register_global("flagged", handler);
manager
.execute(&[], "flagged", "value", Some("abc".to_string()))
.unwrap();
}
#[test]
fn test_category_scoped_handler() {
let mut manager = HandlerManager::new();
let handler = FunctionHandler::new("scoped", |ctx| {
assert_eq!(ctx.category_path(), "category");
Ok(())
});
manager.register_category("category", "scoped", handler);
assert!(manager.has_handler(&["category".to_string()], "scoped"));
assert!(!manager.has_handler(&[], "scoped"));
manager
.execute(&["category".to_string()], "scoped", "value", None)
.unwrap();
}
#[test]
fn test_handler_precedence() {
let mut manager = HandlerManager::new();
let global = FunctionHandler::new("keyword", |_| {
panic!("Should not call global handler");
});
manager.register_global("keyword", global);
let category = FunctionHandler::new("keyword", |_| Ok(()));
manager.register_category("cat", "keyword", category);
manager
.execute(&["cat".to_string()], "keyword", "value", None)
.unwrap();
}
}