use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use core::fmt;
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;
pub mod sections;
pub mod tags;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TagResult {
Processed,
Ignored,
Failed(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SectionResult {
Processed,
Ignored,
Failed(String),
}
pub trait TagHandler: Send + Sync {
fn name(&self) -> &'static str;
fn process(&self, args: &str) -> TagResult;
fn validate(&self, args: &str) -> bool {
!args.is_empty()
}
}
pub trait SectionProcessor: Send + Sync {
fn name(&self) -> &'static str;
fn process(&self, header: &str, lines: &[&str]) -> SectionResult;
fn validate(&self, header: &str, lines: &[&str]) -> bool {
!header.is_empty() && !lines.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PluginError {
DuplicateHandler(String),
HandlerNotFound(String),
ProcessingFailed(String),
InvalidConfig(String),
}
impl fmt::Display for PluginError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateHandler(name) => {
write!(f, "Handler '{name}' already registered")
}
Self::HandlerNotFound(name) => {
write!(f, "Handler '{name}' not found")
}
Self::ProcessingFailed(msg) => {
write!(f, "Plugin processing failed: {msg}")
}
Self::InvalidConfig(msg) => {
write!(f, "Invalid plugin configuration: {msg}")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PluginError {}
pub struct ExtensionRegistry {
tag_handlers: HashMap<String, Box<dyn TagHandler>>,
section_processors: HashMap<String, Box<dyn SectionProcessor>>,
}
impl ExtensionRegistry {
#[must_use]
pub fn new() -> Self {
Self {
tag_handlers: HashMap::new(),
section_processors: HashMap::new(),
}
}
pub fn register_tag_handler(&mut self, handler: Box<dyn TagHandler>) -> Result<()> {
let name = handler.name().to_string();
if self.tag_handlers.contains_key(&name) {
return Err(PluginError::DuplicateHandler(name));
}
self.tag_handlers.insert(name, handler);
Ok(())
}
pub fn register_section_processor(
&mut self,
processor: Box<dyn SectionProcessor>,
) -> Result<()> {
let name = processor.name().to_string();
if self.section_processors.contains_key(&name) {
return Err(PluginError::DuplicateHandler(name));
}
self.section_processors.insert(name, processor);
Ok(())
}
#[must_use]
pub fn process_tag(&self, tag_name: &str, args: &str) -> Option<TagResult> {
self.tag_handlers
.get(tag_name)
.map(|handler| handler.process(args))
}
#[must_use]
pub fn process_section(
&self,
section_name: &str,
header: &str,
lines: &[&str],
) -> Option<SectionResult> {
self.section_processors
.get(section_name)
.map(|processor| processor.process(header, lines))
}
#[must_use]
pub fn tag_handler_names(&self) -> Vec<&str> {
self.tag_handlers.keys().map(String::as_str).collect()
}
#[must_use]
pub fn section_processor_names(&self) -> Vec<&str> {
self.section_processors.keys().map(String::as_str).collect()
}
#[must_use]
pub fn has_tag_handler(&self, name: &str) -> bool {
self.tag_handlers.contains_key(name)
}
#[must_use]
pub fn has_section_processor(&self, name: &str) -> bool {
self.section_processors.contains_key(name)
}
pub fn remove_tag_handler(&mut self, name: &str) -> Option<Box<dyn TagHandler>> {
self.tag_handlers.remove(name)
}
pub fn remove_section_processor(&mut self, name: &str) -> Option<Box<dyn SectionProcessor>> {
self.section_processors.remove(name)
}
pub fn clear(&mut self) {
self.tag_handlers.clear();
self.section_processors.clear();
}
#[must_use]
pub fn extension_count(&self) -> usize {
self.tag_handlers.len() + self.section_processors.len()
}
}
impl Default for ExtensionRegistry {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for ExtensionRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExtensionRegistry")
.field("tag_handlers", &self.tag_handler_names())
.field("section_processors", &self.section_processor_names())
.finish()
}
}
pub type Result<T> = core::result::Result<T, PluginError>;
pub use sections::aegisub::{
create_aegisub_processors, AegisubExtradataProcessor, AegisubProjectProcessor,
};
pub use tags::{
advanced::{create_advanced_handlers, BlurEdgesTagHandler, BorderTagHandler, ShadowTagHandler},
alignment::{
create_alignment_handlers, AlignmentTagHandler, NumpadAlignmentTagHandler,
WrappingStyleTagHandler,
},
animation::{
create_animation_handlers, FadeTagHandler, SimpleFadeTagHandler, TransformTagHandler,
},
clipping::{create_clipping_handlers, ClipTagHandler},
color::{
create_color_handlers, Alpha1TagHandler, Alpha2TagHandler, Alpha3TagHandler,
Alpha4TagHandler, AlphaTagHandler, Color1TagHandler, Color2TagHandler, Color3TagHandler,
Color4TagHandler, PrimaryColorTagHandler,
},
font::{create_font_handlers, FontEncodingTagHandler, FontNameTagHandler, FontSizeTagHandler},
formatting::{
create_formatting_handlers, BoldTagHandler, ItalicTagHandler, StrikeoutTagHandler,
UnderlineTagHandler,
},
karaoke::{
create_karaoke_handlers, BasicKaraokeTagHandler, FillKaraokeTagHandler,
KaraokeTimingTagHandler, OutlineKaraokeTagHandler,
},
misc::{create_misc_handlers, OriginTagHandler, ResetTagHandler, ShortRotationTagHandler},
position::{create_position_handlers, MoveTagHandler, PositionTagHandler},
special::{
create_special_handlers, HardLineBreakTagHandler, HardSpaceTagHandler,
SoftLineBreakTagHandler,
},
transform::{
create_transform_handlers, RotationXTagHandler, RotationYTagHandler, RotationZTagHandler,
ScaleXTagHandler, ScaleYTagHandler, ShearXTagHandler, ShearYTagHandler, SpacingTagHandler,
},
};