|
6 | 6 | bake() delegates to its documents and reports back the end result. |
7 | 7 | """ |
8 | 8 |
|
| 9 | +import shutil |
9 | 10 | from pathlib import Path |
10 | 11 |
|
11 | 12 | from pydantic import BaseModel, ValidationError |
| 13 | +from ruamel.yaml import YAML |
12 | 14 |
|
13 | 15 | from .config import PathSpec |
14 | 16 | from .config.baker import BakerConfig |
15 | 17 | from .document import Document |
16 | | -from .errors import DocumentNotFoundError |
| 18 | +from .errors import DocumentNotFoundError, DryRunCreateFromCompleted |
17 | 19 | from .logging import LoggingMixin, setup_logging |
18 | 20 |
|
19 | 21 | __all__ = ["Baker", "BakerOptions"] |
@@ -53,8 +55,17 @@ def __init__( |
53 | 55 | """Set up logging and load configuration.""" |
54 | 56 | options = options or BakerOptions() |
55 | 57 | setup_logging(quiet=options.quiet, trace=options.trace, verbose=options.verbose) |
56 | | - self.create_from = options.create_from |
57 | | - # FIXME: use create_from to create a new config file |
| 58 | + |
| 59 | + if options.create_from: |
| 60 | + self.create_from( |
| 61 | + svg_path=options.create_from, |
| 62 | + config_path=config_file, |
| 63 | + dry_run=options.dry_run, |
| 64 | + ) |
| 65 | + if options.dry_run: |
| 66 | + # Dry run creations don't continue with dry run processing |
| 67 | + raise DryRunCreateFromCompleted() |
| 68 | + |
58 | 69 | self.log_debug_section("Loading main configuration: %s", config_file) |
59 | 70 | self.config = BakerConfig( |
60 | 71 | config_file=config_file, |
@@ -186,3 +197,102 @@ def teardown(self) -> None: |
186 | 197 | self.log_warning("Top-level build directory not empty - not removing") |
187 | 198 | else: |
188 | 199 | self.log_debug("Top-level build directory does not exist") |
| 200 | + |
| 201 | + def create_from( |
| 202 | + self, svg_path: Path, config_path: Path, dry_run: bool = False |
| 203 | + ) -> None: |
| 204 | + """Create a minimal project structure from an SVG and config path.""" |
| 205 | + project_dir = config_path.parent |
| 206 | + doc_name = svg_path.stem |
| 207 | + doc_dir = project_dir / doc_name |
| 208 | + template_file = doc_dir / "templates" / "main.svg.j2" |
| 209 | + page_file = doc_dir / "pages" / "main.yaml" |
| 210 | + doc_config_file = doc_dir / "config.yaml" |
| 211 | + files_to_create = [config_path, doc_config_file, page_file, template_file] |
| 212 | + dirs_to_create = [ |
| 213 | + d |
| 214 | + for d in [project_dir, doc_dir, doc_dir / "pages", doc_dir / "templates"] |
| 215 | + if not d.exists() |
| 216 | + ] |
| 217 | + |
| 218 | + for f in files_to_create: |
| 219 | + if f.exists(): |
| 220 | + raise FileExistsError(f"File already exists: {f}") |
| 221 | + |
| 222 | + if dry_run: |
| 223 | + for d in dirs_to_create: |
| 224 | + self.log_info("👀 [DRY RUN] Would create directory: %s", d) |
| 225 | + for f in files_to_create: |
| 226 | + self.log_info("👀 [DRY RUN] Would create file: %s", f) |
| 227 | + self.log_info("👀 [DRY RUN] No files created.") |
| 228 | + raise DryRunCreateFromCompleted() |
| 229 | + |
| 230 | + for d in dirs_to_create: |
| 231 | + d.mkdir(parents=True, exist_ok=True) |
| 232 | + |
| 233 | + yaml = YAML() |
| 234 | + yaml.indent(mapping=2, sequence=4, offset=2) |
| 235 | + |
| 236 | + with open(config_path, "w", encoding="utf-8") as f: |
| 237 | + f.write("# PDFBaker main config\n\n") |
| 238 | + yaml.dump({"documents": [doc_name]}, f) |
| 239 | + f.write( |
| 240 | + "\n" |
| 241 | + "# directories: # Override default directories below\n" |
| 242 | + "# dist: dist # Final PDF files are written here\n" |
| 243 | + "# documents: . # Location of document configurations\n" |
| 244 | + "# images: images # Location of image files\n" |
| 245 | + "# pages: pages # Location of page configurations\n" |
| 246 | + "# templates: templates # Location of SVG template files\n" |
| 247 | + "# jinja2_extensions: []" |
| 248 | + " # Jinja2 extensions to load and use in templates\n" |
| 249 | + "# template_renderers: # List of automatically applied renderers\n" |
| 250 | + "# - render_highlight\n" |
| 251 | + "# template_filters: # List of filters made available to templates\n" |
| 252 | + "# - wordwrap\n" |
| 253 | + "# svg2pdf_backend: cairosvg" |
| 254 | + " # Backend to use for SVG to PDF conversion\n" |
| 255 | + "# compress_pdf: false # Whether to compress the final PDF\n" |
| 256 | + "# keep_build: false" |
| 257 | + " # Whether to keep the build directory and its intermediary files\n" |
| 258 | + "\n" |
| 259 | + "# Example custom variables for all pages of all documents:\n" |
| 260 | + "# style:\n" |
| 261 | + "# font: Arial\n" |
| 262 | + "# color: black\n" |
| 263 | + ) |
| 264 | + self.log_info("Created main config: %s", config_path) |
| 265 | + |
| 266 | + with open(doc_config_file, "w", encoding="utf-8") as f: |
| 267 | + f.write("# Document config\n\n") |
| 268 | + yaml.dump({"filename": doc_name, "pages": ["main"]}, f) |
| 269 | + f.write( |
| 270 | + "\n" |
| 271 | + "# compress_pdf: false" |
| 272 | + " # Whether to compress the final PDF for this document\n" |
| 273 | + "# custom_bake: bake.py" |
| 274 | + " # Python file used for custom processing (if found)\n" |
| 275 | + "# variants: # List of document variants\n" |
| 276 | + "\n" |
| 277 | + "# Example custom variables for all pages of this document:\n" |
| 278 | + "# style:\n" |
| 279 | + "# font: Arial\n" |
| 280 | + "# color: black\n" |
| 281 | + ) |
| 282 | + self.log_info("Created document config: %s", doc_config_file) |
| 283 | + |
| 284 | + with open(page_file, "w", encoding="utf-8") as f: |
| 285 | + f.write("# Page config\n\n") |
| 286 | + yaml.dump({"template": "main.svg.j2", "name": "main"}, f) |
| 287 | + f.write( |
| 288 | + "\n" |
| 289 | + "# images: # List of images to use in the page\n" |
| 290 | + "\n" |
| 291 | + "# Example custom variables for this page:\n" |
| 292 | + "# title: My Document\n" |
| 293 | + "# date: 2025-05-19\n" |
| 294 | + ) |
| 295 | + self.log_info("Created page: %s", page_file) |
| 296 | + |
| 297 | + shutil.copy(svg_path, template_file) |
| 298 | + self.log_info("Created template: %s", template_file) |
0 commit comments