@@ -299,7 +299,63 @@ def cleanup_sandbox(sandbox_dir: Path, bblocks: list[BuildingBlock]) -> None:
299299_GET_TRANSFORMER_TYPES = frozenset ({'python' , 'node' , 'jq' , 'xslt' , 'jsonld-frame' })
300300
301301
302- def _build_transforms_registry (bblocks_register : BuildingBlockRegister ) -> dict :
302+ def _bblock_context_dict (bblock_id : str , bblock , cwd : Path ) -> dict :
303+ """Return a JSON-serializable dict of the bblock-level TransformContext fields.
304+
305+ Accepts either a local BuildingBlock or an imported bblock raw dict. The returned
306+ dict can be spread into TransformContext(...) alongside the run-specific fields
307+ (example_index, example, snippet_index, snippet, output_file, output_dir, working_dir,
308+ base_url, github_base_url, git_repository, id_prefix, imported_register_urls,
309+ transform_plugins).
310+ """
311+ if isinstance (bblock , BuildingBlock ):
312+ bblock_metadata = json .loads (json .dumps (bblock .metadata , default = str ))
313+ source_schema_path = None
314+ if bblock .schema :
315+ source_schema_path = _rel (bblock .schema .path , cwd ) if bblock .schema .is_path else bblock .schema .url
316+ return {
317+ 'bblock_id' : bblock_id ,
318+ 'bblock_name' : bblock .name ,
319+ 'bblock_version' : bblock .version ,
320+ 'bblock_tags' : list (bblock .metadata .get ('tags' ) or []),
321+ 'bblock_files_path' : _rel (bblock .files_path , cwd ),
322+ 'bblock_annotated_path' : _rel (bblock .annotated_path , cwd ),
323+ 'bblock_metadata' : bblock_metadata ,
324+ 'source_schema_path' : source_schema_path ,
325+ 'annotated_schema_path' : _rel (bblock .annotated_schema , cwd ) if bblock .annotated_schema .is_file () else None ,
326+ 'jsonld_context_path' : _rel (bblock .jsonld_context , cwd ) if bblock .jsonld_context and bblock .jsonld_context .is_file () else None ,
327+ 'shacl_shapes_paths' : [s if isinstance (s , str ) else _rel (s , cwd ) for s in (bblock .shacl_shapes or [])],
328+ }
329+ else :
330+ # Imported bblock raw dict
331+ raw_copy = {k : v for k , v in bblock .items () if k != 'register' }
332+ bblock_metadata = json .loads (json .dumps (raw_copy , default = str ))
333+ schema = bblock .get ('schema' )
334+ schema_url = None
335+ if isinstance (schema , dict ):
336+ schema_url = schema .get ('application/json' ) or schema .get ('application/yaml' ) or next (iter (schema .values ()), None )
337+ elif isinstance (schema , str ):
338+ schema_url = schema
339+ shacl_shapes = bblock .get ('shaclShapes' ) or bblock .get ('shaclRules' ) or []
340+ if isinstance (shacl_shapes , dict ):
341+ shacl_shapes = [s for shapes in shacl_shapes .values ()
342+ for s in (shapes if isinstance (shapes , list ) else [shapes ])]
343+ return {
344+ 'bblock_id' : bblock_id ,
345+ 'bblock_name' : bblock .get ('name' , bblock_id ),
346+ 'bblock_version' : bblock .get ('version' ),
347+ 'bblock_tags' : list (bblock .get ('tags' ) or []),
348+ 'bblock_files_path' : None ,
349+ 'bblock_annotated_path' : None ,
350+ 'bblock_metadata' : bblock_metadata ,
351+ 'source_schema_path' : schema_url ,
352+ 'annotated_schema_path' : schema_url ,
353+ 'jsonld_context_path' : bblock .get ('ldContext' ),
354+ 'shacl_shapes_paths' : [s for s in shacl_shapes if isinstance (s , str )],
355+ }
356+
357+
358+ def _build_transforms_registry (bblocks_register : BuildingBlockRegister , cwd : Path ) -> dict :
303359 registry = {}
304360
305361 for bblock_id , bblock in bblocks_register .bblocks .items ():
@@ -312,11 +368,8 @@ def _build_transforms_registry(bblocks_register: BuildingBlockRegister) -> dict:
312368 }
313369 if not supported_transforms :
314370 continue
315- bblock_metadata = json .loads (json .dumps (bblock .metadata , default = str ))
316371 registry [bblock_id ] = {
317- 'name' : bblock .metadata .get ('name' , bblock_id ),
318- 'version' : bblock .metadata .get ('version' ),
319- 'bblock_metadata' : bblock_metadata ,
372+ 'context' : _bblock_context_dict (bblock_id , bblock , cwd ),
320373 'transforms' : supported_transforms ,
321374 }
322375
@@ -328,12 +381,8 @@ def _build_transforms_registry(bblocks_register: BuildingBlockRegister) -> dict:
328381 }
329382 if not supported_transforms :
330383 continue
331- raw_copy = {k : v for k , v in raw .items () if k != 'register' }
332- bblock_metadata = json .loads (json .dumps (raw_copy , default = str ))
333384 registry [bblock_id ] = {
334- 'name' : raw .get ('name' , bblock_id ),
335- 'version' : raw .get ('version' ),
336- 'bblock_metadata' : bblock_metadata ,
385+ 'context' : _bblock_context_dict (bblock_id , raw , cwd ),
337386 'transforms' : supported_transforms ,
338387 }
339388
@@ -360,7 +409,12 @@ def apply_transforms(bblock: BuildingBlock,
360409 shutil .rmtree (output_dir , ignore_errors = True )
361410 output_dir .mkdir (parents = True , exist_ok = True )
362411
363- transforms_registry = _build_transforms_registry (bblocks_register ) if bblocks_register else {}
412+ transforms_registry = _build_transforms_registry (bblocks_register , cwd ) if bblocks_register else {}
413+
414+ # Bblock-level context fields — constant for all examples/snippets of this bblock.
415+ # Prefer the registry entry (already computed) to avoid redundant Path → str conversions.
416+ bblock_ctx = (transforms_registry .get (bblock .identifier ) or {}).get ('context' ) \
417+ or _bblock_context_dict (bblock .identifier , bblock , cwd )
364418
365419 # Collects ValidationReportItems per profile across all snippets/transforms,
366420 # so we can write one consolidated _report.json per profile at the end.
@@ -453,29 +507,14 @@ def apply_transforms(bblock: BuildingBlock,
453507 metadata ['_prefixes' ] = example_prefixes
454508
455509 ctx = TransformContext (
456- bblock_id = bblock .identifier ,
457- bblock_name = bblock .name ,
458- bblock_version = bblock .version ,
459- bblock_tags = list (bblock .metadata .get ('tags' ) or []),
460- bblock_files_path = _rel (bblock .files_path , cwd ),
461- bblock_annotated_path = _rel (bblock .annotated_path , cwd ),
462- bblock_metadata = bblock .metadata ,
510+ ** bblock_ctx ,
463511 example_index = example_id ,
464512 example = {k : v for k , v in example .items () if k != 'snippets' },
465513 snippet_index = snippet_id ,
466514 snippet = {k : v for k , v in snippet .items () if k != 'code' },
467515 output_file = _rel (output_fn , cwd ),
468516 output_dir = _rel (output_dir , cwd ),
469517 working_dir = str (cwd ),
470- source_schema_path = (
471- _rel (bblock .schema .path , cwd ) if bblock .schema .is_path else bblock .schema .url
472- ) if bblock .schema else None ,
473- annotated_schema_path = _rel (bblock .annotated_schema , cwd ) if bblock .annotated_schema .is_file () else None ,
474- jsonld_context_path = _rel (bblock .jsonld_context , cwd ) if bblock .jsonld_context .is_file () else None ,
475- shacl_shapes_paths = [
476- s if isinstance (s , str ) else _rel (s , cwd )
477- for s in (bblock .shacl_shapes or [])
478- ],
479518 base_url = base_url ,
480519 github_base_url = github_base_url ,
481520 git_repository = git_repository ,
0 commit comments