///| Placeholder and environment expansion for rule actions.
///
/// A command may contain `{{name}}` placeholders; each is replaced by the
/// matching message value. The same values are exported to the child process
/// as `MAILS_*` environment variables, which is the safe way to pass a subject
/// into a shell: `{{subject}}` is inserted raw, while `{{subject_q}}` is
/// single-quoted for POSIX shells.
///|
pub struct ActionContext {
account : String
mailbox : String
message : MailMessage
rule : Rule
keywords : Array[String]
}
///|
fn placeholder_value(context : ActionContext, name : String) -> String? {
let message = context.message
match name {
"account" => Some(context.account)
"mailbox" => Some(context.mailbox)
"uid" => Some(message.uid)
"subject" => Some(message.subject)
"from" => Some(message.from)
"to" => Some(message.to)
"date" => Some(message.date)
"message_id" | "message-id" => Some(message.message_id)
"body" => Some(truncate_chars(message.body, context.rule.body_chars))
"rule" => Some(context.rule.name)
"keywords" => Some(context.keywords.join(", "))
"fields" =>
Some(
{
let out = StringBuilder()
let mut first = true
for field in context.rule.fields {
if !first {
out.write_char(',')
}
out.write_string(field.as_string())
first = false
}
out.to_string()
},
)
"account_q" => Some(shell_quote(context.account))
"mailbox_q" => Some(shell_quote(context.mailbox))
"uid_q" => Some(shell_quote(message.uid))
"subject_q" => Some(shell_quote(message.subject))
"from_q" => Some(shell_quote(message.from))
"to_q" => Some(shell_quote(message.to))
"date_q" => Some(shell_quote(message.date))
"body_q" =>
Some(shell_quote(truncate_chars(message.body, context.rule.body_chars)))
"rule_q" => Some(shell_quote(context.rule.name))
_ => None
}
}
///|
/// Expand every `{{name}}` placeholder in `template`.
pub fn render_template(template : String, context : ActionContext) -> String {
let out = StringBuilder()
let mut rest = template
let mut attempts = 0
while attempts < 256 {
attempts = attempts + 1
match rest.find("{{") {
None => {
out.write_string(rest)
break
}
Some(start) => {
out.write_string(rest[0:start].to_owned())
let after = rest[start + 2:].to_owned()
match after.find("}}") {
None => {
out.write_string(rest[start:].to_owned())
break
}
Some(end) => {
let name = trim(after[0:end].to_owned())
match placeholder_value(context, name) {
Some(value) => out.write_string(value)
None =>
out.write_string(rest[start:start + 2 + end + 2].to_owned())
}
rest = after[end + 2:].to_owned()
}
}
}
}
}
out.to_string()
}
///|
/// Environment variables exported to a rule command.
pub fn action_env(context : ActionContext) -> Map[String, String] {
let env : Map[String, String] = Map([])
let message = context.message
env.set("MAILS_ACCOUNT", context.account)
env.set("MAILS_MAILBOX", context.mailbox)
env.set("MAILS_UID", message.uid)
env.set("MAILS_SUBJECT", message.subject)
env.set("MAILS_FROM", message.from)
env.set("MAILS_TO", message.to)
env.set("MAILS_DATE", message.date)
env.set("MAILS_MESSAGE_ID", message.message_id)
env.set("MAILS_RULE", context.rule.name)
env.set("MAILS_KEYWORDS", context.keywords.join(", "))
env.set("MAILS_BODY", truncate_chars(message.body, context.rule.body_chars))
env.set("MAILS_BODY_FULL", message.body)
env
}