Skip to content
Closed
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
204 changes: 204 additions & 0 deletions .github/workflows/fodt-to-pdf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
name: Convert FODT to PDF

# Convert added/changed .fodt tutorials to PDF with LibreOffice and place each
# PDF beside its source (same directory / basename).
#
# - push: convert, commit PDF next to each .fodt, upload artifacts
# - pull_request: convert + upload artifacts (CI check for reviewers)
# - workflow_dispatch: manual rebuild + commit
on:
pull_request:
paths:
- "**.fodt"
- "build_tools/**"
- ".github/workflows/fodt-to-pdf.yml"
push:
paths:
- "**.fodt"
workflow_dispatch:
inputs:
all_fodt:
description: "Convert every tracked .fodt file (not only files from the latest commit)"
type: boolean
default: true

concurrency:
# One in-flight run per branch (covers same-repo PR + push races).
group: fodt-to-pdf-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

permissions:
contents: write

jobs:
convert:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Decide target branch
id: meta
shell: bash
run: |
set -euo pipefail
EVENT="${{ github.event_name }}"
# Commit PDFs into the repo on push/manual runs.
# Same-repo PRs still get PDFs via the accompanying push event.
# Fork PRs only receive artifacts (no write access to the fork).
CAN_PUSH=false
BRANCH="${{ github.ref_name }}"

if [[ "$EVENT" == "push" && "${{ github.ref_type }}" == "branch" ]]; then
CAN_PUSH=true
BRANCH="${{ github.ref_name }}"
elif [[ "$EVENT" == "workflow_dispatch" ]]; then
CAN_PUSH=true
BRANCH="${{ github.ref_name }}"
elif [[ "$EVENT" == "pull_request" ]]; then
BRANCH="${{ github.head_ref }}"
fi

echo "event=$EVENT"
echo "branch=$BRANCH"
echo "can_push=$CAN_PUSH"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
echo "can_push=$CAN_PUSH" >> "$GITHUB_OUTPUT"

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# Check out the branch that should receive the PDFs whenever we can push.
ref: ${{ steps.meta.outputs.can_push == 'true' && steps.meta.outputs.branch || github.sha }}
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install LibreOffice
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libreoffice-writer \
default-jre-headless
soffice --version

- name: Collect FODT files to convert
id: fodt
shell: bash
run: |
set -euo pipefail

if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
mapfile -t FILES < <(git diff --name-only --diff-filter=AM "${BASE_SHA}...${HEAD_SHA}" -- '*.fodt' || true)
elif [[ "${{ github.event_name }}" == "push" ]]; then
BEFORE="${{ github.event.before }}"
if [[ -z "$BEFORE" || "$BEFORE" =~ ^0+$ ]]; then
mapfile -t FILES < <(git ls-files '*.fodt')
else
mapfile -t FILES < <(git diff --name-only --diff-filter=AM "$BEFORE" "${{ github.sha }}" -- '*.fodt' || true)
fi
elif [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.all_fodt }}" != "true" ]]; then
mapfile -t FILES < <(git diff --name-only --diff-filter=AM HEAD~1 HEAD -- '*.fodt' || true)
else
mapfile -t FILES < <(git ls-files '*.fodt')
fi

COUNT=0
: > /tmp/fodt_list.txt
for f in "${FILES[@]+"${FILES[@]}"}"; do
[[ -z "${f:-}" ]] && continue
[[ -f "$f" ]] || continue
echo "$f" >> /tmp/fodt_list.txt
COUNT=$((COUNT + 1))
done

echo "count=$COUNT" >> "$GITHUB_OUTPUT"
{
echo "files<<EOF"
cat /tmp/fodt_list.txt
echo "EOF"
} >> "$GITHUB_OUTPUT"

echo "FODT files to convert ($COUNT):"
cat /tmp/fodt_list.txt || true

- name: Skip notice
if: steps.fodt.outputs.count == '0'
run: |
echo "No matching .fodt files to convert for this event."

