Files
jcoffey-dev 136fc2540b
ci / fork-checks (pull_request) Skipped
ci / build (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 7m45s
Sieve playground: the server's interpreter in the browser
tools/sieve-playground builds the vendored sieve-rs 0.7.3 (vnd.inbuxa.*
names included) to WebAssembly, with a static page around it, for inbuxa
Admin's Debug button on a Sieve script: until now that button sent the
script to an outside site whose interpreter knows only upstream's
extension names, so any script that requires vnd.inbuxa.* failed to
compile there.

Ported from the playground in the upstream sieve-rs repository's web/
directory (commit 549a2d57f7), which targets sieve-rs 1.x:

- run.rs and handler.rs drive 0.7's event loop the way the server's
  delivery does, carrying on after a runtime error; llm_prompt compiles
  and answers false, as on a server without a model.
- The expression functions are the server's own modules, included by
  path; tests fail if the list, ApplyString or the few functions copied
  from misc.rs/unicode.rs stop matching crates/common.
- Built for wasm32-wasip1 rather than with wasm-bindgen: sieve-rs and
  mail-builder read the clock, which panics on wasm32-unknown-unknown.
  The page's worker answers the six WASI calls, giving the module the
  playground's current-time setting. shim/gethostname stands in for a
  crate with no WASI build. Nothing in vendor/ or crates/ changes.
- The page carries no upstream names or links; storage keys and the
  samples are renamed to match.

build.sh --admin <dir> stages the result into an inbuxa-admin checkout.
2026-10-09 01:13:53 -07:00

45 lines
1.2 KiB
Sieve

# inbuxa expressions and loops
#
# "vnd.inbuxa.expressions" adds "let" and "eval" with arithmetic,
# string functions and arrays. "vnd.inbuxa.while" adds loops. Both are
# inbuxa extensions to Sieve.
require ["variables", "editheader", "fileinto", "mailbox", "imap4flags",
"vnd.inbuxa.expressions", "vnd.inbuxa.while"];
# Header fields are available as values: the display name of the sender,
# falling back to the address when the name is empty.
let "sender" "header.from.name";
if eval "is_empty(sender)" {
let "sender" "header.from.addr";
}
addheader "X-Sender-Name" "${sender}";
let "recipients" "header.to:cc[*].addr[*]";
let "total" "count(recipients)";
let "shouting" "is_uppercase(header.subject)";
if eval "total > 3" {
addflag "$ManyRecipients";
}
let "i" "0";
let "domains" "''";
while "i < total" {
let "domain" "email_part(recipients[i], 'domain')";
let "domains" "domains + domain + ' '";
let "i" "i + 1";
}
addheader "X-Recipient-Domains" "${domains}";
if eval "shouting" {
addflag "$Shouting";
let "subject" "to_lowercase(header.subject)";
deleteheader "Subject";
addheader "Subject" "${subject}";
}
if eval "contains_ignore_case(header.subject, 'meeting')" {
fileinto :create "Calendar";
}