Skip to content

minor fix

minor fix #195

name: Generate Students Notebooks branch
on:
push:
branches: [main]
paths: ['**.ipynb']
workflow_dispatch:
permissions:
contents: write
actions: write
jobs:
process:
runs-on: ubuntu-latest
env:
CONFIG_PATH: .github/conversion.json
SUMMARY_FILE: /tmp/README.md
steps:
- name: Initialize summary file
run: |
rm -f "$SUMMARY_FILE"
mkdir -p "$(dirname "$SUMMARY_FILE")"
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate JSON config file syntax
run: |
if ! jq empty "$CONFIG_PATH" 2>/tmp/json_error; then
printf "## ❌ Error in JSON configuration file\n" >> $SUMMARY_FILE
printf "Please fix $CONFIG_PATH error:\n" >> $SUMMARY_FILE
cat /tmp/json_error | sed 's/^jq: //' >> $SUMMARY_FILE
exit 1
fi
- name: Validate JSON config file against syntax and config schema
run: |
cat > /tmp/schema.json << 'EOF'
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["solution_marker", "placeholder", "generate_zip", "notebooks_dir", "student_postfix", "tutor_postfix"],
"properties": {
"solution_marker": {
"type": "object",
"required": ["code", "markdown"],
"properties": {
"code": {"type": "string"},
"markdown": {"type": "string"}
},
"additionalProperties": false
},
"placeholder": {
"type": "object",
"required": ["code", "markdown"],
"properties": {
"code": {"type": "string"},
"markdown": {"type": "string"}
},
"additionalProperties": false
},
"generate_zip": {"type": "boolean"},
"notebooks_dir": {"type": "array", "items": {"type": "string"}},
"pre_processing": {"type": "string"},
"post_processing": {"type": "string"},
"tutor_postfix": {"type": "string"},
"student_postfix": {"type": "string"},
"rebuild_all": {"type": "boolean"}
}
}
EOF
npm install -g @jirutka/ajv-cli
if ! ajv validate -s /tmp/schema.json "$CONFIG_PATH" --strict=true --all-errors > /tmp/json_error 2>&1 ; then
printf "## ❌ Error in JSON configuration file\n" >> $SUMMARY_FILE
cat /tmp/json_error | sed 's/^ajv: //' >> $SUMMARY_FILE
exit 1
fi
- name: Check monitored directories existence
run: |
EXIT_WITH_ERROR=false
ERROR_HEADER_CREATED=false
while IFS= read -r dir; do
if [ ! -d "$dir" ]; then
if [ "$ERROR_HEADER_CREATED" = false ]; then
printf "## ❌ Error: Missing notebook directories\n" >> $SUMMARY_FILE
printf "The following directories specified in $CONFIG_PATH don't exist:\n" >> $SUMMARY_FILE
ERROR_HEADER_CREATED=true
fi
printf -- "* \`\`\`%s\`\`\`\n" "$dir" >> $SUMMARY_FILE
EXIT_WITH_ERROR=true
fi
done < <(jq -r '.notebooks_dir[]' "$CONFIG_PATH")
if [ "$EXIT_WITH_ERROR" = true ]; then
printf "\nPlease create these directories or update the configuration file.\n" >> $SUMMARY_FILE
exit 1
fi
- name: Store pre and post-processing command for future execution
run: |
echo "PRE_PROCESSING=$(jq -r '.pre_processing // empty' "$CONFIG_PATH")" >> $GITHUB_ENV
echo "POST_PROCESSING=$(jq -r '.post_processing // empty' "$CONFIG_PATH")" >> $GITHUB_ENV
- name: Check pre/post-processing commands syntax
run: |
if [ -n "$PRE_PROCESSING" ]; then
ERROR_OUTPUT=$(echo "$PRE_PROCESSING" | bash -n 2>&1) || {
printf "## ❌ Error: Pre-processing command syntax error\n" >> $SUMMARY_FILE
printf "Please fix your pre-processing command syntax:\n" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
printf "%s\n" "$PRE_PROCESSING" >> $SUMMARY_FILE
printf "\`\`\`\n\n" >> $SUMMARY_FILE
printf "**Error details:**\n" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
printf "%s\n" "$ERROR_OUTPUT" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
exit 1
}
fi
if [ -n "$POST_PROCESSING" ]; then
ERROR_OUTPUT=$(echo "$POST_PROCESSING" | bash -n 2>&1) || {
printf "## ❌ Error: Post-processing command syntax error\n" >> $SUMMARY_FILE
printf "Please fix your post-processing command syntax:\n" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
printf "%s\n" "$POST_PROCESSING" >> $SUMMARY_FILE
printf "\`\`\`\n\n" >> $SUMMARY_FILE
printf "**Error details:**\n" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
printf "%s\n" "$ERROR_OUTPUT" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
exit 1
}
fi
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Setup uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: ".github/scripts/student_version.py"
# Step 1 : Extract rebuild_all option
- name: Extract rebuild_all option
id: config
run: |
REBUILD_ALL=$(jq -r '.rebuild_all // empty' "$CONFIG_PATH")
if [ "$REBUILD_ALL" = "true" ]; then
echo "rebuild_all=true" >> $GITHUB_OUTPUT
else
echo "rebuild_all=false" >> $GITHUB_OUTPUT
fi
# Step 2 : Manage Students branch (full rebuild or selective)
- name: Manage Students branch
run: |
if [ "${{ steps.config.outputs.rebuild_all }}" = "true" ]; then
git fetch origin
git checkout main
git branch -D Students 2>/dev/null || true
git checkout -b Students
else
git fetch origin Students
git checkout -B Students main
git merge origin/Students || true
fi
# Step 3 : Execute pre-processing command
- name: Execute pre-processing command
run: |
if [ -n "$PRE_PROCESSING" ]; then
OUTPUT=$(bash -c "$PRE_PROCESSING" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
printf "## Pre-processing executed successfully ✅\n" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
else
printf "\n" >> $SUMMARY_FILE
fi
else
printf "## Pre-processing failed ❌\n" >> $SUMMARY_FILE
printf "Exit code: %d\n" "$EXIT_CODE" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "\nCommand output (including errors):\n" >> $SUMMARY_FILE
printf "\`\`\`bash\n" >> $SUMMARY_FILE
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
fi
exit 1
fi
fi
# Step 4 : Detect notebooks to process (changed or all)
- name: Detect notebooks to process
id: notebooks
run: |
REBUILD_ALL="${{ steps.config.outputs.rebuild_all }}"
mapfile -t notebooks_dirs < <(jq -r '.notebooks_dir[]' "$CONFIG_PATH")
# Remove trailing slash from directory if present to avoid double slashes in comparisons
for i in "${!notebooks_dirs[@]}"; do
notebooks_dirs[$i]="${notebooks_dirs[$i]%/}"
done
NOTEBOOKS_LIST="notebooks_to_process.txt"
> $NOTEBOOKS_LIST
# In case of rebuilding of all notebooks, just parse monitored directories
if [ "$REBUILD_ALL" = "true" ]; then
for dir in "${notebooks_dirs[@]}"; do
find "$dir" -type f -name "*.ipynb"
done | sort -u > $NOTEBOOKS_LIST
else
# In case of rebuilding of only changed notebooks, filter by comparing the current commit (HEAD_SHA)
# to the previous commit (BASE_SHA) using git diff
printf "DEBUG: Mode rebuild_all=false, détection des notebooks modifiés\n"
BASE_SHA="${{ github.event.before }}"
HEAD_SHA="${{ github.sha }}"
printf "DEBUG: BASE_SHA='$BASE_SHA', HEAD_SHA='$HEAD_SHA'\n"
git diff --name-only --diff-filter=ACMRT "$BASE_SHA" "$HEAD_SHA" > changed_files.txt
printf "DEBUG: changed_files.txt contenu :\n"
cat changed_files.txt
while IFS= read -r f; do
printf "DEBUG: Fichier changé : '$f'\n"
for d in "${notebooks_dirs[@]}"; do
d="${d%/}"
# Check if the changed file is exactly the monitored directory or inside it, and is a notebook
if { [[ "$f" == "$d" ]] || [[ "$f" == "$d/"* ]]; } && [[ "$f" == *.ipynb ]]; then
echo "$f" >> $NOTEBOOKS_LIST
break
fi
done
done < changed_files.txt
sort -u $NOTEBOOKS_LIST -o $NOTEBOOKS_LIST
fi
echo "notebooks_list=$NOTEBOOKS_LIST" >> $GITHUB_OUTPUT
# Step 5 : Convert notebooks and handle conflicts
- name: Convert notebooks and handle conflicts
run: |
printf "## Notebooks processed\n" >> $SUMMARY_FILE
printf "| Notebook | Questions | Code Blocks | ZIP | Merge conflict |\n" >> $SUMMARY_FILE
printf "|----------|-----------|-------------|-----|----|\n" >> $SUMMARY_FILE
REBUILD_ALL="${{ steps.config.outputs.rebuild_all }}"
NOTEBOOKS_LIST="${{ steps.notebooks.outputs.notebooks_list }}"
> notebooks_with_conflicts.txt
> other_files_with_conflicts.txt
while IFS= read -r nb; do
[ -z "$nb" ] && continue
SUMMARY_LINE=$(uv run .github/scripts/student_version.py "$nb" --config "$CONFIG_PATH" --hide-header)
rm "$nb"
CONFLICT_MARK=""
if [ "$REBUILD_ALL" = "false" ]; then
if git ls-files -u -- "$nb" | grep -q .; then
git checkout --ours -- "$nb"
git add "$nb"
echo "$nb" >> notebooks_with_conflicts.txt
CONFLICT_MARK="⚠️"
else
git add "$nb"
fi
else
git add "$nb"
fi
printf "%s | %s\n" "$SUMMARY_LINE" "$CONFLICT_MARK" >> $SUMMARY_FILE
done < "$NOTEBOOKS_LIST"
if [ "$REBUILD_ALL" = "false" ]; then
git ls-files -u | awk '{print $4}' | grep -v '\.ipynb$' | sort -u | while read -r file; do
git checkout --theirs -- "$file"
git add "$file"
echo "$file" >> other_files_with_conflicts.txt
done
fi
if [ -s other_files_with_conflicts.txt ]; then
echo -e "\n## Other files overwritten due to conflict:\n" >> $SUMMARY_FILE
cat other_files_with_conflicts.txt >> $SUMMARY_FILE
fi
# Step 6 : Execute post-processing command
- name: Execute post-processing command
run: |
if [ -n "$POST_PROCESSING" ]; then
OUTPUT=$(bash -c "$POST_PROCESSING" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
printf "## Post-processing executed successfully ✅\n" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
else
printf "\n" >> $SUMMARY_FILE
fi
else
printf "## Post-processing failed ❌\n" >> $SUMMARY_FILE
printf "Exit code: %d\n" "$EXIT_CODE" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "\nCommand output (including errors):\n" >> $SUMMARY_FILE
printf "\`\`\`bash\n" >> $SUMMARY_FILE
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
printf "\`\`\`\n" >> $SUMMARY_FILE
fi
exit 1
fi
fi
- name: manage summary
run: |
cp -a $SUMMARY_FILE .
cat README.md >> $GITHUB_STEP_SUMMARY
- name: Commit and push
run: |
git add .
git commit -m "Update student materials: $(date +'%Y-%m-%d %H:%M:%S')" || printf "No changes\n"
git push origin Students --force