- name: Convert FODT to PDF beside sources
if: steps.fodt.outputs.count != '0'
shell: bash
run: |
set -euo pipefail
chmod +x build_tools/convert_fodt_to_pdf.sh build_tools/build_pdfs.sh
mapfile -t FILES < /tmp/fodt_list.txt
# Writes each PDF next to its .fodt (same directory, same basename).
./build_tools/build_pdfs.sh "${FILES[@]}"

echo "Generated PDFs (alongside sources):"
while IFS= read -r fodt; do
pdf="${fodt%.fodt}.pdf"
if [[ ! -f "$pdf" ]]; then
echo "error: expected PDF was not created: $pdf" >&2
exit 1
fi
ls -lh "$pdf"
done < /tmp/fodt_list.txt

- name: Stage PDF artifacts
if: steps.fodt.outputs.count != '0'
shell: bash
run: |
set -euo pipefail
mkdir -p /tmp/pdf-artifacts
while IFS= read -r fodt; do
pdf="${fodt%.fodt}.pdf"
mkdir -p "/tmp/pdf-artifacts/$(dirname "$pdf")"
cp "$pdf" "/tmp/pdf-artifacts/$pdf"
done < /tmp/fodt_list.txt
find /tmp/pdf-artifacts -type f -name '*.pdf' -ls

- name: Upload PDF artifacts
if: steps.fodt.outputs.count != '0'
uses: actions/upload-artifact@v4
with:
name: tutorial-pdfs-${{ github.event.pull_request.number || github.run_id }}
path: /tmp/pdf-artifacts
if-no-files-found: error
retention-days: 14

- name: Commit PDFs into the repository
if: steps.fodt.outputs.count != '0' && steps.meta.outputs.can_push == 'true'
shell: bash
run: |
set -euo pipefail

BRANCH="${{ steps.meta.outputs.branch }}"

# Avoid a convert↔push loop if this commit was created by the bot.
if git log -1 --pretty=%an | grep -qx 'github-actions\[bot\]'; then
if git log -1 --pretty=%s | grep -qx 'Auto-convert tutorial FODT to PDF'; then
echo "HEAD is already an auto-PDF commit; skipping another commit"
exit 0
fi
fi

while IFS= read -r fodt; do
pdf="${fodt%.fodt}.pdf"
git add -- "$pdf"
echo "staged $pdf (same directory as $fodt)"
done < /tmp/fodt_list.txt

if git diff --cached --quiet; then
echo "No PDF content changes to commit"
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "Auto-convert tutorial FODT to PDF"

# Push to the branch that owns the .fodt sources.
git push origin "HEAD:${BRANCH}"
echo "Committed PDFs next to their .fodt sources on branch ${BRANCH}"
58 changes: 36 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
# tutorials
This repository is where contributed tutorials for SasView live!

.fodt files placed here are grabbed by Jenkins and built into pdf
at https://jenkins.esss.dk/sasview/job/SasView_Tutorials/
`.fodt` files committed here are automatically converted to PDF by GitHub
Actions (`.github/workflows/fodt-to-pdf.yml`) using LibreOffice. Each PDF is
written next to its source (e.g. `foo.fodt` → `foo.pdf`).

Once any new/changed tutorials are ready for general consumption the
- On **push** to a branch: PDFs are generated beside the `.fodt` files and
committed back to that branch (and uploaded as workflow artifacts).
- On **pull requests**: PDFs are generated and uploaded as artifacts for
review. For same-repo PR branches, the accompanying push event also commits
the PDFs into the branch.
- **workflow_dispatch** can rebuild all (or recently changed) tutorials manually.

To convert locally (requires LibreOffice):

```bash
./build_tools/build_pdfs.sh # all tracked .fodt files
./build_tools/build_pdfs.sh path/to/file.fodt
./build_tools/build_pdfs.sh --root # root-level .fodt only
```

Once any new/changed tutorials are ready for general consumption the
following steps should be taken:

- All the pdf files should be copied to the /downloads folder of the
website repository sasview.github.io. This is effectively the
release step for the tutorials, making them available from
- All the pdf files should be copied to the /downloads folder of the
website repository sasview.github.io. This is effectively the
release step for the tutorials, making them available from
http://www.sasview.org/links.html

