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
6 changes: 3 additions & 3 deletions features/makepot.feature
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Feature: Generate a POT file of a WordPress project
"""
And the foo-plugin.pot file should contain:
"""
#: foo-plugin.php
#: foo-plugin.php:5
"""
And the foo-plugin.pot file should contain:
"""
Expand Down Expand Up @@ -3890,12 +3890,12 @@ Feature: Generate a POT file of a WordPress project
And the foo-theme/foo-theme.pot file should exist
And the foo-theme/foo-theme.pot file should contain:
"""
#: patterns/my-pattern.php
#: patterns/my-pattern.php:3
msgctxt "Pattern title"
msgid "My pattern title."
msgstr ""

#: patterns/my-pattern.php
#: patterns/my-pattern.php:4
msgctxt "Pattern description"
msgid "My pattern description."
msgstr ""
Expand Down
20 changes: 15 additions & 5 deletions src/FileDataExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class FileDataExtractor {
* @param string $file Path to the file.
* @param array $headers List of headers, in the format array('HeaderKey' => 'Header Name').
*
* @return array Array of file headers in `HeaderKey => Header Value` format.
* @return array Array of file headers in `HeaderKey => ['value' => Header Value, 'line' => Line Number]` format.
*/
public static function get_file_data( $file, $headers ) {
// We don't need to write to the file, so just open for reading.
Expand All @@ -42,14 +42,24 @@ public static function get_file_data( $file, $headers ) {
* @param string $text String to look for metadata in.
* @param array $headers List of headers.
*
* @return array Array of file headers in `HeaderKey => Header Value` format.
* @return array Array of file headers in `HeaderKey => ['value' => Header Value, 'line' => Line Number]` format.
*/
public static function get_file_data_from_string( $text, $headers ) {
foreach ( $headers as $field => $regex ) {
if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $text, $match ) && $match[1] ) {
$headers[ $field ] = static::_cleanup_header_comment( $match[1] );
if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $text, $match, PREG_OFFSET_CAPTURE ) && $match[1][0] ) {
$value = static::_cleanup_header_comment( $match[1][0] );

// Calculate line number from the offset
$line_num = substr_count( $text, "\n", 0, $match[0][1] ) + 1;
$headers[ $field ] = [
'value' => $value,
'line' => $line_num,
];
} else {
$headers[ $field ] = '';
$headers[ $field ] = [
'value' => '',
'line' => 0,
];
}
}

Expand Down
33 changes: 25 additions & 8 deletions src/IterableCodeExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,15 @@ public static function fromFile( $file_or_files, Translations $translations, arr
if ( ! empty( $options['wpExtractTemplates'] ) ) {
$headers = FileDataExtractor::get_file_data_from_string( $text, [ 'Template Name' => 'Template Name' ] );

if ( ! empty( $headers['Template Name'] ) ) {
$translation = new Translation( '', $headers['Template Name'] );
if ( ! empty( $headers['Template Name']['value'] ) ) {
$translation = new Translation( '', $headers['Template Name']['value'] );
$translation->addExtractedComment( 'Template Name of the theme' );

// Add file reference with line number if available
if ( ! empty( $headers['Template Name']['line'] ) ) {
$translation->addReference( $options['file'], $headers['Template Name']['line'] );
}

$translations[] = $translation;
}
}
Expand All @@ -87,16 +92,28 @@ public static function fromFile( $file_or_files, Translations $translations, arr
]
);

if ( ! empty( $headers['Title'] ) ) {
$translation = new Translation( 'Pattern title', $headers['Title'] );
$translation->addReference( $options['file'] );
if ( ! empty( $headers['Title']['value'] ) ) {
$translation = new Translation( 'Pattern title', $headers['Title']['value'] );

// Add file reference with line number if available
if ( ! empty( $headers['Title']['line'] ) ) {
$translation->addReference( $options['file'], $headers['Title']['line'] );
} else {
$translation->addReference( $options['file'] );
}

$translations[] = $translation;
}

if ( ! empty( $headers['Description'] ) ) {
$translation = new Translation( 'Pattern description', $headers['Description'] );
$translation->addReference( $options['file'] );
if ( ! empty( $headers['Description']['value'] ) ) {
$translation = new Translation( 'Pattern description', $headers['Description']['value'] );

// Add file reference with line number if available
if ( ! empty( $headers['Description']['line'] ) ) {
$translation->addReference( $options['file'], $headers['Description']['line'] );
} else {
$translation->addReference( $options['file'] );
}

$translations[] = $translation;
}
Expand Down
54 changes: 29 additions & 25 deletions src/MakePotCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ public function handle_arguments( $args, $assoc_args ) {
if ( ! $ignore_domain ) {
$this->domain = $this->slug;

if ( ! empty( $this->main_file_data['Text Domain'] ) ) {
$this->domain = $this->main_file_data['Text Domain'];
if ( ! empty( $this->main_file_data['Text Domain']['value'] ) ) {
$this->domain = $this->main_file_data['Text Domain']['value'];
}

$this->domain = Utils\get_flag_value( $assoc_args, 'domain', $this->domain );
Expand All @@ -362,12 +362,12 @@ public function handle_arguments( $args, $assoc_args ) {
// Determine destination.
$this->destination = "{$this->source}/{$this->slug}.pot";

if ( ! empty( $this->main_file_data['Domain Path'] ) ) {
if ( ! empty( $this->main_file_data['Domain Path']['value'] ) ) {
// Domain Path inside source folder.
$this->destination = sprintf(
'%s/%s/%s.pot',
$this->source,
$this->unslashit( $this->main_file_data['Domain Path'] ),
$this->unslashit( $this->main_file_data['Domain Path']['value'] ),
$this->slug
);
}
Expand Down Expand Up @@ -480,7 +480,7 @@ protected function get_main_file_data() {
);

// Stop when it contains a valid Theme Name header.
if ( ! empty( $theme_data['Theme Name'] ) ) {
if ( ! empty( $theme_data['Theme Name']['value'] ) ) {
WP_CLI::log( 'Theme stylesheet detected.' );
WP_CLI::debug( sprintf( 'Theme stylesheet: %s', $file->getRealPath() ), 'make-pot' );

Expand All @@ -502,7 +502,7 @@ protected function get_main_file_data() {
);

// Stop when it contains a valid Theme Name header.
if ( ! empty( $theme_data['Theme Name'] ) ) {
if ( ! empty( $theme_data['Theme Name']['value'] ) ) {
WP_CLI::log( 'Theme stylesheet detected.' );
WP_CLI::debug( sprintf( 'Theme stylesheet: %s', $file->getRealPath() . '/style.css' ), 'make-pot' );

Expand All @@ -524,7 +524,7 @@ protected function get_main_file_data() {
);

// Stop when we find a file with a valid Plugin Name header.
if ( ! empty( $plugin_data['Plugin Name'] ) ) {
if ( ! empty( $plugin_data['Plugin Name']['value'] ) ) {
WP_CLI::log( 'Plugin file detected.' );
WP_CLI::debug( sprintf( 'Plugin file: %s', $file->getRealPath() ), 'make-pot' );

Expand Down Expand Up @@ -616,11 +616,11 @@ protected function extract_strings() {

// Set entries from main file data.
foreach ( $this->main_file_data as $header => $data ) {
if ( empty( $data ) ) {
if ( empty( $data['value'] ) ) {
continue;
}

$translation = new Translation( '', $data );
$translation = new Translation( '', $data['value'] );

if ( $is_theme ) {
$translation->addExtractedComment( sprintf( '%s of the theme', $header ) );
Expand All @@ -629,9 +629,13 @@ protected function extract_strings() {
}

if ( $this->main_file_path && $this->location ) {
$translation->addReference(
ltrim( str_replace( Utils\normalize_path( "$this->source/" ), '', Utils\normalize_path( $this->main_file_path ) ), '/' )
);
$file_reference = ltrim( str_replace( Utils\normalize_path( "$this->source/" ), '', Utils\normalize_path( $this->main_file_path ) ), '/' );
// Add line number if available
if ( ! empty( $data['line'] ) ) {
$translation->addReference( $file_reference, $data['line'] );
} else {
$translation->addReference( $file_reference );
}
}

$translations[] = $translation;
Expand Down Expand Up @@ -920,38 +924,38 @@ protected function get_file_comment() {
}

if ( isset( $this->main_file_data['Theme Name'] ) ) {
if ( ! empty( $this->main_file_data['License'] ) ) {
if ( ! empty( $this->main_file_data['License']['value'] ) ) {
return sprintf(
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the %3\$s.",
date( 'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$this->main_file_data['Author'],
$this->main_file_data['License']
$this->main_file_data['Author']['value'],
$this->main_file_data['License']['value']
);
}

return sprintf(
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the same license as the %3\$s theme.",
date( 'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$this->main_file_data['Author'],
$this->main_file_data['Theme Name']
$this->main_file_data['Author']['value'],
$this->main_file_data['Theme Name']['value']
);
}

if ( isset( $this->main_file_data['Plugin Name'] ) ) {
if ( ! empty( $this->main_file_data['License'] ) ) {
if ( ! empty( $this->main_file_data['License']['value'] ) ) {
return sprintf(
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the %3\$s.",
date( 'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$this->main_file_data['Author'],
$this->main_file_data['License']
$this->main_file_data['Author']['value'],
$this->main_file_data['License']['value']
);
}

return sprintf(
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the same license as the %3\$s plugin.",
date( 'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$this->main_file_data['Author'],
$this->main_file_data['Plugin Name']
$this->main_file_data['Author']['value'],
$this->main_file_data['Plugin Name']['value']
);
}

Expand All @@ -969,14 +973,14 @@ protected function set_default_headers( $translations ) {
$bugs_address = null;

if ( ! $version && isset( $this->main_file_data['Version'] ) ) {
$version = $this->main_file_data['Version'];
$version = $this->main_file_data['Version']['value'];
}

if ( isset( $this->main_file_data['Theme Name'] ) ) {
$name = $this->main_file_data['Theme Name'];
$name = $this->main_file_data['Theme Name']['value'];
$bugs_address = sprintf( 'https://wordpress.org/support/theme/%s', $this->slug );
} elseif ( isset( $this->main_file_data['Plugin Name'] ) ) {
$name = $this->main_file_data['Plugin Name'];
$name = $this->main_file_data['Plugin Name']['value'];
$bugs_address = sprintf( 'https://wordpress.org/support/plugin/%s', $this->slug );
}

Expand Down
126 changes: 126 additions & 0 deletions tests/FileDataExtractorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace WP_CLI\I18n\Tests;

use WP_CLI\I18n\FileDataExtractor;
use WP_CLI\Tests\TestCase;

class FileDataExtractorTest extends TestCase {
public function test_extracts_headers_with_line_numbers() {
$text = <<<'TEXT'
<?php
/**
* Plugin Name: My Plugin
* Description: A test plugin
* Version: 1.0.0
*/
TEXT;

$headers = FileDataExtractor::get_file_data_from_string(
$text,
[
'Plugin Name' => 'Plugin Name',
'Description' => 'Description',
'Version' => 'Version',
]
);

$this->assertIsArray( $headers['Plugin Name'] );
$this->assertEquals( 'My Plugin', $headers['Plugin Name']['value'] );
$this->assertEquals( 3, $headers['Plugin Name']['line'] );

$this->assertIsArray( $headers['Description'] );
$this->assertEquals( 'A test plugin', $headers['Description']['value'] );
$this->assertEquals( 4, $headers['Description']['line'] );

$this->assertIsArray( $headers['Version'] );
$this->assertEquals( '1.0.0', $headers['Version']['value'] );
$this->assertEquals( 5, $headers['Version']['line'] );
}

public function test_line_numbers_with_different_line_endings() {
// Test with different line positions
$text = "<?php\n\n\n/**\n * Plugin Name: Test Plugin\n * Description: Description here\n */";

$headers = FileDataExtractor::get_file_data_from_string(
$text,
[
'Plugin Name' => 'Plugin Name',
'Description' => 'Description',
]
);

$this->assertEquals( 'Test Plugin', $headers['Plugin Name']['value'] );
$this->assertEquals( 5, $headers['Plugin Name']['line'] );

$this->assertEquals( 'Description here', $headers['Description']['value'] );
$this->assertEquals( 6, $headers['Description']['line'] );
}

public function test_empty_headers_with_line_numbers() {
$text = '<?php // No headers here';

$headers = FileDataExtractor::get_file_data_from_string(
$text,
[
'Plugin Name' => 'Plugin Name',
]
);

$this->assertIsArray( $headers['Plugin Name'] );
$this->assertEquals( '', $headers['Plugin Name']['value'] );
$this->assertEquals( 0, $headers['Plugin Name']['line'] );
}

public function test_theme_headers_with_line_numbers() {
$text = <<<'TEXT'
/*
Theme Name: My Theme
Description: A beautiful theme
Author: John Doe
Version: 2.0.0
*/
TEXT;

$headers = FileDataExtractor::get_file_data_from_string(
$text,
[
'Theme Name' => 'Theme Name',
'Description' => 'Description',
'Author' => 'Author',
'Version' => 'Version',
]
);

$this->assertEquals( 'My Theme', $headers['Theme Name']['value'] );
$this->assertEquals( 2, $headers['Theme Name']['line'] );

$this->assertEquals( 'A beautiful theme', $headers['Description']['value'] );
$this->assertEquals( 3, $headers['Description']['line'] );

$this->assertEquals( 'John Doe', $headers['Author']['value'] );
$this->assertEquals( 4, $headers['Author']['line'] );

$this->assertEquals( '2.0.0', $headers['Version']['value'] );
$this->assertEquals( 5, $headers['Version']['line'] );
}

public function test_header_with_trailing_comment() {
$text = <<<'TEXT'
<?php
/**
* Plugin Name: My Plugin */
*/
TEXT;

$headers = FileDataExtractor::get_file_data_from_string(
$text,
[
'Plugin Name' => 'Plugin Name',
]
);

$this->assertEquals( 'My Plugin', $headers['Plugin Name']['value'] );
$this->assertEquals( 3, $headers['Plugin Name']['line'] );
}
}
Loading