Skip to content

Commit c02ef92

Browse files
authored
Fix juice failing on Windows when Quarto path contains spaces (#14207)
* Fix juice failing on Windows when Quarto path contains spaces Replace io.popen() with pandoc.pipe() in juice() to avoid shell word-splitting when the Quarto install path contains spaces (e.g., C:\Program Files\Quarto\). io.popen() concatenates the command as a string passed to the shell, which breaks at spaces. pandoc.pipe() calls the executable directly with arguments as an array, bypassing shell interpretation entirely. Fixes #14202 * Add changelog entry
1 parent 1ee3677 commit c02ef92

3 files changed

Lines changed: 31 additions & 20 deletions

File tree

.claude/rules/filters/lua-development.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,25 @@ if quarto.log.debug then
142142
end
143143
```
144144

145+
## External Command Execution
146+
147+
Use `pandoc.pipe()` instead of `io.popen()` for calling external programs:
148+
149+
```lua
150+
-- ✅ Correct - pandoc.pipe passes args as array, no shell interpretation
151+
local ok, result = pcall(pandoc.pipe, command, {"arg1", "arg2"}, "")
152+
if not ok then
153+
quarto.log.error("Command failed: " .. tostring(result))
154+
end
155+
156+
-- ❌ Wrong - io.popen uses shell, breaks on paths with spaces
157+
local handle = io.popen(command .. " arg1 arg2", "r")
158+
```
159+
160+
`io.popen()` passes a string to the shell, which breaks when paths contain spaces (e.g., `C:\Program Files\...`). `pandoc.pipe()` calls the executable directly with arguments as an array — no shell, no quoting issues.
161+
162+
Reference: `quarto-pre/shiny.lua`, `quarto-post/pdf-images.lua`
163+
145164
## Filter Return Values
146165

147166
```lua

news/changelog-1.9.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ All changes included in 1.9:
8686
- ([#13978](https://github.com/quarto-dev/quarto-cli/pull/13978)): Keep term and description together in definition lists to avoid breaking across pages. (author: @mcanouil)
8787
- ([#13878](https://github.com/quarto-dev/quarto-cli/issues/13878)): Typst now uses Pandoc's skylighting for syntax highlighting by default (consistent with other formats). Use `syntax-highlighting: idiomatic` to opt-in to Typst's native syntax highlighting instead.
8888
- ([#14126](https://github.com/quarto-dev/quarto-cli/issues/14126)): Fix Skylighting code blocks in Typst lacking full-width background, padding, and border radius. A postprocessor patches the Pandoc-generated Skylighting function to add `width: 100%`, `inset: 8pt`, and `radius: 2pt` to the block call, matching the styling of native code blocks. Brand `monospace-block.background-color` also now correctly applies to Skylighting output. This workaround will be removed once the fix is upstreamed to Skylighting.
89+
- ([#14202](https://github.com/quarto-dev/quarto-cli/issues/14202)): Fix CSS inlining (`juice`) failing on Windows when Quarto is installed in a path with spaces (e.g., `C:\Program Files\Quarto\`).
8990

9091
### `pdf`
9192

src/resources/filters/normalize/astpipeline.lua

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,28 +65,19 @@ function quarto_ast_pipeline()
6565
jin:write(htmltext)
6666
jin:flush()
6767
local quarto_path = quarto.config.cli_path()
68-
local jout, jerr = io.popen(quarto_path .. ' run ' ..
69-
pandoc.path.join({os.getenv('QUARTO_SHARE_PATH'), 'scripts', 'juice.ts'}) .. ' ' ..
70-
juice_in, 'r')
71-
if not jout then
72-
quarto.log.error('Running juice failed with message: ' .. (jerr or "Unknown error"))
68+
local juice_script = pandoc.path.join({os.getenv('QUARTO_SHARE_PATH'), 'scripts', 'juice.ts'})
69+
local ok, content = pcall(pandoc.pipe, quarto_path, {'run', juice_script, juice_in}, '')
70+
if not ok then
71+
quarto.log.error('Running juice failed: ' .. tostring(content))
7372
return htmltext
7473
end
75-
local content = jout:read('a')
76-
local success, _, exitCode = jout:close()
77-
-- Check the exit status
78-
if not success then
79-
quarto.log.error("Running juice failed with exit code: " .. (exitCode or "unknown exit code"))
80-
return htmltext
81-
else
82-
local index = 1
83-
content = content:gsub(data_uri_uuid:gsub('-', '%%-'), function(_)
84-
local data_uri = data_uris[index]
85-
index = index + 1
86-
return data_uri
87-
end)
88-
return content
89-
end
74+
local index = 1
75+
content = content:gsub(data_uri_uuid:gsub('-', '%%-'), function(_)
76+
local data_uri = data_uris[index]
77+
index = index + 1
78+
return data_uri
79+
end)
80+
return content
9081
end)
9182
end
9283
local function should_handle_raw_html_as_table(el)

0 commit comments

Comments
 (0)