66bake() delegates to its documents and reports back the end result.
77"""
88
9+ import logging
910import shutil
1011from pathlib import Path
12+ from typing import NamedTuple
1113
1214from pydantic import BaseModel , ValidationError
1315from ruamel .yaml import YAML
1416
1517from .config import PathSpec
1618from .config .baker import BakerConfig
19+ from .console import build_create_from_panel , build_outcome_panel , stdout_console
1720from .document import Document
1821from .errors import DocumentNotFoundError , DryRunCreateFromCompleted
1922from .logging import LoggingMixin , setup_logging
2023
21- __all__ = ["Baker" , "BakerOptions" ]
24+ __all__ = ["Baker" , "BakerOptions" , "ProcessedDoc" ]
25+
26+
27+ class ProcessedDoc (NamedTuple ):
28+ """The outcome of processing a document, for reporting back to the user."""
29+
30+ document : Document
31+ pdf_files : list [Path ] | None
32+ error_message : str | None
2233
2334
2435class BakerOptions (BaseModel ):
@@ -57,7 +68,7 @@ def __init__(
5768 setup_logging (quiet = options .quiet , trace = options .trace , verbose = options .verbose )
5869
5970 if options .create_from :
60- self .create_from (
71+ project_dir = self .create_from (
6172 svg_path = options .create_from ,
6273 config_path = config_file ,
6374 dry_run = options .dry_run ,
@@ -66,6 +77,10 @@ def __init__(
6677 # Dry run creations don't continue with dry run processing
6778 raise DryRunCreateFromCompleted ()
6879
80+ if self .logger .getEffectiveLevel () <= logging .INFO :
81+ panel = build_create_from_panel (options .create_from , project_dir )
82+ stdout_console .print (panel )
83+
6984 self .log_debug_section ("Loading main configuration: %s" , config_file )
7085 self .config = BakerConfig (
7186 config_file = config_file ,
@@ -74,53 +89,32 @@ def __init__(
7489 dry_run = options .dry_run ,
7590 ** kwargs ,
7691 )
77- self .log_trace (self .config .readable ())
92+ self .log_trace_preview (self .config .readable (), syntax = "yaml" )
7893 self .log_debug ("Build directory: %s" , self .config .directories .build )
7994
8095 def bake (self , document_names : tuple [str , ...] | None = None ) -> None :
8196 """Bake the documents."""
8297 docs = self ._get_selected_documents (document_names )
98+
99+ if self .config .dry_run :
100+ self .log_info ("[DRY RUN] No files will be created." )
83101 self .log_debug_subsection ("Documents to process:" )
84102 self .log_debug (docs )
85103
86- pdfs_created , failed_docs = self ._process_documents (docs )
87-
88- self .log_info ("─" * 80 )
89- if pdfs_created :
90- if self .config .dry_run :
91- self .log_info ("👀 [DRY RUN] Would have created PDFs:" )
92- else :
93- self .log_info ("Successfully created PDFs:" )
94- for pdf in pdfs_created :
95- self .log_info (" %s %s" , "🟨" if self .config .dry_run else "✅" , pdf )
96- else :
97- self .log_warning ("No PDFs were created." )
104+ processed_docs = self ._process_documents (docs )
105+ if not self .config .keep_build :
106+ self .teardown ()
98107
99- if failed_docs :
100- self .log_warning (
101- "Failed to process %d document%s:" ,
102- len (failed_docs ),
103- "" if len (failed_docs ) == 1 else "s" ,
108+ if self .logger .getEffectiveLevel () <= logging .INFO :
109+ outcome_panel = build_outcome_panel (
110+ processed_docs ,
111+ dry_run = self .config .dry_run ,
112+ keep_build = self .config .keep_build ,
113+ build_dir = self .config .directories .build ,
104114 )
105- for failed_doc , error_message in failed_docs :
106- name = failed_doc .config .name
107- if isinstance (failed_doc , Document ) and failed_doc .config .is_variant :
108- name += f' variant "{ failed_doc .config .variant ["name" ]} "'
109- self .log_error (" %s: %s" , name , error_message )
110- if hasattr (failed_doc , "config" ):
111- self .log_debug (
112- 'Build directory for "%s": %s' ,
113- name ,
114- failed_doc .config .directories .build ,
115- )
116-
117- if self .config .keep_build :
118- if not self .config .dry_run :
119- self .log_info ("Build files kept in: %s" , self .config .directories .build )
120- else :
121- self .teardown ()
115+ stdout_console .print (outcome_panel )
122116
123- return not failed_docs
117+ return all ( not d . error_message for d in processed_docs )
124118
125119 def _get_selected_documents (
126120 self , selected_names : tuple [str , ...] | None = None
@@ -144,12 +138,8 @@ def _get_selected_documents(
144138
145139 return [doc for doc in self .config .documents if doc .name in selected_names ]
146140
147- def _process_documents (
148- self , docs : list [PathSpec ]
149- ) -> tuple [list [Path ], list [tuple [PathSpec , str ]]]:
150- pdfs_created : list [Path ] = []
151- failed_docs : list [tuple [PathSpec , str ]] = []
152-
141+ def _process_documents (self , docs : list [PathSpec ]) -> list [ProcessedDoc ]:
142+ processed_docs : list [ProcessedDoc ] = []
153143 for config_path in docs :
154144 try :
155145 document = Document (
@@ -158,7 +148,7 @@ def _process_documents(
158148 except ValidationError as e :
159149 error_message = f'Invalid config for document "{ config_path .name } ": { e } '
160150 self .log_error (error_message )
161- failed_docs .append ((config_path , error_message ))
151+ processed_docs .append (ProcessedDoc (config_path , None , error_message ))
162152 continue
163153
164154 pdf_files , error_message = document .process_document ()
@@ -169,15 +159,14 @@ def _process_documents(
169159 document .config .name ,
170160 error_message ,
171161 )
172- failed_docs .append ((document , error_message ))
162+ processed_docs .append (ProcessedDoc (document , None , error_message ))
173163 else :
174164 if isinstance (pdf_files , Path ):
175165 pdf_files = [pdf_files ]
176- pdfs_created . extend ( pdf_files )
166+ processed_docs . append ( ProcessedDoc ( document , pdf_files , None ) )
177167 if not self .config .keep_build :
178168 document .teardown ()
179-
180- return pdfs_created , failed_docs
169+ return processed_docs
181170
182171 def teardown (self ) -> None :
183172 """Clean up (top-level) build directory after processing."""
@@ -190,7 +179,8 @@ def teardown(self) -> None:
190179 self .log_debug ("Removing top-level build directory..." )
191180 if self .config .dry_run :
192181 self .log_debug (
193- "👀 [DRY RUN] Not removing top-level build directory"
182+ ":no_entry_sign:"
183+ " [DRY RUN] Not removing top-level build directory"
194184 )
195185 else :
196186 build_dir .rmdir ()
@@ -202,8 +192,11 @@ def teardown(self) -> None:
202192 def create_from (
203193 self , svg_path : Path , config_path : Path , dry_run : bool = False
204194 ) -> None :
205- """Create a minimal project structure from an SVG and config path."""
195+ """Create a scaffold for templating from an SVG and config path."""
206196 project_dir = config_path .parent
197+ self .log_debug_section (
198+ "Creating from %s in: %s" , svg_path .resolve (), project_dir .resolve ()
199+ )
207200 doc_name = svg_path .stem
208201 doc_dir = project_dir / doc_name
209202 template_file = doc_dir / "templates" / "main.svg.j2"
@@ -218,22 +211,24 @@ def create_from(
218211
219212 for f in files_to_create :
220213 if f .exists ():
221- raise FileExistsError (f"File already exists: { f } " )
214+ raise FileExistsError (f"File already exists: { f . resolve () } " )
222215
223216 if dry_run :
224217 for d in dirs_to_create :
225- self .log_info ("👀 [DRY RUN] Would create directory: %s" , d )
218+ self .log_info (":no_entry_sign: [DRY RUN] Would create directory: %s" , d )
226219 for f in files_to_create :
227- self .log_info ("👀 [DRY RUN] Would create file: %s" , f )
228- self .log_info ("👀 [DRY RUN] No files created." )
220+ self .log_info (":no_entry_sign: [DRY RUN] Would create file: %s" , f )
221+ self .log_info (":no_entry_sign: [DRY RUN] No files created." )
229222 raise DryRunCreateFromCompleted ()
230223
231224 for d in dirs_to_create :
225+ self .log_debug_subsection ("Ensuring directory exists: %s" , d .resolve ())
232226 d .mkdir (parents = True , exist_ok = True )
233227
234228 yaml = YAML ()
235229 yaml .indent (mapping = 2 , sequence = 4 , offset = 2 )
236230
231+ self .log_debug_subsection ("Writing main config: %s" , config_path .resolve ())
237232 with open (config_path , "w" , encoding = "utf-8" ) as f :
238233 f .write ("# PDFBaker main config\n \n " )
239234 yaml .dump ({"documents" : [doc_name ]}, f )
@@ -262,8 +257,15 @@ def create_from(
262257 "# font: Arial\n "
263258 "# color: black\n "
264259 )
260+ self .log_trace_preview (
261+ config_path .read_text (encoding = "utf-8" ),
262+ syntax = "yaml" ,
263+ )
265264 self .log_info ("Created main config: %s" , config_path )
266265
266+ self .log_debug_subsection (
267+ "Writing document config: %s" , doc_config_file .resolve ()
268+ )
267269 with open (doc_config_file , "w" , encoding = "utf-8" ) as f :
268270 f .write ("# Document config\n \n " )
269271 yaml .dump ({"filename" : doc_name , "pages" : ["main" ]}, f )
@@ -280,8 +282,13 @@ def create_from(
280282 "# font: Arial\n "
281283 "# color: black\n "
282284 )
285+ self .log_trace_preview (
286+ doc_config_file .read_text (encoding = "utf-8" ),
287+ syntax = "yaml" ,
288+ )
283289 self .log_info ("Created document config: %s" , doc_config_file )
284290
291+ self .log_debug_subsection ("Writing page config: %s" , page_file .resolve ())
285292 with open (page_file , "w" , encoding = "utf-8" ) as f :
286293 f .write ("# Page config\n \n " )
287294 yaml .dump ({"template" : "main.svg.j2" , "name" : "main" }, f )
@@ -293,7 +300,20 @@ def create_from(
293300 "# title: My Document\n "
294301 "# date: 2025-05-19\n "
295302 )
296- self .log_info ("Created page: %s" , page_file )
303+ self .log_trace_preview (
304+ page_file .read_text (encoding = "utf-8" ),
305+ syntax = "yaml" ,
306+ )
307+ self .log_info ("Created page config: %s" , page_file )
297308
309+ self .log_debug_subsection (
310+ "Copying SVG to template: %s" , template_file .resolve ()
311+ )
298312 shutil .copy (svg_path , template_file )
313+ self .log_trace_preview (
314+ template_file .read_text (encoding = "utf-8" ),
315+ syntax = "xml" ,
316+ )
299317 self .log_info ("Created template: %s" , template_file )
318+
319+ return project_dir
0 commit comments