Skip to content
Merged
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
57 changes: 57 additions & 0 deletions .github/workflows/quality-assurance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Quality Assurance

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
xml-lint:
name: XML Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.x"

- name: Install xmllint and zpretty
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libxml2-utils
pip install --no-cache-dir zpretty

- name: Well-formedness
id: wellformed
continue-on-error: true
run: bash scripts/lint-xml.sh well-formed

- name: XSD schema validation
id: schema
continue-on-error: true
run: bash scripts/lint-xml.sh schema

- name: zpretty formatting
id: format
continue-on-error: true
run: bash scripts/lint-xml.sh format

- name: Summary
if: always()
run: |
{
echo "## XML Lint"
echo ""
echo "| Phase | Files | Failed | Outcome |"
echo "| --- | ---: | ---: | --- |"
echo "| Well-formedness | ${{ steps.wellformed.outputs.total }} | ${{ steps.wellformed.outputs.failed }} | ${{ steps.wellformed.outcome }} |"
echo "| Schema validation | ${{ steps.schema.outputs.total }} | ${{ steps.schema.outputs.failed }} | ${{ steps.schema.outcome }} |"
echo "| zpretty formatting | ${{ steps.format.outputs.total }} | ${{ steps.format.outputs.failed }} | ${{ steps.format.outcome }} |"
echo ""
echo "_Pre-existing failures are recorded but do not fail the workflow. They will be reduced incrementally._"
} >> "$GITHUB_STEP_SUMMARY"
99 changes: 99 additions & 0 deletions scripts/lint-xml.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# XML quality checks for PenText.
# well-formed - xmllint --noout per file
# schema - XSD validation of files declaring xsi:noNamespaceSchemaLocation
# format - zpretty --check on every *.xml file
# No arg or `all` runs all three; exit non-zero if any phase has failures.
#
# When GITHUB_OUTPUT is set (inside a GitHub Actions step), each phase
# writes `total=` and `failed=` to it so the workflow summary can report
# counts.
set -uo pipefail

phase="${1:-all}"

mapfile -t xml_files < <(
find . -type f -name "*.xml" \
-not -path "./target/*" \
-not -path "./.git/*" \
-not -path "./.github/*" \
| sort
)

emit_summary() {
local name="$1" total="$2" failed="$3"
echo "SUMMARY phase=$name total=$total failed=$failed"
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
{
echo "total=$total"
echo "failed=$failed"
} >> "$GITHUB_OUTPUT"
fi
}

run_wellformed() {
echo "::group::Well-formedness (${#xml_files[@]} files)"
local failed=0
for f in "${xml_files[@]}"; do
if ! xmllint --noout "$f"; then
failed=$((failed + 1))
fi
done
echo "::endgroup::"
emit_summary well-formed "${#xml_files[@]}" "$failed"
[[ $failed -eq 0 ]]
}

run_schema() {
echo "::group::XSD schema validation"
local fails=0 checked=0
local dtd_dir
dtd_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/dtd"
for f in "${xml_files[@]}"; do
local xsd_rel xsd_name xsd_abs
xsd_rel=$(grep -m1 -oE 'xsi:noNamespaceSchemaLocation="[^"]+"' "$f" \
| sed -E 's/.*="([^"]+)"/\1/' || true)
[[ -z "$xsd_rel" ]] && continue
xsd_name="$(basename "$xsd_rel")"
xsd_abs="$dtd_dir/$xsd_name"
if [[ ! -f "$xsd_abs" ]]; then
echo "WARN: schema not found in dtd/ for $f -> $xsd_name" >&2
fails=$((fails + 1))
continue
fi
checked=$((checked + 1))
echo "-> $f (schema: $xsd_name)"
if ! xmllint --noout --xinclude --schema "$xsd_abs" "$f"; then
fails=$((fails + 1))
fi
done
echo "::endgroup::"
emit_summary schema "$checked" "$fails"
[[ $fails -eq 0 ]]
}

run_format() {
echo "::group::zpretty formatting (${#xml_files[@]} files)"
local out
out=$(zpretty --check "${xml_files[@]}" 2>&1 || true)
echo "$out"
local failed
failed=$(printf '%s\n' "$out" | grep -c "^This file would be rewritten:" || true)
echo "::endgroup::"
emit_summary format "${#xml_files[@]}" "$failed"
[[ $failed -eq 0 ]]
}

case "$phase" in
well-formed) run_wellformed ;;
schema) run_schema ;;
format) run_format ;;
all)
rc=0
run_wellformed || rc=1
run_schema || rc=1
run_format || rc=1
exit $rc
;;
*) echo "usage: $0 {well-formed|schema|format|all}" >&2; exit 2 ;;
esac
Loading