Skip to content

Commit

Permalink
Merge pull request #304 from strarsis/add-blade-extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy authored Mar 13, 2022
2 parents fdc6e15 + c3ab287 commit e506287
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 4 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
}
],
"require": {
"eftec/bladeone": "3.52",
"gettext/gettext": "^4.8",
"mck89/peast": "^1.13.11",
"wp-cli/wp-cli": "^2.5"
Expand Down
106 changes: 106 additions & 0 deletions features/makepot.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2911,6 +2911,112 @@ Feature: Generate a POT file of a WordPress project
msgid "Bar"
"""

@blade
Scenario: Extract strings from a Blade-PHP file in a theme (ignoring domains)
Given an empty foo-theme directory
And a foo-theme/style.css file:
"""
/*
Theme Name: Foo Theme
Theme URI: https://example.com
Description:
Author:
Author URI:
Version: 0.1.0
License: GPL-2.0+
Text Domain: foo-theme
*/
"""
And a foo-theme/stuff.blade.php file:
"""
@php
__('Test');
@endphp
@extends('layouts.app')
@php(__('Another test.', 'some-other-domain'))
@section('content')
@include('partials.page-header')
@if (! have_posts())
<x-alert type="warning">
{!! __('Page not found.', 'foo-theme') !!}
</x-alert>
{!! get_search_form(false) !!}
@endif
@endsection
"""

When I try `wp i18n make-pot foo-theme result.pot --ignore-domain --debug`
Then STDOUT should be:
"""
Theme stylesheet detected.
Success: POT file successfully generated!
"""
And the result.pot file should contain:
"""
msgid "Test"
"""
And the result.pot file should contain:
"""
msgid "Page not found."
"""
And the result.pot file should contain:
"""
msgid "Another test."
"""

@blade
Scenario: Extract strings from a Blade-PHP file in a theme
Given an empty foo-theme directory
And a foo-theme/style.css file:
"""
/*
Theme Name: Foo Theme
Theme URI: https://example.com
Description:
Author:
Author URI:
Version: 0.1.0
License: GPL-2.0+
Text Domain: foo-theme
*/
"""
And a foo-theme/stuff.blade.php file:
"""
@php
__('Test');
@endphp
@extends('layouts.app')
@php(__('Another test.', 'some-other-domain'))
@section('content')
@include('partials.page-header')
@if (! have_posts())
<x-alert type="warning">
{!! __('Page not found.', 'foo-theme') !!}
</x-alert>
{!! get_search_form(false) !!}
@endif
@endsection
"""

When I try `wp i18n make-pot foo-theme result.pot --debug`
Then STDOUT should be:
"""
Theme stylesheet detected.
Success: POT file successfully generated!
"""
And the result.pot file should contain:
"""
msgid "Page not found."
"""

Scenario: Custom package name
Given an empty example-project directory
And a example-project/stuff.php file:
Expand Down
66 changes: 66 additions & 0 deletions src/BladeCodeExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace WP_CLI\I18n;

use Exception;
use Gettext\Translations;
use WP_CLI;

final class BladeCodeExtractor extends BladeGettextExtractor {
use IterableCodeExtractor;

public static $options = [
'extractComments' => [ 'translators', 'Translators' ],
'constants' => [],
'functions' => [
'__' => 'text_domain',
'esc_attr__' => 'text_domain',
'esc_html__' => 'text_domain',
'esc_xml__' => 'text_domain',
'_e' => 'text_domain',
'esc_attr_e' => 'text_domain',
'esc_html_e' => 'text_domain',
'esc_xml_e' => 'text_domain',
'_x' => 'text_context_domain',
'_ex' => 'text_context_domain',
'esc_attr_x' => 'text_context_domain',
'esc_html_x' => 'text_context_domain',
'esc_xml_x' => 'text_context_domain',
'_n' => 'single_plural_number_domain',
'_nx' => 'single_plural_number_context_domain',
'_n_noop' => 'single_plural_domain',
'_nx_noop' => 'single_plural_context_domain',

// Compat.
'_' => 'gettext', // Same as 'text_domain'.

// Deprecated.
'_c' => 'text_domain',
'_nc' => 'single_plural_number_domain',
'__ngettext' => 'single_plural_number_domain',
'__ngettext_noop' => 'single_plural_domain',
],
];

protected static $functionsScannerClass = 'WP_CLI\I18n\PhpFunctionsScanner';

/**
* {@inheritdoc}
*/
public static function fromString( $string, Translations $translations, array $options = [] ) {
WP_CLI::debug( "Parsing file {$options['file']}", 'make-pot' );

try {
static::fromStringMultiple( $string, [ $translations ], $options );
} catch ( Exception $exception ) {
WP_CLI::debug(
sprintf(
'Could not parse file %1$s: %2$s',
$options['file'],
$exception->getMessage()
),
'make-pot'
);
}
}
}
51 changes: 51 additions & 0 deletions src/BladeGettextExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace WP_CLI\I18n;

use eftec\bladeone\BladeOne;

// Modified Gettext Blade extractor that
// uses the up-to-date BladeOne standalone Blade engine,
// correctly supports fromStringMultiple.

/**
* Class to get gettext strings from blade.php files returning arrays.
*/
class BladeGettextExtractor extends \Gettext\Extractors\PhpCode {

/**
* Prepares a Blade compiler/engine and returns it.
*
* @return BladeOne
*/
protected static function getBladeCompiler() {
$cache_path = empty( $options['cachePath'] ) ? sys_get_temp_dir() : $options['cachePath'];
$blade_compiler = new BladeOne( null, $cache_path );

if ( method_exists( $blade_compiler, 'withoutComponentTags' ) ) {
$blade_compiler->withoutComponentTags();
}

return $blade_compiler;
}

/**
* Compiles the Blade template string into a PHP string in one step.
*
* @param string $string Blade string to be compiled to a PHP string
* @return string
*/
protected static function compileBladeToPhp( $string ) {
return static::getBladeCompiler()->compileString( $string );
}

/**
* {@inheritdoc}
*
* Note: In the parent PhpCode class fromString() uses fromStringMultiple() (overriden here)
*/
public static function fromStringMultiple( $string, array $translations, array $options = [] ) {
$php_string = static::compileBladeToPhp( $string );
return parent::fromStringMultiple( $php_string, $translations, $options );
}
}
35 changes: 33 additions & 2 deletions src/IterableCodeExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ static function ( $file, $key, $iterator ) use ( $include, $exclude, $extensions
return true;
}

if ( ! $file->isFile() || ! in_array( $file->getExtension(), $extensions, true ) ) {
if ( ! $file->isFile() || ! static::file_has_file_extension( $file, $extensions ) ) {
return false;
}

Expand All @@ -250,7 +250,7 @@ static function ( $file, $key, $iterator ) use ( $include, $exclude, $extensions

foreach ( $files as $file ) {
/** @var SplFileInfo $file */
if ( ! $file->isFile() || ! in_array( $file->getExtension(), $extensions, true ) ) {
if ( ! $file->isFile() || ! static::file_has_file_extension( $file, $extensions ) ) {
continue;
}

Expand All @@ -262,6 +262,37 @@ static function ( $file, $key, $iterator ) use ( $include, $exclude, $extensions
return $filtered_files;
}

/**
* Determines whether the file extension of a file matches any of the given file extensions.
* The end/last part of a multi file extension must also match (`js` of `min.js`).
*
* @param SplFileInfo $file File or directory.
* @param array $extensions List of file extensions to match.
* @return bool Whether the file has a file extension that matches any of the ones in the list.
*/
private static function file_has_file_extension( $file, $extensions ) {
return in_array( $file->getExtension(), $extensions, true ) ||
in_array( static::file_get_extension_multi( $file ), $extensions, true );
}

/**
* Gets the single- (e.g. `php`) or multi-file extension (e.g. `blade.php`) of a file.
*
* @param SplFileInfo $file File or directory.
* @return string The single- or multi-file extension of the file.
*/
private static function file_get_extension_multi( $file ) {
$file_extension_separator = '.';

$filename = $file->getFilename();
$parts = explode( $file_extension_separator, $filename, 2 );
if ( count( $parts ) <= 1 ) {
// if ever something goes wrong, fall back to SPL
return $file->getExtension();
}
return $parts[1];
}

/**
* Trim leading slash from a path.
*
Expand Down
21 changes: 20 additions & 1 deletion src/MakePotCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class MakePotCommand extends WP_CLI_Command {
*/
protected $skip_php = false;

/**
* @var bool
*/
protected $skip_blade = false;

/**
* @var bool
*/
Expand Down Expand Up @@ -163,7 +168,7 @@ class MakePotCommand extends WP_CLI_Command {
/**
* Create a POT file for a WordPress project.
*
* Scans PHP and JavaScript files for translatable strings, as well as theme stylesheets and plugin files
* Scans PHP, Blade-PHP and JavaScript files for translatable strings, as well as theme stylesheets and plugin files
* if the source directory is detected as either a plugin or theme.
*
* ## OPTIONS
Expand Down Expand Up @@ -227,6 +232,9 @@ class MakePotCommand extends WP_CLI_Command {
* [--skip-php]
* : Skips PHP string extraction.
*
* [--skip-blade]
* : Skips Blade-PHP string extraction.
*
* [--skip-block-json]
* : Skips string extraction from block.json files.
*
Expand Down Expand Up @@ -311,6 +319,7 @@ public function handle_arguments( $args, $assoc_args ) {
$this->slug = Utils\get_flag_value( $assoc_args, 'slug', Utils\basename( $this->source ) );
$this->skip_js = Utils\get_flag_value( $assoc_args, 'skip-js', $this->skip_js );
$this->skip_php = Utils\get_flag_value( $assoc_args, 'skip-php', $this->skip_php );
$this->skip_blade = Utils\get_flag_value( $assoc_args, 'skip-blade', $this->skip_blade );
$this->skip_block_json = Utils\get_flag_value( $assoc_args, 'skip-block-json', $this->skip_block_json );
$this->skip_theme_json = Utils\get_flag_value( $assoc_args, 'skip-theme-json', $this->skip_theme_json );
$this->skip_audit = Utils\get_flag_value( $assoc_args, 'skip-audit', $this->skip_audit );
Expand Down Expand Up @@ -609,6 +618,16 @@ protected function extract_strings() {
PhpCodeExtractor::fromDirectory( $this->source, $translations, $options );
}

if ( ! $this->skip_blade ) {
$options = [
'include' => $this->include,
'exclude' => $this->exclude,
'extensions' => [ 'blade.php' ],
'addReferences' => $this->location,
];
BladeCodeExtractor::fromDirectory( $this->source, $translations, $options );
}

if ( ! $this->skip_js ) {
JsCodeExtractor::fromDirectory(
$this->source,
Expand Down
Loading

0 comments on commit e506287

Please sign in to comment.