Skip to content

Commit 5c23113

Browse files
committed
support write_yaml_metadata_block as a path for passing recipients
1 parent 3f396b1 commit 5c23113

4 files changed

Lines changed: 231 additions & 1 deletion

File tree

src/resources/filters/quarto-post/email.lua

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,41 @@ function process_div(div)
401401
table.insert(remaining_content, child)
402402
end
403403
end
404-
404+
405+
-- Check for recipients attribute on the email div itself
406+
-- This allows referencing metadata set via write_yaml_metadata_block()
407+
if div.attributes.recipients then
408+
local meta_key = div.attributes.recipients
409+
local meta_value = quarto.metadata.get(meta_key)
410+
411+
if meta_value then
412+
-- Convert metadata to recipients array
413+
if quarto.utils.type(meta_value) == "List" then
414+
local recipients_from_meta = {}
415+
for _, item in ipairs(meta_value) do
416+
local recipient_str = pandoc.utils.stringify(item)
417+
if recipient_str ~= "" then
418+
table.insert(recipients_from_meta, recipient_str)
419+
end
420+
end
421+
422+
-- If recipients were also found in child divs, merge them
423+
if #current_email.recipients > 0 then
424+
quarto.log.warning("Recipients found in both attribute and child div. Merging both lists.")
425+
for _, recipient in ipairs(recipients_from_meta) do
426+
table.insert(current_email.recipients, recipient)
427+
end
428+
else
429+
current_email.recipients = recipients_from_meta
430+
end
431+
else
432+
quarto.log.warning("Recipients metadata '" .. meta_key .. "' is not a list. Expected format: ['email1@example.com', 'email2@example.com']")
433+
end
434+
else
435+
quarto.log.warning("Recipients attribute references metadata key '" .. meta_key .. "' which does not exist.")
436+
end
437+
end
438+
405439
-- Create a modified div without metadata for processing
406440
local email_without_metadata = pandoc.Div(remaining_content, div.attr)
407441

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: Email Recipients - All Patterns (Python)
3+
author: Jules Walzer-Goldfeld
4+
format: email
5+
email-version: 2
6+
---
7+
8+
```{python}
9+
#| echo: false
10+
import yaml
11+
from IPython.display import Markdown
12+
13+
def write_yaml_metadata_block(**kwargs):
14+
"""Write YAML metadata block that will be parsed by Quarto."""
15+
yaml_content = yaml.dump(
16+
kwargs,
17+
default_flow_style=False,
18+
allow_unicode=True,
19+
sort_keys=False
20+
)
21+
yaml_block = f"---\n{yaml_content}---\n"
22+
return Markdown(yaml_block)
23+
```
24+
25+
Test document demonstrating both recipient patterns with Python.
26+
27+
```{python}
28+
#| output: asis
29+
# Email 1: Metadata attribute pattern
30+
metadata_recipients = ["metadata1@example.com", "metadata2@example.com"]
31+
write_yaml_metadata_block(metadata_recipients=metadata_recipients)
32+
```
33+
34+
::: {.email recipients=metadata_recipients}
35+
36+
::: {.subject}
37+
Email 1: Metadata Attribute Pattern
38+
:::
39+
40+
::: {.email-text}
41+
This email uses the metadata attribute pattern.
42+
:::
43+
44+
First email using metadata attribute pattern with Python.
45+
46+
:::
47+
48+
```{python}
49+
# Email 2: Inline recipients pattern
50+
inline_recipients = ["inline1@example.com", "inline2@example.com"]
51+
```
52+
53+
::: {.email}
54+
55+
::: {.subject}
56+
Email 2: Inline Recipients Pattern
57+
:::
58+
59+
::: {.recipients}
60+
`{python} inline_recipients`
61+
:::
62+
63+
::: {.email-text}
64+
This email uses the inline recipients pattern.
65+
:::
66+
67+
Second email using inline recipients pattern with Python.
68+
69+
:::
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: Email Recipients - All Patterns (R)
3+
author: Jules Walzer-Goldfeld
4+
format: email
5+
email-version: 2
6+
---
7+
8+
```{r}
9+
#| echo: false
10+
# Simple metadata block writer for testing
11+
# (Ideally we use the quarto R package: install.packages("quarto"))
12+
# We are awiting PR #
13+
write_yaml_metadata_block <- function(...) {
14+
args <- list(...)
15+
if (length(args) == 0) {
16+
return()
17+
}
18+
19+
yaml_content <- yaml::as.yaml(args)
20+
yaml_block <- paste0("---\n", yaml_content, "---\n")
21+
knitr::asis_output(yaml_block)
22+
}
23+
```
24+
25+
Test document demonstrating both recipient patterns with R.
26+
27+
```{r}
28+
#| output: asis
29+
# Email 1: Metadata attribute pattern
30+
metadata_recipients <- c("metadata1@example.com", "metadata2@example.com")
31+
write_yaml_metadata_block(metadata_recipients = metadata_recipients)
32+
```
33+
34+
::: {.email recipients=metadata_recipients}
35+
36+
::: {.subject}
37+
Email 1: Metadata Attribute Pattern
38+
:::
39+
40+
::: {.email-text}
41+
This email uses the metadata attribute pattern.
42+
:::
43+
44+
First email using metadata attribute pattern with R.
45+
46+
:::
47+
48+
```{r}
49+
# Email 2: Inline recipients pattern
50+
inline_recipients <- c("inline1@example.com", "inline2@example.com")
51+
```
52+
53+
::: {.email}
54+
55+
::: {.subject}
56+
Email 2: Inline Recipients Pattern
57+
:::
58+
59+
::: {.recipients}
60+
`{r} inline_recipients`
61+
:::
62+
63+
::: {.email-text}
64+
This email uses the inline recipients pattern.
65+
:::
66+
67+
Second email using inline recipients pattern with R.
68+
69+
:::

