File size: 3,213 Bytes
bee6636
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::{cell::RefCell, error::Error, num::TryFromIntError};

use changes::HtmlChanges;
use oxc::allocator::Allocator;
use rule::RewriteRule;
use thiserror::Error;
use tl::ParserOptions;
use visitor::Visitor;

mod changes;
pub mod rule;
mod visitor;

pub use visitor::{VisitorExternalTool, VisitorExternalToolCallback};

#[derive(Debug, Error)]
pub enum RewriterError {
	#[error("tl: {0}")]
	Tl(#[from] tl::ParseError),
	#[error("transformer: {0}")]
	Transformer(#[from] transform::TransformError),

	#[error("rewrite function: {0}")]
	Rewrite(Box<dyn Error + Sync + Send>),
	#[error("external tool function: {0}")]
	ExternalTool(Box<dyn Error + Sync + Send>),
	#[error("external tool didn't return anything")]
	ExternalToolEmpty,

	#[error("Not utf8")]
	NotUtf8,
	#[error("usize too big")]
	ConversionFailed(#[from] TryFromIntError),
	#[error("Already rewriting")]
	AlreadyRewriting,
	#[error("Not rewriting")]
	NotRewriting,
	#[error("Changes left over")]
	Leftover,
}

pub struct Rewriter<T> {
	rules: Vec<RewriteRule<T>>,
	external_tool: VisitorExternalToolCallback<T>,

	changes: RefCell<Option<HtmlChanges<'static, 'static>>>,
}

impl<T> Rewriter<T> {
	pub fn new(
		rules: Vec<RewriteRule<T>>,
		external_tool: VisitorExternalToolCallback<T>,
	) -> Result<Self, RewriterError> {
		Ok(Self {
			rules,
			external_tool,
			changes: RefCell::new(Some(HtmlChanges::new())),
		})
	}

	fn take_changes<'alloc: 'data, 'data>(
		&'data self,
		alloc: &'alloc Allocator,
	) -> Result<HtmlChanges<'alloc, 'data>, RewriterError> {
		let mut slot = self
			.changes
			.try_borrow_mut()
			.map_err(|_| RewriterError::AlreadyRewriting)?;

		slot.take()
			.ok_or(RewriterError::AlreadyRewriting)
			.and_then(|x| {
				let mut x = unsafe {
					std::mem::transmute::<HtmlChanges<'static, 'static>, HtmlChanges<'alloc, 'data>>(
						x,
					)
				};
				x.set_alloc(alloc)?;
				Ok(x)
			})
	}

	fn put_changes<'alloc: 'data, 'data>(
		&'data self,
		mut changes: HtmlChanges<'alloc, 'data>,
	) -> Result<(), RewriterError> {
		if !changes.empty() {
			return Err(RewriterError::Leftover);
		}

		let mut slot = self
			.changes
			.try_borrow_mut()
			.map_err(|_| RewriterError::AlreadyRewriting)?;

		if slot.is_some() {
			Err(RewriterError::NotRewriting)
		} else {
			changes.take_alloc()?;

			let changes = unsafe {
				std::mem::transmute::<HtmlChanges<'alloc, 'data>, HtmlChanges<'static, 'static>>(
					changes,
				)
			};

			slot.replace(changes);

			Ok(())
		}
	}

	pub fn rewrite<'alloc: 'data, 'data>(
		&'data self,
		alloc: &'alloc Allocator,
		html: &'data str,
		data: &T,
		from_top: bool,
	) -> Result<oxc::allocator::Vec<'alloc, u8>, RewriterError> {
		let tree = tl::parse(html, ParserOptions::default())?;

		let mut changes = self.take_changes(alloc)?;

		let visitor = Visitor {
			alloc,
			rules: &self.rules,
			external_tool_func: &self.external_tool,
			rule_data: data,

			data: html,
			tree,
			from_top,
		};
		visitor.rewrite(&mut changes)?;

		let res = changes.perform(html)?;

		self.put_changes(changes)?;

		Ok(res.source)
	}
}