Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelHatherly committed Feb 25, 2025
1 parent 7ff2773 commit 8f86b5f
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .ci/format.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import JuliaFormatter

cd(dirname(@__DIR__)) do
formatted_paths = String[]
unformatted_paths = String[]
subdirs = ["src", "test", ".ci"]
for subdir in subdirs
for (root, dirs, files) in walkdir(subdir)
for file in files
fullpath = joinpath(root, file)
fullpath_lowercase = lowercase(fullpath)
if endswith(fullpath_lowercase, ".jl") &&
!contains(fullpath_lowercase, "vendor")
is_formatted = JuliaFormatter.format(fullpath)
if is_formatted
push!(formatted_paths, fullpath)
else
push!(unformatted_paths, fullpath)
end
end
end
end
end
n = length(formatted_paths) + length(unformatted_paths)
println("Processed $(n) files.")
println("Formatted correctly: $(length(formatted_paths))")
println("Not formatted correctly: $(length(unformatted_paths))")
if !isempty(unformatted_paths)
println("The following files are not formatted correctly:")
[println(x) for x in unformatted_paths]
throw(ErrorException("Some files are not formatted correctly"))
end
end
38 changes: 38 additions & 0 deletions .github/workflows/Documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Documentation

on:
merge_group:
pull_request:
push:
branches:
- main
tags:
- '*'
workflow_dispatch:
concurrency:
# Skip intermediate builds: all builds except for builds on the `master` branch
# Cancel intermediate builds: only pull request builds
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/main' || github.run_number }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

jobs:
build:
permissions:
contents: write
statuses: write
pages: write # to deploy to Pages
id-token: write # to verify the deployment originates from an appropriate source
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- uses: julia-actions/setup-julia@5c9647d97b78a5debe5164e9eec09d653d29bd71 # v2.6.1
with:
version: '1'
- uses: julia-actions/cache@2b1bf4d8a138668ac719ea7ca149b53ed8d8401e # v2.0.7
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: julia --project=docs/ --color=yes docs/make.jl
20 changes: 20 additions & 0 deletions .github/workflows/EnforceChangelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "Enforce changelog"
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]

jobs:
changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: julia-actions/setup-julia@5c9647d97b78a5debe5164e9eec09d653d29bd71 # v2.6.1
with:
version: '1'
- uses: dangoslen/changelog-enforcer@204e7d3ef26579f4cd0fd759c57032656fdf23c7 # v3.6.1
with:
changeLogPath: 'CHANGELOG.md'
skipLabels: 'skip-changelog'
- run: julia --project=.ci -e 'using Pkg; Pkg.instantiate()'
- run: julia --project=.ci .ci/changelog.jl
- run: git diff --exit-code
33 changes: 33 additions & 0 deletions .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: TagBot
on:
issue_comment:
types:
- created
workflow_dispatch:
inputs:
lookback:
default: "3"
permissions:
actions: read
checks: read
contents: write
deployments: read
issues: read
discussions: read
packages: read
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Edit the following line to reflect the actual name of the GitHub Secret containing your private key
ssh: ${{ secrets.DOCUMENTER_KEY }}
# ssh: ${{ secrets.NAME_OF_MY_SSH_PRIVATE_KEY_SECRET }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ LocalPreferences.toml
*.jls
*.ipynb
.cache
docs/build
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
Dates = "1.6"
Pkg = "1.6"
Preferences = "1"
REPL = "1.6"
Requires = "1"
SHA = "0.7, 1.6"
Serialization = "1.6"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# QuartoTools.jl

Helper functions and other utilities for use in `.qmd` notebooks.

