A small PyQt6 desktop app for scanning a batch of files (.pdf, .docx, .txt) for a set of
keywords, showing a found/missing grid you can export to CSV.
- Add files via a file picker or by dragging them onto the window
- Remove selected files from the batch without starting over
- Files from different folders that share a name show a distinguishing path fragment in the grid (full path on hover), so same-named documents stay tellable apart
- Add/remove/save/load keyword lists (a saved list remembers its match mode, so a regex list loads back as regex instead of silently becoming literal substrings)
- Match keywords as a substring, whole word, or regex pattern - one mode for the whole scan, applied to every keyword in the list, not set per keyword - with invalid regex patterns flagged live and blocked from starting a scan
- Scan runs on a background process pool so the UI stays responsive, with a progress dialog you can cancel mid-scan
- Per-file scan errors (corrupt/unreadable files) are reported without failing the whole batch
- Hover a found cell to see every match as a snippet of surrounding text (with a PDF page number, where the format supports one)
- Sort results by clicking a column header, or filter rows by typing in the filter box
- Double-click a row to open that file in its default app
- Export results to CSV or a color-coded
.xlsxworkbook — always with a full-path column, optionally with page-number/snippet columns added - Customizable found/missing/scan-error colors and labels (Options dialog), persisted across runs
- Remembers the last-used directory across file dialogs and the window's size/position across runs
- Has an actual app/taskbar icon instead of the default Qt one
- Python 3.14+
- uv for dependency management
uv sync --group testuv run python main.pyQT_QPA_PLATFORM=offscreen uv run --frozen --group test pytest -v(QT_QPA_PLATFORM=offscreen isn't needed if you have a display available.)
uv run --frozen --group lint ruff check .uv run --group build nuitka main.pyProduces file-scanner.exe on Windows or file-scanner on Linux. Build flags live as
# nuitka-project: comments at the top of main.py, including --low-memory, --lto=no, and
--include-qt-plugins=platforms — these keep the C-compilation step's memory use bounded (see
CLAUDE.md for why). A GitHub Actions workflow (.github/workflows/build-exe.yml) also builds
both platforms and attaches the binaries to GitHub Releases when a v* tag is pushed.
GPL-3.0-only (see LICENSE) — required by PyQt6 itself, which is dual-licensed GPLv3/commercial by Riverbank Computing; this project doesn't hold a commercial PyQt6 license, so GPLv3 is the license that legally covers distributing the built binaries.
main.py— entry point, Nuitka build flagsmain_window.py— main window UI and the backgroundScanWorkerscanner.py— pure scan/extraction logic (no Qt dependency), used by the worker pooloptions_dialog.py— display options (colors/labels for found/missing/scan-error)assets/— app icon (icon.svgsource,icon.pngfor the runtime window icon,icon.icofor the Windows executable resource)tests/— pytest + pytest-qt test suite