tests/smoke/render/render-email.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,64 @@ testRender(docs("email/email-recipients-formats.qmd"), "email", false, [
312312
"SPARK_CONNECT_USER_AGENT": "posit-connect/2026.03.0"
313313
}
314314
});
315+
316+
// Test both recipient patterns with Python (metadata attribute + inline)
317+
testRender(docs("email/email-recipients-all-patterns-python.qmd"), "email", false, [
318+
fileExists(previewFileV2_1),
319+
fileExists(previewFileV2_2),
320+
validJsonWithMultipleEmails(jsonFile, 2, {
321+
"0": {
322+
"email_id": 1,
323+
"subject": "Email 1: Metadata Attribute Pattern",
324+
"recipients": ["metadata1@example.com", "metadata2@example.com"],
325+
"attachments": [],
326+
"suppress_scheduled": false,
327+
"send_report_as_attachment": false
328+
},
329+
"1": {
330+
"email_id": 2,
331+
"subject": "Email 2: Inline Recipients Pattern",
332+
"recipients": ["inline1@example.com", "inline2@example.com"],
333+
"attachments": [],
334+
"suppress_scheduled": false,
335+
"send_report_as_attachment": false
336+
}
337+
})
338+
], {
339+
...cleanupCtx,
340+
env: {
341+
"SPARK_CONNECT_USER_AGENT": "posit-connect/2026.03.0"
342+
}
343+
});
344+
345+
// Test both recipient patterns with R (metadata attribute + inline)
346+
testRender(docs("email/email-recipients-all-patterns-r.qmd"), "email", false, [
347+
fileExists(previewFileV2_1),
348+
fileExists(previewFileV2_2),
349+
validJsonWithMultipleEmails(jsonFile, 2, {
350+
"0": {
351+
"email_id": 1,
352+
"subject": "Email 1: Metadata Attribute Pattern",
353+
"recipients": ["metadata1@example.com", "metadata2@example.com"],
354+
"attachments": [],
355+
"suppress_scheduled": false,
356+
"send_report_as_attachment": false
357+
},
358+
"1": {
359+
"email_id": 2,
360+
"subject": "Email 2: Inline Recipients Pattern",
361+
"recipients": ["inline1@example.com", "inline2@example.com"],
362+
"attachments": [],
363+
"suppress_scheduled": false,
364+
"send_report_as_attachment": false
365+
}
366+
})
367+
], {
368+
...cleanupCtx,
369+
env: {
370+
"SPARK_CONNECT_USER_AGENT": "posit-connect/2026.03.0"
371+
}
372+
});
315373
// Render in a project with an output directory set in _quarto.yml and confirm that everything ends up in the output directory
316374
testProjectRender(docs("email/project/email-attach.qmd"), "email", (outputDir: string) => {
317375
const verify: Verify[]= [];

0 commit comments

Comments
 (0)