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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
__pycache__
*pyc
tests/output/
uv.lock
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
112 changes: 56 additions & 56 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
## [1.5.0] - 2026-04-15

### Added

- 2nd and `p` matrix norms

### Removed

- `autopep8` from `pre-commit` config

## [1.4.0] - 2026-03-31

### Added

- LU decomposition

## [1.3.0] - 2026-03-20

### Added

- Matrix multiplication report

### Fixed

- `md` report generation - images and code blocks

## [1.2.0] - 2026-03-19

### Added

- Strassen matrix multiplication algorithm

## [1.1.0] - 2026-03-18

### Added

- automated report generation

## [1.0.0] - 2026-03-18

### Added

- standard matrix multiplication algorithm
- CI

## Fixed

- python version in `pre-commit` config

## [0.0.1] - 2026-03-18

### Added

- [pre-commit](https://pre-commit.com/)
- setup script
- changelog
## [1.5.0] - 2026-04-15
### Added
- 2nd and `p` matrix norms
### Removed
- `autopep8` from `pre-commit` config
## [1.4.0] - 2026-03-31
### Added
- LU decomposition
## [1.3.0] - 2026-03-20
### Added
- Matrix multiplication report
### Fixed
- `md` report generation - images and code blocks
## [1.2.0] - 2026-03-19
### Added
- Strassen matrix multiplication algorithm
## [1.1.0] - 2026-03-18
### Added
- automated report generation
## [1.0.0] - 2026-03-18
### Added
- standard matrix multiplication algorithm
- CI
## Fixed
- python version in `pre-commit` config
## [0.0.1] - 2026-03-18
### Added
- [pre-commit](https://pre-commit.com/)
- setup script
- changelog
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ To export notebooks to PDF files use:
./convert_notebooks.py --notebooks_dir ./notebooks
```

If you want to force rerender of all notebooks use `--force` flag.

If you work on WSL, you must have installed chromium and it's dependencies on your subsystem.
You can install them using python `playwright' package:

```bash
source ./.venv/bin/activate && playwright install chromium && playwright install-deps chromium
```

## Implemented Algorithms

### Matrix multiplication
Expand Down
46 changes: 30 additions & 16 deletions convert_notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,51 @@
from pathlib import Path

import nbformat
from nbconvert import HTMLExporter
from weasyprint import HTML
from nbconvert import WebPDFExporter


def ipynb_to_pdf(ipynb_path: Path, pdf_path: Path):
try:
with open(ipynb_path, encoding="utf-8") as f:
nb = nbformat.read(f, as_version=4)

html_exporter = HTMLExporter()
html_exporter.exclude_input_prompt = True
html_exporter.exclude_output_prompt = True
body, _ = html_exporter.from_notebook_node(nb)
pdf_exporter = WebPDFExporter()
pdf_exporter.exclude_input_prompt = True
pdf_exporter.exclude_output_prompt = True

HTML(string=body, base_url=str(ipynb_path.parent)).write_pdf(str(pdf_path))
print(f"Converted: {ipynb_path.name} -> {pdf_path.name}")
pdf_data, _ = pdf_exporter.from_notebook_node(nb)

with open(pdf_path, "wb") as f:
f.write(pdf_data)

print(f"✅ Converted: {ipynb_path.name} -> {pdf_path.name}")
except Exception as e:
print(f"Failed to convert {ipynb_path.name} to pdf: {e}")
print(f"Failed to convert {ipynb_path.name} to pdf: {e}")


def sync_notebooks_to_pdf(directory_path: str, output_path: str):
def sync_notebooks_to_pdf(directory_path: str, output_path: str, force: bool = False):
base_path = Path(directory_path)

if output_path:
out_base = Path(output_path)
else:
out_base = base_path / "resources"

out_base.mkdir(parents=True, exist_ok=True)

notebooks = list(base_path.glob("*.ipynb"))

if not notebooks:
print(f"No notebooks found in: {directory_path}")
print(f"⚠️ No notebooks found in: {directory_path}")
return

for ipynb_path in notebooks:
pdf_path = out_base / ipynb_path.with_suffix(".pdf").name

should_convert = False

if not pdf_path.exists():
if force:
should_convert = True
elif not pdf_path.exists():
should_convert = True
else:
ipynb_mtime = ipynb_path.stat().st_mtime
Expand All @@ -56,11 +61,13 @@ def sync_notebooks_to_pdf(directory_path: str, output_path: str):
if should_convert:
ipynb_to_pdf(ipynb_path, pdf_path)
else:
print(f"Skipped: {ipynb_path.name} (PDF is up to date)")
print(f"ℹ️ Skipped: {ipynb_path.name} (PDF is up to date)")


def main():
parser = argparse.ArgumentParser(description="Sync jupyter notebooks to PDF")
parser = argparse.ArgumentParser(
description="Sync jupyter notebooks to PDF with math support",
)

parser.add_argument(
"--notebooks_dir",
Expand All @@ -77,9 +84,16 @@ def main():
help="Output directory for PDF files",
)

parser.add_argument(
"--force",
"-f",
action="store_true",
help="Force re-export of all notebooks even if they haven't changed",
)

args = parser.parse_args()

sync_notebooks_to_pdf(args.notebooks_dir, args.output_dir)
sync_notebooks_to_pdf(args.notebooks_dir, args.output_dir, args.force)


if __name__ == "__main__":
Expand Down
Loading
Loading