Repository: PumasAI/QuartoNotebookRunner.jl (julia)

Issue:
warning: false seems to be ignored
I'm not sure if this is related to QuartoNotebookRunner.jl or occurs downstream.  Please let me know if I should report this at https://github.com/quarto-dev/quarto-cli instead.

Rendering the enclosed with Quarto v1.5.31 shows the warning produced by the last code block.
````quarto
---
engine: julia
---

Define a function that sends a warning

```{julia}
function foo(i::Integer)
    if iszero(i)
        return i
    elseif isone(i)
        @warn "i is one"
        return i
    else
        error("Received i = $i")
    end
    return 0
end
```

```{julia}
#| warning: false
foo(0)
```


```{julia}
#| warning: false
foo(true)
```
````

The .ipynb file produced is
```json
{
    "metadata": {
        "kernelspec": {
            "display_name": "Julia 1.10.2",
            "language": "julia",
            "name": "julia-1.10.2"
        },
        "kernel_info": {
            "name": "julia"
        },
        "language_info": {
            "name": "julia",
            "version": "1.10.2",
            "codemirror_mode": "julia"
        }
    },
    "nbformat": 4,
    "nbformat_minor": 5,
    "cells": [
        {
            "id": "1",
            "cell_type": "markdown",
            "metadata": {
            },
            "source": [
                "---\n",
                "engine: julia\n",
                "---\n",
                "\n",
                "Define a function that sends a warning"
            ]
        },
        {
            "id": "2",
            "cell_type": "code",
            "metadata": {
            },
            "source": [
                "function foo(i::Integer)\n",
                "    if iszero(i)\n",
                "        return i\n",
                "    elseif isone(i)\n",
                "        @warn \"i is one\"\n",
                "        return i\n",
                "    else\n",
                "        error(\"Received i = $i\")\n",
                "    end\n",
                "    return 0\n",
                "end"
            ],
            "outputs": [
                {
                    "output_type": "execute_result",
                    "execution_count": 1,
                    "data": {
                        "text/plain": "foo (generic function with 1 method)"
                    },
                    "metadata": {
                    }
                }
            ],
            "execution_count": 1
        },
        {
            "id": "3",
            "cell_type": "markdown",
            "metadata": {
            },
            "source": [
            ]
        },
        {
            "id": "4",
            "cell_type": "code",
            "metadata": {
            },
            "source": [
                "#| warning: false\n",
                "foo(0)"
            ],
            "outputs": [
                {
                    "output_type": "execute_result",
                    "execution_count": 1,
                    "data": {
                        "text/plain": "0"
                    },
                    "metadata": {
                    }
                }
            ],
            "execution_count": 1
        },
        {
            "id": "5",
            "cell_type": "markdown",
            "metadata": {
            },
            "source": [
                ""
            ]
        },
        {
            "id": "6",
            "cell_type": "code",
            "metadata": {
            },
            "source": [
                "#| warning: false\n",
                "foo(true)"
            ],
            "outputs": [
                {
                    "output_type": "stream",
                    "name": "stdout",
                    "text": "\u001b[33m\u001b[1m┌ \u001b[22m\u001b[39m\u001b[33m\u001b[1mWarning: \u001b[22m\u001b[39mi is one\n\u001b[33m\u001b[1m└ \u001b[22m\u001b[39m\u001b[90m@ Main.Notebook ~/git/scMixedModels/notebooks/warning/warning.qmd:12\u001b[39m\n"
                },
                {
                    "output_type": "execute_result",
                    "execution_count": 1,
                    "data": {
                        "text/plain": "true"
                    },
                    "metadata": {
                    }
                }
            ],
            "execution_count": 1
        }
    ]
}
```

Pull request description:

Patch:
diff --git a/src/QuartoNotebookWorker/src/QuartoNotebookWorker.jl b/src/QuartoNotebookWorker/src/QuartoNotebookWorker.jl
index 8cd0a8d6..ad095b7b 100644
--- a/src/QuartoNotebookWorker/src/QuartoNotebookWorker.jl
+++ b/src/QuartoNotebookWorker/src/QuartoNotebookWorker.jl
@@ -83,6 +83,7 @@ end
 # Imports.
 
 import InteractiveUtils
+import Logging
 import Pkg
 import REPL
 
diff --git a/src/QuartoNotebookWorker/src/render.jl b/src/QuartoNotebookWorker/src/render.jl
index 18832853..c21aef47 100644
--- a/src/QuartoNotebookWorker/src/render.jl
+++ b/src/QuartoNotebookWorker/src/render.jl
@@ -77,8 +77,9 @@ function _render_thunk(
         # to the server. Cell expansion is recursive.
         return _flatmap(expansion) do cell
             wrapped = function ()
-                return QuartoNotebookWorker.Packages.IOCapture.capture(
+                return io_capture(
                     cell.thunk;
+                    cell_options = cell.options,
                     rethrow = InterruptException,
                     color = true,
                 )
@@ -197,7 +198,8 @@ function include_str(
         # Note: IO capturing combines stdout and stderr into a single
         # `.output`, but Jupyter notebook spec appears to want them
         # separate. Revisit this if it causes issues.
-        return Packages.IOCapture.capture(;
+        return io_capture(;
+            cell_options = cell_options,
             rethrow = InterruptException,
             color = true,
             io_context = _io_context(cell_options),
@@ -241,6 +243,23 @@ function include_str(
     end
 end
 
+function io_capture(f; cell_options, kws...)
+    warning = get(cell_options, "warning", true)
+    capture() = Packages.IOCapture.capture(f; kws...)
+    if warning
+        return capture()
+    else
+        logger = Logging.global_logger()
+        current_level = Logging.min_enabled_level(logger)
+        try
+            Logging.disable_logging(Logging.Error)
+            return capture()
+        finally
+            Logging.disable_logging(current_level - 1)
+        end
+    end
+end
+
 # passing our module removes Main.Notebook noise when printing types etc.
 function with_context(io::IO, cell_options = Dict{String,Any}())
     return IOContext(io, _io_context(cell_options)...)


Test patch:
diff --git a/test/examples/warnings.qmd b/test/examples/warnings.qmd
new file mode 100644
index 00000000..2ed4f239
--- /dev/null
+++ b/test/examples/warnings.qmd
@@ -0,0 +1,23 @@
+---
+title: Warnings
+---
+
+```{julia}
+#| warning: false
+@info "info"
+@warn "warn"
+@error "error"
+```
+
+```{julia}
+@info "info"
+@warn "warn"
+@error "error"
+```
+
+```{julia}
+#| warning: true
+@info "info"
+@warn "warn"
+@error "error"
+```
\ No newline at end of file
diff --git a/test/testsets/warnings.jl b/test/testsets/warnings.jl
new file mode 100644
index 00000000..cb33fde8
--- /dev/null
+++ b/test/testsets/warnings.jl
@@ -0,0 +1,18 @@
+include("../utilities/prelude.jl")
+
+test_example(joinpath(@__DIR__, "../examples/warnings.qmd")) do json
+    cells = json["cells"]
+
+    cell = cells[2]
+    @test isempty(cell["outputs"])
+
+    for cell in (cells[4], cells[6])
+        outputs = cell["outputs"]
+        @test length(outputs) == 1
+        @test outputs[1]["output_type"] == "stream"
+        @test contains(outputs[1]["text"], "info")
+        @test contains(outputs[1]["text"], "warn")
+        @test contains(outputs[1]["text"], "error")
+        @test contains(outputs[1]["text"], "warnings.qmd")
+    end
+end