use std::fmt;
use super::originate::OriginateError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecuteOn {
app: String,
arg: Option<String>,
}
impl ExecuteOn {
pub fn new(
app: impl Into<String>,
arg: Option<impl Into<String>>,
) -> Result<Self, OriginateError> {
let app = app.into();
let arg = arg.map(Into::into);
if app.is_empty() {
return Err(OriginateError::ParseError(
"execute_on application name is empty".into(),
));
}
if app.contains([' ', ':']) {
return Err(OriginateError::ParseError(format!(
"execute_on application name {app:?} carries a space or colon, where the \
switch splits the name from its argument"
)));
}
if let Some(arg) = &arg {
if arg.contains("${") {
return Err(OriginateError::ParseError(format!(
"execute_on argument for {app} carries `${{`, which the switch expands \
before the application sees it; pass a path, not content"
)));
}
if arg.contains(['\n', '\r']) {
return Err(OriginateError::ParseError(format!(
"execute_on argument for {app} carries a line break"
)));
}
}
Ok(Self { app, arg })
}
pub fn lua(
script: impl AsRef<str>,
args: impl IntoIterator<Item = impl AsRef<str>>,
) -> Result<Self, OriginateError> {
let mut words = vec![script
.as_ref()
.to_string()];
words.extend(
args.into_iter()
.map(|a| {
a.as_ref()
.to_string()
}),
);
if let Some(word) = words
.iter()
.find(|w| w.is_empty() || w.contains(' '))
{
return Err(OriginateError::ParseError(format!(
"lua argument {word:?} is empty or carries a space, on which mod_lua splits \
its argument list"
)));
}
Self::new("lua", Some(words.join(" ")))
}
pub fn app(&self) -> &str {
&self.app
}
pub fn arg(&self) -> Option<&str> {
self.arg
.as_deref()
}
}
impl fmt::Display for ExecuteOn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.arg {
Some(arg) => write!(f, "{} {}", self.app, arg),
None => f.write_str(&self.app),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn renders_app_and_argument() {
let hook = ExecuteOn::new("set", Some("probe=hooked")).unwrap();
assert_eq!(hook.to_string(), "set probe=hooked");
assert_eq!(hook.app(), "set");
assert_eq!(hook.arg(), Some("probe=hooked"));
let bare = ExecuteOn::new("answer", None::<String>).unwrap();
assert_eq!(bare.to_string(), "answer");
assert_eq!(bare.arg(), None);
}
#[test]
fn lua_joins_script_and_arguments() {
let hook = ExecuteOn::lua("/run/app/load.lua", ["/run/app/doc.xml", "pidf"]).unwrap();
assert_eq!(
hook.to_string(),
"lua /run/app/load.lua /run/app/doc.xml pidf"
);
}
#[test]
fn refuses_a_name_the_switch_would_split() {
assert!(ExecuteOn::new("lua:x", None::<String>).is_err());
assert!(ExecuteOn::new("my app", None::<String>).is_err());
assert!(ExecuteOn::new("", None::<String>).is_err());
}
#[test]
fn refuses_an_argument_the_switch_would_expand_or_the_wire_cannot_carry() {
assert!(ExecuteOn::new("set", Some("k=${v}")).is_err());
assert!(ExecuteOn::new("set", Some("k=a\nb")).is_err());
}
#[test]
fn lua_refuses_a_path_with_a_space() {
assert!(ExecuteOn::lua("/run/my app/load.lua", ["/run/doc.xml"]).is_err());
assert!(ExecuteOn::lua("/run/load.lua", ["/run/my doc.xml"]).is_err());
assert!(ExecuteOn::lua("/run/load.lua", [""]).is_err());
}
}