use {
crate::{
DatalineWriter, DatastarEvent,
consts::{self, ElementPatchMode},
},
core::{fmt, time::Duration},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExecuteScript {
pub id: Option<String>,
pub retry: Duration,
pub script: String,
pub auto_remove: Option<bool>,
pub attributes: Vec<String>,
}
impl ExecuteScript {
pub fn new(script: impl Into<String>) -> Self {
Self {
id: None,
retry: Duration::from_millis(consts::DEFAULT_SSE_RETRY_DURATION),
script: script.into(),
auto_remove: Default::default(),
attributes: Default::default(),
}
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn retry(mut self, retry: Duration) -> Self {
self.retry = retry;
self
}
pub fn auto_remove(mut self, auto_remove: bool) -> Self {
self.auto_remove = Some(auto_remove);
self
}
pub fn attributes(mut self, attributes: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.attributes = attributes.into_iter().map(Into::into).collect();
self
}
#[inline]
pub fn into_datastar_event(mut self) -> DatastarEvent {
let id = self.id.take();
self.convert_to_datastar_event_inner(id)
}
#[inline]
pub fn as_datastar_event(&self) -> DatastarEvent {
self.convert_to_datastar_event_inner(self.id.clone())
}
fn convert_to_datastar_event_inner(&self, id: Option<String>) -> DatastarEvent {
let mut data: Vec<String> = Vec::new();
self.write_datalines(&mut data)
.expect("writing datalines to a Vec cannot fail");
DatastarEvent {
event: consts::EventType::PatchElements,
id,
retry: self.retry,
data,
}
}
pub(crate) fn write_datalines(&self, writer: &mut impl DatalineWriter) -> fmt::Result {
writer.write_dataline(format_args!("{} body", consts::SELECTOR_DATALINE_LITERAL))?;
writer.write_dataline(format_args!(
"{} {}",
consts::MODE_DATALINE_LITERAL,
ElementPatchMode::Append.as_str(),
))?;
let mut script_lines = self.script.lines().peekable();
let first_line = script_lines.next().unwrap_or_default();
let close = script_lines.peek().is_none();
writer.write_dataline(format_args!(
"{} {}",
consts::ELEMENTS_DATALINE_LITERAL,
ScriptOpeningLine {
script: self,
first_line,
close,
}
))?;
while let Some(line) = script_lines.next() {
let closing_tag = if script_lines.peek().is_none() {
"</script>"
} else {
""
};
writer.write_dataline(format_args!(
"{} {}{}",
consts::ELEMENTS_DATALINE_LITERAL,
line,
closing_tag
))?;
}
Ok(())
}
}
struct ScriptOpeningLine<'a> {
script: &'a ExecuteScript,
first_line: &'a str,
close: bool,
}
impl fmt::Display for ScriptOpeningLine<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<script")?;
if self.script.auto_remove.unwrap_or(true) {
f.write_str(r##" data-effect="el.remove()""##)?;
}
for attribute in &self.script.attributes {
f.write_str(" ")?;
f.write_str(attribute)?;
}
f.write_str(">")?;
f.write_str(self.first_line)?;
if self.close {
f.write_str("</script>")?;
}
Ok(())
}
}
impl From<&ExecuteScript> for DatastarEvent {
#[inline]
fn from(val: &ExecuteScript) -> Self {
val.as_datastar_event()
}
}
impl From<ExecuteScript> for DatastarEvent {
#[inline]
fn from(val: ExecuteScript) -> Self {
val.into_datastar_event()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn expected_data() -> Vec<String> {
vec![
"selector body".into(),
"mode append".into(),
"elements <script type=\"module\">first".into(),
"elements second</script>".into(),
]
}
#[test]
fn serializes_script_options_and_conversions() {
let script = ExecuteScript::new("first\nsecond")
.id("script-1")
.retry(Duration::from_millis(2_000))
.auto_remove(false)
.attributes([r#"type="module""#]);
let borrowed = script.as_datastar_event();
assert_eq!(borrowed.id.as_deref(), Some("script-1"));
assert_eq!(borrowed.retry, Duration::from_millis(2_000));
assert_eq!(borrowed.data, expected_data());
assert_eq!(DatastarEvent::from(&script).data, expected_data());
assert_eq!(DatastarEvent::from(script).data, expected_data());
}
#[test]
fn serializes_empty_script_with_defaults() {
let event = ExecuteScript::new("").into_datastar_event();
assert_eq!(
event.data,
[
"selector body",
"mode append",
r#"elements <script data-effect="el.remove()"></script>"#,
]
);
}
}