use crate::runner::runner::RunnerResult;
use crate::tokenizer::byte::tokenize;
use crate::tokenizer::plugin::TokenizerOptions;
use crate::tokenizer::plugin::TokenizerPlugin;
pub struct TokenizerRunner<'a, 'bump> {
bump: &'bump bumpalo::Bump,
pipeline: Vec<Box<dyn TokenizerPlugin + 'bump>>,
options: &'a TokenizerOptions,
}
impl<'a, 'bump> TokenizerRunner<'a, 'bump> {
pub fn new(
bump: &'bump bumpalo::Bump,
options: &'a TokenizerOptions,
pipeline: Vec<Box<dyn TokenizerPlugin + 'bump>>,
) -> Self {
Self {
bump,
pipeline,
options,
}
}
pub fn run(&mut self, input: &'a str) {
let bytes = input.as_bytes();
let context = crate::tokenizer::plugin::TokenizerPluginContext {
runner: &crate::runner::runner::RunnerContext::new(
self.bump,
input,
bytes,
self.options,
),
position: crate::cursor::position::PositionResolver::new(input),
};
tokenize(bytes, |mut event| {
for plugin in &mut self.pipeline {
match plugin.process_event(&event, &context) {
RunnerResult::Replace(new_ev) => {
event = new_ev; }
RunnerResult::Keep => {
}
RunnerResult::Drop => {
return; }
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runner::runner::RunnerResult;
use crate::tokenizer::plugin::{TokenizerOptions, TokenizerPlugin, TokenizerPluginContext};
use crate::tokenizer::Event;
use bumpalo::Bump;
use std::cell::RefCell;
use std::rc::Rc;
fn default_options() -> TokenizerOptions {
TokenizerOptions {
keep_whitespace: false,
lower_idents: false,
delimiter_close_len: 2,
delimiter_open_len: 2,
}
}
struct CollectorPlugin {
events: Rc<RefCell<Vec<Event<'static>>>>,
}
impl CollectorPlugin {
fn new(events: Rc<RefCell<Vec<Event<'static>>>>) -> Self {
Self { events }
}
}
impl TokenizerPlugin for CollectorPlugin {
fn name(&self) -> &str {
"collector"
}
fn process_event<'a, 'bump>(
&mut self,
event: &Event<'static>,
_ctx: &TokenizerPluginContext<'a, 'bump>,
) -> RunnerResult<Event<'static>> {
self.events.borrow_mut().push(event.clone());
RunnerResult::Keep
}
}
struct TextDropperPlugin;
impl TokenizerPlugin for TextDropperPlugin {
fn name(&self) -> &str {
"text_dropper"
}
fn process_event<'a, 'bump>(
&mut self,
event: &Event<'static>,
_ctx: &TokenizerPluginContext<'a, 'bump>,
) -> RunnerResult<Event<'static>> {
match event {
Event::Text { .. } => RunnerResult::Drop,
_ => RunnerResult::Keep,
}
}
}
struct TextModifierPlugin;
impl TokenizerPlugin for TextModifierPlugin {
fn name(&self) -> &str {
"text_modifier"
}
fn process_event<'a, 'bump>(
&mut self,
event: &Event<'static>,
_ctx: &TokenizerPluginContext<'a, 'bump>,
) -> RunnerResult<Event<'static>> {
match event {
Event::Text { start, end } => {
RunnerResult::Replace(Event::Text {
start: start + 1000,
end: *end,
})
}
_ => RunnerResult::Keep,
}
}
}
#[test]
fn test_runner_new() {
let bump = Bump::new();
let options = default_options();
let pipeline: Vec<Box<dyn TokenizerPlugin>> = vec![];
let runner = TokenizerRunner::new(&bump, &options, pipeline);
assert!(runner.pipeline.is_empty());
}
#[test]
fn test_runner_empty_pipeline() {
let bump = Bump::new();
let options = default_options();
let pipeline: Vec<Box<dyn TokenizerPlugin>> = vec![];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("hello world");
}
#[test]
fn test_runner_with_collector_simple_text() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("hello");
let collected = events.borrow();
assert!(!collected.is_empty());
assert!(collected.iter().any(|e| matches!(e, Event::Text { .. })));
assert!(collected.iter().any(|e| matches!(e, Event::End)));
}
#[test]
fn test_runner_with_collector_html_tag() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("<div>content</div>");
let collected = events.borrow();
assert!(collected
.iter()
.any(|e| matches!(e, Event::OpenTagName { .. })));
assert!(collected
.iter()
.any(|e| matches!(e, Event::OpenTagEnd { .. })));
assert!(collected.iter().any(|e| matches!(e, Event::Text { .. })));
assert!(collected
.iter()
.any(|e| matches!(e, Event::CloseTag { .. })));
assert!(collected.iter().any(|e| matches!(e, Event::End)));
}
#[test]
fn test_runner_drop_text_events() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> = vec![
Box::new(TextDropperPlugin),
Box::new(CollectorPlugin::new(events.clone())),
];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("hello world");
let collected = events.borrow();
assert!(!collected.iter().any(|e| matches!(e, Event::Text { .. })));
assert!(collected.iter().any(|e| matches!(e, Event::End)));
}
#[test]
fn test_runner_replace_text_events() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> = vec![
Box::new(TextModifierPlugin),
Box::new(CollectorPlugin::new(events.clone())),
];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("hello");
let collected = events.borrow();
for event in collected.iter() {
if let Event::Text { start, .. } = event {
assert!(
*start >= 1000,
"Text event start should be >= 1000 after modification"
);
}
}
}
#[test]
fn test_runner_with_interpolation() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("{{ message }}");
let collected = events.borrow();
assert!(collected
.iter()
.any(|e| matches!(e, Event::Interpolation { .. })));
}
#[test]
fn test_runner_with_directive() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("<div v-if=\"show\"></div>");
let collected = events.borrow();
assert!(collected.iter().any(|e| matches!(e, Event::DirName { .. })));
}
#[test]
fn test_runner_self_closing_tag() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("<br/>");
let collected = events.borrow();
assert!(collected
.iter()
.any(|e| matches!(e, Event::SelfClosingTag { .. })));
}
#[test]
fn test_runner_comment() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("<!-- comment -->");
let collected = events.borrow();
assert!(collected.iter().any(|e| matches!(e, Event::Comment { .. })));
}
#[test]
fn test_runner_multiple_plugins_pipeline_order() {
let bump = Bump::new();
let options = default_options();
let events1 = Rc::new(RefCell::new(Vec::new()));
let events2 = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> = vec![
Box::new(CollectorPlugin::new(events1.clone())),
Box::new(CollectorPlugin::new(events2.clone())),
];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("test");
let collected1 = events1.borrow();
let collected2 = events2.borrow();
assert_eq!(collected1.len(), collected2.len());
for (e1, e2) in collected1.iter().zip(collected2.iter()) {
assert_eq!(e1, e2);
}
}
#[test]
fn test_runner_empty_input() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("");
let collected = events.borrow();
assert_eq!(collected.len(), 1);
assert!(matches!(collected[0], Event::End));
}
#[test]
fn test_runner_with_attributes() {
let bump = Bump::new();
let options = default_options();
let events = Rc::new(RefCell::new(Vec::new()));
let pipeline: Vec<Box<dyn TokenizerPlugin>> =
vec![Box::new(CollectorPlugin::new(events.clone()))];
let mut runner = TokenizerRunner::new(&bump, &options, pipeline);
runner.run("<div class=\"container\" id=\"main\"></div>");
let collected = events.borrow();
assert!(collected
.iter()
.any(|e| matches!(e, Event::AttribName { .. })));
assert!(collected
.iter()
.any(|e| matches!(e, Event::AttribData { .. })));
}
}