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

# Convert changed .fodt tutorials to PDF beside the source, then commit them
# back to the branch on push / workflow_dispatch.
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

# Keep PR and push runs independent so a PR check cannot cancel the push
# job that commits PDFs into the branch.
concurrency:
group: fodt-to-pdf-${{ github.event_name }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

permissions:
contents: write

jobs:
convert:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# Always check out a named branch for push/dispatch so git push works.
# For pull_request, check out the PR head commit.
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref_name }}
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true

- 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 "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
./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

- 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
# Only push/dispatch write back to the branch. PR jobs upload artifacts only
# (same-repo PR branches still get commits from the parallel push event).
if: >-
steps.fodt.outputs.count != '0' &&
(github.event_name == 'push' || github.event_name == 'workflow_dispatch')
shell: bash
run: |
set -euo pipefail

BRANCH="${{ github.ref_name }}"
echo "Committing PDFs onto branch: ${BRANCH}"
echo "git status before add:"
git status --short

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

echo "git status after add:"
git status --short

if git diff --cached --quiet; then
echo "No PDF content changes to commit (already up to date)."
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"

# Rebase onto remote tip in case another job pushed meanwhile, then push.
git pull --rebase --autostash origin "${BRANCH}"
git push origin "HEAD:${BRANCH}"
echo "Pushed PDFs next to their .fodt sources on ${BRANCH}"
62 changes: 39 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,49 @@
# tutorials
This repository is where contributed tutorials for SasView live!
This is repository for contributed SasView tutorials

.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).
Note: an open PR no longer cancels this push job.
- On **pull requests**: PDFs are generated and uploaded as artifacts for
review. For same-repo PR branches, the accompanying **push** event commits
the PDFs into the branch.
- **workflow_dispatch** can rebuild all (or recently changed) tutorials and
commit the PDFs back to the selected branch.

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