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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ interface BFB_Format_Adapter {
public function slug(): string;
public function to_blocks( string $content, array $options = array() ): array;
public function from_blocks( array $blocks, array $options = array() ): string;
public function detect( string $content ): bool; // reserved for future use
}
```

BFB is a declared-format conversion API. Callers pass the source format explicitly to `bfb_convert()` or
`bfb_to_blocks()`; BFB does not silently guess between HTML, Blocks, Markdown, or custom adapters.

BFB includes two adapters:

- **`BFB_HTML_Adapter`** — `to_blocks()` delegates to `html_to_blocks_raw_handler()` from `html-to-blocks-converter`;
Expand Down Expand Up @@ -119,9 +121,9 @@ Rules:
- Missing or malformed marker values should fall back to the normal HTML conversion path rather than guessing.

The marker contract belongs in BFB because BFB is the public conversion substrate. The runtime HTML-element transforms
belong in h2bc because `BFB_HTML_Adapter::to_blocks()` delegates HTML → Blocks conversion to
`html_to_blocks_raw_handler()`. BFB will inherit marker support after h2bc implements those explicit raw transforms and
the bundled dependency is refreshed.
are currently supplied by h2bc through BFB's bundled dependency, and BFB verifies those markers through the public
`bfb_convert( $html, 'html', 'blocks' )` path. h2bc should treat these attributes as an explicit shared extension
contract with BFB, not as a license to infer Site Editor primitives from unmarked HTML.

## Install

Expand Down
5 changes: 3 additions & 2 deletions docs/block-theme-compiler-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ The deterministic targets are:
avoid implying a WordPress core contract that does not exist.

Implementation note: the marker contract is documented here because BFB owns the public conversion substrate. The actual
HTML-element transforms belong in html-to-blocks-converter, the library BFB delegates to for HTML → Blocks. BFB should not
add a parallel pre-parser around h2bc for these markers.
HTML-element transforms currently live in html-to-blocks-converter, the library BFB delegates to for HTML → Blocks. That
is a shared extension contract: h2bc may recognize these explicitly documented BFB markers, while BFB verifies marker
behavior through `bfb_convert( $html, 'html', 'blocks' )` instead of adding a parallel pre-parser around h2bc.

## Answers

Expand Down
5 changes: 4 additions & 1 deletion docs/block-theme-conversion-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ h2bc should:
- Generate a core-block inventory and classification map from WordPress/Gutenberg `block.json` metadata.
- Keep the generated coverage documentation in sync with that map.
- Implement raw transforms when source HTML carries enough signal to preserve the author's intent.
- Implement explicit BFB marker transforms for primitives such as pattern and template-part references.
- Implement explicit shared marker transforms for primitives such as pattern and template-part references when BFB
documents the public marker vocabulary.
- Fall back safely when markup is ambiguous.

h2bc should not:
Expand All @@ -74,6 +75,7 @@ h2bc should not:
Tracking:

- h2bc #56: https://github.com/chubes4/html-to-blocks-converter/issues/56
- h2bc #418: https://github.com/chubes4/html-to-blocks-converter/issues/418
- h2bc #55: https://github.com/chubes4/html-to-blocks-converter/issues/55

## BFB Responsibilities
Expand All @@ -91,6 +93,7 @@ BFB should:
- Expose ability operations for machine callers, including conversion and capability reporting.
- Keep WP-CLI ergonomics thin and script-friendly for humans and shell-based tools.
- Report what the active substrate supports so compiler consumers can plan fallbacks.
- Consume h2bc's public capability API when available instead of reflecting over h2bc internals.

BFB should not:

Expand Down
162 changes: 144 additions & 18 deletions includes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ function bfb_capabilities(): array {

$h2bc = bfb_h2bc_capabilities();

$block_coverage = isset( $h2bc['inventory']['block_coverage'] ) && is_array( $h2bc['inventory']['block_coverage'] )
? $h2bc['inventory']['block_coverage']
: array(
'source' => (string) ( $h2bc['inventory']['source'] ?? 'h2bc_capability_api_missing' ),
'requires' => (string) ( $h2bc['inventory']['requires'] ?? 'https://github.com/chubes4/html-to-blocks-converter/issues/418' ),
'supported_blocks' => array(),
'unsupported_blocks' => array(),
'classifications' => array(),
);

return array(
'bridge' => array(
'version' => defined( 'BFB_VERSION' ) ? BFB_VERSION : null,
Expand All @@ -77,13 +87,7 @@ function bfb_capabilities(): array {
),
),
'h2bc' => $h2bc,
'block_coverage' => array(
'source' => 'not_available',
'requires' => 'h2bc#56',
'supported_blocks' => array(),
'unsupported_blocks' => array(),
'classifications' => array(),
),
'block_coverage' => $block_coverage,
'hooks' => array(
'filters' => array(
'bfb_register_format_adapter',
Expand Down Expand Up @@ -214,19 +218,145 @@ function bfb_h2bc_capabilities(): array {
}
}

$capability_function = bfb_h2bc_capability_function();
$inventory = array(
'source' => null !== $capability_function ? 'h2bc_capabilities' : 'h2bc_capability_api_missing',
'requires' => null !== $capability_function ? null : 'https://github.com/chubes4/html-to-blocks-converter/issues/418',
);

if ( null !== $capability_function ) {
$capability_report = $capability_function();
if ( is_array( $capability_report ) ) {
$inventory = bfb_normalize_h2bc_inventory( $capability_report );
if ( isset( $inventory['version'] ) && is_string( $inventory['version'] ) && '' !== $inventory['version'] ) {
$version = $inventory['version'];
}
}
}

return array(
'available' => null !== $handler,
'version' => $version,
'path' => $path,
'raw_handler' => $handler,
'inventory' => array(
'source' => 'not_available',
'requires' => 'h2bc#56',
'available' => null !== $handler,
'version' => $version,
'path' => $path,
'raw_handler' => $handler,
'capability_api' => $capability_function,
'inventory' => $inventory,
);
}
}

if ( ! function_exists( 'bfb_h2bc_capability_function' ) ) {
/**
* Resolve h2bc's public capability function when the active substrate exposes one.
*
* @return callable-string|null Callable function name, or null when h2bc lacks the API.
*/
function bfb_h2bc_capability_function(): ?string {
$candidates = array(
'\BlockFormatBridge\Vendor\html_to_blocks_capabilities',
'html_to_blocks_capabilities',
);
$defined = get_defined_functions();
$functions = array_map( 'strtolower', $defined['user'] );

foreach ( $candidates as $candidate ) {
if ( in_array( strtolower( ltrim( $candidate, '\\' ) ), $functions, true ) ) {
/** @var callable-string $candidate */
return $candidate;
}
}

return null;
}
}

if ( ! function_exists( 'bfb_normalize_h2bc_inventory' ) ) {
/**
* Normalize h2bc-owned capability data into BFB's public capability shape.
*
* @param array<string, mixed> $report h2bc capability report.
* @return array<string, mixed>
*/
function bfb_normalize_h2bc_inventory( array $report ): array {
$block_coverage = isset( $report['block_coverage'] ) && is_array( $report['block_coverage'] ) ? $report['block_coverage'] : array();
$transforms = isset( $report['transforms'] ) && is_array( $report['transforms'] ) ? $report['transforms'] : array();

$supported_blocks = bfb_h2bc_report_list( $report, $block_coverage, 'supported_blocks' );
$unsupported_blocks = bfb_h2bc_report_list( $report, $block_coverage, 'unsupported_blocks' );
$classifications = bfb_h2bc_report_array( $report, $block_coverage, 'classifications' );
$families = bfb_h2bc_report_list( $report, $transforms, 'families' );

return array(
'source' => 'h2bc_capabilities',
'version' => isset( $report['version'] ) && is_scalar( $report['version'] ) ? (string) $report['version'] : null,
'handler' => isset( $report['handler'] ) && is_scalar( $report['handler'] ) ? (string) $report['handler'] : null,
'transform_families' => $families,
'block_coverage' => array(
'source' => 'h2bc_capabilities',
'supported_blocks' => $supported_blocks,
'unsupported_blocks' => $unsupported_blocks,
'classifications' => $classifications,
),
'raw' => $report,
);
}
}

if ( ! function_exists( 'bfb_h2bc_report_list' ) ) {
/**
* Return a normalized scalar list from possible report locations.
*
* @param array<string, mixed> $primary Primary report data.
* @param array<string, mixed> $secondary Secondary report data.
* @param string $key Field key.
* @return array<int, string>
*/
function bfb_h2bc_report_list( array $primary, array $secondary, string $key ): array {
$values = array();
if ( isset( $primary[ $key ] ) && is_array( $primary[ $key ] ) ) {
$values = $primary[ $key ];
} elseif ( isset( $secondary[ $key ] ) && is_array( $secondary[ $key ] ) ) {
$values = $secondary[ $key ];
}

return array_values(
array_filter(
array_map(
static function ( $value ): string {
return is_scalar( $value ) ? (string) $value : '';
},
$values
),
static function ( string $value ): bool {
return '' !== $value;
}
)
);
}
}

if ( ! function_exists( 'bfb_h2bc_report_array' ) ) {
/**
* Return an array field from possible report locations.
*
* @param array<string, mixed> $primary Primary report data.
* @param array<string, mixed> $secondary Secondary report data.
* @param string $key Field key.
* @return array<string, mixed>
*/
function bfb_h2bc_report_array( array $primary, array $secondary, string $key ): array {
if ( isset( $primary[ $key ] ) && is_array( $primary[ $key ] ) ) {
return $primary[ $key ];
}

if ( isset( $secondary[ $key ] ) && is_array( $secondary[ $key ] ) ) {
return $secondary[ $key ];
}

return array();
}
}

if ( ! function_exists( 'bfb_get_adapter' ) ) {
/**
* Resolve an adapter by slug.
Expand Down Expand Up @@ -864,10 +994,6 @@ function bfb_render_post( $post, string $format, array $options = array() ): str
return '';
}

if ( ! $post_obj instanceof WP_Post ) {
return '';
}

$content = (string) $post_obj->post_content;
if ( '' === $content ) {
return '';
Expand Down
9 changes: 0 additions & 9 deletions includes/class-bfb-html-adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,4 @@ public function from_blocks( array $blocks, array $options = array() ): string {
}
return $html;
}

/**
* @inheritDoc
*/
public function detect( string $content ): bool {
// Reserved for future use. v0.1.0 doesn't auto-detect.
unset( $content );
return false;
}
}
9 changes: 0 additions & 9 deletions includes/class-bfb-markdown-adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,6 @@ static function ( array $pre_match ): string {
return (string) apply_filters( 'bfb_markdown_output', $markdown, $html, $blocks );
}

/**
* @inheritDoc
*/
public function detect( string $content ): bool {
// Reserved for future use. v0.1.0 doesn't auto-detect.
unset( $content );
return false;
}

/**
* Render markdown to HTML using league/commonmark with GFM extensions.
*
Expand Down
13 changes: 0 additions & 13 deletions includes/interface-bfb-format-adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,4 @@ public function to_blocks( string $content, array $options = array() ): array;
* @return string Content in this adapter's format.
*/
public function from_blocks( array $blocks, array $options = array() ): string;

/**
* Best-effort detection of whether $content is in this format.
*
* Reserved for future use. v0.1.0 does not consult detect() from
* any production path — auto-detection is opt-in via filters and
* per-call hints. Implementations may return false until the
* detection rules are designed.
*
* @param string $content Content to test.
* @return bool
*/
public function detect( string $content ): bool;
}
24 changes: 20 additions & 4 deletions tests/BFBConversionUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,26 @@ function_exists( '\BlockFormatBridge\Vendor\html_to_blocks_raw_handler' ),
}
}

/**
* Explicit Site Editor markers should convert through the public BFB path.
*/
public function test_site_editor_markers_convert_through_bfb_convert(): void {
$pattern = $this->blocks_from( '<section data-bfb-pattern="theme/pricing-table"><h2>Pricing</h2></section>', 'html' );
$this->assertSame( 'core/pattern', $pattern[0]['blockName'] ?? null, 'Pattern marker should convert to core/pattern.' );
$this->assertSame( 'theme/pricing-table', $pattern[0]['attrs']['slug'] ?? null, 'Pattern marker should preserve the explicit slug.' );

$template_part = $this->blocks_from( '<header data-bfb-template-part="header"><h1>Site</h1></header>', 'html' );
$this->assertSame( 'core/template-part', $template_part[0]['blockName'] ?? null, 'Template-part marker should convert to core/template-part.' );
$this->assertSame( 'header', $template_part[0]['attrs']['slug'] ?? null, 'Template-part marker should preserve the explicit slug.' );
$this->assertSame( 'header', $template_part[0]['attrs']['area'] ?? null, 'Standard template-part areas should set area.' );

$invalid_pattern = $this->blocks_from( '<section data-bfb-pattern="pricing-table"><h2>Pricing</h2></section>', 'html' );
$this->assertNotSame( 'core/pattern', $invalid_pattern[0]['blockName'] ?? null, 'Invalid pattern marker should fall through to normal HTML conversion.' );

$unmarked_header = $this->blocks_from( '<header><h1>Site</h1></header>', 'html' );
$this->assertNotSame( 'core/template-part', $unmarked_header[0]['blockName'] ?? null, 'Unmarked header should not be inferred as a template part.' );
}

/**
* Conversion options should flow from the public API into adapters and h2bc args.
*/
Expand Down Expand Up @@ -199,10 +219,6 @@ public function from_blocks( array $blocks, array $options = array() ): string {
return 'probe';
}

public function detect( string $content ): bool {
unset( $content );
return false;
}
};

$adapter_filter = static function ( $adapter, string $slug ) use ( $probe ) {
Expand Down
4 changes: 2 additions & 2 deletions tests/smoke-capabilities-abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ function is_wp_error( $value ): bool {
bfb_capabilities_smoke_assert( isset( $report['formats']['html'] ), 'Capability report should expose registered adapters.' );
bfb_capabilities_smoke_assert( false === $report['formats']['html']['pivot'], 'Adapter formats should not be marked as pivot formats.' );
bfb_capabilities_smoke_assert( isset( $report['conversions']['html_to_blocks'] ), 'Capability report should expose HTML to blocks availability.' );
bfb_capabilities_smoke_assert( 'not_available' === $report['block_coverage']['source'], 'Capability report should include conservative block coverage placeholder.' );
bfb_capabilities_smoke_assert( 'h2bc#56' === $report['block_coverage']['requires'], 'Capability report should point at the h2bc inventory follow-up.' );
bfb_capabilities_smoke_assert( 'h2bc_capability_api_missing' === $report['block_coverage']['source'], 'Capability report should identify the missing h2bc capability API.' );
bfb_capabilities_smoke_assert( 'https://github.com/chubes4/html-to-blocks-converter/issues/418' === $report['block_coverage']['requires'], 'Capability report should point at the h2bc capability API dependency.' );
bfb_capabilities_smoke_assert( in_array( 'bfb_html_to_blocks_args', $report['hooks']['filters'], true ), 'Capability report should list HTML raw-handler args filter.' );
bfb_capabilities_smoke_assert( in_array( 'bfb_diagnostic', $report['hooks']['actions'], true ), 'Capability report should list observability hooks.' );
bfb_capabilities_smoke_assert( in_array( 'block-format-bridge/get-capabilities', $report['abilities'], true ), 'Capability report should list the capabilities ability.' );
Expand Down
Loading
Loading