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
60 changes: 27 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ code. The website is built using the Rust-powered static site generator,
framework
- **[basecoat-css](https://basecoatui.com/)** — a lightweight shadcn/ui
components library built with Tailwind CSS
- **[just](https://github.com/casey/just)** — a Rust-powered command runner,
similar to `make`
- **[Quarto](https://quarto.org/)** — currently used to execute code chunks
and render to commonmark, though this is likely to change
- **[Node.js](https://nodejs.org/en)** — `npm` and `npx` are also used to
Expand All @@ -23,18 +21,13 @@ code. The website is built using the Rust-powered static site generator,
Tailwind extension for icons (currently limited to fa7-brands, lucide, and
mdi icons)

## Pipeline

Running `just build` will do the following:
The build pipeline:

1. **Tailwind** compiles `css/*.css` to `static/style.css`.
2. **Quarto** executes code in `content/**/*.qmd` and compiles to CommonMark in
2. **Quarto** executes code in `content/**/*.qmd` and compiles to commonmark in
`content/**/*.md`.
3. **Zola** compiles commonmark to HTML and builds site in public/.

For development, the same is achieved with `just dev`, which will serve the site
and open it in the default browser with live reload.

## Structure

```
Expand All @@ -60,9 +53,13 @@ and open it in the default browser with live reload.
**Prerequisites**

In addition to the stack software listed above, you will need R installed and
the most recent release version of the rextendr package. The
[`just`](https://github.com/casey/just) command line runner will also be very
beneficial to you.
the most recent release version of the rextendr package. The `just` command line
runner will also be very beneficial to you.

- **[just](https://github.com/casey/just)** v1.55.0 or higher
- **Node.js** v24.15.0 or higher
- **Quarto**
- **Zola** v0.22.1 or higher

**Installation**

Expand All @@ -85,24 +82,21 @@ just dev
just build
```

**Available commands**

Run `just` with no arguments to list all recipes.

| Recipe | Description |
|---------------------|------------------------------------------------------------------------------------|
| `basecoat` | Move basecoat component js files from node_modules to static/js |
| `build` | Compile Tailwind css, then build the site with Zola |
| `check` | Try to build site without rendering (checks links) |
| `default` | List all available recipes |
| `dev` | Run Tailwind watch and Zola live serve in parallel |
| `install` | Install Node.js dependencies |
| `new-page` path | Create a new documentation page at content/{{path}} |
| `new-post` title | Create a new blog post at content/blog/{{date}}-{{title}}/index.qmd |
| `new-section` path | Create a new documentation section at content/{{path}}/ |
| `render` path | Render a single qmd file at content/{{path}} |
| `render-all` | Render all qmd files in content/ (except those in "blog/") |
| `render-dir` path | Render all qmd files in content/{{path}}/ non-recursively (fails if path="blog/") |
| `render-post` title | Render a blog post at content/blog/{{date}}-{{title}}/index.qmd |
| `reset` | Clear public directory generated by Zola |
| `tailwind-build` | Generate Tailwind static/style.css from css/style.css |
**Available recipes**

Run `just` without arguments to list available recipes.

```
Available recipes:
build # Compile Tailwind css, then build the site with Zola
build-tailwind # Generate Tailwind static/style.css from css/style.css
check # Try to build site without rendering (checks links)
dev # Run Tailwind watch and Zola live serve in parallel
install # Install Node.js dependencies
page path # Create a new documentation page at content/{{path}}.qmd
post title # Create a new blog post at content/blog/{{title}}/index.qmd
render # Render all pages in content/ recursively (ignores blog/)
render-page path # Render a documentation page at content/{{path}}.qmd
render-post title # Render a blog post at content/blog/{{title}}/index.qmd
reset # Clear public directory generated by Zola
```
249 changes: 80 additions & 169 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,194 +1,105 @@
# List all available recipes
default:
@just --list
set default-list

# Compile Tailwind css, then build the site with Zola
build: tailwind-build
build: build-tailwind
zola build

# Generate Tailwind static/style.css from css/style.css
build-tailwind:
npm run tailwind:build

# Try to build site without rendering (checks links)
check:
zola check

# Run Tailwind watch and Zola live serve in parallel
dev:
npm run dev

# Clear public directory generated by Zola
reset:
rm -rf public

# Install Node.js dependencies
install:
install: npm basecoat
echo "Successfully installed dependencies and copied basecoat files to static."

[private]
npm:
npm install

# Move basecoat component js files from node_modules to static/js
[private]
basecoat:
#!/usr/bin/env bash
cp node_modules/basecoat-css/dist/js/*.js static/js/
echo "Successfully copied basecoat component js to static/js"

# Render a single qmd file at content/{{path}}
render path:
#!/usr/bin/env bash
set -euo pipefail
src='{{path}}'
src="${src#content/}"
if [[ "$src" == blog/* ]]; then
echo "render: found 'blog/' path, use 'just render-post'" >&2
exit 1
fi
# move to content folder so _quarto.yml is invoked
cd content
# clear .md file to prevent quarto from writing to another file
rm -f "${src%.qmd}.md"
quarto render "$src" -M engine:knitr


# Render all qmd files in content/{{path}}/ non-recursively (fails if path="blog/")
render-dir path:
#!/usr/bin/env bash
set -euo pipefail
target='{{path}}'
target="${target#content/}"
target="${target%/}"
if [ "$target" = "blog" ]; then
echo "render-dir: found 'blog/' path, use 'just render-post' instead" >&2
exit 1
fi
cd content
shopt -s nullglob
files=( "$target"/*.qmd )
if [ ${#files[@]} -eq 0 ]; then
echo "No .qmd files found under content/$target" >&2
exit 1
fi
for src in "${files[@]}"; do
rm -f "${src%.qmd}.md"
base=$(basename "$src")
dir=$(dirname "$src")
tmp_src="$dir/x${base}"
mv "$src" "$tmp_src"
trap 'mv "$tmp_src" "$src" 2>/dev/null' EXIT
quarto render "$tmp_src" -M engine:knitr
mv "${tmp_src%.qmd}.md" "${src%.qmd}.md"
mv "$tmp_src" "$src"
trap - EXIT
done

# Render all qmd files in content/ (except those in "blog/")
render-all:
#!/usr/bin/env bash
set -euo pipefail
cd content
shopt -s globstar nullglob
files=()
for f in **/*.qmd; do
[[ "$f" == blog/* ]] && continue
files+=( "$f" )
done
if [ ${#files[@]} -eq 0 ]; then
echo "No .qmd files to render" >&2
exit 1
fi
for src in "${files[@]}"; do
rm -f "${src%.qmd}.md"
base=$(basename "$src")
dir=$(dirname "$src")
tmp_src="$dir/x${base}"
mv "$src" "$tmp_src"
trap 'mv "$tmp_src" "$src" 2>/dev/null' EXIT
quarto render "$tmp_src" -M engine:knitr
mv "${tmp_src%.qmd}.md" "${src%.qmd}.md"
mv "$tmp_src" "$src"
trap - EXIT
done
cp node_modules/basecoate-css/dist/js/*.js static/js/

# Generate Tailwind static/style.css from css/style.css
tailwind-build:
npm run tailwind:build
# Create a new documentation page at content/{{path}}.qmd
page path:
#!/usr/bin/env bash
set -euo pipefail
if [ -f "content/{{ path }}.qmd" ]; then
echo "Error: content/{{ path }}.qmd already exists."
exit 1
fi
cat > "content/{{ path }}.qmd" <<-EOF
---
title:
description: ""
weight: 0
extra:
short_title:
---
EOF
echo "Created content/{{ path }}.qmd"

# Create a new documentation page at content/{{path}}
new-page path:
#!/usr/bin/env bash
set -euo pipefail
rel='{{path}}'
rel="${rel#content/}"
if [[ "$rel" == blog/* ]]; then
echo "new-page: found 'blog/' path, use 'just new-post' instead" >&2
exit 1
fi
file="content/$rel"
parent=$(dirname "$file")
if [ ! -d "$parent" ]; then
echo "new-page: section $parent does not exist, use 'just new-section' first" >&2
exit 1
fi
if [ -e "$file" ]; then
echo "File exists: $file" >&2
exit 1
fi
cat > "$file" <<-EOF
---
title:
description: ""
weight: 0
extra:
short_title:
---
EOF
echo "Created $file"
# Create a new blog post at content/blog/{{title}}/index.qmd
post title:
#!/usr/bin/env bash
set -euo pipefail
date=$(date +%Y-%m-%d)
dir="content/blog/${date}-{{ title }}"
mkdir -p "$dir"
cat > "$dir/index.qmd" <<-EOF
---
title: {{ title }}
date: "$date"
authors: []
taxonomies:
tags: []
---
EOF
echo "Created $dir/index.qmd"

# Create a new documentation section at content/{{path}}/
new-section path:
#!/usr/bin/env bash
set -euo pipefail
dir="content/{{path}}"
dir="${dir%/}"
file="$dir/_index.qmd"
if [ -e "$file" ]; then
echo "File exists: $file" >&2
exit 1
fi
mkdir -p "$dir"
cat > "$file" <<-EOF
---
title:
description: ""
weight: 0
extra:
section_title:
---
EOF
echo "Created $file"
# Render a documentation page at content/{{path}}.qmd
render-page path: (render-one path)

# Create a new blog post at content/blog/{{date}}-{{title}}/index.qmd
new-post title:
#!/usr/bin/env bash
set -euo pipefail
date=$(date +%Y-%m-%d)
dir="content/posts/${date}-{{title}}"
mkdir -p "$dir"
cat > "$dir/index.qmd" <<-EOF
---
title: {{title}}
date: "$date"
authors: []
taxonomies:
tags: []
---
EOF
echo "Created $dir/index.qmd"
# Render a blog post at content/blog/{{title}}/index.qmd
render-post title: (render-one ("blog" / title / "index"))

# Render a blog post at content/blog/{{date}}-{{title}}/index.qmd
render-post title:
[private]
render-one path:
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
matches=( content/blog/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-{{title}} )
if [ ${#matches[@]} -eq 0 ]; then
echo "No post found matching *-{{title}}" >&2; exit 1
elif [ ${#matches[@]} -gt 1 ]; then
echo "Ambiguous title, matched: ${matches[*]}" >&2; exit 1
cd content
if [ -f "{{ path }}.qmd" ]; then
rm -f "{{ path }}.md"
quarto render "{{ path }}.qmd" {{ quarto_out + " " + quarto_flags }}
else
echo "Error: Could not find content/{{ path }}.qmd."
exit 1
fi
quarto render "${matches[0]}/index.qmd" -M engine:knitr

# Run Tailwind watch and Zola live serve in parallel
dev:
npm run dev
# Render all pages in content/ recursively (ignores blog/)
render:
#!/usr/bin/env bash
set -euo pipefail
shopt -s globstar nullglob
cd content
for page in **/*.qmd; do
[[ "$page" == blog/* ]] && continue
rm -f "${page%.qmd}.md"
quarto render "$page" {{ quarto_out + " " + quarto_flags }}
done
echo "Successfully rendered all documentation pages."

quarto_out := "--to commonmark+yaml_metadata_block"
quarto_flags := "-M engine:knitr -M wrap:preserve -M from:markdown-smart"