Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ All changes included in 1.9:
- ([#13421](https://github.com/quarto-dev/quarto-cli/issues/13421)): Do not word-wrap titles in header.
- ([#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.

### `email`

- ([#13882](https://github.com/quarto-dev/quarto-cli/pull/13882)): Add support for multiple email outputs when rendering to `format: email` for Posit Connect.

### `html`

- ([#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.
Expand Down
74 changes: 74 additions & 0 deletions src/resources/filters/modules/connectversion.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
--[[
Connect version sniffing utilities for email extension

Functions to detect and compare Posit Connect versions from environment variables
]]

-- Parse Connect version from SPARK_CONNECT_USER_AGENT
-- Format: posit-connect/2024.09.0
-- posit-connect/2024.09.0-dev+26-dirty-g51b853f70e
-- Returns: "2024.09.0" or nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any obvious tests for the files around here, but this kind of logic would be lovely to have tests for.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hacky solution was to pass in different mock environments to the rendering tests, since the rendered output is conditioned on the environment quarto is in.

https://github.com/quarto-dev/quarto-cli/pull/13882/files#diff-38d1762233fec375a94fb002d3b59de5322b5a1b7d23fbad65c3c2a04f8f256fR128-R130

As far as I can tell, modules like this one don't have their own unit tests in this repo, but I could also be missing them somewhere.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's relatively hard to write good unit tests for code that runs inside Pandoc filters. In terms of value per unit time invested, we tend to prefer using end-to-end integration tests with our "smoke-all" document infrastructure. One thing you could try is to expose a Lua API that would be visible inside filters, and then run those tests at document-render time.

@jonkeane Do we think unit testing would catch things that an integration test wouldn't? I think the structural risk in this code is the connect integration.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm totally fine with integration or end to end style tests. I didn't totally connect at first what Jules pointed out as how he tested these — but that's on me and not being as familiar with the Lua/Pandoc filter ecosystem more than anything.

TL;DR: I'm good with what is here, I just didn't see/realize this functionality was already covered by tests

function get_connect_version()
local user_agent = os.getenv("SPARK_CONNECT_USER_AGENT")
if not user_agent then
return nil
end

-- Extract the version after "posit-connect/"
local version_with_suffix = string.match(user_agent, "posit%-connect/([%d%.%-+a-z]+)")
if not version_with_suffix then
return nil
end

-- Strip everything after the first "-" (e.g., "-dev+88-gda902918eb")
local idx = string.find(version_with_suffix, "-")
if idx then
return string.sub(version_with_suffix, 1, idx - 1)
end

return version_with_suffix
end

-- Parse a version string into components
-- Versions are in format "X.Y.Z", with all integral components (e.g., "2025.11.0")
-- Returns: {major=2025, minor=11, patch=0} or nil
function parse_version_components(version_string)
if not version_string then
return nil
end

-- Parse version (e.g., "2025.11.0" or "2025.11")
local major, minor, patch = string.match(version_string, "^(%d+)%.(%d+)%.?(%d*)$")
if not major then
return nil
end

return {
major = tonumber(major),
minor = tonumber(minor),
patch = patch ~= "" and tonumber(patch) or 0
}
end

-- Check if Connect version is >= target version
-- Versions are in format "YYYY.MM.patch" (e.g., "2025.11.0")
function is_connect_version_at_least(target_version)
local current_version = get_connect_version()
local current = parse_version_components(current_version)
local target = parse_version_components(target_version)

if not current or not target then
return false
end

-- Convert to numeric YYYYMMPP format and compare
local current_num = current.major * 10000 + current.minor * 100 + current.patch
local target_num = target.major * 10000 + target.minor * 100 + target.patch

return current_num >= target_num
end

-- Export functions for module usage
return {
is_connect_version_at_least = is_connect_version_at_least
}
8 changes: 7 additions & 1 deletion src/resources/filters/modules/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ local kBackgroundColorCaution = "ffe5d0"
local kIncremental = "incremental"
local kNonIncremental = "nonincremental"

-- Connect version requirements
-- TODO update version when email metadata changes are made in connect
local kConnectEmailMetadataChangeVersion = "2026.03"

return {
kCitation = kCitation,
kContainerId = kContainerId,
Expand Down Expand Up @@ -256,5 +260,7 @@ return {
kBackgroundColorCaution = kBackgroundColorCaution,

kIncremental = kIncremental,
kNonIncremental = kNonIncremental
kNonIncremental = kNonIncremental,

kConnectEmailMetadataChangeVersion = kConnectEmailMetadataChangeVersion
}
Loading
Loading