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
62 changes: 62 additions & 0 deletions .github/workflows/action-integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Action Integration Test

on:
pull_request:
workflow_dispatch:
inputs:
ref:
description: 'Optional branch, tag, or SHA to checkout for testing'
required: false
default: 'main'
push:
branches:
- 'main'

jobs:
test-action:
name: Test composite action behavior
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
case:
- name: clean notebook should pass
path: tests/e2e_notebooks/test_nochange.ipynb
expect_failure: false
- name: clean notebook with parentheses in filename should pass
path: tests/e2e_notebooks/test (nochange).ipynb
expect_failure: false
- name: dirty notebook with space in filename should fail
path: tests/e2e_notebooks/test metadata.ipynb
expect_failure: true
- name: dirty notebook should fail
path: tests/e2e_notebooks/test_metadata.ipynb
expect_failure: true
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}

- name: Run action for ${{ matrix.case.name }}
id: run_case
continue-on-error: true
uses: ./
with:
paths: |
${{ matrix.case.path }}

- name: Assert expected outcome
if: always()
shell: bash
run: |
expected_outcome="${{ matrix.case.expect_failure && 'failure' || 'success' }}"
actual_outcome="${{ steps.run_case.outcome }}"

if [ "$actual_outcome" != "$expected_outcome" ]; then
echo "Case: ${{ matrix.case.name }}"
echo "Path: ${{ matrix.case.path }}"
echo "Expected outcome: $expected_outcome"
echo "Actual outcome: $actual_outcome"
exit 1
fi
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ All inputs are optional and have sensible defaults:
| Input | Description | Default |
| ----- | ----------- | ------- |
| `python-version` | Python version to use (supports versions, ranges, or "3.x") | `'3.x'` |
| `paths` | Space-separated list of paths to check (supports wildcards) | `'**/*.ipynb'` |
| `paths` | Newline-separated list of paths to check (supports wildcards) | `'**/*.ipynb'` |
| `extra-keys` | Extra metadata keys to strip (space-separated) | `''` |
| `keep-output` | Keep output in notebooks | `'false'` |
| `keep-count` | Keep execution counts | `'false'` |
Expand All @@ -605,7 +605,9 @@ All inputs are optional and have sensible defaults:
- name: Check notebooks are stripped
uses: kynan/nbstripout@main
with:
paths: 'notebooks/*.ipynb examples/*.ipynb'
paths: |
notebooks/*.ipynb
examples/*.ipynb
```

#### Strip extra metadata keys
Expand Down Expand Up @@ -641,7 +643,8 @@ All inputs are optional and have sensible defaults:
- name: Check notebooks are stripped
uses: kynan/nbstripout@main
with:
paths: 'notebooks/**/*.ipynb'
paths: |
notebooks/**/*.ipynb
keep-count: 'true'
extra-keys: 'metadata.widgets cell.metadata.tags'
```
Expand Down
110 changes: 57 additions & 53 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ inputs:
required: false
default: '3.x'
paths:
description: 'Space-separated list of paths to check (supports wildcards)'
description: 'Newline-separated list of paths to check (supports wildcards)'
required: false
default: '**/*.ipynb'
extra-keys:
Expand All @@ -33,63 +33,69 @@ runs:
with:
python-version: ${{ inputs.python-version }}
pip-install: 'nbstripout'

- name: Find notebook files
id: find-notebooks
shell: bash
run: |
# Find all notebooks matching the patterns
notebooks=""
for pattern in ${{ inputs.paths }}; do
found=$(find . -path "./$pattern" -type f 2>/dev/null || true)
if [ -n "$found" ]; then
notebooks="$notebooks $found"
fi
done

if [ -z "$notebooks" ]; then
echo "No notebook files found"
else
echo "Found notebooks: $notebooks"
echo "notebooks=$notebooks" >> $GITHUB_OUTPUT
fi


- name: Check notebooks are stripped
if: steps.find-notebooks.outputs.notebooks != ''
shell: bash
env:
Comment thread
kynan marked this conversation as resolved.
INPUT_PATHS: ${{ inputs.paths }}
INPUT_EXTRA_KEYS: ${{ inputs.extra-keys }}
INPUT_KEEP_OUTPUT: ${{ inputs.keep-output }}
INPUT_KEEP_COUNT: ${{ inputs.keep-count }}
INPUT_STRIP_INIT_CELLS: ${{ inputs.strip-init-cells }}
run: |
# Build nbstripout command with options
cmd="nbstripout --verify"

if [ "${{ inputs.keep-output }}" = "true" ]; then
cmd="$cmd --keep-output"
# Build nbstripout args as an array (bash 3.1+)
args=(--verify)

if [ "$INPUT_KEEP_OUTPUT" = "true" ]; then
args+=(--keep-output)
fi

if [ "$INPUT_KEEP_COUNT" = "true" ]; then
args+=(--keep-count)
fi
if [ "${{ inputs.keep-count }}" = "true" ]; then
cmd="$cmd --keep-count"

if [ "$INPUT_STRIP_INIT_CELLS" = "true" ]; then
args+=(--strip-init-cells)
fi
if [ "${{ inputs.strip-init-cells }}" = "true" ]; then
cmd="$cmd --strip-init-cells"

if [ -n "$INPUT_EXTRA_KEYS" ]; then
args+=(--extra-keys "$INPUT_EXTRA_KEYS")
fi

if [ -n "${{ inputs.extra-keys }}" ]; then
cmd="$cmd --extra-keys '${{ inputs.extra-keys }}'"

# Use Python for file discovery: correct ** glob handling on all platforms
# Outputs null-delimited paths and deduplicates overlapping patterns.
# INPUT_PATHS is quoted to prevent bash from expanding globs before Python sees them.
notebook_list="${RUNNER_TEMP:-/tmp}/nbstripout_filelist.txt"

python3 - "$INPUT_PATHS" << 'PYTHON' > "$notebook_list"
import sys
from pathlib import Path

raw_input = sys.argv[1]
patterns = [stripped for line in raw_input.splitlines() if (stripped := line.strip())]
seen = set()
for pat in patterns:
for p in sorted(Path(".").glob(pat)):
rp = str(p)
if p.is_file() and rp not in seen:
seen.add(rp)
sys.stdout.write(rp + "\0")
PYTHON

if [ ! -s "$notebook_list" ]; then
echo "No notebook files found matching: $INPUT_PATHS"
exit 0
fi

# Check each notebook
failed=0
for notebook in ${{ steps.find-notebooks.outputs.notebooks }}; do
echo "Checking $notebook..."
if ! eval "$cmd $notebook"; then
echo "❌ $notebook has output that should be stripped"
failed=1
else
echo "✅ $notebook is properly stripped"
fi
done

if [ $failed -eq 1 ]; then

# Count null-delimited entries (POSIX-compatible, works on macOS and Linux)
notebook_count=$(tr -cd '\0' < "$notebook_list" | wc -c | tr -d ' ')
echo "Found $notebook_count notebook(s) to check."

# xargs -0 reads null-delimited paths (handles any filename characters)
# and batches automatically for ARG_MAX safety on large repos
if xargs -0 nbstripout "${args[@]}" < "$notebook_list"; then
echo "All notebooks are properly stripped! ✅"
else
echo ""
echo "================================================"
echo "Some notebooks have output that should be stripped."
Expand All @@ -98,5 +104,3 @@ runs:
echo "================================================"
exit 1
fi

echo "All notebooks are properly stripped! ✅"
61 changes: 61 additions & 0 deletions tests/e2e_notebooks/test (nochange).ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook should not be changed by nbstripout."
]
},
{
"cell_type": "code",
"metadata": {},
"outputs": [],
"source": [
"1+1 # This cell has `\"init_cell:\" true`"
]
},
{
"cell_type": "code",
"metadata": {
"keep_output": true
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2+2 # This cell has `\"keep_output:\" true`"
]
}
],
"metadata": {
"celltoolbar": "Edit Metadata",
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.11"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
101 changes: 101 additions & 0 deletions tests/e2e_notebooks/test metadata.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook tests that cells with either `\"keep_output\": true` or `\"init_cell\": true` are not stripped."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false,
"init_cell": true
},
"outputs": [
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1+1 # This cell has `\"init_cell:\" true`"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false,
"keep_output": true
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2+2 # This cell has `\"keep_output:\" true`"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"6"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"3+3"
]
}
],
"metadata": {
"celltoolbar": "Edit Metadata",
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.11"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Loading
Loading