[![](https://img.shields.io/badge/Docs-Stable-lightgrey.svg)](https://pumasai.github.io/QuartoTools.jl/stable/)
[![](https://img.shields.io/badge/Docs-Dev-blue.svg)](https://pumasai.github.io/QuartoTools.jl/dev/)

3 changes: 3 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
QuartoTools = "5fded309-f5a0-485a-9129-b3749510da85"
6 changes: 6 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Documenter
using QuartoTools

makedocs(sitename = "QuartoTools", format = Documenter.HTML(), modules = [QuartoTools])

deploydocs(repo = "github.com/PumasAI/QuartoTools.jl.git", push_preview = true)
149 changes: 149 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# QuartoTools.jl

*Utilities for working with Quarto notebooks in Julia*

This package provides several utilities that can be used in conjuction with
Quarto notebooks when using the `engine: julia` setting, which executes your
notebook code with
[QuartoNotebookRunner.jl](https://github.com/PumasAI/QuartoNotebookRunner.jl).

## "Expandables"

`QuartoNotebookRunner.jl` has a special feature called cell expansion. It
allows you to have a single code cell that outputs what looks like multiple
code cells and their outputs to Quarto.

*What can you use this feature for?*

Quarto has many advanced options which allow you to create richer output, for
example [tabsets](https://quarto.org/docs/interactive/layout.html#tabset-panel)
which can group several separate sections of a quarto notebook into selectable
tabs. These features are controlled with markdown annotations, for example, a
tabset follows this structure:

````qmd
::: {.panel-tabset}

## Tab 1

Content of tab 1

## Tab 2

Content of tab 2

... possibly more tabs ...

:::
````

As you can see, the tabset begins and ends with a pandoc `:::` div fence and
consists of sections demarcated by markdown headings. This mechanism has two
drawbacks for the user:

- It can be tricky to get the syntax right, especially with multiple nested `:::` fences that need to be closed correctly. (This also applies when you generate markdown programmatically by printing out snippets in loops and using the `output: asis` cell option.)
- It is static. Each tab has to be written into the source markdown explicitly, so you cannot easily create a tabset with a dynamic number of tabs. For example, a tabset with one plot per tab where the number of plots depends on runtime information and therefore is not known in advance.

Cell expansion can solve both of these problems. It relies on a function called
`QuartoNotebookWorker.expand`, which is defined within the notebook worker
process for every notebook that you execute.

When a notebook cell returns a Julia value from a cell, such that it is
displayed but Quarto, the `expand` function will first be called on that value.
By default this returns `nothing` and so we just display the original value.
But if an `expand` method is defined for that type, then it should return a
`Vector{QuartoNotebookWorker.Cell}` object which is then evaluated as if they
were real cells. This feature is recursive, so `Cell`s can themselves return
more vectors of `Cell`s.

Thus we can use these cells to build the structures Quarto expects
programmatically, instead of having to hardcode them into the notebook.

For example, a tabset with plots could be generated by expanding into:

- a cell with markdown output `::: {.panel-tabset}`
- a cell with markdown output `## Tab 1`
- a cell with a plot output, for example a `Makie.Figure`
- more headings and plots
- a cell with markdown output `:::`

!!! note

Cell expansion is not code generation. We do not generate and evaluate
arbitrary code. Instead, we create objects describing code cells together with
their outputs which is easier to reason about and more composable.

Each `QuartoNotebookWorker.Cell` has three fields:

- `thunk` stores a function which returns the fake cell's output value when run. This value is treated as any other code cell output value, so it may be of any type that the display system can handle, and it may even be expandable itself (allowing for recursive expansion).
- `code` may hold a string which will be rendered as the code of the fake cell (this code is not run).
- `options` is a dictionary of quarto cell options, for example `"echo" => false` to hide the source code section.

`QuartoTools` defines a set of helper objects that can serve as building blocks
that can be composed further. For example, a [`Tabset`](@ref QuartoTools.Tabset)
may contain multiple [`Div`](@ref QuartoTools.Div)s, each describing a
two-column layout which is populated with two plots.

## Caching

`QuartoTools` provides a caching mechanism that can be used to save the results
of expensive function calls in your notebook cells. Once loaded into a notebook
via a cell containing `import QuartoTools` you can annotate any subsequent cell
with the `julia.cache.enabled` key as follows:

````qmd
---
engine: julia
---

```{julia}
import QuartoTools
```

```{julia}
#| julia:
#| cache:
#| enabled: true
result = expensive_func(arg)
```
````

The first time that `expensive_func` is called with any specific `arg` value
the result will be saved to disk using [Serialization](@ref). Subsequent calls
with the same `arg` value will return the cached result rather than re-running
`expensive_func`. This can be useful for long-running computations that slow
down the rendering of your notebook.

Avoid using the feature on cells that only take a few seconds that run, since
the overhead of saving and loading cached results can be larger than the time
saved. If you have a particularly complex cell that contains some fast calls
and some slow ones, try to factor them out into separate cells and only run the
caching on the slow ones.

The cache for each notebook is stored alongside it in a folder called `.cache`.
Removing this folder will clear the cache for the notebook. Do not commit the
contents of this folder to version control.

## Serialization

When working with serialized data in Quarto notebooks users must use the
[`QuartoTools.serialize`](@ref) and [`QuartoTools.deserialize`](@ref) functions
provided by the `QuartoTools` package rather than the `Serialization` package.
This is due to the differences in the behaviour of code evaluation between the
Julia REPL and that of Quarto. These two functions are drop-in replacements for
those provided by `Serialization` and fall back on the implementation provided
by it when not run in a Quarto notebook. This means that simply replacing
`using Serialization` with `using QuartoTools` should be sufficient to allow
for transparent serialization and deserialization between notebooks, batch
scripts, and the REPL.

Note that if both `QuartoTools` and `Serialization` are imported with `using`
in the same session then the functions `serialize` and `deserialize` will need
to be prefixed with their package name due to the name collisions between the
two packages. Typically users should only need to import `QuartoTools`.

## Docstrings

```@autodocs
Modules = [QuartoTools]
```
11 changes: 11 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
default:
just -l

changelog:
julia --project=.ci .ci/changelog.jl

docs:
julia --project=docs docs/make.jl

format:
julia --project=.ci .ci/format.jl

0 comments on commit 8f86b5f

Please sign in to comment.