Matterify
Extract and aggregate YAML frontmatter from Markdown files, with optional SHA-256 hashes and file statistics.
Features
- Recursive Markdown discovery with configurable directory exclusions
- YAML frontmatter extraction with structured
ok/illegalstatus reporting - Optional SHA-256 file hashes and file stats (size, mtime, atime)
- Parallel scan workers for faster processing on larger vaults/projects
Quick Start
pip install matterify
matterify ./docs -o output.json
Installation
# Using uv (recommended)
uv add matterify
# Or with pip
pip install matterify
CLI Usage
matterify DIRECTORY [OPTIONS]
DIRECTORY must exist and is scanned recursively for .md and .markdown files.
Options:
--version- Show version information and exit--debug- Enable debug logging-o, --output PATH- Write JSON to file instead of stdout (if omitted, outputs to stdout)--n-procs INT- Worker process count (default: auto-detect CPU cores)-v, --verbose- Show progress and summary-e, --exclude TEXT- Glob patterns to exclude (e.g.,**/.git,**/__pycache__)-i, --include PATH- Additional file paths to include in scan (repeatable)--hash / --no-hash- Enable/disable SHA-256 hash computation--stats / --no-stats- Enable/disable file statistics (size, modified time, access time)--frontmatter / --no-frontmatter- Enable/disable YAML frontmatter extraction--help- Show command help and exit
When --no-frontmatter is used, metadata fields files_with_frontmatter and
files_without_frontmatter are null.
Examples:
# Output to stdout (JSON)
matterify ./docs
# Output to file
matterify ./docs -o output.json
# Verbose output
matterify ./docs --verbose
# Disable hashes and file stats
matterify ./docs --no-hash --no-stats
# Hash + stats only (skip YAML parsing)
matterify ./docs --no-frontmatter
# Exclude directories using glob patterns
matterify ./docs -e '**/build' -e '**/.cache'
# Include additional files (any extension)
matterify ./docs -i notes.txt -i ../shared/changelog.txt
# Full help
matterify --help
Python API
Public Functions
from pathlib import Path
from matterify import (
scan_directory,
)
scan_directory
Scan directory and aggregate frontmatter using parallel workers. Returns a
ScanResults dataclass.
from pathlib import Path
from matterify import scan_directory
result = scan_directory(Path("./docs"))
# ScanResults contains:
# - result.metadata: ScanMetadata with scan statistics
# - result.files: list of file entries with extraction results
# Access metadata
print(result.metadata.total_files)
print(result.metadata.files_with_frontmatter)
print(result.metadata.scan_duration_seconds)
# Access files
for entry in result.files:
print(entry.file_path, entry.status)
print(entry.stats.file_size if entry.stats else None)
Custom data callback
You can pass a callback function to inject custom data into each file entry. The callback
receives the raw file content as a string and should return any value or None. The result
is stored in the custom_data field of each FileEntry.
from pathlib import Path
from matterify import scan_directory
def count_words(content: str) -> object:
return {"word_count": len(content.split())}
result = scan_directory(Path("./docs"), callback=count_words)
for entry in result.files:
if entry.custom_data:
print(entry.file_path, entry.custom_data["word_count"])
Important: The callback must be a module-level function (picklable for multiprocessing), not a lambda or closure.
Public Types
from matterify import (
FileEntry,
ScanMetadata,
ScanResults,
)
# FileEntry: extracted frontmatter from a single file
entry: FileEntry
# ScanMetadata: summary statistics about a scan
metadata: ScanMetadata
# ScanResults: holds metadata and file entries
result: ScanResults
JSON Output Structure
When using CLI (stdout or --output), the payload has this shape:
{
"metadata": {
"root": "/path/to/docs",
"total_files": 10,
"files_with_frontmatter": 8,
"files_without_frontmatter": 2,
"errors": 0,
"scan_duration_seconds": 0.523,
"avg_duration_per_file_ms": 52.3,
"throughput_files_per_second": 19.1
},
"files": [
{
"file_path": "getting-started.md",
"frontmatter": {
"title": "Getting Started",
"date": "2024-01-15",
"tags": ["guide", "tutorial"]
},
"status": "ok",
"error": null,
"stats": {
"file_size": 1234,
"modified_time": "2024-01-15T10:30:00",
"access_time": "2024-01-15T10:30:00"
},
"file_hash": "abc123..."
}
]
}
status is either "ok" or "illegal".
Default Exclusions
The following glob patterns are excluded from scanning by default:
**/.git- Git repositories**/.obsidian- Obsidian vault settings**/__pycache__- Python bytecode cache**/.venv- Python virtual environments**/venv- Python virtual environments**/node_modules- Node.js dependencies**/.mypy_cache- MyPy type checker cache**/.pytest_cache- Pytest cache**/.ruff_cache- Ruff linter cache
The **/ prefix matches directories at any depth. Use -e or --exclude to add custom exclusion patterns.
Development
# Install with dev dependencies
uv sync --all-extras
# Run tests
uv run pytest
# Format and lint
uv run ruff format src/ tests/
uv run ruff check src/ tests/
# Type check
uv run mypy src/
License
MIT License - see LICENSE file for details.
Release files for matterify 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| matterify-0.8.0.tar.gz | 62.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| matterify-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.4 kB
Release files / matterify-0.8.0.tar.gz
| Download URL | matterify-0.8.0.tar.gz |
|---|---|
| Size | 62.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7299cd2b6b483b2c44a85518515150a44b76764b78fd2c6d848869357b041b5d
|
|
BLAKE2b-256 checksum How to use checksums |
451b04de96ec67dba16d1d318baf0047a810ea3ba5d1a429da815f867a4fa34c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 13, 2026.
Transparency logRelease files / matterify-0.8.0-py3-none-any.whl
| Download URL | matterify-0.8.0-py3-none-any.whl |
|---|---|
| Size | 15.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c0ef70f4cdc55b137a7aa7ac29e7b6cd25362df873efbca6ad1c1e81aa07682f
|
|
BLAKE2b-256 checksum How to use checksums |
59a38d5a711dd64d2754059c5ef89558a2ac33a26f56e6eb42f36d8dc7988be9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 13, 2026.
Transparency log