Skip to content

Commit 18d9062

Browse files
authored
feat: Support rendering multiple emails (#13882)
* first pass multiple email filter - Handles original format with document level subject and other divs by producing exactly one email per document. - subject and email_text divs must be nested within email divs to render multiple emails per document. - email output now uses send_report_as_attachment instead of rsc_email_suppress_report_attachment * change v1 trigger to the existence of top level metadata and rename index field in json output to email_id * sniff for connect version * refactor connect verison logic * ensure v1 email previewing * update tests * test with divs outside of the email div * rename email-subject-document-level.qmd * re-factor to enforce v1 output with non-nested structured input * remove logs and some warnings * add multi-email tests * resolve bug with conflicting email-scheduled fields might be prudent just to ignore, since this is a relic of accepting invalid input * import connectversion module as object * Add entry to changelog 1.9
1 parent def4986 commit 18d9062

8 files changed

Lines changed: 638 additions & 155 deletions

File tree

news/changelog-1.9.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ All changes included in 1.9:
2727
- ([#13421](https://github.com/quarto-dev/quarto-cli/issues/13421)): Do not word-wrap titles in header.
2828
- ([#13603](https://github.com/quarto-dev/quarto-cli/issues/13603)): Fix callouts with title but no body content causing fatal error when rendering to GitHub Flavored Markdown.
2929

30+
### `email`
31+
32+
- ([#13882](https://github.com/quarto-dev/quarto-cli/pull/13882)): Add support for multiple email outputs when rendering to `format: email` for Posit Connect.
33+
3034
### `html`
3135

3236
- ([#11929](https://github.com/quarto-dev/quarto-cli/issues/11929)): Import all `brand.typography.fonts` in CSS, whether or not fonts are referenced by typography elements.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
--[[
2+
Connect version sniffing utilities for email extension
3+
4+
Functions to detect and compare Posit Connect versions from environment variables
5+
]]
6+
7+
-- Parse Connect version from SPARK_CONNECT_USER_AGENT
8+
-- Format: posit-connect/2024.09.0
9+
-- posit-connect/2024.09.0-dev+26-dirty-g51b853f70e
10+
-- Returns: "2024.09.0" or nil
11+
function get_connect_version()
12+
local user_agent = os.getenv("SPARK_CONNECT_USER_AGENT")
13+
if not user_agent then
14+
return nil
15+
end
16+
17+
-- Extract the version after "posit-connect/"
18+
local version_with_suffix = string.match(user_agent, "posit%-connect/([%d%.%-+a-z]+)")
19+
if not version_with_suffix then
20+
return nil
21+
end
22+
23+
-- Strip everything after the first "-" (e.g., "-dev+88-gda902918eb")
24+
local idx = string.find(version_with_suffix, "-")
25+
if idx then
26+
return string.sub(version_with_suffix, 1, idx - 1)
27+
end
28+
29+
return version_with_suffix
30+
end
31+
32+
-- Parse a version string into components
33+
-- Versions are in format "X.Y.Z", with all integral components (e.g., "2025.11.0")
34+
-- Returns: {major=2025, minor=11, patch=0} or nil
35+
function parse_version_components(version_string)
36+
if not version_string then
37+
return nil
38+
end
39+
40+
-- Parse version (e.g., "2025.11.0" or "2025.11")
41+
local major, minor, patch = string.match(version_string, "^(%d+)%.(%d+)%.?(%d*)$")
42+
if not major then
43+
return nil
44+
end
45+
46+
return {
47+
major = tonumber(major),
48+
minor = tonumber(minor),
49+
patch = patch ~= "" and tonumber(patch) or 0
50+
}
51+
end
52+
53+
-- Check if Connect version is >= target version
54+
-- Versions are in format "YYYY.MM.patch" (e.g., "2025.11.0")
55+
function is_connect_version_at_least(target_version)
56+
local current_version = get_connect_version()
57+
local current = parse_version_components(current_version)
58+
local target = parse_version_components(target_version)
59+
60+
if not current or not target then
61+
return false
62+
end
63+
64+
-- Convert to numeric YYYYMMPP format and compare
65+
local current_num = current.major * 10000 + current.minor * 100 + current.patch
66+
local target_num = target.major * 10000 + target.minor * 100 + target.patch
67+
68+
return current_num >= target_num
69+
end
70+
71+
-- Export functions for module usage
72+
return {
73+
is_connect_version_at_least = is_connect_version_at_least
74+
}

src/resources/filters/modules/constants.lua

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ local kBackgroundColorCaution = "ffe5d0"
164164
local kIncremental = "incremental"
165165
local kNonIncremental = "nonincremental"
166166

167+
-- Connect version requirements
168+
-- TODO update version when email metadata changes are made in connect
169+
local kConnectEmailMetadataChangeVersion = "2026.03"
170+
167171
return {
168172
kCitation = kCitation,
169173
kContainerId = kContainerId,
@@ -257,5 +261,7 @@ return {
257261
kBackgroundColorCaution = kBackgroundColorCaution,
258262

259263
kIncremental = kIncremental,
260-
kNonIncremental = kNonIncremental
264+
kNonIncremental = kNonIncremental,
265+
266+
kConnectEmailMetadataChangeVersion = kConnectEmailMetadataChangeVersion
261267
}

0 commit comments

Comments
 (0)