Skip to content

Commit 0ecf635

Browse files
authored
Merge pull request #22 from pythonnz/danny-revamp
Revamp - Support custom locations for files/directories (with example document) - Lots of refactoring to cleaner class structure (major simplification from new PDFBakerConfiguration class) - Proper handling of overrides on main, document and page level - Added debug logging and made logging prettier with sections/subsections/errors emphasised - Added "trace" logging with full details (image data, templates etc. truncated for readability) - Add VSCode launch configs and tasks, esp. for tests and test coverage reports - Add full test suite (49 tests, 91% coverage), also test builds all examples - Improved error handling (examples and testing uncovered a few more that could be encountered) - Improved documentation, added diagrams Documentation still needs to be reviewed and adjusted for the new custom locations handling.
2 parents f595dd0 + 1d8f65a commit 0ecf635

47 files changed

Lines changed: 2077 additions & 754 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ wheels/
1313
# venv
1414
/.venv
1515

16-
# PyCharm
17-
.idea/
18-
1916
# Backup files
2017
**.~
2118
*.swp

.vscode/launch.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug Examples",
6+
"type": "debugpy",
7+
"request": "launch",
8+
"module": "pdfbaker",
9+
"args": ["bake", "examples/examples.yaml"],
10+
"console": "integratedTerminal"
11+
},
12+
{
13+
"name": "Debug Tests",
14+
"type": "debugpy",
15+
"request": "launch",
16+
"module": "pytest",
17+
"args": ["-v", "tests"],
18+
"justMyCode": false,
19+
"env": {"PYTEST_ADDOPTS": "--no-cov"},
20+
"console": "integratedTerminal"
21+
}
22+
]
23+
}

.vscode/tasks.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "Bake",
6+
"type": "shell",
7+
"command": "python -m pdfbaker bake ${input:configPath}",
8+
"group": "build",
9+
"presentation": {
10+
"reveal": "always",
11+
"panel": "new"
12+
}
13+
},
14+
{
15+
"label": "Run Tests",
16+
"type": "shell",
17+
"command": "pytest -v tests",
18+
"group": "test",
19+
"presentation": {
20+
"reveal": "always",
21+
"panel": "new"
22+
}
23+
},
24+
{
25+
"label": "Run Coverage",
26+
"type": "shell",
27+
"command": "pytest --cov=pdfbaker --cov-report=html",
28+
"group": "test",
29+
"presentation": {
30+
"reveal": "always",
31+
"panel": "new"
32+
}
33+
},
34+
{
35+
"label": "View Coverage",
36+
"type": "shell",
37+
"command": "python -c \"import webbrowser; webbrowser.open('htmlcov/index.html')\"",
38+
"presentation": {
39+
"reveal": "never",
40+
"panel": "new"
41+
}
42+
}
43+
],
44+
"inputs": [
45+
{
46+
"id": "configPath",
47+
"type": "promptString",
48+
"description": "Path to main YAML config file",
49+
"default": "examples/examples.yaml"
50+
}
51+
]
52+
}

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ pipx ensurepath
3636
sudo apt install ghostscript
3737
```
3838

39-
- If you want to embed particular fonts, they need to be installed. For example for
40-
[Roboto fonts](https://fonts.google.com/specimen/Roboto):
39+
- If you your templates embed particular fonts, they need to be installed. For example
40+
for [Roboto fonts](https://fonts.google.com/specimen/Roboto):
4141
```bash
4242
sudo apt install fonts-roboto
4343
```
@@ -55,14 +55,22 @@ pdfbaker bake <config_file>
5555

5656
This will produce your PDF files in a `dist/` directory where your configuration file
5757
lives. It will also create a `build/` directory with intermediate files, which is only
58-
kept if you specify `--debug`.
58+
kept if you specify `--keep-build-files` (or `--debug`).
5959

6060
## Examples
6161

62-
For working examples, see the [examples](examples) directory.<br> Create all PDFs with:
62+
For working examples, see the [examples](examples) directory:
63+
64+
- [minimal](examples/minimal) - Basic usage
65+
- [regular](examples/regular) - Standard features
66+
- [variants](examples/variants) - Document variants
67+
- [custom_locations](examples/custom_locations) - Custom file/directory locations
68+
- [custom_processing](examples/custom_processing) - Custom processing with Python
69+
70+
Create all PDFs with:
6371

6472
```bash
65-
pdfbaker bake examples/examples.yml
73+
pdfbaker bake examples/examples.yaml
6674
```
6775

6876
## Documentation
@@ -76,6 +84,8 @@ pdfbaker bake examples/examples.yml
7684

7785
## Development
7886

87+
All source code is [on GitHub](https://github.com/pythonnz/pdfbaker).
88+
7989
This project uses [uv](https://github.com/astral-sh/uv) for dependency management. The
8090
`uv.lock` file ensures reproducible builds.
8191

docs/configuration.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,46 @@
44

55
```
66
project/
7-
├── kiwipycon.yml # Main configuration
7+
├── kiwipycon.yaml # Main configuration
88
├── material_specs/ # A document
9-
│ ├── config.yml # Document configuration
9+
│ ├── config.yaml # Document configuration
1010
│ ├── images/ # Images
1111
│ ├── pages/ # Page configurations
1212
│ └── templates/ # SVG templates
1313
└── prospectus/ # Another document
14-
├── config.yml
14+
├── config.yaml
1515
├── images/
1616
├── pages/
1717
└── templates/
1818
```
1919

20+
## Configuration Workflow
21+
22+
For every page, your main configuration (for all documents), document configuration (for
23+
all pages of this document) and the page configuration are merged to form the context
24+
provided to your page template.
25+
26+
```mermaid
27+
flowchart TD
28+
subgraph Configuration
29+
Main[YAML Main Config] -->|inherits| Doc[YAML Document Config]
30+
Doc -->|inherits| Page[YAML Page Config]
31+
end
32+
33+
subgraph Page Processing
34+
Template[SVG Template]
35+
Page -->|context| Render[Template Rendering]
36+
Template -->|jinja2| Render
37+
Render -->|output| SVG[SVG File]
38+
SVG -->|cairosvg| PDF[PDF File]
39+
end
40+
```
41+
2042
## Main Configuration File
2143

2244
| Option | Type | Default | Description |
2345
| ----------------- | ------- | ------------ | ------------------------------------------------------------------------------------------------------------------ |
24-
| `documents` | array | Yes | List of document directories. Each directory must contain a `config.yml` file |
46+
| `documents` | array | Yes | List of document directories. Each directory must contain a `config.yaml` file |
2547
| `style` | object | No | Global style definitions that may reference `theme` values |
2648
| `theme` | object | No | Reusable values (colors, fonts, spacing, etc.) used by `style` |
2749
| `compress_pdf` | boolean | `false` | Whether to compress the final PDF. Requires Ghostscript. |
@@ -33,7 +55,7 @@ highlighted in the color specified by the `highlight_color` in your `style`.
3355
Example:
3456

3557
```yaml
36-
# kiwipycon.yml
58+
# kiwipycon.yaml
3759