- For consistency, and to avoid rendering issues, the pdfs should
be downloaded from Jenkins AND NOT generated locally in LibreOffice,
etc.

- Note that technically only those pdfs that have changed (or which

- For consistency, and to avoid rendering issues, prefer the PDFs
produced by the GitHub Action rather than ad-hoc local exports.

- Note that technically only those pdfs that have changed (or which
are new) need to be copied.
- In order to make the new/changed tutorials available from the
SasView help documentation in the next release (and the developer

- In order to make the new/changed tutorials available from the
SasView help documentation in the next release (and the developer
builds) the following two steps need to be taken:
- The pdf files above need to be copied to the /src/sas/sasview/media

- The pdf files above need to be copied to the /src/sas/sasview/media
folder of the SasView repository and committed or git added.
- If any new pdf tutorial (ie, a new .fodt) was created then the
SasView documentation index needs to be updated by appropriately
editing the /docs/sphinx-docs/source/user/tutorial.rst file in the

- If any new pdf tutorial (ie, a new .fodt) was created then the
SasView documentation index needs to be updated by appropriately
editing the /docs/sphinx-docs/source/user/tutorial.rst file in the
SasView repository

34 changes: 34 additions & 0 deletions build_tools/build_pdfs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Build PDF tutorials from Flat ODT (.fodt) sources using LibreOffice.
#
# Usage:
# ./build_tools/build_pdfs.sh # all tracked *.fodt files
# ./build_tools/build_pdfs.sh path/to/a.fodt # specific file(s)
# ./build_tools/build_pdfs.sh --root # root-level *.fodt only
#
# CI entry point: .github/workflows/fodt-to-pdf.yml
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CONVERT="$ROOT_DIR/build_tools/convert_fodt_to_pdf.sh"

cd "$ROOT_DIR"
chmod +x "$CONVERT"

if [[ "${1:-}" == "--root" ]]; then
shift
shopt -s nullglob
files=(./*.fodt)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No root-level .fodt files found"
exit 0
fi
echo "Converting ${#files[@]} root-level FODT file(s) to PDF"
"$CONVERT" "${files[@]}"
elif [[ $# -gt 0 ]]; then
echo "Converting $# FODT file(s) to PDF"
"$CONVERT" "$@"
else
echo "Converting all tracked FODT files to PDF"
"$CONVERT"
fi
49 changes: 49 additions & 0 deletions build_tools/convert_fodt_to_pdf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Convert one or more Flat ODT (.fodt) tutorials to PDF with LibreOffice.
# Usage:
# convert_fodt_to_pdf.sh path/to/file.fodt [...]
# convert_fodt_to_pdf.sh # convert all tracked *.fodt files
set -euo pipefail

if ! command -v soffice >/dev/null 2>&1 && ! command -v libreoffice >/dev/null 2>&1; then
echo "error: LibreOffice (soffice) is not installed" >&2
exit 1
fi

SOFFICE=(soffice)
if ! command -v soffice >/dev/null 2>&1; then
SOFFICE=(libreoffice)
fi

convert_one() {
local fodt="$1"
if [[ ! -f "$fodt" ]]; then
echo "warning: skipping missing file: $fodt" >&2
return 0
fi
if [[ "$fodt" != *.fodt ]]; then
echo "warning: skipping non-.fodt file: $fodt" >&2
return 0
fi

local dir
dir="$(dirname "$fodt")"
echo "Converting $fodt -> ${dir}/$(basename "${fodt%.fodt}.pdf")"
"${SOFFICE[@]}" --headless --nologo --nofirststartwizard --norestore \
--convert-to pdf --outdir "$dir" "$fodt"
}

if [[ $# -eq 0 ]]; then
mapfile -t files < <(git ls-files '*.fodt' 2>/dev/null || ls -1 ./*.fodt 2>/dev/null || true)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No .fodt files to convert"
exit 0
fi
for fodt in "${files[@]}"; do
convert_one "$fodt"
done
else
for fodt in "$@"; do
convert_one "$fodt"
done
fi
4 changes: 0 additions & 4 deletions build_tools/jenkins_build.sh

This file was deleted.

Loading
Loading