3860
documents:
3961
- prospectus
@@ -74,13 +96,13 @@ for each document.
7496
| Option | Type | Required | Description |
7597
| ---------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
7698
| `filename` | string | Yes | Filename (without extension) of the final PDF document. Can use variables, particularly `variant` (see [Variants](variants.md)) |
77-
| `pages` | array | Yes | List of page names. Each page must have a corresponding `.yml` file in the `pages/` directory |
99+
| `pages` | array | Yes | List of page names. Each page must have a corresponding `.yaml` file in the `pages/` directory |
78100
| `variants` | array | No | List of document variants (see [Variants](variants.md)) |
79101

80102
Example:
81103

82104
```yaml
83-
# prospectus/config.yml
105+
# prospectus/config.yaml
84106
85107
filename: "Kiwi PyCon {{ conference.year }} - Prospectus" # Use config values in config values!
86108
title: "Sponsorship Prospectus"
@@ -105,7 +127,7 @@ merged with this for access to all settings in your template.
105127
Example:
106128

107129
```yaml
108-
# pages/conference_schedule.yml
130+
# pages/conference_schedule.yaml
109131
110132
template: list_section.svg.j2
111133

docs/custom_processing.md

Lines changed: 8 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -10,86 +10,22 @@ customize the document generation process. This allows you to:
1010

1111
## Basic Structure
1212

13-
Your `bake.py` should define a `process_document` function:
13+
As a naming convention, your `bake.py` needs to define a `process_document` function:
1414

1515
```python
1616
from pdfbaker.document import PDFBakerDocument
1717

1818
def process_document(document: PDFBakerDocument) -> None:
1919
# Custom processing logic here
20-
pass
21-
```
22-
23-
## Document Object
24-
25-
The `document` parameter provides access to:
26-
27-
- Document configuration
28-
- Variant processing
29-
- Page rendering
30-
- File management
31-
32-
### Key Methods and Properties
33-
34-
```python
35-
# Access configuration
36-
config = document.config
37-
38-
# Process variants
39-
for variant in document.config.get('variants', []):
40-
# Process variant...
41-
42-
# Process pages
43-
for page in document.config.get('pages', []):
44-
# Process page...
45-
46-
# File management
47-
build_dir = document.build_dir
48-
dist_dir = document.dist_dir
49-
```
50-
51-
## Example: Dynamic Pricing
52-
53-
Here's an example that calculates dynamic pricing based on features:
54-
55-
```python
56-
def process_document(document):
57-
# Load pricing data
58-
with open('content/pricing_data.yaml') as f:
59-
pricing_data = yaml.safe_load(f)
60-
61-
# Calculate pricing for each variant
62-
for variant in document.config.get('variants', []):
63-
base_price = document.config['content']['base_price']
64-
features = len(variant['content']['features'])
65-
66-
# Adjust price based on features
67-
adjusted_price = base_price * (1 + (features - 1) * 0.1)
68-
final_price = adjusted_price * (1 - variant['content']['discount'])
69-
70-
# Update variant content
71-
variant['content']['final_price'] = round(final_price, 2)
72-
73-
# Process as usual
7420
document.process()
7521
```
7622

77-
## Example: Content Generation
78-
79-
Generate content dynamically based on external data:
80-
81-
```python
82-
def process_document(document):
83-
# Fetch data from API
84-
response = requests.get('https://api.example.com/data')
85-
data = response.json()
23+
You will usually just manipulate the data for your templates, and then call `.process()`
24+
on the document to continue with the built-in stages of combining pages and compressing
25+
the PDF as configured.
8626

87-
# Update document content
88-
document.config['content'].update({
89-
'latest_data': data,
90-
'generated_at': datetime.now().isoformat()
91-
})
27+
See `examples/custom_processing/bake.py` for a simple example of how to do this.
9228

93-
# Process as usual
94-
document.process()
95-
```
29+
If you need to fully customise the processing, make sure that your function returns a
30+
Path or list of Path objects (the PDF files that were created) as that is the expected
31+
type of return value for logging.

0 commit comments

Comments
 (0)